@cryptotaxi247 / netdata-1 / commits / 236c04b92

Systemd units function (#16318)

* split systemd-journal.c * split fstat caching * split systemd-journal further * working systemd-units function * do not enable systemd-units when libsystemd does not provide the interface * move the header to the right place * mixed parantheses * update codacy exlcusions * update codacy exlcusions * update codacy exlcusions * added option to show show expanded filters by default * keep the original extension and decode descriptions too * updated systemd-units function to handle all known unit states * dont show the path by default * final touches * remove trailing spaces

Costa Tsaousis committed Nov 2, 2023 at 14:29 UTC 236c04b925cbd56d6cddbd99c86af3cdc0a78067
14 files changed +2794 -1297
.codacy.yml
+8 -1
@@ -10,9 +10,16 @@ exclude_paths:
10 - web/gui/lib/**
11 - web/gui/old/**
12 - web/gui/src/**
13 + - web/gui/v1/**
14 - web/gui/v2/**
15 - web/gui/main.js
16 - tests/**
17 - aclk/tests/**
18 - libnetdata/libjudy/**
18 -
19 + - database/sqlite/sqlite3.c
20 + - ml/dlib/**
21 + - web/server/h2o/libh2o/**
22 + - build/**
23 + - build_external/**
24 + - libnetdata/dyn_conf/tests/**
25 + - packaging/**
Makefile.am
+6
@@ -312,7 +312,13 @@ FREEIPMI_PLUGIN_FILES = \
312 $(NULL)
313
314 SYSTEMD_JOURNAL_PLUGIN_FILES = \
315 + collectors/systemd-journal.plugin/systemd-internals.h \
316 + collectors/systemd-journal.plugin/systemd-main.c \
317 + collectors/systemd-journal.plugin/systemd-units.c \
318 collectors/systemd-journal.plugin/systemd-journal.c \
319 + collectors/systemd-journal.plugin/systemd-journal-annotations.c \
320 + collectors/systemd-journal.plugin/systemd-journal-files.c \
321 + collectors/systemd-journal.plugin/systemd-journal-fstat.c \
322 $(LIBNETDATA_FILES) \
323 $(NULL)
324
collectors/systemd-journal.plugin/systemd-internals.h new
+108
@@ -0,0 +1,108 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#ifndef NETDATA_COLLECTORS_SYSTEMD_INTERNALS_H
4 +#define NETDATA_COLLECTORS_SYSTEMD_INTERNALS_H
5 +
6 +#include "collectors/all.h"
7 +#include "libnetdata/libnetdata.h"
8 +
9 +#include <linux/capability.h>
10 +#include <systemd/sd-journal.h>
11 +#include <syslog.h>
12 +
13 +#define SYSTEMD_JOURNAL_FUNCTION_DESCRIPTION "View, search and analyze systemd journal entries."
14 +#define SYSTEMD_JOURNAL_FUNCTION_NAME "systemd-journal"
15 +#define SYSTEMD_JOURNAL_DEFAULT_TIMEOUT 60
16 +
17 +#define SYSTEMD_UNITS_FUNCTION_DESCRIPTION "View the status of systemd units"
18 +#define SYSTEMD_UNITS_FUNCTION_NAME "systemd-units"
19 +#define SYSTEMD_UNITS_DEFAULT_TIMEOUT 30
20 +
21 +extern __thread size_t fstat_thread_calls;
22 +extern __thread size_t fstat_thread_cached_responses;
23 +void fstat_cache_enable_on_thread(void);
24 +void fstat_cache_disable_on_thread(void);
25 +
26 +extern netdata_mutex_t stdout_mutex;
27 +
28 +
29 +typedef enum {
30 + ND_SD_JOURNAL_NO_FILE_MATCHED,
31 + ND_SD_JOURNAL_FAILED_TO_OPEN,
32 + ND_SD_JOURNAL_FAILED_TO_SEEK,
33 + ND_SD_JOURNAL_TIMED_OUT,
34 + ND_SD_JOURNAL_OK,
35 + ND_SD_JOURNAL_NOT_MODIFIED,
36 + ND_SD_JOURNAL_CANCELLED,
37 +} ND_SD_JOURNAL_STATUS;
38 +
39 +typedef enum {
40 + SDJF_NONE = 0,
41 + SDJF_ALL = (1 << 0),
42 + SDJF_LOCAL_ALL = (1 << 1),
43 + SDJF_REMOTE_ALL = (1 << 2),
44 + SDJF_LOCAL_SYSTEM = (1 << 3),
45 + SDJF_LOCAL_USER = (1 << 4),
46 + SDJF_LOCAL_NAMESPACE = (1 << 5),
47 + SDJF_LOCAL_OTHER = (1 << 6),
48 +} SD_JOURNAL_FILE_SOURCE_TYPE;
49 +
50 +struct journal_file {
51 + const char *filename;
52 + size_t filename_len;
53 + STRING *source;
54 + SD_JOURNAL_FILE_SOURCE_TYPE source_type;
55 + usec_t file_last_modified_ut;
56 + usec_t msg_first_ut;
57 + usec_t msg_last_ut;
58 + usec_t last_scan_ut;
59 + size_t size;
60 + bool logged_failure;
61 + usec_t max_journal_vs_realtime_delta_ut;
62 +};
63 +
64 +#define SDJF_SOURCE_ALL_NAME "all"
65 +#define SDJF_SOURCE_LOCAL_NAME "all-local-logs"
66 +#define SDJF_SOURCE_LOCAL_SYSTEM_NAME "all-local-system-logs"
67 +#define SDJF_SOURCE_LOCAL_USERS_NAME "all-local-user-logs"
68 +#define SDJF_SOURCE_LOCAL_OTHER_NAME "all-uncategorized"
69 +#define SDJF_SOURCE_NAMESPACES_NAME "all-local-namespaces"
70 +#define SDJF_SOURCE_REMOTES_NAME "all-remote-systems"
71 +
72 +#define ND_SD_JOURNAL_OPEN_FLAGS (0)
73 +
74 +#define JOURNAL_VS_REALTIME_DELTA_DEFAULT_UT (5 * USEC_PER_SEC) // assume always 5 seconds latency
75 +#define JOURNAL_VS_REALTIME_DELTA_MAX_UT (2 * 60 * USEC_PER_SEC) // up to 2 minutes latency
76 +
77 +extern DICTIONARY *journal_files_registry;
78 +extern DICTIONARY *used_hashes_registry;
79 +extern DICTIONARY *function_query_status_dict;
80 +extern DICTIONARY *boot_ids_to_first_ut;
81 +
82 +int journal_file_dict_items_backward_compar(const void *a, const void *b);
83 +int journal_file_dict_items_forward_compar(const void *a, const void *b);
84 +void buffer_json_journal_versions(BUFFER *wb);
85 +void available_journal_file_sources_to_json_array(BUFFER *wb);
86 +
87 +FACET_ROW_SEVERITY syslog_priority_to_facet_severity(FACETS *facets, FACET_ROW *row, void *data);
88 +
89 +void netdata_systemd_journal_dynamic_row_id(FACETS *facets, BUFFER *json_array, FACET_ROW_KEY_VALUE *rkv, FACET_ROW *row, void *data);
90 +void netdata_systemd_journal_transform_priority(FACETS *facets, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope, void *data);
91 +void netdata_systemd_journal_transform_syslog_facility(FACETS *facets, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope, void *data);
92 +void netdata_systemd_journal_transform_errno(FACETS *facets, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope, void *data);
93 +void netdata_systemd_journal_transform_boot_id(FACETS *facets, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope, void *data);
94 +void netdata_systemd_journal_transform_uid(FACETS *facets, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope, void *data);
95 +void netdata_systemd_journal_transform_gid(FACETS *facets, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope, void *data);
96 +void netdata_systemd_journal_transform_cap_effective(FACETS *facets, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope, void *data);
97 +void netdata_systemd_journal_transform_timestamp_usec(FACETS *facets, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope, void *data);
98 +
99 +void journal_init_files_and_directories(void);
100 +void journal_init_query_status(void);
101 +void function_systemd_journal(const char *transaction, char *function, int timeout, bool *cancelled);
102 +void journal_files_registry_update(void);
103 +
104 +#ifdef ENABLE_SYSTEMD_DBUS
105 +void function_systemd_units(const char *transaction, char *function, int timeout, bool *cancelled);
106 +#endif
107 +
108 +#endif //NETDATA_COLLECTORS_SYSTEMD_INTERNALS_H
collectors/systemd-journal.plugin/systemd-journal-annotations.c new
+590
@@ -0,0 +1,590 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "systemd-internals.h"
4 +
5 +const char *errno_map[] = {
6 + [1] = "1 (EPERM)", // "Operation not permitted",
7 + [2] = "2 (ENOENT)", // "No such file or directory",
8 + [3] = "3 (ESRCH)", // "No such process",
9 + [4] = "4 (EINTR)", // "Interrupted system call",
10 + [5] = "5 (EIO)", // "Input/output error",
11 + [6] = "6 (ENXIO)", // "No such device or address",
12 + [7] = "7 (E2BIG)", // "Argument list too long",
13 + [8] = "8 (ENOEXEC)", // "Exec format error",
14 + [9] = "9 (EBADF)", // "Bad file descriptor",
15 + [10] = "10 (ECHILD)", // "No child processes",
16 + [11] = "11 (EAGAIN)", // "Resource temporarily unavailable",
17 + [12] = "12 (ENOMEM)", // "Cannot allocate memory",
18 + [13] = "13 (EACCES)", // "Permission denied",
19 + [14] = "14 (EFAULT)", // "Bad address",
20 + [15] = "15 (ENOTBLK)", // "Block device required",
21 + [16] = "16 (EBUSY)", // "Device or resource busy",
22 + [17] = "17 (EEXIST)", // "File exists",
23 + [18] = "18 (EXDEV)", // "Invalid cross-device link",
24 + [19] = "19 (ENODEV)", // "No such device",
25 + [20] = "20 (ENOTDIR)", // "Not a directory",
26 + [21] = "21 (EISDIR)", // "Is a directory",
27 + [22] = "22 (EINVAL)", // "Invalid argument",
28 + [23] = "23 (ENFILE)", // "Too many open files in system",
29 + [24] = "24 (EMFILE)", // "Too many open files",
30 + [25] = "25 (ENOTTY)", // "Inappropriate ioctl for device",
31 + [26] = "26 (ETXTBSY)", // "Text file busy",
32 + [27] = "27 (EFBIG)", // "File too large",
33 + [28] = "28 (ENOSPC)", // "No space left on device",
34 + [29] = "29 (ESPIPE)", // "Illegal seek",
35 + [30] = "30 (EROFS)", // "Read-only file system",
36 + [31] = "31 (EMLINK)", // "Too many links",
37 + [32] = "32 (EPIPE)", // "Broken pipe",
38 + [33] = "33 (EDOM)", // "Numerical argument out of domain",
39 + [34] = "34 (ERANGE)", // "Numerical result out of range",
40 + [35] = "35 (EDEADLK)", // "Resource deadlock avoided",
41 + [36] = "36 (ENAMETOOLONG)", // "File name too long",
42 + [37] = "37 (ENOLCK)", // "No locks available",
43 + [38] = "38 (ENOSYS)", // "Function not implemented",
44 + [39] = "39 (ENOTEMPTY)", // "Directory not empty",
45 + [40] = "40 (ELOOP)", // "Too many levels of symbolic links",
46 + [42] = "42 (ENOMSG)", // "No message of desired type",
47 + [43] = "43 (EIDRM)", // "Identifier removed",
48 + [44] = "44 (ECHRNG)", // "Channel number out of range",
49 + [45] = "45 (EL2NSYNC)", // "Level 2 not synchronized",
50 + [46] = "46 (EL3HLT)", // "Level 3 halted",
51 + [47] = "47 (EL3RST)", // "Level 3 reset",
52 + [48] = "48 (ELNRNG)", // "Link number out of range",
53 + [49] = "49 (EUNATCH)", // "Protocol driver not attached",
54 + [50] = "50 (ENOCSI)", // "No CSI structure available",
55 + [51] = "51 (EL2HLT)", // "Level 2 halted",
56 + [52] = "52 (EBADE)", // "Invalid exchange",
57 + [53] = "53 (EBADR)", // "Invalid request descriptor",
58 + [54] = "54 (EXFULL)", // "Exchange full",
59 + [55] = "55 (ENOANO)", // "No anode",
60 + [56] = "56 (EBADRQC)", // "Invalid request code",
61 + [57] = "57 (EBADSLT)", // "Invalid slot",
62 + [59] = "59 (EBFONT)", // "Bad font file format",
63 + [60] = "60 (ENOSTR)", // "Device not a stream",
64 + [61] = "61 (ENODATA)", // "No data available",
65 + [62] = "62 (ETIME)", // "Timer expired",
66 + [63] = "63 (ENOSR)", // "Out of streams resources",
67 + [64] = "64 (ENONET)", // "Machine is not on the network",
68 + [65] = "65 (ENOPKG)", // "Package not installed",
69 + [66] = "66 (EREMOTE)", // "Object is remote",
70 + [67] = "67 (ENOLINK)", // "Link has been severed",
71 + [68] = "68 (EADV)", // "Advertise error",
72 + [69] = "69 (ESRMNT)", // "Srmount error",
73 + [70] = "70 (ECOMM)", // "Communication error on send",
74 + [71] = "71 (EPROTO)", // "Protocol error",
75 + [72] = "72 (EMULTIHOP)", // "Multihop attempted",
76 + [73] = "73 (EDOTDOT)", // "RFS specific error",
77 + [74] = "74 (EBADMSG)", // "Bad message",
78 + [75] = "75 (EOVERFLOW)", // "Value too large for defined data type",
79 + [76] = "76 (ENOTUNIQ)", // "Name not unique on network",
80 + [77] = "77 (EBADFD)", // "File descriptor in bad state",
81 + [78] = "78 (EREMCHG)", // "Remote address changed",
82 + [79] = "79 (ELIBACC)", // "Can not access a needed shared library",
83 + [80] = "80 (ELIBBAD)", // "Accessing a corrupted shared library",
84 + [81] = "81 (ELIBSCN)", // ".lib section in a.out corrupted",
85 + [82] = "82 (ELIBMAX)", // "Attempting to link in too many shared libraries",
86 + [83] = "83 (ELIBEXEC)", // "Cannot exec a shared library directly",
87 + [84] = "84 (EILSEQ)", // "Invalid or incomplete multibyte or wide character",
88 + [85] = "85 (ERESTART)", // "Interrupted system call should be restarted",
89 + [86] = "86 (ESTRPIPE)", // "Streams pipe error",
90 + [87] = "87 (EUSERS)", // "Too many users",
91 + [88] = "88 (ENOTSOCK)", // "Socket operation on non-socket",
92 + [89] = "89 (EDESTADDRREQ)", // "Destination address required",
93 + [90] = "90 (EMSGSIZE)", // "Message too long",
94 + [91] = "91 (EPROTOTYPE)", // "Protocol wrong type for socket",
95 + [92] = "92 (ENOPROTOOPT)", // "Protocol not available",
96 + [93] = "93 (EPROTONOSUPPORT)", // "Protocol not supported",
97 + [94] = "94 (ESOCKTNOSUPPORT)", // "Socket type not supported",
98 + [95] = "95 (ENOTSUP)", // "Operation not supported",
99 + [96] = "96 (EPFNOSUPPORT)", // "Protocol family not supported",
100 + [97] = "97 (EAFNOSUPPORT)", // "Address family not supported by protocol",
101 + [98] = "98 (EADDRINUSE)", // "Address already in use",
102 + [99] = "99 (EADDRNOTAVAIL)", // "Cannot assign requested address",
103 + [100] = "100 (ENETDOWN)", // "Network is down",
104 + [101] = "101 (ENETUNREACH)", // "Network is unreachable",
105 + [102] = "102 (ENETRESET)", // "Network dropped connection on reset",
106 + [103] = "103 (ECONNABORTED)", // "Software caused connection abort",
107 + [104] = "104 (ECONNRESET)", // "Connection reset by peer",
108 + [105] = "105 (ENOBUFS)", // "No buffer space available",
109 + [106] = "106 (EISCONN)", // "Transport endpoint is already connected",
110 + [107] = "107 (ENOTCONN)", // "Transport endpoint is not connected",
111 + [108] = "108 (ESHUTDOWN)", // "Cannot send after transport endpoint shutdown",
112 + [109] = "109 (ETOOMANYREFS)", // "Too many references: cannot splice",
113 + [110] = "110 (ETIMEDOUT)", // "Connection timed out",
114 + [111] = "111 (ECONNREFUSED)", // "Connection refused",
115 + [112] = "112 (EHOSTDOWN)", // "Host is down",
116 + [113] = "113 (EHOSTUNREACH)", // "No route to host",
117 + [114] = "114 (EALREADY)", // "Operation already in progress",
118 + [115] = "115 (EINPROGRESS)", // "Operation now in progress",
119 + [116] = "116 (ESTALE)", // "Stale file handle",
120 + [117] = "117 (EUCLEAN)", // "Structure needs cleaning",
121 + [118] = "118 (ENOTNAM)", // "Not a XENIX named type file",
122 + [119] = "119 (ENAVAIL)", // "No XENIX semaphores available",
123 + [120] = "120 (EISNAM)", // "Is a named type file",
124 + [121] = "121 (EREMOTEIO)", // "Remote I/O error",
125 + [122] = "122 (EDQUOT)", // "Disk quota exceeded",
126 + [123] = "123 (ENOMEDIUM)", // "No medium found",
127 + [124] = "124 (EMEDIUMTYPE)", // "Wrong medium type",
128 + [125] = "125 (ECANCELED)", // "Operation canceled",
129 + [126] = "126 (ENOKEY)", // "Required key not available",
130 + [127] = "127 (EKEYEXPIRED)", // "Key has expired",
131 + [128] = "128 (EKEYREVOKED)", // "Key has been revoked",
132 + [129] = "129 (EKEYREJECTED)", // "Key was rejected by service",
133 + [130] = "130 (EOWNERDEAD)", // "Owner died",
134 + [131] = "131 (ENOTRECOVERABLE)", // "State not recoverable",
135 + [132] = "132 (ERFKILL)", // "Operation not possible due to RF-kill",
136 + [133] = "133 (EHWPOISON)", // "Memory page has hardware error",
137 +};
138 +
139 +const char *linux_capabilities[] = {
140 + [CAP_CHOWN] = "CHOWN",
141 + [CAP_DAC_OVERRIDE] = "DAC_OVERRIDE",
142 + [CAP_DAC_READ_SEARCH] = "DAC_READ_SEARCH",
143 + [CAP_FOWNER] = "FOWNER",
144 + [CAP_FSETID] = "FSETID",
145 + [CAP_KILL] = "KILL",
146 + [CAP_SETGID] = "SETGID",
147 + [CAP_SETUID] = "SETUID",
148 + [CAP_SETPCAP] = "SETPCAP",
149 + [CAP_LINUX_IMMUTABLE] = "LINUX_IMMUTABLE",
150 + [CAP_NET_BIND_SERVICE] = "NET_BIND_SERVICE",
151 + [CAP_NET_BROADCAST] = "NET_BROADCAST",
152 + [CAP_NET_ADMIN] = "NET_ADMIN",
153 + [CAP_NET_RAW] = "NET_RAW",
154 + [CAP_IPC_LOCK] = "IPC_LOCK",
155 + [CAP_IPC_OWNER] = "IPC_OWNER",
156 + [CAP_SYS_MODULE] = "SYS_MODULE",
157 + [CAP_SYS_RAWIO] = "SYS_RAWIO",
158 + [CAP_SYS_CHROOT] = "SYS_CHROOT",
159 + [CAP_SYS_PTRACE] = "SYS_PTRACE",
160 + [CAP_SYS_PACCT] = "SYS_PACCT",
161 + [CAP_SYS_ADMIN] = "SYS_ADMIN",
162 + [CAP_SYS_BOOT] = "SYS_BOOT",
163 + [CAP_SYS_NICE] = "SYS_NICE",
164 + [CAP_SYS_RESOURCE] = "SYS_RESOURCE",
165 + [CAP_SYS_TIME] = "SYS_TIME",
166 + [CAP_SYS_TTY_CONFIG] = "SYS_TTY_CONFIG",
167 + [CAP_MKNOD] = "MKNOD",
168 + [CAP_LEASE] = "LEASE",
169 + [CAP_AUDIT_WRITE] = "AUDIT_WRITE",
170 + [CAP_AUDIT_CONTROL] = "AUDIT_CONTROL",
171 + [CAP_SETFCAP] = "SETFCAP",
172 + [CAP_MAC_OVERRIDE] = "MAC_OVERRIDE",
173 + [CAP_MAC_ADMIN] = "MAC_ADMIN",
174 + [CAP_SYSLOG] = "SYSLOG",
175 + [CAP_WAKE_ALARM] = "WAKE_ALARM",
176 + [CAP_BLOCK_SUSPEND] = "BLOCK_SUSPEND",
177 + [37 /*CAP_AUDIT_READ*/] = "AUDIT_READ",
178 + [38 /*CAP_PERFMON*/] = "PERFMON",
179 + [39 /*CAP_BPF*/] = "BPF",
180 + [40 /* CAP_CHECKPOINT_RESTORE */] = "CHECKPOINT_RESTORE",
181 +};
182 +
183 +static const char *syslog_facility_to_name(int facility) {
184 + switch (facility) {
185 + case LOG_FAC(LOG_KERN): return "kern";
186 + case LOG_FAC(LOG_USER): return "user";
187 + case LOG_FAC(LOG_MAIL): return "mail";
188 + case LOG_FAC(LOG_DAEMON): return "daemon";
189 + case LOG_FAC(LOG_AUTH): return "auth";
190 + case LOG_FAC(LOG_SYSLOG): return "syslog";
191 + case LOG_FAC(LOG_LPR): return "lpr";
192 + case LOG_FAC(LOG_NEWS): return "news";
193 + case LOG_FAC(LOG_UUCP): return "uucp";
194 + case LOG_FAC(LOG_CRON): return "cron";
195 + case LOG_FAC(LOG_AUTHPRIV): return "authpriv";
196 + case LOG_FAC(LOG_FTP): return "ftp";
197 + case LOG_FAC(LOG_LOCAL0): return "local0";
198 + case LOG_FAC(LOG_LOCAL1): return "local1";
199 + case LOG_FAC(LOG_LOCAL2): return "local2";
200 + case LOG_FAC(LOG_LOCAL3): return "local3";
201 + case LOG_FAC(LOG_LOCAL4): return "local4";
202 + case LOG_FAC(LOG_LOCAL5): return "local5";
203 + case LOG_FAC(LOG_LOCAL6): return "local6";
204 + case LOG_FAC(LOG_LOCAL7): return "local7";
205 + default: return NULL;
206 + }
207 +}
208 +
209 +static const char *syslog_priority_to_name(int priority) {
210 + switch (priority) {
211 + case LOG_ALERT: return "alert";
212 + case LOG_CRIT: return "critical";
213 + case LOG_DEBUG: return "debug";
214 + case LOG_EMERG: return "panic";
215 + case LOG_ERR: return "error";
216 + case LOG_INFO: return "info";
217 + case LOG_NOTICE: return "notice";
218 + case LOG_WARNING: return "warning";
219 + default: return NULL;
220 + }
221 +}
222 +
223 +FACET_ROW_SEVERITY syslog_priority_to_facet_severity(FACETS *facets __maybe_unused, FACET_ROW *row, void *data __maybe_unused) {
224 + // same to
225 + // https://github.com/systemd/systemd/blob/aab9e4b2b86905a15944a1ac81e471b5b7075932/src/basic/terminal-util.c#L1501
226 + // function get_log_colors()
227 +
228 + FACET_ROW_KEY_VALUE *priority_rkv = dictionary_get(row->dict, "PRIORITY");
229 + if(!priority_rkv || priority_rkv->empty)
230 + return FACET_ROW_SEVERITY_NORMAL;
231 +
232 + int priority = str2i(buffer_tostring(priority_rkv->wb));
233 +
234 + if(priority <= LOG_ERR)
235 + return FACET_ROW_SEVERITY_CRITICAL;
236 +
237 + else if (priority <= LOG_WARNING)
238 + return FACET_ROW_SEVERITY_WARNING;
239 +
240 + else if(priority <= LOG_NOTICE)
241 + return FACET_ROW_SEVERITY_NOTICE;
242 +
243 + else if(priority >= LOG_DEBUG)
244 + return FACET_ROW_SEVERITY_DEBUG;
245 +
246 + return FACET_ROW_SEVERITY_NORMAL;
247 +}
248 +
249 +static char *uid_to_username(uid_t uid, char *buffer, size_t buffer_size) {
250 + static __thread char tmp[1024 + 1];
251 + struct passwd pw, *result = NULL;
252 +
253 + if (getpwuid_r(uid, &pw, tmp, sizeof(tmp), &result) != 0 || !result || !pw.pw_name || !(*pw.pw_name))
254 + snprintfz(buffer, buffer_size - 1, "%u", uid);
255 + else
256 + snprintfz(buffer, buffer_size - 1, "%u (%s)", uid, pw.pw_name);
257 +
258 + return buffer;
259 +}
260 +
261 +static char *gid_to_groupname(gid_t gid, char* buffer, size_t buffer_size) {
262 + static __thread char tmp[1024];
263 + struct group grp, *result = NULL;
264 +
265 + if (getgrgid_r(gid, &grp, tmp, sizeof(tmp), &result) != 0 || !result || !grp.gr_name || !(*grp.gr_name))
266 + snprintfz(buffer, buffer_size - 1, "%u", gid);
267 + else
268 + snprintfz(buffer, buffer_size - 1, "%u (%s)", gid, grp.gr_name);
269 +
270 + return buffer;
271 +}
272 +
273 +void netdata_systemd_journal_transform_syslog_facility(FACETS *facets __maybe_unused, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope __maybe_unused, void *data __maybe_unused) {
274 + const char *v = buffer_tostring(wb);
275 + if(*v && isdigit(*v)) {
276 + int facility = str2i(buffer_tostring(wb));
277 + const char *name = syslog_facility_to_name(facility);
278 + if (name) {
279 + buffer_flush(wb);
280 + buffer_strcat(wb, name);
281 + }
282 + }
283 +}
284 +
285 +void netdata_systemd_journal_transform_priority(FACETS *facets __maybe_unused, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope __maybe_unused, void *data __maybe_unused) {
286 + if(scope == FACETS_TRANSFORM_FACET_SORT)
287 + return;
288 +
289 + const char *v = buffer_tostring(wb);
290 + if(*v && isdigit(*v)) {
291 + int priority = str2i(buffer_tostring(wb));
292 + const char *name = syslog_priority_to_name(priority);
293 + if (name) {
294 + buffer_flush(wb);
295 + buffer_strcat(wb, name);
296 + }
297 + }
298 +}
299 +
300 +void netdata_systemd_journal_transform_errno(FACETS *facets __maybe_unused, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope __maybe_unused, void *data __maybe_unused) {
301 + if(scope == FACETS_TRANSFORM_FACET_SORT)
302 + return;
303 +
304 + const char *v = buffer_tostring(wb);
305 + if(*v && isdigit(*v)) {
306 + unsigned err_no = str2u(buffer_tostring(wb));
307 + if(err_no > 0 && err_no < sizeof(errno_map) / sizeof(*errno_map)) {
308 + const char *name = errno_map[err_no];
309 + if(name) {
310 + buffer_flush(wb);
311 + buffer_strcat(wb, name);
312 + }
313 + }
314 + }
315 +}
316 +
317 +// ----------------------------------------------------------------------------
318 +// UID and GID transformation
319 +
320 +#define UID_GID_HASHTABLE_SIZE 10000
321 +
322 +struct word_t2str_hashtable_entry {
323 + struct word_t2str_hashtable_entry *next;
324 + Word_t hash;
325 + size_t len;
326 + char str[];
327 +};
328 +
329 +struct word_t2str_hashtable {
330 + SPINLOCK spinlock;
331 + size_t size;
332 + struct word_t2str_hashtable_entry *hashtable[UID_GID_HASHTABLE_SIZE];
333 +};
334 +
335 +struct word_t2str_hashtable uid_hashtable = {
336 + .size = UID_GID_HASHTABLE_SIZE,
337 +};
338 +
339 +struct word_t2str_hashtable gid_hashtable = {
340 + .size = UID_GID_HASHTABLE_SIZE,
341 +};
342 +
343 +struct word_t2str_hashtable_entry **word_t2str_hashtable_slot(struct word_t2str_hashtable *ht, Word_t hash) {
344 + size_t slot = hash % ht->size;
345 + struct word_t2str_hashtable_entry **e = &ht->hashtable[slot];
346 +
347 + while(*e && (*e)->hash != hash)
348 + e = &((*e)->next);
349 +
350 + return e;
351 +}
352 +
353 +const char *uid_to_username_cached(uid_t uid, size_t *length) {
354 + spinlock_lock(&uid_hashtable.spinlock);
355 +
356 + struct word_t2str_hashtable_entry **e = word_t2str_hashtable_slot(&uid_hashtable, uid);
357 + if(!(*e)) {
358 + static __thread char buf[1024];
359 + const char *name = uid_to_username(uid, buf, sizeof(buf));
360 + size_t size = strlen(name) + 1;
361 +
362 + *e = callocz(1, sizeof(struct word_t2str_hashtable_entry) + size);
363 + (*e)->len = size - 1;
364 + (*e)->hash = uid;
365 + memcpy((*e)->str, name, size);
366 + }
367 +
368 + spinlock_unlock(&uid_hashtable.spinlock);
369 +
370 + *length = (*e)->len;
371 + return (*e)->str;
372 +}
373 +
374 +const char *gid_to_groupname_cached(gid_t gid, size_t *length) {
375 + spinlock_lock(&gid_hashtable.spinlock);
376 +
377 + struct word_t2str_hashtable_entry **e = word_t2str_hashtable_slot(&gid_hashtable, gid);
378 + if(!(*e)) {
379 + static __thread char buf[1024];
380 + const char *name = gid_to_groupname(gid, buf, sizeof(buf));
381 + size_t size = strlen(name) + 1;
382 +
383 + *e = callocz(1, sizeof(struct word_t2str_hashtable_entry) + size);
384 + (*e)->len = size - 1;
385 + (*e)->hash = gid;
386 + memcpy((*e)->str, name, size);
387 + }
388 +
389 + spinlock_unlock(&gid_hashtable.spinlock);
390 +
391 + *length = (*e)->len;
392 + return (*e)->str;
393 +}
394 +
395 +DICTIONARY *boot_ids_to_first_ut = NULL;
396 +
397 +void netdata_systemd_journal_transform_boot_id(FACETS *facets __maybe_unused, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope __maybe_unused, void *data __maybe_unused) {
398 + const char *boot_id = buffer_tostring(wb);
399 + if(*boot_id && isxdigit(*boot_id)) {
400 + usec_t ut = UINT64_MAX;
401 + usec_t *p_ut = dictionary_get(boot_ids_to_first_ut, boot_id);
402 + if(!p_ut) {
403 + struct journal_file *jf;
404 + dfe_start_read(journal_files_registry, jf) {
405 + const char *files[2] = {
406 + [0] = jf_dfe.name,
407 + [1] = NULL,
408 + };
409 +
410 + sd_journal *j = NULL;
411 + if(sd_journal_open_files(&j, files, ND_SD_JOURNAL_OPEN_FLAGS) < 0 || !j) {
412 + internal_error(true, "JOURNAL: cannot open file '%s' to get boot_id", jf_dfe.name);
413 + continue;
414 + }
415 +
416 + char m[100];
417 + size_t len = snprintfz(m, sizeof(m), "_BOOT_ID=%s", boot_id);
418 +
419 + if(sd_journal_add_match(j, m, len) < 0) {
420 + internal_error(true, "JOURNAL: cannot add match '%s' to file '%s'", m, jf_dfe.name);
421 + sd_journal_close(j);
422 + continue;
423 + }
424 +
425 + if(sd_journal_seek_head(j) < 0) {
426 + internal_error(true, "JOURNAL: cannot seek head to file '%s'", jf_dfe.name);
427 + sd_journal_close(j);
428 + continue;
429 + }
430 +
431 + if(sd_journal_next(j) < 0) {
432 + internal_error(true, "JOURNAL: cannot get next of file '%s'", jf_dfe.name);
433 + sd_journal_close(j);
434 + continue;
435 + }
436 +
437 + usec_t t_ut = 0;
438 + if(sd_journal_get_realtime_usec(j, &t_ut) < 0 || !t_ut) {
439 + internal_error(true, "JOURNAL: cannot get realtime_usec of file '%s'", jf_dfe.name);
440 + sd_journal_close(j);
441 + continue;
442 + }
443 +
444 + if(t_ut < ut)
445 + ut = t_ut;
446 +
447 + sd_journal_close(j);
448 + }
449 + dfe_done(jf);
450 +
451 + dictionary_set(boot_ids_to_first_ut, boot_id, &ut, sizeof(ut));
452 + }
453 + else
454 + ut = *p_ut;
455 +
456 + if(ut != UINT64_MAX) {
457 + time_t timestamp_sec = (time_t)(ut / USEC_PER_SEC);
458 + struct tm tm;
459 + char buffer[30];
460 +
461 + gmtime_r(&timestamp_sec, &tm);
462 + strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tm);
463 +
464 + switch(scope) {
465 + default:
466 + case FACETS_TRANSFORM_DATA:
467 + case FACETS_TRANSFORM_VALUE:
468 + buffer_sprintf(wb, " (%s UTC) ", buffer);
469 + break;
470 +
471 + case FACETS_TRANSFORM_FACET:
472 + case FACETS_TRANSFORM_FACET_SORT:
473 + case FACETS_TRANSFORM_HISTOGRAM:
474 + buffer_flush(wb);
475 + buffer_sprintf(wb, "%s UTC", buffer);
476 + break;
477 + }
478 + }
479 + }
480 +}
481 +
482 +void netdata_systemd_journal_transform_uid(FACETS *facets __maybe_unused, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope __maybe_unused, void *data __maybe_unused) {
483 + if(scope == FACETS_TRANSFORM_FACET_SORT)
484 + return;
485 +
486 + const char *v = buffer_tostring(wb);
487 + if(*v && isdigit(*v)) {
488 + uid_t uid = str2i(buffer_tostring(wb));
489 + size_t len;
490 + const char *name = uid_to_username_cached(uid, &len);
491 + buffer_contents_replace(wb, name, len);
492 + }
493 +}
494 +
495 +void netdata_systemd_journal_transform_gid(FACETS *facets __maybe_unused, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope __maybe_unused, void *data __maybe_unused) {
496 + if(scope == FACETS_TRANSFORM_FACET_SORT)
497 + return;
498 +
499 + const char *v = buffer_tostring(wb);
500 + if(*v && isdigit(*v)) {
501 + gid_t gid = str2i(buffer_tostring(wb));
502 + size_t len;
503 + const char *name = gid_to_groupname_cached(gid, &len);
504 + buffer_contents_replace(wb, name, len);
505 + }
506 +}
507 +
508 +void netdata_systemd_journal_transform_cap_effective(FACETS *facets __maybe_unused, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope __maybe_unused, void *data __maybe_unused) {
509 + if(scope == FACETS_TRANSFORM_FACET_SORT)
510 + return;
511 +
512 + const char *v = buffer_tostring(wb);
513 + if(*v && isdigit(*v)) {
514 + uint64_t cap = strtoul(buffer_tostring(wb), NULL, 16);
515 + if(cap) {
516 + buffer_fast_strcat(wb, " (", 2);
517 + for (size_t i = 0, added = 0; i < sizeof(linux_capabilities) / sizeof(linux_capabilities[0]); i++) {
518 + if (linux_capabilities[i] && (cap & (1ULL << i))) {
519 +
520 + if (added)
521 + buffer_fast_strcat(wb, " | ", 3);
522 +
523 + buffer_strcat(wb, linux_capabilities[i]);
524 + added++;
525 + }
526 + }
527 + buffer_fast_strcat(wb, ")", 1);
528 + }
529 + }
530 +}
531 +
532 +void netdata_systemd_journal_transform_timestamp_usec(FACETS *facets __maybe_unused, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope __maybe_unused, void *data __maybe_unused) {
533 + if(scope == FACETS_TRANSFORM_FACET_SORT)
534 + return;
535 +
536 + const char *v = buffer_tostring(wb);
537 + if(*v && isdigit(*v)) {
538 + uint64_t ut = str2ull(buffer_tostring(wb), NULL);
539 + if(ut) {
540 + time_t timestamp_sec = (time_t)(ut / USEC_PER_SEC);
541 + struct tm tm;
542 + char buffer[30];
543 +
544 + gmtime_r(&timestamp_sec, &tm);
545 + strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tm);
546 + buffer_sprintf(wb, " (%s.%06llu UTC)", buffer, ut % USEC_PER_SEC);
547 + }
548 + }
549 +}
550 +
551 +// ----------------------------------------------------------------------------
552 +
553 +void netdata_systemd_journal_dynamic_row_id(FACETS *facets __maybe_unused, BUFFER *json_array, FACET_ROW_KEY_VALUE *rkv, FACET_ROW *row, void *data __maybe_unused) {
554 + FACET_ROW_KEY_VALUE *pid_rkv = dictionary_get(row->dict, "_PID");
555 + const char *pid = pid_rkv ? buffer_tostring(pid_rkv->wb) : FACET_VALUE_UNSET;
556 +
557 + const char *identifier = NULL;
558 + FACET_ROW_KEY_VALUE *container_name_rkv = dictionary_get(row->dict, "CONTAINER_NAME");
559 + if(container_name_rkv && !container_name_rkv->empty)
560 + identifier = buffer_tostring(container_name_rkv->wb);
561 +
562 + if(!identifier) {
563 + FACET_ROW_KEY_VALUE *syslog_identifier_rkv = dictionary_get(row->dict, "SYSLOG_IDENTIFIER");
564 + if(syslog_identifier_rkv && !syslog_identifier_rkv->empty)
565 + identifier = buffer_tostring(syslog_identifier_rkv->wb);
566 +
567 + if(!identifier) {
568 + FACET_ROW_KEY_VALUE *comm_rkv = dictionary_get(row->dict, "_COMM");
569 + if(comm_rkv && !comm_rkv->empty)
570 + identifier = buffer_tostring(comm_rkv->wb);
571 + }
572 + }
573 +
574 + buffer_flush(rkv->wb);
575 +
576 + if(!identifier || !*identifier)
577 + buffer_strcat(rkv->wb, FACET_VALUE_UNSET);
578 + else if(!pid || !*pid)
579 + buffer_sprintf(rkv->wb, "%s", identifier);
580 + else
581 + buffer_sprintf(rkv->wb, "%s[%s]", identifier, pid);
582 +
583 + buffer_json_add_array_item_string(json_array, buffer_tostring(rkv->wb));
584 +}
585 +
586 +static void netdata_systemd_journal_rich_message(FACETS *facets __maybe_unused, BUFFER *json_array, FACET_ROW_KEY_VALUE *rkv, FACET_ROW *row __maybe_unused, void *data __maybe_unused) {
587 + buffer_json_add_array_item_object(json_array);
588 + buffer_json_member_add_string(json_array, "value", buffer_tostring(rkv->wb));
589 + buffer_json_object_close(json_array);
590 +}
collectors/systemd-journal.plugin/systemd-journal-files.c new
+477
@@ -0,0 +1,477 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "systemd-internals.h"
4 +
5 +#define SYSTEMD_JOURNAL_MAX_SOURCE_LEN 64
6 +#define VAR_LOG_JOURNAL_MAX_DEPTH 10
7 +#define MAX_JOURNAL_DIRECTORIES 100
8 +
9 +struct journal_directory {
10 + char *path;
11 + bool logged_failure;
12 +};
13 +
14 +static struct journal_directory journal_directories[MAX_JOURNAL_DIRECTORIES] = { 0 };
15 +DICTIONARY *journal_files_registry = NULL;
16 +DICTIONARY *used_hashes_registry = NULL;
17 +
18 +static usec_t systemd_journal_session = 0;
19 +
20 +void buffer_json_journal_versions(BUFFER *wb) {
21 + buffer_json_member_add_object(wb, "versions");
22 + {
23 + buffer_json_member_add_uint64(wb, "sources",
24 + systemd_journal_session + dictionary_version(journal_files_registry));
25 + }
26 + buffer_json_object_close(wb);
27 +}
28 +
29 +static void journal_file_update_msg_ut(const char *filename, struct journal_file *jf) {
30 + fstat_cache_enable_on_thread();
31 +
32 + const char *files[2] = {
33 + [0] = filename,
34 + [1] = NULL,
35 + };
36 +
37 + sd_journal *j = NULL;
38 + if(sd_journal_open_files(&j, files, ND_SD_JOURNAL_OPEN_FLAGS) < 0 || !j) {
39 + netdata_log_error("JOURNAL: cannot open file '%s' to update msg_ut", filename);
40 + fstat_cache_disable_on_thread();
41 +
42 + if(!jf->logged_failure) {
43 + netdata_log_error("cannot open journal file '%s', using file timestamps to understand time-frame.", filename);
44 + jf->logged_failure = true;
45 + }
46 +
47 + jf->msg_first_ut = 0;
48 + jf->msg_last_ut = jf->file_last_modified_ut;
49 + return;
50 + }
51 +
52 + usec_t first_ut = 0, last_ut = 0;
53 +
54 + if(sd_journal_seek_head(j) < 0 || sd_journal_next(j) < 0 || sd_journal_get_realtime_usec(j, &first_ut) < 0 || !first_ut) {
55 + internal_error(true, "cannot find the timestamp of the first message in '%s'", filename);
56 + first_ut = 0;
57 + }
58 +
59 + if(sd_journal_seek_tail(j) < 0 || sd_journal_previous(j) < 0 || sd_journal_get_realtime_usec(j, &last_ut) < 0 || !last_ut) {
60 + internal_error(true, "cannot find the timestamp of the last message in '%s'", filename);
61 + last_ut = jf->file_last_modified_ut;
62 + }
63 +
64 + sd_journal_close(j);
65 + fstat_cache_disable_on_thread();
66 +
67 + if(first_ut > last_ut) {
68 + internal_error(true, "timestamps are flipped in file '%s'", filename);
69 + usec_t t = first_ut;
70 + first_ut = last_ut;
71 + last_ut = t;
72 + }
73 +
74 + jf->msg_first_ut = first_ut;
75 + jf->msg_last_ut = last_ut;
76 +}
77 +
78 +static STRING *string_strdupz_source(const char *s, const char *e, size_t max_len, const char *prefix) {
79 + char buf[max_len];
80 + size_t len;
81 + char *dst = buf;
82 +
83 + if(prefix) {
84 + len = strlen(prefix);
85 + memcpy(buf, prefix, len);
86 + dst = &buf[len];
87 + max_len -= len;
88 + }
89 +
90 + len = e - s;
91 + if(len >= max_len)
92 + len = max_len - 1;
93 + memcpy(dst, s, len);
94 + dst[len] = '\0';
95 + buf[max_len - 1] = '\0';
96 +
97 + for(size_t i = 0; buf[i] ;i++)
98 + if(!isalnum(buf[i]) && buf[i] != '-' && buf[i] != '.' && buf[i] != ':')
99 + buf[i] = '_';
100 +
101 + return string_strdupz(buf);
102 +}
103 +
104 +static void files_registry_insert_cb(const DICTIONARY_ITEM *item, void *value, void *data __maybe_unused) {
105 + struct journal_file *jf = value;
106 + jf->filename = dictionary_acquired_item_name(item);
107 + jf->filename_len = strlen(jf->filename);
108 + jf->source_type = SDJF_ALL;
109 +
110 + // based on the filename
111 + // decide the source to show to the user
112 + const char *s = strrchr(jf->filename, '/');
113 + if(s) {
114 + if(strstr(jf->filename, "/remote/")) {
115 + jf->source_type |= SDJF_REMOTE_ALL;
116 +
117 + if(strncmp(s, "/remote-", 8) == 0) {
118 + s = &s[8]; // skip "/remote-"
119 +
120 + char *e = strchr(s, '@');
121 + if(!e)
122 + e = strstr(s, ".journal");
123 +
124 + if(e) {
125 + const char *d = s;
126 + for(; d < e && (isdigit(*d) || *d == '.' || *d == ':') ; d++) ;
127 + if(d == e) {
128 + // a valid IP address
129 + char ip[e - s + 1];
130 + memcpy(ip, s, e - s);
131 + ip[e - s] = '\0';
132 + char buf[SYSTEMD_JOURNAL_MAX_SOURCE_LEN];
133 + if(ip_to_hostname(ip, buf, sizeof(buf)))
134 + jf->source = string_strdupz_source(buf, &buf[strlen(buf)], SYSTEMD_JOURNAL_MAX_SOURCE_LEN, "remote-");
135 + else {
136 + internal_error(true, "Cannot find the hostname for IP '%s'", ip);
137 + jf->source = string_strdupz_source(s, e, SYSTEMD_JOURNAL_MAX_SOURCE_LEN, "remote-");
138 + }
139 + }
140 + else
141 + jf->source = string_strdupz_source(s, e, SYSTEMD_JOURNAL_MAX_SOURCE_LEN, "remote-");
142 + }
143 + }
144 + }
145 + else {
146 + jf->source_type |= SDJF_LOCAL_ALL;
147 +
148 + const char *t = s - 1;
149 + while(t >= jf->filename && *t != '.' && *t != '/')
150 + t--;
151 +
152 + if(t >= jf->filename && *t == '.') {
153 + jf->source_type |= SDJF_LOCAL_NAMESPACE;
154 + jf->source = string_strdupz_source(t + 1, s, SYSTEMD_JOURNAL_MAX_SOURCE_LEN, "namespace-");
155 + }
156 + else if(strncmp(s, "/system", 7) == 0)
157 + jf->source_type |= SDJF_LOCAL_SYSTEM;
158 +
159 + else if(strncmp(s, "/user", 5) == 0)
160 + jf->source_type |= SDJF_LOCAL_USER;
161 +
162 + else
163 + jf->source_type |= SDJF_LOCAL_OTHER;
164 + }
165 + }
166 + else
167 + jf->source_type |= SDJF_LOCAL_ALL | SDJF_LOCAL_OTHER;
168 +
169 + journal_file_update_msg_ut(jf->filename, jf);
170 +
171 + internal_error(true,
172 + "found journal file '%s', type %d, source '%s', "
173 + "file modified: %"PRIu64", "
174 + "msg {first: %"PRIu64", last: %"PRIu64"}",
175 + jf->filename, jf->source_type, jf->source ? string2str(jf->source) : "<unset>",
176 + jf->file_last_modified_ut,
177 + jf->msg_first_ut, jf->msg_last_ut);
178 +}
179 +
180 +static bool files_registry_conflict_cb(const DICTIONARY_ITEM *item, void *old_value, void *new_value, void *data __maybe_unused) {
181 + struct journal_file *jf = old_value;
182 + struct journal_file *njf = new_value;
183 +
184 + if(njf->last_scan_ut > jf->last_scan_ut)
185 + jf->last_scan_ut = njf->last_scan_ut;
186 +
187 + if(njf->file_last_modified_ut > jf->file_last_modified_ut) {
188 + jf->file_last_modified_ut = njf->file_last_modified_ut;
189 + jf->size = njf->size;
190 +
191 + const char *filename = dictionary_acquired_item_name(item);
192 + journal_file_update_msg_ut(filename, jf);
193 +
194 +// internal_error(true,
195 +// "updated journal file '%s', type %d, "
196 +// "file modified: %"PRIu64", "
197 +// "msg {first: %"PRIu64", last: %"PRIu64"}",
198 +// filename, jf->source_type,
199 +// jf->file_last_modified_ut,
200 +// jf->msg_first_ut, jf->msg_last_ut);
201 + }
202 +
203 + return false;
204 +}
205 +
206 +struct journal_file_source {
207 + usec_t first_ut;
208 + usec_t last_ut;
209 + size_t count;
210 + uint64_t size;
211 +};
212 +
213 +static void human_readable_size_ib(uint64_t size, char *dst, size_t dst_len) {
214 + if(size > 1024ULL * 1024 * 1024 * 1024)
215 + snprintfz(dst, dst_len, "%0.2f TiB", (double)size / 1024.0 / 1024.0 / 1024.0 / 1024.0);
216 + else if(size > 1024ULL * 1024 * 1024)
217 + snprintfz(dst, dst_len, "%0.2f GiB", (double)size / 1024.0 / 1024.0 / 1024.0);
218 + else if(size > 1024ULL * 1024)
219 + snprintfz(dst, dst_len, "%0.2f MiB", (double)size / 1024.0 / 1024.0);
220 + else if(size > 1024ULL)
221 + snprintfz(dst, dst_len, "%0.2f KiB", (double)size / 1024.0);
222 + else
223 + snprintfz(dst, dst_len, "%"PRIu64" B", size);
224 +}
225 +
226 +#define print_duration(dst, dst_len, pos, remaining, duration, one, many, printed) do { \
227 + if((remaining) > (duration)) { \
228 + uint64_t _count = (remaining) / (duration); \
229 + uint64_t _rem = (remaining) - (_count * (duration)); \
230 + (pos) += snprintfz(&(dst)[pos], (dst_len) - (pos), "%s%s%"PRIu64" %s", (printed) ? ", " : "", _rem ? "" : "and ", _count, _count > 1 ? (many) : (one)); \
231 + (remaining) = _rem; \
232 + (printed) = true; \
233 + } \
234 +} while(0)
235 +
236 +static void human_readable_duration_s(time_t duration_s, char *dst, size_t dst_len) {
237 + if(duration_s < 0)
238 + duration_s = -duration_s;
239 +
240 + size_t pos = 0;
241 + dst[0] = 0 ;
242 +
243 + bool printed = false;
244 + print_duration(dst, dst_len, pos, duration_s, 86400 * 365, "year", "years", printed);
245 + print_duration(dst, dst_len, pos, duration_s, 86400 * 30, "month", "months", printed);
246 + print_duration(dst, dst_len, pos, duration_s, 86400 * 1, "day", "days", printed);
247 + print_duration(dst, dst_len, pos, duration_s, 3600 * 1, "hour", "hours", printed);
248 + print_duration(dst, dst_len, pos, duration_s, 60 * 1, "min", "mins", printed);
249 + print_duration(dst, dst_len, pos, duration_s, 1, "sec", "secs", printed);
250 +}
251 +
252 +static int journal_file_to_json_array_cb(const DICTIONARY_ITEM *item, void *entry, void *data) {
253 + struct journal_file_source *jfs = entry;
254 + BUFFER *wb = data;
255 +
256 + const char *name = dictionary_acquired_item_name(item);
257 +
258 + buffer_json_add_array_item_object(wb);
259 + {
260 + char size_for_humans[100];
261 + human_readable_size_ib(jfs->size, size_for_humans, sizeof(size_for_humans));
262 +
263 + char duration_for_humans[1024];
264 + human_readable_duration_s((time_t)((jfs->last_ut - jfs->first_ut) / USEC_PER_SEC),
265 + duration_for_humans, sizeof(duration_for_humans));
266 +
267 + char info[1024];
268 + snprintfz(info, sizeof(info), "%zu files, with a total size of %s, covering %s",
269 + jfs->count, size_for_humans, duration_for_humans);
270 +
271 + buffer_json_member_add_string(wb, "id", name);
272 + buffer_json_member_add_string(wb, "name", name);
273 + buffer_json_member_add_string(wb, "pill", size_for_humans);
274 + buffer_json_member_add_string(wb, "info", info);
275 + }
276 + buffer_json_object_close(wb); // options object
277 +
278 + return 1;
279 +}
280 +
281 +static bool journal_file_merge_sizes(const DICTIONARY_ITEM *item __maybe_unused, void *old_value, void *new_value , void *data __maybe_unused) {
282 + struct journal_file_source *jfs = old_value, *njfs = new_value;
283 + jfs->count += njfs->count;
284 + jfs->size += njfs->size;
285 +
286 + if(njfs->first_ut && njfs->first_ut < jfs->first_ut)
287 + jfs->first_ut = njfs->first_ut;
288 +
289 + if(njfs->last_ut && njfs->last_ut > jfs->last_ut)
290 + jfs->last_ut = njfs->last_ut;
291 +
292 + return false;
293 +}
294 +
295 +void available_journal_file_sources_to_json_array(BUFFER *wb) {
296 + DICTIONARY *dict = dictionary_create(DICT_OPTION_SINGLE_THREADED|DICT_OPTION_NAME_LINK_DONT_CLONE|DICT_OPTION_DONT_OVERWRITE_VALUE);
297 + dictionary_register_conflict_callback(dict, journal_file_merge_sizes, NULL);
298 +
299 + struct journal_file_source t = { 0 };
300 +
301 + struct journal_file *jf;
302 + dfe_start_read(journal_files_registry, jf) {
303 + t.first_ut = jf->msg_first_ut;
304 + t.last_ut = jf->msg_last_ut;
305 + t.count = 1;
306 + t.size = jf->size;
307 +
308 + dictionary_set(dict, SDJF_SOURCE_ALL_NAME, &t, sizeof(t));
309 +
310 + if(jf->source_type & SDJF_LOCAL_ALL)
311 + dictionary_set(dict, SDJF_SOURCE_LOCAL_NAME, &t, sizeof(t));
312 + if(jf->source_type & SDJF_LOCAL_SYSTEM)
313 + dictionary_set(dict, SDJF_SOURCE_LOCAL_SYSTEM_NAME, &t, sizeof(t));
314 + if(jf->source_type & SDJF_LOCAL_USER)
315 + dictionary_set(dict, SDJF_SOURCE_LOCAL_USERS_NAME, &t, sizeof(t));
316 + if(jf->source_type & SDJF_LOCAL_OTHER)
317 + dictionary_set(dict, SDJF_SOURCE_LOCAL_OTHER_NAME, &t, sizeof(t));
318 + if(jf->source_type & SDJF_LOCAL_NAMESPACE)
319 + dictionary_set(dict, SDJF_SOURCE_NAMESPACES_NAME, &t, sizeof(t));
320 + if(jf->source_type & SDJF_REMOTE_ALL)
321 + dictionary_set(dict, SDJF_SOURCE_REMOTES_NAME, &t, sizeof(t));
322 + if(jf->source)
323 + dictionary_set(dict, string2str(jf->source), &t, sizeof(t));
324 + }
325 + dfe_done(jf);
326 +
327 + dictionary_sorted_walkthrough_read(dict, journal_file_to_json_array_cb, wb);
328 +
329 + dictionary_destroy(dict);
330 +}
331 +
332 +static void files_registry_delete_cb(const DICTIONARY_ITEM *item, void *value, void *data __maybe_unused) {
333 + struct journal_file *jf = value; (void)jf;
334 + const char *filename = dictionary_acquired_item_name(item); (void)filename;
335 +
336 + string_freez(jf->source);
337 + internal_error(true, "removed journal file '%s'", filename);
338 +}
339 +
340 +void journal_directory_scan(const char *dirname, int depth, usec_t last_scan_ut) {
341 + static const char *ext = ".journal";
342 + static const size_t ext_len = sizeof(".journal") - 1;
343 +
344 + if (depth > VAR_LOG_JOURNAL_MAX_DEPTH)
345 + return;
346 +
347 + DIR *dir;
348 + struct dirent *entry;
349 + struct stat info;
350 + char absolute_path[FILENAME_MAX];
351 +
352 + // Open the directory.
353 + if ((dir = opendir(dirname)) == NULL) {
354 + if(errno != ENOENT && errno != ENOTDIR)
355 + netdata_log_error("Cannot opendir() '%s'", dirname);
356 + return;
357 + }
358 +
359 + // Read each entry in the directory.
360 + while ((entry = readdir(dir)) != NULL) {
361 + snprintfz(absolute_path, sizeof(absolute_path), "%s/%s", dirname, entry->d_name);
362 + if (stat(absolute_path, &info) != 0) {
363 + netdata_log_error("Failed to stat() '%s", absolute_path);
364 + continue;
365 + }
366 +
367 + if (S_ISDIR(info.st_mode)) {
368 + // If entry is a directory, call traverse recursively.
369 + if (strcmp(entry->d_name, ".") != 0 && strcmp(entry->d_name, "..") != 0)
370 + journal_directory_scan(absolute_path, depth + 1, last_scan_ut);
371 +
372 + }
373 + else if (S_ISREG(info.st_mode)) {
374 + // If entry is a regular file, check if it ends with .journal.
375 + char *filename = entry->d_name;
376 + size_t len = strlen(filename);
377 +
378 + if (len > ext_len && strcmp(filename + len - ext_len, ext) == 0) {
379 + struct journal_file t = {
380 + .file_last_modified_ut = info.st_mtim.tv_sec * USEC_PER_SEC + info.st_mtim.tv_nsec / NSEC_PER_USEC,
381 + .last_scan_ut = last_scan_ut,
382 + .size = info.st_size,
383 + .max_journal_vs_realtime_delta_ut = JOURNAL_VS_REALTIME_DELTA_DEFAULT_UT,
384 + };
385 + dictionary_set(journal_files_registry, absolute_path, &t, sizeof(t));
386 + }
387 + }
388 + }
389 +
390 + closedir(dir);
391 +}
392 +
393 +void journal_files_registry_update(void) {
394 + usec_t scan_ut = now_monotonic_usec();
395 +
396 + for(unsigned i = 0; i < MAX_JOURNAL_DIRECTORIES ;i++) {
397 + if(!journal_directories[i].path)
398 + break;
399 +
400 + journal_directory_scan(journal_directories[i].path, 0, scan_ut);
401 + }
402 +
403 + struct journal_file *jf;
404 + dfe_start_write(journal_files_registry, jf) {
405 + if(jf->last_scan_ut < scan_ut)
406 + dictionary_del(journal_files_registry, jf_dfe.name);
407 + }
408 + dfe_done(jf);
409 +}
410 +
411 +// ----------------------------------------------------------------------------
412 +
413 +int journal_file_dict_items_backward_compar(const void *a, const void *b) {
414 + const DICTIONARY_ITEM **ad = (const DICTIONARY_ITEM **)a, **bd = (const DICTIONARY_ITEM **)b;
415 + struct journal_file *jfa = dictionary_acquired_item_value(*ad);
416 + struct journal_file *jfb = dictionary_acquired_item_value(*bd);
417 +
418 + if(jfa->msg_last_ut < jfb->msg_last_ut)
419 + return 1;
420 +
421 + if(jfa->msg_last_ut > jfb->msg_last_ut)
422 + return -1;
423 +
424 + if(jfa->msg_first_ut < jfb->msg_first_ut)
425 + return 1;
426 +
427 + if(jfa->msg_first_ut > jfb->msg_first_ut)
428 + return -1;
429 +
430 + return 0;
431 +}
432 +
433 +int journal_file_dict_items_forward_compar(const void *a, const void *b) {
434 + return -journal_file_dict_items_backward_compar(a, b);
435 +}
436 +
437 +void journal_init_files_and_directories(void) {
438 + unsigned d = 0;
439 +
440 + // ------------------------------------------------------------------------
441 + // setup the journal directories
442 +
443 + journal_directories[d++].path = strdupz("/var/log/journal");
444 + journal_directories[d++].path = strdupz("/run/log/journal");
445 +
446 + if(*netdata_configured_host_prefix) {
447 + char path[PATH_MAX];
448 + snprintfz(path, sizeof(path), "%s/var/log/journal", netdata_configured_host_prefix);
449 + journal_directories[d++].path = strdupz(path);
450 + snprintfz(path, sizeof(path), "%s/run/log/journal", netdata_configured_host_prefix);
451 + journal_directories[d++].path = strdupz(path);
452 + }
453 +
454 + // terminate the list
455 + journal_directories[d].path = NULL;
456 +
457 + // ------------------------------------------------------------------------
458 + // initialize the used hashes files registry
459 +
460 + used_hashes_registry = dictionary_create(DICT_OPTION_DONT_OVERWRITE_VALUE);
461 +
462 + systemd_journal_session = (now_realtime_usec() / USEC_PER_SEC) * USEC_PER_SEC;
463 +
464 + journal_files_registry = dictionary_create_advanced(
465 + DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
466 + NULL, sizeof(struct journal_file));
467 +
468 + dictionary_register_insert_callback(journal_files_registry, files_registry_insert_cb, NULL);
469 + dictionary_register_delete_callback(journal_files_registry, files_registry_delete_cb, NULL);
470 + dictionary_register_conflict_callback(journal_files_registry, files_registry_conflict_cb, NULL);
471 +
472 + boot_ids_to_first_ut = dictionary_create_advanced(
473 + DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
474 + NULL, sizeof(usec_t));
475 +
476 + journal_files_registry_update();
477 +}
collectors/systemd-journal.plugin/systemd-journal-fstat.c new
+74
@@ -0,0 +1,74 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "systemd-internals.h"
4 +
5 +
6 +// ----------------------------------------------------------------------------
7 +// fstat64 overloading to speed up libsystemd
8 +// https://github.com/systemd/systemd/pull/29261
9 +
10 +#include <dlfcn.h>
11 +#include <sys/stat.h>
12 +
13 +#define FSTAT_CACHE_MAX 1024
14 +struct fdstat64_cache_entry {
15 + bool enabled;
16 + bool updated;
17 + int err_no;
18 + struct stat64 stat;
19 + int ret;
20 + size_t cached_count;
21 + size_t session;
22 +};
23 +
24 +struct fdstat64_cache_entry fstat64_cache[FSTAT_CACHE_MAX] = {0 };
25 +__thread size_t fstat_thread_calls = 0;
26 +__thread size_t fstat_thread_cached_responses = 0;
27 +static __thread bool enable_thread_fstat = false;
28 +static __thread size_t fstat_caching_thread_session = 0;
29 +static size_t fstat_caching_global_session = 0;
30 +
31 +void fstat_cache_enable_on_thread(void) {
32 + fstat_caching_thread_session = __atomic_add_fetch(&fstat_caching_global_session, 1, __ATOMIC_ACQUIRE);
33 + enable_thread_fstat = true;
34 +}
35 +
36 +void fstat_cache_disable_on_thread(void) {
37 + fstat_caching_thread_session = __atomic_add_fetch(&fstat_caching_global_session, 1, __ATOMIC_RELEASE);
38 + enable_thread_fstat = false;
39 +}
40 +
41 +int fstat64(int fd, struct stat64 *buf) {
42 + static int (*real_fstat)(int, struct stat64 *) = NULL;
43 + if (!real_fstat)
44 + real_fstat = dlsym(RTLD_NEXT, "fstat64");
45 +
46 + fstat_thread_calls++;
47 +
48 + if(fd >= 0 && fd < FSTAT_CACHE_MAX) {
49 + if(enable_thread_fstat && fstat64_cache[fd].session != fstat_caching_thread_session) {
50 + fstat64_cache[fd].session = fstat_caching_thread_session;
51 + fstat64_cache[fd].enabled = true;
52 + fstat64_cache[fd].updated = false;
53 + }
54 +
55 + if(fstat64_cache[fd].enabled && fstat64_cache[fd].updated && fstat64_cache[fd].session == fstat_caching_thread_session) {
56 + fstat_thread_cached_responses++;
57 + errno = fstat64_cache[fd].err_no;
58 + *buf = fstat64_cache[fd].stat;
59 + fstat64_cache[fd].cached_count++;
60 + return fstat64_cache[fd].ret;
61 + }
62 + }
63 +
64 + int ret = real_fstat(fd, buf);
65 +
66 + if(fd >= 0 && fd < FSTAT_CACHE_MAX && fstat64_cache[fd].enabled && fstat64_cache[fd].session == fstat_caching_thread_session) {
67 + fstat64_cache[fd].ret = ret;
68 + fstat64_cache[fd].updated = true;
69 + fstat64_cache[fd].err_no = errno;
70 + fstat64_cache[fd].stat = *buf;
71 + }
72 +
73 + return ret;
74 +}
collectors/systemd-journal.plugin/systemd-journal.c
+11 -1286
@@ -5,13 +5,7 @@
5 * GPL v3+
6 */
7
8 -#include "collectors/all.h"
9 -#include "libnetdata/libnetdata.h"
10 -#include "libnetdata/required_dummies.h"
11 -
12 -#include <linux/capability.h>
13 -#include <systemd/sd-journal.h>
14 -#include <syslog.h>
8 +#include "systemd-internals.h"
9
10 /*
11 * TODO
@@ -20,94 +14,14 @@
14 *
15 */
16
23 -
24 -// ----------------------------------------------------------------------------
25 -// fstat64 overloading to speed up libsystemd
26 -// https://github.com/systemd/systemd/pull/29261
27 -
28 -#define ND_SD_JOURNAL_OPEN_FLAGS (0)
29 -
30 -#include <dlfcn.h>
31 -#include <sys/stat.h>
32 -
33 -#define FSTAT_CACHE_MAX 1024
34 -struct fdstat64_cache_entry {
35 - bool enabled;
36 - bool updated;
37 - int err_no;
38 - struct stat64 stat;
39 - int ret;
40 - size_t cached_count;
41 - size_t session;
42 -};
43 -
44 -struct fdstat64_cache_entry fstat64_cache[FSTAT_CACHE_MAX] = {0 };
45 -static __thread size_t fstat_thread_calls = 0;
46 -static __thread size_t fstat_thread_cached_responses = 0;
47 -static __thread bool enable_thread_fstat = false;
48 -static __thread size_t fstat_caching_thread_session = 0;
49 -static size_t fstat_caching_global_session = 0;
50 -
51 -static void fstat_cache_enable_on_thread(void) {
52 - fstat_caching_thread_session = __atomic_add_fetch(&fstat_caching_global_session, 1, __ATOMIC_ACQUIRE);
53 - enable_thread_fstat = true;
54 -}
55 -
56 -static void fstat_cache_disable_on_thread(void) {
57 - fstat_caching_thread_session = __atomic_add_fetch(&fstat_caching_global_session, 1, __ATOMIC_RELEASE);
58 - enable_thread_fstat = false;
59 -}
60 -
61 -int fstat64(int fd, struct stat64 *buf) {
62 - static int (*real_fstat)(int, struct stat64 *) = NULL;
63 - if (!real_fstat)
64 - real_fstat = dlsym(RTLD_NEXT, "fstat64");
65 -
66 - fstat_thread_calls++;
67 -
68 - if(fd >= 0 && fd < FSTAT_CACHE_MAX) {
69 - if(enable_thread_fstat && fstat64_cache[fd].session != fstat_caching_thread_session) {
70 - fstat64_cache[fd].session = fstat_caching_thread_session;
71 - fstat64_cache[fd].enabled = true;
72 - fstat64_cache[fd].updated = false;
73 - }
74 -
75 - if(fstat64_cache[fd].enabled && fstat64_cache[fd].updated && fstat64_cache[fd].session == fstat_caching_thread_session) {
76 - fstat_thread_cached_responses++;
77 - errno = fstat64_cache[fd].err_no;
78 - *buf = fstat64_cache[fd].stat;
79 - fstat64_cache[fd].cached_count++;
80 - return fstat64_cache[fd].ret;
81 - }
82 - }
83 -
84 - int ret = real_fstat(fd, buf);
85 -
86 - if(fd >= 0 && fd < FSTAT_CACHE_MAX && fstat64_cache[fd].enabled && fstat64_cache[fd].session == fstat_caching_thread_session) {
87 - fstat64_cache[fd].ret = ret;
88 - fstat64_cache[fd].updated = true;
89 - fstat64_cache[fd].err_no = errno;
90 - fstat64_cache[fd].stat = *buf;
91 - }
92 -
93 - return ret;
94 -}
95 -
96 -// ----------------------------------------------------------------------------
97 -
17 #define FACET_MAX_VALUE_LENGTH 8192
99 -#define SYSTEMD_JOURNAL_MAX_SOURCE_LEN 64
18
19 #define SYSTEMD_JOURNAL_FUNCTION_DESCRIPTION "View, search and analyze systemd journal entries."
20 #define SYSTEMD_JOURNAL_FUNCTION_NAME "systemd-journal"
21 #define SYSTEMD_JOURNAL_DEFAULT_TIMEOUT 60
104 -#define SYSTEMD_JOURNAL_MAX_PARAMS 100
22 +#define SYSTEMD_JOURNAL_MAX_PARAMS 1000
23 #define SYSTEMD_JOURNAL_DEFAULT_QUERY_DURATION (1 * 3600)
24 #define SYSTEMD_JOURNAL_DEFAULT_ITEMS_PER_QUERY 200
107 -#define SYSTEMD_JOURNAL_WORKER_THREADS 5
108 -
109 -#define JOURNAL_VS_REALTIME_DELTA_DEFAULT_UT (5 * USEC_PER_SEC) // assume always 5 seconds latency
110 -#define JOURNAL_VS_REALTIME_DELTA_MAX_UT (2 * 60 * USEC_PER_SEC) // up to 2 minutes latency
25
26 #define JOURNAL_PARAMETER_HELP "help"
27 #define JOURNAL_PARAMETER_AFTER "after"
@@ -248,32 +162,8 @@ int fstat64(int fd, struct stat64 *buf) {
162 \
163 ""
164
251 -static netdata_mutex_t stdout_mutex = NETDATA_MUTEX_INITIALIZER;
252 -static bool plugin_should_exit = false;
253 -
165 // ----------------------------------------------------------------------------
166
256 -typedef enum {
257 - ND_SD_JOURNAL_NO_FILE_MATCHED,
258 - ND_SD_JOURNAL_FAILED_TO_OPEN,
259 - ND_SD_JOURNAL_FAILED_TO_SEEK,
260 - ND_SD_JOURNAL_TIMED_OUT,
261 - ND_SD_JOURNAL_OK,
262 - ND_SD_JOURNAL_NOT_MODIFIED,
263 - ND_SD_JOURNAL_CANCELLED,
264 -} ND_SD_JOURNAL_STATUS;
265 -
266 -typedef enum {
267 - SDJF_NONE = 0,
268 - SDJF_ALL = (1 << 0),
269 - SDJF_LOCAL_ALL = (1 << 1),
270 - SDJF_REMOTE_ALL = (1 << 2),
271 - SDJF_LOCAL_SYSTEM = (1 << 3),
272 - SDJF_LOCAL_USER = (1 << 4),
273 - SDJF_LOCAL_NAMESPACE = (1 << 5),
274 - SDJF_LOCAL_OTHER = (1 << 6),
275 -} SD_JOURNAL_FILE_SOURCE_TYPE;
276 -
167 typedef struct function_query_status {
168 bool *cancelled; // a pointer to the cancelling boolean
169 usec_t stop_monotonic_ut;
@@ -304,7 +194,7 @@ typedef struct function_query_status {
194 const char *histogram;
195
196 // per file progress info
307 - size_t cached_count;
197 + // size_t cached_count;
198
199 // progress statistics
200 usec_t matches_setup_ut;
@@ -315,20 +205,6 @@ typedef struct function_query_status {
205 size_t file_working;
206 } FUNCTION_QUERY_STATUS;
207
318 -struct journal_file {
319 - const char *filename;
320 - size_t filename_len;
321 - STRING *source;
322 - SD_JOURNAL_FILE_SOURCE_TYPE source_type;
323 - usec_t file_last_modified_ut;
324 - usec_t msg_first_ut;
325 - usec_t msg_last_ut;
326 - usec_t last_scan_ut;
327 - size_t size;
328 - bool logged_failure;
329 - usec_t max_journal_vs_realtime_delta_ut;
330 -};
331 -
208 static void log_fqs(FUNCTION_QUERY_STATUS *fqs, const char *msg) {
209 netdata_log_error("ERROR: %s, on query "
210 "timeframe [%"PRIu64" - %"PRIu64"], "
@@ -757,428 +633,10 @@ static ND_SD_JOURNAL_STATUS netdata_systemd_journal_query_one_file(
633 return status;
634 }
635
760 -// ----------------------------------------------------------------------------
761 -// journal files registry
762 -
763 -#define VAR_LOG_JOURNAL_MAX_DEPTH 10
764 -#define MAX_JOURNAL_DIRECTORIES 100
765 -
766 -struct journal_directory {
767 - char *path;
768 - bool logged_failure;
769 -};
770 -
771 -static struct journal_directory journal_directories[MAX_JOURNAL_DIRECTORIES] = { 0 };
772 -static DICTIONARY *journal_files_registry = NULL;
773 -static DICTIONARY *used_hashes_registry = NULL;
774 -
775 -static usec_t systemd_journal_session = 0;
776 -
777 -static void buffer_json_journal_versions(BUFFER *wb) {
778 - buffer_json_member_add_object(wb, "versions");
779 - {
780 - buffer_json_member_add_uint64(wb, "sources",
781 - systemd_journal_session + dictionary_version(journal_files_registry));
782 - }
783 - buffer_json_object_close(wb);
784 -}
785 -
786 -static void journal_file_update_msg_ut(const char *filename, struct journal_file *jf) {
787 - fstat_cache_enable_on_thread();
788 -
789 - const char *files[2] = {
790 - [0] = filename,
791 - [1] = NULL,
792 - };
793 -
794 - sd_journal *j = NULL;
795 - if(sd_journal_open_files(&j, files, ND_SD_JOURNAL_OPEN_FLAGS) < 0 || !j) {
796 - netdata_log_error("JOURNAL: cannot open file '%s' to update msg_ut", filename);
797 - fstat_cache_disable_on_thread();
798 -
799 - if(!jf->logged_failure) {
800 - netdata_log_error("cannot open journal file '%s', using file timestamps to understand time-frame.", filename);
801 - jf->logged_failure = true;
802 - }
803 -
804 - jf->msg_first_ut = 0;
805 - jf->msg_last_ut = jf->file_last_modified_ut;
806 - return;
807 - }
808 -
809 - usec_t first_ut = 0, last_ut = 0;
810 -
811 - if(sd_journal_seek_head(j) < 0 || sd_journal_next(j) < 0 || sd_journal_get_realtime_usec(j, &first_ut) < 0 || !first_ut) {
812 - internal_error(true, "cannot find the timestamp of the first message in '%s'", filename);
813 - first_ut = 0;
814 - }
815 -
816 - if(sd_journal_seek_tail(j) < 0 || sd_journal_previous(j) < 0 || sd_journal_get_realtime_usec(j, &last_ut) < 0 || !last_ut) {
817 - internal_error(true, "cannot find the timestamp of the last message in '%s'", filename);
818 - last_ut = jf->file_last_modified_ut;
819 - }
820 -
821 - sd_journal_close(j);
822 - fstat_cache_disable_on_thread();
823 -
824 - if(first_ut > last_ut) {
825 - internal_error(true, "timestamps are flipped in file '%s'", filename);
826 - usec_t t = first_ut;
827 - first_ut = last_ut;
828 - last_ut = t;
829 - }
830 -
831 - jf->msg_first_ut = first_ut;
832 - jf->msg_last_ut = last_ut;
833 -}
834 -
835 -static STRING *string_strdupz_source(const char *s, const char *e, size_t max_len, const char *prefix) {
836 - char buf[max_len];
837 - size_t len;
838 - char *dst = buf;
839 -
840 - if(prefix) {
841 - len = strlen(prefix);
842 - memcpy(buf, prefix, len);
843 - dst = &buf[len];
844 - max_len -= len;
845 - }
846 -
847 - len = e - s;
848 - if(len >= max_len)
849 - len = max_len - 1;
850 - memcpy(dst, s, len);
851 - dst[len] = '\0';
852 - buf[max_len - 1] = '\0';
853 -
854 - for(size_t i = 0; buf[i] ;i++)
855 - if(!isalnum(buf[i]) && buf[i] != '-' && buf[i] != '.' && buf[i] != ':')
856 - buf[i] = '_';
857 -
858 - return string_strdupz(buf);
859 -}
860 -
861 -static void files_registry_insert_cb(const DICTIONARY_ITEM *item, void *value, void *data __maybe_unused) {
862 - struct journal_file *jf = value;
863 - jf->filename = dictionary_acquired_item_name(item);
864 - jf->filename_len = strlen(jf->filename);
865 - jf->source_type = SDJF_ALL;
866 -
867 - // based on the filename
868 - // decide the source to show to the user
869 - const char *s = strrchr(jf->filename, '/');
870 - if(s) {
871 - if(strstr(jf->filename, "/remote/")) {
872 - jf->source_type |= SDJF_REMOTE_ALL;
873 -
874 - if(strncmp(s, "/remote-", 8) == 0) {
875 - s = &s[8]; // skip "/remote-"
876 -
877 - char *e = strchr(s, '@');
878 - if(!e)
879 - e = strstr(s, ".journal");
880 -
881 - if(e) {
882 - const char *d = s;
883 - for(; d < e && (isdigit(*d) || *d == '.' || *d == ':') ; d++) ;
884 - if(d == e) {
885 - // a valid IP address
886 - char ip[e - s + 1];
887 - memcpy(ip, s, e - s);
888 - ip[e - s] = '\0';
889 - char buf[SYSTEMD_JOURNAL_MAX_SOURCE_LEN];
890 - if(ip_to_hostname(ip, buf, sizeof(buf)))
891 - jf->source = string_strdupz_source(buf, &buf[strlen(buf)], SYSTEMD_JOURNAL_MAX_SOURCE_LEN, "remote-");
892 - else {
893 - internal_error(true, "Cannot find the hostname for IP '%s'", ip);
894 - jf->source = string_strdupz_source(s, e, SYSTEMD_JOURNAL_MAX_SOURCE_LEN, "remote-");
895 - }
896 - }
897 - else
898 - jf->source = string_strdupz_source(s, e, SYSTEMD_JOURNAL_MAX_SOURCE_LEN, "remote-");
899 - }
900 - }
901 - }
902 - else {
903 - jf->source_type |= SDJF_LOCAL_ALL;
904 -
905 - const char *t = s - 1;
906 - while(t >= jf->filename && *t != '.' && *t != '/')
907 - t--;
908 -
909 - if(t >= jf->filename && *t == '.') {
910 - jf->source_type |= SDJF_LOCAL_NAMESPACE;
911 - jf->source = string_strdupz_source(t + 1, s, SYSTEMD_JOURNAL_MAX_SOURCE_LEN, "namespace-");
912 - }
913 - else if(strncmp(s, "/system", 7) == 0)
914 - jf->source_type |= SDJF_LOCAL_SYSTEM;
915 -
916 - else if(strncmp(s, "/user", 5) == 0)
917 - jf->source_type |= SDJF_LOCAL_USER;
918 -
919 - else
920 - jf->source_type |= SDJF_LOCAL_OTHER;
921 - }
922 - }
923 - else
924 - jf->source_type |= SDJF_LOCAL_ALL | SDJF_LOCAL_OTHER;
925 -
926 - journal_file_update_msg_ut(jf->filename, jf);
927 -
928 - internal_error(true,
929 - "found journal file '%s', type %d, source '%s', "
930 - "file modified: %"PRIu64", "
931 - "msg {first: %"PRIu64", last: %"PRIu64"}",
932 - jf->filename, jf->source_type, jf->source ? string2str(jf->source) : "<unset>",
933 - jf->file_last_modified_ut,
934 - jf->msg_first_ut, jf->msg_last_ut);
935 -}
936 -
937 -static bool files_registry_conflict_cb(const DICTIONARY_ITEM *item, void *old_value, void *new_value, void *data __maybe_unused) {
938 - struct journal_file *jf = old_value;
939 - struct journal_file *njf = new_value;
940 -
941 - if(njf->last_scan_ut > jf->last_scan_ut)
942 - jf->last_scan_ut = njf->last_scan_ut;
943 -
944 - if(njf->file_last_modified_ut > jf->file_last_modified_ut) {
945 - jf->file_last_modified_ut = njf->file_last_modified_ut;
946 - jf->size = njf->size;
947 -
948 - const char *filename = dictionary_acquired_item_name(item);
949 - journal_file_update_msg_ut(filename, jf);
950 -
951 -// internal_error(true,
952 -// "updated journal file '%s', type %d, "
953 -// "file modified: %"PRIu64", "
954 -// "msg {first: %"PRIu64", last: %"PRIu64"}",
955 -// filename, jf->source_type,
956 -// jf->file_last_modified_ut,
957 -// jf->msg_first_ut, jf->msg_last_ut);
958 - }
959 -
960 - return false;
961 -}
962 -
963 -#define SDJF_SOURCE_ALL_NAME "all"
964 -#define SDJF_SOURCE_LOCAL_NAME "all-local-logs"
965 -#define SDJF_SOURCE_LOCAL_SYSTEM_NAME "all-local-system-logs"
966 -#define SDJF_SOURCE_LOCAL_USERS_NAME "all-local-user-logs"
967 -#define SDJF_SOURCE_LOCAL_OTHER_NAME "all-uncategorized"
968 -#define SDJF_SOURCE_NAMESPACES_NAME "all-local-namespaces"
969 -#define SDJF_SOURCE_REMOTES_NAME "all-remote-systems"
970 -
971 -struct journal_file_source {
972 - usec_t first_ut;
973 - usec_t last_ut;
974 - size_t count;
975 - uint64_t size;
976 -};
977 -
978 -static void human_readable_size_ib(uint64_t size, char *dst, size_t dst_len) {
979 - if(size > 1024ULL * 1024 * 1024 * 1024)
980 - snprintfz(dst, dst_len, "%0.2f TiB", (double)size / 1024.0 / 1024.0 / 1024.0 / 1024.0);
981 - else if(size > 1024ULL * 1024 * 1024)
982 - snprintfz(dst, dst_len, "%0.2f GiB", (double)size / 1024.0 / 1024.0 / 1024.0);
983 - else if(size > 1024ULL * 1024)
984 - snprintfz(dst, dst_len, "%0.2f MiB", (double)size / 1024.0 / 1024.0);
985 - else if(size > 1024ULL)
986 - snprintfz(dst, dst_len, "%0.2f KiB", (double)size / 1024.0);
987 - else
988 - snprintfz(dst, dst_len, "%"PRIu64" B", size);
989 -}
990 -
991 -#define print_duration(dst, dst_len, pos, remaining, duration, one, many, printed) do { \
992 - if((remaining) > (duration)) { \
993 - uint64_t _count = (remaining) / (duration); \
994 - uint64_t _rem = (remaining) - (_count * (duration)); \
995 - (pos) += snprintfz(&(dst)[pos], (dst_len) - (pos), "%s%s%"PRIu64" %s", (printed) ? ", " : "", _rem ? "" : "and ", _count, _count > 1 ? (many) : (one)); \
996 - (remaining) = _rem; \
997 - (printed) = true; \
998 - } \
999 -} while(0)
1000 -
1001 -static void human_readable_duration_s(time_t duration_s, char *dst, size_t dst_len) {
1002 - if(duration_s < 0)
1003 - duration_s = -duration_s;
1004 -
1005 - size_t pos = 0;
1006 - dst[0] = 0 ;
1007 -
1008 - bool printed = false;
1009 - print_duration(dst, dst_len, pos, duration_s, 86400 * 365, "year", "years", printed);
1010 - print_duration(dst, dst_len, pos, duration_s, 86400 * 30, "month", "months", printed);
1011 - print_duration(dst, dst_len, pos, duration_s, 86400 * 1, "day", "days", printed);
1012 - print_duration(dst, dst_len, pos, duration_s, 3600 * 1, "hour", "hours", printed);
1013 - print_duration(dst, dst_len, pos, duration_s, 60 * 1, "min", "mins", printed);
1014 - print_duration(dst, dst_len, pos, duration_s, 1, "sec", "secs", printed);
1015 -}
1016 -
1017 -static int journal_file_to_json_array_cb(const DICTIONARY_ITEM *item, void *entry, void *data) {
1018 - struct journal_file_source *jfs = entry;
1019 - BUFFER *wb = data;
1020 -
1021 - const char *name = dictionary_acquired_item_name(item);
1022 -
1023 - buffer_json_add_array_item_object(wb);
1024 - {
1025 - char size_for_humans[100];
1026 - human_readable_size_ib(jfs->size, size_for_humans, sizeof(size_for_humans));
1027 -
1028 - char duration_for_humans[1024];
1029 - human_readable_duration_s((time_t)((jfs->last_ut - jfs->first_ut) / USEC_PER_SEC),
1030 - duration_for_humans, sizeof(duration_for_humans));
1031 -
1032 - char info[1024];
1033 - snprintfz(info, sizeof(info), "%zu files, with a total size of %s, covering %s",
1034 - jfs->count, size_for_humans, duration_for_humans);
1035 -
1036 - buffer_json_member_add_string(wb, "id", name);
1037 - buffer_json_member_add_string(wb, "name", name);
1038 - buffer_json_member_add_string(wb, "pill", size_for_humans);
1039 - buffer_json_member_add_string(wb, "info", info);
1040 - }
1041 - buffer_json_object_close(wb); // options object
1042 -
1043 - return 1;
1044 -}
1045 -
1046 -static bool journal_file_merge_sizes(const DICTIONARY_ITEM *item __maybe_unused, void *old_value, void *new_value , void *data __maybe_unused) {
1047 - struct journal_file_source *jfs = old_value, *njfs = new_value;
1048 - jfs->count += njfs->count;
1049 - jfs->size += njfs->size;
1050 -
1051 - if(njfs->first_ut && njfs->first_ut < jfs->first_ut)
1052 - jfs->first_ut = njfs->first_ut;
1053 -
1054 - if(njfs->last_ut && njfs->last_ut > jfs->last_ut)
1055 - jfs->last_ut = njfs->last_ut;
1056 -
1057 - return false;
1058 -}
1059 -
1060 -static void available_journal_file_sources_to_json_array(BUFFER *wb) {
1061 - DICTIONARY *dict = dictionary_create(DICT_OPTION_SINGLE_THREADED|DICT_OPTION_NAME_LINK_DONT_CLONE|DICT_OPTION_DONT_OVERWRITE_VALUE);
1062 - dictionary_register_conflict_callback(dict, journal_file_merge_sizes, NULL);
1063 -
1064 - struct journal_file_source t = { 0 };
1065 -
1066 - struct journal_file *jf;
1067 - dfe_start_read(journal_files_registry, jf) {
1068 - t.first_ut = jf->msg_first_ut;
1069 - t.last_ut = jf->msg_last_ut;
1070 - t.count = 1;
1071 - t.size = jf->size;
1072 -
1073 - dictionary_set(dict, SDJF_SOURCE_ALL_NAME, &t, sizeof(t));
1074 -
1075 - if(jf->source_type & SDJF_LOCAL_ALL)
1076 - dictionary_set(dict, SDJF_SOURCE_LOCAL_NAME, &t, sizeof(t));
1077 - if(jf->source_type & SDJF_LOCAL_SYSTEM)
1078 - dictionary_set(dict, SDJF_SOURCE_LOCAL_SYSTEM_NAME, &t, sizeof(t));
1079 - if(jf->source_type & SDJF_LOCAL_USER)
1080 - dictionary_set(dict, SDJF_SOURCE_LOCAL_USERS_NAME, &t, sizeof(t));
1081 - if(jf->source_type & SDJF_LOCAL_OTHER)
1082 - dictionary_set(dict, SDJF_SOURCE_LOCAL_OTHER_NAME, &t, sizeof(t));
1083 - if(jf->source_type & SDJF_LOCAL_NAMESPACE)
1084 - dictionary_set(dict, SDJF_SOURCE_NAMESPACES_NAME, &t, sizeof(t));
1085 - if(jf->source_type & SDJF_REMOTE_ALL)
1086 - dictionary_set(dict, SDJF_SOURCE_REMOTES_NAME, &t, sizeof(t));
1087 - if(jf->source)
1088 - dictionary_set(dict, string2str(jf->source), &t, sizeof(t));
1089 - }
1090 - dfe_done(jf);
1091 -
1092 - dictionary_sorted_walkthrough_read(dict, journal_file_to_json_array_cb, wb);
1093 -
1094 - dictionary_destroy(dict);
1095 -}
1096 -
1097 -static void files_registry_delete_cb(const DICTIONARY_ITEM *item, void *value, void *data __maybe_unused) {
1098 - struct journal_file *jf = value; (void)jf;
1099 - const char *filename = dictionary_acquired_item_name(item); (void)filename;
1100 -
1101 - string_freez(jf->source);
1102 - internal_error(true, "removed journal file '%s'", filename);
1103 -}
1104 -
1105 -void journal_directory_scan(const char *dirname, int depth, usec_t last_scan_ut) {
1106 - static const char *ext = ".journal";
1107 - static const size_t ext_len = sizeof(".journal") - 1;
1108 -
1109 - if (depth > VAR_LOG_JOURNAL_MAX_DEPTH)
1110 - return;
1111 -
1112 - DIR *dir;
1113 - struct dirent *entry;
1114 - struct stat info;
1115 - char absolute_path[FILENAME_MAX];
1116 -
1117 - // Open the directory.
1118 - if ((dir = opendir(dirname)) == NULL) {
1119 - if(errno != ENOENT && errno != ENOTDIR)
1120 - netdata_log_error("Cannot opendir() '%s'", dirname);
1121 - return;
1122 - }
1123 -
1124 - // Read each entry in the directory.
1125 - while ((entry = readdir(dir)) != NULL) {
1126 - snprintfz(absolute_path, sizeof(absolute_path), "%s/%s", dirname, entry->d_name);
1127 - if (stat(absolute_path, &info) != 0) {
1128 - netdata_log_error("Failed to stat() '%s", absolute_path);
1129 - continue;
1130 - }
1131 -
1132 - if (S_ISDIR(info.st_mode)) {
1133 - // If entry is a directory, call traverse recursively.
1134 - if (strcmp(entry->d_name, ".") != 0 && strcmp(entry->d_name, "..") != 0)
1135 - journal_directory_scan(absolute_path, depth + 1, last_scan_ut);
1136 -
1137 - }
1138 - else if (S_ISREG(info.st_mode)) {
1139 - // If entry is a regular file, check if it ends with .journal.
1140 - char *filename = entry->d_name;
1141 - size_t len = strlen(filename);
1142 -
1143 - if (len > ext_len && strcmp(filename + len - ext_len, ext) == 0) {
1144 - struct journal_file t = {
1145 - .file_last_modified_ut = info.st_mtim.tv_sec * USEC_PER_SEC + info.st_mtim.tv_nsec / NSEC_PER_USEC,
1146 - .last_scan_ut = last_scan_ut,
1147 - .size = info.st_size,
1148 - .max_journal_vs_realtime_delta_ut = JOURNAL_VS_REALTIME_DELTA_DEFAULT_UT,
1149 - };
1150 - dictionary_set(journal_files_registry, absolute_path, &t, sizeof(t));
1151 - }
1152 - }
1153 - }
1154 -
1155 - closedir(dir);
1156 -}
1157 -
1158 -static void journal_files_registry_update() {
1159 - usec_t scan_ut = now_monotonic_usec();
1160 -
1161 - for(unsigned i = 0; i < MAX_JOURNAL_DIRECTORIES ;i++) {
1162 - if(!journal_directories[i].path)
1163 - break;
1164 -
1165 - journal_directory_scan(journal_directories[i].path, 0, scan_ut);
1166 - }
1167 -
1168 - struct journal_file *jf;
1169 - dfe_start_write(journal_files_registry, jf) {
1170 - if(jf->last_scan_ut < scan_ut)
1171 - dictionary_del(journal_files_registry, jf_dfe.name);
1172 - }
1173 - dfe_done(jf);
1174 -}
1175 -
1176 -// ----------------------------------------------------------------------------
1177 -
636 static bool jf_is_mine(struct journal_file *jf, FUNCTION_QUERY_STATUS *fqs) {
637
638 if((fqs->source_type == SDJF_NONE && !fqs->sources) || (jf->source_type & fqs->source_type) ||
1181 - (fqs->sources && simple_pattern_matches(fqs->sources, string2str(jf->source)))) {
639 + (fqs->sources && simple_pattern_matches(fqs->sources, string2str(jf->source)))) {
640
641 usec_t anchor_delta = JOURNAL_VS_REALTIME_DELTA_MAX_UT;
642 usec_t first_ut = jf->msg_first_ut;
@@ -1191,30 +649,6 @@ static bool jf_is_mine(struct journal_file *jf, FUNCTION_QUERY_STATUS *fqs) {
649 return false;
650 }
651
1194 -static int journal_file_dict_items_backward_compar(const void *a, const void *b) {
1195 - const DICTIONARY_ITEM **ad = (const DICTIONARY_ITEM **)a, **bd = (const DICTIONARY_ITEM **)b;
1196 - struct journal_file *jfa = dictionary_acquired_item_value(*ad);
1197 - struct journal_file *jfb = dictionary_acquired_item_value(*bd);
1198 -
1199 - if(jfa->msg_last_ut < jfb->msg_last_ut)
1200 - return 1;
1201 -
1202 - if(jfa->msg_last_ut > jfb->msg_last_ut)
1203 - return -1;
1204 -
1205 - if(jfa->msg_first_ut < jfb->msg_first_ut)
1206 - return 1;
1207 -
1208 - if(jfa->msg_first_ut > jfb->msg_first_ut)
1209 - return -1;
1210 -
1211 - return 0;
1212 -}
1213 -
1214 -static int journal_file_dict_items_forward_compar(const void *a, const void *b) {
1215 - return -journal_file_dict_items_backward_compar(a, b);
1216 -}
1217 -
652 static int netdata_systemd_journal_query(BUFFER *wb, FACETS *facets, FUNCTION_QUERY_STATUS *fqs) {
653 ND_SD_JOURNAL_STATUS status = ND_SD_JOURNAL_NO_FILE_MATCHED;
654 struct journal_file *jf;
@@ -1272,7 +706,7 @@ static int netdata_systemd_journal_query(BUFFER *wb, FACETS *facets, FUNCTION_QU
706 continue;
707
708 fqs->file_working++;
1275 - fqs->cached_count = 0;
709 + // fqs->cached_count = 0;
710
711 size_t fs_calls = fstat_thread_calls;
712 size_t fs_cached = fstat_thread_cached_responses;
@@ -1521,593 +955,6 @@ static void netdata_systemd_journal_function_help(const char *transaction) {
955 buffer_free(wb);
956 }
957
1524 -const char *errno_map[] = {
1525 - [1] = "1 (EPERM)", // "Operation not permitted",
1526 - [2] = "2 (ENOENT)", // "No such file or directory",
1527 - [3] = "3 (ESRCH)", // "No such process",
1528 - [4] = "4 (EINTR)", // "Interrupted system call",
1529 - [5] = "5 (EIO)", // "Input/output error",
1530 - [6] = "6 (ENXIO)", // "No such device or address",
1531 - [7] = "7 (E2BIG)", // "Argument list too long",
1532 - [8] = "8 (ENOEXEC)", // "Exec format error",
1533 - [9] = "9 (EBADF)", // "Bad file descriptor",
1534 - [10] = "10 (ECHILD)", // "No child processes",
1535 - [11] = "11 (EAGAIN)", // "Resource temporarily unavailable",
1536 - [12] = "12 (ENOMEM)", // "Cannot allocate memory",
1537 - [13] = "13 (EACCES)", // "Permission denied",
1538 - [14] = "14 (EFAULT)", // "Bad address",
1539 - [15] = "15 (ENOTBLK)", // "Block device required",
1540 - [16] = "16 (EBUSY)", // "Device or resource busy",
1541 - [17] = "17 (EEXIST)", // "File exists",
1542 - [18] = "18 (EXDEV)", // "Invalid cross-device link",
1543 - [19] = "19 (ENODEV)", // "No such device",
1544 - [20] = "20 (ENOTDIR)", // "Not a directory",
1545 - [21] = "21 (EISDIR)", // "Is a directory",
1546 - [22] = "22 (EINVAL)", // "Invalid argument",
1547 - [23] = "23 (ENFILE)", // "Too many open files in system",
1548 - [24] = "24 (EMFILE)", // "Too many open files",
1549 - [25] = "25 (ENOTTY)", // "Inappropriate ioctl for device",
1550 - [26] = "26 (ETXTBSY)", // "Text file busy",
1551 - [27] = "27 (EFBIG)", // "File too large",
1552 - [28] = "28 (ENOSPC)", // "No space left on device",
1553 - [29] = "29 (ESPIPE)", // "Illegal seek",
1554 - [30] = "30 (EROFS)", // "Read-only file system",
1555 - [31] = "31 (EMLINK)", // "Too many links",
1556 - [32] = "32 (EPIPE)", // "Broken pipe",
1557 - [33] = "33 (EDOM)", // "Numerical argument out of domain",
1558 - [34] = "34 (ERANGE)", // "Numerical result out of range",
1559 - [35] = "35 (EDEADLK)", // "Resource deadlock avoided",
1560 - [36] = "36 (ENAMETOOLONG)", // "File name too long",
1561 - [37] = "37 (ENOLCK)", // "No locks available",
1562 - [38] = "38 (ENOSYS)", // "Function not implemented",
1563 - [39] = "39 (ENOTEMPTY)", // "Directory not empty",
1564 - [40] = "40 (ELOOP)", // "Too many levels of symbolic links",
1565 - [42] = "42 (ENOMSG)", // "No message of desired type",
1566 - [43] = "43 (EIDRM)", // "Identifier removed",
1567 - [44] = "44 (ECHRNG)", // "Channel number out of range",
1568 - [45] = "45 (EL2NSYNC)", // "Level 2 not synchronized",
1569 - [46] = "46 (EL3HLT)", // "Level 3 halted",
1570 - [47] = "47 (EL3RST)", // "Level 3 reset",
1571 - [48] = "48 (ELNRNG)", // "Link number out of range",
1572 - [49] = "49 (EUNATCH)", // "Protocol driver not attached",
1573 - [50] = "50 (ENOCSI)", // "No CSI structure available",
1574 - [51] = "51 (EL2HLT)", // "Level 2 halted",
1575 - [52] = "52 (EBADE)", // "Invalid exchange",
1576 - [53] = "53 (EBADR)", // "Invalid request descriptor",
1577 - [54] = "54 (EXFULL)", // "Exchange full",
1578 - [55] = "55 (ENOANO)", // "No anode",
1579 - [56] = "56 (EBADRQC)", // "Invalid request code",
1580 - [57] = "57 (EBADSLT)", // "Invalid slot",
1581 - [59] = "59 (EBFONT)", // "Bad font file format",
1582 - [60] = "60 (ENOSTR)", // "Device not a stream",
1583 - [61] = "61 (ENODATA)", // "No data available",
1584 - [62] = "62 (ETIME)", // "Timer expired",
1585 - [63] = "63 (ENOSR)", // "Out of streams resources",
1586 - [64] = "64 (ENONET)", // "Machine is not on the network",
1587 - [65] = "65 (ENOPKG)", // "Package not installed",
1588 - [66] = "66 (EREMOTE)", // "Object is remote",
1589 - [67] = "67 (ENOLINK)", // "Link has been severed",
1590 - [68] = "68 (EADV)", // "Advertise error",
1591 - [69] = "69 (ESRMNT)", // "Srmount error",
1592 - [70] = "70 (ECOMM)", // "Communication error on send",
1593 - [71] = "71 (EPROTO)", // "Protocol error",
1594 - [72] = "72 (EMULTIHOP)", // "Multihop attempted",
1595 - [73] = "73 (EDOTDOT)", // "RFS specific error",
1596 - [74] = "74 (EBADMSG)", // "Bad message",
1597 - [75] = "75 (EOVERFLOW)", // "Value too large for defined data type",
1598 - [76] = "76 (ENOTUNIQ)", // "Name not unique on network",
1599 - [77] = "77 (EBADFD)", // "File descriptor in bad state",
1600 - [78] = "78 (EREMCHG)", // "Remote address changed",
1601 - [79] = "79 (ELIBACC)", // "Can not access a needed shared library",
1602 - [80] = "80 (ELIBBAD)", // "Accessing a corrupted shared library",
1603 - [81] = "81 (ELIBSCN)", // ".lib section in a.out corrupted",
1604 - [82] = "82 (ELIBMAX)", // "Attempting to link in too many shared libraries",
1605 - [83] = "83 (ELIBEXEC)", // "Cannot exec a shared library directly",
1606 - [84] = "84 (EILSEQ)", // "Invalid or incomplete multibyte or wide character",
1607 - [85] = "85 (ERESTART)", // "Interrupted system call should be restarted",
1608 - [86] = "86 (ESTRPIPE)", // "Streams pipe error",
1609 - [87] = "87 (EUSERS)", // "Too many users",
1610 - [88] = "88 (ENOTSOCK)", // "Socket operation on non-socket",
1611 - [89] = "89 (EDESTADDRREQ)", // "Destination address required",
1612 - [90] = "90 (EMSGSIZE)", // "Message too long",
1613 - [91] = "91 (EPROTOTYPE)", // "Protocol wrong type for socket",
1614 - [92] = "92 (ENOPROTOOPT)", // "Protocol not available",
1615 - [93] = "93 (EPROTONOSUPPORT)", // "Protocol not supported",
1616 - [94] = "94 (ESOCKTNOSUPPORT)", // "Socket type not supported",
1617 - [95] = "95 (ENOTSUP)", // "Operation not supported",
1618 - [96] = "96 (EPFNOSUPPORT)", // "Protocol family not supported",
1619 - [97] = "97 (EAFNOSUPPORT)", // "Address family not supported by protocol",
1620 - [98] = "98 (EADDRINUSE)", // "Address already in use",
1621 - [99] = "99 (EADDRNOTAVAIL)", // "Cannot assign requested address",
1622 - [100] = "100 (ENETDOWN)", // "Network is down",
1623 - [101] = "101 (ENETUNREACH)", // "Network is unreachable",
1624 - [102] = "102 (ENETRESET)", // "Network dropped connection on reset",
1625 - [103] = "103 (ECONNABORTED)", // "Software caused connection abort",
1626 - [104] = "104 (ECONNRESET)", // "Connection reset by peer",
1627 - [105] = "105 (ENOBUFS)", // "No buffer space available",
1628 - [106] = "106 (EISCONN)", // "Transport endpoint is already connected",
1629 - [107] = "107 (ENOTCONN)", // "Transport endpoint is not connected",
1630 - [108] = "108 (ESHUTDOWN)", // "Cannot send after transport endpoint shutdown",
1631 - [109] = "109 (ETOOMANYREFS)", // "Too many references: cannot splice",
1632 - [110] = "110 (ETIMEDOUT)", // "Connection timed out",
1633 - [111] = "111 (ECONNREFUSED)", // "Connection refused",
1634 - [112] = "112 (EHOSTDOWN)", // "Host is down",
1635 - [113] = "113 (EHOSTUNREACH)", // "No route to host",
1636 - [114] = "114 (EALREADY)", // "Operation already in progress",
1637 - [115] = "115 (EINPROGRESS)", // "Operation now in progress",
1638 - [116] = "116 (ESTALE)", // "Stale file handle",
1639 - [117] = "117 (EUCLEAN)", // "Structure needs cleaning",
1640 - [118] = "118 (ENOTNAM)", // "Not a XENIX named type file",
1641 - [119] = "119 (ENAVAIL)", // "No XENIX semaphores available",
1642 - [120] = "120 (EISNAM)", // "Is a named type file",
1643 - [121] = "121 (EREMOTEIO)", // "Remote I/O error",
1644 - [122] = "122 (EDQUOT)", // "Disk quota exceeded",
1645 - [123] = "123 (ENOMEDIUM)", // "No medium found",
1646 - [124] = "124 (EMEDIUMTYPE)", // "Wrong medium type",
1647 - [125] = "125 (ECANCELED)", // "Operation canceled",
1648 - [126] = "126 (ENOKEY)", // "Required key not available",
1649 - [127] = "127 (EKEYEXPIRED)", // "Key has expired",
1650 - [128] = "128 (EKEYREVOKED)", // "Key has been revoked",
1651 - [129] = "129 (EKEYREJECTED)", // "Key was rejected by service",
1652 - [130] = "130 (EOWNERDEAD)", // "Owner died",
1653 - [131] = "131 (ENOTRECOVERABLE)", // "State not recoverable",
1654 - [132] = "132 (ERFKILL)", // "Operation not possible due to RF-kill",
1655 - [133] = "133 (EHWPOISON)", // "Memory page has hardware error",
1656 -};
1657 -
1658 -static const char *syslog_facility_to_name(int facility) {
1659 - switch (facility) {
1660 - case LOG_FAC(LOG_KERN): return "kern";
1661 - case LOG_FAC(LOG_USER): return "user";
1662 - case LOG_FAC(LOG_MAIL): return "mail";
1663 - case LOG_FAC(LOG_DAEMON): return "daemon";
1664 - case LOG_FAC(LOG_AUTH): return "auth";
1665 - case LOG_FAC(LOG_SYSLOG): return "syslog";
1666 - case LOG_FAC(LOG_LPR): return "lpr";
1667 - case LOG_FAC(LOG_NEWS): return "news";
1668 - case LOG_FAC(LOG_UUCP): return "uucp";
1669 - case LOG_FAC(LOG_CRON): return "cron";
1670 - case LOG_FAC(LOG_AUTHPRIV): return "authpriv";
1671 - case LOG_FAC(LOG_FTP): return "ftp";
1672 - case LOG_FAC(LOG_LOCAL0): return "local0";
1673 - case LOG_FAC(LOG_LOCAL1): return "local1";
1674 - case LOG_FAC(LOG_LOCAL2): return "local2";
1675 - case LOG_FAC(LOG_LOCAL3): return "local3";
1676 - case LOG_FAC(LOG_LOCAL4): return "local4";
1677 - case LOG_FAC(LOG_LOCAL5): return "local5";
1678 - case LOG_FAC(LOG_LOCAL6): return "local6";
1679 - case LOG_FAC(LOG_LOCAL7): return "local7";
1680 - default: return NULL;
1681 - }
1682 -}
1683 -
1684 -static const char *syslog_priority_to_name(int priority) {
1685 - switch (priority) {
1686 - case LOG_ALERT: return "alert";
1687 - case LOG_CRIT: return "critical";
1688 - case LOG_DEBUG: return "debug";
1689 - case LOG_EMERG: return "panic";
1690 - case LOG_ERR: return "error";
1691 - case LOG_INFO: return "info";
1692 - case LOG_NOTICE: return "notice";
1693 - case LOG_WARNING: return "warning";
1694 - default: return NULL;
1695 - }
1696 -}
1697 -
1698 -static FACET_ROW_SEVERITY syslog_priority_to_facet_severity(FACETS *facets __maybe_unused, FACET_ROW *row, void *data __maybe_unused) {
1699 - // same to
1700 - // https://github.com/systemd/systemd/blob/aab9e4b2b86905a15944a1ac81e471b5b7075932/src/basic/terminal-util.c#L1501
1701 - // function get_log_colors()
1702 -
1703 - FACET_ROW_KEY_VALUE *priority_rkv = dictionary_get(row->dict, "PRIORITY");
1704 - if(!priority_rkv || priority_rkv->empty)
1705 - return FACET_ROW_SEVERITY_NORMAL;
1706 -
1707 - int priority = str2i(buffer_tostring(priority_rkv->wb));
1708 -
1709 - if(priority <= LOG_ERR)
1710 - return FACET_ROW_SEVERITY_CRITICAL;
1711 -
1712 - else if (priority <= LOG_WARNING)
1713 - return FACET_ROW_SEVERITY_WARNING;
1714 -
1715 - else if(priority <= LOG_NOTICE)
1716 - return FACET_ROW_SEVERITY_NOTICE;
1717 -
1718 - else if(priority >= LOG_DEBUG)
1719 - return FACET_ROW_SEVERITY_DEBUG;
1720 -
1721 - return FACET_ROW_SEVERITY_NORMAL;
1722 -}
1723 -
1724 -static char *uid_to_username(uid_t uid, char *buffer, size_t buffer_size) {
1725 - static __thread char tmp[1024 + 1];
1726 - struct passwd pw, *result = NULL;
1727 -
1728 - if (getpwuid_r(uid, &pw, tmp, sizeof(tmp), &result) != 0 || !result || !pw.pw_name || !(*pw.pw_name))
1729 - snprintfz(buffer, buffer_size - 1, "%u", uid);
1730 - else
1731 - snprintfz(buffer, buffer_size - 1, "%u (%s)", uid, pw.pw_name);
1732 -
1733 - return buffer;
1734 -}
1735 -
1736 -static char *gid_to_groupname(gid_t gid, char* buffer, size_t buffer_size) {
1737 - static __thread char tmp[1024];
1738 - struct group grp, *result = NULL;
1739 -
1740 - if (getgrgid_r(gid, &grp, tmp, sizeof(tmp), &result) != 0 || !result || !grp.gr_name || !(*grp.gr_name))
1741 - snprintfz(buffer, buffer_size - 1, "%u", gid);
1742 - else
1743 - snprintfz(buffer, buffer_size - 1, "%u (%s)", gid, grp.gr_name);
1744 -
1745 - return buffer;
1746 -}
1747 -
1748 -static void netdata_systemd_journal_transform_syslog_facility(FACETS *facets __maybe_unused, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope __maybe_unused, void *data __maybe_unused) {
1749 - const char *v = buffer_tostring(wb);
1750 - if(*v && isdigit(*v)) {
1751 - int facility = str2i(buffer_tostring(wb));
1752 - const char *name = syslog_facility_to_name(facility);
1753 - if (name) {
1754 - buffer_flush(wb);
1755 - buffer_strcat(wb, name);
1756 - }
1757 - }
1758 -}
1759 -
1760 -static void netdata_systemd_journal_transform_priority(FACETS *facets __maybe_unused, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope __maybe_unused, void *data __maybe_unused) {
1761 - if(scope == FACETS_TRANSFORM_FACET_SORT)
1762 - return;
1763 -
1764 - const char *v = buffer_tostring(wb);
1765 - if(*v && isdigit(*v)) {
1766 - int priority = str2i(buffer_tostring(wb));
1767 - const char *name = syslog_priority_to_name(priority);
1768 - if (name) {
1769 - buffer_flush(wb);
1770 - buffer_strcat(wb, name);
1771 - }
1772 - }
1773 -}
1774 -
1775 -static void netdata_systemd_journal_transform_errno(FACETS *facets __maybe_unused, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope __maybe_unused, void *data __maybe_unused) {
1776 - if(scope == FACETS_TRANSFORM_FACET_SORT)
1777 - return;
1778 -
1779 - const char *v = buffer_tostring(wb);
1780 - if(*v && isdigit(*v)) {
1781 - unsigned err_no = str2u(buffer_tostring(wb));
1782 - if(err_no > 0 && err_no < sizeof(errno_map) / sizeof(*errno_map)) {
1783 - const char *name = errno_map[err_no];
1784 - if(name) {
1785 - buffer_flush(wb);
1786 - buffer_strcat(wb, name);
1787 - }
1788 - }
1789 - }
1790 -}
1791 -
1792 -// ----------------------------------------------------------------------------
1793 -// UID and GID transformation
1794 -
1795 -#define UID_GID_HASHTABLE_SIZE 10000
1796 -
1797 -struct word_t2str_hashtable_entry {
1798 - struct word_t2str_hashtable_entry *next;
1799 - Word_t hash;
1800 - size_t len;
1801 - char str[];
1802 -};
1803 -
1804 -struct word_t2str_hashtable {
1805 - SPINLOCK spinlock;
1806 - size_t size;
1807 - struct word_t2str_hashtable_entry *hashtable[UID_GID_HASHTABLE_SIZE];
1808 -};
1809 -
1810 -struct word_t2str_hashtable uid_hashtable = {
1811 - .size = UID_GID_HASHTABLE_SIZE,
1812 -};
1813 -
1814 -struct word_t2str_hashtable gid_hashtable = {
1815 - .size = UID_GID_HASHTABLE_SIZE,
1816 -};
1817 -
1818 -struct word_t2str_hashtable_entry **word_t2str_hashtable_slot(struct word_t2str_hashtable *ht, Word_t hash) {
1819 - size_t slot = hash % ht->size;
1820 - struct word_t2str_hashtable_entry **e = &ht->hashtable[slot];
1821 -
1822 - while(*e && (*e)->hash != hash)
1823 - e = &((*e)->next);
1824 -
1825 - return e;
1826 -}
1827 -
1828 -const char *uid_to_username_cached(uid_t uid, size_t *length) {
1829 - spinlock_lock(&uid_hashtable.spinlock);
1830 -
1831 - struct word_t2str_hashtable_entry **e = word_t2str_hashtable_slot(&uid_hashtable, uid);
1832 - if(!(*e)) {
1833 - static __thread char buf[1024];
1834 - const char *name = uid_to_username(uid, buf, sizeof(buf));
1835 - size_t size = strlen(name) + 1;
1836 -
1837 - *e = callocz(1, sizeof(struct word_t2str_hashtable_entry) + size);
1838 - (*e)->len = size - 1;
1839 - (*e)->hash = uid;
1840 - memcpy((*e)->str, name, size);
1841 - }
1842 -
1843 - spinlock_unlock(&uid_hashtable.spinlock);
1844 -
1845 - *length = (*e)->len;
1846 - return (*e)->str;
1847 -}
1848 -
1849 -const char *gid_to_groupname_cached(gid_t gid, size_t *length) {
1850 - spinlock_lock(&gid_hashtable.spinlock);
1851 -
1852 - struct word_t2str_hashtable_entry **e = word_t2str_hashtable_slot(&gid_hashtable, gid);
1853 - if(!(*e)) {
1854 - static __thread char buf[1024];
1855 - const char *name = gid_to_groupname(gid, buf, sizeof(buf));
1856 - size_t size = strlen(name) + 1;
1857 -
1858 - *e = callocz(1, sizeof(struct word_t2str_hashtable_entry) + size);
1859 - (*e)->len = size - 1;
1860 - (*e)->hash = gid;
1861 - memcpy((*e)->str, name, size);
1862 - }
1863 -
1864 - spinlock_unlock(&gid_hashtable.spinlock);
1865 -
1866 - *length = (*e)->len;
1867 - return (*e)->str;
1868 -}
1869 -
1870 -DICTIONARY *boot_ids_to_first_ut = NULL;
1871 -
1872 -static void netdata_systemd_journal_transform_boot_id(FACETS *facets __maybe_unused, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope __maybe_unused, void *data __maybe_unused) {
1873 - const char *boot_id = buffer_tostring(wb);
1874 - if(*boot_id && isxdigit(*boot_id)) {
1875 - usec_t ut = UINT64_MAX;
1876 - usec_t *p_ut = dictionary_get(boot_ids_to_first_ut, boot_id);
1877 - if(!p_ut) {
1878 - struct journal_file *jf;
1879 - dfe_start_read(journal_files_registry, jf) {
1880 - const char *files[2] = {
1881 - [0] = jf_dfe.name,
1882 - [1] = NULL,
1883 - };
1884 -
1885 - sd_journal *j = NULL;
1886 - if(sd_journal_open_files(&j, files, ND_SD_JOURNAL_OPEN_FLAGS) < 0 || !j) {
1887 - internal_error(true, "JOURNAL: cannot open file '%s' to get boot_id", jf_dfe.name);
1888 - continue;
1889 - }
1890 -
1891 - char m[100];
1892 - size_t len = snprintfz(m, sizeof(m), "_BOOT_ID=%s", boot_id);
1893 -
1894 - if(sd_journal_add_match(j, m, len) < 0) {
1895 - internal_error(true, "JOURNAL: cannot add match '%s' to file '%s'", m, jf_dfe.name);
1896 - sd_journal_close(j);
1897 - continue;
1898 - }
1899 -
1900 - if(sd_journal_seek_head(j) < 0) {
1901 - internal_error(true, "JOURNAL: cannot seek head to file '%s'", jf_dfe.name);
1902 - sd_journal_close(j);
1903 - continue;
1904 - }
1905 -
1906 - if(sd_journal_next(j) < 0) {
1907 - internal_error(true, "JOURNAL: cannot get next of file '%s'", jf_dfe.name);
1908 - sd_journal_close(j);
1909 - continue;
1910 - }
1911 -
1912 - usec_t t_ut = 0;
1913 - if(sd_journal_get_realtime_usec(j, &t_ut) < 0 || !t_ut) {
1914 - internal_error(true, "JOURNAL: cannot get realtime_usec of file '%s'", jf_dfe.name);
1915 - sd_journal_close(j);
1916 - continue;
1917 - }
1918 -
1919 - if(t_ut < ut)
1920 - ut = t_ut;
1921 -
1922 - sd_journal_close(j);
1923 - }
1924 - dfe_done(jf);
1925 -
1926 - dictionary_set(boot_ids_to_first_ut, boot_id, &ut, sizeof(ut));
1927 - }
1928 - else
1929 - ut = *p_ut;
1930 -
1931 - if(ut != UINT64_MAX) {
1932 - time_t timestamp_sec = (time_t)(ut / USEC_PER_SEC);
1933 - struct tm tm;
1934 - char buffer[30];
1935 -
1936 - gmtime_r(&timestamp_sec, &tm);
1937 - strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tm);
1938 -
1939 - switch(scope) {
1940 - default:
1941 - case FACETS_TRANSFORM_DATA:
1942 - case FACETS_TRANSFORM_VALUE:
1943 - buffer_sprintf(wb, " (%s UTC) ", buffer);
1944 - break;
1945 -
1946 - case FACETS_TRANSFORM_FACET:
1947 - case FACETS_TRANSFORM_FACET_SORT:
1948 - case FACETS_TRANSFORM_HISTOGRAM:
1949 - buffer_flush(wb);
1950 - buffer_sprintf(wb, "%s UTC", buffer);
1951 - break;
1952 - }
1953 - }
1954 - }
1955 -}
1956 -
1957 -static void netdata_systemd_journal_transform_uid(FACETS *facets __maybe_unused, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope __maybe_unused, void *data __maybe_unused) {
1958 - if(scope == FACETS_TRANSFORM_FACET_SORT)
1959 - return;
1960 -
1961 - const char *v = buffer_tostring(wb);
1962 - if(*v && isdigit(*v)) {
1963 - uid_t uid = str2i(buffer_tostring(wb));
1964 - size_t len;
1965 - const char *name = uid_to_username_cached(uid, &len);
1966 - buffer_contents_replace(wb, name, len);
1967 - }
1968 -}
1969 -
1970 -static void netdata_systemd_journal_transform_gid(FACETS *facets __maybe_unused, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope __maybe_unused, void *data __maybe_unused) {
1971 - if(scope == FACETS_TRANSFORM_FACET_SORT)
1972 - return;
1973 -
1974 - const char *v = buffer_tostring(wb);
1975 - if(*v && isdigit(*v)) {
1976 - gid_t gid = str2i(buffer_tostring(wb));
1977 - size_t len;
1978 - const char *name = gid_to_groupname_cached(gid, &len);
1979 - buffer_contents_replace(wb, name, len);
1980 - }
1981 -}
1982 -
1983 -const char *linux_capabilities[] = {
1984 - [CAP_CHOWN] = "CHOWN",
1985 - [CAP_DAC_OVERRIDE] = "DAC_OVERRIDE",
1986 - [CAP_DAC_READ_SEARCH] = "DAC_READ_SEARCH",
1987 - [CAP_FOWNER] = "FOWNER",
1988 - [CAP_FSETID] = "FSETID",
1989 - [CAP_KILL] = "KILL",
1990 - [CAP_SETGID] = "SETGID",
1991 - [CAP_SETUID] = "SETUID",
1992 - [CAP_SETPCAP] = "SETPCAP",
1993 - [CAP_LINUX_IMMUTABLE] = "LINUX_IMMUTABLE",
1994 - [CAP_NET_BIND_SERVICE] = "NET_BIND_SERVICE",
1995 - [CAP_NET_BROADCAST] = "NET_BROADCAST",
1996 - [CAP_NET_ADMIN] = "NET_ADMIN",
1997 - [CAP_NET_RAW] = "NET_RAW",
1998 - [CAP_IPC_LOCK] = "IPC_LOCK",
1999 - [CAP_IPC_OWNER] = "IPC_OWNER",
2000 - [CAP_SYS_MODULE] = "SYS_MODULE",
2001 - [CAP_SYS_RAWIO] = "SYS_RAWIO",
2002 - [CAP_SYS_CHROOT] = "SYS_CHROOT",
2003 - [CAP_SYS_PTRACE] = "SYS_PTRACE",
2004 - [CAP_SYS_PACCT] = "SYS_PACCT",
2005 - [CAP_SYS_ADMIN] = "SYS_ADMIN",
2006 - [CAP_SYS_BOOT] = "SYS_BOOT",
2007 - [CAP_SYS_NICE] = "SYS_NICE",
2008 - [CAP_SYS_RESOURCE] = "SYS_RESOURCE",
2009 - [CAP_SYS_TIME] = "SYS_TIME",
2010 - [CAP_SYS_TTY_CONFIG] = "SYS_TTY_CONFIG",
2011 - [CAP_MKNOD] = "MKNOD",
2012 - [CAP_LEASE] = "LEASE",
2013 - [CAP_AUDIT_WRITE] = "AUDIT_WRITE",
2014 - [CAP_AUDIT_CONTROL] = "AUDIT_CONTROL",
2015 - [CAP_SETFCAP] = "SETFCAP",
2016 - [CAP_MAC_OVERRIDE] = "MAC_OVERRIDE",
2017 - [CAP_MAC_ADMIN] = "MAC_ADMIN",
2018 - [CAP_SYSLOG] = "SYSLOG",
2019 - [CAP_WAKE_ALARM] = "WAKE_ALARM",
2020 - [CAP_BLOCK_SUSPEND] = "BLOCK_SUSPEND",
2021 - [37 /*CAP_AUDIT_READ*/] = "AUDIT_READ",
2022 - [38 /*CAP_PERFMON*/] = "PERFMON",
2023 - [39 /*CAP_BPF*/] = "BPF",
2024 - [40 /* CAP_CHECKPOINT_RESTORE */] = "CHECKPOINT_RESTORE",
2025 -};
2026 -
2027 -static void netdata_systemd_journal_transform_cap_effective(FACETS *facets __maybe_unused, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope __maybe_unused, void *data __maybe_unused) {
2028 - if(scope == FACETS_TRANSFORM_FACET_SORT)
2029 - return;
2030 -
2031 - const char *v = buffer_tostring(wb);
2032 - if(*v && isdigit(*v)) {
2033 - uint64_t cap = strtoul(buffer_tostring(wb), NULL, 16);
2034 - if(cap) {
2035 - buffer_fast_strcat(wb, " (", 2);
2036 - for (size_t i = 0, added = 0; i < sizeof(linux_capabilities) / sizeof(linux_capabilities[0]); i++) {
2037 - if (linux_capabilities[i] && (cap & (1ULL << i))) {
2038 -
2039 - if (added)
2040 - buffer_fast_strcat(wb, " | ", 3);
2041 -
2042 - buffer_strcat(wb, linux_capabilities[i]);
2043 - added++;
2044 - }
2045 - }
2046 - buffer_fast_strcat(wb, ")", 1);
2047 - }
2048 - }
2049 -}
2050 -
2051 -static void netdata_systemd_journal_transform_timestamp_usec(FACETS *facets __maybe_unused, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope __maybe_unused, void *data __maybe_unused) {
2052 - if(scope == FACETS_TRANSFORM_FACET_SORT)
2053 - return;
2054 -
2055 - const char *v = buffer_tostring(wb);
2056 - if(*v && isdigit(*v)) {
2057 - uint64_t ut = str2ull(buffer_tostring(wb), NULL);
2058 - if(ut) {
2059 - time_t timestamp_sec = ut / USEC_PER_SEC;
2060 - struct tm tm;
2061 - char buffer[30];
2062 -
2063 - gmtime_r(&timestamp_sec, &tm);
2064 - strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tm);
2065 - buffer_sprintf(wb, " (%s.%06llu UTC)", buffer, ut % USEC_PER_SEC);
2066 - }
2067 - }
2068 -}
2069 -
2070 -// ----------------------------------------------------------------------------
2071 -
2072 -static void netdata_systemd_journal_dynamic_row_id(FACETS *facets __maybe_unused, BUFFER *json_array, FACET_ROW_KEY_VALUE *rkv, FACET_ROW *row, void *data __maybe_unused) {
2073 - FACET_ROW_KEY_VALUE *pid_rkv = dictionary_get(row->dict, "_PID");
2074 - const char *pid = pid_rkv ? buffer_tostring(pid_rkv->wb) : FACET_VALUE_UNSET;
2075 -
2076 - const char *identifier = NULL;
2077 - FACET_ROW_KEY_VALUE *container_name_rkv = dictionary_get(row->dict, "CONTAINER_NAME");
2078 - if(container_name_rkv && !container_name_rkv->empty)
2079 - identifier = buffer_tostring(container_name_rkv->wb);
2080 -
2081 - if(!identifier) {
2082 - FACET_ROW_KEY_VALUE *syslog_identifier_rkv = dictionary_get(row->dict, "SYSLOG_IDENTIFIER");
2083 - if(syslog_identifier_rkv && !syslog_identifier_rkv->empty)
2084 - identifier = buffer_tostring(syslog_identifier_rkv->wb);
2085 -
2086 - if(!identifier) {
2087 - FACET_ROW_KEY_VALUE *comm_rkv = dictionary_get(row->dict, "_COMM");
2088 - if(comm_rkv && !comm_rkv->empty)
2089 - identifier = buffer_tostring(comm_rkv->wb);
2090 - }
2091 - }
2092 -
2093 - buffer_flush(rkv->wb);
2094 -
2095 - if(!identifier || !*identifier)
2096 - buffer_strcat(rkv->wb, FACET_VALUE_UNSET);
2097 - else if(!pid || !*pid)
2098 - buffer_sprintf(rkv->wb, "%s", identifier);
2099 - else
2100 - buffer_sprintf(rkv->wb, "%s[%s]", identifier, pid);
2101 -
2102 - buffer_json_add_array_item_string(json_array, buffer_tostring(rkv->wb));
2103 -}
2104 -
2105 -static void netdata_systemd_journal_rich_message(FACETS *facets __maybe_unused, BUFFER *json_array, FACET_ROW_KEY_VALUE *rkv, FACET_ROW *row __maybe_unused, void *data __maybe_unused) {
2106 - buffer_json_add_array_item_object(json_array);
2107 - buffer_json_member_add_string(json_array, "value", buffer_tostring(rkv->wb));
2108 - buffer_json_object_close(json_array);
2109 -}
2110 -
958 DICTIONARY *function_query_status_dict = NULL;
959
960 static void function_systemd_journal_progress(BUFFER *wb, const char *transaction, const char *progress_id) {
@@ -2168,7 +1015,7 @@ static void function_systemd_journal_progress(BUFFER *wb, const char *transactio
1015 dictionary_acquired_item_release(function_query_status_dict, item);
1016 }
1017
2171 -static void function_systemd_journal(const char *transaction, char *function, int timeout, bool *cancelled) {
1018 +void function_systemd_journal(const char *transaction, char *function, int timeout, bool *cancelled) {
1019 fstat_thread_calls = 0;
1020 fstat_thread_cached_responses = 0;
1021 journal_files_registry_update();
@@ -2233,11 +1080,13 @@ static void function_systemd_journal(const char *transaction, char *function, in
1080 // netdata_systemd_journal_rich_message, NULL);
1081
1082 facets_register_key_name_transformation(facets, "PRIORITY",
2236 - FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_TRANSFORM_VIEW,
1083 + FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_TRANSFORM_VIEW |
1084 + FACET_KEY_OPTION_EXPANDED_FILTER,
1085 netdata_systemd_journal_transform_priority, NULL);
1086
1087 facets_register_key_name_transformation(facets, "SYSLOG_FACILITY",
2240 - FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_TRANSFORM_VIEW,
1088 + FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_TRANSFORM_VIEW |
1089 + FACET_KEY_OPTION_EXPANDED_FILTER,
1090 netdata_systemd_journal_transform_syslog_facility, NULL);
1091
1092 facets_register_key_name_transformation(facets, "ERRNO",
@@ -2705,132 +1554,8 @@ cleanup:
1554 }
1555 }
1556
2708 -// ----------------------------------------------------------------------------
2709 -
2710 -int main(int argc __maybe_unused, char **argv __maybe_unused) {
2711 - stderror = stderr;
2712 - clocks_init();
2713 -
2714 - program_name = "systemd-journal.plugin";
2715 -
2716 - // disable syslog
2717 - error_log_syslog = 0;
2718 -
2719 - // set errors flood protection to 100 logs per hour
2720 - error_log_errors_per_period = 100;
2721 - error_log_throttle_period = 3600;
2722 -
2723 - log_set_global_severity_for_external_plugins();
2724 -
2725 - netdata_configured_host_prefix = getenv("NETDATA_HOST_PREFIX");
2726 - if(verify_netdata_host_prefix() == -1) exit(1);
2727 -
2728 - // ------------------------------------------------------------------------
2729 - // setup the journal directories
2730 -
2731 - unsigned d = 0;
2732 -
2733 - journal_directories[d++].path = strdupz("/var/log/journal");
2734 - journal_directories[d++].path = strdupz("/run/log/journal");
2735 -
2736 - if(*netdata_configured_host_prefix) {
2737 - char path[PATH_MAX];
2738 - snprintfz(path, sizeof(path), "%s/var/log/journal", netdata_configured_host_prefix);
2739 - journal_directories[d++].path = strdupz(path);
2740 - snprintfz(path, sizeof(path), "%s/run/log/journal", netdata_configured_host_prefix);
2741 - journal_directories[d++].path = strdupz(path);
2742 - }
2743 -
2744 - // terminate the list
2745 - journal_directories[d].path = NULL;
2746 -
2747 - // ------------------------------------------------------------------------
2748 -
1557 +void journal_init_query_status(void) {
1558 function_query_status_dict = dictionary_create_advanced(
1559 DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
1560 NULL, sizeof(FUNCTION_QUERY_STATUS));
2752 -
2753 - // ------------------------------------------------------------------------
2754 - // initialize the used hashes files registry
2755 -
2756 - used_hashes_registry = dictionary_create(DICT_OPTION_DONT_OVERWRITE_VALUE);
2757 -
2758 -
2759 - // ------------------------------------------------------------------------
2760 - // initialize the journal files registry
2761 -
2762 - systemd_journal_session = (now_realtime_usec() / USEC_PER_SEC) * USEC_PER_SEC;
2763 -
2764 - journal_files_registry = dictionary_create_advanced(
2765 - DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
2766 - NULL, sizeof(struct journal_file));
2767 -
2768 - dictionary_register_insert_callback(journal_files_registry, files_registry_insert_cb, NULL);
2769 - dictionary_register_delete_callback(journal_files_registry, files_registry_delete_cb, NULL);
2770 - dictionary_register_conflict_callback(journal_files_registry, files_registry_conflict_cb, NULL);
2771 -
2772 - boot_ids_to_first_ut = dictionary_create_advanced(
2773 - DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
2774 - NULL, sizeof(usec_t));
2775 -
2776 - journal_files_registry_update();
2777 -
2778 -
2779 - // ------------------------------------------------------------------------
2780 - // debug
2781 -
2782 - if(argc == 2 && strcmp(argv[1], "debug") == 0) {
2783 - bool cancelled = false;
2784 - char buf[] = "systemd-journal after:-16000000 before:0 last:1";
2785 - // char buf[] = "systemd-journal after:1695332964 before:1695937764 direction:backward last:100 slice:true source:all DHKucpqUoe1:PtVoyIuX.MU";
2786 - // char buf[] = "systemd-journal after:1694511062 before:1694514662 anchor:1694514122024403";
2787 - function_systemd_journal("123", buf, 600, &cancelled);
2788 - exit(1);
2789 - }
2790 -
2791 - // ------------------------------------------------------------------------
2792 - // the event loop for functions
2793 -
2794 - struct functions_evloop_globals *wg =
2795 - functions_evloop_init(SYSTEMD_JOURNAL_WORKER_THREADS, "SDJ", &stdout_mutex, &plugin_should_exit);
2796 -
2797 - functions_evloop_add_function(wg, SYSTEMD_JOURNAL_FUNCTION_NAME, function_systemd_journal,
2798 - SYSTEMD_JOURNAL_DEFAULT_TIMEOUT);
2799 -
2800 -
2801 - // ------------------------------------------------------------------------
2802 -
2803 - time_t started_t = now_monotonic_sec();
2804 -
2805 - size_t iteration = 0;
2806 - usec_t step = 1000 * USEC_PER_MS;
2807 - bool tty = isatty(fileno(stderr)) == 1;
2808 -
2809 - netdata_mutex_lock(&stdout_mutex);
2810 - fprintf(stdout, PLUGINSD_KEYWORD_FUNCTION " GLOBAL \"%s\" %d \"%s\"\n",
2811 - SYSTEMD_JOURNAL_FUNCTION_NAME, SYSTEMD_JOURNAL_DEFAULT_TIMEOUT, SYSTEMD_JOURNAL_FUNCTION_DESCRIPTION);
2812 -
2813 - heartbeat_t hb;
2814 - heartbeat_init(&hb);
2815 - while(!plugin_should_exit) {
2816 - iteration++;
2817 -
2818 - netdata_mutex_unlock(&stdout_mutex);
2819 - heartbeat_next(&hb, step);
2820 - netdata_mutex_lock(&stdout_mutex);
2821 -
2822 - if(!tty)
2823 - fprintf(stdout, "\n");
2824 -
2825 - if(iteration % 60 == 0)
2826 - journal_files_registry_update();
2827 -
2828 - fflush(stdout);
2829 -
2830 - time_t now = now_monotonic_sec();
2831 - if(now - started_t > 86400)
2832 - break;
2833 - }
2834 -
2835 - exit(0);
1561 }
collectors/systemd-journal.plugin/systemd-main.c new
+103
@@ -0,0 +1,103 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "systemd-internals.h"
4 +#include "libnetdata/required_dummies.h"
5 +
6 +#define SYSTEMD_JOURNAL_WORKER_THREADS 5
7 +
8 +netdata_mutex_t stdout_mutex = NETDATA_MUTEX_INITIALIZER;
9 +static bool plugin_should_exit = false;
10 +
11 +int main(int argc __maybe_unused, char **argv __maybe_unused) {
12 + stderror = stderr;
13 + clocks_init();
14 +
15 + program_name = "systemd-journal.plugin";
16 +
17 + // disable syslog
18 + error_log_syslog = 0;
19 +
20 + // set errors flood protection to 100 logs per hour
21 + error_log_errors_per_period = 100;
22 + error_log_throttle_period = 3600;
23 +
24 + log_set_global_severity_for_external_plugins();
25 +
26 + netdata_configured_host_prefix = getenv("NETDATA_HOST_PREFIX");
27 + if(verify_netdata_host_prefix() == -1) exit(1);
28 +
29 + // ------------------------------------------------------------------------
30 + // initialization
31 +
32 + journal_init_query_status();
33 + journal_init_files_and_directories();
34 +
35 + // ------------------------------------------------------------------------
36 + // debug
37 +
38 + if(argc == 2 && strcmp(argv[1], "debug") == 0) {
39 + bool cancelled = false;
40 + char buf[] = "systemd-journal after:-16000000 before:0 last:1";
41 + // char buf[] = "systemd-journal after:1695332964 before:1695937764 direction:backward last:100 slice:true source:all DHKucpqUoe1:PtVoyIuX.MU";
42 + // char buf[] = "systemd-journal after:1694511062 before:1694514662 anchor:1694514122024403";
43 + function_systemd_journal("123", buf, 600, &cancelled);
44 +// function_systemd_units("123", "systemd-units", 600, &cancelled);
45 + exit(1);
46 + }
47 +
48 + // ------------------------------------------------------------------------
49 + // the event loop for functions
50 +
51 + struct functions_evloop_globals *wg =
52 + functions_evloop_init(SYSTEMD_JOURNAL_WORKER_THREADS, "SDJ", &stdout_mutex, &plugin_should_exit);
53 +
54 + functions_evloop_add_function(wg, SYSTEMD_JOURNAL_FUNCTION_NAME, function_systemd_journal,
55 + SYSTEMD_JOURNAL_DEFAULT_TIMEOUT);
56 +
57 +#ifdef ENABLE_SYSTEMD_DBUS
58 + functions_evloop_add_function(wg, SYSTEMD_UNITS_FUNCTION_NAME, function_systemd_units,
59 + SYSTEMD_UNITS_DEFAULT_TIMEOUT);
60 +#endif
61 +
62 + // ------------------------------------------------------------------------
63 +
64 + time_t started_t = now_monotonic_sec();
65 +
66 + size_t iteration = 0;
67 + usec_t step = 1000 * USEC_PER_MS;
68 + bool tty = isatty(fileno(stderr)) == 1;
69 +
70 + netdata_mutex_lock(&stdout_mutex);
71 +
72 + fprintf(stdout, PLUGINSD_KEYWORD_FUNCTION " GLOBAL \"%s\" %d \"%s\"\n",
73 + SYSTEMD_JOURNAL_FUNCTION_NAME, SYSTEMD_JOURNAL_DEFAULT_TIMEOUT, SYSTEMD_JOURNAL_FUNCTION_DESCRIPTION);
74 +
75 +#ifdef ENABLE_SYSTEMD_DBUS
76 + fprintf(stdout, PLUGINSD_KEYWORD_FUNCTION " GLOBAL \"%s\" %d \"%s\"\n",
77 + SYSTEMD_UNITS_FUNCTION_NAME, SYSTEMD_UNITS_DEFAULT_TIMEOUT, SYSTEMD_UNITS_FUNCTION_DESCRIPTION);
78 +#endif
79 +
80 + heartbeat_t hb;
81 + heartbeat_init(&hb);
82 + while(!plugin_should_exit) {
83 + iteration++;
84 +
85 + netdata_mutex_unlock(&stdout_mutex);
86 + heartbeat_next(&hb, step);
87 + netdata_mutex_lock(&stdout_mutex);
88 +
89 + if(!tty)
90 + fprintf(stdout, "\n");
91 +
92 + if(iteration % 60 == 0)
93 + journal_files_registry_update();
94 +
95 + fflush(stdout);
96 +
97 + time_t now = now_monotonic_sec();
98 + if(now - started_t > 86400)
99 + break;
100 + }
101 +
102 + exit(0);
103 +}
collectors/systemd-journal.plugin/systemd-units.c new
+1392
@@ -0,0 +1,1392 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "systemd-internals.h"
4 +
5 +#ifdef ENABLE_SYSTEMD_DBUS
6 +#include <systemd/sd-bus.h>
7 +
8 +#define SYSTEMD_UNITS_MAX_PARAMS 10
9 +#define SYSTEMD_UNITS_DBUS_TYPES "(ssssssouso)"
10 +
11 +// ----------------------------------------------------------------------------
12 +// copied from systemd: string-table.h
13 +
14 +typedef char sd_char;
15 +#define XCONCATENATE(x, y) x ## y
16 +#define CONCATENATE(x, y) XCONCATENATE(x, y)
17 +
18 +#ifndef __COVERITY__
19 +# define VOID_0 ((void)0)
20 +#else
21 +# define VOID_0 ((void*)0)
22 +#endif
23 +
24 +#define ELEMENTSOF(x) \
25 + (__builtin_choose_expr( \
26 + !__builtin_types_compatible_p(typeof(x), typeof(&*(x))), \
27 + sizeof(x)/sizeof((x)[0]), \
28 + VOID_0))
29 +
30 +#define UNIQ_T(x, uniq) CONCATENATE(__unique_prefix_, CONCATENATE(x, uniq))
31 +#define UNIQ __COUNTER__
32 +#define __CMP(aq, a, bq, b) \
33 + ({ \
34 + const typeof(a) UNIQ_T(A, aq) = (a); \
35 + const typeof(b) UNIQ_T(B, bq) = (b); \
36 + UNIQ_T(A, aq) < UNIQ_T(B, bq) ? -1 : \
37 + UNIQ_T(A, aq) > UNIQ_T(B, bq) ? 1 : 0; \
38 + })
39 +#define CMP(a, b) __CMP(UNIQ, (a), UNIQ, (b))
40 +
41 +static inline int strcmp_ptr(const sd_char *a, const sd_char *b) {
42 + if (a && b)
43 + return strcmp(a, b);
44 +
45 + return CMP(a, b);
46 +}
47 +
48 +static inline bool streq_ptr(const sd_char *a, const sd_char *b) {
49 + return strcmp_ptr(a, b) == 0;
50 +}
51 +
52 +ssize_t string_table_lookup(const char * const *table, size_t len, const char *key) {
53 + if (!key)
54 + return -EINVAL;
55 +
56 + for (size_t i = 0; i < len; ++i)
57 + if (streq_ptr(table[i], key))
58 + return (ssize_t) i;
59 +
60 + return -EINVAL;
61 +}
62 +
63 +/* For basic lookup tables with strictly enumerated entries */
64 +#define _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,scope) \
65 + scope const char *name##_to_string(type i) { \
66 + if (i < 0 || i >= (type) ELEMENTSOF(name##_table)) \
67 + return NULL; \
68 + return name##_table[i]; \
69 + }
70 +
71 +#define _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING(name,type,scope) \
72 + scope type name##_from_string(const char *s) { \
73 + return (type) string_table_lookup(name##_table, ELEMENTSOF(name##_table), s); \
74 + }
75 +
76 +#define _DEFINE_STRING_TABLE_LOOKUP(name,type,scope) \
77 + _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,scope) \
78 + _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING(name,type,scope)
79 +
80 +#define DEFINE_STRING_TABLE_LOOKUP(name,type) _DEFINE_STRING_TABLE_LOOKUP(name,type,)
81 +
82 +// ----------------------------------------------------------------------------
83 +// copied from systemd: unit-def.h
84 +
85 +typedef enum UnitType {
86 + UNIT_SERVICE,
87 + UNIT_MOUNT,
88 + UNIT_SWAP,
89 + UNIT_SOCKET,
90 + UNIT_TARGET,
91 + UNIT_DEVICE,
92 + UNIT_AUTOMOUNT,
93 + UNIT_TIMER,
94 + UNIT_PATH,
95 + UNIT_SLICE,
96 + UNIT_SCOPE,
97 + _UNIT_TYPE_MAX,
98 + _UNIT_TYPE_INVALID = -EINVAL,
99 +} UnitType;
100 +
101 +typedef enum UnitLoadState {
102 + UNIT_STUB,
103 + UNIT_LOADED,
104 + UNIT_NOT_FOUND, /* error condition #1: unit file not found */
105 + UNIT_BAD_SETTING, /* error condition #2: we couldn't parse some essential unit file setting */
106 + UNIT_ERROR, /* error condition #3: other "system" error, catchall for the rest */
107 + UNIT_MERGED,
108 + UNIT_MASKED,
109 + _UNIT_LOAD_STATE_MAX,
110 + _UNIT_LOAD_STATE_INVALID = -EINVAL,
111 +} UnitLoadState;
112 +
113 +typedef enum UnitActiveState {
114 + UNIT_ACTIVE,
115 + UNIT_RELOADING,
116 + UNIT_INACTIVE,
117 + UNIT_FAILED,
118 + UNIT_ACTIVATING,
119 + UNIT_DEACTIVATING,
120 + UNIT_MAINTENANCE,
121 + _UNIT_ACTIVE_STATE_MAX,
122 + _UNIT_ACTIVE_STATE_INVALID = -EINVAL,
123 +} UnitActiveState;
124 +
125 +typedef enum AutomountState {
126 + AUTOMOUNT_DEAD,
127 + AUTOMOUNT_WAITING,
128 + AUTOMOUNT_RUNNING,
129 + AUTOMOUNT_FAILED,
130 + _AUTOMOUNT_STATE_MAX,
131 + _AUTOMOUNT_STATE_INVALID = -EINVAL,
132 +} AutomountState;
133 +
134 +typedef enum DeviceState {
135 + DEVICE_DEAD,
136 + DEVICE_TENTATIVE, /* mounted or swapped, but not (yet) announced by udev */
137 + DEVICE_PLUGGED, /* announced by udev */
138 + _DEVICE_STATE_MAX,
139 + _DEVICE_STATE_INVALID = -EINVAL,
140 +} DeviceState;
141 +
142 +typedef enum MountState {
143 + MOUNT_DEAD,
144 + MOUNT_MOUNTING, /* /usr/bin/mount is running, but the mount is not done yet. */
145 + MOUNT_MOUNTING_DONE, /* /usr/bin/mount is running, and the mount is done. */
146 + MOUNT_MOUNTED,
147 + MOUNT_REMOUNTING,
148 + MOUNT_UNMOUNTING,
149 + MOUNT_REMOUNTING_SIGTERM,
150 + MOUNT_REMOUNTING_SIGKILL,
151 + MOUNT_UNMOUNTING_SIGTERM,
152 + MOUNT_UNMOUNTING_SIGKILL,
153 + MOUNT_FAILED,
154 + MOUNT_CLEANING,
155 + _MOUNT_STATE_MAX,
156 + _MOUNT_STATE_INVALID = -EINVAL,
157 +} MountState;
158 +
159 +typedef enum PathState {
160 + PATH_DEAD,
161 + PATH_WAITING,
162 + PATH_RUNNING,
163 + PATH_FAILED,
164 + _PATH_STATE_MAX,
165 + _PATH_STATE_INVALID = -EINVAL,
166 +} PathState;
167 +
168 +typedef enum ScopeState {
169 + SCOPE_DEAD,
170 + SCOPE_START_CHOWN,
171 + SCOPE_RUNNING,
172 + SCOPE_ABANDONED,
173 + SCOPE_STOP_SIGTERM,
174 + SCOPE_STOP_SIGKILL,
175 + SCOPE_FAILED,
176 + _SCOPE_STATE_MAX,
177 + _SCOPE_STATE_INVALID = -EINVAL,
178 +} ScopeState;
179 +
180 +typedef enum ServiceState {
181 + SERVICE_DEAD,
182 + SERVICE_CONDITION,
183 + SERVICE_START_PRE,
184 + SERVICE_START,
185 + SERVICE_START_POST,
186 + SERVICE_RUNNING,
187 + SERVICE_EXITED, /* Nothing is running anymore, but RemainAfterExit is true hence this is OK */
188 + SERVICE_RELOAD, /* Reloading via ExecReload= */
189 + SERVICE_RELOAD_SIGNAL, /* Reloading via SIGHUP requested */
190 + SERVICE_RELOAD_NOTIFY, /* Waiting for READY=1 after RELOADING=1 notify */
191 + SERVICE_STOP, /* No STOP_PRE state, instead just register multiple STOP executables */
192 + SERVICE_STOP_WATCHDOG,
193 + SERVICE_STOP_SIGTERM,
194 + SERVICE_STOP_SIGKILL,
195 + SERVICE_STOP_POST,
196 + SERVICE_FINAL_WATCHDOG, /* In case the STOP_POST executable needs to be aborted. */
197 + SERVICE_FINAL_SIGTERM, /* In case the STOP_POST executable hangs, we shoot that down, too */
198 + SERVICE_FINAL_SIGKILL,
199 + SERVICE_FAILED,
200 + SERVICE_DEAD_BEFORE_AUTO_RESTART,
201 + SERVICE_FAILED_BEFORE_AUTO_RESTART,
202 + SERVICE_DEAD_RESOURCES_PINNED, /* Like SERVICE_DEAD, but with pinned resources */
203 + SERVICE_AUTO_RESTART,
204 + SERVICE_AUTO_RESTART_QUEUED,
205 + SERVICE_CLEANING,
206 + _SERVICE_STATE_MAX,
207 + _SERVICE_STATE_INVALID = -EINVAL,
208 +} ServiceState;
209 +
210 +typedef enum SliceState {
211 + SLICE_DEAD,
212 + SLICE_ACTIVE,
213 + _SLICE_STATE_MAX,
214 + _SLICE_STATE_INVALID = -EINVAL,
215 +} SliceState;
216 +
217 +typedef enum SocketState {
218 + SOCKET_DEAD,
219 + SOCKET_START_PRE,
220 + SOCKET_START_CHOWN,
221 + SOCKET_START_POST,
222 + SOCKET_LISTENING,
223 + SOCKET_RUNNING,
224 + SOCKET_STOP_PRE,
225 + SOCKET_STOP_PRE_SIGTERM,
226 + SOCKET_STOP_PRE_SIGKILL,
227 + SOCKET_STOP_POST,
228 + SOCKET_FINAL_SIGTERM,
229 + SOCKET_FINAL_SIGKILL,
230 + SOCKET_FAILED,
231 + SOCKET_CLEANING,
232 + _SOCKET_STATE_MAX,
233 + _SOCKET_STATE_INVALID = -EINVAL,
234 +} SocketState;
235 +
236 +typedef enum SwapState {
237 + SWAP_DEAD,
238 + SWAP_ACTIVATING, /* /sbin/swapon is running, but the swap not yet enabled. */
239 + SWAP_ACTIVATING_DONE, /* /sbin/swapon is running, and the swap is done. */
240 + SWAP_ACTIVE,
241 + SWAP_DEACTIVATING,
242 + SWAP_DEACTIVATING_SIGTERM,
243 + SWAP_DEACTIVATING_SIGKILL,
244 + SWAP_FAILED,
245 + SWAP_CLEANING,
246 + _SWAP_STATE_MAX,
247 + _SWAP_STATE_INVALID = -EINVAL,
248 +} SwapState;
249 +
250 +typedef enum TargetState {
251 + TARGET_DEAD,
252 + TARGET_ACTIVE,
253 + _TARGET_STATE_MAX,
254 + _TARGET_STATE_INVALID = -EINVAL,
255 +} TargetState;
256 +
257 +typedef enum TimerState {
258 + TIMER_DEAD,
259 + TIMER_WAITING,
260 + TIMER_RUNNING,
261 + TIMER_ELAPSED,
262 + TIMER_FAILED,
263 + _TIMER_STATE_MAX,
264 + _TIMER_STATE_INVALID = -EINVAL,
265 +} TimerState;
266 +
267 +// ----------------------------------------------------------------------------
268 +// copied from systemd: unit-def.c
269 +
270 +static const char* const unit_type_table[_UNIT_TYPE_MAX] = {
271 + [UNIT_SERVICE] = "service",
272 + [UNIT_SOCKET] = "socket",
273 + [UNIT_TARGET] = "target",
274 + [UNIT_DEVICE] = "device",
275 + [UNIT_MOUNT] = "mount",
276 + [UNIT_AUTOMOUNT] = "automount",
277 + [UNIT_SWAP] = "swap",
278 + [UNIT_TIMER] = "timer",
279 + [UNIT_PATH] = "path",
280 + [UNIT_SLICE] = "slice",
281 + [UNIT_SCOPE] = "scope",
282 +};
283 +
284 +DEFINE_STRING_TABLE_LOOKUP(unit_type, UnitType);
285 +
286 +static const char* const unit_load_state_table[_UNIT_LOAD_STATE_MAX] = {
287 + [UNIT_STUB] = "stub",
288 + [UNIT_LOADED] = "loaded",
289 + [UNIT_NOT_FOUND] = "not-found",
290 + [UNIT_BAD_SETTING] = "bad-setting",
291 + [UNIT_ERROR] = "error",
292 + [UNIT_MERGED] = "merged",
293 + [UNIT_MASKED] = "masked"
294 +};
295 +
296 +DEFINE_STRING_TABLE_LOOKUP(unit_load_state, UnitLoadState);
297 +
298 +static const char* const unit_active_state_table[_UNIT_ACTIVE_STATE_MAX] = {
299 + [UNIT_ACTIVE] = "active",
300 + [UNIT_RELOADING] = "reloading",
301 + [UNIT_INACTIVE] = "inactive",
302 + [UNIT_FAILED] = "failed",
303 + [UNIT_ACTIVATING] = "activating",
304 + [UNIT_DEACTIVATING] = "deactivating",
305 + [UNIT_MAINTENANCE] = "maintenance",
306 +};
307 +
308 +DEFINE_STRING_TABLE_LOOKUP(unit_active_state, UnitActiveState);
309 +
310 +static const char* const automount_state_table[_AUTOMOUNT_STATE_MAX] = {
311 + [AUTOMOUNT_DEAD] = "dead",
312 + [AUTOMOUNT_WAITING] = "waiting",
313 + [AUTOMOUNT_RUNNING] = "running",
314 + [AUTOMOUNT_FAILED] = "failed"
315 +};
316 +
317 +DEFINE_STRING_TABLE_LOOKUP(automount_state, AutomountState);
318 +
319 +static const char* const device_state_table[_DEVICE_STATE_MAX] = {
320 + [DEVICE_DEAD] = "dead",
321 + [DEVICE_TENTATIVE] = "tentative",
322 + [DEVICE_PLUGGED] = "plugged",
323 +};
324 +
325 +DEFINE_STRING_TABLE_LOOKUP(device_state, DeviceState);
326 +
327 +static const char* const mount_state_table[_MOUNT_STATE_MAX] = {
328 + [MOUNT_DEAD] = "dead",
329 + [MOUNT_MOUNTING] = "mounting",
330 + [MOUNT_MOUNTING_DONE] = "mounting-done",
331 + [MOUNT_MOUNTED] = "mounted",
332 + [MOUNT_REMOUNTING] = "remounting",
333 + [MOUNT_UNMOUNTING] = "unmounting",
334 + [MOUNT_REMOUNTING_SIGTERM] = "remounting-sigterm",
335 + [MOUNT_REMOUNTING_SIGKILL] = "remounting-sigkill",
336 + [MOUNT_UNMOUNTING_SIGTERM] = "unmounting-sigterm",
337 + [MOUNT_UNMOUNTING_SIGKILL] = "unmounting-sigkill",
338 + [MOUNT_FAILED] = "failed",
339 + [MOUNT_CLEANING] = "cleaning",
340 +};
341 +
342 +DEFINE_STRING_TABLE_LOOKUP(mount_state, MountState);
343 +
344 +static const char* const path_state_table[_PATH_STATE_MAX] = {
345 + [PATH_DEAD] = "dead",
346 + [PATH_WAITING] = "waiting",
347 + [PATH_RUNNING] = "running",
348 + [PATH_FAILED] = "failed"
349 +};
350 +
351 +DEFINE_STRING_TABLE_LOOKUP(path_state, PathState);
352 +
353 +static const char* const scope_state_table[_SCOPE_STATE_MAX] = {
354 + [SCOPE_DEAD] = "dead",
355 + [SCOPE_START_CHOWN] = "start-chown",
356 + [SCOPE_RUNNING] = "running",
357 + [SCOPE_ABANDONED] = "abandoned",
358 + [SCOPE_STOP_SIGTERM] = "stop-sigterm",
359 + [SCOPE_STOP_SIGKILL] = "stop-sigkill",
360 + [SCOPE_FAILED] = "failed",
361 +};
362 +
363 +DEFINE_STRING_TABLE_LOOKUP(scope_state, ScopeState);
364 +
365 +static const char* const service_state_table[_SERVICE_STATE_MAX] = {
366 + [SERVICE_DEAD] = "dead",
367 + [SERVICE_CONDITION] = "condition",
368 + [SERVICE_START_PRE] = "start-pre",
369 + [SERVICE_START] = "start",
370 + [SERVICE_START_POST] = "start-post",
371 + [SERVICE_RUNNING] = "running",
372 + [SERVICE_EXITED] = "exited",
373 + [SERVICE_RELOAD] = "reload",
374 + [SERVICE_RELOAD_SIGNAL] = "reload-signal",
375 + [SERVICE_RELOAD_NOTIFY] = "reload-notify",
376 + [SERVICE_STOP] = "stop",
377 + [SERVICE_STOP_WATCHDOG] = "stop-watchdog",
378 + [SERVICE_STOP_SIGTERM] = "stop-sigterm",
379 + [SERVICE_STOP_SIGKILL] = "stop-sigkill",
380 + [SERVICE_STOP_POST] = "stop-post",
381 + [SERVICE_FINAL_WATCHDOG] = "final-watchdog",
382 + [SERVICE_FINAL_SIGTERM] = "final-sigterm",
383 + [SERVICE_FINAL_SIGKILL] = "final-sigkill",
384 + [SERVICE_FAILED] = "failed",
385 + [SERVICE_DEAD_BEFORE_AUTO_RESTART] = "dead-before-auto-restart",
386 + [SERVICE_FAILED_BEFORE_AUTO_RESTART] = "failed-before-auto-restart",
387 + [SERVICE_DEAD_RESOURCES_PINNED] = "dead-resources-pinned",
388 + [SERVICE_AUTO_RESTART] = "auto-restart",
389 + [SERVICE_AUTO_RESTART_QUEUED] = "auto-restart-queued",
390 + [SERVICE_CLEANING] = "cleaning",
391 +};
392 +
393 +DEFINE_STRING_TABLE_LOOKUP(service_state, ServiceState);
394 +
395 +static const char* const slice_state_table[_SLICE_STATE_MAX] = {
396 + [SLICE_DEAD] = "dead",
397 + [SLICE_ACTIVE] = "active"
398 +};
399 +
400 +DEFINE_STRING_TABLE_LOOKUP(slice_state, SliceState);
401 +
402 +static const char* const socket_state_table[_SOCKET_STATE_MAX] = {
403 + [SOCKET_DEAD] = "dead",
404 + [SOCKET_START_PRE] = "start-pre",
405 + [SOCKET_START_CHOWN] = "start-chown",
406 + [SOCKET_START_POST] = "start-post",
407 + [SOCKET_LISTENING] = "listening",
408 + [SOCKET_RUNNING] = "running",
409 + [SOCKET_STOP_PRE] = "stop-pre",
410 + [SOCKET_STOP_PRE_SIGTERM] = "stop-pre-sigterm",
411 + [SOCKET_STOP_PRE_SIGKILL] = "stop-pre-sigkill",
412 + [SOCKET_STOP_POST] = "stop-post",
413 + [SOCKET_FINAL_SIGTERM] = "final-sigterm",
414 + [SOCKET_FINAL_SIGKILL] = "final-sigkill",
415 + [SOCKET_FAILED] = "failed",
416 + [SOCKET_CLEANING] = "cleaning",
417 +};
418 +
419 +DEFINE_STRING_TABLE_LOOKUP(socket_state, SocketState);
420 +
421 +static const char* const swap_state_table[_SWAP_STATE_MAX] = {
422 + [SWAP_DEAD] = "dead",
423 + [SWAP_ACTIVATING] = "activating",
424 + [SWAP_ACTIVATING_DONE] = "activating-done",
425 + [SWAP_ACTIVE] = "active",
426 + [SWAP_DEACTIVATING] = "deactivating",
427 + [SWAP_DEACTIVATING_SIGTERM] = "deactivating-sigterm",
428 + [SWAP_DEACTIVATING_SIGKILL] = "deactivating-sigkill",
429 + [SWAP_FAILED] = "failed",
430 + [SWAP_CLEANING] = "cleaning",
431 +};
432 +
433 +DEFINE_STRING_TABLE_LOOKUP(swap_state, SwapState);
434 +
435 +static const char* const target_state_table[_TARGET_STATE_MAX] = {
436 + [TARGET_DEAD] = "dead",
437 + [TARGET_ACTIVE] = "active"
438 +};
439 +
440 +DEFINE_STRING_TABLE_LOOKUP(target_state, TargetState);
441 +
442 +static const char* const timer_state_table[_TIMER_STATE_MAX] = {
443 + [TIMER_DEAD] = "dead",
444 + [TIMER_WAITING] = "waiting",
445 + [TIMER_RUNNING] = "running",
446 + [TIMER_ELAPSED] = "elapsed",
447 + [TIMER_FAILED] = "failed"
448 +};
449 +
450 +DEFINE_STRING_TABLE_LOOKUP(timer_state, TimerState);
451 +
452 +typedef struct UnitInfo {
453 + char *id;
454 + char *type;
455 + char *description;
456 + char *load_state;
457 + char *active_state;
458 + char *sub_state;
459 + char *following;
460 + char *unit_path;
461 + uint32_t job_id;
462 + char *job_type;
463 + char *job_path;
464 +
465 + UnitType UnitType;
466 + UnitLoadState UnitLoadState;
467 + UnitActiveState UnitActiveState;
468 +
469 + union {
470 + AutomountState AutomountState;
471 + DeviceState DeviceState;
472 + MountState MountState;
473 + PathState PathState;
474 + ScopeState ScopeState;
475 + ServiceState ServiceState;
476 + SliceState SliceState;
477 + SocketState SocketState;
478 + SwapState SwapState;
479 + TargetState TargetState;
480 + TimerState TimerState;
481 + };
482 +
483 + FACET_ROW_SEVERITY severity;
484 + uint32_t prio;
485 +
486 + struct UnitInfo *prev, *next;
487 +} UnitInfo;
488 +
489 +int bus_parse_unit_info(sd_bus_message *message, UnitInfo *u) {
490 + assert(message);
491 + assert(u);
492 +
493 + u->type = NULL;
494 +
495 + int r = sd_bus_message_read(
496 + message,
497 + SYSTEMD_UNITS_DBUS_TYPES,
498 + &u->id,
499 + &u->description,
500 + &u->load_state,
501 + &u->active_state,
502 + &u->sub_state,
503 + &u->following,
504 + &u->unit_path,
505 + &u->job_id,
506 + &u->job_type,
507 + &u->job_path);
508 +
509 + if(r <= 0)
510 + return r;
511 +
512 + char *dot;
513 + if(u->id && (dot = strrchr(u->id, '.')) != NULL)
514 + u->type = &dot[1];
515 + else
516 + u->type = "unknown";
517 +
518 + u->UnitType = unit_type_from_string(u->type);
519 + u->UnitLoadState = unit_load_state_from_string(u->load_state);
520 + u->UnitActiveState = unit_active_state_from_string(u->active_state);
521 +
522 + switch(u->UnitType) {
523 + case UNIT_SERVICE:
524 + u->ServiceState = service_state_from_string(u->sub_state);
525 + break;
526 +
527 + case UNIT_MOUNT:
528 + u->MountState = mount_state_from_string(u->sub_state);
529 + break;
530 +
531 + case UNIT_SWAP:
532 + u->SwapState = swap_state_from_string(u->sub_state);
533 + break;
534 +
535 + case UNIT_SOCKET:
536 + u->SocketState = socket_state_from_string(u->sub_state);
537 + break;
538 +
539 + case UNIT_TARGET:
540 + u->TargetState = target_state_from_string(u->sub_state);
541 + break;
542 +
543 + case UNIT_DEVICE:
544 + u->DeviceState = device_state_from_string(u->sub_state);
545 + break;
546 +
547 + case UNIT_AUTOMOUNT:
548 + u->AutomountState = automount_state_from_string(u->sub_state);
549 + break;
550 +
551 + case UNIT_TIMER:
552 + u->TimerState = timer_state_from_string(u->sub_state);
553 + break;
554 +
555 + case UNIT_PATH:
556 + u->PathState = path_state_from_string(u->sub_state);
557 + break;
558 +
559 + case UNIT_SLICE:
560 + u->SliceState = slice_state_from_string(u->sub_state);
561 + break;
562 +
563 + case UNIT_SCOPE:
564 + u->ScopeState = scope_state_from_string(u->sub_state);
565 + break;
566 +
567 + default:
568 + break;
569 + }
570 +
571 + return r;
572 +}
573 +
574 +static void log_dbus_error(int r, const char *msg) {
575 + netdata_log_error("SYSTEMD_UNITS: %s failed with error %d", msg, r);
576 +}
577 +
578 +static int hex_to_int(char c) {
579 + if (c >= '0' && c <= '9') return c - '0';
580 + if (c >= 'a' && c <= 'f') return c - 'a' + 10;
581 + if (c >= 'A' && c <= 'F') return c - 'A' + 10;
582 + return 0;
583 +}
584 +
585 +// un-escape hex sequences (\xNN) in id
586 +static void txt_decode(char *txt) {
587 + if(!txt || !*txt)
588 + return;
589 +
590 + char *src = txt, *dst = txt;
591 +
592 + size_t id_len = strlen(src);
593 + size_t s = 0, d = 0;
594 + for(; s < id_len ; s++) {
595 + if(src[s] == '\\' && src[s + 1] == 'x' && isxdigit(src[s + 2]) && isxdigit(src[s + 3])) {
596 + int value = (hex_to_int(src[s + 2]) << 4) + hex_to_int(src[s + 3]);
597 + dst[d++] = (char)value;
598 + s += 3;
599 + }
600 + else
601 + dst[d++] = src[s];
602 + }
603 + dst[d] = '\0';
604 +}
605 +
606 +static UnitInfo *systemd_units_get_all(void) {
607 + sd_bus *bus = NULL;
608 + sd_bus_error error = SD_BUS_ERROR_NULL;
609 + sd_bus_message *reply = NULL;
610 + UnitInfo *base = NULL;
611 + int r;
612 +
613 + r = sd_bus_default_system(&bus);
614 + if (r < 0) {
615 + log_dbus_error(r, "sd_bus_default_system()");
616 + return base;
617 + }
618 +
619 + // This calls the ListUnits method of the org.freedesktop.systemd1.Manager interface
620 + // Replace "ListUnits" with "ListUnitsFiltered" to get specific units based on filters
621 + r = sd_bus_call_method(bus,
622 + "org.freedesktop.systemd1", /* service to contact */
623 + "/org/freedesktop/systemd1", /* object path */
624 + "org.freedesktop.systemd1.Manager", /* interface name */
625 + "ListUnits", /* method name */
626 + &error, /* object to return error in */
627 + &reply, /* return message on success */
628 + NULL); /* input signature */
629 + if (r < 0) {
630 + log_dbus_error(r, "sd_bus_call_method()");
631 + return base;
632 + }
633 +
634 + r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, SYSTEMD_UNITS_DBUS_TYPES);
635 + if (r < 0) {
636 + log_dbus_error(r, "sd_bus_message_enter_container()");
637 + return base;
638 + }
639 +
640 + UnitInfo u;
641 + memset(&u, 0, sizeof(u));
642 + while ((r = bus_parse_unit_info(reply, &u)) > 0) {
643 + UnitInfo *i = callocz(1, sizeof(u));
644 + *i = u;
645 +
646 + i->id = strdupz(u.id && *u.id ? u.id : "-");
647 + txt_decode(i->id);
648 +
649 + i->type = strdupz(u.type && *u.type ? u.type : "-");
650 + i->description = strdupz(u.description && *u.description ? u.description : "-");
651 + txt_decode(i->description);
652 +
653 + i->load_state = strdupz(u.load_state && *u.load_state ? u.load_state : "-");
654 + i->active_state = strdupz(u.active_state && *u.active_state ? u.active_state : "-");
655 + i->sub_state = strdupz(u.sub_state && *u.sub_state ? u.sub_state : "-");
656 + i->following = strdupz(u.following && *u.following ? u.following : "-");
657 + i->unit_path = strdupz(u.unit_path && *u.unit_path ? u.unit_path : "-");
658 + i->job_type = strdupz(u.job_type && *u.job_type ? u.job_type : "-");
659 + i->job_path = strdupz(u.job_path && *u.job_path ? u.job_path : "-");
660 + i->job_id = u.job_id;
661 +
662 + DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(base, i, prev, next);
663 + memset(&u, 0, sizeof(u));
664 + }
665 + if (r < 0) {
666 + log_dbus_error(r, "sd_bus_message_read()");
667 + return base;
668 + }
669 +
670 + r = sd_bus_message_exit_container(reply);
671 + if (r < 0) {
672 + log_dbus_error(r, "sd_bus_message_exit_container()");
673 + return base;
674 + }
675 +
676 + return base;
677 +}
678 +
679 +void systemd_units_free_all(UnitInfo *base) {
680 + while(base) {
681 + UnitInfo *u = base;
682 + DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(base, u, prev, next);
683 + freez((void *)u->id);
684 + freez((void *)u->type);
685 + freez((void *)u->description);
686 + freez((void *)u->load_state);
687 + freez((void *)u->active_state);
688 + freez((void *)u->sub_state);
689 + freez((void *)u->following);
690 + freez((void *)u->unit_path);
691 + freez((void *)u->job_type);
692 + freez((void *)u->job_path);
693 + freez(u);
694 + }
695 +}
696 +
697 +static void netdata_systemd_units_function_help(const char *transaction) {
698 + BUFFER *wb = buffer_create(0, NULL);
699 + buffer_sprintf(wb,
700 + "%s / %s\n"
701 + "\n"
702 + "%s\n"
703 + "\n"
704 + "The following parameters are supported:\n"
705 + "\n"
706 + " help\n"
707 + " Shows this help message.\n"
708 + "\n"
709 + " info\n"
710 + " Request initial configuration information about the plugin.\n"
711 + " The key entity returned is the required_params array, which includes\n"
712 + " all the available systemd journal sources.\n"
713 + " When `info` is requested, all other parameters are ignored.\n"
714 + "\n"
715 + , program_name
716 + , SYSTEMD_UNITS_FUNCTION_NAME
717 + , SYSTEMD_UNITS_FUNCTION_DESCRIPTION
718 + );
719 +
720 + netdata_mutex_lock(&stdout_mutex);
721 + pluginsd_function_result_to_stdout(transaction, HTTP_RESP_OK, "text/plain", now_realtime_sec() + 3600, wb);
722 + netdata_mutex_unlock(&stdout_mutex);
723 +
724 + buffer_free(wb);
725 +}
726 +
727 +static void netdata_systemd_units_function_info(const char *transaction) {
728 + BUFFER *wb = buffer_create(0, NULL);
729 + buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_MINIFY);
730 +
731 + buffer_json_member_add_uint64(wb, "status", HTTP_RESP_OK);
732 + buffer_json_member_add_string(wb, "type", "table");
733 + buffer_json_member_add_string(wb, "help", SYSTEMD_UNITS_FUNCTION_DESCRIPTION);
734 +
735 + buffer_json_finalize(wb);
736 + netdata_mutex_lock(&stdout_mutex);
737 + pluginsd_function_result_to_stdout(transaction, HTTP_RESP_OK, "text/plain", now_realtime_sec() + 3600, wb);
738 + netdata_mutex_unlock(&stdout_mutex);
739 +
740 + buffer_free(wb);
741 +}
742 +
743 +static void systemd_unit_priority(UnitInfo *u, size_t units) {
744 + uint32_t prio;
745 +
746 + switch(u->severity) {
747 + case FACET_ROW_SEVERITY_CRITICAL:
748 + prio = 0;
749 + break;
750 +
751 + default:
752 + case FACET_ROW_SEVERITY_WARNING:
753 + prio = 1;
754 + break;
755 +
756 + case FACET_ROW_SEVERITY_NOTICE:
757 + prio = 2;
758 + break;
759 +
760 + case FACET_ROW_SEVERITY_NORMAL:
761 + prio = 3;
762 + break;
763 +
764 + case FACET_ROW_SEVERITY_DEBUG:
765 + prio = 4;
766 + break;
767 + }
768 +
769 + prio = prio * (uint32_t)(_UNIT_TYPE_MAX + 1) + (uint32_t)u->UnitType;
770 + u->prio = (prio * units) + u->prio;
771 +}
772 +
773 +#define if_less(current, max, target) ({ \
774 + typeof(current) _wanted = (current); \
775 + if((current) < (target)) \
776 + _wanted = (target) > (max) ? (max) : (target); \
777 + _wanted; \
778 +})
779 +
780 +#define if_normal(current, max, target) ({ \
781 + typeof(current) _wanted = (current); \
782 + if((current) == FACET_ROW_SEVERITY_NORMAL) \
783 + _wanted = (target) > (max) ? (max) : (target); \
784 + _wanted; \
785 +})
786 +
787 +FACET_ROW_SEVERITY system_unit_severity(UnitInfo *u) {
788 + FACET_ROW_SEVERITY severity, max_severity;
789 +
790 + switch(u->UnitLoadState) {
791 + case UNIT_ERROR:
792 + case UNIT_BAD_SETTING:
793 + severity = FACET_ROW_SEVERITY_CRITICAL;
794 + max_severity = FACET_ROW_SEVERITY_CRITICAL;
795 + break;
796 +
797 + default:
798 + severity = FACET_ROW_SEVERITY_WARNING;
799 + max_severity = FACET_ROW_SEVERITY_CRITICAL;
800 + break;
801 +
802 + case UNIT_NOT_FOUND:
803 + severity = FACET_ROW_SEVERITY_NOTICE;
804 + max_severity = FACET_ROW_SEVERITY_NOTICE;
805 + break;
806 +
807 + case UNIT_LOADED:
808 + severity = FACET_ROW_SEVERITY_NORMAL;
809 + max_severity = FACET_ROW_SEVERITY_CRITICAL;
810 + break;
811 +
812 + case UNIT_MERGED:
813 + case UNIT_MASKED:
814 + case UNIT_STUB:
815 + severity = FACET_ROW_SEVERITY_DEBUG;
816 + max_severity = FACET_ROW_SEVERITY_DEBUG;
817 + break;
818 + }
819 +
820 + switch(u->UnitActiveState) {
821 + case UNIT_FAILED:
822 + severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_CRITICAL);
823 + break;
824 +
825 + default:
826 + case UNIT_RELOADING:
827 + case UNIT_ACTIVATING:
828 + case UNIT_DEACTIVATING:
829 + severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_WARNING);
830 + break;
831 +
832 + case UNIT_MAINTENANCE:
833 + severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_NOTICE);
834 + break;
835 +
836 + case UNIT_ACTIVE:
837 + break;
838 +
839 + case UNIT_INACTIVE:
840 + severity = if_normal(severity, max_severity, FACET_ROW_SEVERITY_DEBUG);
841 + break;
842 + }
843 +
844 + switch(u->UnitType) {
845 + case UNIT_SERVICE:
846 + switch(u->ServiceState) {
847 + case SERVICE_FAILED:
848 + case SERVICE_FAILED_BEFORE_AUTO_RESTART:
849 + severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_CRITICAL);
850 + break;
851 +
852 + default:
853 + case SERVICE_STOP:
854 + case SERVICE_STOP_WATCHDOG:
855 + case SERVICE_STOP_SIGTERM:
856 + case SERVICE_STOP_SIGKILL:
857 + case SERVICE_STOP_POST:
858 + case SERVICE_FINAL_WATCHDOG:
859 + case SERVICE_FINAL_SIGTERM:
860 + case SERVICE_FINAL_SIGKILL:
861 + case SERVICE_AUTO_RESTART:
862 + case SERVICE_AUTO_RESTART_QUEUED:
863 + severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_WARNING);
864 + break;
865 +
866 + case SERVICE_CONDITION:
867 + case SERVICE_START_PRE:
868 + case SERVICE_START:
869 + case SERVICE_START_POST:
870 + case SERVICE_RELOAD:
871 + case SERVICE_RELOAD_SIGNAL:
872 + case SERVICE_RELOAD_NOTIFY:
873 + case SERVICE_DEAD_RESOURCES_PINNED:
874 + case SERVICE_CLEANING:
875 + severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_NOTICE);
876 + break;
877 +
878 + case SERVICE_EXITED:
879 + case SERVICE_RUNNING:
880 + break;
881 +
882 + case SERVICE_DEAD:
883 + case SERVICE_DEAD_BEFORE_AUTO_RESTART:
884 + severity = if_normal(severity, max_severity, FACET_ROW_SEVERITY_DEBUG);
885 + break;
886 + }
887 + break;
888 +
889 + case UNIT_MOUNT:
890 + switch(u->MountState) {
891 + case MOUNT_FAILED:
892 + severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_CRITICAL);
893 + break;
894 +
895 + default:
896 + case MOUNT_REMOUNTING_SIGTERM:
897 + case MOUNT_REMOUNTING_SIGKILL:
898 + case MOUNT_UNMOUNTING_SIGTERM:
899 + case MOUNT_UNMOUNTING_SIGKILL:
900 + severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_WARNING);
901 + break;
902 +
903 + case MOUNT_MOUNTING:
904 + case MOUNT_MOUNTING_DONE:
905 + case MOUNT_REMOUNTING:
906 + case MOUNT_UNMOUNTING:
907 + case MOUNT_CLEANING:
908 + severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_NOTICE);
909 + break;
910 +
911 + case MOUNT_MOUNTED:
912 + break;
913 +
914 + case MOUNT_DEAD:
915 + severity = if_normal(severity, max_severity, FACET_ROW_SEVERITY_DEBUG);
916 + break;
917 + }
918 + break;
919 +
920 + case UNIT_SWAP:
921 + switch(u->SwapState) {
922 + case SWAP_FAILED:
923 + severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_CRITICAL);
924 + break;
925 +
926 + default:
927 + case SWAP_DEACTIVATING_SIGTERM:
928 + case SWAP_DEACTIVATING_SIGKILL:
929 + severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_WARNING);
930 + break;
931 +
932 + case SWAP_ACTIVATING:
933 + case SWAP_ACTIVATING_DONE:
934 + case SWAP_DEACTIVATING:
935 + case SWAP_CLEANING:
936 + severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_NOTICE);
937 + break;
938 +
939 + case SWAP_ACTIVE:
940 + break;
941 +
942 + case SWAP_DEAD:
943 + severity = if_normal(severity, max_severity, FACET_ROW_SEVERITY_DEBUG);
944 + break;
945 + }
946 + break;
947 +
948 + case UNIT_SOCKET:
949 + switch(u->SocketState) {
950 + case SOCKET_FAILED:
951 + severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_CRITICAL);
952 + break;
953 +
954 + default:
955 + case SOCKET_STOP_PRE_SIGTERM:
956 + case SOCKET_STOP_PRE_SIGKILL:
957 + case SOCKET_FINAL_SIGTERM:
958 + case SOCKET_FINAL_SIGKILL:
959 + severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_WARNING);
960 + break;
961 +
962 + case SOCKET_START_PRE:
963 + case SOCKET_START_CHOWN:
964 + case SOCKET_START_POST:
965 + case SOCKET_STOP_PRE:
966 + case SOCKET_STOP_POST:
967 + severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_NOTICE);
968 + break;
969 +
970 + case SOCKET_RUNNING:
971 + case SOCKET_LISTENING:
972 + break;
973 +
974 + case SOCKET_DEAD:
975 + severity = if_normal(severity, max_severity, FACET_ROW_SEVERITY_DEBUG);
976 + break;
977 + }
978 + break;
979 +
980 + case UNIT_TARGET:
981 + switch(u->TargetState) {
982 + default:
983 + severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_WARNING);
984 + break;
985 +
986 + case TARGET_ACTIVE:
987 + break;
988 +
989 + case TARGET_DEAD:
990 + severity = if_normal(severity, max_severity, FACET_ROW_SEVERITY_DEBUG);
991 + break;
992 + }
993 + break;
994 +
995 + case UNIT_DEVICE:
996 + switch(u->DeviceState) {
997 + default:
998 + severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_WARNING);
999 + break;
1000 +
1001 + case DEVICE_TENTATIVE:
1002 + severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_NOTICE);
1003 + break;
1004 +
1005 + case DEVICE_PLUGGED:
1006 + break;
1007 +
1008 + case DEVICE_DEAD:
1009 + severity = if_normal(severity, max_severity, FACET_ROW_SEVERITY_DEBUG);
1010 + break;
1011 + }
1012 + break;
1013 +
1014 + case UNIT_AUTOMOUNT:
1015 + switch(u->AutomountState) {
1016 + case AUTOMOUNT_FAILED:
1017 + severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_CRITICAL);
1018 + break;
1019 +
1020 + default:
1021 + severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_WARNING);
1022 + break;
1023 +
1024 + case AUTOMOUNT_WAITING:
1025 + case AUTOMOUNT_RUNNING:
1026 + break;
1027 +
1028 + case AUTOMOUNT_DEAD:
1029 + severity = if_normal(severity, max_severity, FACET_ROW_SEVERITY_DEBUG);
1030 + break;
1031 + }
1032 + break;
1033 +
1034 + case UNIT_TIMER:
1035 + switch(u->TimerState) {
1036 + case TIMER_FAILED:
1037 + severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_CRITICAL);
1038 + break;
1039 +
1040 + default:
1041 + case TIMER_ELAPSED:
1042 + severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_WARNING);
1043 + break;
1044 +
1045 + case TIMER_WAITING:
1046 + case TIMER_RUNNING:
1047 + break;
1048 +
1049 + case TIMER_DEAD:
1050 + severity = if_normal(severity, max_severity, FACET_ROW_SEVERITY_DEBUG);
1051 + break;
1052 + }
1053 + break;
1054 +
1055 + case UNIT_PATH:
1056 + switch(u->PathState) {
1057 + case PATH_FAILED:
1058 + severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_CRITICAL);
1059 + break;
1060 +
1061 + default:
1062 + severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_WARNING);
1063 + break;
1064 +
1065 + case PATH_WAITING:
1066 + case PATH_RUNNING:
1067 + break;
1068 +
1069 + case PATH_DEAD:
1070 + severity = if_normal(severity, max_severity, FACET_ROW_SEVERITY_DEBUG);
1071 + break;
1072 + }
1073 + break;
1074 +
1075 + case UNIT_SLICE:
1076 + switch(u->SliceState) {
1077 + default:
1078 + severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_WARNING);
1079 + break;
1080 +
1081 + case SLICE_ACTIVE:
1082 + break;
1083 +
1084 + case SLICE_DEAD:
1085 + severity = if_normal(severity, max_severity, FACET_ROW_SEVERITY_DEBUG);
1086 + break;
1087 + }
1088 + break;
1089 +
1090 + case UNIT_SCOPE:
1091 + switch(u->ScopeState) {
1092 + case SCOPE_FAILED:
1093 + severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_CRITICAL);
1094 + break;
1095 +
1096 + default:
1097 + case SCOPE_STOP_SIGTERM:
1098 + case SCOPE_STOP_SIGKILL:
1099 + severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_WARNING);
1100 + break;
1101 +
1102 + case SCOPE_ABANDONED:
1103 + case SCOPE_START_CHOWN:
1104 + severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_NOTICE);
1105 + break;
1106 +
1107 + case SCOPE_RUNNING:
1108 + break;
1109 +
1110 + case SCOPE_DEAD:
1111 + severity = if_normal(severity, max_severity, FACET_ROW_SEVERITY_DEBUG);
1112 + break;
1113 + }
1114 + break;
1115 +
1116 + default:
1117 + severity = if_less(severity, max_severity, FACET_ROW_SEVERITY_WARNING);
1118 + break;
1119 + }
1120 +
1121 + u->severity = severity;
1122 + return severity;
1123 +}
1124 +
1125 +int unit_info_compar(const void *a, const void *b) {
1126 + UnitInfo *u1 = *((UnitInfo **)a);
1127 + UnitInfo *u2 = *((UnitInfo **)b);
1128 +
1129 + return strcasecmp(u1->id, u2->id);
1130 +}
1131 +
1132 +void systemd_units_assign_priority(UnitInfo *base) {
1133 + size_t units = 0, c = 0, prio = 0;
1134 + for(UnitInfo *u = base; u ; u = u->next)
1135 + units++;
1136 +
1137 + UnitInfo *array[units];
1138 + for(UnitInfo *u = base; u ; u = u->next)
1139 + array[c++] = u;
1140 +
1141 + qsort(array, units, sizeof(UnitInfo *), unit_info_compar);
1142 +
1143 + for(c = 0; c < units ; c++) {
1144 + array[c]->prio = prio++;
1145 + system_unit_severity(array[c]);
1146 + systemd_unit_priority(array[c], units);
1147 + }
1148 +}
1149 +
1150 +void function_systemd_units(const char *transaction, char *function, int timeout, bool *cancelled) {
1151 + char *words[SYSTEMD_UNITS_MAX_PARAMS] = { NULL };
1152 + size_t num_words = quoted_strings_splitter_pluginsd(function, words, SYSTEMD_UNITS_MAX_PARAMS);
1153 + for(int i = 1; i < SYSTEMD_UNITS_MAX_PARAMS ;i++) {
1154 + char *keyword = get_word(words, num_words, i);
1155 + if(!keyword) break;
1156 +
1157 + if(strcmp(keyword, "info") == 0) {
1158 + netdata_systemd_units_function_info(transaction);
1159 + return;
1160 + }
1161 + else if(strcmp(keyword, "help") == 0) {
1162 + netdata_systemd_units_function_help(transaction);
1163 + return;
1164 + }
1165 + }
1166 +
1167 + UnitInfo *base = systemd_units_get_all();
1168 + systemd_units_assign_priority(base);
1169 +
1170 + BUFFER *wb = buffer_create(0, NULL);
1171 + buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_MINIFY);
1172 +
1173 + buffer_json_member_add_uint64(wb, "status", HTTP_RESP_OK);
1174 + buffer_json_member_add_string(wb, "type", "table");
1175 + buffer_json_member_add_time_t(wb, "update_every", 10);
1176 + buffer_json_member_add_string(wb, "help", SYSTEMD_UNITS_FUNCTION_DESCRIPTION);
1177 + buffer_json_member_add_array(wb, "data");
1178 +
1179 + for(UnitInfo *u = base; u ;u = u->next) {
1180 + buffer_json_add_array_item_array(wb);
1181 + {
1182 + buffer_json_add_array_item_string(wb, u->id);
1183 +
1184 + buffer_json_add_array_item_object(wb);
1185 + {
1186 + buffer_json_member_add_string(wb, "severity", facets_severity_to_string(u->severity));
1187 + }
1188 + buffer_json_object_close(wb);
1189 +
1190 + buffer_json_add_array_item_string(wb, u->type);
1191 + buffer_json_add_array_item_string(wb, u->description);
1192 + buffer_json_add_array_item_string(wb, u->load_state);
1193 + buffer_json_add_array_item_string(wb, u->active_state);
1194 + buffer_json_add_array_item_string(wb, u->sub_state);
1195 + buffer_json_add_array_item_string(wb, u->following);
1196 + buffer_json_add_array_item_string(wb, u->unit_path);
1197 + buffer_json_add_array_item_uint64(wb, u->job_id);
1198 + buffer_json_add_array_item_string(wb, u->job_type);
1199 + buffer_json_add_array_item_string(wb, u->job_path);
1200 + buffer_json_add_array_item_uint64(wb, u->prio);
1201 + buffer_json_add_array_item_uint64(wb, 1); // count
1202 + }
1203 + buffer_json_array_close(wb);
1204 + }
1205 +
1206 + buffer_json_array_close(wb); // data
1207 +
1208 + buffer_json_member_add_object(wb, "columns");
1209 + {
1210 + size_t field_id = 0;
1211 +
1212 + buffer_rrdf_table_add_field(wb, field_id++, "id", "Unit ID",
1213 + RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
1214 + 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
1215 + RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_NONE,
1216 + RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_UNIQUE_KEY | RRDF_FIELD_OPTS_WRAP | RRDF_FIELD_OPTS_FULL_WIDTH,
1217 + NULL);
1218 +
1219 + buffer_rrdf_table_add_field(
1220 + wb, field_id++,
1221 + "rowOptions", "rowOptions",
1222 + RRDF_FIELD_TYPE_NONE,
1223 + RRDR_FIELD_VISUAL_ROW_OPTIONS,
1224 + RRDF_FIELD_TRANSFORM_NONE, 0, NULL, NAN,
1225 + RRDF_FIELD_SORT_FIXED,
1226 + NULL,
1227 + RRDF_FIELD_SUMMARY_COUNT,
1228 + RRDF_FIELD_FILTER_NONE,
1229 + RRDF_FIELD_OPTS_DUMMY,
1230 + NULL);
1231 +
1232 + buffer_rrdf_table_add_field(wb, field_id++, "type", "Unit Type",
1233 + RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
1234 + 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
1235 + RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT,
1236 + RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_EXPANDED_FILTER,
1237 + NULL);
1238 +
1239 + buffer_rrdf_table_add_field(wb, field_id++, "description", "Unit Description",
1240 + RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
1241 + 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
1242 + RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_NONE,
1243 + RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_WRAP | RRDF_FIELD_OPTS_FULL_WIDTH,
1244 + NULL);
1245 +
1246 + buffer_rrdf_table_add_field(wb, field_id++, "loadState", "Unit Load State",
1247 + RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
1248 + 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
1249 + RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT,
1250 + RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_EXPANDED_FILTER,
1251 + NULL);
1252 +
1253 + buffer_rrdf_table_add_field(wb, field_id++, "activeState", "Unit Active State",
1254 + RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
1255 + 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
1256 + RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT,
1257 + RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_EXPANDED_FILTER,
1258 + NULL);
1259 +
1260 + buffer_rrdf_table_add_field(wb, field_id++, "subState", "Unit Sub State",
1261 + RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
1262 + 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
1263 + RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT,
1264 + RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_EXPANDED_FILTER,
1265 + NULL);
1266 +
1267 + buffer_rrdf_table_add_field(wb, field_id++, "following", "Unit Following",
1268 + RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
1269 + 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
1270 + RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_NONE,
1271 + RRDF_FIELD_OPTS_WRAP,
1272 + NULL);
1273 +
1274 + buffer_rrdf_table_add_field(wb, field_id++, "path", "Unit Path",
1275 + RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
1276 + 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
1277 + RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_NONE,
1278 + RRDF_FIELD_OPTS_WRAP | RRDF_FIELD_OPTS_FULL_WIDTH,
1279 + NULL);
1280 +
1281 + buffer_rrdf_table_add_field(wb, field_id++, "jobId", "Unit Job ID",
1282 + RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
1283 + 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
1284 + RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_NONE,
1285 + RRDF_FIELD_OPTS_NONE,
1286 + NULL);
1287 +
1288 + buffer_rrdf_table_add_field(wb, field_id++, "jobType", "Unit Job Type",
1289 + RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
1290 + 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
1291 + RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT,
1292 + RRDF_FIELD_OPTS_NONE,
1293 + NULL);
1294 +
1295 + buffer_rrdf_table_add_field(wb, field_id++, "jobPath", "Unit Job Path",
1296 + RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
1297 + 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
1298 + RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_NONE,
1299 + RRDF_FIELD_OPTS_WRAP | RRDF_FIELD_OPTS_FULL_WIDTH,
1300 + NULL);
1301 +
1302 + buffer_rrdf_table_add_field(wb, field_id++, "priority", "Priority",
1303 + RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
1304 + 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
1305 + RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_NONE,
1306 + RRDF_FIELD_OPTS_NONE,
1307 + NULL);
1308 +
1309 + buffer_rrdf_table_add_field(wb, field_id++, "count", "Count",
1310 + RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
1311 + 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
1312 + RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_NONE,
1313 + RRDF_FIELD_OPTS_NONE,
1314 + NULL);
1315 + }
1316 +
1317 + buffer_json_object_close(wb); // columns
1318 + buffer_json_member_add_string(wb, "default_sort_column", "priority");
1319 +
1320 + buffer_json_member_add_object(wb, "charts");
1321 + {
1322 + buffer_json_member_add_object(wb, "count");
1323 + {
1324 + buffer_json_member_add_string(wb, "name", "count");
1325 + buffer_json_member_add_string(wb, "type", "stacked-bar");
1326 + buffer_json_member_add_array(wb, "columns");
1327 + {
1328 + buffer_json_add_array_item_string(wb, "count");
1329 + }
1330 + buffer_json_array_close(wb);
1331 + }
1332 + buffer_json_object_close(wb);
1333 + }
1334 + buffer_json_object_close(wb); // charts
1335 +
1336 + buffer_json_member_add_array(wb, "default_charts");
1337 + {
1338 + buffer_json_add_array_item_array(wb);
1339 + buffer_json_add_array_item_string(wb, "count");
1340 + buffer_json_add_array_item_string(wb, "activeState");
1341 + buffer_json_array_close(wb);
1342 + buffer_json_add_array_item_array(wb);
1343 + buffer_json_add_array_item_string(wb, "count");
1344 + buffer_json_add_array_item_string(wb, "subState");
1345 + buffer_json_array_close(wb);
1346 + }
1347 + buffer_json_array_close(wb);
1348 +
1349 + buffer_json_member_add_object(wb, "group_by");
1350 + {
1351 + buffer_json_member_add_object(wb, "type");
1352 + {
1353 + buffer_json_member_add_string(wb, "name", "Top Down Tree");
1354 + buffer_json_member_add_array(wb, "columns");
1355 + {
1356 + buffer_json_add_array_item_string(wb, "type");
1357 + buffer_json_add_array_item_string(wb, "loadState");
1358 + buffer_json_add_array_item_string(wb, "activeState");
1359 + buffer_json_add_array_item_string(wb, "subState");
1360 + }
1361 + buffer_json_array_close(wb);
1362 + }
1363 + buffer_json_object_close(wb);
1364 +
1365 + buffer_json_member_add_object(wb, "subState");
1366 + {
1367 + buffer_json_member_add_string(wb, "name", "Bottom Up Tree");
1368 + buffer_json_member_add_array(wb, "columns");
1369 + {
1370 + buffer_json_add_array_item_string(wb, "subState");
1371 + buffer_json_add_array_item_string(wb, "activeState");
1372 + buffer_json_add_array_item_string(wb, "loadState");
1373 + buffer_json_add_array_item_string(wb, "type");
1374 + }
1375 + buffer_json_array_close(wb);
1376 + }
1377 + buffer_json_object_close(wb);
1378 + }
1379 + buffer_json_object_close(wb); // group_by
1380 +
1381 + buffer_json_member_add_time_t(wb, "expires", now_realtime_sec() + 1);
1382 + buffer_json_finalize(wb);
1383 +
1384 + netdata_mutex_lock(&stdout_mutex);
1385 + pluginsd_function_result_to_stdout(transaction, HTTP_RESP_OK, "application/json", now_realtime_sec() + 3600, wb);
1386 + netdata_mutex_unlock(&stdout_mutex);
1387 +
1388 + buffer_free(wb);
1389 + systemd_units_free_all(base);
1390 +}
1391 +
1392 +#endif // ENABLE_SYSTEMD_DBUS
configure.ac
+8
@@ -1184,6 +1184,14 @@ if test "${have_sd_journal_restart_fields}" = "yes"; then
1184 AC_DEFINE([HAVE_SD_JOURNAL_RESTART_FIELDS], [1], [sd_journal_restart_fields usability])
1185 fi
1186
1187 +AC_CHECK_LIB([systemd], [sd_bus_default_system, sd_bus_call_method, sd_bus_message_enter_container, sd_bus_message_read, sd_bus_message_exit_container],
1188 + [SYSTEMD_DBUS_FOUND=yes],
1189 + [SYSTEMD_DBUS_FOUND=no])
1190 +
1191 +if test "x$SYSTEMD_DBUS_FOUND" = "xyes"; then
1192 + AC_DEFINE([ENABLE_SYSTEMD_DBUS], [1], [libsystemd dbus usability])
1193 +fi
1194 +
1195 AC_MSG_NOTICE([OPTIONAL_SYSTEMD_LIBS is set to: ${OPTIONAL_SYSTEMD_LIBS}])
1196
1197 if test "${enable_plugin_systemd_journal}" = "yes"; then
database/rrdfunctions.c
+4 -4
@@ -991,7 +991,7 @@ int rrd_function_run(RRDHOST *host, BUFFER *result_wb, int timeout, const char *
991 // the caller has to wait
992
993 code = rdcf->execute_cb(result_wb, timeout, sanitized_cmd, rdcf->execute_cb_data,
994 - NULL, NULL, // no callback needed, it is synchronous
994 + result_cb, result_cb_data,
995 is_cancelled_cb, is_cancelled_cb_data, // it is ok to pass these, we block the caller
996 NULL, NULL); // no need to pass, we will wait
997
@@ -1372,19 +1372,19 @@ int rrdhost_function_streaming(BUFFER *wb, int timeout __maybe_unused, const cha
1372 RRDF_FIELD_TYPE_TIMESTAMP, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_DATETIME_MS,
1373 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
1374 RRDF_FIELD_SUMMARY_MIN, RRDF_FIELD_FILTER_RANGE,
1375 - RRDF_FIELD_OPTS_VISIBLE, NULL);
1375 + RRDF_FIELD_OPTS_NONE, NULL);
1376
1377 buffer_rrdf_table_add_field(wb, field_id++, "dbTo", "DB Data Retention To",
1378 RRDF_FIELD_TYPE_TIMESTAMP, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_DATETIME_MS,
1379 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
1380 RRDF_FIELD_SUMMARY_MAX, RRDF_FIELD_FILTER_RANGE,
1381 - RRDF_FIELD_OPTS_VISIBLE, NULL);
1381 + RRDF_FIELD_OPTS_NONE, NULL);
1382
1383 buffer_rrdf_table_add_field(wb, field_id++, "dbDuration", "DB Data Retention Duration",
1384 RRDF_FIELD_TYPE_DURATION, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_DURATION_S,
1385 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
1386 RRDF_FIELD_SUMMARY_MAX, RRDF_FIELD_FILTER_RANGE,
1387 - RRDF_FIELD_OPTS_NONE, NULL);
1387 + RRDF_FIELD_OPTS_VISIBLE, NULL);
1388
1389 buffer_rrdf_table_add_field(wb, field_id++, "dbMetrics", "Time-series Metrics in the DB",
1390 RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NUMBER,
libnetdata/buffer/buffer.h
+4 -2
@@ -959,7 +959,8 @@ typedef enum __attribute__((packed)) {
959 RRDF_FIELD_OPTS_STICKY = (1 << 2), // the field should be sticky
960 RRDF_FIELD_OPTS_FULL_WIDTH = (1 << 3), // the field should get full width
961 RRDF_FIELD_OPTS_WRAP = (1 << 4), // the field should wrap
962 - RRDR_FIELD_OPTS_DUMMY = (1 << 5), // not a presentable field
962 + RRDF_FIELD_OPTS_DUMMY = (1 << 5), // not a presentable field
963 + RRDF_FIELD_OPTS_EXPANDED_FILTER = (1 << 6), // show the filter expanded
964 } RRDF_FIELD_OPTIONS;
965
966 typedef enum __attribute__((packed)) {
@@ -1173,8 +1174,9 @@ buffer_rrdf_table_add_field(BUFFER *wb, size_t field_id, const char *key, const
1174
1175 buffer_json_member_add_boolean(wb, "full_width", options & RRDF_FIELD_OPTS_FULL_WIDTH);
1176 buffer_json_member_add_boolean(wb, "wrap", options & RRDF_FIELD_OPTS_WRAP);
1177 + buffer_json_member_add_boolean(wb, "default_expanded_filter", options & RRDF_FIELD_OPTS_EXPANDED_FILTER);
1178
1177 - if(options & RRDR_FIELD_OPTS_DUMMY)
1179 + if(options & RRDF_FIELD_OPTS_DUMMY)
1180 buffer_json_member_add_boolean(wb, "dummy", true);
1181 }
1182 buffer_json_object_close(wb);
libnetdata/facets/facets.c
+6 -4
@@ -1948,7 +1948,7 @@ bool facets_row_finished(FACETS *facets, usec_t usec) {
1948 // ----------------------------------------------------------------------------
1949 // output
1950
1951 -static const char *facets_severity_to_string(FACET_ROW_SEVERITY severity) {
1951 +const char *facets_severity_to_string(FACET_ROW_SEVERITY severity) {
1952 switch(severity) {
1953 default:
1954 case FACET_ROW_SEVERITY_NORMAL:
@@ -2338,7 +2338,7 @@ void facets_report(FACETS *facets, BUFFER *wb, DICTIONARY *used_hashes_registry)
2338 NULL,
2339 RRDF_FIELD_SUMMARY_COUNT,
2340 RRDF_FIELD_FILTER_NONE,
2341 - RRDR_FIELD_OPTS_DUMMY,
2341 + RRDF_FIELD_OPTS_DUMMY,
2342 NULL);
2343
2344 FACET_KEY *k;
@@ -2358,6 +2358,9 @@ void facets_report(FACETS *facets, BUFFER *wb, DICTIONARY *used_hashes_registry)
2358 if (k->options & FACET_KEY_OPTION_MAIN_TEXT)
2359 options |= RRDF_FIELD_OPTS_FULL_WIDTH | RRDF_FIELD_OPTS_WRAP;
2360
2361 + if (k->options & FACET_KEY_OPTION_EXPANDED_FILTER)
2362 + options |= RRDF_FIELD_OPTS_EXPANDED_FILTER;
2363 +
2364 const char *hash_str = hash_to_static_string(k->hash);
2365
2366 buffer_rrdf_table_add_field(
@@ -2369,8 +2372,7 @@ void facets_report(FACETS *facets, BUFFER *wb, DICTIONARY *used_hashes_registry)
2372 RRDF_FIELD_SORT_FIXED,
2373 NULL,
2374 RRDF_FIELD_SUMMARY_COUNT,
2372 - (k->options & FACET_KEY_OPTION_NEVER_FACET) ? RRDF_FIELD_FILTER_NONE
2373 - : RRDF_FIELD_FILTER_FACET,
2375 + (k->options & FACET_KEY_OPTION_NEVER_FACET) ? RRDF_FIELD_FILTER_NONE : RRDF_FIELD_FILTER_FACET,
2376 options, FACET_VALUE_UNSET);
2377 }
2378 foreach_key_in_facets_done(k);
libnetdata/facets/facets.h
+3
@@ -31,6 +31,7 @@ typedef enum __attribute__((packed)) {
31 FACET_KEY_OPTION_RICH_TEXT = (1 << 7),
32 FACET_KEY_OPTION_REORDER = (1 << 8), // give the key a new order id on first encounter
33 FACET_KEY_OPTION_TRANSFORM_VIEW = (1 << 9), // when registering the transformation, do it only at the view, not on all data
34 + FACET_KEY_OPTION_EXPANDED_FILTER = (1 << 10), // the presentation should have this filter expanded by default
35 } FACET_KEY_OPTIONS;
36
37 typedef enum __attribute__((packed)) {
@@ -115,4 +116,6 @@ uint32_t facets_rows(FACETS *facets);
116
117 void facets_table_config(BUFFER *wb);
118
119 +const char *facets_severity_to_string(FACET_ROW_SEVERITY severity);
120 +
121 #endif