projekt

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit a4c729337cb393d52a1b379354cb12e7428a1767
parent bec74fc8413ba79d7bf367e9c421692299d1955d
Author: jbitta <bittara3@uniba.sk>
Date:   Mon, 16 Jun 2025 16:58:33 +0200

2. cast

Diffstat:
MMakefile | 40++++++++++++++++++++++------------------
MREADME.md | 104+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Mmain.c | 370+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Mprocess_exit.bpf.c | 10++++++++++
Astats.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()