]> git.mar77i.info Git - perftrace/commitdiff
major update: track multiple pids and children
authormar77i <mar77i@protonmail.ch>
Sun, 22 Mar 2026 06:59:59 +0000 (07:59 +0100)
committermar77i <mar77i@protonmail.ch>
Sun, 22 Mar 2026 06:59:59 +0000 (07:59 +0100)
.gitignore
Makefile
perftrace.c
sleep_some_work_some.c [deleted file]
test_process_tree.sh [new file with mode: 0755]
work_some.c [new file with mode: 0644]

index 3f6a3ae4db5b29e3d7db98a164b5eb6c3de60d71..4f2ed6ea3948f0d319f143bcbaf7f12eb523384b 100644 (file)
@@ -1,3 +1,3 @@
 *.o
 perftrace
-sleep_some_work_some
+work_some
index a7d80d659ea674a9dee7c6382eabb64af459bb70..fb1598b8bd8c99561a68fdc6c6cacdf8a24edfb4 100644 (file)
--- 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
 
index 1b3a71136ba328d0ecee01971ca8c41fd5579a58..01502e5c6258057720f055b3d34d5564024e089f 100644 (file)
 
 // perftrace.c
 
+#include <dirent.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <limits.h>
+#include <signal.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <time.h>
 #include <unistd.h>
 #include <sys/time.h>
-#include <sys/wait.h>
+
+#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/<pid>/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(&current.tv_sec, &lt);
+    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, &current) < 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 (file)
index 283e200..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-#include <stdlib.h>
-#include <time.h>
-#include <stdio.h>
-#include <unistd.h>
-
-#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, &current) < 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 (executable)
index 0000000..e9ba1df
--- /dev/null
@@ -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 (file)
index 0000000..83c5473
--- /dev/null
@@ -0,0 +1,70 @@
+#include <stdlib.h>
+#include <time.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#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, &current) < 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;
+}