0
0
Fork 0
mirror of https://github.com/netdata/netdata.git synced 2025-04-13 17:19:11 +00:00

apps.plugin improvements ()

* apps.plugin now supports simple patterns when an asterisk is in the middle of a match; expanded kernel threads matching to group them into meaningful entities

* removed cli tools

* systemd merged

* apps.plugin now has the option to print the tree with the target assignment

* apps.plugin now extracts the full comm name from the cmdline

* optimizations

* updated windows comm handling

* get the full command line on windows

* extract service names for svchost.exe processes

* get service names from SCM

* Update src/collectors/apps.plugin/README.md

Co-authored-by: Fotis Voutsas <fotis@netdata.cloud>

* Update src/collectors/apps.plugin/README.md

Co-authored-by: Fotis Voutsas <fotis@netdata.cloud>

* Update src/collectors/apps.plugin/README.md

Co-authored-by: Fotis Voutsas <fotis@netdata.cloud>

* Update src/collectors/apps.plugin/README.md

Co-authored-by: Fotis Voutsas <fotis@netdata.cloud>

* Update src/collectors/apps.plugin/README.md

Co-authored-by: Fotis Voutsas <fotis@netdata.cloud>

* Update src/collectors/apps.plugin/README.md

Co-authored-by: Fotis Voutsas <fotis@netdata.cloud>

* Update src/collectors/apps.plugin/README.md

Co-authored-by: Fotis Voutsas <fotis@netdata.cloud>

* Update src/collectors/apps.plugin/README.md

Co-authored-by: Fotis Voutsas <fotis@netdata.cloud>

* Update src/collectors/apps.plugin/README.md

Co-authored-by: Fotis Voutsas <fotis@netdata.cloud>

* Update src/collectors/apps.plugin/README.md

Co-authored-by: Fotis Voutsas <fotis@netdata.cloud>

* fix compilation on freebsd and macos

* windows priveleges

* add missing opening quote on windows spawn server

* fix alerts notifications infinite loop when alarm-notify.sh cannot be executed

---------

Co-authored-by: Fotis Voutsas <fotis@netdata.cloud>
This commit is contained in:
Costa Tsaousis 2024-10-02 18:12:41 +03:00 committed by GitHub
parent 2b6b10573f
commit 587e836019
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 804 additions and 302 deletions

View file

@ -1895,12 +1895,14 @@ if(ENABLE_PLUGIN_APPS)
src/collectors/apps.plugin/apps_os_macos.c
src/collectors/apps.plugin/apps_os_windows.c
src/collectors/apps.plugin/apps_incremental_collection.c
src/collectors/apps.plugin/apps_os_windows_nt.c
)
add_executable(apps.plugin ${APPS_PLUGIN_FILES})
target_link_libraries(apps.plugin libnetdata ${CAP_LIBRARIES}
"$<$<BOOL:${OS_WINDOWS}>:Version>")
"$<$<BOOL:${OS_WINDOWS}>:Version>"
"$<$<BOOL:${OS_WINDOWS}>:ntdll>")
target_include_directories(apps.plugin PRIVATE ${CAP_INCLUDE_DIRS})
target_compile_options(apps.plugin PRIVATE ${CAP_CFLAGS_OTHER})

View file

@ -132,6 +132,26 @@ its CPU resources will be cut in half, and data collection will be once every 2
The configuration file is `/etc/netdata/apps_groups.conf`. You can edit this file using our [`edit-config`](docs/netdata-agent/configuration/README.md) script.
### Configuring process managers
`apps.plugin` needs to know the common process managers, meaning the names of the processes
which spawn other processes. Process managers are used so that `apps.plugin` will automatically
consider all their sub-processes important to monitor.
Process managers are configured in `apps_groups.conf` with the prefix `managers:`, like this:
```
managers: process1 process2 process3
```
Multiple lines may exist, all starting with `managers:`.
The process names given here should be exactly as the operating system sets them. In Linux these
process names are limited to 15 characters. Usually the command `ps -e` or `cat /proc/{PID}/stat`
states the names needed here.
### Configuring process groups and renaming processes
The configuration file works accepts multiple lines, each having this format:
```txt
@ -140,48 +160,39 @@ group: process1 process2 ...
Each group can be given multiple times, to add more processes to it.
For the **Applications** section, only groups configured in this file are reported.
All other processes will be reported as `other`.
For each process given, its whole process tree will be grouped, not just the process matched.
The plugin will include both parents and children. If including the parents into the group is
undesirable, the line `other: *` should be appended to the `apps_groups.conf`.
For each process given, all of its sub-processes will be grouped, not just the matched process.
The process names are the ones returned by:
- `ps -e` or `cat /proc/PID/stat`
- in case of substring mode (see below): `/proc/PID/cmdline`
- **comm**: `ps -e` or `cat /proc/{PID}/stat`
- **cmdline**: in case of substring mode (see below): `/proc/{PID}/cmdline`
On Linux **comm** is limited to just a few characters. `apps.plugin` attempts to find the entire
**comm** name by looking for it at the **cmdline**. When this is successful, the entire process name
is available, otherwise the shortened one is used.
To add process names with spaces, enclose them in quotes (single or double)
example: `'Plex Media Serv'` or `"my other process"`.
You can add an asterisk `*` at the beginning and/or the end of a process:
You can add asterisks (`*`) to provide a pattern:
- `*name` _suffix_ mode: will search for processes ending with `name` (at `/proc/PID/stat`)
- `name*` _prefix_ mode: will search for processes beginning with `name` (at `/proc/PID/stat`)
- `*name*` _substring_ mode: will search for `name` in the whole command line (at `/proc/PID/cmdline`)
- `*name` _suffix_ mode: will match a **comm** ending with `name`.
- `name*` _prefix_ mode: will match a **comm** beginning with `name`.
- `*name*` _substring_ mode: will search for `name` in **cmdline**.
If you enter even just one _name_ (substring), `apps.plugin` will process
`/proc/PID/cmdline` for all processes (of course only once per process: when they are first seen).
Asterisks may appear in the middle of `name` (like `na*me`), without affecting what is being
matched (**comm** or **cmdline**).
To add processes with single quotes, enclose them in double quotes: `"process with this ' single quote"`
To add processes with double quotes, enclose them in single quotes: `'process with this " double quote'`
If a group or process name starts with a `-`, the dimension will be hidden from the chart (cpu chart only).
The order of the entries in this list is important: the first one that matches a process is used, so follow a top-down hierarchy.
Processes not matched by any row, will inherit it from their parents.
If a process starts with a `+`, debugging will be enabled for it (debugging produces a lot of output - do not enable it in production systems).
You can add any number of groups. Only the ones found running will affect the charts generated.
However, producing charts with hundreds of dimensions may slow down your web browser.
The order of the entries in this list is important: the first that matches a process is used, so put important
ones at the top. Processes not matched by any row, will inherit it from their parents or children.
The order also controls the order of the dimensions on the generated charts (although applications started
after apps.plugin is started, will be appended to the existing list of dimensions the `netdata` daemon maintains).
There are a few command line options you can pass to `apps.plugin`. The list of available options can be acquired with the `--help` flag. The options can be set in the `netdata.conf` file. For example, to disable user and user group charts you should set
There are a few command line options you can pass to `apps.plugin`. The list of available
options can be acquired with the `--help` flag. The options can be set in the `netdata.conf` using the [`edit-config` script](/docs/netdata-agent/configuration/README.md).
For example, to disable user and user group charts you would set:
```
[plugin:apps]

View file

@ -74,7 +74,7 @@ static inline void aggregate_pid_on_target(struct target *w, struct pid_stat *p,
if(!w->uptime_min || p->values[PDF_UPTIME] < w->uptime_min) w->uptime_min = p->values[PDF_UPTIME];
if(!w->uptime_max || w->uptime_max < p->values[PDF_UPTIME]) w->uptime_max = p->values[PDF_UPTIME];
if(unlikely(debug_enabled || w->debug_enabled)) {
if(unlikely(debug_enabled)) {
struct pid_on_target *pid_on_target = mallocz(sizeof(struct pid_on_target));
pid_on_target->pid = p->pid;
pid_on_target->next = w->root_pid;
@ -110,27 +110,61 @@ static inline void cleanup_exited_pids(void) {
}
}
static struct target *get_app_group_target_for_pid(struct pid_stat *p) {
static struct target *matched_apps_groups_target(struct pid_stat *p, struct target *w) {
if(is_process_manager(p))
return NULL;
p->matched_by_config = true;
return w->target ? w->target : w;
}
static struct target *get_apps_groups_target_for_pid(struct pid_stat *p) {
targets_assignment_counter++;
for(struct target *w = apps_groups_root_target; w ; w = w->next) {
if(w->type != TARGET_TYPE_APP_GROUP) continue;
// find it - 4 cases:
// 1. the target is not a pattern
// 2. the target has the prefix
// 3. the target has the suffix
// 4. the target is something inside cmdline
if(unlikely(( (!w->starts_with && !w->ends_with && w->compare == p->comm)
|| (w->starts_with && !w->ends_with && string_starts_with_string(p->comm, w->compare))
|| (!w->starts_with && w->ends_with && string_ends_with_string(p->comm, w->compare))
|| (proc_pid_cmdline_is_needed && w->starts_with && w->ends_with && strstr(pid_stat_cmdline(p), string2str(w->compare)))
))) {
p->matched_by_config = true;
if(w->target) return w->target;
else return w;
if(!w->starts_with && !w->ends_with) {
if(w->ag.pattern) {
if(simple_pattern_matches_string(w->ag.pattern, p->comm))
return matched_apps_groups_target(p, w);
}
else {
if(w->ag.compare == p->comm || w->ag.compare == p->comm_orig)
return matched_apps_groups_target(p, w);
}
}
else if(w->starts_with && !w->ends_with) {
if(w->ag.pattern) {
if(simple_pattern_matches_string(w->ag.pattern, p->comm))
return matched_apps_groups_target(p, w);
}
else {
if(string_starts_with_string(p->comm, w->ag.compare) ||
(p->comm != p->comm_orig && string_starts_with_string(p->comm, w->ag.compare)))
return matched_apps_groups_target(p, w);
}
}
else if(!w->starts_with && w->ends_with) {
if(w->ag.pattern) {
if(simple_pattern_matches_string(w->ag.pattern, p->comm))
return matched_apps_groups_target(p, w);
}
else {
if(string_ends_with_string(p->comm, w->ag.compare) ||
(p->comm != p->comm_orig && string_ends_with_string(p->comm, w->ag.compare)))
return matched_apps_groups_target(p, w);
}
}
else if(w->starts_with && w->ends_with && p->cmdline) {
if(w->ag.pattern) {
if(simple_pattern_matches_string(w->ag.pattern, p->cmdline))
return matched_apps_groups_target(p, w);
}
else {
if(strstr(string2str(p->cmdline), string2str(w->ag.compare)))
return matched_apps_groups_target(p, w);
}
}
}
@ -141,19 +175,23 @@ static void assign_a_target_to_all_processes(void) {
// assign targets from app_groups.conf
for(struct pid_stat *p = root_of_pids(); p ; p = p->next) {
if(!p->target)
p->target = get_app_group_target_for_pid(p);
p->target = get_apps_groups_target_for_pid(p);
}
// assign targets from their parents, if they have
for(struct pid_stat *p = root_of_pids(); p ; p = p->next) {
if(!p->target) {
for(struct pid_stat *pp = p->parent ; pp ; pp = pp->parent) {
if(pp->target) {
if(pp->matched_by_config) {
// we are only interested about app_groups.conf matches
p->target = pp->target;
if(!p->is_manager) {
for (struct pid_stat *pp = p->parent; pp; pp = pp->parent) {
if(pp->is_manager) break;
if (pp->target) {
if (pp->matched_by_config) {
// we are only interested about app_groups.conf matches
p->target = pp->target;
}
break;
}
break;
}
}
@ -180,6 +218,7 @@ void aggregate_processes_to_targets(void) {
// this has to be done, before the cleanup
struct target *w = NULL, *o = NULL;
(void)w; (void)o;
// concentrate everything on the targets
for(struct pid_stat *p = root_of_pids(); p ; p = p->next) {

View file

@ -4,19 +4,21 @@
## Documentation at:
## https://github.com/netdata/netdata/blob/master/src/collectors/apps.plugin/README.md
##
## The list of process managers can be configured here (uncomment and edit):
## Subprocesses of process managers are monitored.
## (uncomment to edit - the default is also hardcoded into the plugin)
## Linux
#managers: init systemd containerd-shim dumb-init gnome-shell docker-init
## Linux process managers
#managers: init systemd containerd-shim-runc-v2 dumb-init gnome-shell docker-init
#managers: openrc-run.sh crond plasmashell xfwm4
## FreeBSD
## FreeBSD process managers
#managers: init
## MacOS
## MacOS process managers
#managers: launchd
## Windows
#managers: System services wininit
## Windows process managers
#managers: wininit services explorer System
## -----------------------------------------------------------------------------
## Processes of interest
@ -26,23 +28,23 @@ netdata: netdata
## netdata known plugins
## plugins not defined here will be accumulated into netdata, above
apps.plugin: *apps.plugin*
freeipmi.plugin: *freeipmi.plugin*
nfacct.plugin: *nfacct.plugin*
cups.plugin: *cups.plugin*
xenstat.plugin: *xenstat.plugin*
perf.plugin: *perf.plugin*
charts.d.plugin: *charts.d.plugin*
python.d.plugin: *python.d.plugin*
go.d.plugin: *go.d.plugin*
systemd-journal.plugin: *systemd-journal.plugin*
network-viewer.plugin: *network-viewer.plugin*
windows-events.plugin: *windows-events.plugin*
tc-qos-helper: *tc-qos-helper.sh*
fping: fping
ioping: ioping
go.d.plugin: *go.d.plugin*
cups.plugin: *cups.plugin*
perf.plugin: *perf.plugin*
nfacct.plugin: *nfacct.plugin*
xenstat.plugin: *xenstat.plugin*
freeipmi.plugin: *freeipmi.plugin*
charts.d.plugin: *charts.d.plugin*
python.d.plugin: *python.d.plugin*
slabinfo.plugin: *slabinfo.plugin*
ebpf.plugin: *ebpf.plugin*
debugfs.plugin: *debugfs.plugin*
tc-qos-helper: *tc-qos-helper.sh*
fping: fping
ioping: ioping
## agent-service-discovery
agent_sd: agent_sd
@ -65,32 +67,26 @@ azure: mdsd *waagent* *omiserver* *omiagent* hv_kvp_daemon hv_vss_daemon *auoms*
datadog: *datadog*
newrelic: newrelic*
google-agent: *google_guest_agent* *google_osconfig_agent*
ceph: ceph-* ceph_* radosgw* rbd-* cephfs-* osdmaptool crushtool
samba: smbd nmbd winbindd ctdbd ctdb-* ctdb_*
nfs: rpcbind rpc.* nfs*
zfs: spl_* z_* txg_* zil_* arc_* l2arc*
iscsi: iscsid iscsi_eh
afp: netatalk afpd cnid_dbd cnid_metad
aws-s3: '*aws s3*' s3cmd s5cmd
proxmox-ve: pve* spiceproxy
libvirt: virtlogd virtqemud virtstoraged virtnetworkd virtlockd virtinterfaced
libvirt: virtnodedevd virtproxyd virtsecretd libvirtd
guest-agent: qemu-ga spice-vdagent cloud-init*
dhcp: *dhcp* dhclient
dhcp: dhcp* dhclient
build: cc1 cc1plus as gcc* cppcheck ld make cmake automake autoconf autoreconf
build: cargo rustc bazel buck git gdb valgrind* rpmbuild dpkg-buildpackage
packagemanager: apt* dpkg* dselect dnf yum rpm zypp* yast* pacman xbps* swupd* emerge*
packagemanager: packagekitd pkgin pkg apk snapd slackpkg slapt-get
packagemanager: apt* dpkg* dselect dnf yum rpm zypp* yast* pacman xbps* swupd*
packagemanager: packagekitd pkgin pkg apk snapd slackpkg slapt-get emerge*
clam: clam* *clam
backup: rsync lsyncd bacula* borg rclone
cron: cron* atd anacron *systemd-cron* incrond
ups: upsmon upsd */nut/* apcupsd
audio: pulse* pipewire wireplumber jack*
rabbitmq: *rabbitmq*
sidekiq: *sidekiq*
erlang: beam.smp
postfix: *postfix*
## -----------------------------------------------------------------------------
## java applications
@ -117,12 +113,134 @@ kafka: *kafka.Kafka*
## -----------------------------------------------------------------------------
## Kernel / System
## The following are interesting kernel threads and related processes to
## monitor individually, mainly for their CPU utilization.
## These kernel threads switch tasks all the time, so they should never be
## categorized as anything specific.
kernel: kworker/*
## Kernel Samepage Merging (KSM) daemon that looks for identical memory pages
## across processes and merges them to save memory.
ksmd: ksmd
khugepaged: khugepaged
## Handles migration of processes between CPU cores to balance load.
kmigration: migration/*
## Manages memory compaction, moving memory pages around to reduce
## fragmentation.
kcompactd: kcompactd*
## Responsible for freeing up memory by swapping pages to disk when needed.
kswapd: kswapd*
## DAMON is a mechanism designed to efficiently monitor the memory access
## patterns of running processes or the system itself.
kdamond: kdamond
kswapd: kswapd
zswap: zswap
kcompactd: kcompactd
ipvs: ipvs_*
## Manages ballooning in virtualized environments.
vballoon: vballoon*
## virtio - Handles or I/O (storage and network) on virtual machines.
kvirtio: virtio-* vhost-*
## Layer 4 (transport layer) load balancing
ipvs: ipvsd ipvs_* ip_vs_*
## Hugepages
## Scans memory regions and tries to promote regular-sized pages (4KB) into
## hugepages (2MB) where possible. Merge smaller contiguous 4KB pages into 2MB
## pages. Hugepages also use: kswapd, kcompactd, and migration.
khugepaged: khugepaged
## Note about zswap:
## zswap does not introduce its own dedicated kernel threads. Instead, it
## operates within the existing memory management and swapping framework of the
## kernel:
## - kswapd: swaps pages in/out of memory, using compression in the process.
## - kcompactd: compacts memory when pages are compressed or moved around.
## -----------------------------------------------------------------------------
## Block Devices
## Handles deferred block I/O operations for block devices.
kblockd: kblockd
## Device Mapper (DM)
device-mapper: kcopyd/* kcryptd/* kdmflush/* dm_bufio_cache
device-mapper: raid1/* raid5/* raid10/* multipathd bioset/*
## Software RAID (MD)
md-raid: md*_raid* md*_resync md*_reshape md*_recovery md_thread
md-raid: flush_md* raid*_sync
## iSCSI
iscsi: iscsid iscsiadm iscsi_eh/* iscsi_xmit/* iscsi_ttx/* iscsi_rx/* iscsi_trx/*
## SCSI
scsi: scsi_eh/* scsi_tmf/* scsi_wq/*
## BCACHE
bcache: bcache* bch_btree_io bch_journal
## SAS
sas: sas_task/* mpt*
## Fibre Channel (FC)
fc: fc_transport qla2xxx*
## loop devices
loop: loop* flush-loop*
## -----------------------------------------------------------------------------
## Filesystems
## Ext4
ext4: ext4-* jbd2/*
## XFS
xfs: xfs*
## BTRFS
btrfs: btrfs*
## NFS
nfs: rpcbind rpc.* nfs* rpciod
## ZFS
zfs: spl_* z_* txg_* zil_* arc_* l2arc* zfs* zed zdb zpool*
## CEPH
ceph: ceph-* ceph_* radosgw* rbd-* cephfs-*
ceph: ceph cephadm osdmaptool crushtool rados rbd
## CIFS & Samba
cifs: smbd nmbd winbindd ctdbd ctdb-* ctdb_*
cifs: cifsd cifscreds cifs.upcall
## Apple Filling Protocol (AFP)
afp: netatalk afpd cnid_dbd cnid_metad
## -----------------------------------------------------------------------------
## Desktops
systemd-journald: *systemd-journal*
systemd: systemd systemd-*
## GNOME
desktop: gnome-* gsd-* gjs goa-* gcr-* gvfs-* *xdg-*-gnome* passimd gvfsd*
desktop: at-spi-* at-spi2-* dconf-service gcr-*
## KDE
desktop: plasmashell kwin-* kde* *-kde-* klauncher kactivitymanagerd krunner
desktop: kdeconnectd ksmserver kglobalaccel5 plasma-* *org.kde.*
desktop: sddm* kwalletd5 knotify5 kmix kscreen kwayland-*
## XFCE4
desktop: xfce4-* xfwm4 xfdesktop xfce4-panel xfsettingsd xfconfd
desktop: lightdm lightdm-*
## Generic tools related to desktop
desktop: gdm gdm-* dbus-* xdg-* ibus-* evolution-* accounts-daemon colord
desktop: geoclue pulse* pipewire* wireplumber jack* touchegg pulseaudio
desktop: Xwayland Xorg

View file

@ -291,7 +291,7 @@ bool apps_os_read_pid_stat_freebsd(struct pid_stat *p, void *ptr) {
usec_t started_ut = timeval_usec(&proc_info->ki_start);
p->values[PDF_UPTIME] = (system_current_time_ut > started_ut) ? (system_current_time_ut - started_ut) / USEC_PER_SEC : 0;
if(unlikely(debug_enabled || (p->target && p->target->debug_enabled)))
if(unlikely(debug_enabled || p->target))
debug_log_int("READ PROC/PID/STAT: %s/proc/%d/stat, process: '%s' on target '%s' (dt=%llu) VALUES: utime=" KERNEL_UINT_FORMAT ", stime=" KERNEL_UINT_FORMAT ", cutime=" KERNEL_UINT_FORMAT ", cstime=" KERNEL_UINT_FORMAT ", minflt=" KERNEL_UINT_FORMAT ", majflt=" KERNEL_UINT_FORMAT ", cminflt=" KERNEL_UINT_FORMAT ", cmajflt=" KERNEL_UINT_FORMAT ", threads=%d",
netdata_configured_host_prefix, p->pid, pid_stat_comm(p), (p->target)?string2str(p->target->name):"UNSET",
p->stat_collected_usec - p->last_stat_collected_usec,

View file

@ -93,7 +93,7 @@ bool apps_os_read_pid_fds_linux(struct pid_stat *p, void *ptr __maybe_unused) {
if(unlikely(l == -1)) {
// cannot read the link
if(debug_enabled || (p->target && p->target->debug_enabled))
if(debug_enabled)
netdata_log_error("Cannot read link %s", p->fds[fdid].filename);
if(unlikely(p->fds[fdid].fd < 0)) {
@ -689,7 +689,7 @@ bool apps_os_read_pid_stat_linux(struct pid_stat *p, void *ptr __maybe_unused) {
}
}
if(unlikely(debug_enabled || (p->target && p->target->debug_enabled)))
if(unlikely(debug_enabled))
debug_log_int("READ PROC/PID/STAT: %s/proc/%d/stat, process: '%s' on target '%s' (dt=%llu) VALUES: utime=" KERNEL_UINT_FORMAT ", stime=" KERNEL_UINT_FORMAT ", cutime=" KERNEL_UINT_FORMAT ", cstime=" KERNEL_UINT_FORMAT ", minflt=" KERNEL_UINT_FORMAT ", majflt=" KERNEL_UINT_FORMAT ", cminflt=" KERNEL_UINT_FORMAT ", cmajflt=" KERNEL_UINT_FORMAT ", threads=" KERNEL_UINT_FORMAT,
netdata_configured_host_prefix, p->pid, pid_stat_comm(p), (p->target)?string2str(p->target->name):"UNSET", p->stat_collected_usec - p->last_stat_collected_usec,
p->values[PDF_UTIME],

View file

@ -242,7 +242,7 @@ bool apps_os_read_pid_stat_macos(struct pid_stat *p, void *ptr) {
// Note: Some values such as guest time, cutime, cstime, etc., are not directly available in MacOS.
// You might need to approximate or leave them unset depending on your needs.
if(unlikely(debug_enabled || (p->target && p->target->debug_enabled))) {
if(unlikely(debug_enabled || p->target)) {
debug_log_int("READ PROC/PID/STAT for MacOS: process: '%s' on target '%s' VALUES: utime=" KERNEL_UINT_FORMAT ", stime=" KERNEL_UINT_FORMAT ", minflt=" KERNEL_UINT_FORMAT ", majflt=" KERNEL_UINT_FORMAT ", threads=%d",
pid_stat_comm(p), (p->target) ? string2str(p->target->name) : "UNSET",
p->values[PDF_UTIME],

View file

@ -451,6 +451,8 @@
#include <tchar.h>
#include <strsafe.h>
WCHAR* GetProcessCommandLine(HANDLE hProcess);
struct perflib_data {
PERF_DATA_BLOCK *pDataBlock;
PERF_OBJECT_TYPE *pObjectType;
@ -458,34 +460,17 @@ struct perflib_data {
DWORD pid;
};
BOOL EnableDebugPrivilege() {
HANDLE hToken;
LUID luid;
TOKEN_PRIVILEGES tkp;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
return FALSE;
if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid))
return FALSE;
tkp.PrivilegeCount = 1;
tkp.Privileges[0].Luid = luid;
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if (!AdjustTokenPrivileges(hToken, FALSE, &tkp, sizeof(tkp), NULL, NULL))
return FALSE;
CloseHandle(hToken);
return TRUE;
}
void apps_os_init_windows(void) {
PerflibNamesRegistryInitialize();
if(!EnableDebugPrivilege())
nd_log(NDLS_COLLECTORS, NDLP_WARNING, "Failed to enable debug privilege");
if(!EnableWindowsPrivilege(SE_DEBUG_NAME))
nd_log(NDLS_COLLECTORS, NDLP_WARNING, "Failed to enable %s privilege", SE_DEBUG_NAME);
if(!EnableWindowsPrivilege(SE_SYSTEM_PROFILE_NAME))
nd_log(NDLS_COLLECTORS, NDLP_WARNING, "Failed to enable %s privilege", SE_SYSTEM_PROFILE_NAME);
if(!EnableWindowsPrivilege(SE_PROF_SINGLE_PROCESS_NAME))
nd_log(NDLS_COLLECTORS, NDLP_WARNING, "Failed to enable %s privilege", SE_PROF_SINGLE_PROCESS_NAME);
}
uint64_t apps_os_get_total_memory_windows(void) {
@ -500,10 +485,31 @@ uint64_t apps_os_get_total_memory_windows(void) {
return memStat.ullTotalPhys;
}
static __thread wchar_t unicode[PATH_MAX];
// remove the PID suffix and .exe suffix, if any
static void fix_windows_comm(struct pid_stat *p, char *comm) {
char pid[UINT64_MAX_LENGTH + 1]; // +1 for the underscore
pid[0] = '_';
print_uint64(&pid[1], p->pid);
size_t pid_len = strlen(pid);
size_t comm_len = strlen(comm);
if (pid_len < comm_len) {
char *compare = &comm[comm_len - pid_len];
if (strcmp(pid, compare) == 0)
*compare = '\0';
}
// remove the .exe suffix, if any
comm_len = strlen(comm);
size_t exe_len = strlen(".exe");
if(exe_len < comm_len) {
char *compare = &comm[comm_len - exe_len];
if (strcmp(".exe", compare) == 0)
*compare = '\0';
}
}
// Convert wide string to UTF-8
static STRING *wchar_to_string(WCHAR *s) {
static char *wchar_to_utf8(WCHAR *s) {
static __thread char utf8[PATH_MAX];
static __thread int utf8_size = sizeof(utf8);
@ -512,33 +518,152 @@ static STRING *wchar_to_string(WCHAR *s) {
return NULL;
WideCharToMultiByte(CP_UTF8, 0, s, -1, utf8, utf8_size, NULL, NULL);
return string_strdupz(utf8);
return utf8;
}
STRING *GetProcessFriendlyName(WCHAR *path) {
// Convert wide string to UTF-8
static STRING *wchar_to_string(WCHAR *s) {
return string_strdupz(wchar_to_utf8(s));
}
// --------------------------------------------------------------------------------------------------------------------
// return a sanitized name for the process
STRING *GetProcessFriendlyNameSanitized(WCHAR *path) {
static __thread uint8_t void_buf[1024 * 1024];
static __thread DWORD void_buf_size = sizeof(void_buf);
static __thread wchar_t unicode[PATH_MAX];
static __thread DWORD unicode_size = sizeof(unicode) / sizeof(*unicode);
DWORD handle;
DWORD size = GetFileVersionInfoSizeW(path, &handle);
if (size == 0 || size > sizeof(void_buf))
if (size == 0 || size > void_buf_size)
return FALSE;
if (GetFileVersionInfoW(path, handle, size, void_buf)) {
LPWSTR value = NULL;
UINT len = 0;
DWORD unicode_size = sizeof(unicode) / sizeof(*unicode);
if (VerQueryValueW(void_buf, L"\\StringFileInfo\\040904B0\\FileDescription", (LPVOID*)&value, &len) &&
len > 0 && len < unicode_size) {
wcsncpy(unicode, value, unicode_size - 1);
unicode[unicode_size - 1] = L'\0';
return wchar_to_string(unicode);
char *name = wchar_to_utf8(unicode);
sanitize_chart_meta(name);
return string_strdupz(name);
}
}
return NULL;
}
#define SERVICE_PREFIX "Service "
// return a sanitized name for the process
static STRING *GetNameFromCmdlineSanitized(struct pid_stat *p) {
if(!p->cmdline) return NULL;
char buf[string_strlen(p->cmdline) + 1];
memcpy(buf, string2str(p->cmdline), sizeof(buf));
char *words[100];
size_t num_words = quoted_strings_splitter(buf, words, 100, isspace_map_pluginsd);
if(string_strcmp(p->comm, "svchost") == 0) {
// find -s SERVICE in the command line
for(size_t i = 0; i < num_words ;i++) {
if(strcmp(words[i], "-s") == 0 && i + 1 < num_words) {
char service[strlen(words[i + 1]) + sizeof(SERVICE_PREFIX)]; // sizeof() includes a null
strcpy(service, SERVICE_PREFIX);
strcpy(&service[sizeof(SERVICE_PREFIX) - 1], words[i + 1]);
sanitize_chart_meta(service);
return string_strdupz(service);
}
}
}
return NULL;
}
static void GetServiceNames(void) {
SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE);
if (hSCManager == NULL) return;
DWORD dwBytesNeeded = 0, dwServicesReturned = 0, dwResumeHandle = 0;
ENUM_SERVICE_STATUS_PROCESS *pServiceStatus = NULL;
// First, query the required buffer size
EnumServicesStatusEx(
hSCManager, SC_ENUM_PROCESS_INFO, SERVICE_WIN32, SERVICE_STATE_ALL,
NULL, 0, &dwBytesNeeded, &dwServicesReturned, &dwResumeHandle, NULL);
if (dwBytesNeeded == 0) {
CloseServiceHandle(hSCManager);
return;
}
// Allocate memory to hold the services
pServiceStatus = mallocz(dwBytesNeeded);
// Now, retrieve the list of services
if (!EnumServicesStatusEx(
hSCManager, SC_ENUM_PROCESS_INFO, SERVICE_WIN32, SERVICE_STATE_ALL,
(LPBYTE)pServiceStatus, dwBytesNeeded, &dwBytesNeeded, &dwServicesReturned,
&dwResumeHandle, NULL)) {
freez(pServiceStatus);
CloseServiceHandle(hSCManager);
return;
}
// Loop through the services
for (DWORD i = 0; i < dwServicesReturned; i++) {
if(!pServiceStatus[i].lpDisplayName || !*pServiceStatus[i].lpDisplayName)
continue;
struct pid_stat *p = find_pid_entry((pid_t)pServiceStatus[i].ServiceStatusProcess.dwProcessId);
if(p && !p->got_service) {
p->got_service = true;
size_t len = strlen(pServiceStatus[i].lpDisplayName);
char buf[len + 1];
memcpy(buf, pServiceStatus[i].lpDisplayName, sizeof(buf));
sanitize_chart_meta(buf);
string_freez(p->name);
p->name = string_strdupz(buf);
}
}
free(pServiceStatus);
CloseServiceHandle(hSCManager);
}
static WCHAR *executable_path_from_cmdline(WCHAR *cmdline) {
if (!cmdline || !*cmdline) return NULL;
WCHAR *exe_path_start = cmdline;
WCHAR *exe_path_end = NULL;
if (cmdline[0] == L'"') {
// Command line starts with a double quote
exe_path_start++; // Move past the first double quote
exe_path_end = wcschr(exe_path_start, L'"'); // Find the next quote
}
else {
// Command line does not start with a double quote
exe_path_end = wcschr(exe_path_start, L' '); // Find the first space
}
if (exe_path_end) {
// Null-terminate the string at the end of the executable path
*exe_path_end = L'\0';
return exe_path_start;
}
return NULL;
}
void GetAllProcessesInfo(void) {
static __thread wchar_t unicode[PATH_MAX];
static __thread DWORD unicode_size = sizeof(unicode) / sizeof(*unicode);
calls_counter++;
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
@ -552,45 +677,70 @@ void GetAllProcessesInfo(void) {
return;
}
bool need_service_names = false;
do {
if(!pe32.th32ProcessID) continue;
struct pid_stat *p = get_or_allocate_pid_entry((pid_t)pe32.th32ProcessID);
p->ppid = (pid_t)pe32.th32ParentProcessID;
if(p->got_info) continue;
p->got_info = true;
if(!p->initialized) {
string_freez(p->comm);
p->comm = wchar_to_string(pe32.szExeFile);
p->assigned_to_target = false;
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, p->pid);
if (hProcess == NULL)
continue;
// Get the full command line, if possible
{
WCHAR *cmdline = GetProcessCommandLine(hProcess); // returns malloc'd buffer
if (cmdline) {
string_freez(p->cmdline);
p->cmdline = wchar_to_string(cmdline);
// extract the process full path from the command line
WCHAR *path = executable_path_from_cmdline(cmdline);
if(path) {
string_freez(p->name);
p->name = GetProcessFriendlyNameSanitized(path);
}
free(cmdline); // free(), not freez()
}
}
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, p->pid);
if (hProcess == NULL) continue;
if(!p->cmdline || !p->name) {
if (QueryFullProcessImageNameW(hProcess, 0, unicode, &unicode_size)) {
// put the full path name to the command into cmdline
if(!p->cmdline)
p->cmdline = wchar_to_string(unicode);
STRING *full_path = NULL;
STRING *friendly_name = NULL;
DWORD unicode_size = sizeof(unicode) / sizeof(*unicode);
if(QueryFullProcessImageNameW(hProcess, 0, unicode, &unicode_size)) {
full_path = wchar_to_string(unicode);
friendly_name = GetProcessFriendlyName(unicode);
if(!p->name)
p->name = GetProcessFriendlyNameSanitized(unicode);
}
}
CloseHandle(hProcess);
if(full_path) {
string_freez(p->cmdline);
p->cmdline = full_path;
char *comm = wchar_to_utf8(pe32.szExeFile);
fix_windows_comm(p, comm);
update_pid_comm(p, comm); // will sanitize p->comm
if(!need_service_names && string_strcmp(p->comm, "svchost") == 0)
need_service_names = true;
STRING *better_name = GetNameFromCmdlineSanitized(p);
if(better_name) {
string_freez(p->name);
p->name = better_name;
}
if(friendly_name) {
string_freez(p->name);
p->name = friendly_name;
p->assigned_to_target = false;
}
} while (Process32NextW(hSnapshot, &pe32));
CloseHandle(hSnapshot);
if(need_service_names)
GetServiceNames();
}
static inline kernel_uint_t perflib_cpu_utilization(COUNTER_DATA *d) {
@ -692,40 +842,17 @@ bool apps_os_collect_all_pids_windows(void) {
// a new pid
p->initialized = true;
static __thread char name[MAX_PATH];
static __thread char comm[MAX_PATH];
if (getInstanceName(d.pDataBlock, d.pObjectType, d.pi, name, sizeof(name))) {
// remove the PID suffix, if any
char pid[UINT64_MAX_LENGTH + 1]; // +1 for the underscore
pid[0] = '_';
print_uint64(&pid[1], p->pid);
size_t pid_len = strlen(pid);
size_t name_len = strlen(name);
if (pid_len < name_len) {
char *compare = &name[name_len - pid_len];
if (strcmp(pid, compare) == 0)
*compare = '\0';
}
// remove the .exe suffix, if any
name_len = strlen(name);
size_t exe_len = strlen(".exe");
if(exe_len < name_len) {
char *compare = &name[name_len - exe_len];
if (strcmp(".exe", compare) == 0)
*compare = '\0';
}
}
if (getInstanceName(d.pDataBlock, d.pObjectType, d.pi, comm, sizeof(comm)))
fix_windows_comm(p, comm);
else
strncpyz(name, "unknown", sizeof(name) - 1);
strncpyz(comm, "unknown", sizeof(comm) - 1);
if(strcmp(name, "wininit") == 0)
if(strcmp(comm, "wininit") == 0)
INIT_PID = p->pid;
string_freez(p->comm); // it may be detected in a previous run via GetAllProcessesInfo()
p->comm = string_strdupz(name);
p->got_info = false;
p->assigned_to_target = false;
update_pid_comm(p, comm); // will sanitize p->comm
added++;
COUNTER_DATA ppid = {.key = "Creating Process ID"};

View file

@ -0,0 +1,44 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// this must not include libnetdata.h because STRING is defined in winternl.h
#include "config.h"
#if defined(OS_WINDOWS)
#include <windows.h>
#include <winternl.h>
#include <psapi.h>
#include <stdint.h>
// --------------------------------------------------------------------------------------------------------------------
// Get the full windows command line
WCHAR* GetProcessCommandLine(HANDLE hProcess) {
PROCESS_BASIC_INFORMATION pbi;
ULONG len;
NTSTATUS status = NtQueryInformationProcess(hProcess, 0, &pbi, sizeof(pbi), &len);
if (status != 0)
return NULL;
// The rest of the function remains the same as before
PEB peb;
if (!ReadProcessMemory(hProcess, pbi.PebBaseAddress, &peb, sizeof(peb), NULL))
return NULL;
RTL_USER_PROCESS_PARAMETERS procParams;
if (!ReadProcessMemory(hProcess, peb.ProcessParameters, &procParams, sizeof(procParams), NULL))
return NULL;
WCHAR* commandLine = (WCHAR*)malloc(procParams.CommandLine.MaximumLength);
if (!commandLine)
return NULL;
if (!ReadProcessMemory(hProcess, procParams.CommandLine.Buffer, commandLine, procParams.CommandLine.MaximumLength, NULL)) {
free(commandLine);
return NULL;
}
return commandLine;
}
#endif

View file

@ -134,6 +134,7 @@ void del_pid_entry(pid_t pid) {
freez(p->fds);
#endif
string_freez(p->comm_orig);
string_freez(p->comm);
string_freez(p->cmdline);
aral_freez(pids.all_pids.aral, p);
@ -316,7 +317,49 @@ static inline void link_all_processes_to_their_parents(void) {
// --------------------------------------------------------------------------------------------------------------------
static inline STRING *comm_from_cmdline_sanitized(char *comm, STRING *cmdline) {
if(!cmdline) {
sanitize_chart_meta(comm);
return string_strdupz(comm);
}
const char *cl = string2str(cmdline);
size_t len = string_strlen(cmdline);
char buf_cmd[len + 1];
// if it is enclosed in (), remove the parenthesis
if(cl[0] == '(' && cl[len - 1] == ')') {
memcpy(buf_cmd, &cl[1], len - 2);
buf_cmd[len - 2] = '\0';
}
else
memcpy(buf_cmd, cl, sizeof(buf_cmd));
size_t comm_len = strlen(comm);
char *start = strstr(buf_cmd, comm);
if(start) {
char *end = start + comm_len;
while(*end && !isspace((uint8_t)*end) && *end != '/' && *end != '\\' && *end != '"') end++;
*end = '\0';
sanitize_chart_meta(start);
return string_strdupz(start);
}
sanitize_chart_meta(comm);
return string_strdupz(comm);
}
void update_pid_comm(struct pid_stat *p, const char *comm) {
if(p->comm_orig && string_strcmp(p->comm_orig, comm) == 0)
// no change
return;
#if (PROCESSES_HAVE_CMDLINE == 1)
if(likely(proc_pid_cmdline_is_needed && !p->cmdline))
managed_log(p, PID_LOG_CMDLINE, read_proc_pid_cmdline(p));
#endif
// some process names have ( and ), remove the parenthesis
size_t len = strlen(comm);
char buf[len + 1];
@ -327,22 +370,18 @@ void update_pid_comm(struct pid_stat *p, const char *comm) {
else
memcpy(buf, comm, sizeof(buf));
// check if the comm is changed
if(!p->comm || strcmp(pid_stat_comm(p), buf) != 0) {
// it is changed
string_freez(p->comm_orig);
p->comm_orig = string_strdupz(comm);
string_freez(p->comm);
p->comm = string_strdupz(buf);
string_freez(p->comm);
p->comm = comm_from_cmdline_sanitized(buf, p->cmdline);
#if (PROCESSES_HAVE_CMDLINE == 1)
if(likely(proc_pid_cmdline_is_needed))
managed_log(p, PID_LOG_CMDLINE, read_proc_pid_cmdline(p));
#endif
p->is_manager = is_process_manager(p);
p->is_aggregator = is_process_aggregator(p);
// the process changes comm, we may have to reassign it to
// an apps_groups.conf target.
p->target = NULL;
}
// the process changed comm, we may have to reassign it to
// an apps_groups.conf target.
p->target = NULL;
}
// --------------------------------------------------------------------------------------------------------------------

View file

@ -121,6 +121,84 @@ size_t pagesize;
// ----------------------------------------------------------------------------
// update chart dimensions
// Helper function to count the number of processes in the linked list
int count_processes(struct pid_stat *root) {
int count = 0;
for(struct pid_stat *p = root; p ; p = p->next)
if(p->updated) count++;
return count;
}
// Comparator function to sort by pid
int compare_by_pid(const void *a, const void *b) {
struct pid_stat *pa = *(struct pid_stat **)a;
struct pid_stat *pb = *(struct pid_stat **)b;
return ((int)pa->pid - (int)pb->pid);
}
// Function to print a process and its children recursively
void print_process_tree(struct pid_stat *root, struct pid_stat *parent, int depth, int total_processes) {
// Allocate an array of pointers for processes with the given parent
struct pid_stat **children = (struct pid_stat **)malloc(total_processes * sizeof(struct pid_stat *));
int children_count = 0;
// Populate the array with processes that have the given parent
struct pid_stat *p = root;
while (p != NULL) {
if (p->updated && p->parent == parent) {
children[children_count++] = p;
}
p = p->next;
}
// Sort the children array by pid
qsort(children, children_count, sizeof(struct pid_stat *), compare_by_pid);
// Print each child and recurse
for (int i = 0; i < children_count; i++) {
// Print the current process with indentation based on depth
if (depth > 0) {
for (int j = 0; j < (depth - 1) * 4; j++) {
printf(" ");
}
printf(" \\_ ");
}
#if (PROCESSES_HAVE_COMM_AND_NAME == 1)
printf("[%d] %s (name: %s) [%s]: %s\n", children[i]->pid,
string2str(children[i]->comm),
string2str(children[i]->name),
string2str(children[i]->target->name),
string2str(children[i]->cmdline));
#else
printf("[%d] %s [%s]: %s\n", children[i]->pid,
string2str(children[i]->comm),
string2str(children[i]->target->name),
string2str(children[i]->cmdline));
#endif
// Recurse to print this child's children
print_process_tree(root, children[i], depth + 1, total_processes);
}
// Free the allocated array
free(children);
}
// Function to print the full hierarchy
void print_hierarchy(struct pid_stat *root) {
// Count the total number of processes
int total_processes = count_processes(root);
// Start printing from processes with parent = NULL (i.e., root processes)
print_process_tree(root, NULL, 0, total_processes);
}
// ----------------------------------------------------------------------------
// update chart dimensions
#if (ALL_PIDS_ARE_READ_INSTANTLY == 0)
static void normalize_utilization(struct target *root) {
struct target *w;
@ -297,6 +375,7 @@ cleanup:
}
static bool profile_speed = false;
static bool print_tree_and_exit = false;
static void parse_args(int argc, char **argv)
{
@ -316,6 +395,11 @@ static void parse_args(int argc, char **argv)
exit(0);
}
if(strcmp("print", argv[i]) == 0 || strcmp("-print", argv[i]) == 0 || strcmp("--print", argv[i]) == 0) {
print_tree_and_exit = true;
continue;
}
#if defined(OS_LINUX)
if(strcmp("test-permissions", argv[i]) == 0 || strcmp("-t", argv[i]) == 0) {
if(!check_proc_1_io()) {
@ -618,7 +702,7 @@ int main(int argc, char **argv) {
procfile_adaptive_initial_allocation = 1;
os_get_system_HZ();
os_get_system_cpus_uncached();
apps_orchestrators_and_aggregators_init(); // before parsing args!
apps_managers_and_aggregators_init(); // before parsing args!
parse_args(argc, argv);
#if !defined(OS_WINDOWS)
@ -702,6 +786,11 @@ int main(int argc, char **argv) {
normalize_utilization(apps_groups_root_target);
#endif
if(unlikely(print_tree_and_exit)) {
print_hierarchy(root_of_pids());
exit(0);
}
if(send_resource_usage)
send_resource_usage_to_netdata(dt);

View file

@ -371,7 +371,10 @@ struct target {
TARGET_TYPE type;
union {
STRING *compare;
struct {
SIMPLE_PATTERN *pattern;
STRING *compare;
} ag;
#if (PROCESSES_HAVE_UID == 1)
uid_t uid;
#endif
@ -393,11 +396,8 @@ struct target {
#endif
bool exposed:1; // if set, we have sent this to netdata
bool hidden:1; // if set, we set the hidden flag on the dimension
bool debug_enabled:1;
bool ends_with:1;
bool starts_with:1; // if set, the compare string matches only the
// beginning of the command
bool ends_with:1; // if set, the compare string matches the end of the command
bool starts_with:1; // if set, the compare string matches the start of the command
struct pid_on_target *root_pid; // list of aggregated pids for target debugging
@ -476,7 +476,7 @@ struct pid_stat {
struct pid_stat *next;
struct pid_stat *prev;
struct target *target; // app_groups.conf targets
struct target *target; // app_groups.conf/tree targets
#if (PROCESSES_HAVE_UID == 1)
struct target *uid_target; // uid based targets
@ -485,9 +485,10 @@ struct pid_stat {
struct target *gid_target; // gid based targets
#endif
STRING *comm; // the command name (short version)
STRING *name; // a better name, or NULL
STRING *cmdline; // the full command line (or on windows, the full pathname of the program)
STRING *comm_orig; // the command, as-collected
STRING *comm; // the command, sanitized
STRING *name; // the command name if any, sanitized
STRING *cmdline; // the full command line of the program
#if defined(OS_WINDOWS)
COUNTER_DATA perflib[PDF_MAX];
@ -531,6 +532,8 @@ struct pid_stat {
bool updated:1; // true when the process is currently running
bool merged:1; // true when it has been merged to its parent
bool keep:1; // true when we need to keep this process in memory even after it exited
bool is_manager:1; // true when this pid is a process manager
bool is_aggregator:1; // true when this pid is a process aggregator
bool matched_by_config:1;
@ -540,7 +543,7 @@ struct pid_stat {
#if defined(OS_WINDOWS)
bool got_info:1;
bool assigned_to_target:1;
bool got_service:1;
bool initialized:1;
#endif
@ -631,7 +634,7 @@ bool managed_log(struct pid_stat *p, PID_LOG log, bool status);
#define pid_incremental_cpu(type, idx, value) \
incremental_rate(p->values[idx], p->raw[idx], value, p->type##_collected_usec, p->last_##type##_collected_usec, CPU_TO_NANOSECONDCORES)
void apps_orchestrators_and_aggregators_init(void);
void apps_managers_and_aggregators_init(void);
void apps_users_and_groups_init(void);
void apps_pids_init(void);
@ -675,6 +678,8 @@ struct pid_stat *find_pid_entry(pid_t pid);
void del_pid_entry(pid_t pid);
void update_pid_comm(struct pid_stat *p, const char *comm);
bool is_process_manager(struct pid_stat *p);
bool is_process_aggregator(struct pid_stat *p);
// --------------------------------------------------------------------------------------------------------------------
// targets management

View file

@ -31,35 +31,7 @@ struct target *find_target_by_name(struct target *base, const char *name) {
}
// --------------------------------------------------------------------------------------------------------------------
// Tree
static inline STRING *comm_from_cmdline(STRING *comm, STRING *cmdline) {
if(!cmdline) return sanitize_chart_meta_string(comm);
const char *cl = string2str(cmdline);
size_t len = string_strlen(cmdline);
char buf_cmd[len + 1];
// if it is enclosed in (), remove the parenthesis
if(cl[0] == '(' && cl[len - 1] == ')') {
memcpy(buf_cmd, &cl[1], len - 2);
buf_cmd[len - 2] = '\0';
}
else
memcpy(buf_cmd, cl, sizeof(buf_cmd));
char *start = strstr(buf_cmd, string2str(comm));
if(start) {
char *end = start + string_strlen(comm);
while(*end && !isspace((uint8_t)*end) && *end != '/' && *end != '\\') end++;
*end = '\0';
sanitize_chart_meta(start);
return string_strdupz(start);
}
return sanitize_chart_meta_string(comm);
}
// Process managers and aggregators
struct comm_list {
STRING *comm;
@ -111,21 +83,26 @@ static void managed_list_add(struct managed_list *list, const char *s) {
static STRING *KernelAggregator = NULL;
void apps_orchestrators_and_aggregators_init(void) {
void apps_managers_and_aggregators_init(void) {
KernelAggregator = string_strdupz("kernel");
managed_list_clear(&tree.managers);
#if defined(OS_LINUX)
managed_list_add(&tree.managers, "init"); // linux systems
managed_list_add(&tree.managers, "systemd"); // lxc containers and host systems (this also catches "systemd --user")
managed_list_add(&tree.managers, "containerd-shim"); // docker containers
managed_list_add(&tree.managers, "docker-init"); // docker containers
managed_list_add(&tree.managers, "dumb-init"); // some docker containers use this
managed_list_add(&tree.managers, "gnome-shell"); // gnome user applications
managed_list_add(&tree.managers, "init"); // linux systems
managed_list_add(&tree.managers, "systemd"); // lxc containers and host systems (this also catches "systemd --user")
managed_list_add(&tree.managers, "containerd-shim-runc-v2"); // docker containers
managed_list_add(&tree.managers, "docker-init"); // docker containers
managed_list_add(&tree.managers, "dumb-init"); // some docker containers use this
managed_list_add(&tree.managers, "openrc-run.sh"); // openrc
managed_list_add(&tree.managers, "crond"); // linux crond
managed_list_add(&tree.managers, "gnome-shell"); // gnome user applications
managed_list_add(&tree.managers, "plasmashell"); // kde user applications
managed_list_add(&tree.managers, "xfwm4"); // xfce4 user applications
#elif defined(OS_WINDOWS)
managed_list_add(&tree.managers, "System");
managed_list_add(&tree.managers, "services");
managed_list_add(&tree.managers, "wininit");
managed_list_add(&tree.managers, "services");
managed_list_add(&tree.managers, "explorer");
managed_list_add(&tree.managers, "System");
#elif defined(OS_FREEBSD)
managed_list_add(&tree.managers, "init");
#elif defined(OS_MACOS)
@ -142,49 +119,52 @@ void apps_orchestrators_and_aggregators_init(void) {
#endif
}
static inline bool is_orchestrator(struct pid_stat *p) {
bool is_process_manager(struct pid_stat *p) {
for(size_t c = 0; c < tree.managers.used ; c++) {
if(p->comm == tree.managers.array[c].comm)
if(p->comm == tree.managers.array[c].comm ||
p->comm_orig == tree.managers.array[c].comm)
return true;
}
return false;
}
static inline bool is_aggregator(struct pid_stat *p) {
bool is_process_aggregator(struct pid_stat *p) {
for(size_t c = 0; c < tree.aggregators.used ; c++) {
if(p->comm == tree.aggregators.array[c].comm)
if(p->comm == tree.aggregators.array[c].comm ||
p->comm_orig == tree.aggregators.array[c].comm)
return true;
}
return false;
}
// --------------------------------------------------------------------------------------------------------------------
// Tree
struct target *get_tree_target(struct pid_stat *p) {
// // skip fast all the children that are more than 3 levels down
// while(p->parent && p->parent->pid != INIT_PID && p->parent->parent && p->parent->parent->parent)
// p = p->parent;
// keep the children of INIT_PID, and process orchestrators
while(p->parent && p->parent->pid != INIT_PID && p->parent->pid != 0 && !is_orchestrator(p->parent))
while(p->parent && p->parent->pid != INIT_PID && p->parent->pid != 0 && !p->parent->is_manager)
p = p->parent;
// merge all processes into process aggregators
STRING *search_for = string_dup(p->comm);
bool aggregator = false;
if((p->ppid == 0 && p->pid != INIT_PID) || (p->parent && is_aggregator(p->parent))) {
aggregator = true;
STRING *search_for = NULL;
if((p->ppid == 0 && p->pid != INIT_PID) || (p->parent && p->parent->is_aggregator)) {
search_for = string_dup(KernelAggregator);
}
if(!aggregator) {
else {
#if (PROCESSES_HAVE_COMM_AND_NAME == 1)
search_for = sanitize_chart_meta_string(p->name ? p->name : p->comm);
search_for = string_dup(p->name ? p->name : p->comm);
#else
search_for = comm_from_cmdline(p->comm, p->cmdline);
search_for = string_dup(p->comm);
#endif
}
// find an existing target with the required name
struct target *w;
for(w = apps_groups_root_target; w ; w = w->next) {
if (w->name == search_for) {
@ -196,7 +176,7 @@ struct target *get_tree_target(struct pid_stat *p) {
w = callocz(sizeof(struct target), 1);
w->type = TARGET_TYPE_TREE;
w->starts_with = w->ends_with = false;
w->compare = string_dup(p->comm);
w->ag.compare = string_dup(search_for);
w->id = search_for;
w->name = string_dup(search_for);
w->clean_name = get_clean_name(w->name);
@ -302,17 +282,17 @@ struct target *apps_groups_root_target = NULL;
// find or create a new target
// there are targets that are just aggregated to other target (the second argument)
static struct target *get_apps_groups_target(const char *id, struct target *target, const char *name) {
bool tdebug = false, thidden = target ? target->hidden : false, ends_with = false, starts_with = false;
static struct target *get_apps_groups_target(const char *comm, struct target *target, const char *name) {
bool ends_with = false, starts_with = false, has_asterisk_inside = false;
STRING *id_lookup = NULL;
STRING *comm_lookup = NULL;
STRING *name_lookup = NULL;
// extract the options from the id
{
size_t len = strlen(id);
size_t len = strlen(comm);
char buf[len + 1];
memcpy(buf, id, sizeof(buf));
memcpy(buf, comm, sizeof(buf));
if(buf[len - 1] == '*') {
buf[--len] = '\0';
@ -320,37 +300,25 @@ static struct target *get_apps_groups_target(const char *id, struct target *targ
}
const char *nid = buf;
while (nid[0] == '-' || nid[0] == '+' || nid[0] == '*') {
if (nid[0] == '-') thidden = true;
if (nid[0] == '+') tdebug = true;
if (nid[0] == '*') ends_with = true;
if (nid[0] == '*') {
ends_with = true;
nid++;
}
id_lookup = string_strdupz(nid);
if(strchr(nid, '*'))
has_asterisk_inside = true;
comm_lookup = string_strdupz(nid);
}
// extract the options from the name
{
size_t len = strlen(name);
char buf[len + 1];
memcpy(buf, name, sizeof(buf));
const char *nn = buf;
while (nn[0] == '-' || nn[0] == '+') {
if (nn[0] == '-') thidden = true;
if (nn[0] == '+') tdebug = true;
nn++;
}
name_lookup = string_strdupz(nn);
}
name_lookup = string_strdupz(name);
// find if it already exists
struct target *w, *last = apps_groups_root_target;
for(w = apps_groups_root_target ; w ; w = w->next) {
if(w->id == id_lookup) {
string_freez(id_lookup);
if(w->id == comm_lookup) {
string_freez(comm_lookup);
string_freez(name_lookup);
return w;
}
@ -368,19 +336,22 @@ static struct target *get_apps_groups_target(const char *id, struct target *targ
if(target && target->target)
fatal("Internal Error: request to link process '%s' to target '%s' which is linked to target '%s'",
id, string2str(target->id), string2str(target->target->id));
comm, string2str(target->id), string2str(target->target->id));
w = callocz(sizeof(struct target), 1);
w->type = TARGET_TYPE_APP_GROUP;
w->compare = string_dup(id_lookup);
w->ag.compare = string_dup(comm_lookup);
w->starts_with = starts_with;
w->ends_with = ends_with;
w->id = string_dup(id_lookup);
w->id = string_dup(comm_lookup);
if(has_asterisk_inside)
w->ag.pattern = simple_pattern_create(comm, " ", SIMPLE_PATTERN_EXACT, true);
if(unlikely(!target))
w->name = string_dup(name_lookup); // copy the name
else
w->name = string_dup(id_lookup); // copy the id
w->name = string_dup(comm_lookup); // copy the id
// dots are used to distinguish chart type and id in streaming, so we should replace them
w->clean_name = get_clean_name(w->name);
@ -388,29 +359,20 @@ static struct target *get_apps_groups_target(const char *id, struct target *targ
if(w->starts_with && w->ends_with)
proc_pid_cmdline_is_needed = true;
w->hidden = thidden;
#ifdef NETDATA_INTERNAL_CHECKS
w->debug_enabled = tdebug;
#else
if(tdebug)
fprintf(stderr, "apps.plugin has been compiled without debugging\n");
#endif
w->target = target;
// append it, to maintain the order in apps_groups.conf
if(last) last->next = w;
else apps_groups_root_target = w;
debug_log("ADDING TARGET ID '%s', process name '%s' (%s), aggregated on target '%s', options: %s %s"
debug_log("ADDING TARGET ID '%s', process name '%s' (%s), aggregated on target '%s'"
, string2str(w->id)
, string2str(w->compare)
, string2str(w->ag.compare)
, (w->starts_with && w->ends_with)?"substring":((w->starts_with)?"prefix":((w->ends_with)?"suffix":"exact"))
, w->target?w->target->name:w->name
, (w->hidden)?"hidden":"-"
, (w->debug_enabled)?"debug":"-"
);
string_freez(id_lookup);
string_freez(comm_lookup);
string_freez(name_lookup);
return w;

View file

@ -1139,6 +1139,15 @@ int main(int argc __maybe_unused, char **argv __maybe_unused) {
sid_cache_init();
field_cache_init();
if(!EnableWindowsPrivilege(SE_SECURITY_NAME))
nd_log(NDLS_COLLECTORS, NDLP_WARNING, "Failed to enable %s privilege", SE_SECURITY_NAME);
if(!EnableWindowsPrivilege(SE_BACKUP_NAME))
nd_log(NDLS_COLLECTORS, NDLP_WARNING, "Failed to enable %s privilege", SE_BACKUP_NAME);
if(!EnableWindowsPrivilege(SE_AUDIT_NAME))
nd_log(NDLS_COLLECTORS, NDLP_WARNING, "Failed to enable %s privilege", SE_AUDIT_NAME);
// ------------------------------------------------------------------------
// debug

View file

@ -20,17 +20,27 @@ struct health_raised_summary {
};
void health_alarm_wait_for_execution(ALARM_ENTRY *ae) {
if (!(ae->flags & HEALTH_ENTRY_FLAG_EXEC_IN_PROGRESS))
return;
// this has to ALWAYS remove the given alarm entry from the queue
if(!ae->popen_instance) {
// nd_log(NDLS_DAEMON, NDLP_ERR, "attempted to wait for the execution of alert that has not spawn a notification");
return;
int code = 0;
if (!(ae->flags & HEALTH_ENTRY_FLAG_EXEC_IN_PROGRESS)) {
nd_log(NDLS_DAEMON, NDLP_ERR, "attempted to wait for the execution of alert that has not an execution in progress");
code = 128;
goto cleanup;
}
ae->exec_code = spawn_popen_wait(ae->popen_instance);
if(!ae->popen_instance) {
nd_log(NDLS_DAEMON, NDLP_ERR, "attempted to wait for the execution of alert that has not spawn a notification");
code = 128;
goto cleanup;
}
code = spawn_popen_wait(ae->popen_instance);
netdata_log_debug(D_HEALTH, "done executing command - returned with code %d", ae->exec_code);
cleanup:
ae->exec_code = code;
ae->flags &= ~HEALTH_ENTRY_FLAG_EXEC_IN_PROGRESS;
if(ae->exec_code != 0)
@ -466,13 +476,18 @@ void health_send_notification(RRDHOST *host, ALARM_ENTRY *ae, struct health_rais
ae->exec_run_timestamp = now_realtime_sec(); /* will be updated by real time after spawning */
netdata_log_debug(D_HEALTH, "executing command '%s'", command_to_run);
ae->flags |= HEALTH_ENTRY_FLAG_EXEC_IN_PROGRESS;
ae->popen_instance = spawn_popen_run(command_to_run);
enqueue_alarm_notify_in_progress(ae);
if(ae->popen_instance) {
ae->flags |= HEALTH_ENTRY_FLAG_EXEC_IN_PROGRESS;
enqueue_alarm_notify_in_progress(ae);
}
else
netdata_log_error("Failed to execute alarm notification");
health_alarm_log_save(host, ae);
} else {
netdata_log_error("Failed to format command arguments");
}
else
netdata_log_error("Failed to format command arguments");
buffer_free(warn_alarms);
buffer_free(crit_alarms);

View file

@ -58,4 +58,42 @@ bool netdata_registry_get_string(char *out, unsigned int length, void *hKey, cha
return status;
}
bool EnableWindowsPrivilege(const char *privilegeName) {
HANDLE hToken;
LUID luid;
TOKEN_PRIVILEGES tkp;
// Open the process token with appropriate access rights
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
return false;
// Lookup the LUID for the specified privilege
if (!LookupPrivilegeValue(NULL, privilegeName, &luid)) {
CloseHandle(hToken); // Close the token handle before returning
return false;
}
// Set up the TOKEN_PRIVILEGES structure
tkp.PrivilegeCount = 1;
tkp.Privileges[0].Luid = luid;
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
// Adjust the token's privileges
if (!AdjustTokenPrivileges(hToken, FALSE, &tkp, sizeof(tkp), NULL, NULL)) {
CloseHandle(hToken); // Close the token handle before returning
return false;
}
// Check if AdjustTokenPrivileges succeeded
if (GetLastError() == ERROR_NOT_ALL_ASSIGNED) {
CloseHandle(hToken); // Close the token handle before returning
return false;
}
// Close the handle to the token after success
CloseHandle(hToken);
return true;
}
#endif

View file

@ -14,5 +14,7 @@ bool netdata_registry_get_dword(unsigned int *out, void *hKey, char *subKey, cha
long netdata_registry_get_string_from_open_key(char *out, unsigned int length, void *lKey, char *name);
bool netdata_registry_get_string(char *out, unsigned int length, void *hKey, char *subKey, char *name);
bool EnableWindowsPrivilege(const char *privilegeName);
#endif // OS_WINDOWS
#endif //NETDATA_OS_WINDOWS_WRAPPERS_H

View file

@ -54,7 +54,7 @@ static BUFFER *argv_to_windows(const char **argv) {
BUFFER *wb = buffer_create(0, NULL);
// argv[0] is the path
char b[strlen(argv[0]) * 2 + 1024];
char b[strlen(argv[0]) * 2 + FILENAME_MAX];
cygwin_conv_path(CCP_POSIX_TO_WIN_A | CCP_ABSOLUTE, argv[0], b, sizeof(b));
for(size_t i = 0; argv[i] ;i++) {
@ -84,6 +84,8 @@ static BUFFER *argv_to_windows(const char **argv) {
else
buffer_putc(wb, ' ');
}
else if (needs_quotes)
buffer_putc(wb, '"');
for(const char *c = s; *c ; c++) {
switch(*c) {