| /* |
| * Copyright (C) 2008 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include <ctype.h> |
| #include <dirent.h> |
| #include <errno.h> |
| #include <signal.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| |
| #define MAX_LINE 512 |
| #define MAX_FILENAME 64 |
| |
| const char *EXPECTED_VERSION = "Latency Top version : v0.1\n"; |
| const char *SYSCTL_FILE = "/proc/sys/kernel/latencytop"; |
| const char *GLOBAL_STATS_FILE = "/proc/latency_stats"; |
| const char *THREAD_STATS_FILE_FORMAT = "/proc/%d/task/%d/latency"; |
| |
| struct latency_entry { |
| struct latency_entry *next; |
| unsigned long count; |
| unsigned long max; |
| unsigned long total; |
| char reason[MAX_LINE]; |
| }; |
| |
| static inline void check_latencytop() { } |
| |
| static struct latency_entry *read_global_stats(struct latency_entry *list, int erase); |
| static struct latency_entry *read_process_stats(struct latency_entry *list, int erase, int pid); |
| static struct latency_entry *read_thread_stats(struct latency_entry *list, int erase, int pid, int tid, int fatal); |
| |
| static struct latency_entry *alloc_latency_entry(void); |
| static void free_latency_entry(struct latency_entry *e); |
| |
| static void set_latencytop(int on); |
| static struct latency_entry *read_latency_file(FILE *f, struct latency_entry *list); |
| static void erase_latency_file(FILE *f); |
| |
| static struct latency_entry *find_latency_entry(struct latency_entry *e, char *reason); |
| static void print_latency_entries(struct latency_entry *head); |
| |
| static void signal_handler(int sig); |
| static void disable_latencytop(void); |
| |
| static int numcmp(const long long a, const long long b); |
| static int lat_cmp(const void *a, const void *b); |
| |
| static void clear_screen(void); |
| static void usage(const char *cmd); |
| |
| struct latency_entry *free_entries; |
| |
| int main(int argc, char *argv[]) { |
| struct latency_entry *e; |
| int delay, iterations; |
| int pid, tid; |
| int count, erase; |
| int i; |
| |
| delay = 1; |
| iterations = 0; |
| pid = tid = 0; |
| |
| for (i = 1; i < argc; i++) { |
| if (!strcmp(argv[i], "-d")) { |
| if (i >= argc - 1) { |
| fprintf(stderr, "Option -d expects an argument.\n"); |
| exit(EXIT_FAILURE); |
| } |
| delay = atoi(argv[++i]); |
| continue; |
| } |
| if (!strcmp(argv[i], "-n")) { |
| if (i >= argc - 1) { |
| fprintf(stderr, "Option -n expects an argument.\n"); |
| exit(EXIT_FAILURE); |
| } |
| iterations = atoi(argv[++i]); |
| continue; |
| } |
| if (!strcmp(argv[i], "-h")) { |
| usage(argv[0]); |
| exit(EXIT_SUCCESS); |
| } |
| if (!strcmp(argv[i], "-p")) { |
| if (i >= argc - 1) { |
| fprintf(stderr, "Option -p expects an argument.\n"); |
| exit(EXIT_FAILURE); |
| } |
| pid = atoi(argv[++i]); |
| continue; |
| } |
| if (!strcmp(argv[i], "-t")) { |
| if (i >= argc - 1) { |
| fprintf(stderr, "Option -t expects an argument.\n"); |
| exit(EXIT_FAILURE); |
| } |
| tid = atoi(argv[++i]); |
| continue; |
| } |
| fprintf(stderr, "Invalid argument \"%s\".\n", argv[i]); |
| usage(argv[0]); |
| exit(EXIT_FAILURE); |
| } |
| |
| if (tid && !pid) { |
| fprintf(stderr, "If you provide a thread ID with -t, you must provide a process ID with -p.\n"); |
| exit(EXIT_FAILURE); |
| } |
| |
| check_latencytop(); |
| |
| free_entries = NULL; |
| |
| signal(SIGINT, &signal_handler); |
| signal(SIGTERM, &signal_handler); |
| |
| atexit(&disable_latencytop); |
| |
| set_latencytop(1); |
| |
| count = 0; |
| erase = 1; |
| |
| while ((iterations == 0) || (count++ < iterations)) { |
| |
| sleep(delay); |
| |
| e = NULL; |
| if (pid) { |
| if (tid) { |
| e = read_thread_stats(e, erase, pid, tid, 1); |
| } else { |
| e = read_process_stats(e, erase, pid); |
| } |
| } else { |
| e = read_global_stats(e, erase); |
| } |
| erase = 0; |
| |
| clear_screen(); |
| if (pid) { |
| if (tid) { |
| printf("Latencies for thread %d in process %d:\n", tid, pid); |
| } else { |
| printf("Latencies for process %d:\n", pid); |
| } |
| } else { |
| printf("Latencies across all processes:\n"); |
| } |
| print_latency_entries(e); |
| } |
| |
| set_latencytop(0); |
| |
| return 0; |
| } |
| |
| static struct latency_entry *read_global_stats(struct latency_entry *list, int erase) { |
| FILE *f; |
| struct latency_entry *e; |
| |
| if (erase) { |
| f = fopen(GLOBAL_STATS_FILE, "w"); |
| if (!f) { |
| fprintf(stderr, "Could not open global latency stats file: %s\n", strerror(errno)); |
| exit(EXIT_FAILURE); |
| } |
| fprintf(f, "erase\n"); |
| fclose(f); |
| } |
| |
| f = fopen(GLOBAL_STATS_FILE, "r"); |
| if (!f) { |
| fprintf(stderr, "Could not open global latency stats file: %s\n", strerror(errno)); |
| exit(EXIT_FAILURE); |
| } |
| |
| e = read_latency_file(f, list); |
| |
| fclose(f); |
| |
| return e; |
| } |
| |
| static struct latency_entry *read_process_stats(struct latency_entry *list, int erase, int pid) { |
| char dirname[MAX_FILENAME]; |
| DIR *dir; |
| struct dirent *ent; |
| struct latency_entry *e; |
| int tid; |
| |
| sprintf(dirname, "/proc/%d/task", pid); |
| dir = opendir(dirname); |
| if (!dir) { |
| fprintf(stderr, "Could not open task dir for process %d.\n", pid); |
| fprintf(stderr, "Perhaps the process has terminated?\n"); |
| exit(EXIT_FAILURE); |
| } |
| |
| e = list; |
| while ((ent = readdir(dir))) { |
| if (!isdigit(ent->d_name[0])) |
| continue; |
| |
| tid = atoi(ent->d_name); |
| |
| e = read_thread_stats(e, erase, pid, tid, 0); |
| } |
| |
| closedir(dir); |
| |
| return e; |
| } |
| |
| static struct latency_entry *read_thread_stats(struct latency_entry *list, int erase, int pid, int tid, int fatal) { |
| char filename[MAX_FILENAME]; |
| FILE *f; |
| struct latency_entry *e; |
| |
| sprintf(filename, THREAD_STATS_FILE_FORMAT, pid, tid); |
| |
| if (erase) { |
| f = fopen(filename, "w"); |
| if (!f) { |
| if (fatal) { |
| fprintf(stderr, "Could not open %s: %s\n", filename, strerror(errno)); |
| fprintf(stderr, "Perhaps the process or thread has terminated?\n"); |
| exit(EXIT_FAILURE); |
| } else { |
| return list; |
| } |
| } |
| fprintf(f, "erase\n"); |
| fclose(f); |
| } |
| |
| f = fopen(GLOBAL_STATS_FILE, "r"); |
| if (!f) { |
| if (fatal) { |
| fprintf(stderr, "Could not open %s: %s\n", filename, strerror(errno)); |
| fprintf(stderr, "Perhaps the process or thread has terminated?\n"); |
| exit(EXIT_FAILURE); |
| } else { |
| return list; |
| } |
| } |
| |
| e = read_latency_file(f, list); |
| |
| fclose(f); |
| |
| return e; |
| } |
| |
| static struct latency_entry *alloc_latency_entry(void) { |
| struct latency_entry *e; |
| |
| if (free_entries) { |
| e = free_entries; |
| free_entries = free_entries->next; |
| } else { |
| e = calloc(1, sizeof(struct latency_entry)); |
| if (!e) { |
| fprintf(stderr, "Could not allocate latency entry: %s\n", strerror(errno)); |
| exit(EXIT_FAILURE); |
| } |
| } |
| |
| return e; |
| } |
| |
| static void free_latency_entry(struct latency_entry *e) { |
| e->next = free_entries; |
| free_entries = e; |
| } |
| |
| static struct latency_entry *find_latency_entry(struct latency_entry *head, char *reason) { |
| struct latency_entry *e; |
| |
| e = head; |
| |
| while (e) { |
| if (!strcmp(e->reason, reason)) |
| return e; |
| e = e->next; |
| } |
| |
| return NULL; |
| } |
| |
| static void set_latencytop(int on) { |
| FILE *f; |
| |
| f = fopen(SYSCTL_FILE, "w"); |
| if (!f) { |
| fprintf(stderr, "Could not open %s: %s\n", SYSCTL_FILE, strerror(errno)); |
| exit(EXIT_FAILURE); |
| } |
| |
| fprintf(f, "%d\n", on); |
| |
| fclose(f); |
| } |
| |
| static void erase_latency_file(FILE *f) { |
| fprintf(f, "erase\n"); |
| } |
| |
| static struct latency_entry *read_latency_file(FILE *f, struct latency_entry *list) { |
| struct latency_entry *e, *head; |
| char line[MAX_LINE]; |
| unsigned long count, max, total; |
| char reason[MAX_LINE]; |
| |
| head = list; |
| |
| if (!fgets(line, MAX_LINE, f)) { |
| fprintf(stderr, "Could not read latency file version: %s\n", strerror(errno)); |
| exit(EXIT_FAILURE); |
| } |
| |
| if (strcmp(line, EXPECTED_VERSION) != 0) { |
| fprintf(stderr, "Expected version: %s\n", EXPECTED_VERSION); |
| fprintf(stderr, "But got version: %s", line); |
| exit(EXIT_FAILURE); |
| } |
| |
| while (fgets(line, MAX_LINE, f)) { |
| sscanf(line, "%ld %ld %ld %s", &count, &total, &max, reason); |
| if (max > 0 || total > 0) { |
| e = find_latency_entry(head, reason); |
| if (e) { |
| e->count += count; |
| if (max > e->max) |
| e->max = max; |
| e->total += total; |
| } else { |
| e = alloc_latency_entry(); |
| e->count = count; |
| e->max = max; |
| e->total = total; |
| strcpy(e->reason, reason); |
| e->next = head; |
| head = e; |
| } |
| } |
| } |
| |
| return head; |
| } |
| |
| static void print_latency_entries(struct latency_entry *head) { |
| struct latency_entry *e, **array; |
| unsigned long average; |
| int i, count; |
| |
| e = head; |
| count = 0; |
| while (e) { |
| count++; |
| e = e->next; |
| } |
| |
| e = head; |
| array = calloc(count, sizeof(struct latency_entry *)); |
| if (!array) { |
| fprintf(stderr, "Error allocating array: %s\n", strerror(errno)); |
| exit(EXIT_FAILURE); |
| } |
| for (i = 0; i < count; i++) { |
| array[i] = e; |
| e = e->next; |
| } |
| |
| qsort(array, count, sizeof(struct latency_entry *), &lat_cmp); |
| |
| printf("%10s %10s %7s %s\n", "Maximum", "Average", "Count", "Reason"); |
| for (i = 0; i < count; i++) { |
| e = array[i]; |
| average = e->total / e->count; |
| printf("%4lu.%02lu ms %4lu.%02lu ms %7ld %s\n", |
| e->max / 1000, (e->max % 1000) / 10, |
| average / 1000, (average % 1000) / 10, |
| e->count, |
| e->reason); |
| } |
| |
| free(array); |
| } |
| |
| static void signal_handler(int sig) { |
| exit(EXIT_SUCCESS); |
| } |
| |
| static void disable_latencytop(void) { |
| set_latencytop(0); |
| } |
| |
| static void clear_screen(void) { |
| printf("\n\n"); |
| } |
| |
| static void usage(const char *cmd) { |
| fprintf(stderr, "Usage: %s [ -d delay ] [ -n iterations ] [ -p pid [ -t tid ] ] [ -h ]\n" |
| " -d delay Time to sleep between updates.\n" |
| " -n iterations Number of updates to show (0 = infinite).\n" |
| " -p pid Process to monitor (default is all).\n" |
| " -t tid Thread (within specified process) to monitor (default is all).\n" |
| " -h Display this help screen.\n", |
| cmd); |
| } |
| |
| static int numcmp(const long long a, const long long b) { |
| if (a < b) return -1; |
| if (a > b) return 1; |
| return 0; |
| } |
| |
| static int lat_cmp(const void *a, const void *b) { |
| const struct latency_entry *pa, *pb; |
| |
| pa = (*((struct latency_entry **)a)); |
| pb = (*((struct latency_entry **)b)); |
| |
| return numcmp(pb->max, pa->max); |
| } |