diff options
author | Franciszek Malinka <franciszek.malinka@gmail.com> | 2022-05-02 01:06:19 +0200 |
---|---|---|
committer | Franciszek Malinka <franciszek.malinka@gmail.com> | 2022-05-02 01:06:19 +0200 |
commit | 45541328b12800591e947b8932bb70f8309cff7a (patch) | |
tree | acfe29906101494787802f17af168df38aafb1f8 |
Initial commit
-rw-r--r-- | Makefile | 26 | ||||
-rw-r--r-- | daemonize.c | 190 | ||||
-rw-r--r-- | mystat.c | 249 |
3 files changed, 465 insertions, 0 deletions
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5c2a9ab --- /dev/null +++ b/Makefile @@ -0,0 +1,26 @@ +CC = gcc +CFLAGS = -std=gnu17 -g -Wall -Wshadow -Wextra -fsanitize=address -fsanitize=undefined + +C_FILES = $(wildcard *.c) +H_FILES = $(wildcard *.h) +O_FILES = $(C_FILES:%.c=build/%.o) + +TARGET_APP = mystat + +.PHONY = all clean +.DEFAULT = all + +all: $(TARGET_APP) + +build: + @mkdir -p build + +build/%.o: %.c $(H_FILES) | build + $(CC) $(CFLAGS) -c $< -o $@ + +$(TARGET_APP): $(O_FILES) + $(CC) $(CFLAGS) -o $@ $^ + +clean: + -rm -f $(TARGET_APP) + -rm -rf build diff --git a/daemonize.c b/daemonize.c new file mode 100644 index 0000000..d15963c --- /dev/null +++ b/daemonize.c @@ -0,0 +1,190 @@ +/* Copyright 2022 Franciszek Malinka + * daemonize.c + * + * SysV daemonization procedure based on daemon(7) + * + * "Grandchildren are the worst daemons" ~ TWi */ + + +#include <unistd.h> +#include <string.h> +#include <stdio.h> +#include <stdint.h> +#include <sys/wait.h> +#include <sys/resource.h> +#include <sys/stat.h> +#include <errno.h> +#include <signal.h> +#include <stdlib.h> +#include <fcntl.h> + +#define SUCCESS 0 +#define STREAM_REDIR_ERROR 1 +#define CHDIR_ERROR 2 +#define PERMISSIONS_ERROR 3 +#define OTHER_DEAMON_ERROR 4 + +static void close_redundant_fds() { + uint i; + struct rlimit fd_lim; + if (getrlimit(RLIMIT_NOFILE, &fd_lim)) { + fprintf(stderr, "Getrlimit error: %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + + for (i = 3; i < fd_lim.rlim_cur; i++) { + close(i); + } +} + +static void setup_signals() { + int i = 0; + for (i = 0; i < _NSIG; i++) { + signal(i, SIG_DFL); + } + + sigset_t sig_mask; + sigemptyset(&sig_mask); + if (sigprocmask(SIG_SETMASK, &sig_mask, NULL)) { + fprintf(stderr, "Sigprocmask error: %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } +} + +static int redirect_std_streams() { + int dev_null = open("/dev/null", O_RDWR); + if (dev_null == -1) { + return 1; + } + + if (dup2(dev_null, 0) == -1 || dup2(dev_null, 1) == -1 || dup2(dev_null, 2) == -1) { + return 1; + } + + return 0; +} + +/* This may look redundant, but it makes a pointer from a defined value */ +static void inform_grandparent(int grandparent_fd, int err) { + write(grandparent_fd, &err, 1); +} + +static void grandchild_prep(int grandparent_fd) { + int pidfd; + char *pidpath = "/run/mystat.pid"; + + if (redirect_std_streams()) { + inform_grandparent(grandparent_fd, STREAM_REDIR_ERROR); + exit(EXIT_FAILURE); + } + + umask(0); + + if (chdir("/")) { + inform_grandparent(grandparent_fd, CHDIR_ERROR); + exit(EXIT_FAILURE); + } + + /* There is a mystat daemon running */ + pidfd = open(pidpath, O_RDWR | O_CREAT, 0644); + if (pidfd < 0) { + inform_grandparent(grandparent_fd, PERMISSIONS_ERROR); + exit(EXIT_FAILURE); + } + if (lockf(pidfd, F_TLOCK, 0) < 0) { + inform_grandparent(grandparent_fd, OTHER_DEAMON_ERROR); + exit(EXIT_FAILURE); + } + + char str[30]; + sprintf(str, "%d\n", getpid()); + write(pidfd, str, strlen(str)); + + inform_grandparent(grandparent_fd, SUCCESS); +} + +static void child_prep(int pipefd[2]) { + pid_t pid; + + close(pipefd[0]); + if (setsid() == -1) { + fprintf(stderr, "Setsid error: %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + + pid = fork(); + if (pid == -1) { + fprintf(stderr, "Fork error: %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + + /* child */ + if (pid) + exit(EXIT_SUCCESS); + + grandchild_prep(pipefd[1]); +} + +static const char *error_reason(int err) { + const char *err_to_str[] = { + "Success", + "Standard stream redirections failed", + "Changing dir to / failed", + "Insuficient permissions", + "Other daemon is running", + }; + + if (err >= 0 && err < 5) { + return err_to_str[err]; + } + + return "Unknown error lol"; +} + +/* Prepare the process to run as a SysV daemon */ +int daemonize() { + pid_t pid; + int pipe_fd[2]; + int child_status; + uint8_t grandchild_status = 7; + + close_redundant_fds(); + setup_signals(); + pipe(pipe_fd); + + pid = fork(); + if (pid == -1) { + fprintf(stderr, "Fork error: %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + + if (!pid) { + child_prep(pipe_fd); + } + else { + close(pipe_fd[1]); + wait(&child_status); + if (child_status != EXIT_SUCCESS) { + fprintf(stderr, "Daemon creation failed.\n"); + exit(EXIT_FAILURE); + } + + /* Wait for grandchild_prep to finish */ + if (read(pipe_fd[0], &grandchild_status, 1) == -1) { + fprintf(stderr, "No message from grandchild before death :(\n"); + exit(EXIT_FAILURE); + } + if (grandchild_status != SUCCESS) { + fprintf(stderr, + "Daemonization failed in grandchild preparation.\nReason: %s\n", + error_reason(grandchild_status)); + exit(EXIT_FAILURE); + } + + exit(EXIT_SUCCESS); + } + + /* This can be only returned by a working grandchild daemon */ + close(pipe_fd[1]); + return 0; +} diff --git a/mystat.c b/mystat.c new file mode 100644 index 0000000..317f337 --- /dev/null +++ b/mystat.c @@ -0,0 +1,249 @@ +/* 2022 Franciszek Malinka + * + * mystat.c -- simple program that collects data from /proc/stat + * + * On my architecture the structure of the /proc/stat is as follows: + * cpu 200779 21 55244 4146211 5560 10264 4839 0 0 0 + * cpu0 27525 8 7218 515865 449 910 561 0 0 0 + * cpu1 26412 4 6853 515802 1706 1646 547 0 0 0 + * cpu2 26332 1 6736 517933 643 912 419 0 0 0 + * cpu3 21850 3 6661 519799 701 2603 753 0 0 0 + * cpu4 25237 1 7431 518646 555 970 427 0 0 0 + * cpu5 24193 0 6882 518962 466 1262 1172 0 0 0 + * cpu6 24494 1 6775 519317 578 1100 580 0 0 0 + * cpu7 24730 0 6686 519884 457 857 376 0 0 0 + * intr ..... + * ctxt 32023323 + * btime 1650997904 + * processes 30170 + * procs_running 2 + * procs_blocked 0 + * softirq 11416320 4647225 651176 5 112176 97363 0 199218 3774892 943 1933322 + * + * Atm I really care about the first 9 lines, in particular the first and third + * column which is the time spent in user and system respectively. + * + * */ + +#include <getopt.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <stdbool.h> +#include <errno.h> +#include <string.h> +#include <time.h> +#include <stdint.h> +#include <sys/resource.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <sys/param.h> +#include <signal.h> + +static int run_as_daemon = 0; +static char *logfile = "/var/log/mystat.log"; +static int log_fd = -1; +int daemonize(); + +static void sighup_handler(int signum) { + close(log_fd); + log_fd = open(logfile, O_CREAT | O_WRONLY | O_TRUNC, 0644); + + if (log_fd < 0) { + _exit(EXIT_FAILURE); + } +} + +static struct option long_options[] = { + {"daemon", no_argument, &run_as_daemon, 1}, + {"period", required_argument, 0, 'p'}, + {"interval", required_argument, 0, 'i'}, + {"logfile", required_argument, 0, 'f'}, + {"help", required_argument, 0, 'h'}, + {0, 0, 0, 0}, +}; + +void usage(char *prog_name) { + fprintf(stderr, + "Usage: %s [ -h ] [ -p period ] [ -i interval ] [ -f logfile ]\n", + prog_name); +} + +typedef uint32_t proc_time_t; + +typedef struct proc_data { + proc_time_t user_time; + proc_time_t nice_time; + proc_time_t system_time; + proc_time_t idle_time; +} proc_data_t; + +typedef struct log_data { + time_t time; + double min_load; + double max_load; + double avg_load; +} log_data_t; + +proc_time_t sum_work_time(proc_data_t *data) { + return data->idle_time + data->user_time + data->system_time + data->nice_time; +} + +void populate_log_data(log_data_t *log_data, proc_data_t *beg_data, proc_data_t *end_data) { + proc_time_t work_time = sum_work_time(end_data) - sum_work_time(beg_data); + double cur_workload = (double)1.0 + - ((double)(end_data->idle_time - beg_data->idle_time) + / (double)work_time); + + // printf("Current workload: %f\n", cur_workload); + // printf("%u %u\n", sum_work_time(end_data), sum_work_time(beg_data)); + // printf("%u %u\n", (end_data->idle_time - beg_data->idle_time),work_time); + + log_data->min_load = MIN(log_data->min_load, cur_workload); + log_data->max_load = MAX(log_data->max_load, cur_workload); + log_data->avg_load += cur_workload; +} + +void write_log_data(log_data_t *data) { + char buf[200]; + size_t buf_len; + + if (log_fd == -1) { + log_fd = open(logfile, O_CREAT | O_WRONLY | O_TRUNC, 0644); + if (log_fd < 0) { + fprintf(stderr, "Can't open logfile %s: %s\n", logfile, strerror(errno)); + exit(EXIT_FAILURE); + } + } + + buf_len = sprintf(buf, "%ld %.4f %.4f %.4f\n", + data->time, + data->avg_load, + data->min_load, + data->max_load); + write(log_fd, buf, buf_len); + + data->min_load = 1; + data->max_load = -1; + data->avg_load = 0; +} + +void gather_data(int period, int interval) { + int stat_fd; + const size_t BUF_SZ = 300; + char buf[BUF_SZ]; + size_t log_freq; + size_t stat_cnt = 0; + proc_data_t data, prev_data; + log_data_t log_data = { + .time = time(0), + .avg_load = 0, + .max_load = -1, + .min_load = 1 + }; + + prev_data.user_time = 0; + + log_freq = interval / period; + if (!log_freq) log_freq = 1; + + while (1) { + stat_fd = open("/proc/stat", O_RDONLY); + if (stat_fd < 0) { + fprintf (stderr, "Cannot open /proc/stat: %s", strerror(errno)); + exit(EXIT_FAILURE); + } + + if (read(stat_fd, buf, BUF_SZ) == -1) { + fprintf(stderr, "Read error: %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + + if (close(stat_fd) == -1) { + fprintf(stderr, "Close error: %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + + sscanf(buf, "cpu %u %u %u %u", + &data.user_time, + &data.nice_time, + &data.system_time, + &data.idle_time); + printf("cpu %u %u %u %u\n", + data.user_time, + data.nice_time, + data.system_time, + data.idle_time); + + stat_cnt++; + if (prev_data.user_time) + populate_log_data(&log_data, &prev_data, &data); + + if (stat_cnt == log_freq) { + log_data.avg_load /= (double)log_freq; + log_data.time = time(0); + write_log_data(&log_data); + stat_cnt = 0; + } + + prev_data = data; + sleep(period); + } + + /* This should be unreachable */ +} + +int main(int argc, char *argv[]) { + int c, option_idx; + int period = 1, interval = 60; + bool bad_option = false; + + while (1) { + c = getopt_long(argc, argv, "hp:i:f:", long_options, &option_idx); + if (c == -1) + break; + + switch(c) { + case 'p': + period = atoi(optarg); + if (!period) { + fprintf(stderr, "Invalid period argument: %s\n", optarg); + exit(EXIT_FAILURE); + } + break; + case 'i': + interval = atoi(optarg); + if (!interval) { + fprintf(stderr, "Invalid interval argument: %s\n", optarg); + exit(EXIT_FAILURE); + } + break; + case 'f': + logfile = optarg; + break; + case 'h': + usage(argv[0]); + exit(EXIT_SUCCESS); + case '?': + bad_option = true; + break; + case 0: + break; + default: + abort(); + } + } + + if (bad_option) { + usage(argv[0]); + exit(EXIT_FAILURE); + } + + if (run_as_daemon) { + daemonize(); + signal(SIGHUP, sighup_handler); + } + + gather_data(period, interval); +} |