commit a4c729337cb393d52a1b379354cb12e7428a1767
parent bec74fc8413ba79d7bf367e9c421692299d1955d
Author: jbitta <bittara3@uniba.sk>
Date: Mon, 16 Jun 2025 16:58:33 +0200
2. cast
Diffstat:
M | Makefile | | | 40 | ++++++++++++++++++++++------------------ |
M | README.md | | | 104 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------- |
M | main.c | | | 370 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------- |
M | process_exit.bpf.c | | | 10 | ++++++++++ |
A | stats.py | | | 231 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
5 files changed, 661 insertions(+), 94 deletions(-)
diff --git a/Makefile b/Makefile
@@ -1,16 +1,30 @@
ARCH=$(shell uname -m | sed 's/x86_64/x86/' | sed 's/aarch64/arm64/')
-run: main
- sudo ./main
+# set this variable (e.g. to 1) if you have older libbpf version (<0.6.0)
+SET_OLD_LIBBPF?=0
+
+ifdef SET_OLD_LIBBPF
+OLD_LIBBPF=-DOLD_LIBBPF
+endif
main: main.c process_exit.skel.h
- gcc -Wall -o main main.c -L../libbpf/src -l:libbpf.a -lelf -lz
+ gcc $(OLD_LIBBPF) -Wall -o main main.c -lbpf -lelf -lz
-website: webpage/index.html
- scp webpage/index.html bittara3@davinci.fmph.uniba.sk:~/public_html/index.html
+run: main
+ sudo ./main
+
+KERNEL ?= /lib/modules/$(shell uname -r)/build/
+LINUXINCLUDE += -I$(KERNEL)/arch/$(ARCH)/include/generated/uapi
+LINUXINCLUDE += -I$(KERNEL)/arch/$(ARCH)/include/generated
+LINUXINCLUDE += -I$(KERNEL)/arch/$(ARCH)/include
+LINUXINCLUDE += -I$(KERNEL)/arch/$(ARCH)/include/uapi
+LINUXINCLUDE += -I$(KERNEL)/include
+LINUXINCLUDE += -I$(KERNEL)/include/uapi
+LINUXINCLUDE += -include $(KERNEL)/include/linux/kconfig.h
+LINUXINCLUDE += -I$(KERNEL)/include/generated/uapi
process_exit.bpf.o: process_exit.bpf.c vmlinux.h
- clang \
+ clang $(LINUXINCLUDE) \
-target bpf \
-D __TARGET_ARCH_$(ARCH) \
-I/usr/include/$(shell uname -m)-linux-gnu \
@@ -23,16 +37,6 @@ vmlinux.h:
bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
clean:
- rm process_exit.bpf.o
- rm main
-
-source:
- mkdir -p source-c
- cp main.c process_exit.h process_exit.bpf.c source-c
- tar czf source-c.tar.gz source-c/
-
-# gcc -Wall -o main main.c -L../libbpf/src -l:libbpf.a -lelf -lz
-
-# bpf-gcc -I/usr/include/ -mxbpf exit.bpf.c -o exit.bpf.o
+ rm -f process_exit.bpf.o main vmlinux.h process_exit.skel.h
-.PHONY: run website clean source
+.PHONY: run clean
diff --git a/README.md b/README.md
@@ -1,22 +1,106 @@
-# Dependencie
+# eBPF nástroj
-Na debianových systémoch
+## Dependencie
+
+Ubuntu (a debianové systémy)
```bash
-sudo apt install bpfcc-tools python3-bpfcc libbpfcc libbpfcc-dev
+sudo apt install clang libbpf-dev linux-tools-$(uname -r) gcc make linux-headers-$(uname -r)
```
-Pre iné systémy sú inštrukcie tu: https://github.com/iovisor/bcc/blob/master/INSTALL.md
+CentOS
+```bash
+sudo dnf install bpftool libbpf-devel clang gcc make kernel-headers
+```
-# Spustenie
+## Kompilácia
```bash
make
```
+
+## Spustenie
+```bash
+sudo ./main
+```
+alebo
+```
+make run
+```
+
+## Prepínače
+
+- `-h` - help
+- `-c, --csv` - výstup vo formáte csv
+- `-f FILE, --file FILE` - výstup zapíš do súbora `FILE`
+- `-F param1,param2,...`, `--filter param1,param2,...` - filtruje parametre
+- `-C, --cumulative` - vypíš kumulatívne hodnoty pre `utime`, `stime`,
+ `inblock`, `oublock`, odfiltruje `cutime`, `cstime`, `cinblock`,
+ `coublock`
+- `-u UID, --uid UID` - vypíš údaje len od procesov, ktoré vlastní
+ používateľ s uid UID
+
+povolené hodnoty parametrov pre filter:
+- pcomm
+- tgid
+- pid
+- ppid
+- uid
+- age
+- utime
+- stime
+- exit
+- exitsig
+- nvcs
+- nivcs
+- cutime
+- cstime
+- inblock
+- oublock
+- cinblock
+- coublock
+
+## Príklad použitia
+```bash
+sudo ./main -F pcomm,ppid,utime,exit
+```
+
+# Štatistický program
+
+## Spustenie
+```bash
+python stats.py [FILE]
+```
alebo
```bash
-python3 main.py # alebo ./main.py
+./stats.py [FILE]
```
-# Prepínače
-`-h` - help
-`-c, --csv` - výstup vo formáte csv
-`-f FILE, --file FILE` - výstup zapíš do súbora `FILE`
+## Prepínače
+
+- `-h` - help
+- `-i N` - veľkosť intervalu v histograme (defaultne 1)
+- `-r {linear,exponential}` - rýchlosť rastu veľkosti intervalu v
+ histograme (defaultne linear)
+- `-f FILTER` - filter pre histogramy, čiarkou oddelený zoznam
+ parametrov
+
+FILTER má tvar `param1,param2,...,paramN`, napr. `age,utime,stime`,
+povolené hodnoty parametrov sú:
+- age
+- utime
+- stime
+- nvcsw
+- nivcsw
+- cutime
+- cstime
+- inblk
+- oublk
+- cinblk
+- coublk
+- uid
+- exit
+- exitsig
+
+## Príklad použitia
+```bash
+python stats.py -i 10 -r linear zaznam.txt
+```
diff --git a/main.c b/main.c
@@ -1,13 +1,36 @@
-#include <stdio.h>
-#include <unistd.h>
-#include <getopt.h>
#include <errno.h>
+#include <getopt.h>
+#include <signal.h>
#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/wait.h>
+#include <sys/utsname.h>
+#include <time.h>
+#include <unistd.h>
#include <bpf/libbpf.h>
#include "process_exit.h"
#include "process_exit.skel.h"
-/* #define x x */
+#define PCOMM_FLAG 0x00001
+#define TGID_FLAG 0x00002
+#define PID_FLAG 0x00004
+#define PPID_FLAG 0x00008
+#define UID_FLAG 0x00010
+#define AGE_FLAG 0x00020
+#define UTIME_FLAG 0x00040
+#define STIME_FLAG 0x00080
+#define EXIT_FLAG 0x00100
+#define EXITSIG_FLAG 0x00200
+#define NVCS_FLAG 0x00400
+#define NIVCS_FLAG 0x00800
+#define CUTIME_FLAG 0x01000
+#define CSTIME_FLAG 0x02000
+#define INBLOCK_FLAG 0x04000
+#define OUBLOCK_FLAG 0x08000
+#define CINBLOCK_FLAG 0x10000
+#define COUBLOCK_FLAG 0x20000
+#define TIME_FLAG 0x40000
static int libbpf_print_fn(enum libbpf_print_level level,
const char* format, va_list args) {
@@ -19,20 +42,79 @@ static int libbpf_print_fn(enum libbpf_print_level level,
static int csv = 0;
static FILE* output_file = 0;
-static uint32_t parameter_flags = 0;
+static uint32_t parameter_flags = 0x7ffff; // defaultne vsetky parametre
+static int enabled_flag_count = 0;
+static int current_flags_printed = 1;
+static uint64_t processes_handled = 0;
+static int cumulative = 0;
+static volatile __sig_atomic_t running = 1;
+static uint32_t filtered_uid = 0;
+
+static void parse_filter_flags(char* flag_string) {
+ const char* token = strtok(flag_string, ",");
+ while (token) {
+ if (strcmp(token, "pcomm") == 0) parameter_flags |= PCOMM_FLAG;
+ else if (strcmp(token, "tgid") == 0) parameter_flags |= TGID_FLAG;
+ else if (strcmp(token, "pid") == 0) parameter_flags |= PID_FLAG;
+ else if (strcmp(token, "ppid") == 0) parameter_flags |= PPID_FLAG;
+ else if (strcmp(token, "uid") == 0) parameter_flags |= UID_FLAG;
+ else if (strcmp(token, "age") == 0) parameter_flags |= AGE_FLAG;
+ else if (strcmp(token, "utime") == 0) parameter_flags |= UTIME_FLAG;
+ else if (strcmp(token, "stime") == 0) parameter_flags |= STIME_FLAG;
+ else if (strcmp(token, "exit") == 0) parameter_flags |= EXIT_FLAG;
+ else if (strcmp(token, "exitsig") == 0) parameter_flags |= EXITSIG_FLAG;
+ else if (strcmp(token, "nvcs") == 0) parameter_flags |= NVCS_FLAG;
+ else if (strcmp(token, "nivcs") == 0) parameter_flags |= NIVCS_FLAG;
+ else if (strcmp(token, "cutime") == 0) parameter_flags |= CUTIME_FLAG;
+ else if (strcmp(token, "cstime") == 0) parameter_flags |= CSTIME_FLAG;
+ else if (strcmp(token, "inblock") == 0) parameter_flags |= INBLOCK_FLAG;
+ else if (strcmp(token, "oublock") == 0) parameter_flags |= OUBLOCK_FLAG;
+ else if (strcmp(token, "cinblock") == 0) parameter_flags |= CINBLOCK_FLAG;
+ else if (strcmp(token, "coublock") == 0) parameter_flags |= COUBLOCK_FLAG;
+ else fprintf(stderr, "Unknown parameter '%s'\n", token);
+
+ token = strtok(NULL, ",");
+ }
+}
+
+static void print_help(char* argv[]) {
+ fprintf(stdout,
+ "Usage: %s [OPTION]...\n"
+ "Print information about terminated processes.\n"
+ "\n"
+ "Options:\n"
+ " -c, --csv use csv format\n"
+ " -f FILE, --file FILE print to file FILE\n"
+ " -F params..., --filter params... filter process information based on\n"
+ " params\n"
+ " -u UID, --uid UID show only processes owened by user\n"
+ " with uid UID\n"
+ " -C, --cumulative show cumulative data for utime,\n"
+ " stime, inblock, oublock, filters\n"
+ " out cutime, cstime, cinblock,\n"
+ " coublock\n"
+ " -h, --help print this help message and exit\n"
+ "\n"
+ "Filter parameters:\n"
+ " pcomm, tgid, pid, ppid, uid, age, utime, stime, exit, exitsig, nvcs,\n"
+ " nivcs, cutime, cstime, inblock, oublock, cinblock, coublock\n",
+ argv[0]);
+}
-void parse_options(int argc, char* argv[], const char** file) {
+static void parse_options(int argc, char* argv[], const char** file) {
static struct option long_options[] = {
- { "csv" , no_argument, 0, 'c' },
- { "file" , required_argument, 0, 'f' },
- { "filter", required_argument, 0, 'F' },
- { "help" , no_argument, 0, 'h' },
- { 0 , 0, 0, 0 },
+ { "csv" , no_argument, 0, 'c' },
+ { "file" , required_argument, 0, 'f' },
+ { "filter" , required_argument, 0, 'F' },
+ { "cumulative" , no_argument, 0, 'C' },
+ { "help" , no_argument, 0, 'h' },
+ { "uid" , required_argument, 0, 'u' },
+ { 0 , 0, 0, 0 },
};
int ret = EXIT_FAILURE;
int opt;
- while ((opt = getopt_long(argc, argv, "cf:hF:", long_options, NULL)) != -1) {
+ while ((opt = getopt_long(argc, argv, "cf:hF:Cu:", long_options, NULL)) != -1) {
switch (opt) {
case 'c':
csv = 1;
@@ -41,54 +123,153 @@ void parse_options(int argc, char* argv[], const char** file) {
*file = optarg;
break;
case 'F':
+ parameter_flags = 0;
+ parameter_flags |= TIME_FLAG; // zatial defaultne nechame TIME_FLAG
+ parse_filter_flags(optarg);
+ break;
+ case 'C':
+ cumulative = 1;
+ break;
+ case 'u':
+ errno = 0;
+ filtered_uid = strtoul(optarg, NULL, 10);
+ if (errno != 0 || optarg[0] == '-') {
+ fprintf(stderr, "Invalid UID after -u.\n");
+ exit(EXIT_FAILURE);
+ }
break;
case 'h':
ret = EXIT_SUCCESS;
default:
- fprintf(stderr, "Usage: %s [-c] [-f file]\n", argv[0]);
+ print_help(argv);
exit(ret);
}
}
}
-void handle_event(void* ctx, int cpu, void* data, unsigned int data_sz) {
- const struct data_t* event = (struct data_t*)data;
+static void print_int_parameter2(__u64 parameter, const char* format) {
+ if (csv) {
+ format = "%d";
+ if (current_flags_printed < enabled_flag_count) {
+ format = "%d,";
+ current_flags_printed++;
+ } else {
+ current_flags_printed = 1;
+ }
+ }
+ fprintf(output_file, format, parameter);
+}
+
+static void print_int_parameter(__u64 parameter) {
+ print_int_parameter2(parameter, "%-7d\t");
+}
+
+static void print_double_parameter(double parameter) {
+ const char* format = "%-7.3f\t";
+ if (csv) {
+ format = "%.3f";
+ if (current_flags_printed < enabled_flag_count) {
+ format = "%.3f,";
+ current_flags_printed++;
+ } else {
+ current_flags_printed = 1;
+ }
+ }
+ fprintf(output_file, format, parameter);
+}
+
+static void print_pcomm_parameter(const char* parameter) {
+ const char* format = "%-16s";
+ if (csv) {
+ format = "%s";
+ if (current_flags_printed < enabled_flag_count) {
+ format = "%s,";
+ current_flags_printed++;
+ } else {
+ current_flags_printed = 1;
+ }
+ }
+ fprintf(output_file, format, parameter);
+}
+
+static void print_string_parameter(const char* parameter) {
+ const char* format = "%s\t";
+ if (csv) {
+ format = "%s";
+ if (current_flags_printed < enabled_flag_count) {
+ format = "%s,";
+ current_flags_printed++;
+ } else {
+ current_flags_printed = 1;
+ }
+ }
+ fprintf(output_file, format, parameter);
+}
+
+static void print_time_parameter(time_t sec, time_t ns) {
+ const char* format = "%d.%06d\t";
+ if (csv) {
+ format = "%d.%06d";
+ if (current_flags_printed < enabled_flag_count) {
+ format = "%d.%06d,";
+ current_flags_printed++;
+ } else {
+ current_flags_printed = 1;
+ }
+ }
+ fprintf(output_file, format, sec, ns / 1000);
+}
+
+static void handle_event(void* ctx, int cpu, void* data, unsigned int data_sz) {
+ struct data_t* event = (struct data_t*)data;
(void)ctx;
(void)cpu;
(void)data_sz;
- const char* output_format = "%-16s %-7d %-7d %-7d %-7d %-7.3f %-7.3f %-7.3f %-7d %-7d %-7d %-7d %-7.3f %-7.3f %-7d %-7d %-7d %-7d\n";
- if (csv) {
- output_format = "%s,%d,%d,%d,%d,%.3f,%.3f,%.3f,%d,%d,%d,%d,%.3f,%.3f,%d,%d,%d,%d\n";
+ if (filtered_uid != 0 && filtered_uid != event->uid) {
+ return;
}
- float age = (event->exit_time - event->start_time) / 1e9;
+ if (cumulative) {
+ event->utime += event->cutime;
+ event->stime += event->cstime;
+ event->inblock += event->cinblock;
+ event->oublock += event->coublock;
+ }
- fprintf(output_file,
- output_format,
- event->task,
- event->tgid,
- event->pid,
- event->ppid,
- event->uid,
- age,
- event->utime / 1e9,
- event->stime / 1e9,
- event->exit_code,
- event->exit_signal,
- event->nvcsw,
- event->nivcsw,
- event->cutime / 1e9,
- event->cstime / 1e9,
- event->inblock,
- event->oublock,
- event->cinblock,
- event->coublock);
+ processes_handled++;
+
+ struct timespec tm;
+ if (clock_gettime(CLOCK_REALTIME, &tm) == -1) {
+ perror("clock_gettime");
+ exit(EXIT_FAILURE);
+ }
+
+ if (parameter_flags & TIME_FLAG ) print_time_parameter(tm.tv_sec, tm.tv_nsec);
+ if (parameter_flags & PCOMM_FLAG ) print_pcomm_parameter(event->task);
+ if (parameter_flags & TGID_FLAG ) print_int_parameter(event->tgid);
+ if (parameter_flags & PID_FLAG ) print_int_parameter(event->pid);
+ if (parameter_flags & PPID_FLAG ) print_int_parameter(event->ppid);
+ if (parameter_flags & UID_FLAG ) print_int_parameter(event->uid);
+ if (parameter_flags & AGE_FLAG ) print_double_parameter((event->exit_time - event->start_time) / 1e9);
+ if (parameter_flags & UTIME_FLAG ) print_double_parameter(event->utime / 1e9);
+ if (parameter_flags & STIME_FLAG ) print_double_parameter(event->stime / 1e9);
+ if (parameter_flags & EXIT_FLAG ) print_int_parameter(WIFEXITED(event->exit_code) ? WEXITSTATUS(event->exit_code) : -1);
+ if (parameter_flags & EXITSIG_FLAG ) print_int_parameter(WIFSIGNALED(event->exit_code) ? WTERMSIG(event->exit_code) : -1);
+ if (parameter_flags & NVCS_FLAG ) print_int_parameter(event->nvcsw);
+ if (parameter_flags & NIVCS_FLAG ) print_int_parameter(event->nivcsw);
+ if (parameter_flags & CUTIME_FLAG ) print_double_parameter(event->cutime / 1e9);
+ if (parameter_flags & CSTIME_FLAG ) print_double_parameter(event->cstime / 1e9);
+ if (parameter_flags & INBLOCK_FLAG ) print_int_parameter(event->inblock);
+ if (parameter_flags & OUBLOCK_FLAG ) print_int_parameter(event->oublock);
+ if (parameter_flags & CINBLOCK_FLAG) print_int_parameter(event->cinblock);
+ if (parameter_flags & COUBLOCK_FLAG) print_int_parameter(event->coublock);
+ fprintf(output_file, "\n");
fflush(output_file);
}
-void lost_event(void* ctx, int cpu, long long unsigned cnt) {
+static void lost_event(void* ctx, int cpu, long long unsigned cnt) {
(void)ctx;
(void)cpu;
(void)cnt;
@@ -96,41 +277,76 @@ void lost_event(void* ctx, int cpu, long long unsigned cnt) {
printf("lost event\n");
}
-void print_header(void) {
- const char* output_format = "%-16s %-7s %-7s %-7s %-7s %-7s %-7s %-7s %-7s %-7s %-7s %-7s %-7s %-7s %-7s %-7s %-7s %-7s\n";
- if (csv) {
- output_format = "%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s\n";
- }
- fprintf(output_file, output_format,
- "PCOMM",
- "TGID",
- "PID",
- "PPID",
- "UID",
- "age",
- "utime",
- "stime",
- "exit",
- "exitsig",
- "nvcs",
- "nivcs",
- "cutime",
- "cstime",
- "inblock",
- "oublock",
- "cinblock",
- "coublock");
+static void print_header(void) {
+ time_t start_time = time(NULL);
+ fprintf(output_file, "start timestamp: %s", ctime(&start_time));
+
+ struct utsname sysinfo;
+ if (uname(&sysinfo) == -1) {
+ perror("uname");
+ exit(EXIT_FAILURE);
+ }
+
+ fprintf(output_file,
+ "system info: %s %s %s %s %s\n",
+ sysinfo.sysname,
+ sysinfo.nodename,
+ sysinfo.release,
+ sysinfo.version,
+ sysinfo.machine);
+
+ if (parameter_flags & TIME_FLAG ) print_string_parameter("TIME");
+ if (parameter_flags & PCOMM_FLAG ) print_pcomm_parameter("PCOMM");
+ if (parameter_flags & TGID_FLAG ) print_string_parameter("TGID");
+ if (parameter_flags & PID_FLAG ) print_string_parameter("PID");
+ if (parameter_flags & PPID_FLAG ) print_string_parameter("PPID");
+ if (parameter_flags & UID_FLAG ) print_string_parameter("UID");
+ if (parameter_flags & AGE_FLAG ) print_string_parameter("age");
+ if (parameter_flags & UTIME_FLAG ) print_string_parameter("utime");
+ if (parameter_flags & STIME_FLAG ) print_string_parameter("stime");
+ if (parameter_flags & EXIT_FLAG ) print_string_parameter("exit");
+ if (parameter_flags & EXITSIG_FLAG ) print_string_parameter("exitsig");
+ if (parameter_flags & NVCS_FLAG ) print_string_parameter("nvcsw");
+ if (parameter_flags & NIVCS_FLAG ) print_string_parameter("nivcsw");
+ if (parameter_flags & CUTIME_FLAG ) print_string_parameter("cutime");
+ if (parameter_flags & CSTIME_FLAG ) print_string_parameter("cstime");
+ if (parameter_flags & INBLOCK_FLAG ) print_string_parameter("inblk");
+ if (parameter_flags & OUBLOCK_FLAG ) print_string_parameter("oublk");
+ if (parameter_flags & CINBLOCK_FLAG) print_string_parameter("cinblk");
+ if (parameter_flags & COUBLOCK_FLAG) print_string_parameter("coublk");
+ fprintf(output_file, "\n");
+
fflush(output_file);
}
+static void catch_interrupt(int signal) {
+ (void)signal;
+ running = 0;
+}
+
int main(int argc, char* argv[]) {
output_file = stdout;
const char* file_name = NULL;
parse_options(argc, argv, &file_name);
+ if ((enabled_flag_count = __builtin_popcount(parameter_flags)) == 0) {
+ fprintf(stderr, "No valid parameters in filter\n");
+ exit(EXIT_FAILURE);
+ }
+
+ if (cumulative) {
+ // pri kumulativnom vypise nahradime hodnoty suctom
+ // rodicovskych a dcerskych hodnot, preto nebudeme vypisovat
+ // cutime, cstime, atd.
+ parameter_flags &= ~CUTIME_FLAG;
+ parameter_flags &= ~CSTIME_FLAG;
+ parameter_flags &= ~CINBLOCK_FLAG;
+ parameter_flags &= ~COUBLOCK_FLAG;
+ enabled_flag_count -= 4;
+ }
if (getuid() != 0) {
- fprintf(stderr, "You should run this program with root privileges.\n");
+ fprintf(stderr, "You should run this program with root privileges (sudo).\n");
exit(EXIT_FAILURE);
}
@@ -149,12 +365,23 @@ int main(int argc, char* argv[]) {
return 1;
}
+#ifndef OLD_LIBBPF
struct perf_buffer* pb = perf_buffer__new(bpf_map__fd(skel->maps.output),
8,
handle_event,
lost_event,
NULL,
NULL);
+#else
+ const struct perf_buffer_opts opts = {
+ handle_event,
+ lost_event,
+ NULL
+ };
+ struct perf_buffer* pb = perf_buffer__new(bpf_map__fd(skel->maps.output),
+ 8,
+ &opts);
+#endif
if (!pb) {
err = -1;
fprintf(stderr, "Failed to create ring buffer\n");
@@ -162,6 +389,12 @@ int main(int argc, char* argv[]) {
return -err;
}
+ if (signal(SIGINT, catch_interrupt) == SIG_ERR || signal(SIGTERM, catch_interrupt) == SIG_ERR) {
+ fprintf(stderr, "Can't set signal handler: %s\n", strerror(errno));
+ err = 1;
+ running = 0;
+ }
+
int writing_to_file = 0;
if (file_name != NULL && strcmp(file_name, "-") != 0) {
output_file = fopen(file_name, "w+");
@@ -169,7 +402,7 @@ int main(int argc, char* argv[]) {
}
print_header();
- while (1) {
+ while (running) {
err = perf_buffer__poll(pb, 1000);
if (err == -EINTR) {
err = 0;
@@ -181,6 +414,11 @@ int main(int argc, char* argv[]) {
}
}
+ printf("\nHandled %lu processes.\n", processes_handled);
+ time_t end_time = time(NULL);
+ fprintf(output_file, "end timestamp: %s", ctime(&end_time));
+ fflush(output_file);
+
if (writing_to_file) {
fclose(output_file);
}
diff --git a/process_exit.bpf.c b/process_exit.bpf.c
@@ -1,5 +1,6 @@
#include "vmlinux.h"
#include <bpf/bpf_tracing.h>
+#include <bpf/bpf_helpers.h>
#include "process_exit.h"
struct {
@@ -43,6 +44,15 @@ int sched_process_exit(void* ctx)
bpf_probe_read_kernel(&data.cinblock, sizeof(data.cinblock), &sig->cinblock);
bpf_probe_read_kernel(&data.coublock, sizeof(data.coublock), &sig->coublock);
+ struct task_io_accounting* ioac = NULL;
+ bpf_probe_read_kernel(&ioac, sizeof(struct ioac_accounting*), &task->ioac);
+ __u64 read = 0;
+ __u64 written = 0;
+ bpf_probe_read_kernel(&read, sizeof(read), &ioac->read_bytes);
+ bpf_probe_read_kernel(&written, sizeof(written), &ioac->write_bytes);
+ data.inblock += read >> 9;
+ data.oublock += written >> 9;
+
bpf_get_current_comm(&data.task, sizeof(data.task));
bpf_perf_event_output(ctx, &output, BPF_F_CURRENT_CPU,
diff --git a/stats.py b/stats.py
@@ -0,0 +1,231 @@
+#!/usr/bin/env python3
+
+import argparse
+from enum import Enum
+from decimal import Decimal
+from statistics import mean, median, stdev
+
+
+def get_parser():
+ parser = argparse.ArgumentParser(
+ description="Create statistics out of process records file",
+ )
+ parser.add_argument("file", help="process records file")
+ parser.add_argument("-i", "--increment",
+ default=1,
+ type=Decimal,
+ help="size of a single histogram interval")
+ parser.add_argument("-r", "--rate",
+ default="linear",
+ choices=["linear", "exponential"],
+ help="set growth rate of histogram intervals")
+ parser.add_argument("-R", "--raw",
+ action="store_true",
+ default=False,
+ help="output just histograms, in csv format for further processing")
+ parser.add_argument("-f", "--filter",
+ default="all",
+ help="comma separated list of histograms to show (age,stime,...)")
+ return parser
+
+
+class HistogramRate(Enum):
+ EXPONENTIAL = 1
+ LINEAR = 2
+
+
+def histogram(data, /, start=0.001, step=2, rate=HistogramRate.EXPONENTIAL):
+ data = sorted(data)
+ left, right = sorted([0, start])
+ histogram_data = {}
+
+ i = 0
+ while i < len(data):
+ if left <= data[i] < right:
+ histogram_data[(left, right)] = histogram_data.get((left, right), 0) + 1
+ i += 1
+ else:
+ if rate == HistogramRate.EXPONENTIAL:
+ size = right - left
+ left = right
+ right += step * size
+ elif rate == HistogramRate.LINEAR:
+ left = right
+ right += step
+ else:
+ raise ValueError(f"Invalid rate type: {rate}")
+
+ return histogram_data
+
+
+def visualize_histogram(histogram):
+ m = max(histogram.values())
+ max_width = 60
+ pad = max(map(lambda s: 1 + len(str(s[0])) + 2 + len(str(s[1])) + 1, histogram.keys()))
+
+ for (left, right), v in histogram.items():
+ bar_width = int(max_width * v / m)
+ print(f"{f'[{left}, {right})':{pad}} {bar_width * '█'} {v}")
+
+
+def retype(data, header):
+ if header == "PCOMM":
+ return data
+
+ if header in {"TIME", "age", "utime", "stime", "cutime", "cstime"}:
+ return Decimal(data)
+
+ return int(data)
+
+
+def averageable(header):
+ return header in {
+ "age",
+ "utime",
+ "stime",
+ "nvcsw",
+ "nivcsw",
+ "cutime",
+ "cstime",
+ "inblk",
+ "oublk",
+ "cinblk",
+ "coublk",
+ }
+
+
+def unit(header):
+ times = {
+ "age",
+ "utime",
+ "stime",
+ "cutime",
+ "cstime",
+ }
+
+ if header in times:
+ return 's'
+
+ return ''
+
+
+def medianable(header):
+ return averageable(header)
+
+
+def histogramable(header):
+ return averageable(header) or header in {"UID", "exit", "exitsig"}
+
+
+def print_pcomm_stats(data):
+ pcomm_stats = {}
+ for d in data:
+ pcomm_stats[d["PCOMM"]] = pcomm_stats.get(d["PCOMM"], 0) + 1
+
+ pcomm_intensive = {}
+ for d in data:
+ prev_i, prev_p = pcomm_intensive.get(d["PCOMM"], (0, 0))
+ if is_process_intensive(d):
+ prev_i += 1
+ else:
+ prev_p += 1
+ pcomm_intensive[d["PCOMM"]] = (prev_i, prev_p)
+
+ m = max(map(len, pcomm_stats.keys()))
+ for program, count in sorted(pcomm_stats.items(), key=lambda p: p[::-1], reverse=True):
+ intensive, passive = pcomm_intensive[program]
+ print(f"{f'{program}':{m}} {count}\t({intensive}/{passive})")
+
+
+def is_process_intensive(data):
+ age = data["age"]
+ utime = data["utime"]
+ stime = data["stime"]
+ return age > 0.1 and (utime + stime) / age > 0.3
+
+
+def main():
+ parser = get_parser()
+ args = parser.parse_args()
+
+ if args.rate == "exponential" and args.increment <= 1:
+ print("Exponential rate can't have increment <= 1\n")
+ parser.print_help()
+ return
+
+ log_filename = args.file
+
+ lines = []
+ with open(log_filename, 'r') as f:
+ lines = f.readlines()
+
+ data = []
+
+ headers = lines[2].strip().split()
+ delim = None
+ if len(headers) == 1:
+ delim = ','
+ headers = lines[2].strip(delim).split()
+
+ for line in lines[3:-3]:
+ time, line = line.split(maxsplit=1)
+ line_tokens = line.strip(delim).rsplit(maxsplit=len(headers) - 2)
+ line_tokens = [time] + line_tokens
+
+ process_data = {}
+ for i, v in enumerate(headers):
+ process_data[v] = retype(line_tokens[i], v)
+
+ data.append(process_data)
+
+ filtered = list(map(str.lower, args.filter.split(',')))
+ if args.raw:
+ for header in headers:
+ if histogramable(header) and (header.lower() in filtered or args.filter == "all"):
+ print(f"{header}_from,{header}_to,count")
+ h = histogram([d[header] for d in data], start=-1 if not averageable(header) else args.increment,
+ step=args.increment,
+ rate=HistogramRate[args.rate.upper()])
+ for (start, end), count in h.items():
+ print(f"{start},{end},{count}")
+ print()
+ return
+
+ print(f"Number of processes handled: {len(data)}")
+ print(lines[0].strip())
+ print(lines[-1].strip())
+ print(lines[1].strip())
+
+ operations = [
+ (averageable, mean, "Average"),
+ (medianable, median, "Median"),
+ (averageable, min, "Minimal"),
+ (averageable, max, "Maximal"),
+ (averageable, stdev, "Standard deviation for"),
+ ]
+
+ for condition, operation, label in operations:
+ print()
+ for header in headers:
+ if condition(header):
+ print(f"{label} {header}: {operation([d[header] for d in data]): .3f}{unit(header)}")
+
+ # histograms
+ for header in headers:
+ if histogramable(header) and (header in filtered or args.filter == "all"):
+ print()
+ print(f"{header.capitalize()} histogram:")
+ h = histogram([d[header] for d in data], start=-1 if not averageable(header) else args.increment,
+ step=args.increment,
+ rate=HistogramRate[args.rate.upper()])
+ visualize_histogram(h)
+
+ # pcomm stats
+ if "pcomm" in filtered or args.filter == "all":
+ print()
+ print("PCOMM stats:")
+ print_pcomm_stats(data)
+
+
+if __name__ == "__main__":
+ main()