diff options
author | Costa Tsaousis <costa@netdata.cloud> | 2022-10-05 14:13:46 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-10-05 14:13:46 +0300 |
commit | 8fc3b351a2e7fc96eced8f924de2e9cec9842128 (patch) | |
tree | bde41c66573ccaf8876c280e00742cc6096b587c /collectors/apps.plugin | |
parent | 6850878e697d66dc90b9af1e750b22238c63c292 (diff) |
Allow netdata plugins to expose functions for querying more information about specific charts (#13720)
* function renames and code cleanup in popen.c; no actual code changes
* netdata popen() now opens both child process stdin and stdout and returns FILE * for both
* pass both input and output to parser structures
* updated rrdset to call custom functions
* RRDSET FUNCTION leading calls for both sync and async operation
* put RRDSET functions to a separate file
* added format and timeout at function definition
* support for synchronous (internal plugins) and asynchronous (external plugins and children) functions
* /api/v1/function endpoint
* functions are now attached to the host and there is a dictionary view per chart
* functions implemented at plugins.d
* remove the defer until keyword hook from plugins.d when it is done
* stream sender implementation of functions
* sanitization of all functions so that certain characters are only allowed
* strictier sanitization
* common max size
* 1st working plugins.d example
* always init inflight dictionary
* properly destroy dictionaries to avoid parallel insertion of items
* add more debugging on disconnection reasons
* add more debugging on disconnection reasons again
* streaming receiver respects newlines
* dont use the same fp for both streaming receive and send
* dont free dbengine memory with internal checks
* make sender proceed in the buffer
* added timing info and garbage collection at plugins.d
* added info about routing nodes
* added info about routing nodes with delay
* added more info about delays
* added more info about delays again
* signal sending thread to wake up
* streaming version labeling and commented code to support capabilities
* added functions to /api/v1/data, /api/v1/charts, /api/v1/chart, /api/v1/info
* redirect top output to stdout
* address coverity findings
* fix resource leaks of popen
* log attempts to connect to individual destinations
* better messages
* properly parse destinations
* try to find a function from the most matching to the least matching
* log added streaming destinations
* rotate destinations bypassing a node in the middle that does not accept our connection
* break the loops properly
* use typedef to define callbacks
* capabilities negotiation during streaming
* functions exposed upstream based on capabilities; compression disabled per node persisting reconnects; always try to connect with all capabilities
* restore functionality to lookup functions
* better logging of capabilities
* remove old versions from capabilities when a newer version is there
* fix formatting
* optimization for plugins.d rrdlabels to avoid creating and destructing dictionaries all the time
* delayed health initialization for rrddim and rrdset
* cleanup health initialization
* fix for popen() not returning the right value
* add health worker jobs for initializing rrdset and rrddim
* added content type support for functions; apps.plugin permanent function to display all the processes
* fixes for functions parameters parsing in apps.plugin
* fix for process matching in apps.plugiin
* first working function for apps.plugin
* Dashboard ACL is disabled for functions; Function errors are all in JSON format
* apps.plugin function processes returns json table
* use json_escape_string() to escape message
* fix formatting
* apps.plugin exposes all its metrics to function processes
* fix json formatting when filtering out some rows
* reopen the internal pipe of rrdpush in case of errors
* misplaced statement
* do not use buffer->len
* support for GLOBAL functions (functions that are not linked to a chart
* added /api/v1/functions endpoint; removed format from the FUNCTIONS api;
* swagger documentation about the new api end points
* added plugins.d documentation about functions
* never re-close a file
* remove uncessesary ifdef
* fixed issues identified by codacy
* fix for null label value
* make edit-config copy-and-paste friendly
* Revert "make edit-config copy-and-paste friendly"
This reverts commit 54500c0e0a97f65a0c66c4d34e966f6a9056698e.
* reworked sender handshake to fix coverity findings
* timeout is zero, for both send_timeout() and recv_timeout()
* properly detect that parent closed the socket
* support caching of function responses; limit function response to 10MB; added protection from malformed function responses
* disabled excessive logging
* added units to apps.plugin function processes and normalized all values to be human readable
* shorter field names
* fixed issues reported
* fixed apps.plugin error response; tested that pluginsd can properly handle faulty responses
* use double linked list macros for double linked list management
* faster apps.plugin function printing by minimizing file operations
* added memory percentage
* fix compatibility issues with older compilers and FreeBSD
* rrdpush sender code cleanup; rrhost structure cleanup from sender flags and variables;
* fix letftover variable in ifdef
* apps.plugin: do not call detach from the thread; exit immediately when input is broken
* exclude AR charts from health
* flush cleaner; prefer sender output
* clarity
* do not fill the cbuffer if not connected
* fix
* dont enabled host->sender if streaming is not enabled; send host label updates to parent;
* functions are only available through ACLK
* Prepared statement reports only in dev mode
* fix AR chart detection
* fix for streaming not being enabling itself
* more cleanup of sender and receiver structures
* moved read-only flags and configuration options to rrdhost->options
* fixed merge with master
* fix for incomplete rename
* prevent service thread from working on charts that are being collected
Co-authored-by: Stelios Fragkakis <52996999+stelfrag@users.noreply.github.com>
Diffstat (limited to 'collectors/apps.plugin')
-rw-r--r-- | collectors/apps.plugin/apps_plugin.c | 831 |
1 files changed, 737 insertions, 94 deletions
diff --git a/collectors/apps.plugin/apps_plugin.c b/collectors/apps.plugin/apps_plugin.c index 8521e078e8..212374e828 100644 --- a/collectors/apps.plugin/apps_plugin.c +++ b/collectors/apps.plugin/apps_plugin.c @@ -10,6 +10,11 @@ #include "libnetdata/libnetdata.h" #include "libnetdata/required_dummies.h" +#define APPS_PLUGIN_FUNCTIONS() do { \ + fprintf(stdout, PLUGINSD_KEYWORD_FUNCTION " \"processes\" 10 \"Detailed information on the currently running processes on this node\"\n"); \ + } while(0) + + // ---------------------------------------------------------------------------- // debugging @@ -191,6 +196,18 @@ struct pid_on_target { struct pid_on_target *next; }; +struct openfds { + kernel_uint_t files; + kernel_uint_t pipes; + kernel_uint_t sockets; + kernel_uint_t inotifies; + kernel_uint_t eventfds; + kernel_uint_t timerfds; + kernel_uint_t signalfds; + kernel_uint_t eventpolls; + kernel_uint_t other; +}; + // ---------------------------------------------------------------------------- // target // @@ -235,24 +252,16 @@ struct target { kernel_uint_t io_logical_bytes_read; kernel_uint_t io_logical_bytes_written; - // kernel_uint_t io_read_calls; - // kernel_uint_t io_write_calls; + kernel_uint_t io_read_calls; + kernel_uint_t io_write_calls; kernel_uint_t io_storage_bytes_read; kernel_uint_t io_storage_bytes_written; - // kernel_uint_t io_cancelled_write_bytes; + kernel_uint_t io_cancelled_write_bytes; int *target_fds; int target_fds_size; - kernel_uint_t openfiles; - kernel_uint_t openpipes; - kernel_uint_t opensockets; - kernel_uint_t openinotifies; - kernel_uint_t openeventfds; - kernel_uint_t opentimerfds; - kernel_uint_t opensignalfds; - kernel_uint_t openeventpolls; - kernel_uint_t openother; + struct openfds openfds; kernel_uint_t starttime; kernel_uint_t collected_starttime; @@ -382,22 +391,24 @@ struct pid_stat { kernel_uint_t io_logical_bytes_read_raw; kernel_uint_t io_logical_bytes_written_raw; - // kernel_uint_t io_read_calls_raw; - // kernel_uint_t io_write_calls_raw; + kernel_uint_t io_read_calls_raw; + kernel_uint_t io_write_calls_raw; kernel_uint_t io_storage_bytes_read_raw; kernel_uint_t io_storage_bytes_written_raw; - // kernel_uint_t io_cancelled_write_bytes_raw; + kernel_uint_t io_cancelled_write_bytes_raw; kernel_uint_t io_logical_bytes_read; kernel_uint_t io_logical_bytes_written; - // kernel_uint_t io_read_calls; - // kernel_uint_t io_write_calls; + kernel_uint_t io_read_calls; + kernel_uint_t io_write_calls; kernel_uint_t io_storage_bytes_read; kernel_uint_t io_storage_bytes_written; - // kernel_uint_t io_cancelled_write_bytes; + kernel_uint_t io_cancelled_write_bytes; struct pid_fd *fds; // array of fds it uses - size_t fds_size; // the size of the fds array + size_t fds_size; // the size of the fds array + + struct openfds openfds; int children_count; // number of processes directly referencing this unsigned char keep:1; // 1 when we need to keep this process in memory even after it exited @@ -447,8 +458,8 @@ kernel_uint_t global_uptime; static struct pid_stat *root_of_pids = NULL, // global list of all processes running - **all_pids = NULL; // to avoid allocations, we pre-allocate the - // the entire pid space. + **all_pids = NULL; // to avoid allocations, we pre-allocate + // a pointer for each pid in the entire pid space. static size_t all_pids_count = 0; // the number of processes running @@ -961,15 +972,10 @@ static inline struct pid_stat *get_pid_entry(pid_t pid) { p->fds = mallocz(sizeof(struct pid_fd) * MAX_SPARE_FDS); p->fds_size = MAX_SPARE_FDS; init_pid_fds(p, 0, p->fds_size); - - if(likely(root_of_pids)) - root_of_pids->prev = p; - - p->next = root_of_pids; - root_of_pids = p; - p->pid = pid; + DOUBLE_LINKED_LIST_APPEND_UNSAFE(root_of_pids, p, prev, next); + all_pids[pid] = p; all_pids_count++; @@ -986,11 +992,7 @@ static inline void del_pid_entry(pid_t pid) { debug_log("process %d %s exited, deleting it.", pid, p->comm); - if(root_of_pids == p) - root_of_pids = p->next; - - if(p->next) p->next->prev = p->prev; - if(p->prev) p->prev->next = p->next; + DOUBLE_LINKED_LIST_REMOVE_UNSAFE(root_of_pids, p, prev, next); // free the filename #ifndef __FreeBSD__ @@ -1554,21 +1556,21 @@ static inline int read_proc_pid_io(struct pid_stat *p, void *ptr) { #else pid_incremental_rate(io, p->io_logical_bytes_read, str2kernel_uint_t(procfile_lineword(ff, 0, 1))); pid_incremental_rate(io, p->io_logical_bytes_written, str2kernel_uint_t(procfile_lineword(ff, 1, 1))); - // pid_incremental_rate(io, p->io_read_calls, str2kernel_uint_t(procfile_lineword(ff, 2, 1))); - // pid_incremental_rate(io, p->io_write_calls, str2kernel_uint_t(procfile_lineword(ff, 3, 1))); + pid_incremental_rate(io, p->io_read_calls, str2kernel_uint_t(procfile_lineword(ff, 2, 1))); + pid_incremental_rate(io, p->io_write_calls, str2kernel_uint_t(procfile_lineword(ff, 3, 1))); pid_incremental_rate(io, p->io_storage_bytes_read, str2kernel_uint_t(procfile_lineword(ff, 4, 1))); pid_incremental_rate(io, p->io_storage_bytes_written, str2kernel_uint_t(procfile_lineword(ff, 5, 1))); - // pid_incremental_rate(io, p->io_cancelled_write_bytes, str2kernel_uint_t(procfile_lineword(ff, 6, 1))); + pid_incremental_rate(io, p->io_cancelled_write_bytes, str2kernel_uint_t(procfile_lineword(ff, 6, 1))); #endif if(unlikely(global_iterations_counter == 1)) { p->io_logical_bytes_read = 0; p->io_logical_bytes_written = 0; - // p->io_read_calls = 0; - // p->io_write_calls = 0; + p->io_read_calls = 0; + p->io_write_calls = 0; p->io_storage_bytes_read = 0; p->io_storage_bytes_written = 0; - // p->io_cancelled_write_bytes = 0; + p->io_cancelled_write_bytes = 0; } return 1; @@ -1577,11 +1579,11 @@ static inline int read_proc_pid_io(struct pid_stat *p, void *ptr) { cleanup: p->io_logical_bytes_read = 0; p->io_logical_bytes_written = 0; - // p->io_read_calls = 0; - // p->io_write_calls = 0; + p->io_read_calls = 0; + p->io_write_calls = 0; p->io_storage_bytes_read = 0; p->io_storage_bytes_written = 0; - // p->io_cancelled_write_bytes = 0; + p->io_cancelled_write_bytes = 0; return 0; #endif } @@ -1888,7 +1890,7 @@ static inline int file_descriptor_find_or_add(const char *name, uint32_t hash) { else if(likely(strncmp(name, "anon_inode:", 11) == 0)) { const char *t = &name[11]; - if(strcmp(t, "inotify") == 0) type = FILETYPE_INOTIFY; + if(strcmp(t, "inotify") == 0) type = FILETYPE_INOTIFY; else if(strcmp(t, "[eventfd]") == 0) type = FILETYPE_EVENTFD; else if(strcmp(t, "[eventpoll]") == 0) type = FILETYPE_EVENTPOLL; else if(strcmp(t, "[timerfd]") == 0) type = FILETYPE_TIMERFD; @@ -1943,7 +1945,6 @@ static inline void cleanup_negative_pid_fds(struct pid_stat *p) { static inline void init_pid_fds(struct pid_stat *p, size_t first, size_t size) { struct pid_fd *pfd = &p->fds[first], *pfdend = &p->fds[first + size]; - size_t i = first; while(pfd < pfdend) { #ifndef __FreeBSD__ @@ -1951,7 +1952,6 @@ static inline void init_pid_fds(struct pid_stat *p, size_t first, size_t size) { #endif clear_pid_fd(pfd); pfd++; - i++; } } @@ -2904,24 +2904,24 @@ static size_t zero_all_targets(struct target *root) { w->io_logical_bytes_read = 0; w->io_logical_bytes_written = 0; - // w->io_read_calls = 0; - // w->io_write_calls = 0; + w->io_read_calls = 0; + w->io_write_calls = 0; w->io_storage_bytes_read = 0; w->io_storage_bytes_written = 0; - // w->io_cancelled_write_bytes = 0; + w->io_cancelled_write_bytes = 0; // zero file counters if(w->target_fds) { memset(w->target_fds, 0, sizeof(int) * w->target_fds_size); - w->openfiles = 0; - w->openpipes = 0; - w->opensockets = 0; - w->openinotifies = 0; - w->openeventfds = 0; - w->opentimerfds = 0; - w->opensignalfds = 0; - w->openeventpolls = 0; - w->openother = 0; + w->openfds.files = 0; + w->openfds.pipes = 0; + w->openfds.sockets = 0; + w->openfds.inotifies = 0; + w->openfds.eventfds = 0; + w->openfds.timerfds = 0; + w->openfds.signalfds = 0; + w->openfds.eventpolls = 0; + w->openfds.other = 0; } w->collected_starttime = 0; @@ -2956,60 +2956,64 @@ static inline void reallocate_target_fds(struct target *w) { } } -static inline void aggregate_fd_on_target(int fd, struct target *w) { - if(unlikely(!w)) - return; - - if(unlikely(w->target_fds[fd])) { - // it is already aggregated - // just increase its usage counter - w->target_fds[fd]++; - return; - } - - // increase its usage counter - // so that we will not add it again - w->target_fds[fd]++; - - switch(all_files[fd].type) { +static void aggregage_fd_type_on_openfds(FD_FILETYPE type, struct openfds *openfds) { + switch(type) { case FILETYPE_FILE: - w->openfiles++; + openfds->files++; break; case FILETYPE_PIPE: - w->openpipes++; + openfds->pipes++; break; case FILETYPE_SOCKET: - w->opensockets++; + openfds->sockets++; break; case FILETYPE_INOTIFY: - w->openinotifies++; + openfds->inotifies++; break; case FILETYPE_EVENTFD: - w->openeventfds++; + openfds->eventfds++; break; case FILETYPE_TIMERFD: - w->opentimerfds++; + openfds->timerfds++; break; case FILETYPE_SIGNALFD: - w->opensignalfds++; + openfds->signalfds++; break; case FILETYPE_EVENTPOLL: - w->openeventpolls++; + openfds->eventpolls++; break; case FILETYPE_OTHER: - w->openother++; + openfds->other++; break; } } +static inline void aggregate_fd_on_target(int fd, struct target *w) { + if(unlikely(!w)) + return; + + if(unlikely(w->target_fds[fd])) { + // it is already aggregated + // just increase its usage counter + w->target_fds[fd]++; + return; + } + + // increase its usage counter + // so that we will not add it again + w->target_fds[fd]++; + + aggregage_fd_type_on_openfds(all_files[fd].type, &w->openfds); +} + static inline void aggregate_pid_fds_on_targets(struct pid_stat *p) { if(unlikely(!p->updated)) { @@ -3023,6 +3027,16 @@ static inline void aggregate_pid_fds_on_targets(struct pid_stat *p) { reallocate_target_fds(u); reallocate_target_fds(g); + p->openfds.files = 0; + p->openfds.pipes = 0; + p->openfds.sockets = 0; + p->openfds.inotifies = 0; + p->openfds.eventfds = 0; + p->openfds.timerfds = 0; + p->openfds.signalfds = 0; + p->openfds.eventpolls = 0; + p->openfds.other = 0; + long currentfds = 0; size_t c, size = p->fds_size; struct pid_fd *fds = p->fds; @@ -3033,6 +3047,7 @@ static inline void aggregate_pid_fds_on_targets(struct pid_stat *p) { continue; currentfds++; + aggregage_fd_type_on_openfds(all_files[fd].type, &p->openfds); aggregate_fd_on_target(fd, w); aggregate_fd_on_target(fd, u); @@ -3079,11 +3094,11 @@ static inline void aggregate_pid_on_target(struct target *w, struct pid_stat *p, w->io_logical_bytes_read += p->io_logical_bytes_read; w->io_logical_bytes_written += p->io_logical_bytes_written; - // w->io_read_calls += p->io_read_calls; - // w->io_write_calls += p->io_write_calls; + w->io_read_calls += p->io_read_calls; + w->io_write_calls += p->io_write_calls; w->io_storage_bytes_read += p->io_storage_bytes_read; w->io_storage_bytes_written += p->io_storage_bytes_written; - // w->io_cancelled_write_bytes += p->io_cancelled_write_bytes; + w->io_cancelled_write_bytes += p->io_cancelled_write_bytes; w->processes++; w->num_threads += p->num_threads; @@ -3638,7 +3653,7 @@ static void send_collected_data_to_netdata(struct target *root, const char *type send_BEGIN(type, "files", dt); for (w = root; w; w = w->next) { if (unlikely(w->exposed && w->processes)) - send_SET(w->name, w->openfiles); + send_SET(w->name, w->openfds.files); } if (!strcmp("apps", type)){ kernel_uint_t usedfdpercentage = (kernel_uint_t) ((currentmaxfds * 100) / sysconf(_SC_OPEN_MAX)); @@ -3649,14 +3664,14 @@ static void send_collected_data_to_netdata(struct target *root, const char *type send_BEGIN(type, "sockets", dt); for (w = root; w; w = w->next) { if (unlikely(w->exposed && w->processes)) - send_SET(w->name, w->opensockets); + send_SET(w->name, w->openfds.sockets); } send_END(); send_BEGIN(type, "pipes", dt); for (w = root; w; w = w->next) { if (unlikely(w->exposed && w->processes)) - send_SET(w->name, w->openpipes); + send_SET(w->name, w->openfds.pipes); } send_END(); } @@ -3704,30 +3719,36 @@ static void send_charts_updates_to_netdata(struct target *root, const char *type if(unlikely(w->exposed)) fprintf(stdout, "DIMENSION %s '' absolute 1 %llu %s\n", w->name, time_factor * RATES_DETAIL / 100, w->hidden ? "hidden" : ""); } + APPS_PLUGIN_FUNCTIONS(); fprintf(stdout, "CHART %s.mem '' '%s Real Memory (w/o shared)' 'MiB' mem %s.mem stacked 20003 %d\n", type, title, type, update_every); for (w = root; w ; w = w->next) { if(unlikely(w->exposed)) fprintf(stdout, "DIMENSION %s '' absolute %ld %ld\n", w->name, 1L, 1024L); } + APPS_PLUGIN_FUNCTIONS(); + fprintf(stdout, "CHART %s.vmem '' '%s Virtual Memory Size' 'MiB' mem %s.vmem stacked 20005 %d\n", type, title, type, update_every); for (w = root; w ; w = w->next) { if(unlikely(w->exposed)) fprintf(stdout, "DIMENSION %s '' absolute %ld %ld\n", w->name, 1L, 1024L); } + APPS_PLUGIN_FUNCTIONS(); fprintf(stdout, "CHART %s.threads '' '%s Threads' 'threads' processes %s.threads stacked 20006 %d\n", type, title, type, update_every); for (w = root; w ; w = w->next) { if(unlikely(w->exposed)) fprintf(stdout, "DIMENSION %s '' absolute 1 1\n", w->name); } + APPS_PLUGIN_FUNCTIONS(); fprintf(stdout, "CHART %s.processes '' '%s Processes' 'processes' processes %s.processes stacked 20007 %d\n", type, title, type, update_every); for (w = root; w ; w = w->next) { if(unlikely(w->exposed)) fprintf(stdout, "DIMENSION %s '' absolute 1 1\n", w->name); } + APPS_PLUGIN_FUNCTIONS(); #ifndef __FreeBSD__ fprintf(stdout, "CHART %s.uptime '' '%s Carried Over Uptime' 'seconds' processes %s.uptime line 20008 %d\n", type, title, type, update_every); @@ -3735,6 +3756,7 @@ static void send_charts_updates_to_netdata(struct target *root, const char *type if(unlikely(w->exposed)) fprintf(stdout, "DIMENSION %s '' absolute 1 1\n", w->name); } + APPS_PLUGIN_FUNCTIONS(); if (enable_detailed_uptime_charts) { fprintf(stdout, "CHART %s.uptime_min '' '%s Minimum Uptime' 'seconds' processes %s.uptime_min line 20009 %d\n", type, title, type, update_every); @@ -3742,18 +3764,21 @@ static void send_charts_updates_to_netdata(struct target *root, const char *type if(unlikely(w->exposed)) fprintf(stdout, "DIMENSION %s '' absolute 1 1\n", w->name); } + APPS_PLUGIN_FUNCTIONS(); fprintf(stdout, "CHART %s.uptime_avg '' '%s Average Uptime' 'seconds' processes %s.uptime_avg line 20010 %d\n", type, title, type, update_every); for (w = root; w ; w = w->next) { if(unlikely(w->exposed)) fprintf(stdout, "DIMENSION %s '' absolute 1 1\n", w->name); } + APPS_PLUGIN_FUNCTIONS(); fprintf(stdout, "CHART %s.uptime_max '' '%s Maximum Uptime' 'seconds' processes %s.uptime_max line 20011 %d\n", type, title, type, update_every); for (w = root; w ; w = w->next) { if(unlikely(w->exposed)) fprintf(stdout, "DIMENSION %s '' absolute 1 1\n", w->name); } + APPS_PLUGIN_FUNCTIONS(); } #endif @@ -3762,12 +3787,14 @@ static void send_charts_updates_to_netdata(struct target *root, const char *type if(unlikely(w->exposed)) fprintf(stdout, "DIMENSION %s '' absolute 1 %llu\n", w->name, time_factor * RATES_DETAIL / 100LLU); } + APPS_PLUGIN_FUNCTIONS(); fprintf(stdout, "CHART %s.cpu_system '' '%s CPU System Time (100%% = 1 core)' 'percentage' cpu %s.cpu_system stacked 20021 %d\n", type, title, type, update_every); for (w = root; w ; w = w->next) { if(unlikely(w->exposed)) fprintf(stdout, "DIMENSION %s '' absolute 1 %llu\n", w->name, time_factor * RATES_DETAIL / 100LLU); } + APPS_PLUGIN_FUNCTIONS(); if(show_guest_time) { fprintf(stdout, "CHART %s.cpu_guest '' '%s CPU Guest Time (100%% = 1 core)' 'percentage' cpu %s.cpu_guest stacked 20022 %d\n", type, title, type, update_every); @@ -3775,6 +3802,7 @@ static void send_charts_updates_to_netdata(struct target *root, const char *type if(unlikely(w->exposed)) fprintf(stdout, "DIMENSION %s '' absolute 1 %llu\n", w->name, time_factor * RATES_DETAIL / 100LLU); } + APPS_PLUGIN_FUNCTIONS(); } #ifndef __FreeBSD__ @@ -3783,6 +3811,7 @@ static void send_charts_updates_to_netdata(struct target *root, const char *type if(unlikely(w->exposed)) fprintf(stdout, "DIMENSION %s '' absolute %ld %ld\n", w->name, 1L, 1024L); } + APPS_PLUGIN_FUNCTIONS(); #endif fprintf(stdout, "CHART %s.major_faults '' '%s Major Page Faults (swap read)' 'page faults/s' swap %s.major_faults stacked 20012 %d\n", type, title, type, update_every); @@ -3790,12 +3819,14 @@ static void send_charts_updates_to_netdata(struct target *root, const char *type if(unlikely(w->exposed)) fprintf(stdout, "DIMENSION %s '' absolute 1 %llu\n", w->name, RATES_DETAIL); } + APPS_PLUGIN_FUNCTIONS(); fprintf(stdout, "CHART %s.minor_faults '' '%s Minor Page Faults' 'page faults/s' mem %s.minor_faults stacked 20011 %d\n", type, title, type, update_every); for (w = root; w ; w = w->next) { if(unlikely(w->exposed)) fprintf(stdout, "DIMENSION %s '' absolute 1 %llu\n", w->name, RATES_DETAIL); } + APPS_PLUGIN_FUNCTIONS(); #ifdef __FreeBSD__ fprintf(stdout, "CHART %s.preads '' '%s Disk Reads' 'blocks/s' disk %s.preads stacked 20002 %d\n", type, title, type, update_every); @@ -3803,36 +3834,42 @@ static void send_charts_updates_to_netdata(struct target *root, const char *type if(unlikely(w->exposed)) fprintf(stdout, "DIMENSION %s '' absolute 1 %llu\n", w->name, RATES_DETAIL); } + APPS_PLUGIN_FUNCTIONS(); fprintf(stdout, "CHART %s.pwrites '' '%s Disk Writes' 'blocks/s' disk %s.pwrites stacked 20002 %d\n", type, title, type, update_every); for (w = root; w ; w = w->next) { if(unlikely(w->exposed)) fprintf(stdout, "DIMENSION %s '' absolute 1 %llu\n", w->name, RATES_DETAIL); } + APPS_PLUGIN_FUNCTIONS(); #else fprintf(stdout, "CHART %s.preads '' '%s Disk Reads' 'KiB/s' disk %s.preads stacked 20002 %d\n", type, title, type, update_every); for (w = root; w ; w = w->next) { if(unlikely(w->exposed)) fprintf(stdout, "DIMENSION %s '' absolute 1 %llu\n", w->name, 1024LLU * RATES_DETAIL); } + APPS_PLUGIN_FUNCTIONS(); fprintf(stdout, "CHART %s.pwrites '' '%s Disk Writes' 'KiB/s' disk %s.pwrites stacked 20002 %d\n", type, title, type, update_every); for (w = root; w ; w = w->next) { if(unlikely(w->exposed)) fprintf(stdout, "DIMENSION %s '' absolute 1 %llu\n", w->name, 1024LLU * RATES_DETAIL); } + APPS_PLUGIN_FUNCTIONS(); fprintf(stdout, "CHART %s.lreads '' '%s Disk Logical Reads' 'KiB/s' disk %s.lreads stacked 20042 %d\n", type, title, type, update_every); for (w = root; w ; w = w->next) { if(unlikely(w->exposed)) fprintf(stdout, "DIMENSION %s '' absolute 1 %llu\n", w->name, 1024LLU * RATES_DETAIL); } + APPS_PLUGIN_FUNCTIONS(); fprintf(stdout, "CHART %s.lwrites '' '%s I/O Logical Writes' 'KiB/s' disk %s.lwrites stacked 20042 %d\n", type, title, type, update_every); for (w = root; w ; w = w->next) { if(unlikely(w->exposed)) fprintf(stdout, "DIMENSION %s '' absolute 1 %llu\n", w->name, 1024LLU * RATES_DETAIL); } + APPS_PLUGIN_FUNCTIONS(); #endif if(enable_file_charts) { @@ -3842,6 +3879,7 @@ static void send_charts_updates_to_netdata(struct target *root, const char *type if (unlikely(w->exposed)) fprintf(stdout, "DIMENSION %s '' absolute 1 1\n", w->name); } + APPS_PLUGIN_FUNCTIONS(); fprintf(stdout, "CHART %s.sockets '' '%s Open Sockets' 'open sockets' net %s.sockets stacked 20051 %d\n", type, title, type, update_every); @@ -3849,6 +3887,7 @@ static void send_charts_updates_to_netdata(struct target *root, const char *type if (unlikely(w->exposed)) fprintf(stdout, "DIMENSION %s '' absolute 1 1\n", w->name); } + APPS_PLUGIN_FUNCTIONS(); fprintf(stdout, "CHART %s.pipes '' '%s Pipes' 'open pipes' processes %s.pipes stacked 20053 %d\n", type, title, type, update_every); @@ -3856,6 +3895,7 @@ static void send_charts_updates_to_netdata(struct target *root, const char *type if (unlikely(w->exposed)) fprintf(stdout, "DIMENSION %s '' absolute 1 1\n", w->name); } + APPS_PLUGIN_FUNCTIONS(); } } @@ -4134,6 +4174,592 @@ static int check_capabilities() { } #endif +netdata_mutex_t mutex = NETDATA_MUTEX_INITIALIZER; + +#define PROCESS_FILTER_CATEGORY "category:" +#define PROCESS_FILTER_USER "user:" +#define PROCESS_FILTER_GROUP "group:" +#define PROCESS_FILTER_PROCESS "process:" +#define PROCESS_FILTER_PID "pid:" +#define PROCESS_FILTER_UID "uid:" +#define PROCESS_FILTER_GID "gid:" + +static struct target *find_target_by_name(struct target *base, const char *name) { + struct target *t; + for(t = base; t ; t = t->next) { + if (strcmp(t->name, name) == 0) + return t; + } + + return NULL; +} + +static kernel_uint_t MemTotal = 0; + +static void get_MemTotal(void) { +#ifdef __FreeBSD__ + // TODO - fix this for FreeBSD + return; +#else + char filename[FILENAME_MAX + 1]; + snprintfz(filename, FILENAME_MAX, "%s/proc/meminfo", netdata_configured_host_prefix); + + procfile *ff = procfile_open(filename, ": \t", PROCFILE_FLAG_DEFAULT); + if(!ff) + return; + + ff = procfile_readall(ff); + if(!ff) + return; + + size_t line, lines = procfile_lines(ff); + + for(line = 0; line < lines ;line++) { + size_t words = procfile_linewords(ff, line); + if(words == 3 && strcmp(procfile_lineword(ff, line, 0), "MemTotal") == 0 && strcmp(procfile_lineword(ff, line, 2), "kB") == 0) { + kernel_uint_t n = str2ull(procfile_lineword(ff, line, 1)); + if(n) MemTotal = n; + break; + } + } + + procfile_close(ff); +#endif +} + +static void apps_plugin_function_error(const char *transaction, int code, const char *msg) { + char buffer[PLUGINSD_LINE_MAX + 1]; + json_escape_string(buffer, msg, PLUGINSD_LINE_MAX); + + pluginsd_function_result_begin_to_stdout(transaction, code, "application/json", now_realtime_sec()); + fprintf(stdout, "{\"status\":%d,\"error_message\":\"%s\"}", code, buffer); + pluginsd_function_result_end_to_stdout(); +} + +static void apps_plugin_function_processes_help(const char *transaction) { + pluginsd_function_result_begin_to_stdout(transaction, HTTP_RESP_OK, "text/plain", now_realtime_sec() + 3600); + fprintf(stdout, "%s", + "apps.plugin / processes\n" + "\n" + "Function `processes` presents all the currently running processes of the system.\n" + "\n" + "The following filters are supported:\n" + "\n" + " category:NAME\n" + " Shows only processes that are assigned the category `NAME` in apps_groups.conf\n" + "\n" + " user:NAME\n" + " Shows only processes that are running as user name `NAME`.\n" + "\n" + " group:NAME\n" + " Shows only processes that are running as group name `NAME`.\n" + "\n" + " process:NAME\n" + " Shows only processes that their Command is `NAME` or their parent's Command is `NAME`.\n" + "\n" + " pid:NUMBER\n" + " Shows only processes that their PID is `NUMBER` or their parent's PID is `NUMBER`\n" + "\n" + " uid:NUMBER\n" + " Shows only processes that their UID is `NUMBER`\n" + "\n" + " gid:NUMBER\n" + " Shows only processes that their GID is `NUMBER`\n" + "\n" + "Filters can be combined. Each filter can be given only one time.\n" + ); + pluginsd_function_result_end_to_stdout(); +} + +#define add_table_field(wb, key, name, visible, type, units, max, sort) do { \ + if(fields_added) buffer_strcat(wb, ","); \ + buffer_sprintf(wb, "\n \"%s\": {", key); \ + buffer_sprintf(wb, "\n \"index\":%d,", fields_added); \ + buffer_sprintf(wb, "\n \"name\":\"%s\",", name); \ + buffer_sprintf(wb, "\n \"visible\":%s,", (visible)?"true":"false"); \ + buffer_sprintf(wb, "\n \"type\":\"%s\",", type); \ + if(units) \ + buffer_sprintf(wb, "\n \"units\":\"%s\",", (char*)(units)); \ + if(!isnan(max)) \ + buffer_sprintf(wb, "\n \"max\":\"%f\",", (NETDATA_DOUBLE)(max)); \ + buffer_sprintf(wb, "\n \"sort\":\"%s\"", sort); \ + buffer_sprintf(wb, "\n }"); \ + fields_added++; \ + } while(0) + +static BUFFER *func_processes_fields = NULL; + +static void apps_plugin_function_processes(const char *transaction, char *function __maybe_unused, char *line_buffer __maybe_unused, int line_max __maybe_unused, int timeout __maybe_unused) { + struct pid_stat *p; + + char *words[PLUGINSD_MAX_WORDS] = { NULL }; + pluginsd_split_words(function, words, PLUGINSD_MAX_WORDS, NULL, NULL, 0); + + struct target *category = NULL, *user = NULL, *group = NULL; + const char *process_name = NULL; + pid_t pid = 0; + uid_t uid = 0; + gid_t gid = 0; + + bool filter_pid = false, filter_uid = false, filter_gid = false; + + for(int i = 1; i < PLUGINSD_MAX_WORDS ;i++) { + if(!words[i]) break; + + if(!category && strncmp(words[i], PROCESS_FILTER_CATEGORY, strlen(PROCESS_FILTER_CATEGORY)) == 0) { + category = find_target_by_name(apps_groups_root_target, &words[i][strlen(PROCESS_FILTER_CATEGORY)]); + if(!category) { + apps_plugin_function_error(transaction, HTTP_RESP_BAD_REQUEST, "No category with that name found."); + return; + } + } + else if(!user && strncmp(words[i], PROCESS_FILTER_USER, strlen(PROCESS_FILTER_USER)) == 0) { + user = find_target_by_name(users_root_target, &words[i][strlen(PROCESS_FILTER_USER)]); + if(!user) { + apps_plugin_function_error(transaction, HTTP_RESP_BAD_REQUEST, "No user with that name found."); + return; + } + } + else if(strncmp(words[i], PROCESS_FILTER_GROUP, strlen(PROCESS_FILTER_GROUP)) == 0) { + group = find_target_by_name(groups_root_target, &words[i][strlen(PROCESS_FILTER_GROUP)]); + if(!group) { + apps_plugin_function_error(transaction, HTTP_RESP_BAD_REQUEST, "No group with that name found."); + return; + } + } + else if(!process_name && strncmp(words[i], PROCESS_FILTER_PROCESS, strlen(PROCESS_FILTER_PROCESS)) == 0) { + process_name = &words[i][strlen(PROCESS_FILTER_PROCESS)]; + } + else if(!pid && strncmp(words[i], PROCESS_FILTER_PID, strlen(PROCESS_FILTER_PID)) == 0) { + pid = str2i(&words[i][strlen(PROCESS_FILTER_PID)]); + filter_pid = true; + } + else if(!uid && strncmp(words[i], PROCESS_FILTER_UID, strlen(PROCESS_FILTER_UID)) == 0) { + uid = str2i(&words[i][strlen(PROCESS_FILTER_UID)]); + filter_uid = true; + } + else if(!gid && strncmp(words[i], PROCESS_FILTER_GID, strlen(PROCESS_FILTER_GID)) == 0) { + gid = str2i(&words[i][strlen(PROCESS_FILTER_GID)]); + filter_gid = true; + } + else if(strcmp(words[i], "help") == 0) { + apps_plugin_function_processes_help(transaction); + return; + } + else { + char msg[PLUGINSD_LINE_MAX]; + snprintfz(msg, PLUGINSD_LINE_MAX, "Invalid parameter '%s'", words[i]); + apps_plugin_function_error(transaction, HTTP_RESP_BAD_REQUEST, msg); + return; + } + } + + time_t expires = now_realtime_sec() + update_every; + pluginsd_function_result_begin_to_stdout(transaction, HTTP_RESP_OK, "application/json", expires); + + if(unlikely(!func_processes_fields)) { + func_processes_fields = buffer_create(1000); + + int fields_added = 0;< |