From: mar77i Date: Sat, 21 Feb 2026 13:37:31 +0000 (+0100) Subject: initial commit X-Git-Url: https://git.mar77i.info/?a=commitdiff_plain;h=HEAD;p=perftrace initial commit --- fefdc7b26f4e53b101f8a404b952081da4dd8d8f diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3f6a3ae --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.o +perftrace +sleep_some_work_some diff --git a/Makefile b/Makefile new file mode 100644 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 index 0000000..92175d8 --- /dev/null +++ b/global.mk @@ -0,0 +1,35 @@ + +# global.mk +# +# Copyright (c) 2017, mar77i +# +# 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 index 0000000..1fd11e9 --- /dev/null +++ b/perftrace.c @@ -0,0 +1,302 @@ + +// perftrace2.c + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 index 0000000..283e200 --- /dev/null +++ b/sleep_some_work_some.c @@ -0,0 +1,30 @@ +#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; +}