+
+// 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;
+}