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(×tamp_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(×tamp_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(×tamp_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(×tamp_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