]> git.mar77i.info Git - perftrace/commitdiff
initial commit master
authormar77i <mar77i@protonmail.ch>
Sat, 21 Feb 2026 13:37:31 +0000 (14:37 +0100)
committermar77i <mar77i@protonmail.ch>
Sat, 21 Feb 2026 13:37:31 +0000 (14:37 +0100)
.gitignore [new file with mode: 0644]
Makefile [new file with mode: 0644]
global.mk [new file with mode: 0644]
perftrace.c [new file with mode: 0644]
sleep_some_work_some.c [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..3f6a3ae
--- /dev/null
@@ -0,0 +1,3 @@
+*.o
+perftrace
+sleep_some_work_some
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..a7d80d6
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,13 @@
+
+CFLAGS += -D_DEFAULT_SOURCE $(PROD_CFLAGS)
+LDFLAGS += $(PROD_LDFLAGS)
+LDLIBS +=
+
+all: perftrace sleep_some_work_some
+
+perftrace: perftrace.o
+sleep_some_work_some: sleep_some_work_some.o
+
+include global.mk
+
+.PHONY: all
diff --git a/global.mk b/global.mk
new file mode 100644 (file)
index 0000000..92175d8
--- /dev/null
+++ b/global.mk
@@ -0,0 +1,35 @@
+
+# global.mk
+#
+# Copyright (c) 2017, mar77i <mar77i at protonmail dot ch>
+#
+# This software may be modified and distributed under the terms
+# of the ISC license.  See the LICENSE file for details.
+
+# useful compiler flags; thanks xavier roche @ httrack blog
+
+CFLAGS += -std=c99 -pipe -fvisibility=hidden -Wall -Wextra -Wformat \
+       -Wformat-security -Wreturn-type -Wpointer-arith -Winit-self \
+       -Wsign-compare -Wmultichar -Wuninitialized -Werror -funroll-loops \
+       -funswitch-loops -pedantic
+
+SHARED_CFLAGS = -fPIC -fvisibility=default
+
+LDFLAGS += -Wl,-z,relro,-z,now,-O1,--as-needed,--no-undefined \
+       -Wl,--build-id=sha1,--no-allow-shlib-undefined -rdynamic
+
+VALGRIND_FLAGS = --trace-children=yes --leak-check=full --track-origins=yes \
+       --show-leak-kinds=all
+
+LINK = $(CC) $(LDFLAGS) $> $^ $(LOADLIBES) $(LDLIBS) $(LDADD) -o $@
+LINK_SHARED = \
+       $(CC) $(LDFLAGS) $> $^ $(LOADLIBES) $(LDLIBS) $(LDADD) -shared -o $@
+
+vg:
+       valgrind $(VALGRIND_FLAGS) $(ARGS)
+
+DEV_CFLAGS = -Og -g
+PROD_CFLAGS = -O3
+PROD_LDFLAGS = -Wl,--discard-all
+
+.PHONY: vg
diff --git a/perftrace.c b/perftrace.c
new file mode 100644 (file)
index 0000000..1fd11e9
--- /dev/null
@@ -0,0 +1,302 @@
+
+// perftrace2.c
+
+#include <fcntl.h>
+#include <limits.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 MICRO_IN_SEC 1000000
+#define NANO_IN_SEC 1000000000
+
+struct args {
+    struct timeval delay;
+    pid_t pid;
+    char **argv;
+};
+
+static inline int parse_delay(const char *ptr, struct timeval *delay) {
+    long new_value, fractions = -1;
+    *delay = (struct timeval){ 0, 0 };
+    do {
+        if (*ptr == '.')
+            fractions = MICRO_IN_SEC / 10;
+        else if (fractions == -1) {
+            new_value = 10 * delay->tv_sec + *ptr - '0';
+            if (*ptr < '0' || new_value / 10 != delay->tv_sec)
+                return -1;
+            delay->tv_sec = new_value;
+        } else if (fractions > 0 && *ptr >= '0' && *ptr <= '9') {
+            delay->tv_usec += fractions * (*ptr - '0');
+            fractions /= 10;
+        } else
+            return -1;
+    } while (*(++ptr) != '\0');
+    return 0;
+}
+
+static inline int parse_pid(const char *ptr, pid_t *pid) {
+    pid_t pid_new;
+    do {
+        pid_new = 10 * *pid + *ptr - '0';
+        if (*ptr < '0' || pid_new / 10 != *pid)
+            return -1;
+        *pid = pid_new;
+    } while (*(++ptr) != '\0');
+    return 0;
+}
+
+static int page_size, alarm_set;
+static long clock_ticks;
+
+int parse_args(int argc, char **argv, struct args *a) {
+    int i;
+    char pid_or_argv_set = 0, *arg;
+    if (argc < 2) {
+        fprintf(stderr, "Error: missing arguments.\n");
+        return -1;
+    }
+    for (i = 1; i < argc; i++) {
+        arg = argv[i];
+        if (strncmp(arg, "--delay", 7) == 0) {
+            switch (arg[7]) {
+            case '=':
+                if (parse_delay(arg + 8, &a->delay) < 0)
+                    goto error;
+                break;
+            case '\0':
+                if (++i >= argc || parse_delay(arg, &a->delay) < 0)
+                    goto error;
+                break;
+            default:
+                goto error;
+            }
+            if (
+                a->delay.tv_sec < 1
+                && a->delay.tv_usec < MICRO_IN_SEC / clock_ticks
+            ) {
+                fprintf(stderr, "Error: delay too small!\n");
+                return -1;
+            }
+        } else if (strncmp(arg, "--pid", 5) == 0) {
+            if (pid_or_argv_set != 0) {
+                fprintf(stderr, "Error: --pid already specified.\n");
+                return -1;
+            }
+            pid_or_argv_set = 1;
+            switch(arg[5]) {
+            case '=':
+                if (parse_pid(arg + 6, &a->pid) < 0)
+                    goto error;
+                break;
+            case '\0':
+                if (++i >= argc || parse_pid(arg, &a->pid) < 0)
+                    goto error;
+                break;
+            default:
+                goto error;
+            }
+        } else if (strcmp(arg, "--") == 0) {
+            if (pid_or_argv_set != 0) {
+                fprintf(stderr, "Error: --pid already specified.\n");
+                return -1;
+            }
+            pid_or_argv_set = 2;
+            if (++i >= argc)
+                goto error;
+            a->pid = argc - i;
+            a->argv = argv + i;
+            break;
+        } else {
+            fprintf(stderr, "Error: Invalid arguments.\n");
+            return -1;
+        }
+    }
+    if (pid_or_argv_set == 0) {
+        fprintf(stderr, "Error: Must either specify -- or --attach.\n");
+        return -1;
+    }
+    return 0;
+error:
+    fprintf(stderr, "Error: parsing '%s'\n", arg);
+    return -1;
+}
+
+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;
+    }
+}
+
+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,
+    };
+    if (tsb.tv_nsec > tsa.tv_nsec) {
+        result.tv_sec -= 1;
+        result.tv_nsec += NANO_IN_SEC;
+    }
+    result.tv_nsec -= tsb.tv_nsec;
+    return result;
+}
+
+void handle_alarm(int signal) {
+    alarm_set |= signal == SIGALRM;
+}
+
+struct file_buffer {
+    int fd;
+    char buffer[4], 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;
+    }
+    fb->size = ret;
+    fb->consumed = 0;
+next:
+    return fb->buffer[(int)fb->consumed++];
+}
+
+static inline int read_unsigned_long(
+    struct file_buffer *fb, unsigned long *result, int end_char
+) {
+    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");
+            return -1;
+        }
+        *result = result_new;
+    }
+}
+
+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;
+    }
+    return ptr;
+}
+
+int read_stat(pid_t pid, unsigned long *vm_size, unsigned long *resident) {
+    struct file_buffer fb = { 0, { 0 }, 0, 0 };
+    int ret = 0;
+    char filename[128];
+    memcpy(filename, "/proc/", 6);
+    memcpy(stringify_pid(filename + 6, pid), "/statm", 7);
+    fb.fd = open(filename, O_RDONLY);
+    if (
+        read_unsigned_long(&fb, vm_size, ' ') < 0
+        || read_unsigned_long(&fb, resident, ' ') < 0
+    )
+        ret = -1;
+    close(fb.fd);
+    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;
+    struct args a = { { 0, MICRO_IN_SEC / 2 }, 0, NULL };
+    unsigned long vm_size, resident;
+    clockid_t clock_id;
+    clock_ticks = sysconf(_SC_CLK_TCK);
+    page_size = getpagesize();
+    if (parse_args(argc, argv, &a) < 0)
+        return EXIT_FAILURE;
+    sa.sa_handler = handle_alarm;
+    sigemptyset(&sa.sa_mask);
+    if (sigaction(SIGALRM, &sa, NULL) < 0) {
+        perror("sigaction");
+        return EXIT_FAILURE;
+    }
+    if (
+        setitimer(
+            ITIMER_REAL, &(struct itimerval){ a.delay, a.delay }, NULL
+        ) < 0
+    ) {
+        perror("setitimer");
+        return EXIT_FAILURE;
+    }
+    if (a.argv != NULL && run_proc(&a) < 0)
+        return EXIT_FAILURE;
+    if (clock_getcpuclockid(a.pid, &clock_id) < 0)
+        goto error;
+    for (;;) {
+        if (clock_gettime(CLOCK_MONOTONIC, current) < 0)
+            goto error;
+        if (waitpid(a.pid, NULL, WNOHANG) != 0)
+            break;
+        if (clock_gettime(clock_id, utime) < 0)
+            goto error;
+        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]);
+        }
+        if (read_stat(a.pid, &vm_size, &resident) < 0)
+            goto error;
+        printf(
+            (
+                "{\"ts\":%ld.%09ld,\"utime\":%ld.%09ld,"
+                "\"vm_size\":%ld,\"resident\":%ld}\n"
+            ),
+            current[2].tv_sec,
+            current[2].tv_nsec,
+            utime[2].tv_sec,
+            utime[2].tv_nsec,
+            vm_size * page_size,
+            resident * page_size
+        );
+        for (alarm_set = 0; !alarm_set;)
+            sleep(a.delay.tv_sec + (a.delay.tv_sec < INT_MAX));
+        current[1] = current[0];
+        utime[1] = utime[0];
+    }
+    return EXIT_SUCCESS;
+error:
+    if (a.argv != NULL)
+        waitpid(a.pid, NULL, 0);
+    return EXIT_FAILURE;
+}
diff --git a/sleep_some_work_some.c b/sleep_some_work_some.c
new file mode 100644 (file)
index 0000000..283e200
--- /dev/null
@@ -0,0 +1,30 @@
+#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;
+}