1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <ctype.h>
18 #include <dirent.h>
19 #include <errno.h>
20 #include <signal.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <unistd.h>
25 
26 #define MAX_LINE 512
27 #define MAX_FILENAME 64
28 
29 const char *EXPECTED_VERSION = "Latency Top version : v0.1\n";
30 const char *SYSCTL_FILE = "/proc/sys/kernel/latencytop";
31 const char *GLOBAL_STATS_FILE = "/proc/latency_stats";
32 const char *THREAD_STATS_FILE_FORMAT = "/proc/%d/task/%d/latency";
33 
34 struct latency_entry {
35     struct latency_entry *next;
36     unsigned long count;
37     unsigned long max;
38     unsigned long total;
39     char reason[MAX_LINE];
40 };
41 
check_latencytop()42 static inline void check_latencytop() { }
43 
44 static struct latency_entry *read_global_stats(struct latency_entry *list, int erase);
45 static struct latency_entry *read_process_stats(struct latency_entry *list, int erase, int pid);
46 static struct latency_entry *read_thread_stats(struct latency_entry *list, int erase, int pid, int tid, int fatal);
47 
48 static struct latency_entry *alloc_latency_entry(void);
49 static void free_latency_entry(struct latency_entry *e);
50 
51 static void set_latencytop(int on);
52 static struct latency_entry *read_latency_file(FILE *f, struct latency_entry *list);
53 static void erase_latency_file(FILE *f);
54 
55 static struct latency_entry *find_latency_entry(struct latency_entry *e, char *reason);
56 static void print_latency_entries(struct latency_entry *head);
57 
58 static void signal_handler(int sig);
59 static void disable_latencytop(void);
60 
61 static int numcmp(const long long a, const long long b);
62 static int lat_cmp(const void *a, const void *b);
63 
64 static void clear_screen(void);
65 static void usage(const char *cmd);
66 
67 struct latency_entry *free_entries;
68 
main(int argc,char * argv[])69 int main(int argc, char *argv[]) {
70     struct latency_entry *e;
71     int delay, iterations;
72     int pid, tid;
73     int count, erase;
74     int i;
75 
76     delay = 1;
77     iterations = 0;
78     pid = tid = 0;
79 
80     for (i = 1; i < argc; i++) {
81         if (!strcmp(argv[i], "-d")) {
82             if (i >= argc - 1) {
83                 fprintf(stderr, "Option -d expects an argument.\n");
84                 exit(EXIT_FAILURE);
85             }
86             delay = atoi(argv[++i]);
87             continue;
88         }
89         if (!strcmp(argv[i], "-n")) {
90             if (i >= argc - 1) {
91                 fprintf(stderr, "Option -n expects an argument.\n");
92                 exit(EXIT_FAILURE);
93             }
94             iterations = atoi(argv[++i]);
95             continue;
96         }
97         if (!strcmp(argv[i], "-h")) {
98             usage(argv[0]);
99             exit(EXIT_SUCCESS);
100         }
101         if (!strcmp(argv[i], "-p")) {
102             if (i >= argc - 1) {
103                 fprintf(stderr, "Option -p expects an argument.\n");
104                 exit(EXIT_FAILURE);
105             }
106             pid = atoi(argv[++i]);
107             continue;
108         }
109         if (!strcmp(argv[i], "-t")) {
110             if (i >= argc - 1) {
111                 fprintf(stderr, "Option -t expects an argument.\n");
112                 exit(EXIT_FAILURE);
113             }
114             tid = atoi(argv[++i]);
115             continue;
116         }
117         fprintf(stderr, "Invalid argument \"%s\".\n", argv[i]);
118         usage(argv[0]);
119         exit(EXIT_FAILURE);
120     }
121 
122     if (tid && !pid) {
123         fprintf(stderr, "If you provide a thread ID with -t, you must provide a process ID with -p.\n");
124         exit(EXIT_FAILURE);
125     }
126 
127     check_latencytop();
128 
129     free_entries = NULL;
130 
131     signal(SIGINT, &signal_handler);
132     signal(SIGTERM, &signal_handler);
133 
134     atexit(&disable_latencytop);
135 
136     set_latencytop(1);
137 
138     count = 0;
139     erase = 1;
140 
141     while ((iterations == 0) || (count++ < iterations)) {
142 
143         sleep(delay);
144 
145         e = NULL;
146         if (pid) {
147             if (tid) {
148                 e = read_thread_stats(e, erase, pid, tid, 1);
149             } else {
150                 e = read_process_stats(e, erase, pid);
151             }
152         } else {
153             e = read_global_stats(e, erase);
154         }
155         erase = 0;
156 
157         clear_screen();
158         if (pid) {
159             if (tid) {
160                 printf("Latencies for thread %d in process %d:\n", tid, pid);
161             } else {
162                 printf("Latencies for process %d:\n", pid);
163             }
164         } else {
165             printf("Latencies across all processes:\n");
166         }
167         print_latency_entries(e);
168     }
169 
170     set_latencytop(0);
171 
172     return 0;
173 }
174 
read_global_stats(struct latency_entry * list,int erase)175 static struct latency_entry *read_global_stats(struct latency_entry *list, int erase) {
176     FILE *f;
177     struct latency_entry *e;
178 
179     if (erase) {
180         f = fopen(GLOBAL_STATS_FILE, "w");
181         if (!f) {
182             fprintf(stderr, "Could not open global latency stats file: %s\n", strerror(errno));
183             exit(EXIT_FAILURE);
184         }
185         fprintf(f, "erase\n");
186         fclose(f);
187     }
188 
189     f = fopen(GLOBAL_STATS_FILE, "r");
190     if (!f) {
191         fprintf(stderr, "Could not open global latency stats file: %s\n", strerror(errno));
192         exit(EXIT_FAILURE);
193     }
194 
195     e = read_latency_file(f, list);
196 
197     fclose(f);
198 
199     return e;
200 }
201 
read_process_stats(struct latency_entry * list,int erase,int pid)202 static struct latency_entry *read_process_stats(struct latency_entry *list, int erase, int pid) {
203     char dirname[MAX_FILENAME];
204     DIR *dir;
205     struct dirent *ent;
206     struct latency_entry *e;
207     int tid;
208 
209     sprintf(dirname, "/proc/%d/task", pid);
210     dir = opendir(dirname);
211     if (!dir) {
212         fprintf(stderr, "Could not open task dir for process %d.\n", pid);
213         fprintf(stderr, "Perhaps the process has terminated?\n");
214         exit(EXIT_FAILURE);
215     }
216 
217     e = list;
218     while ((ent = readdir(dir))) {
219         if (!isdigit(ent->d_name[0]))
220             continue;
221 
222         tid = atoi(ent->d_name);
223 
224         e = read_thread_stats(e, erase, pid, tid, 0);
225     }
226 
227     closedir(dir);
228 
229     return e;
230 }
231 
read_thread_stats(struct latency_entry * list,int erase,int pid,int tid,int fatal)232 static struct latency_entry *read_thread_stats(struct latency_entry *list, int erase, int pid, int tid, int fatal) {
233     char filename[MAX_FILENAME];
234     FILE *f;
235     struct latency_entry *e;
236 
237     sprintf(filename, THREAD_STATS_FILE_FORMAT, pid, tid);
238 
239     if (erase) {
240         f = fopen(filename, "w");
241         if (!f) {
242             if (fatal) {
243                 fprintf(stderr, "Could not open %s: %s\n", filename, strerror(errno));
244                 fprintf(stderr, "Perhaps the process or thread has terminated?\n");
245                 exit(EXIT_FAILURE);
246             } else {
247                 return list;
248             }
249         }
250         fprintf(f, "erase\n");
251         fclose(f);
252     }
253 
254     f = fopen(GLOBAL_STATS_FILE, "r");
255     if (!f) {
256         if (fatal) {
257             fprintf(stderr, "Could not open %s: %s\n", filename, strerror(errno));
258             fprintf(stderr, "Perhaps the process or thread has terminated?\n");
259             exit(EXIT_FAILURE);
260         } else {
261             return list;
262         }
263     }
264 
265     e = read_latency_file(f, list);
266 
267     fclose(f);
268 
269     return e;
270 }
271 
alloc_latency_entry(void)272 static struct latency_entry *alloc_latency_entry(void) {
273     struct latency_entry *e;
274 
275     if (free_entries) {
276         e = free_entries;
277         free_entries = free_entries->next;
278     } else {
279         e = calloc(1, sizeof(struct latency_entry));
280         if (!e) {
281             fprintf(stderr, "Could not allocate latency entry: %s\n", strerror(errno));
282             exit(EXIT_FAILURE);
283         }
284     }
285 
286     return e;
287 }
288 
free_latency_entry(struct latency_entry * e)289 static void free_latency_entry(struct latency_entry *e) {
290     e->next = free_entries;
291     free_entries = e;
292 }
293 
find_latency_entry(struct latency_entry * head,char * reason)294 static struct latency_entry *find_latency_entry(struct latency_entry *head, char *reason) {
295     struct latency_entry *e;
296 
297     e = head;
298 
299     while (e) {
300         if (!strcmp(e->reason, reason))
301             return e;
302         e = e->next;
303     }
304 
305     return NULL;
306 }
307 
set_latencytop(int on)308 static void set_latencytop(int on) {
309     FILE *f;
310 
311     f = fopen(SYSCTL_FILE, "w");
312     if (!f) {
313         fprintf(stderr, "Could not open %s: %s\n", SYSCTL_FILE, strerror(errno));
314         exit(EXIT_FAILURE);
315     }
316 
317     fprintf(f, "%d\n", on);
318 
319     fclose(f);
320 }
321 
erase_latency_file(FILE * f)322 static void erase_latency_file(FILE *f) {
323     fprintf(f, "erase\n");
324 }
325 
read_latency_file(FILE * f,struct latency_entry * list)326 static struct latency_entry *read_latency_file(FILE *f, struct latency_entry *list) {
327     struct latency_entry *e, *head;
328     char line[MAX_LINE];
329     unsigned long count, max, total;
330     char reason[MAX_LINE];
331 
332     head = list;
333 
334     if (!fgets(line, MAX_LINE, f)) {
335         fprintf(stderr, "Could not read latency file version: %s\n", strerror(errno));
336         exit(EXIT_FAILURE);
337     }
338 
339     if (strcmp(line, EXPECTED_VERSION) != 0) {
340         fprintf(stderr, "Expected version: %s\n", EXPECTED_VERSION);
341         fprintf(stderr, "But got version: %s", line);
342         exit(EXIT_FAILURE);
343     }
344 
345     while (fgets(line, MAX_LINE, f)) {
346         sscanf(line, "%ld %ld %ld %s", &count, &total, &max, reason);
347         if (max > 0 || total > 0) {
348             e = find_latency_entry(head, reason);
349             if (e) {
350                 e->count += count;
351                 if (max > e->max)
352                     e->max = max;
353                 e->total += total;
354             } else {
355                 e = alloc_latency_entry();
356                 e->count = count;
357                 e->max = max;
358                 e->total = total;
359                 strcpy(e->reason, reason);
360                 e->next = head;
361                 head = e;
362             }
363         }
364     }
365 
366     return head;
367 }
368 
print_latency_entries(struct latency_entry * head)369 static void print_latency_entries(struct latency_entry *head) {
370     struct latency_entry *e, **array;
371     unsigned long average;
372     int i, count;
373 
374     e = head;
375     count = 0;
376     while (e) {
377         count++;
378         e = e->next;
379     }
380 
381     e = head;
382     array = calloc(count, sizeof(struct latency_entry *));
383     if (!array) {
384         fprintf(stderr, "Error allocating array: %s\n", strerror(errno));
385         exit(EXIT_FAILURE);
386     }
387     for (i = 0; i < count; i++) {
388         array[i] = e;
389         e = e->next;
390     }
391 
392     qsort(array, count, sizeof(struct latency_entry *), &lat_cmp);
393 
394     printf("%10s  %10s  %7s  %s\n", "Maximum", "Average", "Count", "Reason");
395     for (i = 0; i < count; i++) {
396         e = array[i];
397         average = e->total / e->count;
398         printf("%4lu.%02lu ms  %4lu.%02lu ms  %7ld  %s\n",
399             e->max / 1000, (e->max % 1000) / 10,
400             average / 1000, (average % 1000) / 10,
401             e->count,
402             e->reason);
403     }
404 
405     free(array);
406 }
407 
signal_handler(int sig)408 static void signal_handler(int sig) {
409     exit(EXIT_SUCCESS);
410 }
411 
disable_latencytop(void)412 static void disable_latencytop(void) {
413     set_latencytop(0);
414 }
415 
clear_screen(void)416 static void clear_screen(void) {
417     printf("\n\n");
418 }
419 
usage(const char * cmd)420 static void usage(const char *cmd) {
421     fprintf(stderr, "Usage: %s [ -d delay ] [ -n iterations ] [ -p pid [ -t tid ] ] [ -h ]\n"
422                     "    -d delay       Time to sleep between updates.\n"
423                     "    -n iterations  Number of updates to show (0 = infinite).\n"
424                     "    -p pid         Process to monitor (default is all).\n"
425                     "    -t tid         Thread (within specified process) to monitor (default is all).\n"
426                     "    -h             Display this help screen.\n",
427         cmd);
428 }
429 
numcmp(const long long a,const long long b)430 static int numcmp(const long long a, const long long b) {
431     if (a < b) return -1;
432     if (a > b) return 1;
433     return 0;
434 }
435 
lat_cmp(const void * a,const void * b)436 static int lat_cmp(const void *a, const void *b) {
437     const struct latency_entry *pa, *pb;
438 
439     pa = (*((struct latency_entry **)a));
440     pb = (*((struct latency_entry **)b));
441 
442     return numcmp(pb->max, pa->max);
443 }
444