diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:32:58 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:32:58 -0800 |
commit | e16cb84e2324f05334d18dcf5956f20f44262b62 (patch) | |
tree | c87defa74181089778bc8e5671e2896d95d4e2b0 /latencytop/latencytop.c | |
parent | 19ddb4b1680760e2d6863c3003976882ebd9d0fa (diff) | |
download | extras-e16cb84e2324f05334d18dcf5956f20f44262b62.tar.gz |
auto import from //depot/cupcake/@135843
Diffstat (limited to 'latencytop/latencytop.c')
-rw-r--r-- | latencytop/latencytop.c | 442 |
1 files changed, 442 insertions, 0 deletions
diff --git a/latencytop/latencytop.c b/latencytop/latencytop.c new file mode 100644 index 00000000..78d7c71d --- /dev/null +++ b/latencytop/latencytop.c @@ -0,0 +1,442 @@ +/* + * 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); +} |