aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFranciszek Malinka <franciszek.malinka@gmail.com>2022-05-02 01:06:19 +0200
committerFranciszek Malinka <franciszek.malinka@gmail.com>2022-05-02 01:06:19 +0200
commit45541328b12800591e947b8932bb70f8309cff7a (patch)
treeacfe29906101494787802f17af168df38aafb1f8
Initial commit
-rw-r--r--Makefile26
-rw-r--r--daemonize.c190
-rw-r--r--mystat.c249
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);
+}