From: Masatake YAMATO Date: Sun, 31 Oct 2021 19:52:48 +0000 (+0900) Subject: lsfd: implement --summary and --counter options X-Git-Tag: v2.38-rc1~144^2~3 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=7d8c81d7fe35cd7975670eb5263cc842a29cfb08;p=thirdparty%2Futil-linux.git lsfd: implement --summary and --counter options Signed-off-by: Masatake YAMATO --- diff --git a/misc-utils/Makemodule.am b/misc-utils/Makemodule.am index abfddf3b39..afd11dbe08 100644 --- a/misc-utils/Makemodule.am +++ b/misc-utils/Makemodule.am @@ -255,6 +255,8 @@ lsfd_SOURCES = \ misc-utils/lsfd.h \ misc-utils/lsfd-filter.h \ misc-utils/lsfd-filter.c \ + misc-utils/lsfd-counter.h \ + misc-utils/lsfd-counter.c \ misc-utils/lsfd-file.c \ misc-utils/lsfd-cdev.c \ misc-utils/lsfd-bdev.c \ diff --git a/misc-utils/lsfd-counter.c b/misc-utils/lsfd-counter.c new file mode 100644 index 0000000000..55f0fde470 --- /dev/null +++ b/misc-utils/lsfd-counter.c @@ -0,0 +1,56 @@ +/* + * lsfd-counter.c - counter implementation used in --summary option + * + * Copyright (C) 2021 Red Hat, Inc. + * Copyright (C) 2021 Masatake YAMATO + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +#include "lsfd-counter.h" + +#include "xalloc.h" + +struct lsfd_counter { + char *name; + size_t value; + struct lsfd_filter *filter; +}; + +struct lsfd_counter *lsfd_counter_new(const char *const name, struct lsfd_filter *filter) +{ + struct lsfd_counter *counter = xmalloc(sizeof(struct lsfd_counter)); + + counter->name = xstrdup(name); + counter->value = 0; + counter->filter = filter; + + return counter; +} + +void lsfd_counter_free(struct lsfd_counter *counter) +{ + lsfd_filter_free(counter->filter); + free(counter->name); + free(counter); +} + +bool lsfd_counter_accumulate(struct lsfd_counter *counter, struct libscols_line *ln) +{ + if (lsfd_filter_apply(counter->filter, ln)) { + counter->value++; + return true; + } + return false; +} + +const char *lsfd_counter_name(struct lsfd_counter *counter) +{ + return counter->name; +} + +size_t lsfd_counter_value(struct lsfd_counter *counter) +{ + return counter->value; +} diff --git a/misc-utils/lsfd-counter.h b/misc-utils/lsfd-counter.h new file mode 100644 index 0000000000..bac11a913a --- /dev/null +++ b/misc-utils/lsfd-counter.h @@ -0,0 +1,30 @@ +/* + * lsfd-counter.h - counter implementation used in --summary option + * + * Copyright (C) 2021 Red Hat, Inc. + * Copyright (C) 2021 Masatake YAMATO + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ +#ifndef UTIL_LINUX_LSFD_COUNTER_H +#define UTIL_LINUX_LSFD_COUNTER_H + +#include "libsmartcols.h" +#include "lsfd-filter.h" +#include + +struct lsfd_counter; + +/* The created counter takes the ownership of the filter; the filter is + * freed in lsfd_counter_free(). + */ +struct lsfd_counter *lsfd_counter_new(const char *const name, struct lsfd_filter *filter); +void lsfd_counter_free(struct lsfd_counter *counter); + +bool lsfd_counter_accumulate(struct lsfd_counter *counter, struct libscols_line *ln); + +const char *lsfd_counter_name(struct lsfd_counter *counter); +size_t lsfd_counter_value(struct lsfd_counter *counter); + +#endif /* UTIL_LINUX_LSFD_COUNTER_H */ diff --git a/misc-utils/lsfd.c b/misc-utils/lsfd.c index 874b4919b7..5d9274059e 100644 --- a/misc-utils/lsfd.c +++ b/misc-utils/lsfd.c @@ -59,6 +59,7 @@ static int kcmp(pid_t pid1, pid_t pid2, int type, #include "lsfd.h" #include "lsfd-filter.h" +#include "lsfd-counter.h" /* * /proc/$pid/mountinfo entries @@ -189,6 +190,84 @@ static size_t ncolumns; static ino_t *mnt_namespaces; static size_t nspaces; +struct counter_spec { + struct list_head specs; + const char *name; + const char *expr; +}; + +static struct counter_spec default_counter_specs[] = { + { + .name = N_("processes"), + .expr = "ASSOC == 'cwd'", + }, + { + .name = N_("root owned processes"), + .expr = "(ASSOC == 'cwd') && (UID == 0)", + }, + { + .name = N_("kernel threads"), + .expr = "(ASSOC == 'cwd') && KTHREAD", + }, + { + .name = N_("open files"), + .expr = "FD >= 0", + }, + { + .name = N_("RO open files"), + .expr = "(FD >= 0) and (MODE == 'r--')", + }, + { + .name = N_("WO open files"), + .expr = "(FD >= 0) and (MODE == '-w-')", + }, + { + .name = N_("shared mappings"), + .expr = "ASSOC == 'shm'", + }, + { + .name = N_("RO shared mappings"), + .expr = "(ASSOC == 'shm') and (MODE == 'r--')", + }, + { + .name = N_("WO shared mappings"), + .expr = "(ASSOC == 'shm') and (MODE == '-w-')", + }, + { + .name = N_("regular files"), + .expr = "(FD >= 0) && (TYPE == 'REG')", + }, + { + .name = N_("directories"), + .expr = "(FD >= 0) && (TYPE == 'DIR')", + }, + { + .name = N_("sockets"), + .expr = "(FD >= 0) && (TYPE == 'SOCK')", + }, + { + .name = N_("fifos/pipes"), + .expr = "(FD >= 0) && (TYPE == 'FIFO')", + }, + { + .name = N_("character devices"), + .expr = "(FD >= 0) && (TYPE == 'CHR')", + }, + { + .name = N_("block devices"), + .expr = "(FD >= 0) && (TYPE == 'BLK')", + }, + { + .name = N_("unknown types"), + .expr = "(FD >= 0) && (TYPE == 'UNKN')", + } +}; + +enum { + SUMMARY_EMIT = 1 << 0, + SUMMARY_ONLY = 1 << 1, +}; + struct lsfd_control { struct libscols_table *tb; /* output */ struct list_head procs; /* list of all processes */ @@ -197,9 +276,11 @@ struct lsfd_control { raw : 1, json : 1, notrunc : 1, - threads : 1; + threads : 1, + summary: 2; struct lsfd_filter *filter; + struct lsfd_counter **counters; /* NULL terminated array. */ }; static void xstrappend(char **a, const char *b); @@ -759,13 +840,23 @@ static void convert(struct list_head *procs, struct lsfd_control *ctl) list_for_each (f, &proc->files) { struct file *file = list_entry(f, struct file, files); struct libscols_line *ln = scols_table_new_line(ctl->tb, NULL); + struct lsfd_counter **counter = NULL; + if (!ln) err(EXIT_FAILURE, _("failed to allocate output line")); convert_file(proc, file, ln); - if (!lsfd_filter_apply(ctl->filter, ln)) + if (!lsfd_filter_apply(ctl->filter, ln)) { scols_table_remove_line(ctl->tb, ln); + continue; + } + + if (!ctl->counters) + continue; + + for (counter = ctl->counters; *counter; counter++) + lsfd_counter_accumulate(*counter, ln); } } } @@ -776,6 +867,12 @@ static void delete(struct list_head *procs, struct lsfd_control *ctl) scols_unref_table(ctl->tb); lsfd_filter_free(ctl->filter); + if (ctl->counters) { + struct lsfd_counter **counter; + for (counter = ctl->counters; *counter; counter++) + lsfd_counter_free(*counter); + free(ctl->counters); + } } static void emit(struct lsfd_control *ctl) @@ -1046,6 +1143,9 @@ static void __attribute__((__noreturn__)) usage(void) fputs(_(" -p, --pid collect information only specified processes\n"), out); fputs(_(" -Q, --filter apply display filter\n"), out); fputs(_(" --debug-filter dump the innternal data structure of filter and exit\n"), out); + fputs(_(" -C, --counter :\n" + " make a counter used in --summary output\n"), out); + fputs(_(" --summary[=when] print summary information (never,always or only)\n"), out); fputs(USAGE_SEPARATOR, out); printf(USAGE_HELP_OPTIONS(23)); @@ -1117,6 +1217,153 @@ static struct lsfd_filter *new_filter(const char *expr, bool debug, const char * return filter; } +static struct counter_spec *new_counter_spec(const char *spec_str) +{ + char *sep; + struct counter_spec *spec; + + if (spec_str[0] == '\0') + errx(EXIT_FAILURE, + _("too short counter specification: -C/--counter %s"), + spec_str); + if (spec_str[0] == ':') + errx(EXIT_FAILURE, + _("no name for counter: -C/--counter %s"), + spec_str); + + sep = strchr(spec_str, ':'); + if (sep == NULL) + errx(EXIT_FAILURE, + _("no name for counter: -C/--counter %s"), + spec_str); + if (sep[1] == '\0') + errx(EXIT_FAILURE, + _("empty ecounter expression given: -C/--counter %s"), + spec_str); + + /* Split the spec_str in to name and expr. */ + *sep = '\0'; + + if (strchr(spec_str, '{')) + errx(EXIT_FAILURE, + _("don't use `{' in the name of a counter: %s"), + spec_str); + + spec = xmalloc(sizeof(struct counter_spec)); + INIT_LIST_HEAD(&spec->specs); + spec->name = spec_str; + spec->expr = sep + 1; + + return spec; +} + +static void free_counter_spec(struct counter_spec *counter_spec) +{ + free(counter_spec); +} + +static struct lsfd_counter *new_counter(struct counter_spec *spec, struct lsfd_control *ctl) +{ + struct lsfd_filter *filter; + + filter = new_filter(spec->expr, false, + _("failed in making filter for a counter: "), + ctl); + return lsfd_counter_new(spec->name, filter); +} + +static struct lsfd_counter **new_counters(struct list_head *specs, struct lsfd_control *ctl) +{ + struct lsfd_counter **counters; + size_t len = list_count_entries(specs); + size_t i = 0; + struct list_head *s; + + counters = xcalloc(len + 1, sizeof(struct lsfd_counter *)); + list_for_each(s, specs) { + struct counter_spec *spec = list_entry(s, struct counter_spec, specs); + counters[i++] = new_counter(spec, ctl); + } + assert(counters[len] == NULL); + + return counters; +} + +static struct lsfd_counter **new_default_counters(struct lsfd_control *ctl) +{ + struct lsfd_counter **counters; + size_t len = ARRAY_SIZE(default_counter_specs); + size_t i; + + counters = xcalloc(len + 1, sizeof(struct lsfd_counter *)); + for (i = 0; i < len; i++) { + struct counter_spec *spec = default_counter_specs + i; + counters[i] = new_counter(spec, ctl); + } + assert(counters[len] == NULL); + + return counters; +} + +static struct libscols_table *new_summary_table(struct lsfd_control *ctl) +{ + struct libscols_table *tb = scols_new_table(); + + struct libscols_column *name_cl, *value_cl; + + if (!tb) + err(EXIT_FAILURE, _("failed to allocate summary table")); + + scols_table_enable_noheadings(tb, ctl->noheadings); + scols_table_enable_raw(tb, ctl->raw); + scols_table_enable_json(tb, ctl->json); + + if(ctl->json) + scols_table_set_name(tb, "lsfd-summary"); + + + value_cl = scols_table_new_column(tb, _("VALUE"), 0, SCOLS_FL_RIGHT); + if (!value_cl) + err(EXIT_FAILURE, _("failed to allocate summary column")); + if (ctl->json) + scols_column_set_json_type(value_cl, SCOLS_JSON_NUMBER); + + name_cl = scols_table_new_column(tb, _("COUNTER"), 0, 0); + if (!name_cl) + err(EXIT_FAILURE, _("failed to allocate summary column")); + if (ctl->json) + scols_column_set_json_type(name_cl, SCOLS_JSON_STRING); + + return tb; +} + +static void fill_summary_line(struct libscols_line *ln, struct lsfd_counter *counter) +{ + char *str = NULL; + + xasprintf(&str, "%llu", (unsigned long long)lsfd_counter_value(counter)); + if (!str) + err(EXIT_FAILURE, _("failed to add summary data")); + if (scols_line_refer_data(ln, 0, str)) + err(EXIT_FAILURE, _("failed to add summary data")); + + if (scols_line_set_data(ln, 1, lsfd_counter_name(counter))) + err(EXIT_FAILURE, _("failed to add summary data")); +} + +static void emit_summary(struct lsfd_control *ctl, struct lsfd_counter **counter) +{ + struct libscols_table *tb = new_summary_table(ctl); + + for (; *counter; counter++) { + struct libscols_line *ln = scols_table_new_line(tb, NULL); + fill_summary_line(ln, *counter); + } + scols_print_table(tb); + + scols_unref_table(tb); +} + int main(int argc, char *argv[]) { int c; @@ -1127,9 +1374,13 @@ int main(int argc, char *argv[]) bool debug_filter = false; pid_t *pids = NULL; int n_pids = 0; + struct list_head counter_specs; + + INIT_LIST_HEAD(&counter_specs); enum { - OPT_DEBUG_FILTER = CHAR_MAX + 1 + OPT_DEBUG_FILTER = CHAR_MAX + 1, + OPT_SUMMARY, }; static const struct option longopts[] = { { "noheadings", no_argument, NULL, 'n' }, @@ -1143,6 +1394,8 @@ int main(int argc, char *argv[]) { "pid", required_argument, NULL, 'p' }, { "filter", required_argument, NULL, 'Q' }, { "debug-filter",no_argument, NULL, OPT_DEBUG_FILTER }, + { "summary", optional_argument, NULL, OPT_SUMMARY }, + { "counter", required_argument, NULL, 'C' }, { NULL, 0, NULL, 0 }, }; @@ -1151,7 +1404,7 @@ int main(int argc, char *argv[]) textdomain(PACKAGE); close_stdout_atexit(); - while ((c = getopt_long(argc, argv, "no:JrVhluQ:p:", longopts, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "no:JrVhluQ:p:C:s", longopts, NULL)) != -1) { switch (c) { case 'n': ctl.noheadings = 1; @@ -1177,9 +1430,27 @@ int main(int argc, char *argv[]) case 'Q': append_filter_expr(&filter_expr, optarg, true); break; + case 'C': { + struct counter_spec *c = new_counter_spec(optarg); + list_add_tail(&c->specs, &counter_specs); + break; + } case OPT_DEBUG_FILTER: debug_filter = true; break; + case OPT_SUMMARY: + if (optarg) { + if (strcmp(optarg, "never") == 0) + ctl.summary = 0; + else if (strcmp(optarg, "only") == 0) + ctl.summary |= (SUMMARY_ONLY|SUMMARY_EMIT); + else if (strcmp(optarg, "always") == 0) + ctl.summary |= SUMMARY_EMIT; + else + errx(EXIT_FAILURE, _("unsupported --summary argument")); + } else + ctl.summary |= SUMMARY_EMIT; + break; case 'V': print_version(EXIT_SUCCESS); case 'h': @@ -1239,6 +1510,17 @@ int main(int argc, char *argv[]) free(filter_expr); } + /* make counters */ + if (ctl.summary & SUMMARY_EMIT) { + if (list_empty(&counter_specs)) + ctl.counters = new_default_counters(&ctl); + else { + ctl.counters = new_counters(&counter_specs, &ctl); + list_free(&counter_specs, struct counter_spec, specs, + free_counter_spec); + } + } + if (n_pids > 0) sort_pids(pids, n_pids); @@ -1250,7 +1532,10 @@ int main(int argc, char *argv[]) free(pids); convert(&ctl.procs, &ctl); - emit(&ctl); + if (!(ctl.summary & SUMMARY_ONLY)) + emit(&ctl); + if (ctl.counters && (ctl.summary & SUMMARY_EMIT)) + emit_summary(&ctl, ctl.counters); /* cleanup */ delete(&ctl.procs, &ctl); diff --git a/misc-utils/meson.build b/misc-utils/meson.build index bff8947b05..d59d8e4b5d 100644 --- a/misc-utils/meson.build +++ b/misc-utils/meson.build @@ -44,6 +44,8 @@ lsfd_sources = files ( 'lsfd.h', 'lsfd-filter.h', 'lsfd-filter.c', + 'lsfd-counter.h', + 'lsfd-counter.c', 'lsfd-file.c', 'lsfd-cdev.c', 'lsfd-bdev.c', diff --git a/tests/expected/lsfd/option-summary b/tests/expected/lsfd/option-summary new file mode 100644 index 0000000000..4e96f55764 --- /dev/null +++ b/tests/expected/lsfd/option-summary @@ -0,0 +1,3 @@ + 10 GROUP + 3 PASSWD + 13 PROC diff --git a/tests/ts/lsfd/lsfd-functions.bash b/tests/ts/lsfd/lsfd-functions.bash index 47b2499be4..597e48cf43 100644 --- a/tests/ts/lsfd/lsfd-functions.bash +++ b/tests/ts/lsfd/lsfd-functions.bash @@ -15,6 +15,15 @@ # GNU General Public License for more details. # +function lsfd_wait_for_pausing { + ts_check_prog "sleep" + + local PID=$1 + until [[ $(ps --no-headers -ostat "${PID}") =~ S.* ]]; do + sleep 1 + done +} + function lsfd_compare_dev { local LSFD=$1 local FINDMNT=$2 diff --git a/tests/ts/lsfd/option-summary b/tests/ts/lsfd/option-summary new file mode 100755 index 0000000000..466e3d80b8 --- /dev/null +++ b/tests/ts/lsfd/option-summary @@ -0,0 +1,62 @@ +#!/bin/bash +# +# Copyright (C) 2021 Masatake YAMATO +# +# This file is part of util-linux. +# +# This file is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This file is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +TS_TOPDIR="${0%/*}/../.." +TS_DESC="directory" + +. $TS_TOPDIR/functions.sh +ts_init "$*" + +. $TS_SELF/lsfd-functions.bash + +ts_check_test_command "$TS_CMD_LSFD" +ts_check_test_command "$TS_HELPER_MKFDS" +ts_check_prog "ps" + +ts_cd "$TS_OUTDIR" + +FD=3 +F_GROUP=/etc/group +F_PASSWD=/etc/passwd +PIDS= +PID= + +for i in {1..10}; do + "$TS_HELPER_MKFDS" -q ro-regular-file $FD file=$F_GROUP & + PID=$! + PIDS="${PIDS} ${PID} " + lsfd_wait_for_pausing "${PID}" +done + +for i in {1..3}; do + "$TS_HELPER_MKFDS" -q ro-regular-file $FD file=$F_PASSWD & + PID=$! + PIDS="${PIDS} ${PID} " + lsfd_wait_for_pausing "${PID}" +done + +${TS_CMD_LSFD} -n --summary=only \ + --pid="${PIDS}" \ + --counter=GROUP:'(NAME == "/etc/group")' \ + --counter=PASSWD:'(NAME == "/etc/passwd")' \ + --counter=PROC:'(ASSOC == "exe")' \ + > $TS_OUTPUT 2>&1 + +for PID in ${PIDS}; do + kill -CONT "${PID}" +done + +ts_finalize