From 1de559ae8ae03ab22b2694f65716451e21fbf97a Mon Sep 17 00:00:00 2001 From: mar77i Date: Sun, 22 Mar 2026 07:59:59 +0100 Subject: [PATCH] major update: track multiple pids and children --- .gitignore | 2 +- Makefile | 4 +- perftrace.c | 819 ++++++++++++++++++++++++++++------------- sleep_some_work_some.c | 30 -- test_process_tree.sh | 27 ++ work_some.c | 70 ++++ 6 files changed, 665 insertions(+), 287 deletions(-) delete mode 100644 sleep_some_work_some.c create mode 100755 test_process_tree.sh create mode 100644 work_some.c diff --git a/.gitignore b/.gitignore index 3f6a3ae..4f2ed6e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ *.o perftrace -sleep_some_work_some +work_some diff --git a/Makefile b/Makefile index a7d80d6..fb1598b 100644 --- a/Makefile +++ b/Makefile @@ -3,10 +3,10 @@ CFLAGS += -D_DEFAULT_SOURCE $(PROD_CFLAGS) LDFLAGS += $(PROD_LDFLAGS) LDLIBS += -all: perftrace sleep_some_work_some +all: perftrace work_some perftrace: perftrace.o -sleep_some_work_some: sleep_some_work_some.o +work_some: work_some.o include global.mk diff --git a/perftrace.c b/perftrace.c index 1b3a711..01502e5 100644 --- a/perftrace.c +++ b/perftrace.c @@ -1,168 +1,383 @@ // perftrace.c +#include #include #include #include +#include #include #include #include #include #include #include -#include + +#define ARRAY_LENGTH(x) (sizeof(x) / sizeof((x)[0])) #define MICRO_IN_SEC 1000000 #define NANO_IN_SEC 1000000000 +#define TIMESPEC_TO_DOUBLE(t) \ + ((double)(t).tv_sec + (double)(t).tv_nsec / NANO_IN_SEC) +#define TIMESPEC_ISZERO(t) ((t).tv_sec == 0 && (t).tv_nsec == 0) + +struct list { + size_t len, allo; + void *data; +}; +#define LIST_EMPTY ((struct list){ 0, 0, NULL }) +#define LIST_ITEMS(l, t) ((t*)(l).data) +#define LIST_POS(p, l, t) (size_t)((p) - LIST_ITEMS((l), t)) +#define LIST_AT(l, i, t) (LIST_ITEMS((l), t) + i) + +static inline int list_maybe_grow( + struct list *l, size_t new_len, size_t item_size +) { + void *np; + if (new_len <= l->allo) + return 0; + do + l->allo = l->allo ? l->allo * 2 : 8; + while (new_len > l->allo); + np = realloc(l->data, l->allo * item_size); + if (np == NULL) { + perror("realloc"); + return -1; + } + l->data = np; + return 0; +} -struct args { - struct timeval delay; +struct arg_pid_item { pid_t pid; - char **argv; + char mode; }; +static inline int parse_unsigned_long( + unsigned long *result, char c, char *label +) { + register unsigned long new_value = 10 * *result + c - '0'; + if (c >= '0' && new_value / 10 == *result) { + *result = new_value; + return 0; + } + if (label) + fprintf( + stderr, + "Error: %s: invalid character or overflow ('%c')\n", + label, + c + ); + return -1; +} + +static inline int parse_pid(const char *ptr, struct arg_pid_item *new_item) { + unsigned long pid = 0; + do { + if (parse_unsigned_long(&pid, *ptr, NULL) < 0) + return -1; + ptr++; + } while (*ptr != '\0' && *ptr != '+' && *ptr != '*'); + *new_item = (struct arg_pid_item){ pid, *ptr }; + if (ptr[0] != '\0' && ptr[1] != '\0') + return -1; + return 0; +} + +static inline int parse_arg_pid(int argc, char ***argv, struct list *arg_pids) { + if ( + list_maybe_grow( + arg_pids, ++arg_pids->len, sizeof (struct arg_pid_item) + ) < 0 + ) + return -1; + if ((**argv)[5] == '=') { + if ( + parse_pid( + **argv + 6, + LIST_ITEMS(*arg_pids, struct arg_pid_item) + arg_pids->len - 1 + ) < 0 + ) + return -1; + } else if ((**argv)[5] == '\0') { + if ( + --argc == 0 + || parse_pid( + *++(*argv), + LIST_ITEMS(*arg_pids, struct arg_pid_item) + arg_pids->len - 1 + ) < 0 + ) + return -1; + } else + return -1; + return argc; +} + static inline int parse_delay(const char *ptr, struct timeval *delay) { - long new_value, fractions = -1; + register unsigned long fractions; *delay = (struct timeval){ 0, 0 }; - for (;;) { - new_value = 10 * delay->tv_sec + *ptr - '0'; - if (*ptr < '0' || new_value / 10 != delay->tv_sec) + for (; *ptr != '\0' && *ptr != '.'; ptr++) + if (parse_unsigned_long((unsigned long*)&delay->tv_sec, *ptr, NULL) < 0) return -1; - delay->tv_sec = new_value; - ptr++; - if (*ptr == '\0') - return 0; - else if (*ptr == '.') - break; - } - for (fractions = MICRO_IN_SEC / 10; fractions > 0; fractions /= 10) { - if (*(++ptr) == '\0') - break; - else if (*ptr < '0' || *ptr > '9') + ptr += *ptr != '\0'; + for ( + fractions = MICRO_IN_SEC / 10; + *ptr && fractions > 0; + ptr++, fractions /= 10 + ) + if (*ptr < '0' || *ptr > '9') return -1; - delay->tv_usec += fractions * (*ptr - '0'); - } + else + delay->tv_usec += fractions * (*ptr - '0'); return 0; } static inline int parse_arg_delay( - int argc, char **argv, struct args *a, long clock_ticks + int argc, char ***argv, struct timeval *delay ) { - switch (*argv[7]) { - case '=': - if (parse_delay(*argv + 8, &a->delay) < 0) - goto error; - break; - case '\0': - if (argc-- == 0 || parse_delay(*++argv, &a->delay) < 0) - goto error; - break; - default: - goto error; - } + long clock_ticks; + if ((**argv)[7] == '=') { + if (parse_delay(**argv + 8, delay) < 0) + return -1; + } else if ((**argv)[7] == '\0') { + if (--argc == 0 || parse_delay(*++(*argv), delay) < 0) + return -1; + } else + return -1; + clock_ticks = sysconf(_SC_CLK_TCK); if ( - (clock_ticks <= 1 && a->delay.tv_sec < 1) || ( - a->delay.tv_sec < 1 && a->delay.tv_usec < MICRO_IN_SEC / clock_ticks + (clock_ticks <= 1 && delay->tv_sec < 1) || ( + delay->tv_sec < 1 && delay->tv_usec < MICRO_IN_SEC / clock_ticks ) ) { fprintf(stderr, "Error: delay too small!\n"); return -1; } return argc; -error: - fprintf(stderr, "Error: parsing '--delay'\n"); - return -1; } -static inline pid_t parse_pid(const char *ptr) { - pid_t pid = 0, pid_new; - do { - pid_new = 10 * pid + *ptr - '0'; - if (*ptr < '0' || pid_new / 10 != pid) +int parse_args( + int argc, + char **argv, + struct list *pids, + struct timeval *delay, + int *humanize_mem +) { + for (argc--, argv++; argc > 0; argc--, argv++) { + if (strncmp(*argv, "--pid", 5) == 0) + argc = parse_arg_pid(argc, &argv, pids); + else if (strncmp(*argv, "--delay", 7) == 0) + argc = parse_arg_delay(argc, &argv, delay); + else if (strcmp(*argv, "--humanize") == 0) + *humanize_mem = 1; + else + argc = -1; + if (argc == -1) { + fprintf(stderr, "Error: Could not process argument: '%s'\n", *argv); return -1; - pid = pid_new; - } while (*(++ptr) != '\0'); - return pid; + } + } + return 0; } -static inline int parse_arg_pid(int argc, char **argv, struct args *a) { - if (a->pid != 0) { - fprintf(stderr, "Error: '--pid' already specified.\n"); - return -1; - } - switch (*argv[5]) { - case '=': - if ((a->pid = parse_pid(*argv + 6)) < 0) - break; - return argc; - case '\0': - if (argc-- == 0 || (a->pid = parse_pid(*++argv)) < 0) +static int alarm_set, int_set, page_size; + +void handle_alarm(int signal) { + alarm_set |= signal == SIGALRM; +} + +void handle_int(int signal) { + int_set |= signal == SIGINT; +} + +static inline int alarm_await(void) { + if (int_set) + return 0; + while (!alarm_set) + sleep(UINT_MAX); + return alarm_set && !int_set; +} + +struct pid_item { + pid_t pid, ppid; + struct timespec utime; + unsigned long vsize, rss; +}; + +static inline void get_field_labels(char digits, char *buffer) { + const char *s = "ppidvsizerss"; + char *ptr = buffer; + for (digits &= 7; digits; digits >>= 1) { + if (!(digits & 1)) { + s += 4; + if (*s != 'u') + s++; + continue; + } + if (ptr > buffer) { + *ptr++ = ','; + *ptr++ = ' '; + } + *ptr++ = *s++; + *ptr++ = *s++; + *ptr++ = *s++; + if (!*s) break; - return argc; + *ptr++ = *s++; + if (*s != 'u') + *ptr++ = *s++; } - fprintf(stderr, "Error: parsing '--pid'\n"); - return -1; + *ptr = '\0'; } -static inline int parse_arg_argv(int argc, char **argv, struct args *a) { - if (a->pid != 0) { - fprintf(stderr, "Error: '--pid' already specified.\n"); +static inline int perftrace_collect_details(pid_t pid, struct pid_item *item) { + struct timespec utime; + unsigned long ppid = 0; + clockid_t clock_id = 0; + int fd, read_bytes = 0, ret = -1; + char state = 0, buffer[64], digits = 0, *ptr = buffer; + snprintf(buffer, sizeof buffer, "/proc/%d/stat", pid); + if (clock_getcpuclockid(pid, &clock_id) < 0) { + perror("clock_getcpuclockid"); return -1; } - if (argc-- == 0) { - fprintf(stderr, "Error: arguments expected after '--'\n"); + if (clock_gettime(clock_id, &utime) < 0) { + if (errno == EINVAL) + utime = (struct timespec){ 0, 0 }; + else { + perror("clock_gettime"); + return -1; + } + } + fd = open(buffer, O_RDONLY); + if (fd < 0) { + perror("open"); return -1; } - a->argv = ++argv; - return argc; + for (;; ptr++) { + if (ptr - buffer >= read_bytes) { + ptr = buffer; + if ((read_bytes = read(fd, &buffer, sizeof buffer)) < 0) { + perror("read"); + goto error; + } else if (read_bytes == 0) + break; + } + if (*ptr == ')') { + state = 2; + *item = (struct pid_item){ pid, 0, utime, 0, 0 }; + } else if (state > 0 && *ptr == ' ') + state++; + else switch (state) { + case 4: + if (parse_unsigned_long(&ppid, *ptr, "ppid") < 0) + goto error; + item->ppid = ppid; + digits |= 1; + break; + case 23: + if (parse_unsigned_long(&item->vsize, *ptr, "vsize") < 0) + goto error; + digits |= 2; + break; + case 24: + if (parse_unsigned_long(&item->rss, *ptr, "rss") < 0) + goto error; + digits |= 4; + break; + } + } + if (digits == 7) + ret = 0; + else { + get_field_labels(digits ^ 7, buffer); + fprintf(stderr, "Error: missing state (%s)\n", buffer); + } +error: + close(fd); + return ret; } -int parse_args(int argc, char **argv, struct args *a, long clock_ticks) { - for (argv++, argc--; argc > 0; argv++, argc--) { - if (strncmp(*argv, "--delay", 7) == 0) - argc = parse_arg_delay(argc, argv, a, clock_ticks); - else if (strncmp(*argv, "--pid", 5) == 0) - argc = parse_arg_pid(argc, argv, a); - else if (strcmp(*argv, "--") == 0) - argc = parse_arg_argv(argc, argv, a); - else { - fprintf(stderr, "Error: Invalid argument: '%s'.\n", *argv); +int perftrace_collect(struct list *pl) { + DIR *d = opendir("/proc"); + struct dirent *ent; + unsigned long pid; + size_t old_len; + char *ptr; + while ((ent = readdir(d)) != NULL) { + pid = 0; + for (ptr = ent->d_name; *ptr; ptr++) + if (parse_unsigned_long(&pid, *ptr, NULL) < 0) { + pid = ULONG_MAX; + break; + } + if (pid == ULONG_MAX) + continue; + old_len = pl->len; + if (list_maybe_grow(pl, ++pl->len, sizeof (struct pid_item)) < 0) return -1; + if ( + perftrace_collect_details( + pid, LIST_ITEMS(*pl, struct pid_item) + old_len + ) < 0 + ) { + pl->len--; + continue; } - if (argc == -1) - return -1; - } - if (a->pid == 0 && a->argv == NULL) { - fprintf(stderr, "Error: Must either specify '--' or '--pid'.\n"); - return -1; } + closedir(d); return 0; } -static inline int run_proc(struct args *a) { - pid_t pid = fork(); - switch (pid) { - case -1: - perror("fork"); - return -1; - break; - case 0: - execvp(*a->argv, a->argv); - perror("execvp"); - exit(1); - default: - a->pid = pid; - return 0; +static inline struct pid_item *perftrace_find_pid_item( + struct list pl, pid_t pid +) { + struct pid_item *item; + if (pid <= 0) + return NULL; + for (item = pl.data; LIST_POS(item, pl, struct pid_item) < pl.len; item++) + if (item->pid == pid) + return item; + return NULL; +} + +static inline void swap_pid_items( + struct pid_item *a, struct pid_item *b +) { + struct pid_item tmp; + tmp = *a; + *a = *b; + *b = tmp; +} + +static inline void perftrace_invalidate_missing_pids( + struct list new, struct list *old +) { + struct pid_item *old_item; + if (old->len == 0) + return; + for ( + old_item = old->data; + LIST_POS(old_item, *old, struct pid_item) < old->len; + old_item++ + ) { + if (perftrace_find_pid_item(new, old_item->pid) != NULL) + continue; + old_item->pid = -1; + if (LIST_POS(old_item, *old, struct pid_item) < --old->len) + swap_pid_items( + old_item--, LIST_AT(*old, old->len, struct pid_item) + ); } } static inline struct timespec timespec_diff( struct timespec tsa, struct timespec tsb ) { - struct timespec result = { - .tv_sec = tsa.tv_sec - tsb.tv_sec, - .tv_nsec = tsa.tv_nsec, - }; + struct timespec result = { 0, 0 }; + if (TIMESPEC_ISZERO(tsa) || TIMESPEC_ISZERO(tsb)) + return result; + result = (struct timespec){ tsa.tv_sec - tsb.tv_sec, tsa.tv_nsec }; if (tsb.tv_nsec > tsa.tv_nsec) { result.tv_sec -= 1; result.tv_nsec += NANO_IN_SEC; @@ -171,194 +386,290 @@ static inline struct timespec timespec_diff( return result; } -struct file_buffer { - int fd; - char buffer[128], size, consumed; -}; - -static inline int file_buffer_get_char(struct file_buffer *fb) { - int ret; - if (fb->consumed < fb->size) - goto next; - ret = read(fb->fd, fb->buffer, sizeof fb->buffer); - switch (ret) { - case -1: - perror("read"); - return -1; - case 0: - return INT_MIN; +static inline char humanize(double *value) { + const char *label = "BKMGTPEZY"; + while (*value > 2048 && *label != 'Y') { + *value /= 1024; + label++; } - fb->size = ret; - fb->consumed = 0; -next: - return fb->buffer[(int)fb->consumed++]; + return *label; } -static inline int read_unsigned_long( - struct file_buffer *fb, unsigned long *result, int end_char +static inline void perftrace_humanize(double vsize, double rss) { + char rss_unit, vsize_unit; + vsize_unit = humanize(&vsize); + rss_unit = humanize(&rss); + printf( + ( + " \"vsize\": \"%.2f%c\",\n" + " \"rss\": \"%.2f%c\"\n" + ), + vsize, + vsize_unit, + rss, + rss_unit + ); +} + +static inline int perftrace_pid( + struct pid_item item, + struct list *old, + struct timespec diff, + int humanize_mem, + int *json_state ) { - unsigned long result_new; - int c; - *result = 0; - for (;;) { - if ((c = file_buffer_get_char(fb)) < 0) - return c == INT_MIN && end_char == EOF ? 0 : -1; - else if (c == end_char) - return 0; - result_new = 10 * *result + c - '0'; - if (c < '0' || result_new / 10 != *result) { - fprintf(stderr, "Error: parsing stat value.\n"); + struct pid_item *old_item = perftrace_find_pid_item(*old, item.pid); + struct timespec utime_diff; + double cpu = 0; + if ( + !TIMESPEC_ISZERO(diff) + && old_item != NULL + && !TIMESPEC_ISZERO( + utime_diff = timespec_diff(item.utime, old_item->utime) + ) + ) + cpu = TIMESPEC_TO_DOUBLE(utime_diff) / TIMESPEC_TO_DOUBLE(diff) * 100; + printf( + ( + "%s\n" + " {\n" + " \"pid\": %d,\n" + " \"ppid\": %d,\n" + " \"cpu%%\": %.03f,\n" + ), + *json_state == 2 ? "," : "", + item.pid, + item.ppid, + cpu + ); + if (humanize_mem) { + perftrace_humanize(item.vsize, item.rss * page_size); + } else + printf( + ( + " \"vsize\": %lu,\n" + " \"rss\": %lu\n" + ), + item.vsize, + item.rss * page_size + ); + printf(" }"); + *json_state = 2; + if (old_item == NULL) { + if ( + list_maybe_grow( + old, ++old->len, sizeof (struct pid_item) + ) < 0 + ) return -1; - } - *result = result_new; + old_item = LIST_AT(*old, old->len - 1, struct pid_item); } + *old_item = item; + return 0; } -static inline char *stringify_pid(char *buf, pid_t pid) { - ssize_t i; - char *ptr = buf, tmp; - do { - *ptr++ = '0' + pid % 10; - pid /= 10; - } while (pid > 0); - for (i = 0; i < (ptr - buf) / 2; i++) { - tmp = buf[i]; - buf[i] = ptr[-i - 1]; - ptr[-i - 1] = tmp; +static inline int perftrace_children( + struct pid_item item, + struct list new, + struct list *old, + struct timespec diff, + int humanize_mem, + int *json_state +) { + struct pid_item *ptr; + for (ptr = new.data; LIST_POS(ptr, new, struct pid_item) < new.len; ptr++) { + if (ptr->ppid != item.pid) + continue; + if (perftrace_pid(*ptr, old, diff, humanize_mem, json_state) < 0) + return -1; } - return ptr; + return 0; } -static inline int read_utime(clockid_t clock_id, struct timespec *utime) { - // maybe offer to record system time, too, from /proc//stat - if (clock_gettime(clock_id, utime) < 0) { - if (errno == EINVAL) - return -2; - perror("clock_gettime"); - return -1; - } - return 0; +static inline pid_t *perftrace_find_pid(struct list found_pids, pid_t pid) { + pid_t *ptr; + if (pid <= 0) + return NULL; + for ( + ptr = found_pids.data; + LIST_POS(ptr, found_pids, pid_t) < found_pids.len; + ptr++ + ) + if (*ptr == pid) + return ptr; + return NULL; } -static inline int read_statm( - pid_t pid, unsigned long *vm_size, unsigned long *resident +static inline int perftrace_recursive( + struct pid_item item, + struct list new, + struct list *old, + struct timespec diff, + int humanize_mem, + int *json_state ) { - struct file_buffer fb = { 0, { 0 }, 0, 0 }; + struct list found_pids = LIST_EMPTY; + struct pid_item *pip; + size_t found = 0, last_found; + pid_t *ptr; int ret = 0; - char filename[128]; - memcpy(filename, "/proc/", 6); - memcpy(stringify_pid(filename + 6, pid), "/statm", 7); - if ((fb.fd = open(filename, O_RDONLY)) < 0) { - if (errno == ESRCH) - return -2; - perror("open"); + if (list_maybe_grow(&found_pids, ++found_pids.len, sizeof (pid_t)) < 0) return -1; - } - if ( - read_unsigned_long(&fb, vm_size, ' ') < 0 - || read_unsigned_long(&fb, resident, ' ') < 0 - ) - ret = -1; - // POSIX.1-2024 close - if (close(fb.fd) < 0) { - perror("close"); - if (errno != EINTR) - ret = -1; - else if (close(fb.fd) < 0) { - perror("close"); - ret = -1; - } - } + *LIST_AT(found_pids, 0, pid_t) = item.pid; + do { + last_found = found; + for ( + ptr = LIST_AT(found_pids, last_found, pid_t); + LIST_POS(ptr, found_pids, pid_t) < found_pids.len; + ptr++ + ) + for ( + pip = new.data; + LIST_POS(pip, new, struct pid_item) < new.len; + pip++ + ) { + if ( + pip->ppid != *ptr + // avoid repeating pids in found_pids + || perftrace_find_pid(found_pids, pip->pid) != NULL + ) + continue; + if ( + perftrace_pid(*pip, old, diff, humanize_mem, json_state) < 0 + || list_maybe_grow( + &found_pids, ++found_pids.len, sizeof (pid_t) + ) < 0 + ) + goto error; + *LIST_AT(found_pids, found_pids.len - 1, pid_t) = pip->pid; + found++; + } + } while (found > last_found); + ret = 0; +error: + free(found_pids.data); return ret; } -static inline int check_child(struct args *a) { - pid_t pid = waitpid(a->pid, NULL, WNOHANG); - if (pid == a->pid) - a->argv = NULL; - return pid; -} - -static int alarm_set; - -void handle_alarm(int signal) { - alarm_set |= signal == SIGALRM; +static inline int perftrace( + struct list pids, + struct list *old, + struct timespec current, + struct timespec prev, + int humanize_mem, + int *json_state +) { + struct arg_pid_item *arg_pid; + struct pid_item *item; + struct list new = LIST_EMPTY; + struct timespec diff = timespec_diff(current, prev); + struct tm lt; + size_t old_len_before = old->len; + int ret = -1; + if (perftrace_collect(&new) < 0) + return -1; + perftrace_invalidate_missing_pids(new, old); + if (old_len_before > 0 && old->len == 0) { + ret = 0; + goto error; + } + localtime_r(¤t.tv_sec, <); + printf( + ( + "%s" + " {\n" + " \"ts\": \"%04d-%02d-%02d %02d:%02d:%02d.%03ld\",\n" + " \"perftrace\": [" + ), + *json_state ? "\n ]\n },\n" : "", + lt.tm_year + 1900, + lt.tm_mon + 1, + lt.tm_mday, + lt.tm_hour, + lt.tm_min, + lt.tm_sec, + (current.tv_nsec + MICRO_IN_SEC / 2 - 1) / MICRO_IN_SEC + ); + *json_state = 1; + for ( + arg_pid = pids.data; + LIST_POS(arg_pid, pids, struct arg_pid_item) < pids.len; + arg_pid++ + ) { + if ((item = perftrace_find_pid_item(new, arg_pid->pid)) == NULL) + continue; + if (perftrace_pid(*item, old, diff, humanize_mem, json_state) < 0) + goto error; + if ( + ( + arg_pid->mode == '+' + && perftrace_children( + *item, new, old, diff, humanize_mem, json_state + ) < 0 + ) || ( + arg_pid->mode == '*' + && perftrace_recursive( + *item, new, old, diff, humanize_mem, json_state + ) < 0 + ) + ) + goto error; + } + ret = 0; +error: + free(new.data); + return ret; } -int main(int argc, char *argv[]) { - struct timespec current[3] = { { 0, 0 }, { 0, 0 }, { 0, 0 } }; - struct timespec utime[3] = { { 0, 0 }, { 0, 0 }, { 0, 0 } }; - struct sigaction sa = { .sa_handler = handle_alarm, .sa_flags = 0 }; - struct args a = { { 0, MICRO_IN_SEC / 2 }, 0, NULL }; - unsigned long vm_size, resident; - long clock_ticks = sysconf(_SC_CLK_TCK); - clockid_t clock_id; - int page_size = getpagesize(), ret = EXIT_FAILURE; - if (parse_args(argc, argv, &a, clock_ticks) < 0) - return EXIT_FAILURE; - sa.sa_handler = handle_alarm; +static inline int setup_signal_handler(int signum, void (*func)(int)) { + struct sigaction sa = { .sa_handler = func, .sa_flags = 0 }; sigemptyset(&sa.sa_mask); - if (sigaction(SIGALRM, &sa, NULL) < 0) { + if (sigaction(signum, &sa, NULL) < 0) { perror("sigaction"); - return EXIT_FAILURE; + return -1; } + return 0; +} + +int main(int argc, char *argv[]) { + struct list pids = LIST_EMPTY, old = LIST_EMPTY; + struct timespec current, prev = { 0, 0 }; + struct timeval delay = { 0, MICRO_IN_SEC / 2 }; + int ret = EXIT_FAILURE, humanize_mem = 0, json_state = 0; + page_size = getpagesize(); + if ( + setup_signal_handler(SIGALRM, handle_alarm) < 0 + || setup_signal_handler(SIGINT, handle_int) < 0 + ) + goto error; if ( setitimer( - ITIMER_REAL, &(struct itimerval){ a.delay, a.delay }, NULL + ITIMER_REAL, &(struct itimerval){ delay, delay }, NULL ) < 0 ) { perror("setitimer"); - return EXIT_FAILURE; + goto error; } - if (a.argv != NULL && run_proc(&a) < 0) + if (parse_args(argc, argv, &pids, &delay, &humanize_mem) < 0) return EXIT_FAILURE; - if (clock_getcpuclockid(a.pid, &clock_id) < 0) - goto error; - for (alarm_set = 0;; alarm_set = 0) { - if (a.argv != NULL && check_child(&a) < 0) - goto error; - if (clock_gettime(CLOCK_MONOTONIC, current) < 0) { + printf("[\n"); + for (alarm_set = 0; alarm_await(); alarm_set = 0) { + if (clock_gettime(CLOCK_REALTIME, ¤t) < 0) { perror("clock_gettime"); - return -1; - } - switch (read_utime(clock_id, utime)) { - case -1: goto error; - case -2: - goto done; - } - if (current[1].tv_sec | current[1].tv_nsec) { - current[2] = timespec_diff(current[0], current[1]); - utime[2] = timespec_diff(utime[0], utime[1]); } - current[1] = current[0]; - utime[1] = utime[0]; - switch (read_statm(a.pid, &vm_size, &resident)) { - case -1: + if (perftrace(pids, &old, current, prev, humanize_mem, &json_state) < 0) goto error; - case -2: - goto done; - } - printf( - ( - "{\"ts\":%ld.%09ld,\"ts_diff\":%ld.%09ld," - "\"utime\":%ld.%09ld,\"vm_size\":%ld,\"resident\":%ld}\n" - ), - current[0].tv_sec, - current[0].tv_nsec, - current[2].tv_sec, - current[2].tv_nsec, - utime[2].tv_sec, - utime[2].tv_nsec, - vm_size * page_size, - resident * page_size - ); - sleep(!alarm_set * UINT_MAX); + if (old.len == 0) + break; + prev = current; } -done: ret = EXIT_SUCCESS; error: - if (a.argv != NULL && waitpid(a.pid, NULL, 0) < 0) - perror("waitpid"); + if (json_state) + printf("\n ]\n }\n"); + printf("]\n"); + free(pids.data); + free(old.data); return ret; } diff --git a/sleep_some_work_some.c b/sleep_some_work_some.c deleted file mode 100644 index 283e200..0000000 --- a/sleep_some_work_some.c +++ /dev/null @@ -1,30 +0,0 @@ -#include -#include -#include -#include - -#define SECONDS 4 - -int main(void) { - int i, j; - void *ptrs[SECONDS * 16]; - struct timespec start, current; - sleep(SECONDS); - if (clock_gettime(CLOCK_MONOTONIC, &start) < 0) - return EXIT_FAILURE; - start.tv_sec += SECONDS; - for (i = 0; i < SECONDS * 16; i++) - ptrs[i] = malloc(32768 * 1024); - for (;;) { - if (clock_gettime(CLOCK_MONOTONIC, ¤t) < 0) - return EXIT_FAILURE; - if (current.tv_sec >= start.tv_sec && current.tv_nsec >= start.tv_nsec) - break; - for (i = 0; i < 1000000; i++) - j += i; - } - for (i = 0; i < SECONDS * 16; i++) - free(ptrs[i]); - sleep(SECONDS); - return EXIT_FAILURE; -} diff --git a/test_process_tree.sh b/test_process_tree.sh new file mode 100755 index 0000000..e9ba1df --- /dev/null +++ b/test_process_tree.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +make || exit 1 + +sleep_some_work_some() { + sleep $1 + ./work_some $2 + sleep $1 +} + +{ + sleep 3 + sleep_some_work_some 3 & + a="$!" + sleep_some_work_some 2 & + b="$!" + sleep_some_work_some 4 & + wait $a $b + sleep 1 + wait $! + sleep 1 +} & +a="$!" +sleep_some_work_some 3 & +b="$!" + +make vg ARGS='./perftrace --pid '"$a"'\* --pid='$b' --delay .5' diff --git a/work_some.c b/work_some.c new file mode 100644 index 0000000..83c5473 --- /dev/null +++ b/work_some.c @@ -0,0 +1,70 @@ +#include +#include +#include +#include + +#define NUM_PTRS 64 +#define BUCKET_SIZE 1024 * 1024 * 16 +#define ARRAY_LENGTH(x) (sizeof(x) / sizeof((x)[0])) + +static inline int parse_args(int argc, char **argv, size_t *seconds) { + size_t new_value; + char *ptr; + if (argc == 0) + return 0; + else if (argc > 1) { + fprintf(stderr, "Error: At most one argument expected.\n"); + return -1; + } + ptr = *argv; + if (*ptr == '\0') + goto error; + *seconds = 0; + do { + new_value = *seconds * 10 + *ptr - '0'; + if (*ptr < '0' || new_value / 10 != *seconds) { + printf("heh: %c %d\n", *ptr, *ptr); + goto error; + } + *seconds = new_value; + } while (*++ptr != '\0'); + return 0; +error: + fprintf(stderr, "Error: Invalid argument: '%s'.\n", *argv); + return -1; +} + +int main(int argc, char *argv[]) { + struct timespec current, end; + void *ptrs[NUM_PTRS]; + size_t num_ptrs, i, j, k, seconds = 4; + int ret = EXIT_FAILURE; + if (clock_gettime(CLOCK_MONOTONIC, &end) < 0) { + perror("clock_gettime"); + return EXIT_FAILURE; + } + if (parse_args(argc - 1, argv + 1, &seconds) < 0) + return EXIT_FAILURE; + end.tv_sec += seconds; + for (num_ptrs = 0; num_ptrs < ARRAY_LENGTH(ptrs); num_ptrs++) + if ((ptrs[num_ptrs] = malloc(BUCKET_SIZE)) == NULL) { + perror("malloc"); + goto error; + } + for (i = 0;; i++) { + if (clock_gettime(CLOCK_MONOTONIC, ¤t) < 0) { + perror("clock_gettime"); + goto error; + } + if (current.tv_sec >= end.tv_sec && current.tv_nsec >= end.tv_nsec) + break; + for (j = 0; j < num_ptrs / 2; j++) + for (k = 0; k < BUCKET_SIZE; k++) + ((char*)ptrs[j])[k] = i ^ j ^ i; + } + ret = EXIT_SUCCESS; +error: + for (i = 0; i < num_ptrs; i++) + free(ptrs[i]); + return ret; +} -- 2.53.0