commit cfd059879192b8809c2ac048b8fa0621f2ff94b8 Author: Noah Swerhun Date: Fri May 24 16:54:17 2024 -0500 first commit diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..ea66270 --- /dev/null +++ b/.clang-format @@ -0,0 +1 @@ +BasedOnStyle: "Google" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..ce018ce --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# pss-total +sum proportional set size of every process for accurate memory usage statistics. +In non-pretty mode, the number is given in kibibytes. + +### Summary +pss-total [OPTIONS] + +### Options + - `-p`, `--pretty`: format output using sensible units: KiB, MiB, or GiB. + - `-h`, `--help`: print this help message. diff --git a/build.ninja b/build.ninja new file mode 100644 index 0000000..1c63bfb --- /dev/null +++ b/build.ninja @@ -0,0 +1,85 @@ +## Generated by ngen +## Do not modify by hand + +builddir = build + +rule mkdir + command = mkdir -p $out + description = Creating directory $out + +rule regen_ninjafile + command = ngen -c $in -o $out + generator = 1 + description = Regenerating $out + +rule regen_compile_commands + command = ngen -c $in --write-compile-commands $out + description = Regenerating $out + +build $builddir: mkdir + +build $builddir/compile_commands.json: regen_compile_commands ngen.toml || $builddir + pool = console + +build build.ninja: regen_ninjafile ngen.toml || $builddir/compile_commands.json + pool = console + +# BEGIN TARGET debug +rule cc_debug + deps = gcc + depfile = $dep + command = cc -I. -fsanitize=address -O0 -g -MD -MF $dep -o $out -c $in + description = Building $in -> $out +rule link_debug + command = cc -fsanitize=address -o $out $in + description = Linking $out + +build $builddir/debug/obj: mkdir +build $builddir/debug/dep: mkdir + +build $builddir/debug/obj/src-main.c.o: cc_debug src/main.c + dep = $builddir/debug/dep/src-main.c.o.d + +build $builddir/debug/pss-total: link_debug $builddir/debug/obj/src-main.c.o | || $builddir/debug/obj $builddir/debug/dep +build debug: phony $builddir/debug/pss-total +# END TARGET debug + +# BEGIN TARGET release +rule cc_release + deps = gcc + depfile = $dep + command = cc -I. -flto -O2 -MD -MF $dep -o $out -c $in + description = Building $in -> $out +rule link_release + command = cc -flto -o $out $in + description = Linking $out + +build $builddir/release/obj: mkdir +build $builddir/release/dep: mkdir + +build $builddir/release/obj/src-main.c.o: cc_release src/main.c + dep = $builddir/release/dep/src-main.c.o.d + +build $builddir/release/pss-total: link_release $builddir/release/obj/src-main.c.o | || $builddir/release/obj $builddir/release/dep +build release: phony $builddir/release/pss-total +# END TARGET release + +# BEGIN TARGET main +rule cc_main + deps = gcc + depfile = $dep + command = cc -I. -MD -MF $dep -o $out -c $in + description = Building $in -> $out +rule link_main + command = cc -o $out $in + description = Linking $out + +build $builddir/main/obj: mkdir +build $builddir/main/dep: mkdir + +build $builddir/main/obj/src-main.c.o: cc_main src/main.c + dep = $builddir/main/dep/src-main.c.o.d + +build $builddir/main/pss-total: link_main $builddir/main/obj/src-main.c.o | || $builddir/main/obj $builddir/main/dep +build main: phony $builddir/main/pss-total +# END TARGET main diff --git a/install.sh b/install.sh new file mode 100644 index 0000000..2ac3431 --- /dev/null +++ b/install.sh @@ -0,0 +1,2 @@ +mkdir -p /usr/local/bin +cp build/release/pss-total /usr/local/bin diff --git a/lib/include/ns_fget_line.h b/lib/include/ns_fget_line.h new file mode 100644 index 0000000..7d31f15 --- /dev/null +++ b/lib/include/ns_fget_line.h @@ -0,0 +1,20 @@ +#ifndef NS_GET_LINE_H +#define NS_GET_LINE_H + +#include + +#include "ns_str.h" + +static int ns_fget_line(FILE *stream, Str *str) { + char c; + + while ((c = fgetc(stream)) != '\n') { + if (c == EOF) { + return EOF; + } + ns_str_push_char(str, c); + } + return 1; +} + +#endif diff --git a/lib/include/ns_str.h b/lib/include/ns_str.h new file mode 100644 index 0000000..547194d --- /dev/null +++ b/lib/include/ns_str.h @@ -0,0 +1,60 @@ +#ifndef NS_STR_H +#define NS_STR_H + +#include +#include +#include + +struct Str_T { + char *arr; + size_t len; + size_t cap; +}; + +typedef struct Str_T Str; + +static void ns_str_init_empty(Str *nstr, size_t capactiy) { + nstr->len = 0; + nstr->cap = capactiy; + nstr->arr = calloc(nstr->cap, sizeof(char)); +} + +static void ns_str_push_char(Str *nstr, char c) { + if (nstr->len + 2 > nstr->cap) { + nstr->cap += 8; + nstr->arr = realloc(nstr->arr, nstr->cap * sizeof(char)); + assert(nstr->arr != NULL); + } + nstr->arr[nstr->len] = c; + nstr->len++; + nstr->arr[nstr->len] = '\0'; +} + +static void ns_str_push(Str *nstr, char *str) { + size_t strlen_str = strlen(str); + if (nstr->len + strlen_str + 1 > nstr->cap) { + nstr->cap = nstr->len + strlen_str + 8; + nstr->arr = realloc(nstr->arr, nstr->cap * sizeof(char)); + assert(nstr->arr != NULL); + } + strncpy(nstr->arr + nstr->len, str, nstr->cap - nstr->len); + nstr->len = nstr->len + strlen_str; +} + +static void ns_str_set(Str *nstr, char *str) { + size_t strlen_str = strlen(str); + nstr->cap = strlen_str + 1; + nstr->arr = realloc(nstr->arr, nstr->cap * sizeof(char)); + assert(nstr->arr != NULL); + strncpy(nstr->arr, str, nstr->cap); + nstr->len = strlen_str; +} + +static void ns_str_destroy(Str *nstr) { + free(nstr->arr); + nstr->arr = NULL; + nstr->len = 0; + nstr->cap = 0; +} + +#endif diff --git a/lib/include/ns_vec.h b/lib/include/ns_vec.h new file mode 100644 index 0000000..ded263b --- /dev/null +++ b/lib/include/ns_vec.h @@ -0,0 +1,34 @@ +#ifndef NS_VEC_H +#define NS_VEC_H + +#include +#include + +#define ns_vec_gen(type) \ + struct Vec_##type##_T { \ + type *arr; \ + size_t len; \ + size_t cap; \ + }; \ + typedef struct Vec_##type##_T Vec_##type; + +#define ns_vec_init(type, vec, cap_) \ + vec.len = 0; \ + vec.cap = cap_; \ + vec.arr = malloc(vec.cap * sizeof(type)); \ + assert(vec.arr != NULL); + +#define ns_vec_push(type, vec, obj) \ + if (vec.len + 1 > vec.cap) { \ + vec.cap += 8; \ + vec.arr = realloc(vec.arr, vec.cap * sizeof(type)); \ + assert(vec.arr != NULL); \ + } \ + vec.arr[vec.len] = obj; \ + vec.len++; + +#define ns_vec_destroy(vec) \ + free(vec.arr); \ + vec.arr = NULL; + +#endif diff --git a/ngen.toml b/ngen.toml new file mode 100644 index 0000000..448ff13 --- /dev/null +++ b/ngen.toml @@ -0,0 +1,17 @@ +[config] +compile_commands = true + +[targets.main] +outfile = "pss-total" +compiler_flags = ["-I."] +sources = [ + "src/main.c", +] + +[targets.debug] +compiler_flags = ["-fsanitize=address", "-O0", "-g"] +linker_flags = ["-fsanitize=address"] + +[targets.release] +compiler_flags = ["-flto", "-O2"] +linker_flags = ["-flto"] diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..848fc76 --- /dev/null +++ b/src/main.c @@ -0,0 +1,147 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "lib/include/ns_fget_line.h" +#include "lib/include/ns_str.h" + +double read_file(const char *filename) { + FILE *fp; + int status; + Str line = {0}; + char *pss; + + double sum = 0; + size_t line_pss = 0; + + errno = 0; + fp = fopen(filename, "r"); + + if (fp == NULL) { + switch (errno) { + case EACCES: + return 0; + break; + default: + perror("cannot open file pointer"); + assert(fp != NULL); + break; + } + } + + while (1) { + ns_str_init_empty(&line, 256); + + errno = 0; + status = ns_fget_line(fp, &line); + + if (status == EOF) { + if (errno != 0) { + perror("error reading file"); + ns_str_destroy(&line); + return 0; + } else { + ns_str_destroy(&line); + break; + } + } + + status = sscanf(line.arr, "Pss: %lu", &line_pss); + + if (status != 1) { + ns_str_destroy(&line); + continue; + } + + sum += line_pss; + + ns_str_destroy(&line); + } + + fclose(fp); + + return sum; +} + +int main(int argc, char **argv) { + DIR *proc_dp; + struct dirent *proc_ent; + Str smaps_path = {0}; + double total_KiB = 0; + + errno = 0; + proc_dp = opendir("/proc"); + + if (proc_dp == NULL) { + perror("error opening /proc for enumeration"); + assert(proc_dp != NULL); + } + + while (1) { + errno = 0; + proc_ent = readdir(proc_dp); + + if (proc_ent == NULL) { + if (errno != 0) { + perror("error enumerating /proc"); + assert(errno == 0); + } else { + break; + } + } + + // skip if not a PID + if (!isdigit(proc_ent->d_name[0])) continue; + + ns_str_set(&smaps_path, "/proc/"); + ns_str_push(&smaps_path, proc_ent->d_name); + ns_str_push(&smaps_path, "/smaps"); + + total_KiB += read_file(smaps_path.arr); + + ns_str_destroy(&smaps_path); + } + + closedir(proc_dp); + + if (argc == 2) { + if ((strcmp(argv[1], "--pretty") == 0) || (strcmp(argv[1], "-p") == 0)) { + const char *prefixes[] = {"KiB", "MiB", "GiB"}; + size_t i = 0; + while (total_KiB >= 1024 && i < 2) { + total_KiB /= 1024; + i++; + } + + printf("%0.2f %s\n", total_KiB, prefixes[i]); + exit(0); + } else if ((strcmp(argv[1], "--help") == 0) || + (strcmp(argv[1], "-h") == 0)) { + printf("DESCRIPTION\n"); + printf( + " pss-total: sum proportional set size of every process for " + "accurate memory\n"); + printf( + " usage statistics. In non-pretty mode, the number is given in " + "kibibytes.\n"); + printf("\n"); + printf("SUMMARY\n"); + printf(" pss-total [OPTIONS]\n"); + printf("\n"); + printf("OPTIONS\n"); + printf(" -p, --pretty\n"); + printf(" format output using sensible units: KiB, MiB, or GiB.\n"); + printf("\n"); + printf(" -h, --help\n"); + printf(" print this help message.\n"); + exit(0); + } + } + + printf("%.0f\n", total_KiB); + return 0; +} diff --git a/uninstall.sh b/uninstall.sh new file mode 100644 index 0000000..8015851 --- /dev/null +++ b/uninstall.sh @@ -0,0 +1 @@ +rm /usr/local/bin/pss-total