@cryptotaxi247 / netdata-1 / commits / 9911045b5

systemd-Journal by file (#16038)

* query journal file by file: 17% faster * maintain a registry of journal files in memory and support multiple journal directories; offer sources of journal directories * fixes * overloaded libc fstat64() call to speed up libsystemd * do not just copy unset values, there is a flag that tracks them * optimize facets_row_finished() * use container name in ND_JOURNAL_PROCESS * fix compatibility with versions of libsystemd without sd_journal_open_files_fd() * added more statistics about the time spent per journal file * optimize facets_row_finished() * optimize facets_rows_begin() * tuning * progress reporting * fix journal seek to precisely match log timestamps * support remote sources and namespaces * jf_is_mine() as function * fixes * fixes 2 * fixes 3 * added debug * fixed log * added source for fqs * fix all source names * fix jf_is_mine() to return a value * add rows_useful to journal files * sorted list of all sources * make hostname visible by default * rename sources * increase number of columns * updated apps_groups.conf * support view only transformations * add support for slicing * add support for older versions of systemd * cleanup * added ordering of key values * convert remote IPs to hostnames * fix for hostname resolution * standardize the source name length * added versions * added sources pills and info * fix plural * better formatting for durations * support dynamic unset value * fix sorting * errno to still show numeric values * maintain a used hashes registry * fixed severity * updated function help message with all current parameters accepted * remove internal error * always return null as empty values in data * add default sd_journal_open flags * validate anchor * fix compiler warning * calculate journal vs realtime delta per journal file * up to 2 minutes journal vs realtime delta * more detailed message * do not log zero anchor * fixed message * fix seek to db * request details and dump of all journal files in response * sort files before processing them * do not sort if fewer than 2 files * documentation * added documentation about performance * added field transformations documentation and annotated _CAP_EFFECTIVE * updated docs * updated docs * annotated SOURCE_REALTIME_TIMESTAMP * updated docs * workaround for old systems * updated docs * updated docs * updated docs * updated docs * more fields to show by default * filter data-only query by libsystemd on slice mode * better tail * restore operation of full queries * updated docs * updated docs * added smart field _BOOT_ID to automatically extract the timestamp of the first message of this boot_id * do not seek to anchor on full queries * added tail and delta * alphabetical sort on calculated columns * simplify sorting of facet values * fix sorting of transformed values * simplify code * numeric values for capabilities that do not exist in old systems * do not log if directories do not exist or are not directories

Costa Tsaousis committed Oct 2, 2023 at 18:32 UTC 9911045b59d16f0a5df324802367fc7d5e0e2dbf
9 files changed +3072 -511
collectors/apps.plugin/apps_groups.conf
+10 -10
@@ -83,14 +83,14 @@ xenstat.plugin: xenstat.plugin
83 perf.plugin: perf.plugin
84 charts.d.plugin: *charts.d.plugin*
85 python.d.plugin: *python.d.plugin*
86 +systemd-journal.plugin:*systemd-journal.plugin*
87 tc-qos-helper: *tc-qos-helper.sh*
88 fping: fping
89 ioping: ioping
90 go.d.plugin: *go.d.plugin*
90 -slabinfo.plugin: slabinfo.plugin
91 +slabinfo.plugin: *slabinfo.plugin*
92 ebpf.plugin: *ebpf.plugin*
93 debugfs.plugin: *debugfs.plugin*
93 -systemd-journal.plugin: systemd-journal
94
95 # agent-service-discovery
96 agent_sd: agent_sd
@@ -137,7 +137,7 @@ modem: ModemManager
137 netmanager: NetworkManager nm* systemd-networkd networkctl netplan connmand wicked* avahi-autoipd networkd-dispatcher
138 firewall: firewalld ufw nft
139 tor: tor
140 -bluetooth: bluetooth bluez bluedevil obexd
140 +bluetooth: bluetooth bluetoothd bluez bluedevil obexd
141
142 # -----------------------------------------------------------------------------
143 # high availability and balancers
@@ -160,7 +160,7 @@ chat: irssi *vines* *prosody* murmurd
160 # -----------------------------------------------------------------------------
161 # monitoring
162
163 -logs: ulogd* syslog* rsyslog* logrotate systemd-journald rotatelogs sysklogd metalog
163 +logs: ulogd* syslog* rsyslog* logrotate *systemd-journal* rotatelogs sysklogd metalog
164 nms: snmpd vnstatd smokeping zabbix* munin* mon openhpid tailon nrpe
165 monit: monit
166 splunk: splunkd
@@ -210,7 +210,7 @@ proxmox-ve: pve* spiceproxy
210 # -----------------------------------------------------------------------------
211 # containers & virtual machines
212
213 -containers: lxc* docker* balena*
213 +containers: lxc* docker* balena* containerd
214 VMs: vbox* VBox* qemu* kvm*
215 libvirt: virtlogd virtqemud virtstoraged virtnetworkd virtlockd virtinterfaced
216 libvirt: virtnodedevd virtproxyd virtsecretd libvirtd
@@ -239,7 +239,7 @@ dhcp: *dhcp* dhclient
239 # -----------------------------------------------------------------------------
240 # name servers and clients
241
242 -dns: named unbound nsd pdns_server knotd gdnsd yadifad dnsmasq systemd-resolve* pihole* avahi-daemon avahi-dnsconfd
242 +dns: named unbound nsd pdns_server knotd gdnsd yadifad dnsmasq *systemd-resolve* pihole* avahi-daemon avahi-dnsconfd
243 dnsdist: dnsdist
244
245 # -----------------------------------------------------------------------------
@@ -272,7 +272,7 @@ backup: rsync lsyncd bacula* borg rclone
272 # -----------------------------------------------------------------------------
273 # cron
274
275 -cron: cron* atd anacron systemd-cron* incrond
275 +cron: cron* atd anacron *systemd-cron* incrond
276
277 # -----------------------------------------------------------------------------
278 # UPS
@@ -320,7 +320,7 @@ airflow: *airflow*
320 # -----------------------------------------------------------------------------
321 # GUI
322
323 -X: X Xorg xinit xdm Xwayland xsettingsd
323 +X: X Xorg xinit xdm Xwayland xsettingsd touchegg
324 wayland: swaylock swayidle waypipe wayvnc
325 kde: *kdeinit* kdm sddm plasmashell startplasma-* kwin* kwallet* krunner kactivitymanager*
326 gnome: gnome-* gdm gconf* mutter
@@ -354,11 +354,11 @@ kswapd: kswapd
354 zswap: zswap
355 kcompactd: kcompactd
356
357 -system: systemd-* udisks* udevd* *udevd ipv6_addrconf dbus-* rtkit*
357 +system: systemd* udisks* udevd* *udevd ipv6_addrconf dbus-* rtkit*
358 system: mdadm acpid uuidd upowerd elogind* eudev mdev lvmpolld dmeventd
359 system: accounts-daemon rngd haveged rasdaemon irqbalance start-stop-daemon
360 system: supervise-daemon openrc* init runit runsvdir runsv auditd lsmd
361 -system: abrt* nscd rtkit-daemon gpg-agent usbguard*
361 +system: abrt* nscd rtkit-daemon gpg-agent usbguard* boltd geoclue
362
363 kernel: kworker kthreadd kauditd lockd khelper kdevtmpfs khungtaskd rpciod
364 kernel: fsnotify_mark kthrotld deferwq scsi_* kdmflush oom_reaper kdevtempfs
collectors/plugins.d/pluginsd_parser.c
+1 -1
@@ -888,7 +888,7 @@ static void inflight_functions_delete_callback(const DICTIONARY_ITEM *item __may
888 pf->result_cb(pf->result_body_wb, pf->code, pf->result_cb_data);
889
890 string_freez(pf->function);
891 - freez(pf->payload);
891 + freez((void *)pf->payload);
892 }
893
894 void inflight_functions_init(PARSER *parser) {
collectors/systemd-journal.plugin/README.md
+357
@@ -0,0 +1,357 @@
1 +<!--
2 +title: "SystemD-Journal"
3 +description: "View and analyze logs available in systemd journal"
4 +custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/systemd-journal.plugin/README.md"
5 +sidebar_label: "SystemD-Journal"
6 +learn_status: "Published"
7 +learn_rel_path: "Integrations/Logs"
8 +-->
9 +
10 +[KEY FEATURES](#key-features) | [JOURNAL SOURCES](#journal-sources) | [JOURNAL FIELDS](#journal-fields) |
11 +[PLAY MODE](#play-mode) | [FULL TEXT SEARCH](#full-text-search) | [PERFORMANCE](#query-performance) |
12 +[CONFIGURATION](#configuration-and-maintenance) | [FAQ](#faq)
13 +
14 +# SystemD Journal
15 +
16 +The SystemD Journal plugin by Netdata makes viewing, exploring and analyzing systemd journal logs simple and efficient.
17 +It automatically discovers available journal sources, allows advanced filtering, offers interactive visual
18 +representations and supports exploring the logs of both individual servers and the logs on infrastructure wide
19 +journal centralization servers.
20 +
21 +![image](https://github.com/netdata/netdata/assets/2662304/691b7470-ec56-430c-8b81-0c9e49012679)
22 +
23 +## Key features:
24 +
25 +- Works on both **individual servers** and **journal centralization servers**.
26 +- Supports `persistent` and `volatile` journals.
27 +- Supports `system`, `user`, `namespaces` and `remote` journals.
28 +- Allows filtering on **any journal field** or **field value**, for any time-frame.
29 +- Allows **full text search** (`grep`) on all journal fields, for any time-frame.
30 +- Provides a **histogram** for log entries over time, with a break down per field-value, for any field and any time-frame.
31 +- Works directly on journal files, without any other third party components.
32 +- Supports coloring log entries, the same way `journalctl` does.
33 +- In PLAY mode provides the same experience as `journalctl -f`, showing new logs entries immediately after they are received.
34 +
35 +### Prerequisites
36 +
37 +`systemd-journal.plugin` is a Netdata Function Plugin.
38 +
39 +To protect your privacy, as with all Netdata Functions, a free Netdata Cloud user account is required to access it.
40 +
41 +### Limitations:
42 +
43 +- This plugin is not available when Netdata is installed in a container. The problem is that `libsystemd` is not available in Alpine Linux (there is a `libsystemd`, but it is a dummy that returns failure on all calls). We plan to change this, by shipping Netdata containers based on Debian.
44 +- For the same reason (lack of `systemd` support for Alpine Linux), the plugin is not available on `static` builds of Netdata (which are based on `muslc`, not `glibc`).
45 +
46 +## Journal Sources
47 +
48 +The plugin automatically detects the available journal sources, based on the journal files available in
49 +`/var/log/journal` (persistent logs) and `/run/log/journal` (volatile logs).
50 +
51 +![journal-sources](https://github.com/netdata/netdata/assets/2662304/28e63a3e-6809-4586-b3b0-80755f340e31)
52 +
53 +The plugin, by default, merges all journal sources together, to provide a unified view of all log messages available.
54 +
55 +> To improve query performance, we recommend selecting the relevant journal source, before doing more analysis on the logs.
56 +
57 +### `system` journals
58 +
59 +These are the default journals available on all systems.
60 +
61 +`system` journals contain:
62 +
63 +- kernel log messages (via `kmsg`),
64 +- audit records, originating from the kernel audit subsystem,
65 +- messages received via `syslog`,
66 +- messages received via the standard output and error of service units,
67 +- structured messages received via the native journal API.
68 +
69 +### `user` journals
70 +
71 +By default, each user, with a UID outside the range of system users (0 - 999), dynamic service users,
72 +and the nobody user (65534), will get their own set of `user` journal files. For more information about
73 +this policy check [Users, Groups, UIDs and GIDs on systemd Systems](https://systemd.io/UIDS-GIDS/).
74 +
75 +The plugin allows viewing, exploring and querying the journal files of all users.
76 +
77 +### `namespaces` journals
78 +
79 +Journal 'namespaces' are both a mechanism for logically isolating the log stream of projects consisting
80 +of one or more services from the rest of the system and a mechanism for improving performance. Systemd service
81 +units may be assigned to a specific journal namespace through the `LogNamespace=` unit file setting.
82 +
83 +The plugin auto-detects the namespaces available and provides a list of all namespaces at the "sources" list on the UI.
84 +
85 +### `remote` journals
86 +
87 +Remote journals are created by `systemd-journal-remote`. This feature allows creating logs centralization points within
88 +your infrastructure.
89 +
90 +Usually `remote` journals are named by the IP of the server sending these logs. The Netdata plugin automatically
91 +extracts these IPs and performs a reverse DNS lookup to find their hostnames. When this is successful,
92 +`remote` journals are named by the hostnames of the origin servers.
93 +
94 +For information about configuring a journals' centralization server, check [this FAQ item](#how-do-i-configure-a-journals-centralization-server).
95 +
96 +## Journal Fields
97 +
98 +Fields found in the journal files are automatically added to the UI in multiple places to help you explore
99 +and filter the data.
100 +
101 +The plugin automatically enriches certain fields to make them more user-friendly:
102 +
103 +- `_BOOT_ID`: the hex value is annotated with the timestamp of the first message encountered for this boot id.
104 +- `PRIORITY`: the numeric value is replaced with the human-readable name of each priority.
105 +- `SYSLOG_FACILITY`: the encoded value is replaced with the human-readable name of each value.
106 +- `ERRNO`: the numeric value is annotated with the short name of each value.
107 +- `_UID` `_AUDIT_LOGINUID` and `_SYSTEMD_OWNER_UID`: the local user database is consulted to annotate them with usernames.
108 +- `_GID`: the local group database is consulted to annotate them with group names.
109 +- `_CAP_EFFECTIVE`: the encoded value is annotated with a human-readable list of the linux capabilities.
110 +- `_SOURCE_REALTIME_TIMESTAMP`: the numeric value is annotated with human-readable datetime in UTC.
111 +
112 +The values of all other fields are presented as found in the journals.
113 +
114 +> IMPORTANT:</br>
115 +> `_UID` `_AUDIT_LOGINUID`, `_SYSTEMD_OWNER_UID` and `_GID` annotations are added during presentation and are taken
116 +> from the server running the plugin. For `remote` sources, the names presented may not reflect the actual user and
117 +> group names on the origin server.
118 +
119 +The annotations are not searchable with full text search. They are only added for the presentation of the fields.
120 +
121 +### Journal fields as columns in the table
122 +
123 +All journal fields available in the journal files are offered as columns on the UI. Use the gear button above the table:
124 +
125 +![image](https://github.com/netdata/netdata/assets/2662304/cd75fb55-6821-43d4-a2aa-033792c7f7ac)
126 +
127 +### Journal fields as additional info to each log entry
128 +
129 +When you click a log line, the sidebar, on the right of the screen, provides the full list of fields related to this
130 +log line. You can close this info sidebar, by selecting the filter icon at its top.
131 +
132 +![image](https://github.com/netdata/netdata/assets/2662304/3207794c-a61b-444c-8ffe-6c07cbc90ae2)
133 +
134 +### Journal fields as filters
135 +
136 +The plugin presents a select list of fields as filters to the query, with counters for each of the possible values
137 +for the field. This list can used to quickly check which fields and values are available for the entire time-frame
138 +of the query.
139 +
140 +Internally the plugin has:
141 +
142 +1. A white-list of fields, to be presented as filters.
143 +2. A black-list of fields, to prevent them from becoming filters. This list includes fields with a very high cardinality, like timestamps, unique message ids, etc. This is mainly for protecting the server's performance, to avoid building in memory indexes for the fields that almost each of their values is unique.
144 +
145 +Keep in mind that the values presented in the filters, and their sorting is affected by the "full data queries"
146 +setting:
147 +
148 +![image](https://github.com/netdata/netdata/assets/2662304/ac710d46-07c2-487b-8ce3-e7f767b9ae0f)
149 +
150 +When "full data queries" is off, empty values are hidden and cannot be selected. This is due to a limitation of
151 +`libsystemd` that does not allow negative or empty matches. Also, values with zero counters may appear in the list.
152 +
153 +When "full data queries" is on, Netdata is applying all filtering to the data (not `libsystemd`), but this means
154 +that all the data of the entire time-frame, without any filtering applied, have to be read by the plugin to prepare
155 +the response required.
156 +
157 +### Journal fields as histogram sources
158 +
159 +The plugin presents a histogram of the number of log entries across time.
160 +
161 +The data source of this histogram can be any of the fields that are available as filters.
162 +For each of the values this field has, across the entire time-frame of the query, the histogram will get corresponding
163 +dimensions, showing the number of log entries, per value, over time.
164 +
165 +The granularity of the histogram is adjusted automatically to have about 150 columns visible on screen.
166 +
167 +The histogram presented by the plugin is interactive:
168 +
169 +- **Zoom**, either with the global date-time picker, or the zoom tool in the histogram's toolbox.
170 +- **Pan**, either with global date-time picker, or by dragging with the mouse the chart to the left or the right.
171 +- **Click**, to quickly jump to the highlighted point in time in the log entries.
172 +
173 +![image](https://github.com/netdata/netdata/assets/2662304/d3dcb1d1-daf4-49cf-9663-91b5b3099c2d)
174 +
175 +## PLAY mode
176 +
177 +The plugin supports PLAY mode, to continuously update the screen with new log entries found in the journal files.
178 +
179 +On centralized log servers, this provides a unified view of all the logs encountered across the entire infrastructure.
180 +
181 +## Full-text search
182 +
183 +The plugin supports searching for any text on all fields of the log entries.
184 +
185 +Full text search is combined with the selected filters.
186 +
187 +## Query performance
188 +
189 +Journal files are designed to be accessed by multiple readers and one writer, concurrently.
190 +
191 +Readers (like this Netdata plugin), open the journal files and `libsystemd`, behind the scenes, maps regions
192 +of the files into memory, to satisfy each query.
193 +
194 +On logs aggregation servers, the performance of the queries depend on the following factors:
195 +
196 +1. The number of files involved in each query. This is why we suggest to select a source when possible.
197 +2. The speed of the disks hosting the journal files. Journal files perform a lot of reading while querying, so the fastest the disks, the faster the query will finish.
198 +3. The memory available for caching parts of the files. Increased memory will help the kernel cache the most frequently used parts of the journal files, avoiding disk I/O and speeding up queries.
199 +4. The number of filters applied. Queries are significantly faster when just a few filters are selected.
200 +
201 +In general, for a faster experience, keep a low number of rows within the visible timeframe.
202 +
203 +Even on long timeframes, selecting a couple of filters that will result in a few dozen thousand log entries
204 +will provide fast / rapid responses, usually less than a second. To the contrary, viewing timeframes with millions
205 +of entries may result in longer delays.
206 +
207 +The plugin aborts journal queries when your browser cancels inflight requests. This allows you to work on the UI
208 +while there are background queries running.
209 +
210 +At the time of this writing, this Netdata plugin is about 25-30 times faster than `journalctl` on queries that access
211 +multiple journal files, over long time-frames.
212 +
213 +During the development of this plugin, we submitted, to `systemd`, a number of patches to improve `journalctl`
214 +performance by a factor of 14:
215 +
216 +- https://github.com/systemd/systemd/pull/29365
217 +- https://github.com/systemd/systemd/pull/29366
218 +- https://github.com/systemd/systemd/pull/29261
219 +
220 +However, even after these patches are merged, `journalctl` will still be 2x slower than this Netdata plugin,
221 +on multi-journal queries.
222 +
223 +The problem lies in the way `libsystemd` handles multi-journal file queries. To overcome this problem,
224 +the Netdata plugin queries each file individually and it then it merges the results to be returned.
225 +This is transparent, thanks to the `facets` library in `libnetdata` that handles on-the-fly indexing, filtering,
226 +and searching of any dataset, independently of its source.
227 +
228 +## Configuration and maintenance
229 +
230 +This Netdata plugin does not require any configuration or maintenance.
231 +
232 +## FAQ
233 +
234 +### Can I use this plugin on journals' centralization servers?
235 +
236 +Yes. You can centralize your logs using systemd journal, and then install Netdata
237 +on this logs centralization server to explore the logs of all your infrastructure.
238 +
239 +This plugin will automatically provide multi-node views of your logs and also give you the ability to combine the logs
240 +of multiple servers, as you see fit.
241 +
242 +Check [configuring a logs centralization server](#configuring-a-journals-centralization-server).
243 +
244 +### Can I use this plugin from a parent Netdata?
245 +
246 +Yes. When your nodes are connected to a Netdata parent, all their functions are available
247 +via the parent's UI. So, from the parent UI, you can access the functions of all your nodes.
248 +
249 +Keep in mind that to protect your privacy, in order to access Netdata functions, you need a
250 +free Netdata Cloud account.
251 +
252 +### Is any of my data exposed to Netdata Cloud from this plugin?
253 +
254 +No. When you access the agent directly, none of your data passes through Netdata Cloud.
255 +You need a free Netdata Cloud account only to verify your identity and enable the use of
256 +Netdata Functions. Once this is done, all the data flow directly from your Netdata agent
257 +to your web browser.
258 +
259 +When you access Netdata via `https://app.netdata.cloud`, your data travel via Netdata Cloud,
260 +but they are not stored in Netdata Cloud. This is to allow you access your Netdata agents from
261 +anywhere. All communication from/to Netdata Cloud is encrypted.
262 +
263 +### What are `volatile` and `persistent` journals?
264 +
265 +SystemD JournalD allows creating both `volatile` journals in a `tmpfs` ram drive,
266 +and `persistent` journals stored on disk.
267 +
268 +`volatile` journals are particularly useful when the system monitored is sensitive to
269 +disk I/O, or does not have any writable disks at all.
270 +
271 +For more information check `man systemd-journald`.
272 +
273 +### Is it worth to build a systemd logs centralization server?
274 +
275 +Yes. It is simple, fast and the software to do it is already in your systems.
276 +
277 +For application and system logs, systemd journal is ideal and the visibility you can get
278 +by centralizing your system logs and the use of this Netdata plugin, is unparalleled.
279 +
280 +### How do I configure a journals' centralization server?
281 +
282 +A short summary to get journal server running can be found below.
283 +
284 +For more options and reference to documentation, check `man systemd-journal-remote` and `man systemd-journal-upload`.
285 +
286 +#### Configuring a journals' centralization server
287 +
288 +On the centralization server install `systemd-journal-remote`, and enable it with `systemctl`, like this:
289 +
290 +```sh
291 +# change this according to your distro
292 +sudo apt-get install systemd-journal-remote
293 +
294 +# enable receiving
295 +sudo systemctl enable --now systemd-journal-remote.socket
296 +sudo systemctl enable systemd-journal-remote.service
297 +```
298 +
299 +`systemd-journal-remote` is now listening for incoming journals from remote hosts, on port `19532`.
300 +Please note that `systemd-journal-remote` supports using secure connections.
301 +To learn more run `man systemd-journal-remote`.
302 +
303 +To change the protocol of the journal transfer (HTTP/HTTPS) and the save location, do:
304 +
305 +```sh
306 +# copy the service file
307 +sudo cp /lib/systemd/system/systemd-journal-remote.service /etc/systemd/system/
308 +
309 +# edit it
310 +# --listen-http=-3 specifies the incoming journal for http.
311 +# If you want to use https, change it to --listen-https=-3.
312 +nano /etc/systemd/system/systemd-journal-remote.service
313 +
314 +# reload systemd
315 +sudo systemctl daemon-reload
316 +```
317 +
318 +To change the port, copy `/lib/systemd/system/systemd-journal-remote.socket` to `/etc/systemd/system/` and edit it.
319 +Then do `sudo systemctrl daemon-reload`
320 +
321 +
322 +#### Configuring journal clients to push their logs to the server
323 +
324 +On the clients you want to centralize their logs, install `systemd-journal-remote`, configure `systemd-journal-upload`, enable it and start it with `systemctl`.
325 +
326 +To install it run:
327 +
328 +```sh
329 +# change this according to your distro
330 +sudo apt-get install systemd-journal-remote
331 +```
332 +
333 +Then, edit `/etc/systemd/journal-upload.conf` and set the IP address and the port of the server, like this:
334 +
335 +```
336 +[Upload]
337 +URL=http://centralization.server.ip:19532
338 +```
339 +
340 +Remember to match the protocol (http/https) the server expects.
341 +
342 +Finally, enable and start `systemd-journal-upload`, like this:
343 +
344 +```sh
345 +sudo systemctl enable systemd-journal-upload
346 +sudo systemctl start systemd-journal-upload
347 +```
348 +
349 +Keep in mind that immediately after starting `systemd-journal-upload` on a server, a replication process starts pushing logs in the order they have been received. This means that depending on the size of the available logs, some time may be needed for Netdata to show the most recent logs of that server.
350 +
351 +#### Limitations when using a logs centralization server
352 +
353 +As of this writing `namespaces` support by systemd is limited:
354 +
355 +- Docker containers cannot log to namespaces. Check [this issue](https://github.com/moby/moby/issues/41879).
356 +- `systemd-journal-upload` automatically uploads `system` and `user` journals, but not `namespaces` journals. For this you need to spawn a `systemd-journal-upload` per namespace.
357 +
collectors/systemd-journal.plugin/systemd-journal.c
+1884 -276
@@ -9,19 +9,97 @@
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>
15
16 +// ----------------------------------------------------------------------------
17 +// fstat64 overloading to speed up libsystemd
18 +// https://github.com/systemd/systemd/pull/29261
19 +
20 +#define ND_SD_JOURNAL_OPEN_FLAGS (0)
21 +
22 +#ifdef HAVE_SD_JOURNAL_OPEN_FILES_FD
23 +
24 +#include <dlfcn.h>
25 +#include <sys/stat.h>
26 +
27 +#define FSTAT_CACHE_MAX 1024
28 +struct fdstat64_cache_entry {
29 + bool enabled;
30 + bool updated;
31 + int err_no;
32 + struct stat64 stat;
33 + int ret;
34 + size_t cached_count;
35 +};
36 +struct fdstat64_cache_entry fstat64_cache[FSTAT_CACHE_MAX] = {0 };
37 +
38 +static void fstat_cache_enable(int fd) {
39 + if(fd >= 0 && fd < FSTAT_CACHE_MAX) {
40 + fstat64_cache[fd].enabled = true;
41 + fstat64_cache[fd].updated = false;
42 + fstat64_cache[fd].cached_count = 0;
43 + }
44 +}
45 +
46 +static size_t fstat_cache_disable(int fd) {
47 + size_t cached_count = 0;
48 +
49 + if(fd >= 0 && fd < FSTAT_CACHE_MAX) {
50 + fstat64_cache[fd].enabled = false;
51 + fstat64_cache[fd].updated = false;
52 + cached_count = fstat64_cache[fd].cached_count;
53 + fstat64_cache[fd].cached_count = 0;
54 + }
55 +
56 + return cached_count;
57 +}
58 +
59 +static size_t fstat_calls = 0;
60 +static size_t fstat_cached_responses = 0;
61 +
62 +int fstat64(int fd, struct stat64 *buf) {
63 + static int (*real_fstat)(int, struct stat64 *) = NULL;
64 + if (!real_fstat)
65 + real_fstat = dlsym(RTLD_NEXT, "fstat64");
66 +
67 + fstat_calls++;
68 +
69 + if(fd >= 0 && fd < FSTAT_CACHE_MAX && fstat64_cache[fd].enabled && fstat64_cache[fd].updated) {
70 + fstat_cached_responses++;
71 + errno = fstat64_cache[fd].err_no;
72 + *buf = fstat64_cache[fd].stat;
73 + fstat64_cache[fd].cached_count++;
74 + return fstat64_cache[fd].ret;
75 + }
76 +
77 + int ret = real_fstat(fd, buf);
78 +
79 + if(fd >= 0 && fd < FSTAT_CACHE_MAX && fstat64_cache[fd].enabled) {
80 + fstat64_cache[fd].ret = ret;
81 + fstat64_cache[fd].updated = true;
82 + fstat64_cache[fd].err_no = errno;
83 + fstat64_cache[fd].stat = *buf;
84 + }
85 +
86 + return ret;
87 +}
88 +
89 +#endif // HAVE_SD_JOURNAL_OPEN_FILES_FD
90 +
91 +// ----------------------------------------------------------------------------
92 +
93 #define FACET_MAX_VALUE_LENGTH 8192
94 +#define SYSTEMD_JOURNAL_MAX_SOURCE_LEN 64
95
96 #define SYSTEMD_JOURNAL_FUNCTION_DESCRIPTION "View, search and analyze systemd journal entries."
97 #define SYSTEMD_JOURNAL_FUNCTION_NAME "systemd-journal"
19 -#define SYSTEMD_JOURNAL_DEFAULT_TIMEOUT 30
98 +#define SYSTEMD_JOURNAL_DEFAULT_TIMEOUT 60
99 #define SYSTEMD_JOURNAL_MAX_PARAMS 100
100 #define SYSTEMD_JOURNAL_DEFAULT_QUERY_DURATION (3 * 3600)
101 #define SYSTEMD_JOURNAL_DEFAULT_ITEMS_PER_QUERY 200
23 -#define SYSTEMD_JOURNAL_EXCESS_ROWS_ALLOWED 50
24 -#define SYSTEMD_JOURNAL_WORKER_THREADS 2
102 +#define SYSTEMD_JOURNAL_WORKER_THREADS 5
103
104 #define JOURNAL_PARAMETER_HELP "help"
105 #define JOURNAL_PARAMETER_AFTER "after"
@@ -36,22 +114,60 @@
114 #define JOURNAL_PARAMETER_DATA_ONLY "data_only"
115 #define JOURNAL_PARAMETER_SOURCE "source"
116 #define JOURNAL_PARAMETER_INFO "info"
117 +#define JOURNAL_PARAMETER_ID "id"
118 +#define JOURNAL_PARAMETER_PROGRESS "progress"
119 +#define JOURNAL_PARAMETER_SLICE "slice"
120 +#define JOURNAL_PARAMETER_DELTA "delta"
121 +#define JOURNAL_PARAMETER_TAIL "tail"
122 +
123 +#define JOURNAL_DEFAULT_SLICE_MODE true
124 +#define JOURNAL_DEFAULT_DIRECTION FACETS_ANCHOR_DIRECTION_BACKWARD
125
126 #define SYSTEMD_ALWAYS_VISIBLE_KEYS NULL
41 -#define SYSTEMD_KEYS_EXCLUDED_FROM_FACETS NULL
127 +
128 +#define SYSTEMD_KEYS_EXCLUDED_FROM_FACETS \
129 + "*MESSAGE*" \
130 + "|CODE_LINE" \
131 + "|*DOCUMENTATION*" \
132 + "|TID" \
133 + "|*_RAW" \
134 + "|*_NSEC" \
135 + "|*TIMESTAMP*" \
136 + "|*_ID" \
137 + "|*_ID_*" \
138 + "|*_PID" \
139 + "|*_TID" \
140 + "|__*" \
141 + ""
142 +
143 #define SYSTEMD_KEYS_INCLUDED_IN_FACETS \
43 - "_TRANSPORT" \
144 + "_COMM" \
145 + "|CONTAINER_NAME" \
146 + "|CONTAINER_TAG" \
147 + "|_TRANSPORT" \
148 "|SYSLOG_IDENTIFIER" \
149 "|SYSLOG_FACILITY" \
150 "|PRIORITY" \
47 - "|_UID" \
48 - "|_GID" \
151 "|_SYSTEMD_UNIT" \
152 "|_SYSTEMD_SLICE" \
51 - "|_COMM" \
153 + "|_SYSTEMD_USER_UNIT" \
154 + "|_SYSTEMD_USER_SLICE" \
155 + "|_SYSTEMD_OWNER_UID" \
156 + "|_UID" \
157 + "|_GID" \
158 "|UNIT" \
53 - "|CONTAINER_NAME" \
159 + "|USER_UNIT" \
160 "|IMAGE_NAME" \
161 + "|ERRNO" \
162 + "|_NAMESPACE" \
163 + "|COREDUMP_COMM" \
164 + "|COREDUMP_UNIT" \
165 + "|COREDUMP_USER_UNIT" \
166 + "|COREDUMP_SIGNAL_NAME" \
167 + "|COREDUMP_CGROUP" \
168 + "|_HOSTNAME" \
169 + "|UNIT_RESULT" \
170 + "|_RUNTIME_SCOPE" \
171 ""
172
173 static netdata_mutex_t stdout_mutex = NETDATA_MUTEX_INITIALIZER;
@@ -59,34 +175,9 @@ static bool plugin_should_exit = false;
175
176 // ----------------------------------------------------------------------------
177
62 -static inline sd_journal *netdata_open_systemd_journal(void) {
63 - sd_journal *j = NULL;
64 - int r;
65 -
66 - if(*netdata_configured_host_prefix) {
67 -#ifdef HAVE_SD_JOURNAL_OS_ROOT
68 - // Give our host prefix to systemd journal
69 - r = sd_journal_open_directory(&j, netdata_configured_host_prefix, SD_JOURNAL_OS_ROOT);
70 -#else
71 - char buf[FILENAME_MAX + 1];
72 - snprintfz(buf, FILENAME_MAX, "%s/var/log/journal", netdata_configured_host_prefix);
73 - r = sd_journal_open_directory(&j, buf, 0);
74 -#endif
75 - }
76 - else {
77 - // Open the system journal for reading
78 - r = sd_journal_open(&j, 0);
79 - }
80 -
81 - if (r < 0) {
82 - netdata_log_error("SYSTEMD-JOURNAL: Failed to open SystemD Journal, with error %d", r);
83 - return NULL;
84 - }
85 -
86 - return j;
87 -}
88 -
178 typedef enum {
179 + ND_SD_JOURNAL_NO_FILE_MATCHED,
180 + ND_SD_JOURNAL_FAILED_TO_OPEN,
181 ND_SD_JOURNAL_FAILED_TO_SEEK,
182 ND_SD_JOURNAL_TIMED_OUT,
183 ND_SD_JOURNAL_OK,
@@ -94,6 +185,69 @@ typedef enum {
185 ND_SD_JOURNAL_CANCELLED,
186 } ND_SD_JOURNAL_STATUS;
187
188 +typedef enum {
189 + SDJF_ALL = 0,
190 + SDJF_LOCAL = (1 << 0),
191 + SDJF_REMOTE = (1 << 1),
192 + SDJF_SYSTEM = (1 << 2),
193 + SDJF_USER = (1 << 3),
194 + SDJF_NAMESPACE = (1 << 4),
195 + SDJF_OTHER = (1 << 5),
196 +} SD_JOURNAL_FILE_SOURCE_TYPE;
197 +
198 +typedef struct function_query_status {
199 + bool *cancelled; // a pointer to the cancelling boolean
200 + usec_t stop_monotonic_ut;
201 +
202 + usec_t started_monotonic_ut;
203 +
204 + // request
205 + SD_JOURNAL_FILE_SOURCE_TYPE source_type;
206 + STRING *source;
207 + usec_t after_ut;
208 + usec_t before_ut;
209 +
210 + struct {
211 + usec_t start_ut;
212 + usec_t stop_ut;
213 + } anchor;
214 +
215 + FACETS_ANCHOR_DIRECTION direction;
216 + size_t entries;
217 + usec_t if_modified_since;
218 + bool delta;
219 + bool tail;
220 + bool data_only;
221 + bool slice;
222 + size_t filters;
223 + usec_t last_modified;
224 + const char *query;
225 + const char *histogram;
226 +
227 + // per file progress info
228 + size_t cached_count;
229 +
230 + // progress statistics
231 + usec_t matches_setup_ut;
232 + size_t rows_useful;
233 + size_t rows_read;
234 + size_t bytes_read;
235 + size_t files_matched;
236 + size_t file_working;
237 +} FUNCTION_QUERY_STATUS;
238 +
239 +struct journal_file {
240 + STRING *source;
241 + SD_JOURNAL_FILE_SOURCE_TYPE source_type;
242 + usec_t file_last_modified_ut;
243 + usec_t msg_first_ut;
244 + usec_t msg_last_ut;
245 + usec_t last_scan_ut;
246 + size_t size;
247 + bool logged_failure;
248 + usec_t max_journal_vs_realtime_delta_ut;
249 +};
250 +
251 static inline bool netdata_systemd_journal_seek_to(sd_journal *j, usec_t timestamp) {
252 if(sd_journal_seek_realtime_usec(j, timestamp) < 0) {
253 netdata_log_error("SYSTEMD-JOURNAL: Failed to seek to %" PRIu64, timestamp);
@@ -106,296 +260,1003 @@ static inline bool netdata_systemd_journal_seek_to(sd_journal *j, usec_t timesta
260 return true;
261 }
262
109 -static inline void netdata_systemd_journal_process_row(sd_journal *j, FACETS *facets) {
263 +#define JD_SOURCE_REALTIME_TIMESTAMP "_SOURCE_REALTIME_TIMESTAMP"
264 +
265 +#define JOURNAL_VS_REALTIME_DELTA_DEFAULT_UT (2 * USEC_PER_SEC) // assume always 2 seconds latency
266 +#define JOURNAL_VS_REALTIME_DELTA_MAX_UT (2 * 60 * USEC_PER_SEC) // up to 2 minutes delta
267 +
268 +static inline bool parse_journal_field(const char *data, size_t data_length, const char **key, size_t *key_length, const char **value, size_t *value_length) {
269 + const char *k = data;
270 + const char *equal = strchr(k, '=');
271 + if(unlikely(!equal))
272 + return false;
273 +
274 + size_t kl = equal - k;
275 +
276 + const char *v = ++equal;
277 + size_t vl = data_length - kl - 1;
278 +
279 + *key = k;
280 + *key_length = kl;
281 + *value = v;
282 + *value_length = vl;
283 +
284 + return true;
285 +}
286 +
287 +static inline size_t netdata_systemd_journal_process_row(sd_journal *j, FACETS *facets, struct journal_file *jf, usec_t *msg_ut) {
288 const void *data;
111 - size_t length;
289 + size_t length, bytes = 0;
290 +
291 SD_JOURNAL_FOREACH_DATA(j, data, length) {
113 - const char *key = data;
114 - const char *equal = strchr(key, '=');
115 - if(unlikely(!equal))
116 - continue;
292 + const char *key, *value;
293 + size_t key_length, value_length;
294
118 - const char *value = ++equal;
119 - size_t key_length = value - key; // including '\0'
295 + if(!parse_journal_field(data, length, &key, &key_length, &value, &value_length))
296 + continue;
297
121 - char key_copy[key_length];
122 - memcpy(key_copy, key, key_length - 1);
123 - key_copy[key_length - 1] = '\0';
298 + usec_t origin_journal_ut = *msg_ut;
299 +
300 + if(unlikely(key_length == sizeof(JD_SOURCE_REALTIME_TIMESTAMP) - 1 &&
301 + memcmp(key, JD_SOURCE_REALTIME_TIMESTAMP, sizeof(JD_SOURCE_REALTIME_TIMESTAMP) - 1) == 0)) {
302 + usec_t ut = str2ull(value, NULL);
303 + if(ut && ut < *msg_ut) {
304 + usec_t delta = *msg_ut - ut;
305 + *msg_ut = ut;
306 +
307 + if(delta > JOURNAL_VS_REALTIME_DELTA_MAX_UT)
308 + delta = JOURNAL_VS_REALTIME_DELTA_MAX_UT;
309 +
310 + // update max_journal_vs_realtime_delta_ut if the delta increased
311 + usec_t expected = jf->max_journal_vs_realtime_delta_ut;
312 + do {
313 + if(delta <= expected)
314 + break;
315 + } while(!__atomic_compare_exchange_n(&jf->max_journal_vs_realtime_delta_ut, &expected, delta, false, __ATOMIC_RELAXED, __ATOMIC_RELAXED));
316 +
317 + internal_error(delta > expected,
318 + "increased max_journal_vs_realtime_delta_ut from %"PRIu64" to %"PRIu64", "
319 + "journal %"PRIu64", actual %"PRIu64" (delta %"PRIu64")"
320 + , expected, delta, origin_journal_ut, *msg_ut, origin_journal_ut - (*msg_ut));
321 + }
322 + }
323
125 - size_t value_length = length - key_length; // without '\0'
126 - facets_add_key_value_length(facets, key_copy, key_length - 1, value, value_length <= FACET_MAX_VALUE_LENGTH ? value_length : FACET_MAX_VALUE_LENGTH);
324 + bytes += length;
325 + facets_add_key_value_length(facets, key, key_length, value, value_length <= FACET_MAX_VALUE_LENGTH ? value_length : FACET_MAX_VALUE_LENGTH);
326 }
327 +
328 + return bytes;
329 }
330
130 -static inline ND_SD_JOURNAL_STATUS check_stop(size_t row_counter, const bool *cancelled, usec_t stop_monotonic_ut) {
131 - if((row_counter % 1000) == 0) {
132 - if(cancelled && __atomic_load_n(cancelled, __ATOMIC_RELAXED)) {
133 - internal_error(true, "Function has been cancelled");
134 - return ND_SD_JOURNAL_CANCELLED;
135 - }
331 +#define FUNCTION_PROGRESS_UPDATE_ROWS(rows_read, rows) __atomic_fetch_add(&(rows_read), rows, __ATOMIC_RELAXED)
332 +#define FUNCTION_PROGRESS_UPDATE_BYTES(bytes_read, bytes) __atomic_fetch_add(&(bytes_read), bytes, __ATOMIC_RELAXED)
333 +#define FUNCTION_PROGRESS_EVERY_ROWS 10000
334
137 - if(now_monotonic_usec() > stop_monotonic_ut) {
138 - internal_error(true, "Function timed out");
139 - return ND_SD_JOURNAL_TIMED_OUT;
140 - }
335 +static inline ND_SD_JOURNAL_STATUS check_stop(const bool *cancelled, const usec_t *stop_monotonic_ut) {
336 + if(cancelled && __atomic_load_n(cancelled, __ATOMIC_RELAXED)) {
337 + internal_error(true, "Function has been cancelled");
338 + return ND_SD_JOURNAL_CANCELLED;
339 + }
340 +
341 + if(now_monotonic_usec() > __atomic_load_n(stop_monotonic_ut, __ATOMIC_RELAXED)) {
342 + internal_error(true, "Function timed out");
343 + return ND_SD_JOURNAL_TIMED_OUT;
344 }
345
346 return ND_SD_JOURNAL_OK;
347 }
348
146 -ND_SD_JOURNAL_STATUS netdata_systemd_journal_query_full(
349 +ND_SD_JOURNAL_STATUS netdata_systemd_journal_query_backward(
350 sd_journal *j, BUFFER *wb __maybe_unused, FACETS *facets,
148 - usec_t after_ut, usec_t before_ut,
149 - usec_t if_modified_since, usec_t stop_monotonic_ut, usec_t *last_modified,
150 - bool *cancelled) {
151 - if(!netdata_systemd_journal_seek_to(j, before_ut))
351 + struct journal_file *jf, FUNCTION_QUERY_STATUS *fqs) {
352 +
353 + usec_t anchor_delta = __atomic_load_n(&jf->max_journal_vs_realtime_delta_ut, __ATOMIC_RELAXED);
354 +
355 + usec_t start_ut = ((fqs->data_only && fqs->anchor.start_ut) ? fqs->anchor.start_ut : fqs->before_ut) + anchor_delta;
356 + usec_t stop_ut = (fqs->data_only && fqs->anchor.stop_ut) ? fqs->anchor.stop_ut : fqs->after_ut;
357 +
358 + if(!netdata_systemd_journal_seek_to(j, start_ut))
359 return ND_SD_JOURNAL_FAILED_TO_SEEK;
360
361 size_t errors_no_timestamp = 0;
155 - usec_t first_msg_ut = 0;
156 - size_t row_counter = 0;
157 -
158 - // the entries are not guaranteed to be sorted, so we process up to 100 entries beyond
159 - // the end of the query to find possibly useful logs for our time-frame
160 - size_t excess_rows_allowed = SYSTEMD_JOURNAL_EXCESS_ROWS_ALLOWED;
362 + usec_t earliest_msg_ut = 0;
363 + size_t row_counter = 0, last_row_counter = 0;
364 + size_t bytes = 0, last_bytes = 0;
365
366 ND_SD_JOURNAL_STATUS status = ND_SD_JOURNAL_OK;
367
368 facets_rows_begin(facets);
369 while (status == ND_SD_JOURNAL_OK && sd_journal_previous(j) > 0) {
166 - row_counter++;
167 -
168 - usec_t msg_ut;
169 - if(sd_journal_get_realtime_usec(j, &msg_ut) < 0) {
370 + usec_t msg_ut = 0;
371 + if(sd_journal_get_realtime_usec(j, &msg_ut) < 0 || !msg_ut) {
372 errors_no_timestamp++;
373 continue;
374 }
375
174 - if(unlikely(!first_msg_ut)) {
175 - if(msg_ut == if_modified_since) {
176 - return ND_SD_JOURNAL_NOT_MODIFIED;
177 - }
178 -
179 - first_msg_ut = msg_ut;
180 - }
376 + if(unlikely(msg_ut > earliest_msg_ut))
377 + earliest_msg_ut = msg_ut;
378
182 - if (msg_ut > before_ut)
379 + if (unlikely(msg_ut > start_ut))
380 continue;
381
185 - if (msg_ut < after_ut) {
186 - if(--excess_rows_allowed == 0)
187 - break;
382 + if (unlikely(msg_ut < stop_ut))
383 + break;
384
189 - continue;
385 + bytes += netdata_systemd_journal_process_row(j, facets, jf, &msg_ut);
386 + if(facets_row_finished(facets, msg_ut))
387 + fqs->rows_useful++;
388 +
389 + row_counter++;
390 + if(row_counter % 100 == 0 && fqs->data_only && facets_rows(facets) >= fqs->entries) {
391 + // stop the data only query
392 + usec_t oldest = facets_row_oldest_ut(facets);
393 + if(oldest && msg_ut < (oldest - anchor_delta))
394 + break;
395 }
396
192 - netdata_systemd_journal_process_row(j, facets);
193 - facets_row_finished(facets, msg_ut);
397 + if(row_counter % FUNCTION_PROGRESS_EVERY_ROWS == 0) {
398 + FUNCTION_PROGRESS_UPDATE_ROWS(fqs->rows_read, row_counter - last_row_counter);
399 + last_row_counter = row_counter;
400 +
401 + FUNCTION_PROGRESS_UPDATE_BYTES(fqs->bytes_read, bytes - last_bytes);
402 + last_bytes = bytes;
403
195 - status = check_stop(row_counter, cancelled, stop_monotonic_ut);
404 + status = check_stop(fqs->cancelled, &fqs->stop_monotonic_ut);
405 + }
406 }
407
408 + FUNCTION_PROGRESS_UPDATE_ROWS(fqs->rows_read, row_counter - last_row_counter);
409 + FUNCTION_PROGRESS_UPDATE_BYTES(fqs->bytes_read, bytes - last_bytes);
410 +
411 if(errors_no_timestamp)
412 netdata_log_error("SYSTEMD-JOURNAL: %zu lines did not have timestamps", errors_no_timestamp);
413
201 - *last_modified = first_msg_ut;
414 + if(earliest_msg_ut > fqs->last_modified)
415 + fqs->last_modified = earliest_msg_ut;
416
417 return status;
418 }
419
206 -ND_SD_JOURNAL_STATUS netdata_systemd_journal_query_data_forward(
420 +ND_SD_JOURNAL_STATUS netdata_systemd_journal_query_forward(
421 sd_journal *j, BUFFER *wb __maybe_unused, FACETS *facets,
208 - usec_t after_ut, usec_t before_ut,
209 - usec_t anchor, size_t entries, usec_t stop_monotonic_ut,
210 - bool *cancelled) {
422 + struct journal_file *jf, FUNCTION_QUERY_STATUS *fqs) {
423 +
424 + usec_t anchor_delta = __atomic_load_n(&jf->max_journal_vs_realtime_delta_ut, __ATOMIC_RELAXED);
425
212 - if(!netdata_systemd_journal_seek_to(j, anchor))
426 + usec_t start_ut = (fqs->data_only && fqs->anchor.start_ut) ? fqs->anchor.start_ut : fqs->after_ut;
427 + usec_t stop_ut = ((fqs->data_only && fqs->anchor.stop_ut) ? fqs->anchor.stop_ut : fqs->before_ut) + anchor_delta;
428 +
429 + if(!netdata_systemd_journal_seek_to(j, start_ut))
430 return ND_SD_JOURNAL_FAILED_TO_SEEK;
431
432 size_t errors_no_timestamp = 0;
216 - size_t row_counter = 0;
217 - size_t rows_added = 0;
218 -
219 - // the entries are not guaranteed to be sorted, so we process up to 100 entries beyond
220 - // the end of the query to find possibly useful logs for our time-frame
221 - size_t excess_rows_allowed = SYSTEMD_JOURNAL_EXCESS_ROWS_ALLOWED;
433 + usec_t earliest_msg_ut = 0;
434 + size_t row_counter = 0, last_row_counter = 0;
435 + size_t bytes = 0, last_bytes = 0;
436
437 ND_SD_JOURNAL_STATUS status = ND_SD_JOURNAL_OK;
438
439 facets_rows_begin(facets);
440 while (status == ND_SD_JOURNAL_OK && sd_journal_next(j) > 0) {
227 - row_counter++;
228 -
229 - usec_t msg_ut;
230 - if(sd_journal_get_realtime_usec(j, &msg_ut) < 0) {
441 + usec_t msg_ut = 0;
442 + if(sd_journal_get_realtime_usec(j, &msg_ut) < 0 || !msg_ut) {
443 errors_no_timestamp++;
444 continue;
445 }
446
235 - if (msg_ut > before_ut || msg_ut <= anchor)
447 + if(likely(msg_ut > earliest_msg_ut))
448 + earliest_msg_ut = msg_ut;
449 +
450 + if (unlikely(msg_ut < start_ut))
451 continue;
452
238 - if (msg_ut < after_ut) {
239 - if(--excess_rows_allowed == 0)
240 - break;
453 + if (unlikely(msg_ut > stop_ut))
454 + break;
455
242 - continue;
456 + bytes += netdata_systemd_journal_process_row(j, facets, jf, &msg_ut);
457 + if(facets_row_finished(facets, msg_ut))
458 + fqs->rows_useful++;
459 +
460 + row_counter++;
461 + if(row_counter % 100 == 0 && fqs->data_only && facets_rows(facets) >= fqs->entries) {
462 + usec_t newest = facets_row_newest_ut(facets);
463 + if(newest && msg_ut > (newest + anchor_delta))
464 + break;
465 }
466
245 - if(rows_added > entries && --excess_rows_allowed == 0)
246 - break;
467 + if(row_counter % FUNCTION_PROGRESS_EVERY_ROWS == 0) {
468 + FUNCTION_PROGRESS_UPDATE_ROWS(fqs->rows_read, row_counter - last_row_counter);
469 + last_row_counter = row_counter;
470
248 - netdata_systemd_journal_process_row(j, facets);
249 - facets_row_finished(facets, msg_ut);
250 - rows_added++;
471 + FUNCTION_PROGRESS_UPDATE_BYTES(fqs->bytes_read, bytes - last_bytes);
472 + last_bytes = bytes;
473
252 - status = check_stop(row_counter, cancelled, stop_monotonic_ut);
474 + status = check_stop(fqs->cancelled, &fqs->stop_monotonic_ut);
475 + }
476 }
477
478 + FUNCTION_PROGRESS_UPDATE_ROWS(fqs->rows_read, row_counter - last_row_counter);
479 + FUNCTION_PROGRESS_UPDATE_BYTES(fqs->bytes_read, bytes - last_bytes);
480 +
481 if(errors_no_timestamp)
482 netdata_log_error("SYSTEMD-JOURNAL: %zu lines did not have timestamps", errors_no_timestamp);
483
484 + if(earliest_msg_ut > fqs->last_modified)
485 + fqs->last_modified = earliest_msg_ut;
486 +
487 return status;
488 }
489
261 -ND_SD_JOURNAL_STATUS netdata_systemd_journal_query_data_backward(
262 - sd_journal *j, BUFFER *wb __maybe_unused, FACETS *facets,
263 - usec_t after_ut, usec_t before_ut,
264 - usec_t anchor, size_t entries, usec_t stop_monotonic_ut,
265 - bool *cancelled) {
490 +bool netdata_systemd_journal_check_if_modified_since(sd_journal *j, usec_t seek_to, usec_t last_modified) {
491 + // return true, if data have been modified since the timestamp
492
267 - if(!netdata_systemd_journal_seek_to(j, anchor))
268 - return ND_SD_JOURNAL_FAILED_TO_SEEK;
493 + if(!last_modified || !seek_to)
494 + return false;
495 +
496 + if(!netdata_systemd_journal_seek_to(j, seek_to))
497 + return false;
498 +
499 + usec_t first_msg_ut = 0;
500 + while (sd_journal_previous(j) > 0) {
501 + usec_t msg_ut;
502 + if(sd_journal_get_realtime_usec(j, &msg_ut) < 0)
503 + continue;
504 +
505 + first_msg_ut = msg_ut;
506 + break;
507 + }
508 +
509 + return first_msg_ut != last_modified;
510 +}
511 +
512 +#ifdef HAVE_SD_JOURNAL_RESTART_FIELDS
513 +static bool netdata_systemd_filtering_by_journal(sd_journal *j, FACETS *facets, FUNCTION_QUERY_STATUS *fqs) {
514 + const char *field = NULL;
515 + const void *data = NULL;
516 + size_t data_length;
517 + size_t added_keys = 0;
518 + size_t failures = 0;
519 + size_t filters_added = 0;
520 +
521 + SD_JOURNAL_FOREACH_FIELD(j, field) {
522 + bool interesting;
523 +
524 + if(fqs->data_only)
525 + interesting = facets_key_name_is_filter(facets, field);
526 + else
527 + interesting = facets_key_name_is_facet(facets, field);
528 +
529 + if(interesting) {
530 + if(sd_journal_query_unique(j, field) >= 0) {
531 + bool added_this_key = false;
532 + size_t added_values = 0;
533 +
534 + SD_JOURNAL_FOREACH_UNIQUE(j, data, data_length) {
535 + const char *key, *value;
536 + size_t key_length, value_length;
537 +
538 + if(!parse_journal_field(data, data_length, &key, &key_length, &value, &value_length))
539 + continue;
540 +
541 + facets_add_possible_value_name_to_key(facets, key, key_length, value, value_length);
542 +
543 + if(!facets_key_name_value_length_is_selected(facets, key, key_length, value, value_length))
544 + continue;
545 +
546 + if(added_keys && !added_this_key) {
547 + if(sd_journal_add_conjunction(j) < 0)
548 + failures++;
549 +
550 + added_this_key = true;
551 + added_keys++;
552 + }
553 + else if(added_values)
554 + if(sd_journal_add_disjunction(j) < 0)
555 + failures++;
556 +
557 + if(sd_journal_add_match(j, data, data_length) < 0)
558 + failures++;
559 +
560 + added_values++;
561 + filters_added++;
562 + }
563 + }
564 + }
565 + }
566 +
567 + if(failures) {
568 + netdata_log_error("failed to setup journal filter, will run the full query.");
569 + sd_journal_flush_matches(j);
570 + return true;
571 + }
572 +
573 + return filters_added ? true : false;
574 +}
575 +#endif // HAVE_SD_JOURNAL_RESTART_FIELDS
576 +
577 +static ND_SD_JOURNAL_STATUS netdata_systemd_journal_query_one_file(
578 + const char *filename, BUFFER *wb, FACETS *facets,
579 + struct journal_file *jf, FUNCTION_QUERY_STATUS *fqs) {
580 +
581 + sd_journal *j = NULL;
582 + errno = 0;
583 +
584 +#ifdef HAVE_SD_JOURNAL_OPEN_FILES_FD
585 + int fd = open(filename, O_RDONLY);
586 + fstat_cache_enable(fd);
587 +
588 + if(sd_journal_open_files_fd(&j, &fd, 1, ND_SD_JOURNAL_OPEN_FLAGS) < 0 || !j) {
589 + fqs->cached_count += fstat_cache_disable(fd);
590 + close(fd);
591 + return ND_SD_JOURNAL_FAILED_TO_OPEN;
592 + }
593 +#else // !HAVE_SD_JOURNAL_OPEN_FILES_FD
594 +
595 + const char *paths[2] = {
596 + [0] = filename,
597 + [1] = NULL,
598 + };
599 + if(sd_journal_open_files(&j, paths, ND_SD_JOURNAL_OPEN_FLAGS) < 0 || !j)
600 + return ND_SD_JOURNAL_FAILED_TO_OPEN;
601 +
602 +#endif // !HAVE_SD_JOURNAL_OPEN_FILES_FD
603 +
604 + ND_SD_JOURNAL_STATUS status;
605 + bool matches_filters = true;
606 +
607 +#ifdef HAVE_SD_JOURNAL_RESTART_FIELDS
608 + if(fqs->slice) {
609 + usec_t started = now_monotonic_usec();
610 +
611 + matches_filters = netdata_systemd_filtering_by_journal(j, facets, fqs) || !fqs->filters;
612 + usec_t ended = now_monotonic_usec();
613 +
614 + fqs->matches_setup_ut += (ended - started);
615 + }
616 +#endif // HAVE_SD_JOURNAL_RESTART_FIELDS
617 +
618 + if(matches_filters) {
619 + if(fqs->direction == FACETS_ANCHOR_DIRECTION_FORWARD)
620 + status = netdata_systemd_journal_query_forward(j, wb, facets, jf, fqs);
621 + else
622 + status = netdata_systemd_journal_query_backward(j, wb, facets, jf, fqs);
623 + }
624 + else
625 + status = ND_SD_JOURNAL_NO_FILE_MATCHED;
626 +
627 + sd_journal_close(j);
628 +
629 +#ifdef HAVE_SD_JOURNAL_OPEN_FILES_FD
630 + fqs->cached_count += fstat_cache_disable(fd);
631 + close(fd);
632 +#endif
633 +
634 + return status;
635 +}
636 +
637 +// ----------------------------------------------------------------------------
638 +// journal files registry
639 +
640 +#define VAR_LOG_JOURNAL_MAX_DEPTH 10
641 +#define MAX_JOURNAL_DIRECTORIES 100
642 +
643 +struct journal_directory {
644 + char *path;
645 + bool logged_failure;
646 +};
647 +
648 +static struct journal_directory journal_directories[MAX_JOURNAL_DIRECTORIES] = { 0 };
649 +static DICTIONARY *journal_files_registry = NULL;
650 +static DICTIONARY *used_hashes_registry = NULL;
651 +
652 +static usec_t systemd_journal_session = 0;
653 +
654 +static void buffer_json_journal_versions(BUFFER *wb) {
655 + buffer_json_member_add_object(wb, "versions");
656 + {
657 + buffer_json_member_add_uint64(wb, "sources",
658 + systemd_journal_session + dictionary_version(journal_files_registry));
659 + }
660 + buffer_json_object_close(wb);
661 +}
662 +
663 +static void journal_file_update_msg_ut(const char *filename, struct journal_file *jf) {
664 + const char *files[2] = {
665 + [0] = filename,
666 + [1] = NULL,
667 + };
668 +
669 + sd_journal *j = NULL;
670 + if(sd_journal_open_files(&j, files, ND_SD_JOURNAL_OPEN_FLAGS) < 0 || !j) {
671 + if(!jf->logged_failure) {
672 + netdata_log_error("cannot open journal file '%s', using file timestamps to understand time-frame.", filename);
673 + jf->logged_failure = true;
674 + }
675 +
676 + jf->msg_first_ut = 0;
677 + jf->msg_last_ut = jf->file_last_modified_ut;
678 + return;
679 + }
680 +
681 + usec_t first_ut = 0, last_ut = 0;
682 +
683 + if(sd_journal_seek_head(j) < 0 || sd_journal_next(j) < 0 || sd_journal_get_realtime_usec(j, &first_ut) < 0 || !first_ut) {
684 + internal_error(true, "cannot find the timestamp of the first message in '%s'", filename);
685 + first_ut = 0;
686 + }
687 +
688 + if(sd_journal_seek_tail(j) < 0 || sd_journal_previous(j) < 0 || sd_journal_get_realtime_usec(j, &last_ut) < 0 || !last_ut) {
689 + internal_error(true, "cannot find the timestamp of the last message in '%s'", filename);
690 + last_ut = jf->file_last_modified_ut;
691 + }
692 +
693 + sd_journal_close(j);
694 +
695 + if(first_ut > last_ut) {
696 + internal_error(true, "timestamps are flipped in file '%s'", filename);
697 + usec_t t = first_ut;
698 + first_ut = last_ut;
699 + last_ut = t;
700 + }
701 +
702 + jf->msg_first_ut = first_ut;
703 + jf->msg_last_ut = last_ut;
704 +}
705 +
706 +static STRING *string_strdupz_source(const char *s, const char *e, size_t max_len, const char *prefix) {
707 + char buf[max_len];
708 + size_t len;
709 + char *dst = buf;
710 +
711 + if(prefix) {
712 + len = strlen(prefix);
713 + memcpy(buf, prefix, len);
714 + dst = &buf[len];
715 + max_len -= len;
716 + }
717 +
718 + len = e - s;
719 + if(len >= max_len)
720 + len = max_len - 1;
721 + memcpy(dst, s, len);
722 + dst[len] = '\0';
723 + buf[max_len - 1] = '\0';
724 +
725 + for(size_t i = 0; buf[i] ;i++)
726 + if(!isalnum(buf[i]) && buf[i] != '-' && buf[i] != '.' && buf[i] != ':')
727 + buf[i] = '_';
728 +
729 + return string_strdupz(buf);
730 +}
731 +
732 +static void files_registry_insert_cb(const DICTIONARY_ITEM *item, void *value, void *data __maybe_unused) {
733 + struct journal_file *jf = value;
734 + const char *filename = dictionary_acquired_item_name(item);
735 +
736 + // based on the filename
737 + // decide the source to show to the user
738 + const char *s = strrchr(filename, '/');
739 + if(s) {
740 + if(strstr(filename, "/remote/"))
741 + jf->source_type = SDJF_REMOTE;
742 + else {
743 + const char *t = s - 1;
744 + while(t >= filename && *t != '.' && *t != '/')
745 + t--;
746 +
747 + if(t >= filename && *t == '.') {
748 + jf->source_type = SDJF_NAMESPACE;
749 + jf->source = string_strdupz_source(t + 1, s, SYSTEMD_JOURNAL_MAX_SOURCE_LEN, "namespace-");
750 + }
751 + else
752 + jf->source_type = SDJF_LOCAL;
753 + }
754 +
755 + if(strncmp(s, "/system", 7) == 0)
756 + jf->source_type |= SDJF_SYSTEM;
757 +
758 + else if(strncmp(s, "/user", 5) == 0)
759 + jf->source_type |= SDJF_USER;
760 +
761 + else if(strncmp(s, "/remote-", 8) == 0) {
762 + jf->source_type |= SDJF_REMOTE;
763 +
764 + s = &s[8]; // skip "/remote-"
765 +
766 + char *e = strchr(s, '@');
767 + if(!e)
768 + e = strstr(s, ".journal");
769 +
770 + if(e) {
771 + const char *d = s;
772 + for(; d < e && (isdigit(*d) || *d == '.' || *d == ':') ; d++) ;
773 + if(d == e) {
774 + // a valid IP address
775 + char ip[e - s + 1];
776 + memcpy(ip, s, e - s);
777 + ip[e - s] = '\0';
778 + char buf[SYSTEMD_JOURNAL_MAX_SOURCE_LEN];
779 + if(ip_to_hostname(ip, buf, sizeof(buf)))
780 + jf->source = string_strdupz_source(buf, &buf[strlen(buf)], SYSTEMD_JOURNAL_MAX_SOURCE_LEN, "remote-");
781 + else {
782 + internal_error(true, "Cannot find the hostname for IP '%s'", ip);
783 + jf->source = string_strdupz_source(s, e, SYSTEMD_JOURNAL_MAX_SOURCE_LEN, "remote-");
784 + }
785 + }
786 + else
787 + jf->source = string_strdupz_source(s, e, SYSTEMD_JOURNAL_MAX_SOURCE_LEN, "remote-");
788 + }
789 + else
790 + jf->source_type |= SDJF_OTHER;
791 + }
792 + else
793 + jf->source_type |= SDJF_OTHER;
794 + }
795 + else
796 + jf->source_type = SDJF_LOCAL | SDJF_OTHER;
797 +
798 + journal_file_update_msg_ut(filename, jf);
799 +
800 + internal_error(true,
801 + "found journal file '%s', type %d, source '%s', "
802 + "file modified: %"PRIu64", "
803 + "msg {first: %"PRIu64", last: %"PRIu64"}",
804 + filename, jf->source_type, jf->source ? string2str(jf->source) : "<unset>",
805 + jf->file_last_modified_ut,
806 + jf->msg_first_ut, jf->msg_last_ut);
807 +}
808 +
809 +static bool files_registry_conflict_cb(const DICTIONARY_ITEM *item, void *old_value, void *new_value, void *data __maybe_unused) {
810 + struct journal_file *jf = old_value;
811 + struct journal_file *njf = new_value;
812 +
813 + if(njf->last_scan_ut > jf->last_scan_ut)
814 + jf->last_scan_ut = njf->last_scan_ut;
815 +
816 + if(njf->file_last_modified_ut > jf->file_last_modified_ut) {
817 + jf->file_last_modified_ut = njf->file_last_modified_ut;
818 + jf->size = njf->size;
819 +
820 + const char *filename = dictionary_acquired_item_name(item);
821 + journal_file_update_msg_ut(filename, jf);
822 +
823 +// internal_error(true,
824 +// "updated journal file '%s', type %d, "
825 +// "file modified: %"PRIu64", "
826 +// "msg {first: %"PRIu64", last: %"PRIu64"}",
827 +// filename, jf->source_type,
828 +// jf->file_last_modified_ut,
829 +// jf->msg_first_ut, jf->msg_last_ut);
830 + }
831 +
832 + return false;
833 +}
834 +
835 +#define SDJF_SOURCE_ALL_NAME "all"
836 +#define SDJF_SOURCE_LOCAL_NAME "all-local-logs"
837 +#define SDJF_SOURCE_LOCAL_SYSTEM_NAME "all-local-system-logs"
838 +#define SDJF_SOURCE_LOCAL_USERS_NAME "all-local-user-logs"
839 +#define SDJF_SOURCE_LOCAL_OTHER_NAME "all-uncategorized"
840 +#define SDJF_SOURCE_NAMESPACES_NAME "all-local-namespaces"
841 +#define SDJF_SOURCE_REMOTES_NAME "all-remote-systems"
842 +
843 +struct journal_file_source {
844 + usec_t first_ut;
845 + usec_t last_ut;
846 + size_t count;
847 + uint64_t size;
848 +};
849 +
850 +static void human_readable_size_ib(uint64_t size, char *dst, size_t dst_len) {
851 + if(size > 1024ULL * 1024 * 1024 * 1024)
852 + snprintfz(dst, dst_len, "%0.2f TiB", (double)size / 1024.0 / 1024.0 / 1024.0 / 1024.0);
853 + else if(size > 1024ULL * 1024 * 1024)
854 + snprintfz(dst, dst_len, "%0.2f GiB", (double)size / 1024.0 / 1024.0 / 1024.0);
855 + else if(size > 1024ULL * 1024)
856 + snprintfz(dst, dst_len, "%0.2f MiB", (double)size / 1024.0 / 1024.0);
857 + else if(size > 1024ULL)
858 + snprintfz(dst, dst_len, "%0.2f KiB", (double)size / 1024.0);
859 + else
860 + snprintfz(dst, dst_len, "%"PRIu64" B", size);
861 +}
862 +
863 +#define print_duration(dst, dst_len, pos, remaining, duration, one, many, printed) do { \
864 + if((remaining) > (duration)) { \
865 + uint64_t _count = (remaining) / (duration); \
866 + uint64_t _rem = (remaining) - (_count * (duration)); \
867 + (pos) += snprintfz(&(dst)[pos], (dst_len) - (pos), "%s%s%"PRIu64" %s", (printed) ? ", " : "", _rem ? "" : "and ", _count, _count > 1 ? (many) : (one)); \
868 + (remaining) = _rem; \
869 + (printed) = true; \
870 + } \
871 +} while(0)
872 +
873 +static void human_readable_duration_s(time_t duration_s, char *dst, size_t dst_len) {
874 + if(duration_s < 0)
875 + duration_s = -duration_s;
876 +
877 + size_t pos = 0;
878 + dst[0] = 0 ;
879 +
880 + bool printed = false;
881 + print_duration(dst, dst_len, pos, duration_s, 86400 * 365, "year", "years", printed);
882 + print_duration(dst, dst_len, pos, duration_s, 86400 * 30, "month", "months", printed);
883 + print_duration(dst, dst_len, pos, duration_s, 86400 * 1, "day", "days", printed);
884 + print_duration(dst, dst_len, pos, duration_s, 3600 * 1, "hour", "hours", printed);
885 + print_duration(dst, dst_len, pos, duration_s, 60 * 1, "min", "mins", printed);
886 + print_duration(dst, dst_len, pos, duration_s, 1, "sec", "secs", printed);
887 +}
888 +
889 +static int journal_file_to_json_array_cb(const DICTIONARY_ITEM *item, void *entry, void *data) {
890 + struct journal_file_source *jfs = entry;
891 + BUFFER *wb = data;
892 +
893 + const char *name = dictionary_acquired_item_name(item);
894 +
895 + buffer_json_add_array_item_object(wb);
896 + {
897 + char size_for_humans[100];
898 + human_readable_size_ib(jfs->size, size_for_humans, sizeof(size_for_humans));
899 +
900 + char duration_for_humans[1024];
901 + human_readable_duration_s((time_t)((jfs->last_ut - jfs->first_ut) / USEC_PER_SEC),
902 + duration_for_humans, sizeof(duration_for_humans));
903 +
904 + char info[1024];
905 + snprintfz(info, sizeof(info), "%zu files, with a total size of %s, covering %s",
906 + jfs->count, size_for_humans, duration_for_humans);
907 +
908 + buffer_json_member_add_string(wb, "id", name);
909 + buffer_json_member_add_string(wb, "name", name);
910 + buffer_json_member_add_string(wb, "pill", size_for_humans);
911 + buffer_json_member_add_string(wb, "info", info);
912 + }
913 + buffer_json_object_close(wb); // options object
914 +
915 + return 1;
916 +}
917 +
918 +static bool journal_file_merge_sizes(const DICTIONARY_ITEM *item __maybe_unused, void *old_value, void *new_value , void *data __maybe_unused) {
919 + struct journal_file_source *jfs = old_value, *njfs = new_value;
920 + jfs->count += njfs->count;
921 + jfs->size += njfs->size;
922 +
923 + if(njfs->first_ut && njfs->first_ut < jfs->first_ut)
924 + jfs->first_ut = njfs->first_ut;
925 +
926 + if(njfs->last_ut && njfs->last_ut > jfs->last_ut)
927 + jfs->last_ut = njfs->last_ut;
928 +
929 + return false;
930 +}
931 +
932 +static void available_journal_file_sources_to_json_array(BUFFER *wb) {
933 + DICTIONARY *dict = dictionary_create(DICT_OPTION_SINGLE_THREADED|DICT_OPTION_NAME_LINK_DONT_CLONE|DICT_OPTION_DONT_OVERWRITE_VALUE);
934 + dictionary_register_conflict_callback(dict, journal_file_merge_sizes, NULL);
935 +
936 + struct journal_file_source t = { 0 };
937 +
938 + struct journal_file *jf;
939 + dfe_start_read(journal_files_registry, jf) {
940 + t.first_ut = jf->msg_first_ut;
941 + t.last_ut = jf->msg_last_ut;
942 + t.count = 1;
943 + t.size = jf->size;
944 +
945 + dictionary_set(dict, SDJF_SOURCE_ALL_NAME, &t, sizeof(t));
946 +
947 + if((jf->source_type & (SDJF_LOCAL)) == (SDJF_LOCAL))
948 + dictionary_set(dict, SDJF_SOURCE_LOCAL_NAME, &t, sizeof(t));
949 + if((jf->source_type & (SDJF_LOCAL | SDJF_SYSTEM)) == (SDJF_LOCAL | SDJF_SYSTEM))
950 + dictionary_set(dict, SDJF_SOURCE_LOCAL_SYSTEM_NAME, &t, sizeof(t));
951 + if((jf->source_type & (SDJF_LOCAL | SDJF_USER)) == (SDJF_LOCAL | SDJF_USER))
952 + dictionary_set(dict, SDJF_SOURCE_LOCAL_USERS_NAME, &t, sizeof(t));
953 + if((jf->source_type & (SDJF_LOCAL | SDJF_OTHER)) == (SDJF_LOCAL | SDJF_OTHER))
954 + dictionary_set(dict, SDJF_SOURCE_LOCAL_OTHER_NAME, &t, sizeof(t));
955 + if((jf->source_type & (SDJF_NAMESPACE)) == (SDJF_NAMESPACE))
956 + dictionary_set(dict, SDJF_SOURCE_NAMESPACES_NAME, &t, sizeof(t));
957 + if((jf->source_type & (SDJF_REMOTE)) == (SDJF_REMOTE))
958 + dictionary_set(dict, SDJF_SOURCE_REMOTES_NAME, &t, sizeof(t));
959 + if(jf->source)
960 + dictionary_set(dict, string2str(jf->source), &t, sizeof(t));
961 + }
962 + dfe_done(jf);
963 +
964 + dictionary_sorted_walkthrough_read(dict, journal_file_to_json_array_cb, wb);
965 +
966 + dictionary_destroy(dict);
967 +}
968 +
969 +static void files_registry_delete_cb(const DICTIONARY_ITEM *item, void *value, void *data __maybe_unused) {
970 + struct journal_file *jf = value; (void)jf;
971 + const char *filename = dictionary_acquired_item_name(item); (void)filename;
972 +
973 + string_freez(jf->source);
974 + internal_error(true, "removed journal file '%s'", filename);
975 +}
976 +
977 +void journal_directory_scan(const char *dirname, int depth, usec_t last_scan_ut) {
978 + static const char *ext = ".journal";
979 + static const size_t ext_len = sizeof(".journal") - 1;
980 +
981 + if (depth > VAR_LOG_JOURNAL_MAX_DEPTH)
982 + return;
983 +
984 + DIR *dir;
985 + struct dirent *entry;
986 + struct stat info;
987 + char absolute_path[FILENAME_MAX];
988 +
989 + // Open the directory.
990 + if ((dir = opendir(dirname)) == NULL) {
991 + if(errno != ENOENT && errno != ENOTDIR)
992 + netdata_log_error("Cannot opendir() '%s'", dirname);
993 + return;
994 + }
995 +
996 + // Read each entry in the directory.
997 + while ((entry = readdir(dir)) != NULL) {
998 + snprintfz(absolute_path, sizeof(absolute_path), "%s/%s", dirname, entry->d_name);
999 + if (stat(absolute_path, &info) != 0) {
1000 + netdata_log_error("Failed to stat() '%s", absolute_path);
1001 + continue;
1002 + }
1003 +
1004 + if (S_ISDIR(info.st_mode)) {
1005 + // If entry is a directory, call traverse recursively.
1006 + if (strcmp(entry->d_name, ".") != 0 && strcmp(entry->d_name, "..") != 0)
1007 + journal_directory_scan(absolute_path, depth + 1, last_scan_ut);
1008 +
1009 + }
1010 + else if (S_ISREG(info.st_mode)) {
1011 + // If entry is a regular file, check if it ends with .journal.
1012 + char *filename = entry->d_name;
1013 + size_t len = strlen(filename);
1014 +
1015 + if (len > ext_len && strcmp(filename + len - ext_len, ext) == 0) {
1016 + struct journal_file t = {
1017 + .file_last_modified_ut = info.st_mtim.tv_sec * USEC_PER_SEC + info.st_mtim.tv_nsec / NSEC_PER_USEC,
1018 + .last_scan_ut = last_scan_ut,
1019 + .size = info.st_size,
1020 + .max_journal_vs_realtime_delta_ut = JOURNAL_VS_REALTIME_DELTA_DEFAULT_UT,
1021 + };
1022 + dictionary_set(journal_files_registry, absolute_path, &t, sizeof(t));
1023 + }
1024 + }
1025 + }
1026 +
1027 + closedir(dir);
1028 +}
1029 +
1030 +static void journal_files_registry_update() {
1031 + usec_t scan_ut = now_monotonic_usec();
1032 +
1033 + for(unsigned i = 0; i < MAX_JOURNAL_DIRECTORIES ;i++) {
1034 + if(!journal_directories[i].path)
1035 + break;
1036 +
1037 + journal_directory_scan(journal_directories[i].path, 0, scan_ut);
1038 + }
1039 +
1040 + struct journal_file *jf;
1041 + dfe_start_write(journal_files_registry, jf) {
1042 + if(jf->last_scan_ut < scan_ut)
1043 + dictionary_del(journal_files_registry, jf_dfe.name);
1044 + }
1045 + dfe_done(jf);
1046 +}
1047 +
1048 +// ----------------------------------------------------------------------------
1049 +
1050 +static bool jf_is_mine(struct journal_file *jf, FUNCTION_QUERY_STATUS *fqs) {
1051 +
1052 + if((fqs->source_type == SDJF_ALL || (jf->source_type & fqs->source_type) == fqs->source_type) &&
1053 + (!fqs->source || fqs->source == jf->source)) {
1054 +
1055 + usec_t anchor_delta = JOURNAL_VS_REALTIME_DELTA_MAX_UT;
1056 + usec_t first_ut = jf->msg_first_ut;
1057 + usec_t last_ut = jf->msg_last_ut + anchor_delta;
1058 +
1059 + if(last_ut >= fqs->after_ut && first_ut <= fqs->before_ut)
1060 + return true;
1061 + }
1062 +
1063 + return false;
1064 +}
1065 +
1066 +static int journal_file_dict_items_backward_compar(const void *a, const void *b) {
1067 + const DICTIONARY_ITEM **ad = (const DICTIONARY_ITEM **)a, **bd = (const DICTIONARY_ITEM **)b;
1068 + struct journal_file *jfa = dictionary_acquired_item_value(*ad);
1069 + struct journal_file *jfb = dictionary_acquired_item_value(*bd);
1070 +
1071 + if(jfa->msg_last_ut < jfb->msg_last_ut)
1072 + return 1;
1073 +
1074 + if(jfa->msg_last_ut > jfb->msg_last_ut)
1075 + return -1;
1076 +
1077 + if(jfa->msg_first_ut < jfb->msg_first_ut)
1078 + return 1;
1079 +
1080 + if(jfa->msg_first_ut > jfb->msg_first_ut)
1081 + return -1;
1082 +
1083 + return 0;
1084 +}
1085
270 - size_t errors_no_timestamp = 0;
271 - size_t row_counter = 0;
272 - size_t rows_added = 0;
1086 +static int journal_file_dict_items_forward_compar(const void *a, const void *b) {
1087 + return -journal_file_dict_items_backward_compar(a, b);
1088 +}
1089
274 - // the entries are not guaranteed to be sorted, so we process up to 100 entries beyond
275 - // the end of the query to find possibly useful logs for our time-frame
276 - size_t excess_rows_allowed = SYSTEMD_JOURNAL_EXCESS_ROWS_ALLOWED;
1090 +static int netdata_systemd_journal_query(BUFFER *wb, FACETS *facets, FUNCTION_QUERY_STATUS *fqs) {
1091 + ND_SD_JOURNAL_STATUS status = ND_SD_JOURNAL_NO_FILE_MATCHED;
1092 + struct journal_file *jf;
1093
278 - ND_SD_JOURNAL_STATUS status = ND_SD_JOURNAL_OK;
1094 + fqs->files_matched = 0;
1095 + fqs->file_working = 0;
1096
280 - facets_rows_begin(facets);
281 - while (status == ND_SD_JOURNAL_OK && sd_journal_previous(j) > 0) {
282 - row_counter++;
1097 + size_t files_used = 0;
1098 + size_t files_max = dictionary_entries(journal_files_registry);
1099 + const DICTIONARY_ITEM *file_items[files_max];
1100
284 - usec_t msg_ut;
285 - if(sd_journal_get_realtime_usec(j, &msg_ut) < 0) {
286 - errors_no_timestamp++;
1101 + // count the files
1102 + bool files_are_newer = false;
1103 + dfe_start_read(journal_files_registry, jf) {
1104 + if(!jf_is_mine(jf, fqs))
1105 continue;
288 - }
1106
290 - if (msg_ut > before_ut || msg_ut >= anchor)
291 - continue;
1107 + file_items[files_used++] = dictionary_acquired_item_dup(journal_files_registry, jf_dfe.item);
1108
293 - if (msg_ut < after_ut) {
294 - if(--excess_rows_allowed == 0)
295 - break;
1109 + if(jf->msg_last_ut > fqs->if_modified_since)
1110 + files_are_newer = true;
1111 + }
1112 + dfe_done(jf);
1113
297 - continue;
298 - }
1114 + fqs->files_matched = files_used;
1115
300 - if(rows_added > entries && --excess_rows_allowed == 0)
301 - break;
1116 + if(fqs->if_modified_since && !files_are_newer) {
1117 + buffer_flush(wb);
1118 + return HTTP_RESP_NOT_MODIFIED;
1119 + }
1120
303 - netdata_systemd_journal_process_row(j, facets);
304 - facets_row_finished(facets, msg_ut);
305 - rows_added++;
1121 + // We will not do an if_modified_since query
1122 + // we know something changed in the files
1123 + fqs->if_modified_since = 0;
1124
307 - status = check_stop(row_counter, cancelled, stop_monotonic_ut);
1125 + // sort the files, so that they are optimal for facets
1126 + if(files_used >= 2) {
1127 + if (fqs->direction == FACETS_ANCHOR_DIRECTION_BACKWARD)
1128 + qsort(file_items, files_used, sizeof(const DICTIONARY_ITEM *),
1129 + journal_file_dict_items_backward_compar);
1130 + else
1131 + qsort(file_items, files_used, sizeof(const DICTIONARY_ITEM *),
1132 + journal_file_dict_items_forward_compar);
1133 }
1134
310 - if(errors_no_timestamp)
311 - netdata_log_error("SYSTEMD-JOURNAL: %zu lines did not have timestamps", errors_no_timestamp);
1135 + bool partial = false;
1136 + usec_t started_ut;
1137 + usec_t ended_ut = now_monotonic_usec();
1138
313 - return status;
314 -}
1139 + buffer_json_member_add_array(wb, "_journal_files");
1140 + for(size_t f = 0; f < files_used ;f++) {
1141 + const char *filename = dictionary_acquired_item_name(file_items[f]);
1142 + jf = dictionary_acquired_item_value(file_items[f]);
1143
316 -bool netdata_systemd_journal_check_if_modified_since(sd_journal *j, usec_t seek_to, usec_t last_modified) {
317 - // return true, if data have been modified since the timestamp
1144 + if(!jf_is_mine(jf, fqs))
1145 + continue;
1146
319 - if(!last_modified || !seek_to)
320 - return false;
1147 + fqs->file_working++;
1148 + fqs->cached_count = 0;
1149
322 - if(!netdata_systemd_journal_seek_to(j, seek_to))
323 - return false;
1150 + size_t rows_useful = fqs->rows_useful;
1151 + size_t rows_read = fqs->rows_read;
1152 + size_t bytes_read = fqs->bytes_read;
1153 + size_t matches_setup_ut = fqs->matches_setup_ut;
1154
325 - usec_t first_msg_ut = 0;
326 - while (sd_journal_previous(j) > 0) {
327 - usec_t msg_ut;
328 - if(sd_journal_get_realtime_usec(j, &msg_ut) < 0)
329 - continue;
1155 + ND_SD_JOURNAL_STATUS tmp_status = netdata_systemd_journal_query_one_file(filename, wb, facets, jf, fqs);
1156
331 - first_msg_ut = msg_ut;
332 - break;
333 - }
1157 + rows_useful = fqs->rows_useful - rows_useful;
1158 + rows_read = fqs->rows_read - rows_read;
1159 + bytes_read = fqs->bytes_read - bytes_read;
1160 + matches_setup_ut = fqs->matches_setup_ut - matches_setup_ut;
1161
335 - return first_msg_ut != last_modified;
336 -}
1162 + started_ut = ended_ut;
1163 + ended_ut = now_monotonic_usec();
1164 + usec_t duration_ut = ended_ut - started_ut;
1165
338 -static int netdata_systemd_journal_query(BUFFER *wb, FACETS *facets,
339 - usec_t after_ut, usec_t before_ut,
340 - usec_t anchor, FACETS_ANCHOR_DIRECTION direction, size_t entries,
341 - usec_t if_modified_since, bool data_only,
342 - usec_t stop_monotonic_ut,
343 - bool *cancelled) {
344 - sd_journal *j = netdata_open_systemd_journal();
345 - if(!j)
346 - return HTTP_RESP_INTERNAL_SERVER_ERROR;
1166 + buffer_json_add_array_item_object(wb); // journal file
1167 + {
1168 + // information about the file
1169 + buffer_json_member_add_string(wb, "_filename", filename);
1170 + buffer_json_member_add_uint64(wb, "_source_type", jf->source_type);
1171 + buffer_json_member_add_string(wb, "_source", string2str(jf->source));
1172 + buffer_json_member_add_uint64(wb, "_last_modified_ut", jf->file_last_modified_ut);
1173 + buffer_json_member_add_uint64(wb, "_msg_first_ut", jf->msg_first_ut);
1174 + buffer_json_member_add_uint64(wb, "_msg_last_ut", jf->msg_last_ut);
1175 + buffer_json_member_add_uint64(wb, "_journal_vs_realtime_delta_ut", jf->max_journal_vs_realtime_delta_ut);
1176 +
1177 + // information about the current use of the file
1178 + buffer_json_member_add_uint64(wb, "duration_ut", ended_ut - started_ut);
1179 + buffer_json_member_add_uint64(wb, "rows_read", rows_read);
1180 + buffer_json_member_add_uint64(wb, "rows_useful", rows_useful);
1181 + buffer_json_member_add_double(wb, "rows_per_second", (double) rows_read / (double) duration_ut * (double) USEC_PER_SEC);
1182 + buffer_json_member_add_uint64(wb, "bytes_read", bytes_read);
1183 + buffer_json_member_add_double(wb, "bytes_per_second", (double) bytes_read / (double) duration_ut * (double) USEC_PER_SEC);
1184 + buffer_json_member_add_uint64(wb, "duration_matches_ut", matches_setup_ut);
1185 + }
1186 + buffer_json_object_close(wb); // journal file
1187
348 - usec_t last_modified = 0;
1188 + bool stop = false;
1189 + switch(tmp_status) {
1190 + case ND_SD_JOURNAL_OK:
1191 + case ND_SD_JOURNAL_NO_FILE_MATCHED:
1192 + status = (status == ND_SD_JOURNAL_OK) ? ND_SD_JOURNAL_OK : tmp_status;
1193 + break;
1194
350 - ND_SD_JOURNAL_STATUS status;
1195 + case ND_SD_JOURNAL_FAILED_TO_OPEN:
1196 + case ND_SD_JOURNAL_FAILED_TO_SEEK:
1197 + partial = true;
1198 + if(status == ND_SD_JOURNAL_NO_FILE_MATCHED)
1199 + status = tmp_status;
1200 + break;
1201
352 - if(data_only && anchor /* && !netdata_systemd_journal_check_if_modified_since(j, before_ut, if_modified_since) */) {
353 - facets_data_only_mode(facets);
1202 + case ND_SD_JOURNAL_CANCELLED:
1203 + case ND_SD_JOURNAL_TIMED_OUT:
1204 + partial = true;
1205 + stop = true;
1206 + status = tmp_status;
1207 + break;
1208
355 - // we can do a data-only query
356 - if(direction == FACETS_ANCHOR_DIRECTION_FORWARD)
357 - status = netdata_systemd_journal_query_data_forward(j, wb, facets, after_ut, before_ut, anchor, entries, stop_monotonic_ut, cancelled);
358 - else
359 - status = netdata_systemd_journal_query_data_backward(j, wb, facets, after_ut, before_ut, anchor, entries, stop_monotonic_ut, cancelled);
360 - }
361 - else {
362 - // we have to do a full query
363 - status = netdata_systemd_journal_query_full(j, wb, facets,
364 - after_ut, before_ut, if_modified_since,
365 - stop_monotonic_ut, &last_modified, cancelled);
1209 + case ND_SD_JOURNAL_NOT_MODIFIED:
1210 + internal_fatal(true, "this should never be returned here");
1211 + break;
1212 + }
1213 +
1214 + if(stop)
1215 + break;
1216 }
1217 + buffer_json_array_close(wb); // _journal_files
1218
368 - sd_journal_close(j);
1219 + // release the files
1220 + for(size_t f = 0; f < files_used ;f++)
1221 + dictionary_acquired_item_release(journal_files_registry, file_items[f]);
1222
370 - if(status != ND_SD_JOURNAL_OK && status != ND_SD_JOURNAL_TIMED_OUT) {
371 - buffer_flush(wb);
1223 + switch (status) {
1224 + case ND_SD_JOURNAL_OK:
1225 + case ND_SD_JOURNAL_TIMED_OUT:
1226 + case ND_SD_JOURNAL_NO_FILE_MATCHED:
1227 + break;
1228
373 - switch (status) {
374 - case ND_SD_JOURNAL_CANCELLED:
375 - return HTTP_RESP_CLIENT_CLOSED_REQUEST;
1229 + case ND_SD_JOURNAL_CANCELLED:
1230 + buffer_flush(wb);
1231 + return HTTP_RESP_CLIENT_CLOSED_REQUEST;
1232
377 - case ND_SD_JOURNAL_NOT_MODIFIED:
378 - return HTTP_RESP_NOT_MODIFIED;
1233 + case ND_SD_JOURNAL_NOT_MODIFIED:
1234 + buffer_flush(wb);
1235 + return HTTP_RESP_NOT_MODIFIED;
1236
380 - default:
381 - case ND_SD_JOURNAL_FAILED_TO_SEEK:
382 - return HTTP_RESP_INTERNAL_SERVER_ERROR;
383 - }
1237 + default:
1238 + case ND_SD_JOURNAL_FAILED_TO_OPEN:
1239 + case ND_SD_JOURNAL_FAILED_TO_SEEK:
1240 + buffer_flush(wb);
1241 + return HTTP_RESP_INTERNAL_SERVER_ERROR;
1242 }
1243
1244 buffer_json_member_add_uint64(wb, "status", HTTP_RESP_OK);
387 - buffer_json_member_add_boolean(wb, "partial", status != ND_SD_JOURNAL_OK);
1245 + buffer_json_member_add_boolean(wb, "partial", partial);
1246 buffer_json_member_add_string(wb, "type", "table");
1247
390 - if(!data_only) {
1248 + if(!fqs->data_only) {
1249 buffer_json_member_add_time_t(wb, "update_every", 1);
1250 buffer_json_member_add_string(wb, "help", SYSTEMD_JOURNAL_FUNCTION_DESCRIPTION);
393 - buffer_json_member_add_uint64(wb, "last_modified", last_modified);
1251 }
1252
396 - facets_report(facets, wb);
1253 + if(!fqs->data_only || fqs->tail)
1254 + buffer_json_member_add_uint64(wb, "last_modified", fqs->last_modified);
1255 +
1256 + facets_sort_and_reorder_keys(facets);
1257 + facets_report(facets, wb, used_hashes_registry);
1258
398 - buffer_json_member_add_time_t(wb, "expires", now_realtime_sec() + (data_only ? 3600 : 0));
1259 + buffer_json_member_add_time_t(wb, "expires", now_realtime_sec() + (fqs->data_only ? 3600 : 0));
1260 buffer_json_finalize(wb);
1261
1262 return HTTP_RESP_OK;
@@ -408,38 +1269,108 @@ static void netdata_systemd_journal_function_help(const char *transaction) {
1269 "\n"
1270 "%s\n"
1271 "\n"
411 - "The following filters are supported:\n"
1272 + "The following parameters are supported:\n"
1273 "\n"
413 - " help\n"
1274 + " "JOURNAL_PARAMETER_HELP"\n"
1275 " Shows this help message.\n"
1276 "\n"
416 - " before:TIMESTAMP\n"
1277 + " "JOURNAL_PARAMETER_ID":STRING\n"
1278 + " Caller supplied unique ID of the request.\n"
1279 + " This can be used later to request a progress report of the query.\n"
1280 + " Optional, but if omitted no `"JOURNAL_PARAMETER_PROGRESS"` can be requested.\n"
1281 + "\n"
1282 + " "JOURNAL_PARAMETER_INFO"\n"
1283 + " Request initial configuration information about the plugin.\n"
1284 + " The key entity returned is the required_params array, which includes\n"
1285 + " all the available systemd journal sources.\n"
1286 + " When `"JOURNAL_PARAMETER_INFO"` is requested, all other parameters are ignored.\n"
1287 + "\n"
1288 + " "JOURNAL_PARAMETER_DELTA"\n"
1289 + " When doing data queries, include deltas for histogram and facets.\n"
1290 + "\n"
1291 + " "JOURNAL_PARAMETER_TAIL"\n"
1292 + " Do a tail query, to return the newest items between the anchor and before.\n"
1293 + "\n"
1294 + " "JOURNAL_PARAMETER_PROGRESS"\n"
1295 + " Request a progress report (the `id` of a running query is required).\n"
1296 + " When `"JOURNAL_PARAMETER_PROGRESS"` is requested, only parameter `"JOURNAL_PARAMETER_ID"` is used.\n"
1297 + "\n"
1298 + " "JOURNAL_PARAMETER_DATA_ONLY"\n"
1299 + " Quickly respond with data requested, without generating a\n"
1300 + " histogram and facets counters.\n"
1301 + "\n"
1302 + " "JOURNAL_PARAMETER_SLICE":true or "JOURNAL_PARAMETER_SLICE":false\n"
1303 + " When it is turned on, the plugin is executing filtering via libsystemd,\n"
1304 + " utilizing all the available indexes of the journal files.\n"
1305 + " When it is off, only the time constraint is handled by libsystemd and\n"
1306 + " all filtering is done by the plugin.\n"
1307 + " The default is: %s\n"
1308 + "\n"
1309 + " "JOURNAL_PARAMETER_SOURCE":SOURCE\n"
1310 + " Query only the specified journal sources.\n"
1311 + " Do an `"JOURNAL_PARAMETER_INFO"` query to find the sources.\n"
1312 + "\n"
1313 + " "JOURNAL_PARAMETER_BEFORE":TIMESTAMP_IN_SECONDS\n"
1314 " Absolute or relative (to now) timestamp in seconds, to start the query.\n"
1315 " The query is always executed from the most recent to the oldest log entry.\n"
1316 " If not given the default is: now.\n"
1317 "\n"
421 - " after:TIMESTAMP\n"
1318 + " "JOURNAL_PARAMETER_AFTER":TIMESTAMP_IN_SECONDS\n"
1319 " Absolute or relative (to `before`) timestamp in seconds, to end the query.\n"
1320 " If not given, the default is %d.\n"
1321 "\n"
425 - " last:ITEMS\n"
1322 + " "JOURNAL_PARAMETER_LAST":ITEMS\n"
1323 " The number of items to return.\n"
1324 " The default is %d.\n"
1325 "\n"
429 - " anchor:NUMBER\n"
430 - " The `timestamp` of the item last received, to return log entries after that.\n"
431 - " If not given, the query will return the top `ITEMS` from the most recent.\n"
1326 + " "JOURNAL_PARAMETER_ANCHOR":TIMESTAMP_IN_MICROSECONDS\n"
1327 + " Return items relative to this timestamp.\n"
1328 + " The exact items to be returned depend on the query `"JOURNAL_PARAMETER_DIRECTION"`.\n"
1329 + "\n"
1330 + " "JOURNAL_PARAMETER_DIRECTION":forward or "JOURNAL_PARAMETER_DIRECTION":backward\n"
1331 + " When set to `backward` (default) the items returned are the newest before the\n"
1332 + " `"JOURNAL_PARAMETER_ANCHOR"`, (or `"JOURNAL_PARAMETER_BEFORE"` if `"JOURNAL_PARAMETER_ANCHOR"` is not set)\n"
1333 + " When set to `forward` the items returned are the oldest after the\n"
1334 + " `"JOURNAL_PARAMETER_ANCHOR"`, (or `"JOURNAL_PARAMETER_AFTER"` if `"JOURNAL_PARAMETER_ANCHOR"` is not set)\n"
1335 + " The default is: %s\n"
1336 + "\n"
1337 + " "JOURNAL_PARAMETER_QUERY":SIMPLE_PATTERN\n"
1338 + " Do a full text search to find the log entries matching the pattern given.\n"
1339 + " The plugin is searching for matches on all fields of the database.\n"
1340 + "\n"
1341 + " "JOURNAL_PARAMETER_IF_MODIFIED_SINCE":TIMESTAMP_IN_MICROSECONDS\n"
1342 + " Each successful response, includes a `last_modified` field.\n"
1343 + " By providing the timestamp to the `"JOURNAL_PARAMETER_IF_MODIFIED_SINCE"` parameter,\n"
1344 + " the plugin will return 200 with a successful response, or 304 if the source has not\n"
1345 + " been modified since that timestamp.\n"
1346 + "\n"
1347 + " "JOURNAL_PARAMETER_HISTOGRAM":facet_id\n"
1348 + " Use the given `facet_id` for the histogram.\n"
1349 + " This parameter is ignored in `"JOURNAL_PARAMETER_DATA_ONLY"` mode.\n"
1350 + "\n"
1351 + " "JOURNAL_PARAMETER_FACETS":facet_id1,facet_id2,facet_id3,...\n"
1352 + " Add the given facets to the list of fields for which analysis is required.\n"
1353 + " The plugin will offer both a histogram and facet value counters for its values.\n"
1354 + " This parameter is ignored in `"JOURNAL_PARAMETER_DATA_ONLY"` mode.\n"
1355 "\n"
1356 " facet_id:value_id1,value_id2,value_id3,...\n"
1357 " Apply filters to the query, based on the facet IDs returned.\n"
1358 " Each `facet_id` can be given once, but multiple `facet_ids` can be given.\n"
1359 "\n"
437 - "Filters can be combined. Each filter can be given only one time.\n"
1360 + " There is special mode. By specifying:\n"
1361 + "\n"
1362 + " - `"JOURNAL_PARAMETER_DIRECTION":forward`,\n"
1363 + " - `"JOURNAL_PARAMETER_ANCHOR":TIMESTAMP_IN_USEC`,\n"
1364 + " - `"JOURNAL_PARAMETER_DATA_ONLY"`, and\n"
1365 + " - `"JOURNAL_PARAMETER_IF_MODIFIED_SINCE":TIMESTAMP_IN_USEC`\n"
1366 + "\n"
1367 , program_name
1368 , SYSTEMD_JOURNAL_FUNCTION_NAME
1369 , SYSTEMD_JOURNAL_FUNCTION_DESCRIPTION
1370 + , JOURNAL_DEFAULT_SLICE_MODE ? "true" : "false" // slice
1371 , -SYSTEMD_JOURNAL_DEFAULT_QUERY_DURATION
1372 , SYSTEMD_JOURNAL_DEFAULT_ITEMS_PER_QUERY
1373 + , JOURNAL_DEFAULT_DIRECTION == FACETS_ANCHOR_DIRECTION_BACKWARD ? "backward" : "forward"
1374 );
1375
1376 netdata_mutex_lock(&stdout_mutex);
@@ -449,6 +1380,140 @@ static void netdata_systemd_journal_function_help(const char *transaction) {
1380 buffer_free(wb);
1381 }
1382
1383 +const char *errno_map[] = {
1384 + [1] = "1 (EPERM)", // "Operation not permitted",
1385 + [2] = "2 (ENOENT)", // "No such file or directory",
1386 + [3] = "3 (ESRCH)", // "No such process",
1387 + [4] = "4 (EINTR)", // "Interrupted system call",
1388 + [5] = "5 (EIO)", // "Input/output error",
1389 + [6] = "6 (ENXIO)", // "No such device or address",
1390 + [7] = "7 (E2BIG)", // "Argument list too long",
1391 + [8] = "8 (ENOEXEC)", // "Exec format error",
1392 + [9] = "9 (EBADF)", // "Bad file descriptor",
1393 + [10] = "10 (ECHILD)", // "No child processes",
1394 + [11] = "11 (EAGAIN)", // "Resource temporarily unavailable",
1395 + [12] = "12 (ENOMEM)", // "Cannot allocate memory",
1396 + [13] = "13 (EACCES)", // "Permission denied",
1397 + [14] = "14 (EFAULT)", // "Bad address",
1398 + [15] = "15 (ENOTBLK)", // "Block device required",
1399 + [16] = "16 (EBUSY)", // "Device or resource busy",
1400 + [17] = "17 (EEXIST)", // "File exists",
1401 + [18] = "18 (EXDEV)", // "Invalid cross-device link",
1402 + [19] = "19 (ENODEV)", // "No such device",
1403 + [20] = "20 (ENOTDIR)", // "Not a directory",
1404 + [21] = "21 (EISDIR)", // "Is a directory",
1405 + [22] = "22 (EINVAL)", // "Invalid argument",
1406 + [23] = "23 (ENFILE)", // "Too many open files in system",
1407 + [24] = "24 (EMFILE)", // "Too many open files",
1408 + [25] = "25 (ENOTTY)", // "Inappropriate ioctl for device",
1409 + [26] = "26 (ETXTBSY)", // "Text file busy",
1410 + [27] = "27 (EFBIG)", // "File too large",
1411 + [28] = "28 (ENOSPC)", // "No space left on device",
1412 + [29] = "29 (ESPIPE)", // "Illegal seek",
1413 + [30] = "30 (EROFS)", // "Read-only file system",
1414 + [31] = "31 (EMLINK)", // "Too many links",
1415 + [32] = "32 (EPIPE)", // "Broken pipe",
1416 + [33] = "33 (EDOM)", // "Numerical argument out of domain",
1417 + [34] = "34 (ERANGE)", // "Numerical result out of range",
1418 + [35] = "35 (EDEADLK)", // "Resource deadlock avoided",
1419 + [36] = "36 (ENAMETOOLONG)", // "File name too long",
1420 + [37] = "37 (ENOLCK)", // "No locks available",
1421 + [38] = "38 (ENOSYS)", // "Function not implemented",
1422 + [39] = "39 (ENOTEMPTY)", // "Directory not empty",
1423 + [40] = "40 (ELOOP)", // "Too many levels of symbolic links",
1424 + [42] = "42 (ENOMSG)", // "No message of desired type",
1425 + [43] = "43 (EIDRM)", // "Identifier removed",
1426 + [44] = "44 (ECHRNG)", // "Channel number out of range",
1427 + [45] = "45 (EL2NSYNC)", // "Level 2 not synchronized",
1428 + [46] = "46 (EL3HLT)", // "Level 3 halted",
1429 + [47] = "47 (EL3RST)", // "Level 3 reset",
1430 + [48] = "48 (ELNRNG)", // "Link number out of range",
1431 + [49] = "49 (EUNATCH)", // "Protocol driver not attached",
1432 + [50] = "50 (ENOCSI)", // "No CSI structure available",
1433 + [51] = "51 (EL2HLT)", // "Level 2 halted",
1434 + [52] = "52 (EBADE)", // "Invalid exchange",
1435 + [53] = "53 (EBADR)", // "Invalid request descriptor",
1436 + [54] = "54 (EXFULL)", // "Exchange full",
1437 + [55] = "55 (ENOANO)", // "No anode",
1438 + [56] = "56 (EBADRQC)", // "Invalid request code",
1439 + [57] = "57 (EBADSLT)", // "Invalid slot",
1440 + [59] = "59 (EBFONT)", // "Bad font file format",
1441 + [60] = "60 (ENOSTR)", // "Device not a stream",
1442 + [61] = "61 (ENODATA)", // "No data available",
1443 + [62] = "62 (ETIME)", // "Timer expired",
1444 + [63] = "63 (ENOSR)", // "Out of streams resources",
1445 + [64] = "64 (ENONET)", // "Machine is not on the network",
1446 + [65] = "65 (ENOPKG)", // "Package not installed",
1447 + [66] = "66 (EREMOTE)", // "Object is remote",
1448 + [67] = "67 (ENOLINK)", // "Link has been severed",
1449 + [68] = "68 (EADV)", // "Advertise error",
1450 + [69] = "69 (ESRMNT)", // "Srmount error",
1451 + [70] = "70 (ECOMM)", // "Communication error on send",
1452 + [71] = "71 (EPROTO)", // "Protocol error",
1453 + [72] = "72 (EMULTIHOP)", // "Multihop attempted",
1454 + [73] = "73 (EDOTDOT)", // "RFS specific error",
1455 + [74] = "74 (EBADMSG)", // "Bad message",
1456 + [75] = "75 (EOVERFLOW)", // "Value too large for defined data type",
1457 + [76] = "76 (ENOTUNIQ)", // "Name not unique on network",
1458 + [77] = "77 (EBADFD)", // "File descriptor in bad state",
1459 + [78] = "78 (EREMCHG)", // "Remote address changed",
1460 + [79] = "79 (ELIBACC)", // "Can not access a needed shared library",
1461 + [80] = "80 (ELIBBAD)", // "Accessing a corrupted shared library",
1462 + [81] = "81 (ELIBSCN)", // ".lib section in a.out corrupted",
1463 + [82] = "82 (ELIBMAX)", // "Attempting to link in too many shared libraries",
1464 + [83] = "83 (ELIBEXEC)", // "Cannot exec a shared library directly",
1465 + [84] = "84 (EILSEQ)", // "Invalid or incomplete multibyte or wide character",
1466 + [85] = "85 (ERESTART)", // "Interrupted system call should be restarted",
1467 + [86] = "86 (ESTRPIPE)", // "Streams pipe error",
1468 + [87] = "87 (EUSERS)", // "Too many users",
1469 + [88] = "88 (ENOTSOCK)", // "Socket operation on non-socket",
1470 + [89] = "89 (EDESTADDRREQ)", // "Destination address required",
1471 + [90] = "90 (EMSGSIZE)", // "Message too long",
1472 + [91] = "91 (EPROTOTYPE)", // "Protocol wrong type for socket",
1473 + [92] = "92 (ENOPROTOOPT)", // "Protocol not available",
1474 + [93] = "93 (EPROTONOSUPPORT)", // "Protocol not supported",
1475 + [94] = "94 (ESOCKTNOSUPPORT)", // "Socket type not supported",
1476 + [95] = "95 (ENOTSUP)", // "Operation not supported",
1477 + [96] = "96 (EPFNOSUPPORT)", // "Protocol family not supported",
1478 + [97] = "97 (EAFNOSUPPORT)", // "Address family not supported by protocol",
1479 + [98] = "98 (EADDRINUSE)", // "Address already in use",
1480 + [99] = "99 (EADDRNOTAVAIL)", // "Cannot assign requested address",
1481 + [100] = "100 (ENETDOWN)", // "Network is down",
1482 + [101] = "101 (ENETUNREACH)", // "Network is unreachable",
1483 + [102] = "102 (ENETRESET)", // "Network dropped connection on reset",
1484 + [103] = "103 (ECONNABORTED)", // "Software caused connection abort",
1485 + [104] = "104 (ECONNRESET)", // "Connection reset by peer",
1486 + [105] = "105 (ENOBUFS)", // "No buffer space available",
1487 + [106] = "106 (EISCONN)", // "Transport endpoint is already connected",
1488 + [107] = "107 (ENOTCONN)", // "Transport endpoint is not connected",
1489 + [108] = "108 (ESHUTDOWN)", // "Cannot send after transport endpoint shutdown",
1490 + [109] = "109 (ETOOMANYREFS)", // "Too many references: cannot splice",
1491 + [110] = "110 (ETIMEDOUT)", // "Connection timed out",
1492 + [111] = "111 (ECONNREFUSED)", // "Connection refused",
1493 + [112] = "112 (EHOSTDOWN)", // "Host is down",
1494 + [113] = "113 (EHOSTUNREACH)", // "No route to host",
1495 + [114] = "114 (EALREADY)", // "Operation already in progress",
1496 + [115] = "115 (EINPROGRESS)", // "Operation now in progress",
1497 + [116] = "116 (ESTALE)", // "Stale file handle",
1498 + [117] = "117 (EUCLEAN)", // "Structure needs cleaning",
1499 + [118] = "118 (ENOTNAM)", // "Not a XENIX named type file",
1500 + [119] = "119 (ENAVAIL)", // "No XENIX semaphores available",
1501 + [120] = "120 (EISNAM)", // "Is a named type file",
1502 + [121] = "121 (EREMOTEIO)", // "Remote I/O error",
1503 + [122] = "122 (EDQUOT)", // "Disk quota exceeded",
1504 + [123] = "123 (ENOMEDIUM)", // "No medium found",
1505 + [124] = "124 (EMEDIUMTYPE)", // "Wrong medium type",
1506 + [125] = "125 (ECANCELED)", // "Operation canceled",
1507 + [126] = "126 (ENOKEY)", // "Required key not available",
1508 + [127] = "127 (EKEYEXPIRED)", // "Key has expired",
1509 + [128] = "128 (EKEYREVOKED)", // "Key has been revoked",
1510 + [129] = "129 (EKEYREJECTED)", // "Key was rejected by service",
1511 + [130] = "130 (EOWNERDEAD)", // "Owner died",
1512 + [131] = "131 (ENOTRECOVERABLE)", // "State not recoverable",
1513 + [132] = "132 (ERFKILL)", // "Operation not possible due to RF-kill",
1514 + [133] = "133 (EHWPOISON)", // "Memory page has hardware error",
1515 +};
1516 +
1517 static const char *syslog_facility_to_name(int facility) {
1518 switch (facility) {
1519 case LOG_FAC(LOG_KERN): return "kern";
@@ -489,11 +1554,17 @@ static const char *syslog_priority_to_name(int priority) {
1554 }
1555 }
1556
492 -static FACET_ROW_SEVERITY syslog_priority_to_facet_severity(int priority) {
1557 +static FACET_ROW_SEVERITY syslog_priority_to_facet_severity(FACETS *facets __maybe_unused, FACET_ROW *row, void *data __maybe_unused) {
1558 // same to
1559 // https://github.com/systemd/systemd/blob/aab9e4b2b86905a15944a1ac81e471b5b7075932/src/basic/terminal-util.c#L1501
1560 // function get_log_colors()
1561
1562 + FACET_ROW_KEY_VALUE *priority_rkv = dictionary_get(row->dict, "PRIORITY");
1563 + if(!priority_rkv || priority_rkv->empty)
1564 + return FACET_ROW_SEVERITY_NORMAL;
1565 +
1566 + int priority = str2i(buffer_tostring(priority_rkv->wb));
1567 +
1568 if(priority <= LOG_ERR)
1569 return FACET_ROW_SEVERITY_CRITICAL;
1570
@@ -533,7 +1604,7 @@ static char *gid_to_groupname(gid_t gid, char* buffer, size_t buffer_size) {
1604 return buffer;
1605 }
1606
536 -static void netdata_systemd_journal_transform_syslog_facility(FACETS *facets __maybe_unused, BUFFER *wb, void *data __maybe_unused) {
1607 +static void netdata_systemd_journal_transform_syslog_facility(FACETS *facets __maybe_unused, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope __maybe_unused, void *data __maybe_unused) {
1608 const char *v = buffer_tostring(wb);
1609 if(*v && isdigit(*v)) {
1610 int facility = str2i(buffer_tostring(wb));
@@ -545,7 +1616,10 @@ static void netdata_systemd_journal_transform_syslog_facility(FACETS *facets __m
1616 }
1617 }
1618
548 -static void netdata_systemd_journal_transform_priority(FACETS *facets __maybe_unused, BUFFER *wb, void *data __maybe_unused) {
1619 +static void netdata_systemd_journal_transform_priority(FACETS *facets __maybe_unused, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope __maybe_unused, void *data __maybe_unused) {
1620 + if(scope == FACETS_TRANSFORM_FACET_SORT)
1621 + return;
1622 +
1623 const char *v = buffer_tostring(wb);
1624 if(*v && isdigit(*v)) {
1625 int priority = str2i(buffer_tostring(wb));
@@ -554,8 +1628,23 @@ static void netdata_systemd_journal_transform_priority(FACETS *facets __maybe_un
1628 buffer_flush(wb);
1629 buffer_strcat(wb, name);
1630 }
1631 + }
1632 +}
1633
558 - facets_set_current_row_severity(facets, syslog_priority_to_facet_severity(priority));
1634 +static void netdata_systemd_journal_transform_errno(FACETS *facets __maybe_unused, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope __maybe_unused, void *data __maybe_unused) {
1635 + if(scope == FACETS_TRANSFORM_FACET_SORT)
1636 + return;
1637 +
1638 + const char *v = buffer_tostring(wb);
1639 + if(*v && isdigit(*v)) {
1640 + unsigned err_no = str2u(buffer_tostring(wb));
1641 + if(err_no > 0 && err_no < sizeof(errno_map) / sizeof(*errno_map)) {
1642 + const char *name = errno_map[err_no];
1643 + if(name) {
1644 + buffer_flush(wb);
1645 + buffer_strcat(wb, name);
1646 + }
1647 + }
1648 }
1649 }
1650
@@ -637,7 +1726,78 @@ const char *gid_to_groupname_cached(gid_t gid, size_t *length) {
1726 return (*e)->str;
1727 }
1728
640 -static void netdata_systemd_journal_transform_uid(FACETS *facets __maybe_unused, BUFFER *wb, void *data __maybe_unused) {
1729 +DICTIONARY *boot_ids_to_first_ut = NULL;
1730 +
1731 +static void netdata_systemd_journal_transform_boot_id(FACETS *facets __maybe_unused, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope __maybe_unused, void *data __maybe_unused) {
1732 + const char *boot_id = buffer_tostring(wb);
1733 + if(*boot_id && isxdigit(*boot_id)) {
1734 + usec_t ut = UINT64_MAX;
1735 + usec_t *p_ut = dictionary_get(boot_ids_to_first_ut, boot_id);
1736 + if(!p_ut) {
1737 + struct journal_file *jf;
1738 + dfe_start_read(journal_files_registry, jf) {
1739 + const char *files[2] = {
1740 + [0] = jf_dfe.name,
1741 + [1] = NULL,
1742 + };
1743 +
1744 + sd_journal *j = NULL;
1745 + if(sd_journal_open_files(&j, files, ND_SD_JOURNAL_OPEN_FLAGS) < 0 || !j)
1746 + continue;
1747 +
1748 + char m[100];
1749 + size_t len = snprintfz(m, sizeof(m), "_BOOT_ID=%s", boot_id);
1750 + usec_t t_ut = 0;
1751 + if(sd_journal_add_match(j, m, len) < 0 ||
1752 + sd_journal_seek_head(j) < 0 ||
1753 + sd_journal_next(j) < 0 ||
1754 + sd_journal_get_realtime_usec(j, &t_ut) < 0 || !t_ut) {
1755 + sd_journal_close(j);
1756 + continue;
1757 + }
1758 +
1759 + if(t_ut < ut)
1760 + ut = t_ut;
1761 +
1762 + sd_journal_close(j);
1763 + }
1764 + dfe_done(jf);
1765 +
1766 + dictionary_set(boot_ids_to_first_ut, boot_id, &ut, sizeof(ut));
1767 + }
1768 + else
1769 + ut = *p_ut;
1770 +
1771 + if(ut != UINT64_MAX) {
1772 + time_t timestamp_sec = (time_t)(ut / USEC_PER_SEC);
1773 + struct tm tm;
1774 + char buffer[30];
1775 +
1776 + gmtime_r(&timestamp_sec, &tm);
1777 + strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tm);
1778 +
1779 + switch(scope) {
1780 + default:
1781 + case FACETS_TRANSFORM_DATA:
1782 + case FACETS_TRANSFORM_VALUE:
1783 + buffer_sprintf(wb, " (%s UTC) ", buffer);
1784 + break;
1785 +
1786 + case FACETS_TRANSFORM_FACET:
1787 + case FACETS_TRANSFORM_FACET_SORT:
1788 + case FACETS_TRANSFORM_HISTOGRAM:
1789 + buffer_flush(wb);
1790 + buffer_sprintf(wb, "%s UTC", buffer);
1791 + break;
1792 + }
1793 + }
1794 + }
1795 +}
1796 +
1797 +static void netdata_systemd_journal_transform_uid(FACETS *facets __maybe_unused, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope __maybe_unused, void *data __maybe_unused) {
1798 + if(scope == FACETS_TRANSFORM_FACET_SORT)
1799 + return;
1800 +
1801 const char *v = buffer_tostring(wb);
1802 if(*v && isdigit(*v)) {
1803 uid_t uid = str2i(buffer_tostring(wb));
@@ -647,7 +1807,10 @@ static void netdata_systemd_journal_transform_uid(FACETS *facets __maybe_unused,
1807 }
1808 }
1809
650 -static void netdata_systemd_journal_transform_gid(FACETS *facets __maybe_unused, BUFFER *wb, void *data __maybe_unused) {
1810 +static void netdata_systemd_journal_transform_gid(FACETS *facets __maybe_unused, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope __maybe_unused, void *data __maybe_unused) {
1811 + if(scope == FACETS_TRANSFORM_FACET_SORT)
1812 + return;
1813 +
1814 const char *v = buffer_tostring(wb);
1815 if(*v && isdigit(*v)) {
1816 gid_t gid = str2i(buffer_tostring(wb));
@@ -657,24 +1820,120 @@ static void netdata_systemd_journal_transform_gid(FACETS *facets __maybe_unused,
1820 }
1821 }
1822
1823 +const char *linux_capabilities[] = {
1824 + [CAP_CHOWN] = "CHOWN",
1825 + [CAP_DAC_OVERRIDE] = "DAC_OVERRIDE",
1826 + [CAP_DAC_READ_SEARCH] = "DAC_READ_SEARCH",
1827 + [CAP_FOWNER] = "FOWNER",
1828 + [CAP_FSETID] = "FSETID",
1829 + [CAP_KILL] = "KILL",
1830 + [CAP_SETGID] = "SETGID",
1831 + [CAP_SETUID] = "SETUID",
1832 + [CAP_SETPCAP] = "SETPCAP",
1833 + [CAP_LINUX_IMMUTABLE] = "LINUX_IMMUTABLE",
1834 + [CAP_NET_BIND_SERVICE] = "NET_BIND_SERVICE",
1835 + [CAP_NET_BROADCAST] = "NET_BROADCAST",
1836 + [CAP_NET_ADMIN] = "NET_ADMIN",
1837 + [CAP_NET_RAW] = "NET_RAW",
1838 + [CAP_IPC_LOCK] = "IPC_LOCK",
1839 + [CAP_IPC_OWNER] = "IPC_OWNER",
1840 + [CAP_SYS_MODULE] = "SYS_MODULE",
1841 + [CAP_SYS_RAWIO] = "SYS_RAWIO",
1842 + [CAP_SYS_CHROOT] = "SYS_CHROOT",
1843 + [CAP_SYS_PTRACE] = "SYS_PTRACE",
1844 + [CAP_SYS_PACCT] = "SYS_PACCT",
1845 + [CAP_SYS_ADMIN] = "SYS_ADMIN",
1846 + [CAP_SYS_BOOT] = "SYS_BOOT",
1847 + [CAP_SYS_NICE] = "SYS_NICE",
1848 + [CAP_SYS_RESOURCE] = "SYS_RESOURCE",
1849 + [CAP_SYS_TIME] = "SYS_TIME",
1850 + [CAP_SYS_TTY_CONFIG] = "SYS_TTY_CONFIG",
1851 + [CAP_MKNOD] = "MKNOD",
1852 + [CAP_LEASE] = "LEASE",
1853 + [CAP_AUDIT_WRITE] = "AUDIT_WRITE",
1854 + [CAP_AUDIT_CONTROL] = "AUDIT_CONTROL",
1855 + [CAP_SETFCAP] = "SETFCAP",
1856 + [CAP_MAC_OVERRIDE] = "MAC_OVERRIDE",
1857 + [CAP_MAC_ADMIN] = "MAC_ADMIN",
1858 + [CAP_SYSLOG] = "SYSLOG",
1859 + [CAP_WAKE_ALARM] = "WAKE_ALARM",
1860 + [CAP_BLOCK_SUSPEND] = "BLOCK_SUSPEND",
1861 + [37 /*CAP_AUDIT_READ*/] = "AUDIT_READ",
1862 + [38 /*CAP_PERFMON*/] = "PERFMON",
1863 + [39 /*CAP_BPF*/] = "BPF",
1864 + [40 /* CAP_CHECKPOINT_RESTORE */] = "CHECKPOINT_RESTORE",
1865 +};
1866 +
1867 +static void netdata_systemd_journal_transform_cap_effective(FACETS *facets __maybe_unused, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope __maybe_unused, void *data __maybe_unused) {
1868 + if(scope == FACETS_TRANSFORM_FACET_SORT)
1869 + return;
1870 +
1871 + const char *v = buffer_tostring(wb);
1872 + if(*v && isdigit(*v)) {
1873 + uint64_t cap = strtoul(buffer_tostring(wb), NULL, 16);
1874 + if(cap) {
1875 + buffer_fast_strcat(wb, " (", 2);
1876 + for (size_t i = 0, added = 0; i < sizeof(linux_capabilities) / sizeof(linux_capabilities[0]); i++) {
1877 + if (linux_capabilities[i] && (cap & (1ULL << i))) {
1878 +
1879 + if (added)
1880 + buffer_fast_strcat(wb, " | ", 3);
1881 +
1882 + buffer_strcat(wb, linux_capabilities[i]);
1883 + added++;
1884 + }
1885 + }
1886 + buffer_fast_strcat(wb, ")", 1);
1887 + }
1888 + }
1889 +}
1890 +
1891 +static void netdata_systemd_journal_transform_timestamp_usec(FACETS *facets __maybe_unused, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope __maybe_unused, void *data __maybe_unused) {
1892 + if(scope == FACETS_TRANSFORM_FACET_SORT)
1893 + return;
1894 +
1895 + const char *v = buffer_tostring(wb);
1896 + if(*v && isdigit(*v)) {
1897 + uint64_t ut = str2ull(buffer_tostring(wb), NULL);
1898 + if(ut) {
1899 + time_t timestamp_sec = ut / USEC_PER_SEC;
1900 + struct tm tm;
1901 + char buffer[30];
1902 +
1903 + gmtime_r(&timestamp_sec, &tm);
1904 + strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tm);
1905 + buffer_sprintf(wb, " (%s.%06llu UTC)", buffer, ut % USEC_PER_SEC);
1906 + }
1907 + }
1908 +}
1909 +
1910 // ----------------------------------------------------------------------------
1911
1912 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) {
1913 FACET_ROW_KEY_VALUE *pid_rkv = dictionary_get(row->dict, "_PID");
1914 const char *pid = pid_rkv ? buffer_tostring(pid_rkv->wb) : FACET_VALUE_UNSET;
1915
666 - FACET_ROW_KEY_VALUE *syslog_identifier_rkv = dictionary_get(row->dict, "SYSLOG_IDENTIFIER");
667 - const char *identifier = syslog_identifier_rkv ? buffer_tostring(syslog_identifier_rkv->wb) : FACET_VALUE_UNSET;
1916 + const char *identifier = NULL;
1917 + FACET_ROW_KEY_VALUE *container_name_rkv = dictionary_get(row->dict, "CONTAINER_NAME");
1918 + if(container_name_rkv && !container_name_rkv->empty)
1919 + identifier = buffer_tostring(container_name_rkv->wb);
1920 +
1921 + if(!identifier) {
1922 + FACET_ROW_KEY_VALUE *syslog_identifier_rkv = dictionary_get(row->dict, "SYSLOG_IDENTIFIER");
1923 + if(syslog_identifier_rkv && !syslog_identifier_rkv->empty)
1924 + identifier = buffer_tostring(syslog_identifier_rkv->wb);
1925
669 - if(strcmp(identifier, FACET_VALUE_UNSET) == 0) {
670 - FACET_ROW_KEY_VALUE *comm_rkv = dictionary_get(row->dict, "_COMM");
671 - identifier = comm_rkv ? buffer_tostring(comm_rkv->wb) : FACET_VALUE_UNSET;
1926 + if(!identifier) {
1927 + FACET_ROW_KEY_VALUE *comm_rkv = dictionary_get(row->dict, "_COMM");
1928 + if(comm_rkv && !comm_rkv->empty)
1929 + identifier = buffer_tostring(comm_rkv->wb);
1930 + }
1931 }
1932
1933 buffer_flush(rkv->wb);
1934
676 - if(strcmp(pid, FACET_VALUE_UNSET) == 0)
677 - buffer_strcat(rkv->wb, identifier);
1935 + if(!identifier)
1936 + buffer_strcat(rkv->wb, FACET_VALUE_UNSET);
1937 else
1938 buffer_sprintf(rkv->wb, "%s[%s]", identifier, pid);
1939
@@ -687,11 +1946,82 @@ static void netdata_systemd_journal_rich_message(FACETS *facets __maybe_unused,
1946 buffer_json_object_close(json_array);
1947 }
1948
1949 +DICTIONARY *function_query_status_dict = NULL;
1950 +
1951 +static void function_systemd_journal_progress(BUFFER *wb, const char *transaction, const char *progress_id) {
1952 + if(!progress_id || !(*progress_id)) {
1953 + netdata_mutex_lock(&stdout_mutex);
1954 + pluginsd_function_json_error_to_stdout(transaction, HTTP_RESP_BAD_REQUEST, "missing progress id");
1955 + netdata_mutex_unlock(&stdout_mutex);
1956 + return;
1957 + }
1958 +
1959 + const DICTIONARY_ITEM *item = dictionary_get_and_acquire_item(function_query_status_dict, progress_id);
1960 +
1961 + if(!item) {
1962 + netdata_mutex_lock(&stdout_mutex);
1963 + pluginsd_function_json_error_to_stdout(transaction, HTTP_RESP_NOT_FOUND, "progress id is not found here");
1964 + netdata_mutex_unlock(&stdout_mutex);
1965 + return;
1966 + }
1967 +
1968 + FUNCTION_QUERY_STATUS *fqs = dictionary_acquired_item_value(item);
1969 +
1970 + usec_t now_monotonic_ut = now_monotonic_usec();
1971 + if(now_monotonic_ut + 10 * USEC_PER_SEC > fqs->stop_monotonic_ut)
1972 + fqs->stop_monotonic_ut = now_monotonic_ut + 10 * USEC_PER_SEC;
1973 +
1974 + usec_t duration_ut = now_monotonic_ut - fqs->started_monotonic_ut;
1975 +
1976 + size_t files_matched = fqs->files_matched;
1977 + size_t file_working = fqs->file_working;
1978 + if(file_working > files_matched)
1979 + files_matched = file_working;
1980 +
1981 + size_t rows_read = __atomic_load_n(&fqs->rows_read, __ATOMIC_RELAXED);
1982 + size_t bytes_read = __atomic_load_n(&fqs->bytes_read, __ATOMIC_RELAXED);
1983 +
1984 + buffer_flush(wb);
1985 + buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_MINIFY);
1986 + buffer_json_member_add_uint64(wb, "status", HTTP_RESP_OK);
1987 + buffer_json_member_add_string(wb, "type", "table");
1988 + buffer_json_member_add_uint64(wb, "running_duration_usec", duration_ut);
1989 + buffer_json_member_add_double(wb, "progress", (double)file_working * 100.0 / (double)files_matched);
1990 + char msg[1024 + 1];
1991 + snprintfz(msg, 1024,
1992 + "Read %zu rows (%0.0f rows/s), "
1993 + "data %0.1f MB (%0.1f MB/s), "
1994 + "file %zu of %zu",
1995 + rows_read, (double)rows_read / (double)duration_ut * (double)USEC_PER_SEC,
1996 + (double)bytes_read / 1024.0 / 1024.0, ((double)bytes_read / (double)duration_ut * (double)USEC_PER_SEC) / 1024.0 / 1024.0,
1997 + file_working, files_matched
1998 + );
1999 + buffer_json_member_add_string(wb, "message", msg);
2000 + buffer_json_finalize(wb);
2001 +
2002 + netdata_mutex_lock(&stdout_mutex);
2003 + pluginsd_function_result_to_stdout(transaction, HTTP_RESP_OK, "application/json", now_realtime_sec() + 1, wb);
2004 + netdata_mutex_unlock(&stdout_mutex);
2005 +
2006 + dictionary_acquired_item_release(function_query_status_dict, item);
2007 +}
2008 +
2009 static void function_systemd_journal(const char *transaction, char *function, int timeout, bool *cancelled) {
2010 + journal_files_registry_update();
2011 +
2012 BUFFER *wb = buffer_create(0, NULL);
2013 buffer_flush(wb);
2014 buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_MINIFY);
2015
2016 + usec_t now_monotonic_ut = now_monotonic_usec();
2017 + FUNCTION_QUERY_STATUS tmp_fqs = {
2018 + .cancelled = cancelled,
2019 + .started_monotonic_ut = now_monotonic_ut,
2020 + .stop_monotonic_ut = now_monotonic_ut + timeout * USEC_PER_SEC,
2021 + };
2022 + FUNCTION_QUERY_STATUS *fqs = NULL;
2023 + const DICTIONARY_ITEM *fqs_item = NULL;
2024 +
2025 FACETS *facets = facets_create(50, FACETS_OPTION_ALL_KEYS_FTS,
2026 SYSTEMD_ALWAYS_VISIBLE_KEYS,
2027 SYSTEMD_KEYS_INCLUDED_IN_FACETS,
@@ -709,9 +2039,22 @@ static void function_systemd_journal(const char *transaction, char *function, in
2039 facets_accepted_param(facets, JOURNAL_PARAMETER_HISTOGRAM);
2040 facets_accepted_param(facets, JOURNAL_PARAMETER_IF_MODIFIED_SINCE);
2041 facets_accepted_param(facets, JOURNAL_PARAMETER_DATA_ONLY);
2042 + facets_accepted_param(facets, JOURNAL_PARAMETER_ID);
2043 + facets_accepted_param(facets, JOURNAL_PARAMETER_PROGRESS);
2044 + facets_accepted_param(facets, JOURNAL_PARAMETER_DELTA);
2045 + facets_accepted_param(facets, JOURNAL_PARAMETER_TAIL);
2046 +
2047 +#ifdef HAVE_SD_JOURNAL_RESTART_FIELDS
2048 + facets_accepted_param(facets, JOURNAL_PARAMETER_SLICE);
2049 +#endif // HAVE_SD_JOURNAL_RESTART_FIELDS
2050
2051 // register the fields in the order you want them on the dashboard
2052
2053 + facets_register_row_severity(facets, syslog_priority_to_facet_severity, NULL);
2054 +
2055 + facets_register_key_name(facets, "_HOSTNAME",
2056 + FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_VISIBLE | FACET_KEY_OPTION_FTS);
2057 +
2058 facets_register_dynamic_key_name(facets, "ND_JOURNAL_PROCESS",
2059 FACET_KEY_OPTION_NEVER_FACET | FACET_KEY_OPTION_VISIBLE | FACET_KEY_OPTION_FTS,
2060 netdata_systemd_journal_dynamic_row_id, NULL);
@@ -725,32 +2068,70 @@ static void function_systemd_journal(const char *transaction, char *function, in
2068 // FACET_KEY_OPTION_VISIBLE | FACET_KEY_OPTION_FTS,
2069 // netdata_systemd_journal_rich_message, NULL);
2070
728 - facets_register_key_name_transformation(facets, "PRIORITY", FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_FTS,
2071 + facets_register_key_name_transformation(facets, "PRIORITY",
2072 + FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_FTS | FACET_KEY_OPTION_TRANSFORM_VIEW,
2073 netdata_systemd_journal_transform_priority, NULL);
2074
731 - facets_register_key_name_transformation(facets, "SYSLOG_FACILITY", FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_FTS,
2075 + facets_register_key_name_transformation(facets, "SYSLOG_FACILITY",
2076 + FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_FTS | FACET_KEY_OPTION_TRANSFORM_VIEW,
2077 netdata_systemd_journal_transform_syslog_facility, NULL);
2078
734 - facets_register_key_name(facets, "SYSLOG_IDENTIFIER", FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_FTS);
735 - facets_register_key_name(facets, "UNIT", FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_FTS);
736 - facets_register_key_name(facets, "USER_UNIT", FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_FTS);
2079 + facets_register_key_name_transformation(facets, "ERRNO",
2080 + FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_FTS | FACET_KEY_OPTION_TRANSFORM_VIEW,
2081 + netdata_systemd_journal_transform_errno, NULL);
2082
738 - facets_register_key_name_transformation(facets, "_UID", FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_FTS,
2083 + facets_register_key_name(facets, "SYSLOG_IDENTIFIER",
2084 + FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_FTS);
2085 +
2086 + facets_register_key_name(facets, "UNIT",
2087 + FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_FTS);
2088 +
2089 + facets_register_key_name(facets, "USER_UNIT",
2090 + FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_FTS);
2091 +
2092 + facets_register_key_name_transformation(facets, "_BOOT_ID",
2093 + FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_FTS | FACET_KEY_OPTION_TRANSFORM_VIEW,
2094 + netdata_systemd_journal_transform_boot_id, NULL);
2095 +
2096 + facets_register_key_name_transformation(facets, "_SYSTEMD_OWNER_UID",
2097 + FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_FTS | FACET_KEY_OPTION_TRANSFORM_VIEW,
2098 netdata_systemd_journal_transform_uid, NULL);
2099
741 - facets_register_key_name_transformation(facets, "_GID", FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_FTS,
2100 + facets_register_key_name_transformation(facets, "_UID",
2101 + FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_FTS | FACET_KEY_OPTION_TRANSFORM_VIEW,
2102 + netdata_systemd_journal_transform_uid, NULL);
2103 +
2104 + facets_register_key_name_transformation(facets, "_GID",
2105 + FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_FTS | FACET_KEY_OPTION_TRANSFORM_VIEW,
2106 netdata_systemd_journal_transform_gid, NULL);
2107
744 - bool info = false;
745 - bool data_only = false;
2108 + facets_register_key_name_transformation(facets, "_CAP_EFFECTIVE",
2109 + FACET_KEY_OPTION_FTS | FACET_KEY_OPTION_TRANSFORM_VIEW,
2110 + netdata_systemd_journal_transform_cap_effective, NULL);
2111 +
2112 + facets_register_key_name_transformation(facets, "_AUDIT_LOGINUID",
2113 + FACET_KEY_OPTION_FTS | FACET_KEY_OPTION_TRANSFORM_VIEW,
2114 + netdata_systemd_journal_transform_uid, NULL);
2115 +
2116 + facets_register_key_name_transformation(facets, "_SOURCE_REALTIME_TIMESTAMP",
2117 + FACET_KEY_OPTION_FTS | FACET_KEY_OPTION_TRANSFORM_VIEW,
2118 + netdata_systemd_journal_transform_timestamp_usec, NULL);
2119 +
2120 + // ------------------------------------------------------------------------
2121 + // parse the parameters
2122 +
2123 + bool info = false, data_only = false, progress = false, slice = JOURNAL_DEFAULT_SLICE_MODE, delta = false, tail = false;
2124 time_t after_s = 0, before_s = 0;
2125 usec_t anchor = 0;
2126 usec_t if_modified_since = 0;
2127 size_t last = 0;
750 - FACETS_ANCHOR_DIRECTION direction = FACETS_ANCHOR_DIRECTION_BACKWARD;
2128 + FACETS_ANCHOR_DIRECTION direction = JOURNAL_DEFAULT_DIRECTION;
2129 const char *query = NULL;
2130 const char *chart = NULL;
2131 const char *source = NULL;
2132 + const char *progress_id = NULL;
2133 + SD_JOURNAL_FILE_SOURCE_TYPE source_type = SDJF_ALL;
2134 + size_t filters = 0;
2135
2136 buffer_json_member_add_object(wb, "request");
2137
@@ -767,11 +2148,82 @@ static void function_systemd_journal(const char *transaction, char *function, in
2148 else if(strcmp(keyword, JOURNAL_PARAMETER_INFO) == 0) {
2149 info = true;
2150 }
770 - else if(strcmp(keyword, JOURNAL_PARAMETER_DATA_ONLY) == 0) {
771 - data_only = true;
2151 + else if(strcmp(keyword, JOURNAL_PARAMETER_PROGRESS) == 0) {
2152 + progress = true;
2153 + }
2154 + else if(strncmp(keyword, JOURNAL_PARAMETER_DELTA ":", sizeof(JOURNAL_PARAMETER_DELTA ":") - 1) == 0) {
2155 + char *v = &keyword[sizeof(JOURNAL_PARAMETER_DELTA ":") - 1];
2156 +
2157 + if(strcmp(v, "false") == 0 || strcmp(v, "no") == 0 || strcmp(v, "0") == 0)
2158 + delta = false;
2159 + else
2160 + delta = true;
2161 + }
2162 + else if(strncmp(keyword, JOURNAL_PARAMETER_TAIL ":", sizeof(JOURNAL_PARAMETER_TAIL ":") - 1) == 0) {
2163 + char *v = &keyword[sizeof(JOURNAL_PARAMETER_TAIL ":") - 1];
2164 +
2165 + if(strcmp(v, "false") == 0 || strcmp(v, "no") == 0 || strcmp(v, "0") == 0)
2166 + tail = false;
2167 + else
2168 + tail = true;
2169 + }
2170 + else if(strncmp(keyword, JOURNAL_PARAMETER_DATA_ONLY ":", sizeof(JOURNAL_PARAMETER_DATA_ONLY ":") - 1) == 0) {
2171 + char *v = &keyword[sizeof(JOURNAL_PARAMETER_DATA_ONLY ":") - 1];
2172 +
2173 + if(strcmp(v, "false") == 0 || strcmp(v, "no") == 0 || strcmp(v, "0") == 0)
2174 + data_only = false;
2175 + else
2176 + data_only = true;
2177 + }
2178 + else if(strncmp(keyword, JOURNAL_PARAMETER_SLICE ":", sizeof(JOURNAL_PARAMETER_SLICE ":") - 1) == 0) {
2179 + char *v = &keyword[sizeof(JOURNAL_PARAMETER_SLICE ":") - 1];
2180 +
2181 + if(strcmp(v, "false") == 0 || strcmp(v, "no") == 0 || strcmp(v, "0") == 0)
2182 + slice = false;
2183 + else
2184 + slice = true;
2185 + }
2186 + else if(strncmp(keyword, JOURNAL_PARAMETER_ID ":", sizeof(JOURNAL_PARAMETER_ID ":") - 1) == 0) {
2187 + char *id = &keyword[sizeof(JOURNAL_PARAMETER_ID ":") - 1];
2188 +
2189 + if(*id)
2190 + progress_id = id;
2191 }
2192 else if(strncmp(keyword, JOURNAL_PARAMETER_SOURCE ":", sizeof(JOURNAL_PARAMETER_SOURCE ":") - 1) == 0) {
2193 source = &keyword[sizeof(JOURNAL_PARAMETER_SOURCE ":") - 1];
2194 +
2195 + if(strcmp(source, SDJF_SOURCE_ALL_NAME) == 0) {
2196 + source_type = SDJF_ALL;
2197 + source = NULL;
2198 + }
2199 + else if(strcmp(source, SDJF_SOURCE_LOCAL_NAME) == 0) {
2200 + source_type = SDJF_LOCAL;
2201 + source = NULL;
2202 + }
2203 + else if(strcmp(source, SDJF_SOURCE_REMOTES_NAME) == 0) {
2204 + source_type = SDJF_REMOTE;
2205 + source = NULL;
2206 + }
2207 + else if(strcmp(source, SDJF_SOURCE_NAMESPACES_NAME) == 0) {
2208 + source_type = SDJF_NAMESPACE;
2209 + source = NULL;
2210 + }
2211 + else if(strcmp(source, SDJF_SOURCE_LOCAL_SYSTEM_NAME) == 0) {
2212 + source_type = SDJF_LOCAL | SDJF_SYSTEM;
2213 + source = NULL;
2214 + }
2215 + else if(strcmp(source, SDJF_SOURCE_LOCAL_USERS_NAME) == 0) {
2216 + source_type = SDJF_LOCAL | SDJF_USER;
2217 + source = NULL;
2218 + }
2219 + else if(strcmp(source, SDJF_SOURCE_LOCAL_OTHER_NAME) == 0) {
2220 + source_type = SDJF_LOCAL | SDJF_OTHER;
2221 + source = NULL;
2222 + }
2223 + else {
2224 + source_type = SDJF_ALL;
2225 + // else, match the source, whatever it is
2226 + }
2227 }
2228 else if(strncmp(keyword, JOURNAL_PARAMETER_AFTER ":", sizeof(JOURNAL_PARAMETER_AFTER ":") - 1) == 0) {
2229 after_s = str2l(&keyword[sizeof(JOURNAL_PARAMETER_AFTER ":") - 1]);
@@ -830,6 +2282,7 @@ static void function_systemd_journal(const char *transaction, char *function, in
2282
2283 facets_register_facet_id_filter(facets, keyword, value, FACET_KEY_OPTION_FACET|FACET_KEY_OPTION_FTS|FACET_KEY_OPTION_REORDER);
2284 buffer_json_add_array_item_string(wb, value);
2285 + filters++;
2286
2287 value = sep;
2288 }
@@ -839,6 +2292,22 @@ static void function_systemd_journal(const char *transaction, char *function, in
2292 }
2293 }
2294
2295 + // ------------------------------------------------------------------------
2296 + // put this request into the progress db
2297 +
2298 + if(progress_id && *progress_id) {
2299 + fqs_item = dictionary_set_and_acquire_item(function_query_status_dict, progress_id, &tmp_fqs, sizeof(tmp_fqs));
2300 + fqs = dictionary_acquired_item_value(fqs_item);
2301 + }
2302 + else {
2303 + // no progress id given, proceed without registering our progress in the dictionary
2304 + fqs = &tmp_fqs;
2305 + fqs_item = NULL;
2306 + }
2307 +
2308 + // ------------------------------------------------------------------------
2309 + // validate parameters
2310 +
2311 time_t expires = now_realtime_sec() + 1;
2312 time_t now_s;
2313
@@ -862,18 +2331,103 @@ static void function_systemd_journal(const char *transaction, char *function, in
2331 if(!last)
2332 last = SYSTEMD_JOURNAL_DEFAULT_ITEMS_PER_QUERY;
2333
865 - buffer_json_member_add_string(wb, "source", source ? source : "default");
866 - buffer_json_member_add_time_t(wb, "after", after_s);
867 - buffer_json_member_add_time_t(wb, "before", before_s);
868 - buffer_json_member_add_uint64(wb, "if_modified_since", if_modified_since);
869 - buffer_json_member_add_uint64(wb, "anchor", anchor);
870 - buffer_json_member_add_string(wb, "direction", direction == FACETS_ANCHOR_DIRECTION_FORWARD ? "forward" : "backward");
871 - buffer_json_member_add_uint64(wb, "last", last);
872 - buffer_json_member_add_string(wb, "query", query);
873 - buffer_json_member_add_string(wb, "chart", chart);
874 - buffer_json_member_add_time_t(wb, "timeout", timeout);
2334 +
2335 + // ------------------------------------------------------------------------
2336 + // set query time-frame, anchors and direction
2337 +
2338 + fqs->after_ut = after_s * USEC_PER_SEC;
2339 + fqs->before_ut = before_s * USEC_PER_SEC;
2340 + fqs->if_modified_since = if_modified_since;
2341 + fqs->data_only = data_only;
2342 + fqs->delta = (fqs->data_only) ? delta : false;
2343 + fqs->tail = (fqs->data_only && fqs->if_modified_since) ? tail : false;
2344 + fqs->source = string_strdupz(source);
2345 + fqs->source_type = source_type;
2346 + fqs->entries = last;
2347 + fqs->last_modified = 0;
2348 + fqs->filters = filters;
2349 + fqs->query = (query && *query) ? query : NULL;
2350 + fqs->histogram = (chart && *chart) ? chart : NULL;
2351 +
2352 + if(anchor && anchor < fqs->after_ut) {
2353 + netdata_log_error("Received anchor %"PRIu64" is too small for query time-frame [%"PRIu64" - %"PRIu64"]",
2354 + anchor, fqs->after_ut, fqs->before_ut);
2355 + anchor = 0;
2356 + }
2357 + else if(anchor > fqs->before_ut) {
2358 + netdata_log_error("Received anchor %"PRIu64" is too big for query time-frame [%"PRIu64" - %"PRIu64"]",
2359 + anchor, fqs->after_ut, fqs->before_ut);
2360 + anchor = 0;
2361 + }
2362 +
2363 + fqs->direction = direction;
2364 + fqs->anchor.start_ut = anchor;
2365 + fqs->anchor.stop_ut = 0;
2366 +
2367 + if(fqs->anchor.start_ut && fqs->tail) {
2368 + // a tail request
2369 + // we need the top X entries from BEFORE
2370 + // but, we need to calculate the facets and the
2371 + // histogram up to the anchor
2372 + fqs->direction = direction = FACETS_ANCHOR_DIRECTION_BACKWARD;
2373 + fqs->anchor.start_ut = 0;
2374 + fqs->anchor.stop_ut = anchor;
2375 + }
2376 +
2377 + facets_set_anchor(facets, fqs->anchor.start_ut, fqs->anchor.stop_ut, fqs->direction);
2378 +
2379 + facets_set_additional_options(facets,
2380 + ((fqs->data_only) ? FACETS_OPTION_DATA_ONLY : 0) |
2381 + ((fqs->delta) ? FACETS_OPTION_SHOW_DELTAS : 0));
2382 +
2383 + // ------------------------------------------------------------------------
2384 + // set the rest of the query parameters
2385 +
2386 +
2387 + facets_set_items(facets, fqs->entries);
2388 + facets_set_query(facets, fqs->query);
2389 +
2390 +#ifdef HAVE_SD_JOURNAL_RESTART_FIELDS
2391 + fqs->slice = slice;
2392 + if(slice)
2393 + facets_enable_slice_mode(facets);
2394 +#else
2395 + fqs->slice = false;
2396 +#endif
2397 +
2398 + if(fqs->histogram)
2399 + facets_set_timeframe_and_histogram_by_id(facets, fqs->histogram, fqs->after_ut, fqs->before_ut);
2400 + else
2401 + facets_set_timeframe_and_histogram_by_name(facets, "PRIORITY", fqs->after_ut, fqs->before_ut);
2402 +
2403 +
2404 + // ------------------------------------------------------------------------
2405 + // complete the request object
2406 +
2407 + buffer_json_member_add_boolean(wb, JOURNAL_PARAMETER_INFO, false);
2408 + buffer_json_member_add_boolean(wb, JOURNAL_PARAMETER_SLICE, fqs->slice);
2409 + buffer_json_member_add_boolean(wb, JOURNAL_PARAMETER_DATA_ONLY, fqs->data_only);
2410 + buffer_json_member_add_boolean(wb, JOURNAL_PARAMETER_PROGRESS, false);
2411 + buffer_json_member_add_boolean(wb, JOURNAL_PARAMETER_DELTA, fqs->delta);
2412 + buffer_json_member_add_boolean(wb, JOURNAL_PARAMETER_TAIL, fqs->tail);
2413 + buffer_json_member_add_string(wb, JOURNAL_PARAMETER_ID, progress_id);
2414 + buffer_json_member_add_string(wb, JOURNAL_PARAMETER_SOURCE, string2str(fqs->source));
2415 + buffer_json_member_add_uint64(wb, "source_type", fqs->source_type);
2416 + buffer_json_member_add_uint64(wb, JOURNAL_PARAMETER_AFTER, fqs->after_ut / USEC_PER_SEC);
2417 + buffer_json_member_add_uint64(wb, JOURNAL_PARAMETER_BEFORE, fqs->before_ut / USEC_PER_SEC);
2418 + buffer_json_member_add_uint64(wb, "if_modified_since", fqs->if_modified_since);
2419 + buffer_json_member_add_uint64(wb, JOURNAL_PARAMETER_ANCHOR, anchor);
2420 + buffer_json_member_add_string(wb, JOURNAL_PARAMETER_DIRECTION, fqs->direction == FACETS_ANCHOR_DIRECTION_FORWARD ? "forward" : "backward");
2421 + buffer_json_member_add_uint64(wb, JOURNAL_PARAMETER_LAST, fqs->entries);
2422 + buffer_json_member_add_string(wb, JOURNAL_PARAMETER_QUERY, fqs->query);
2423 + buffer_json_member_add_string(wb, JOURNAL_PARAMETER_HISTOGRAM, fqs->histogram);
2424 buffer_json_object_close(wb); // request
2425
2426 + buffer_json_journal_versions(wb);
2427 +
2428 + // ------------------------------------------------------------------------
2429 + // run the request
2430 +
2431 int response;
2432
2433 if(info) {
@@ -888,12 +2442,7 @@ static void function_systemd_journal(const char *transaction, char *function, in
2442 buffer_json_member_add_string(wb, "type", "select");
2443 buffer_json_member_add_array(wb, "options");
2444 {
891 - buffer_json_add_array_item_object(wb);
892 - {
893 - buffer_json_member_add_string(wb, "id", "default");
894 - buffer_json_member_add_string(wb, "name", "default");
895 - }
896 - buffer_json_object_close(wb); // options object
2445 + available_journal_file_sources_to_json_array(wb);
2446 }
2447 buffer_json_array_close(wb); // options array
2448 }
@@ -901,6 +2450,8 @@ static void function_systemd_journal(const char *transaction, char *function, in
2450 }
2451 buffer_json_array_close(wb); // required_params array
2452
2453 + facets_table_config(wb);
2454 +
2455 buffer_json_member_add_uint64(wb, "status", HTTP_RESP_OK);
2456 buffer_json_member_add_string(wb, "type", "table");
2457 buffer_json_member_add_string(wb, "help", SYSTEMD_JOURNAL_FUNCTION_DESCRIPTION);
@@ -909,22 +2460,21 @@ static void function_systemd_journal(const char *transaction, char *function, in
2460 goto output;
2461 }
2462
912 - facets_set_items(facets, last);
913 - facets_set_anchor(facets, anchor, direction);
914 - facets_set_query(facets, query);
2463 + if(progress) {
2464 + function_systemd_journal_progress(wb, transaction, progress_id);
2465 + goto cleanup;
2466 + }
2467
916 - if(chart && *chart)
917 - facets_set_histogram_by_id(facets, chart,
918 - after_s * USEC_PER_SEC, before_s * USEC_PER_SEC);
919 - else
920 - facets_set_histogram_by_name(facets, "PRIORITY",
921 - after_s * USEC_PER_SEC, before_s * USEC_PER_SEC);
2468 + response = netdata_systemd_journal_query(wb, facets, fqs);
2469 +
2470 + // ------------------------------------------------------------------------
2471 + // cleanup query params
2472 +
2473 + string_freez(fqs->source);
2474 + fqs->source = NULL;
2475
923 - response = netdata_systemd_journal_query(wb, facets, after_s * USEC_PER_SEC, before_s * USEC_PER_SEC,
924 - anchor, direction, last,
925 - if_modified_since, data_only,
926 - now_monotonic_usec() + (timeout - 1) * USEC_PER_SEC,
927 - cancelled);
2476 + // ------------------------------------------------------------------------
2477 + // handle error response
2478
2479 if(response != HTTP_RESP_OK) {
2480 netdata_mutex_lock(&stdout_mutex);
@@ -941,6 +2491,12 @@ output:
2491 cleanup:
2492 facets_destroy(facets);
2493 buffer_free(wb);
2494 +
2495 + if(fqs_item) {
2496 + dictionary_del(function_query_status_dict, dictionary_acquired_item_name(fqs_item));
2497 + dictionary_acquired_item_release(function_query_status_dict, fqs_item);
2498 + dictionary_garbage_collect(function_query_status_dict);
2499 + }
2500 }
2501
2502 // ----------------------------------------------------------------------------
@@ -961,14 +2517,66 @@ int main(int argc __maybe_unused, char **argv __maybe_unused) {
2517 netdata_configured_host_prefix = getenv("NETDATA_HOST_PREFIX");
2518 if(verify_netdata_host_prefix() == -1) exit(1);
2519
2520 + // ------------------------------------------------------------------------
2521 + // setup the journal directories
2522 +
2523 + unsigned d = 0;
2524 +
2525 + journal_directories[d++].path = strdupz("/var/log/journal");
2526 + journal_directories[d++].path = strdupz("/run/log/journal");
2527 +
2528 + if(*netdata_configured_host_prefix) {
2529 + char path[PATH_MAX];
2530 + snprintfz(path, sizeof(path), "%s/var/log/journal", netdata_configured_host_prefix);
2531 + journal_directories[d++].path = strdupz(path);
2532 + snprintfz(path, sizeof(path), "%s/run/log/journal", netdata_configured_host_prefix);
2533 + journal_directories[d++].path = strdupz(path);
2534 + }
2535 +
2536 + // terminate the list
2537 + journal_directories[d].path = NULL;
2538 +
2539 + // ------------------------------------------------------------------------
2540 +
2541 + function_query_status_dict = dictionary_create_advanced(
2542 + DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
2543 + NULL, sizeof(FUNCTION_QUERY_STATUS));
2544 +
2545 + // ------------------------------------------------------------------------
2546 + // initialize the used hashes files registry
2547 +
2548 + used_hashes_registry = dictionary_create(DICT_OPTION_DONT_OVERWRITE_VALUE);
2549 +
2550 +
2551 + // ------------------------------------------------------------------------
2552 + // initialize the journal files registry
2553 +
2554 + systemd_journal_session = (now_realtime_usec() / USEC_PER_SEC) * USEC_PER_SEC;
2555 +
2556 + journal_files_registry = dictionary_create_advanced(
2557 + DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
2558 + NULL, sizeof(struct journal_file));
2559 +
2560 + dictionary_register_insert_callback(journal_files_registry, files_registry_insert_cb, NULL);
2561 + dictionary_register_delete_callback(journal_files_registry, files_registry_delete_cb, NULL);
2562 + dictionary_register_conflict_callback(journal_files_registry, files_registry_conflict_cb, NULL);
2563 +
2564 + boot_ids_to_first_ut = dictionary_create_advanced(
2565 + DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
2566 + NULL, sizeof(usec_t));
2567 +
2568 + journal_files_registry_update();
2569 +
2570 +
2571 // ------------------------------------------------------------------------
2572 // debug
2573
2574 if(argc == 2 && strcmp(argv[1], "debug") == 0) {
2575 bool cancelled = false;
2576 char buf[] = "systemd-journal after:-2592000 before:0 last:500";
2577 + // char buf[] = "systemd-journal after:1695332964 before:1695937764 direction:backward last:100 slice:true source:all DHKucpqUoe1:PtVoyIuX.MU";
2578 // char buf[] = "systemd-journal after:1694511062 before:1694514662 anchor:1694514122024403";
971 - function_systemd_journal("123", buf, 30, &cancelled);
2579 + function_systemd_journal("123", buf, 600, &cancelled);
2580 exit(1);
2581 }
2582
configure.ac
+10
@@ -1141,6 +1141,16 @@ fi
1141 AC_MSG_RESULT([${enable_plugin_systemd_journal}])
1142 AM_CONDITIONAL([ENABLE_PLUGIN_SYSTEMD_JOURNAL], [test "${enable_plugin_systemd_journal}" = "yes"])
1143
1144 +AC_CHECK_LIB([systemd], [sd_journal_open_files_fd], [have_sd_journal_open_files_fd=yes], [have_sd_journal_open_files_fd=no])
1145 +if test "${have_sd_journal_open_files_fd}" = "yes"; then
1146 + AC_DEFINE([HAVE_SD_JOURNAL_OPEN_FILES_FD], [1], [sd_journal_open_files_fd usability])
1147 +fi
1148 +
1149 +AC_CHECK_LIB([systemd], [sd_journal_restart_fields], [have_sd_journal_restart_fields=yes], [have_sd_journal_restart_fields=no])
1150 +if test "${have_sd_journal_restart_fields}" = "yes"; then
1151 + AC_DEFINE([HAVE_SD_JOURNAL_RESTART_FIELDS], [1], [sd_journal_restart_fields usability])
1152 +fi
1153 +
1154 AC_MSG_NOTICE([OPTIONAL_SYSTEMD_LIBS is set to: ${OPTIONAL_SYSTEMD_LIBS}])
1155
1156 if test "${enable_plugin_systemd_journal}" = "yes"; then
libnetdata/facets/facets.c
+735 -212
@@ -1,9 +1,12 @@
1 // SPDX-License-Identifier: GPL-3.0-or-later
2 #include "facets.h"
3
4 -#define HISTOGRAM_COLUMNS 100
5 -#define FACETS_KEYS_HASHTABLE_ENTRIES 1000
6 -#define FACETS_VALUES_HASHTABLE_ENTRIES 20
4 +#define HISTOGRAM_COLUMNS 150 // the target number of points in a histogram
5 +#define FACETS_KEYS_WITH_VALUES_MAX 200 // the max number of keys that can be facets
6 +#define FACETS_KEYS_IN_ROW_MAX 500 // the max number of keys in a row
7 +
8 +#define FACETS_KEYS_HASHTABLE_ENTRIES 256
9 +#define FACETS_VALUES_HASHTABLE_ENTRIES 32
10
11 // ----------------------------------------------------------------------------
12
@@ -106,6 +109,7 @@ typedef struct facet_value {
109
110 uint32_t rows_matching_facet_value;
111 uint32_t final_facet_value_counter;
112 + uint32_t order;
113
114 uint32_t *histogram;
115 uint32_t min, max, sum;
@@ -131,9 +135,11 @@ struct facet_key {
135 // members about the current row
136 uint32_t key_found_in_row;
137 uint32_t key_values_selected_in_row;
138 + uint32_t order;
139
140 struct {
141 bool enabled;
142 + uint32_t used;
143 FACET_VALUE *hashtable[FACETS_VALUES_HASHTABLE_ENTRIES];
144 FACET_VALUE *ll;
145 } values;
@@ -150,14 +156,13 @@ struct facet_key {
156 FACET_VALUE *v;
157 } empty_value;
158
153 - uint32_t order;
154 -
159 struct {
160 facet_dynamic_row_t cb;
161 void *data;
162 } dynamic;
163
164 struct {
165 + bool view_only;
166 facets_key_transformer_t cb;
167 void *data;
168 } transform;
@@ -177,7 +182,8 @@ struct facets {
182 FACETS_OPTIONS options;
183
184 struct {
180 - usec_t key;
185 + usec_t start_ut;
186 + usec_t stop_ut;
187 FACETS_ANCHOR_DIRECTION direction;
188 } anchor;
189
@@ -187,10 +193,23 @@ struct facets {
193 DICTIONARY *accepted_params;
194
195 struct {
196 + size_t count;
197 FACET_KEY *hashtable[FACETS_KEYS_HASHTABLE_ENTRIES];
198 FACET_KEY *ll;
199 } keys;
200
201 + struct {
202 + // this is like a stack, of the keys that are used as facets
203 + size_t used;
204 + FACET_KEY *array[FACETS_KEYS_WITH_VALUES_MAX];
205 + } keys_with_values;
206 +
207 + struct {
208 + // this is like a stack, of the keys that need to clean up between each row
209 + size_t used;
210 + FACET_KEY *array[FACETS_KEYS_IN_ROW_MAX];
211 + } keys_in_row;
212 +
213 FACET_ROW *base; // double linked list of the selected facets rows
214
215 uint32_t items_to_return;
@@ -203,6 +222,12 @@ struct facets {
222 } current_row;
223
224 struct {
225 + usec_t after_ut;
226 + usec_t before_ut;
227 + } timeframe;
228 +
229 + struct {
230 + FACET_KEY *key;
231 FACETS_HASH hash;
232 char *chart;
233 bool enabled;
@@ -212,6 +237,11 @@ struct facets {
237 usec_t before_ut;
238 } histogram;
239
240 + struct {
241 + facet_row_severity_t cb;
242 + void *data;
243 + } severity;
244 +
245 struct {
246 FACET_ROW *last_added;
247
@@ -252,6 +282,24 @@ struct facets {
282 } operations;
283 };
284
285 +usec_t facets_row_oldest_ut(FACETS *facets) {
286 + if(facets->base)
287 + return facets->base->prev->usec;
288 +
289 + return 0;
290 +}
291 +
292 +usec_t facets_row_newest_ut(FACETS *facets) {
293 + if(facets->base)
294 + return facets->base->usec;
295 +
296 + return 0;
297 +}
298 +
299 +uint32_t facets_rows(FACETS *facets) {
300 + return facets->items_to_return;
301 +}
302 +
303 // ----------------------------------------------------------------------------
304
305 static void facets_row_free(FACETS *facets __maybe_unused, FACET_ROW *row);
@@ -268,6 +316,7 @@ static inline bool facets_key_is_facet(FACETS *facets, FACET_KEY *k);
316
317 static inline void FACETS_VALUES_INDEX_CREATE(FACET_KEY *k) {
318 k->values.ll = NULL;
319 + k->values.used = 0;
320 }
321
322 static inline void FACETS_VALUES_INDEX_DESTROY(FACET_KEY *k) {
@@ -280,6 +329,7 @@ static inline void FACETS_VALUES_INDEX_DESTROY(FACET_KEY *k) {
329 v = next;
330 }
331 k->values.ll = NULL;
332 + k->values.used = 0;
333 memset(k->values.hashtable, 0, sizeof(k->values.hashtable));
334 k->values.enabled = false;
335 }
@@ -309,6 +359,11 @@ static inline FACET_VALUE **facets_values_hashtable_slot(FACET_KEY *k, FACETS_HA
359 return v;
360 }
361
362 +static inline FACET_VALUE *FACET_VALUE_GET_FROM_INDEX(FACET_KEY *k, FACETS_HASH hash) {
363 + FACET_VALUE **v_ptr = facets_values_hashtable_slot(k, hash);
364 + return *v_ptr;
365 +}
366 +
367 static inline FACET_VALUE *FACET_VALUE_ADD_TO_INDEX(FACET_KEY *k, const FACET_VALUE * const tv) {
368 FACET_VALUE **v_ptr = facets_values_hashtable_slot(k, tv->hash);
369
@@ -328,6 +383,7 @@ static inline FACET_VALUE *FACET_VALUE_ADD_TO_INDEX(FACET_KEY *k, const FACET_VA
383 memcpy(v, tv, sizeof(*v));
384
385 DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(k->values.ll, v, prev, next);
386 + k->values.used++;
387
388 if(!v->selected)
389 v->selected = k->default_selected_for_values;
@@ -403,11 +459,15 @@ static inline void facet_key_late_init(FACETS *facets, FACET_KEY *k) {
459 if(facets_key_is_facet(facets, k)) {
460 FACETS_VALUES_INDEX_CREATE(k);
461 k->values.enabled = true;
462 + if(facets->keys_with_values.used < FACETS_KEYS_WITH_VALUES_MAX)
463 + facets->keys_with_values.array[facets->keys_with_values.used++] = k;
464 }
465 }
466
467 static inline void FACETS_KEYS_INDEX_CREATE(FACETS *facets) {
468 facets->keys.ll = NULL;
469 + facets->keys.count = 0;
470 + facets->keys_with_values.used = 0;
471 }
472
473 static inline void FACETS_KEYS_INDEX_DESTROY(FACETS *facets) {
@@ -424,6 +484,8 @@ static inline void FACETS_KEYS_INDEX_DESTROY(FACETS *facets) {
484 }
485 memset(facets->keys.hashtable, 0, sizeof(facets->keys.hashtable));
486 facets->keys.ll = NULL;
487 + facets->keys.count = 0;
488 + facets->keys_with_values.used = 0;
489 }
490
491 static inline FACET_KEY **facets_keys_hashtable_slot(FACETS *facets, FACETS_HASH hash) {
@@ -441,45 +503,67 @@ static inline FACET_KEY *FACETS_KEY_GET_FROM_INDEX(FACETS *facets, FACETS_HASH h
503 return *k;
504 }
505
444 -static inline FACET_KEY *FACETS_KEY_ADD_TO_INDEX(FACETS *facets, FACETS_HASH hash, const char *name, FACET_KEY_OPTIONS options) {
445 - facets->operations.keys.registered++;
506 +bool facets_key_name_value_length_is_selected(FACETS *facets, const char *key, size_t key_length, const char *value, size_t value_length) {
507 + FACETS_HASH hash = FACETS_HASH_FUNCTION(key, key_length);
508 + FACET_KEY *k = FACETS_KEY_GET_FROM_INDEX(facets, hash);
509 + if(!k || k->default_selected_for_values)
510 + return false;
511
447 - FACET_KEY **k_ptr = facets_keys_hashtable_slot(facets, hash);
512 + hash = FACETS_HASH_FUNCTION(value, value_length);
513 + FACET_VALUE *v = FACET_VALUE_GET_FROM_INDEX(k, hash);
514 + return (v && v->selected) ? true : false;
515 +}
516
449 - if(likely(*k_ptr)) {
450 - // already exists
517 +void facets_add_possible_value_name_to_key(FACETS *facets, const char *key, size_t key_length, const char *value, size_t value_length) {
518 + FACETS_HASH hash = FACETS_HASH_FUNCTION(key, key_length);
519 + FACET_KEY *k = FACETS_KEY_GET_FROM_INDEX(facets, hash);
520 + if(!k) return;
521
452 - FACET_KEY *k = *k_ptr;
522 + hash = FACETS_HASH_FUNCTION(value, value_length);
523 + FACET_VALUE *v = FACET_VALUE_GET_FROM_INDEX(k, hash);
524 + if(v && v->name) return;
525
454 - if(!k->name && name) {
455 - // an actual value, not a filter
456 - k->name = strdupz(name);
457 - facet_key_late_init(facets, k);
458 - }
526 + BUFFER *wb = buffer_create(0, NULL);
527 + buffer_contents_replace(wb, value, value_length);
528
460 - internal_fatal(k->name && name && strcmp(k->name, name) != 0,
461 - "key hash conflict: '%s' and '%s' have the same hash '%s'",
462 - k->name, name,
463 - hash_to_static_string(hash));
529 + FACET_VALUE tv = {
530 + .hash = hash,
531 + .name = buffer_tostring(wb),
532 + };
533 + FACET_VALUE_ADD_TO_INDEX(k, &tv);
534
465 - if(k->options & FACET_KEY_OPTION_REORDER) {
466 - k->order = facets->order++;
467 - k->options &= ~FACET_KEY_OPTION_REORDER;
468 - }
535 + buffer_free(wb);
536 +}
537
470 - return k;
471 - }
538 +static void facet_key_set_name(FACET_KEY *k, const char *name, size_t name_length) {
539 + internal_fatal(k->name && name && (strncmp(k->name, name, name_length) != 0 || k->name[name_length] != '\0'),
540 + "key hash conflict: '%s' and '%s' have the same hash",
541 + k->name, name);
542
473 - // we have to add it
543 + if(k->name || !name || !name_length)
544 + return;
545 +
546 + // an actual value, not a filter
547 +
548 + char buf[name_length + 1];
549 + memcpy(buf, name, name_length);
550 + buf[name_length] = '\0';
551 +
552 + internal_fatal(strchr(buf, '='), "found = in key");
553 +
554 + k->name = strdupz(buf);
555 + facet_key_late_init(k->facets, k);
556 +}
557 +
558 +static inline FACET_KEY *FACETS_KEY_CREATE(FACETS *facets, FACETS_HASH hash, const char *name, size_t name_length, FACET_KEY_OPTIONS options) {
559 facets->operations.keys.unique++;
560
561 FACET_KEY *k = callocz(1, sizeof(*k));
477 - *k_ptr = k; // add it to the index
562
563 k->hash = hash;
564 k->facets = facets;
565 k->options = options;
482 - k->current_value.b = buffer_create(0, NULL);
566 + k->current_value.b = buffer_create(sizeof(FACET_VALUE_UNSET), NULL);
567 k->default_selected_for_values = true;
568
569 if(!(k->options & FACET_KEY_OPTION_REORDER))
@@ -488,17 +572,52 @@ static inline FACET_KEY *FACETS_KEY_ADD_TO_INDEX(FACETS *facets, FACETS_HASH has
572 if((k->options & FACET_KEY_OPTION_FTS) || (facets->options & FACETS_OPTION_ALL_KEYS_FTS))
573 facets->keys_filtered_by_query++;
574
491 - if(name) {
492 - // an actual value, not a filter
493 - k->name = strdupz(name);
494 - facet_key_late_init(facets, k);
495 - }
575 + facet_key_set_name(k, name, name_length);
576
577 DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(facets->keys.ll, k, prev, next);
578 + facets->keys.count++;
579 +
580 + return k;
581 +}
582 +
583 +static inline FACET_KEY *FACETS_KEY_ADD_TO_INDEX(FACETS *facets, FACETS_HASH hash, const char *name, size_t name_length, FACET_KEY_OPTIONS options) {
584 + facets->operations.keys.registered++;
585 +
586 + FACET_KEY **k_ptr = facets_keys_hashtable_slot(facets, hash);
587 +
588 + if(unlikely(!(*k_ptr))) {
589 + // we have to add it
590 + *k_ptr = FACETS_KEY_CREATE(facets, hash, name, name_length, options);
591 + return (*k_ptr);
592 + }
593 +
594 + // already in the index
595 +
596 + FACET_KEY *k = *k_ptr;
597 +
598 + facet_key_set_name(k, name, name_length);
599 +
600 + if(unlikely(k->options & FACET_KEY_OPTION_REORDER)) {
601 + k->order = facets->order++;
602 + k->options &= ~FACET_KEY_OPTION_REORDER;
603 + }
604
605 return k;
606 }
607
608 +bool facets_key_name_is_filter(FACETS *facets, const char *key) {
609 + FACETS_HASH hash = FACETS_HASH_FUNCTION(key, strlen(key));
610 + FACET_KEY *k = FACETS_KEY_GET_FROM_INDEX(facets, hash);
611 + return (!k || k->default_selected_for_values) ? false : true;
612 +}
613 +
614 +bool facets_key_name_is_facet(FACETS *facets, const char *key) {
615 + size_t key_len = strlen(key);
616 + FACETS_HASH hash = FACETS_HASH_FUNCTION(key, key_len);
617 + FACET_KEY *k = FACETS_KEY_ADD_TO_INDEX(facets, hash, key, key_len, 0);
618 + return (k && (k->options & FACET_KEY_OPTION_FACET));
619 +}
620 +
621 // ----------------------------------------------------------------------------
622
623 static usec_t calculate_histogram_bar_width(usec_t after_ut, usec_t before_ut) {
@@ -530,7 +649,7 @@ static inline usec_t facets_histogram_slot_baseline_ut(FACETS *facets, usec_t ut
649 return ut - delta_ut;
650 }
651
533 -void facets_set_histogram_by_id(FACETS *facets, const char *key_id, usec_t after_ut, usec_t before_ut) {
652 +void facets_set_timeframe_and_histogram_by_id(FACETS *facets, const char *key_id, usec_t after_ut, usec_t before_ut) {
653 if(after_ut > before_ut) {
654 usec_t t = after_ut;
655 after_ut = before_ut;
@@ -549,37 +668,50 @@ void facets_set_histogram_by_id(FACETS *facets, const char *key_id, usec_t after
668 facets->histogram.hash = FACETS_HASH_ZERO;
669 }
670
671 + facets->timeframe.after_ut = after_ut;
672 + facets->timeframe.before_ut = before_ut;
673 +
674 facets->histogram.slot_width_ut = calculate_histogram_bar_width(after_ut, before_ut);
675 facets->histogram.after_ut = facets_histogram_slot_baseline_ut(facets, after_ut);
676 facets->histogram.before_ut = facets_histogram_slot_baseline_ut(facets, before_ut) + facets->histogram.slot_width_ut;
677 facets->histogram.slots = (facets->histogram.before_ut - facets->histogram.after_ut) / facets->histogram.slot_width_ut + 1;
678
679 + internal_fatal(after_ut < facets->histogram.after_ut, "histogram after_ut is not less or equal to wanted after_ut");
680 + internal_fatal(before_ut > facets->histogram.before_ut, "histogram before_ut is not more or equal to wanted before_ut");
681 +
682 if(facets->histogram.slots > 1000) {
683 facets->histogram.slots = 1000 + 1;
684 facets->histogram.slot_width_ut = (facets->histogram.before_ut - facets->histogram.after_ut) / 1000;
685 }
686 }
687
563 -void facets_set_histogram_by_name(FACETS *facets, const char *key_name, usec_t after_ut, usec_t before_ut) {
688 +void facets_set_timeframe_and_histogram_by_name(FACETS *facets, const char *key_name, usec_t after_ut, usec_t before_ut) {
689 char hash_str[FACET_STRING_HASH_SIZE];
690 FACETS_HASH hash = FACETS_HASH_FUNCTION(key_name, strlen(key_name));
691 facets_hash_to_str(hash, hash_str);
567 - facets_set_histogram_by_id(facets, hash_str, after_ut, before_ut);
692 + facets_set_timeframe_and_histogram_by_id(facets, hash_str, after_ut, before_ut);
693 }
694
570 -static inline void facets_histogram_update_value(FACETS *facets, FACET_KEY *k __maybe_unused, FACET_VALUE *v, usec_t usec) {
571 - if(!facets->histogram.enabled)
695 +static inline void facets_histogram_update_value(FACETS *facets, usec_t usec) {
696 + if(!facets->histogram.enabled ||
697 + !facets->histogram.key ||
698 + !facets->histogram.key->values.enabled ||
699 + !facets->histogram.key->current_value.v ||
700 + usec < facets->histogram.after_ut ||
701 + usec > facets->histogram.before_ut)
702 return;
703
704 + FACET_VALUE *v = facets->histogram.key->current_value.v;
705 +
706 if(unlikely(!v->histogram))
707 v->histogram = callocz(facets->histogram.slots, sizeof(*v->histogram));
708
709 usec_t base_ut = facets_histogram_slot_baseline_ut(facets, usec);
710
579 - if(base_ut < facets->histogram.after_ut)
711 + if(unlikely(base_ut < facets->histogram.after_ut))
712 base_ut = facets->histogram.after_ut;
713
582 - if(base_ut > facets->histogram.before_ut)
714 + if(unlikely(base_ut > facets->histogram.before_ut))
715 base_ut = facets->histogram.before_ut;
716
717 uint32_t slot = (base_ut - facets->histogram.after_ut) / facets->histogram.slot_width_ut;
@@ -591,6 +723,8 @@ static inline void facets_histogram_update_value(FACETS *facets, FACET_KEY *k __
723 }
724
725 static inline void facets_histogram_value_names(BUFFER *wb, FACETS *facets __maybe_unused, FACET_KEY *k, const char *key, const char *first_key) {
726 + BUFFER *tb = NULL;
727 +
728 buffer_json_member_add_array(wb, key);
729 {
730 if(first_key)
@@ -602,12 +736,24 @@ static inline void facets_histogram_value_names(BUFFER *wb, FACETS *facets __may
736 if (unlikely(!v->histogram))
737 continue;
738
605 - buffer_json_add_array_item_string(wb, v->name);
739 + if(!v->empty && k->transform.cb && k->transform.view_only) {
740 + if(!tb)
741 + tb = buffer_create(0, NULL);
742 +
743 + buffer_flush(tb);
744 + buffer_strcat(tb, v->name);
745 + k->transform.cb(facets, tb, FACETS_TRANSFORM_HISTOGRAM, k->transform.data);
746 + buffer_json_add_array_item_string(wb, buffer_tostring(tb));
747 + }
748 + else
749 + buffer_json_add_array_item_string(wb, v->name);
750 }
751 foreach_value_in_key_done(v);
752 }
753 }
754 buffer_json_array_close(wb); // key
755 +
756 + buffer_free(tb);
757 }
758
759 static inline void facets_histogram_value_units(BUFFER *wb, FACETS *facets __maybe_unused, FACET_KEY *k, const char *key) {
@@ -1122,7 +1268,7 @@ static inline bool facets_key_is_facet(FACETS *facets, FACET_KEY *k) {
1268 }
1269 }
1270
1125 - if(included && !excluded && !(facets->options & FACETS_OPTION_DISABLE_ALL_FACETS)) {
1271 + if(included && !excluded) {
1272 k->options |= FACET_KEY_OPTION_FACET;
1273 k->options &= ~FACET_KEY_OPTION_NO_FACET;
1274 return true;
@@ -1150,7 +1296,8 @@ FACETS *facets_create(uint32_t items_to_return, FACETS_OPTIONS options, const ch
1296 facets->visible_keys = simple_pattern_create(visible_keys, "|", SIMPLE_PATTERN_EXACT, true);
1297
1298 facets->max_items_to_return = items_to_return;
1153 - facets->anchor.key = 0;
1299 + facets->anchor.start_ut = 0;
1300 + facets->anchor.stop_ut = 0;
1301 facets->anchor.direction = FACETS_ANCHOR_DIRECTION_BACKWARD;
1302 facets->order = 1;
1303
@@ -1183,7 +1330,7 @@ void facets_accepted_param(FACETS *facets, const char *param) {
1330 }
1331
1332 static inline FACET_KEY *facets_register_key_name_length(FACETS *facets, const char *key, size_t key_length, FACET_KEY_OPTIONS options) {
1186 - return FACETS_KEY_ADD_TO_INDEX(facets, FACETS_HASH_FUNCTION(key, key_length), key, options);
1333 + return FACETS_KEY_ADD_TO_INDEX(facets, FACETS_HASH_FUNCTION(key, key_length), key, key_length, options);
1334 }
1335
1336 inline FACET_KEY *facets_register_key_name(FACETS *facets, const char *key, FACET_KEY_OPTIONS options) {
@@ -1194,6 +1341,7 @@ inline FACET_KEY *facets_register_key_name_transformation(FACETS *facets, const
1341 FACET_KEY *k = facets_register_key_name(facets, key, options);
1342 k->transform.cb = cb;
1343 k->transform.data = data;
1344 + k->transform.view_only = (options & FACET_KEY_OPTION_TRANSFORM_VIEW) ? true : false;
1345 return k;
1346 }
1347
@@ -1215,9 +1363,21 @@ void facets_set_items(FACETS *facets, uint32_t items) {
1363 facets->max_items_to_return = items;
1364 }
1365
1218 -void facets_set_anchor(FACETS *facets, usec_t anchor, FACETS_ANCHOR_DIRECTION direction) {
1219 - facets->anchor.key = anchor;
1366 +void facets_set_anchor(FACETS *facets, usec_t start_ut, usec_t stop_ut, FACETS_ANCHOR_DIRECTION direction) {
1367 + facets->anchor.start_ut = start_ut;
1368 + facets->anchor.stop_ut = stop_ut;
1369 facets->anchor.direction = direction;
1370 +
1371 + if((facets->anchor.direction == FACETS_ANCHOR_DIRECTION_BACKWARD && facets->anchor.start_ut && facets->anchor.start_ut < facets->anchor.stop_ut) ||
1372 + (facets->anchor.direction == FACETS_ANCHOR_DIRECTION_FORWARD && facets->anchor.stop_ut && facets->anchor.stop_ut < facets->anchor.start_ut)) {
1373 + internal_error(true, "start and stop anchors are flipped");
1374 + facets->anchor.start_ut = stop_ut;
1375 + facets->anchor.stop_ut = start_ut;
1376 + }
1377 +}
1378 +
1379 +void facets_enable_slice_mode(FACETS *facets) {
1380 + facets->options |= FACETS_OPTION_DONT_SEND_EMPTY_VALUE_FACETS | FACETS_OPTION_SORT_FACETS_ALPHABETICALLY;
1381 }
1382
1383 inline FACET_KEY *facets_register_facet_id(FACETS *facets, const char *key_id, FACET_KEY_OPTIONS options) {
@@ -1229,7 +1389,7 @@ inline FACET_KEY *facets_register_facet_id(FACETS *facets, const char *key_id, F
1389 internal_error(strcmp(hash_to_static_string(hash), key_id) != 0,
1390 "Regenerating the user supplied key, does not produce the same hash string");
1391
1232 - FACET_KEY *k = FACETS_KEY_ADD_TO_INDEX(facets, hash, NULL, options);
1392 + FACET_KEY *k = FACETS_KEY_ADD_TO_INDEX(facets, hash, NULL, 0, options);
1393 k->options |= FACET_KEY_OPTION_FACET;
1394 k->options &= ~FACET_KEY_OPTION_NO_FACET;
1395 facet_key_late_init(facets, k);
@@ -1251,22 +1411,33 @@ void facets_set_current_row_severity(FACETS *facets, FACET_ROW_SEVERITY severity
1411 facets->current_row.severity = severity;
1412 }
1413
1254 -void facets_data_only_mode(FACETS *facets) {
1255 - facets->options |= FACETS_OPTION_DISABLE_ALL_FACETS | FACETS_OPTION_DISABLE_HISTOGRAM | FACETS_OPTION_DATA_ONLY;
1414 +void facets_register_row_severity(FACETS *facets, facet_row_severity_t cb, void *data) {
1415 + facets->severity.cb = cb;
1416 + facets->severity.data = data;
1417 +}
1418 +
1419 +void facets_set_additional_options(FACETS *facets, FACETS_OPTIONS options) {
1420 + facets->options |= options;
1421 }
1422
1423 // ----------------------------------------------------------------------------
1424
1425 static inline void facets_key_set_empty_value(FACETS *facets, FACET_KEY *k) {
1426 + if(likely(!k->current_value.updated && facets->keys_in_row.used < FACETS_KEYS_IN_ROW_MAX))
1427 + facets->keys_in_row.array[facets->keys_in_row.used++] = k;
1428 +
1429 k->current_value.updated = true;
1430 k->current_value.empty = true;
1431
1432 facets->operations.values.registered++;
1433 facets->operations.values.empty++;
1434
1267 - buffer_contents_replace(k->current_value.b, FACET_VALUE_UNSET, sizeof(FACET_VALUE_UNSET) - 1);
1435 + // no need to copy the UNSET value
1436 + // empty values are exported as empty
1437 + k->current_value.b->len = 0;
1438 + // buffer_contents_replace(k->current_value.b, FACET_VALUE_UNSET, sizeof(FACET_VALUE_UNSET) - 1);
1439
1269 - if(k->values.enabled)
1440 + if(unlikely(k->values.enabled))
1441 FACET_VALUE_ADD_EMPTY_VALUE_TO_INDEX(k);
1442 else {
1443 k->key_found_in_row++;
@@ -1275,14 +1446,17 @@ static inline void facets_key_set_empty_value(FACETS *facets, FACET_KEY *k) {
1446 }
1447
1448 static inline void facets_key_check_value(FACETS *facets, FACET_KEY *k) {
1449 + if(likely(!k->current_value.updated && facets->keys_in_row.used < FACETS_KEYS_IN_ROW_MAX))
1450 + facets->keys_in_row.array[facets->keys_in_row.used++] = k;
1451 +
1452 k->current_value.updated = true;
1453 k->current_value.empty = false;
1454
1455 facets->operations.values.registered++;
1456
1283 - if(k->transform.cb) {
1457 + if(k->transform.cb && !k->transform.view_only) {
1458 facets->operations.values.transformed++;
1285 - k->transform.cb(facets, k->current_value.b, k->transform.data);
1459 + k->transform.cb(facets, k->current_value.b, FACETS_TRANSFORM_VALUE, k->transform.data);
1460 }
1461
1462 // bool found = false;
@@ -1326,7 +1500,8 @@ static void facet_row_key_value_insert_callback(const DICTIONARY_ITEM *item __ma
1500 FACET_ROW *row = data; (void)row;
1501
1502 rkv->wb = buffer_create(0, NULL);
1329 - buffer_strcat(rkv->wb, rkv->tmp);
1503 + if(!rkv->empty)
1504 + buffer_strcat(rkv->wb, rkv->tmp);
1505 }
1506
1507 static bool facet_row_key_value_conflict_callback(const DICTIONARY_ITEM *item __maybe_unused, void *old_value, void *new_value, void *data) {
@@ -1334,8 +1509,11 @@ static bool facet_row_key_value_conflict_callback(const DICTIONARY_ITEM *item __
1509 FACET_ROW_KEY_VALUE *n_rkv = new_value;
1510 FACET_ROW *row = data; (void)row;
1511
1512 + rkv->empty = n_rkv->empty;
1513 +
1514 buffer_flush(rkv->wb);
1338 - buffer_strcat(rkv->wb, n_rkv->tmp);
1515 + if(!rkv->empty)
1516 + buffer_strcat(rkv->wb, n_rkv->tmp);
1517
1518 return false;
1519 }
@@ -1377,9 +1555,9 @@ static FACET_ROW *facets_row_create(FACETS *facets, usec_t usec, FACET_ROW *into
1555 FACET_KEY *k;
1556 foreach_key_in_facets(facets, k) {
1557 FACET_ROW_KEY_VALUE t = {
1380 - .tmp = (k->current_value.updated) ? buffer_tostring(k->current_value.b) : FACET_VALUE_UNSET,
1558 + .tmp = (k->current_value.updated && !k->current_value.empty) ? buffer_tostring(k->current_value.b) : NULL,
1559 .wb = NULL,
1382 - .empty = k->current_value.empty,
1560 + .empty = !k->current_value.updated || k->current_value.empty,
1561 };
1562 dictionary_set(row->dict, k->name, &t, sizeof(t));
1563 }
@@ -1418,10 +1596,8 @@ static void facets_row_keep_first_entry(FACETS *facets, usec_t usec) {
1596 facets->operations.first++;
1597 }
1598
1421 -static void facets_row_keep(FACETS *facets, usec_t usec) {
1422 - facets->operations.rows.matched++;
1423 -
1424 - if(facets->anchor.key) {
1599 +static inline bool facets_is_entry_within_anchor(FACETS *facets, usec_t usec) {
1600 + if(facets->anchor.start_ut || facets->anchor.stop_ut) {
1601 // we have an anchor key
1602 // we don't want to keep rows on the other side of the direction
1603
@@ -1429,22 +1605,36 @@ static void facets_row_keep(FACETS *facets, usec_t usec) {
1605 default:
1606 case FACETS_ANCHOR_DIRECTION_BACKWARD:
1607 // we need to keep only the smaller timestamps
1432 - if (usec >= facets->anchor.key) {
1608 + if (facets->anchor.start_ut && usec >= facets->anchor.start_ut) {
1609 facets->operations.skips_before++;
1434 - return;
1610 + return false;
1611 + }
1612 + if (facets->anchor.stop_ut && usec <= facets->anchor.stop_ut) {
1613 + facets->operations.skips_after++;
1614 + return false;
1615 }
1616 break;
1617
1618 case FACETS_ANCHOR_DIRECTION_FORWARD:
1619 // we need to keep only the bigger timestamps
1440 - if (usec <= facets->anchor.key) {
1620 + if (facets->anchor.start_ut && usec <= facets->anchor.start_ut) {
1621 facets->operations.skips_after++;
1442 - return;
1622 + return false;
1623 + }
1624 + if (facets->anchor.stop_ut && usec >= facets->anchor.stop_ut) {
1625 + facets->operations.skips_before++;
1626 + return false;
1627 }
1628 break;
1629 }
1630 }
1631
1632 + return true;
1633 +}
1634 +
1635 +static void facets_row_keep(FACETS *facets, usec_t usec) {
1636 + facets->operations.rows.matched++;
1637 +
1638 if(unlikely(!facets->base)) {
1639 // the first row to keep
1640 facets_row_keep_first_entry(facets, usec);
@@ -1510,81 +1700,112 @@ static void facets_row_keep(FACETS *facets, usec_t usec) {
1700 facets->items_to_return++;
1701 }
1702
1703 +static inline void facets_reset_key(FACET_KEY *k) {
1704 + k->key_found_in_row = 0;
1705 + k->key_values_selected_in_row = 0;
1706 + k->current_value.updated = false;
1707 + k->current_value.empty = false;
1708 + k->current_value.hash = FACETS_HASH_ZERO;
1709 + k->current_value.v = NULL;
1710 +}
1711 +
1712 +static void facets_reset_keys_with_value_and_row(FACETS *facets) {
1713 + size_t entries = facets->keys_in_row.used;
1714 +
1715 + for(size_t p = 0; p < entries ;p++) {
1716 + FACET_KEY *k = facets->keys_in_row.array[p];
1717 + facets_reset_key(k);
1718 + }
1719 +
1720 + facets->current_row.severity = FACET_ROW_SEVERITY_NORMAL;
1721 + facets->current_row.keys_matched_by_query = 0;
1722 + facets->keys_in_row.used = 0;
1723 +}
1724 +
1725 void facets_rows_begin(FACETS *facets) {
1726 FACET_KEY *k;
1727 foreach_key_in_facets(facets, k) {
1516 - k->key_found_in_row = 0;
1517 - k->key_values_selected_in_row = 0;
1518 - k->current_value.updated = false;
1519 - k->current_value.empty = false;
1520 - k->current_value.hash = FACETS_HASH_ZERO;
1521 - k->current_value.v = NULL;
1728 + facets_reset_key(k);
1729 }
1730 foreach_key_in_facets_done(k);
1731
1525 - facets->current_row.severity = FACET_ROW_SEVERITY_NORMAL;
1526 - facets->current_row.keys_matched_by_query = 0;
1732 + facets->keys_in_row.used = 0;
1733 + facets_reset_keys_with_value_and_row(facets);
1734 }
1735
1529 -void facets_row_finished(FACETS *facets, usec_t usec) {
1530 - if(facets->query && facets->keys_filtered_by_query && !facets->current_row.keys_matched_by_query)
1531 - goto cleanup;
1532 -
1736 +bool facets_row_finished(FACETS *facets, usec_t usec) {
1737 facets->operations.rows.evaluated++;
1738
1535 - uint32_t total_keys = 0;
1536 - uint32_t selected_by = 0;
1739 + if((facets->query && facets->keys_filtered_by_query && !facets->current_row.keys_matched_by_query) ||
1740 + (facets->timeframe.before_ut && usec > facets->timeframe.before_ut) ||
1741 + (facets->timeframe.after_ut && usec < facets->timeframe.after_ut)) {
1742 + // this row is not useful
1743 + // 1. not matched by full text search, or
1744 + // 2. not in our timeframe
1745 + facets_reset_keys_with_value_and_row(facets);
1746 + return false;
1747 + }
1748 +
1749 + size_t entries = facets->keys_with_values.used;
1750 + size_t total_keys = 0;
1751 + size_t selected_keys = 0;
1752 +
1753 + for(size_t p = 0; p < entries ;p++) {
1754 + FACET_KEY *k = facets->keys_with_values.array[p];
1755
1538 - FACET_KEY *k;
1539 - foreach_key_in_facets(facets, k) {
1756 if(!k->key_found_in_row) {
1757 // put the FACET_VALUE_UNSET value into it
1758 facets_key_set_empty_value(facets, k);
1759 }
1760
1545 - internal_fatal(!k->key_found_in_row, "all keys should be found in the row at this point");
1761 internal_fatal(k->key_found_in_row != 1, "all keys should be matched exactly once at this point");
1762 internal_fatal(k->key_values_selected_in_row > 1, "key values are selected in row more than once");
1763
1549 - k->key_found_in_row = 1;
1550 -
1764 total_keys++;
1552 - selected_by += (k->key_values_selected_in_row) ? 1 : 0;
1765 +
1766 + if(k->key_values_selected_in_row)
1767 + selected_keys++;
1768 +
1769 + if(unlikely(!facets->histogram.key && facets->histogram.hash == k->hash))
1770 + facets->histogram.key = k;
1771 }
1554 - foreach_key_in_facets_done(k);
1772
1556 - if(selected_by >= total_keys - 1) {
1557 - uint32_t found = 0;
1773 + bool within_anchor = facets_is_entry_within_anchor(facets, usec);
1774
1559 - foreach_key_in_facets(facets, k) {
1560 - uint32_t counted_by = selected_by;
1775 + if(within_anchor && selected_keys >= total_keys - 1) {
1776 + size_t found = 0; (void)found;
1777 +
1778 + for(size_t p = 0; p < entries ;p++) {
1779 + FACET_KEY *k = facets->keys_with_values.array[p];
1780 +
1781 + size_t counted_by = selected_keys;
1782
1783 if (counted_by != total_keys && !k->key_values_selected_in_row)
1784 counted_by++;
1785
1565 - if(counted_by == total_keys) {
1566 - if(k->values.enabled) {
1567 - FACET_VALUE *v = FACET_VALUE_GET_CURRENT_VALUE(k);
1568 - v->final_facet_value_counter++;
1569 -
1570 - if(selected_by == total_keys)
1571 - facets_histogram_update_value(facets, k, v, usec);
1572 - }
1786 + if (counted_by == total_keys) {
1787 + FACET_VALUE *v = FACET_VALUE_GET_CURRENT_VALUE(k);
1788 + v->final_facet_value_counter++;
1789
1790 found++;
1791 }
1792 }
1577 - foreach_key_in_facets_done(k);
1793
1794 internal_fatal(!found, "We should find at least one facet to count this row");
1580 - (void)found;
1795 }
1796
1583 - if(selected_by == total_keys)
1584 - facets_row_keep(facets, usec);
1797 + if(selected_keys == total_keys) {
1798 + // we need to keep this row
1799 +
1800 + facets_histogram_update_value(facets, usec);
1801 +
1802 + if(within_anchor)
1803 + facets_row_keep(facets, usec);
1804 + }
1805 +
1806 + facets_reset_keys_with_value_and_row(facets);
1807
1586 -cleanup:
1587 - facets_rows_begin(facets);
1808 + return selected_keys == total_keys;
1809 }
1810
1811 // ----------------------------------------------------------------------------
@@ -1635,49 +1856,307 @@ void facets_accepted_parameters_to_json_array(FACETS *facets, BUFFER *wb, bool w
1856 buffer_json_array_close(wb); // accepted_params
1857 }
1858
1638 -void facets_report(FACETS *facets, BUFFER *wb) {
1639 - if(!(facets->options & FACETS_OPTION_DATA_ONLY)) {
1640 - buffer_json_member_add_boolean(wb, "show_ids", false); // do not show the column ids to the user
1641 - buffer_json_member_add_boolean(wb, "has_history", true); // enable date-time picker with after-before
1859 +static int facets_keys_reorder_compar(const void *a, const void *b) {
1860 + const FACET_KEY *ak = *((const FACET_KEY **)a);
1861 + const FACET_KEY *bk = *((const FACET_KEY **)b);
1862
1643 - buffer_json_member_add_object(wb, "pagination");
1644 - {
1645 - buffer_json_member_add_boolean(wb, "enabled", true);
1646 - buffer_json_member_add_string(wb, "key", "anchor");
1647 - buffer_json_member_add_string(wb, "column", "timestamp");
1648 - buffer_json_member_add_string(wb, "units", "timestamp_usec");
1863 + const char *an = ak->name;
1864 + const char *bn = bk->name;
1865 +
1866 + if(!an) an = "0";
1867 + if(!bn) bn = "0";
1868 +
1869 + while(*an && ispunct(*an)) an++;
1870 + while(*bn && ispunct(*bn)) bn++;
1871 +
1872 + return strcasecmp(an, bn);
1873 +}
1874 +
1875 +void facets_sort_and_reorder_keys(FACETS *facets) {
1876 + size_t entries = facets->keys_with_values.used;
1877 + if(!entries)
1878 + return;
1879 +
1880 + FACET_KEY *keys[entries];
1881 + memcpy(keys, facets->keys_with_values.array, sizeof(FACET_KEY *) * entries);
1882 +
1883 + qsort(keys, entries, sizeof(FACET_KEY *), facets_keys_reorder_compar);
1884 +
1885 + for(size_t i = 0; i < entries ;i++)
1886 + keys[i]->order = i + 1;
1887 +}
1888 +
1889 +static int facets_key_values_reorder_by_name_compar(const void *a, const void *b) {
1890 + const FACET_VALUE *av = *((const FACET_VALUE **)a);
1891 + const FACET_VALUE *bv = *((const FACET_VALUE **)b);
1892 +
1893 + const char *an = av->name;
1894 + const char *bn = bv->name;
1895 +
1896 + if(!an) an = "0";
1897 + if(!bn) bn = "0";
1898 +
1899 + while(*an && ispunct(*an)) an++;
1900 + while(*bn && ispunct(*bn)) bn++;
1901 +
1902 + int ret = strcasecmp(an, bn);
1903 + return ret;
1904 +}
1905 +
1906 +static int facets_key_values_reorder_by_count_compar(const void *a, const void *b) {
1907 + const FACET_VALUE *av = *((const FACET_VALUE **)a);
1908 + const FACET_VALUE *bv = *((const FACET_VALUE **)b);
1909 +
1910 + if(av->final_facet_value_counter < bv->final_facet_value_counter)
1911 + return 1;
1912 +
1913 + if(av->final_facet_value_counter > bv->final_facet_value_counter)
1914 + return -1;
1915 +
1916 + return facets_key_values_reorder_by_name_compar(a, b);
1917 +}
1918 +
1919 +static int facets_key_values_reorder_by_name_numeric_compar(const void *a, const void *b) {
1920 + const FACET_VALUE *av = *((const FACET_VALUE **)a);
1921 + const FACET_VALUE *bv = *((const FACET_VALUE **)b);
1922 +
1923 + const char *an = av->name;
1924 + const char *bn = bv->name;
1925 +
1926 + if(!an) an = "0";
1927 + if(!bn) bn = "0";
1928 +
1929 + if(strcmp(an, FACET_VALUE_UNSET) == 0) an = "0";
1930 + if(strcmp(bn, FACET_VALUE_UNSET) == 0) bn = "0";
1931 +
1932 + int64_t ad = str2ll(an, NULL);
1933 + int64_t bd = str2ll(bn, NULL);
1934 +
1935 + if(ad < bd)
1936 + return -1;
1937 +
1938 + if(ad > bd)
1939 + return 1;
1940 +
1941 + return facets_key_values_reorder_by_name_compar(a, b);
1942 +}
1943 +
1944 +static uint32_t facets_sort_and_reorder_values_internal(FACET_KEY *k) {
1945 + bool all_values_numeric = true;
1946 + size_t entries = k->values.used;
1947 + FACET_VALUE *values[entries], *v;
1948 + uint32_t used = 0;
1949 + foreach_value_in_key(k, v) {
1950 + if((k->facets->options & FACETS_OPTION_DONT_SEND_EMPTY_VALUE_FACETS) && v->empty)
1951 + continue;
1952 +
1953 + if(used >= entries)
1954 + break;
1955 +
1956 + values[used++] = v;
1957 +
1958 + if(all_values_numeric && !v->empty && v->name) {
1959 + const char *s = v->name;
1960 + while(isdigit(*s)) s++;
1961 + if(*s != '\0')
1962 + all_values_numeric = false;
1963 }
1650 - buffer_json_object_close(wb); // pagination
1964 + }
1965 + foreach_value_in_key_done(v);
1966 +
1967 + if(!used)
1968 + return 0;
1969 +
1970 + if(k->facets->options & FACETS_OPTION_SORT_FACETS_ALPHABETICALLY) {
1971 + if(all_values_numeric)
1972 + qsort(values, used, sizeof(FACET_VALUE *), facets_key_values_reorder_by_name_numeric_compar);
1973 + else
1974 + qsort(values, used, sizeof(FACET_VALUE *), facets_key_values_reorder_by_name_compar);
1975 + }
1976 + else
1977 + qsort(values, used, sizeof(FACET_VALUE *), facets_key_values_reorder_by_count_compar);
1978 +
1979 + for(size_t i = 0; i < used; i++)
1980 + values[i]->order = i + 1;
1981 +
1982 + return used;
1983 +}
1984 +
1985 +static uint32_t facets_sort_and_reorder_values(FACET_KEY *k) {
1986 + if(!k->values.enabled || !k->values.ll || !k->values.used)
1987 + return 0;
1988 +
1989 + if(!k->transform.cb || !(k->facets->options & FACETS_OPTION_SORT_FACETS_ALPHABETICALLY))
1990 + return facets_sort_and_reorder_values_internal(k);
1991 +
1992 + // we have a transformation and has to be sorted alphabetically
1993 +
1994 + BUFFER *tb = buffer_create(0, NULL);
1995 + uint32_t ret = 0;
1996 +
1997 + size_t entries = k->values.used;
1998 + const char *values[entries];
1999 + FACET_VALUE *v;
2000 + uint32_t used = 0;
2001 +
2002 + foreach_value_in_key(k, v) {
2003 + if(used >= entries)
2004 + break;
2005 +
2006 + values[used++] = v->name;
2007 +
2008 + buffer_flush(tb);
2009 + buffer_strcat(tb, v->name);
2010 + k->transform.cb(k->facets, tb, FACETS_TRANSFORM_FACET_SORT, k->transform.data);
2011 + v->name = strdupz(buffer_tostring(tb));
2012 + }
2013 + foreach_value_in_key_done(v);
2014 +
2015 + ret = facets_sort_and_reorder_values_internal(k);
2016 +
2017 + used = 0;
2018 + foreach_value_in_key(k, v) {
2019 + if(used >= entries)
2020 + break;
2021 +
2022 + freez((void *)v->name);
2023 + v->name = values[used++];
2024 + }
2025 + foreach_value_in_key_done(v);
2026 +
2027 + buffer_free(tb);
2028 + return ret;
2029 +}
2030 +
2031 +void facets_table_config(BUFFER *wb) {
2032 + buffer_json_member_add_boolean(wb, "show_ids", false); // do not show the column ids to the user
2033 + buffer_json_member_add_boolean(wb, "has_history", true); // enable date-time picker with after-before
2034 +
2035 + buffer_json_member_add_object(wb, "pagination");
2036 + {
2037 + buffer_json_member_add_boolean(wb, "enabled", true);
2038 + buffer_json_member_add_string(wb, "key", "anchor");
2039 + buffer_json_member_add_string(wb, "column", "timestamp");
2040 + buffer_json_member_add_string(wb, "units", "timestamp_usec");
2041 + }
2042 + buffer_json_object_close(wb); // pagination
2043 +}
2044 +
2045 +static const char *facets_json_key_name_string(FACET_KEY *k, DICTIONARY *used_hashes_registry) {
2046 + if(k->name) {
2047 + if(used_hashes_registry && !k->default_selected_for_values) {
2048 + char hash_str[FACET_STRING_HASH_SIZE];
2049 + facets_hash_to_str(k->hash, hash_str);
2050 + dictionary_set(used_hashes_registry, hash_str, (void *)k->name, strlen(k->name) + 1);
2051 + }
2052 +
2053 + return k->name;
2054 + }
2055 +
2056 + // key has no name
2057 + const char *name = "[UNAVAILABLE_FIELD]";
2058
2059 + if(used_hashes_registry) {
2060 + char hash_str[FACET_STRING_HASH_SIZE];
2061 + facets_hash_to_str(k->hash, hash_str);
2062 + const char *s = dictionary_get(used_hashes_registry, hash_str);
2063 + if(s) name = s;
2064 + }
2065 +
2066 + return name;
2067 +}
2068 +
2069 +static const char *facets_json_key_value_string(FACET_KEY *k, FACET_VALUE *v, DICTIONARY *used_hashes_registry) {
2070 + if(v->name) {
2071 + if(used_hashes_registry && !k->default_selected_for_values && v->selected) {
2072 + char hash_str[FACET_STRING_HASH_SIZE];
2073 + facets_hash_to_str(v->hash, hash_str);
2074 + dictionary_set(used_hashes_registry, hash_str, (void *)v->name, strlen(v->name) + 1);
2075 + }
2076 +
2077 + return v->name;
2078 + }
2079 +
2080 + // key has no name
2081 + const char *name = "[unavailable field]";
2082 +
2083 + if(used_hashes_registry) {
2084 + char hash_str[FACET_STRING_HASH_SIZE];
2085 + facets_hash_to_str(v->hash, hash_str);
2086 + const char *s = dictionary_get(used_hashes_registry, hash_str);
2087 + if(s) name = s;
2088 + }
2089 +
2090 + return name;
2091 +}
2092 +
2093 +void facets_report(FACETS *facets, BUFFER *wb, DICTIONARY *used_hashes_registry) {
2094 + if(!(facets->options & FACETS_OPTION_DATA_ONLY)) {
2095 + facets_table_config(wb);
2096 facets_accepted_parameters_to_json_array(facets, wb, true);
2097 }
2098
1655 - if(!(facets->options & FACETS_OPTION_DISABLE_ALL_FACETS)) {
1656 - buffer_json_member_add_array(wb, "facets");
1657 - {
2099 + // ------------------------------------------------------------------------
2100 + // facets
2101 +
2102 + if(!(facets->options & FACETS_OPTION_DONT_SEND_FACETS)) {
2103 + bool show_facets = false;
2104 +
2105 + if(facets->options & FACETS_OPTION_DATA_ONLY) {
2106 + if(facets->options & FACETS_OPTION_SHOW_DELTAS) {
2107 + buffer_json_member_add_array(wb, "facets_delta");
2108 + show_facets = true;
2109 + }
2110 + }
2111 + else {
2112 + buffer_json_member_add_array(wb, "facets");
2113 + show_facets = true;
2114 + }
2115 +
2116 + if(show_facets) {
2117 + BUFFER *tb = NULL;
2118 FACET_KEY *k;
2119 foreach_key_in_facets(facets, k) {
2120 if(!k->values.enabled)
2121 continue;
2122
2123 + if(!facets_sort_and_reorder_values(k))
2124 + // no values for this key
2125 + continue;
2126 +
2127 buffer_json_add_array_item_object(wb); // key
2128 {
2129 buffer_json_member_add_string(wb, "id", hash_to_static_string(k->hash));
1666 - buffer_json_member_add_string(wb, "name", k->name);
1667 -
1668 - if(!k->order)
1669 - k->order = facets->order++;
2130 + buffer_json_member_add_string(wb, "name", facets_json_key_name_string(k, used_hashes_registry));
2131
2132 + if(!k->order) k->order = facets->order++;
2133 buffer_json_member_add_uint64(wb, "order", k->order);
2134 +
2135 buffer_json_member_add_array(wb, "options");
2136 {
2137 FACET_VALUE *v;
2138 foreach_value_in_key(k, v) {
2139 + if((facets->options & FACETS_OPTION_DONT_SEND_EMPTY_VALUE_FACETS) && v->empty)
2140 + continue;
2141 +
2142 buffer_json_add_array_item_object(wb);
2143 {
2144 buffer_json_member_add_string(wb, "id", hash_to_static_string(v->hash));
1679 - buffer_json_member_add_string(wb, "name", v->name);
2145 +
2146 + if(!v->empty && k->transform.cb && k->transform.view_only) {
2147 + if(!tb)
2148 + tb = buffer_create(0, NULL);
2149 +
2150 + buffer_flush(tb);
2151 + buffer_strcat(tb, v->name);
2152 + k->transform.cb(facets, tb, FACETS_TRANSFORM_FACET, k->transform.data);
2153 + buffer_json_member_add_string(wb, "name", buffer_tostring(tb));
2154 + }
2155 + else
2156 + buffer_json_member_add_string(wb, "name", facets_json_key_value_string(k, v, used_hashes_registry));
2157 +
2158 buffer_json_member_add_uint64(wb, "count", v->final_facet_value_counter);
2159 + buffer_json_member_add_uint64(wb, "order", v->order);
2160 }
2161 buffer_json_object_close(wb);
2162 }
@@ -1688,77 +2167,81 @@ void facets_report(FACETS *facets, BUFFER *wb) {
2167 buffer_json_object_close(wb); // key
2168 }
2169 foreach_key_in_facets_done(k);
2170 + buffer_free(tb);
2171 + buffer_json_array_close(wb); // facets
2172 }
1692 - buffer_json_array_close(wb); // facets
2173 }
2174
1695 - if(!(facets->options & FACETS_OPTION_DATA_ONLY)) {
1696 - buffer_json_member_add_object(wb, "columns");
1697 - {
1698 - size_t field_id = 0;
1699 - buffer_rrdf_table_add_field(
1700 - wb, field_id++,
1701 - "timestamp", "Timestamp",
1702 - RRDF_FIELD_TYPE_TIMESTAMP,
1703 - RRDF_FIELD_VISUAL_VALUE,
1704 - RRDF_FIELD_TRANSFORM_DATETIME_USEC, 0, NULL, NAN,
1705 - RRDF_FIELD_SORT_DESCENDING,
1706 - NULL,
1707 - RRDF_FIELD_SUMMARY_COUNT,
1708 - RRDF_FIELD_FILTER_RANGE,
1709 - RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_UNIQUE_KEY,
1710 - NULL);
1711 -
1712 - buffer_rrdf_table_add_field(
1713 - wb, field_id++,
1714 - "rowOptions", "rowOptions",
1715 - RRDF_FIELD_TYPE_NONE,
1716 - RRDR_FIELD_VISUAL_ROW_OPTIONS,
1717 - RRDF_FIELD_TRANSFORM_NONE, 0, NULL, NAN,
1718 - RRDF_FIELD_SORT_FIXED,
1719 - NULL,
1720 - RRDF_FIELD_SUMMARY_COUNT,
1721 - RRDF_FIELD_FILTER_NONE,
1722 - RRDR_FIELD_OPTS_DUMMY,
1723 - NULL);
2175 + // ------------------------------------------------------------------------
2176 + // columns
2177
1725 - FACET_KEY *k;
1726 - foreach_key_in_facets(facets, k) {
1727 - RRDF_FIELD_OPTIONS options = RRDF_FIELD_OPTS_NONE;
1728 - bool visible = k->options & (FACET_KEY_OPTION_VISIBLE | FACET_KEY_OPTION_STICKY);
1729 -
1730 - if ((facets->options & FACETS_OPTION_ALL_FACETS_VISIBLE && k->values.enabled))
1731 - visible = true;
1732 -
1733 - if (!visible)
1734 - visible = simple_pattern_matches(facets->visible_keys, k->name);
1735 -
1736 - if (visible)
1737 - options |= RRDF_FIELD_OPTS_VISIBLE;
1738 -
1739 - if (k->options & FACET_KEY_OPTION_MAIN_TEXT)
1740 - options |= RRDF_FIELD_OPTS_FULL_WIDTH | RRDF_FIELD_OPTS_WRAP;
1741 -
1742 - const char *hash_str = hash_to_static_string(k->hash);
1743 -
1744 - buffer_rrdf_table_add_field(
1745 - wb, field_id++,
1746 - hash_str, k->name ? k->name : hash_str,
1747 - RRDF_FIELD_TYPE_STRING,
1748 - (k->options & FACET_KEY_OPTION_RICH_TEXT) ? RRDF_FIELD_VISUAL_RICH : RRDF_FIELD_VISUAL_VALUE,
1749 - RRDF_FIELD_TRANSFORM_NONE, 0, NULL, NAN,
1750 - RRDF_FIELD_SORT_ASCENDING,
1751 - NULL,
1752 - RRDF_FIELD_SUMMARY_COUNT,
1753 - (k->options & FACET_KEY_OPTION_NEVER_FACET) ? RRDF_FIELD_FILTER_NONE
1754 - : RRDF_FIELD_FILTER_FACET,
1755 - options,
1756 - FACET_VALUE_UNSET);
1757 - }
1758 - foreach_key_in_facets_done(k);
1759 - }
1760 - buffer_json_object_close(wb); // columns
2178 + buffer_json_member_add_object(wb, "columns");
2179 + {
2180 + size_t field_id = 0;
2181 + buffer_rrdf_table_add_field(
2182 + wb, field_id++,
2183 + "timestamp", "Timestamp",
2184 + RRDF_FIELD_TYPE_TIMESTAMP,
2185 + RRDF_FIELD_VISUAL_VALUE,
2186 + RRDF_FIELD_TRANSFORM_DATETIME_USEC, 0, NULL, NAN,
2187 + RRDF_FIELD_SORT_DESCENDING,
2188 + NULL,
2189 + RRDF_FIELD_SUMMARY_COUNT,
2190 + RRDF_FIELD_FILTER_RANGE,
2191 + RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_UNIQUE_KEY,
2192 + NULL);
2193 +
2194 + buffer_rrdf_table_add_field(
2195 + wb, field_id++,
2196 + "rowOptions", "rowOptions",
2197 + RRDF_FIELD_TYPE_NONE,
2198 + RRDR_FIELD_VISUAL_ROW_OPTIONS,
2199 + RRDF_FIELD_TRANSFORM_NONE, 0, NULL, NAN,
2200 + RRDF_FIELD_SORT_FIXED,
2201 + NULL,
2202 + RRDF_FIELD_SUMMARY_COUNT,
2203 + RRDF_FIELD_FILTER_NONE,
2204 + RRDR_FIELD_OPTS_DUMMY,
2205 + NULL);
2206 +
2207 + FACET_KEY *k;
2208 + foreach_key_in_facets(facets, k) {
2209 + RRDF_FIELD_OPTIONS options = RRDF_FIELD_OPTS_NONE;
2210 + bool visible = k->options & (FACET_KEY_OPTION_VISIBLE | FACET_KEY_OPTION_STICKY);
2211 +
2212 + if ((facets->options & FACETS_OPTION_ALL_FACETS_VISIBLE && k->values.enabled))
2213 + visible = true;
2214 +
2215 + if (!visible)
2216 + visible = simple_pattern_matches(facets->visible_keys, k->name);
2217 +
2218 + if (visible)
2219 + options |= RRDF_FIELD_OPTS_VISIBLE;
2220 +
2221 + if (k->options & FACET_KEY_OPTION_MAIN_TEXT)
2222 + options |= RRDF_FIELD_OPTS_FULL_WIDTH | RRDF_FIELD_OPTS_WRAP;
2223 +
2224 + const char *hash_str = hash_to_static_string(k->hash);
2225 +
2226 + buffer_rrdf_table_add_field(
2227 + wb, field_id++,
2228 + hash_str, k->name ? k->name : hash_str,
2229 + RRDF_FIELD_TYPE_STRING,
2230 + (k->options & FACET_KEY_OPTION_RICH_TEXT) ? RRDF_FIELD_VISUAL_RICH : RRDF_FIELD_VISUAL_VALUE,
2231 + RRDF_FIELD_TRANSFORM_NONE, 0, NULL, NAN,
2232 + RRDF_FIELD_SORT_ASCENDING,
2233 + NULL,
2234 + RRDF_FIELD_SUMMARY_COUNT,
2235 + (k->options & FACET_KEY_OPTION_NEVER_FACET) ? RRDF_FIELD_FILTER_NONE
2236 + : RRDF_FIELD_FILTER_FACET,
2237 + options, FACET_VALUE_UNSET);
2238 + }
2239 + foreach_key_in_facets_done(k);
2240 }
2241 + buffer_json_object_close(wb); // columns
2242 +
2243 + // ------------------------------------------------------------------------
2244 + // rows data
2245
2246 buffer_json_member_add_array(wb, "data");
2247 {
@@ -1767,10 +2250,10 @@ void facets_report(FACETS *facets, BUFFER *wb) {
2250 for(FACET_ROW *row = facets->base ; row ;row = row->next) {
2251
2252 internal_fatal(
1770 - facets->anchor.key && (
1771 - (facets->anchor.direction == FACETS_ANCHOR_DIRECTION_BACKWARD && row->usec >= facets->anchor.key) ||
1772 - (facets->anchor.direction == FACETS_ANCHOR_DIRECTION_FORWARD && row->usec <= facets->anchor.key)
1773 - ), "Wrong data returned related to %s anchor!", facets->anchor.direction == FACETS_ANCHOR_DIRECTION_FORWARD ? "forward" : "backward");
2253 + facets->anchor.start_ut && (
2254 + (facets->anchor.direction == FACETS_ANCHOR_DIRECTION_BACKWARD && row->usec >= facets->anchor.start_ut) ||
2255 + (facets->anchor.direction == FACETS_ANCHOR_DIRECTION_FORWARD && row->usec <= facets->anchor.start_ut)
2256 + ), "Wrong data returned related to %s start anchor!", facets->anchor.direction == FACETS_ANCHOR_DIRECTION_FORWARD ? "forward" : "backward");
2257
2258 internal_fatal(last_usec && row->usec > last_usec, "Wrong order of data returned!");
2259
@@ -1780,6 +2263,9 @@ void facets_report(FACETS *facets, BUFFER *wb) {
2263 buffer_json_add_array_item_uint64(wb, row->usec);
2264 buffer_json_add_array_item_object(wb);
2265 {
2266 + if(facets->severity.cb)
2267 + row->severity = facets->severity.cb(facets, row, facets->severity.data);
2268 +
2269 buffer_json_member_add_string(wb, "severity", facets_severity_to_string(row->severity));
2270 }
2271 buffer_json_object_close(wb);
@@ -1796,8 +2282,13 @@ void facets_report(FACETS *facets, BUFFER *wb) {
2282 facets->operations.values.dynamic++;
2283 }
2284 else {
1799 - if(!rkv || rkv->empty)
1800 - buffer_json_add_array_item_string(wb, FACET_VALUE_UNSET);
2285 + if(!rkv || rkv->empty) {
2286 + buffer_json_add_array_item_string(wb, NULL);
2287 + }
2288 + else if(unlikely(k->transform.cb && k->transform.view_only)) {
2289 + k->transform.cb(facets, rkv->wb, FACETS_TRANSFORM_DATA, k->transform.data);
2290 + buffer_json_add_array_item_string(wb, buffer_tostring(rkv->wb));
2291 + }
2292 else
2293 buffer_json_add_array_item_string(wb, buffer_tostring(rkv->wb));
2294 }
@@ -1814,7 +2305,10 @@ void facets_report(FACETS *facets, BUFFER *wb) {
2305 buffer_json_array_close(wb);
2306 }
2307
1817 - if(facets->histogram.enabled && !(facets->options & FACETS_OPTION_DISABLE_HISTOGRAM)) {
2308 + // ------------------------------------------------------------------------
2309 + // histogram
2310 +
2311 + if(facets->histogram.enabled && !(facets->options & FACETS_OPTION_DONT_SEND_HISTOGRAM)) {
2312 FACETS_HASH first_histogram_hash = 0;
2313 buffer_json_member_add_array(wb, "available_histograms");
2314 {
@@ -1840,31 +2334,60 @@ void facets_report(FACETS *facets, BUFFER *wb) {
2334 if(!k || !k->values.enabled)
2335 k = FACETS_KEY_GET_FROM_INDEX(facets, first_histogram_hash);
2336
1843 - buffer_json_member_add_object(wb, "histogram");
1844 - {
2337 + bool show_histogram = false;
2338 +
2339 + if(facets->options & FACETS_OPTION_DATA_ONLY) {
2340 + if(facets->options & FACETS_OPTION_SHOW_DELTAS) {
2341 + buffer_json_member_add_object(wb, "histogram_delta");
2342 + show_histogram = true;
2343 + }
2344 + }
2345 + else {
2346 + buffer_json_member_add_object(wb, "histogram");
2347 + show_histogram = true;
2348 + }
2349 +
2350 + if(show_histogram) {
2351 buffer_json_member_add_string(wb, "id", k ? hash_to_static_string(k->hash) : "");
2352 buffer_json_member_add_string(wb, "name", k ? k->name : "");
2353 buffer_json_member_add_object(wb, "chart");
1848 - facets_histogram_generate(facets, k, wb);
1849 - buffer_json_object_close(wb);
2354 + {
2355 + facets_histogram_generate(facets, k, wb);
2356 + }
2357 + buffer_json_object_close(wb); // chart
2358 + buffer_json_object_close(wb); // histogram
2359 }
1851 - buffer_json_object_close(wb); // histogram
2360 }
2361 }
2362
1855 - if(!(facets->options & FACETS_OPTION_DATA_ONLY)) {
1856 - buffer_json_member_add_object(wb, "items");
1857 - {
1858 - buffer_json_member_add_uint64(wb, "evaluated", facets->operations.rows.evaluated);
1859 - buffer_json_member_add_uint64(wb, "matched", facets->operations.rows.matched);
1860 - buffer_json_member_add_uint64(wb, "returned", facets->items_to_return);
1861 - buffer_json_member_add_uint64(wb, "max_to_return", facets->max_items_to_return);
1862 - buffer_json_member_add_uint64(wb, "before", facets->operations.skips_before);
1863 - buffer_json_member_add_uint64(wb, "after", facets->operations.skips_after + facets->operations.shifts);
2363 + // ------------------------------------------------------------------------
2364 + // items
2365 +
2366 + bool show_items = false;
2367 + if(facets->options & FACETS_OPTION_DATA_ONLY) {
2368 + if(facets->options & FACETS_OPTION_SHOW_DELTAS) {
2369 + buffer_json_member_add_object(wb, "items_delta");
2370 + show_items = true;
2371 }
2372 + }
2373 + else {
2374 + buffer_json_member_add_object(wb, "items");
2375 + show_items = true;
2376 + }
2377 +
2378 + if(show_items) {
2379 + buffer_json_member_add_uint64(wb, "evaluated", facets->operations.rows.evaluated);
2380 + buffer_json_member_add_uint64(wb, "matched", facets->operations.rows.matched);
2381 + buffer_json_member_add_uint64(wb, "returned", facets->items_to_return);
2382 + buffer_json_member_add_uint64(wb, "max_to_return", facets->max_items_to_return);
2383 + buffer_json_member_add_uint64(wb, "before", facets->operations.skips_before);
2384 + buffer_json_member_add_uint64(wb, "after", facets->operations.skips_after + facets->operations.shifts);
2385 buffer_json_object_close(wb); // items
2386 }
2387
2388 + // ------------------------------------------------------------------------
2389 + // stats
2390 +
2391 buffer_json_member_add_object(wb, "stats");
2392 {
2393 buffer_json_member_add_uint64(wb, "first", facets->operations.first);
libnetdata/facets/facets.h
+40 -12
@@ -12,6 +12,14 @@ typedef enum __attribute__((packed)) {
12 FACETS_ANCHOR_DIRECTION_BACKWARD,
13 } FACETS_ANCHOR_DIRECTION;
14
15 +typedef enum __attribute__((packed)) {
16 + FACETS_TRANSFORM_VALUE,
17 + FACETS_TRANSFORM_HISTOGRAM,
18 + FACETS_TRANSFORM_FACET,
19 + FACETS_TRANSFORM_DATA,
20 + FACETS_TRANSFORM_FACET_SORT,
21 +} FACETS_TRANSFORMATION_SCOPE;
22 +
23 typedef enum __attribute__((packed)) {
24 FACET_KEY_OPTION_FACET = (1 << 0), // filterable values
25 FACET_KEY_OPTION_NO_FACET = (1 << 1), // non-filterable value
@@ -22,6 +30,7 @@ typedef enum __attribute__((packed)) {
30 FACET_KEY_OPTION_MAIN_TEXT = (1 << 6), // full width and wrap
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_OPTIONS;
35
36 typedef enum __attribute__((packed)) {
@@ -48,17 +57,22 @@ typedef struct facet_row {
57 typedef struct facets FACETS;
58 typedef struct facet_key FACET_KEY;
59
51 -typedef void (*facets_key_transformer_t)(FACETS *facets __maybe_unused, BUFFER *wb, void *data);
60 +typedef void (*facets_key_transformer_t)(FACETS *facets __maybe_unused, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope, void *data);
61 typedef void (*facet_dynamic_row_t)(FACETS *facets, BUFFER *json_array, FACET_ROW_KEY_VALUE *rkv, FACET_ROW *row, void *data);
62 +typedef FACET_ROW_SEVERITY (*facet_row_severity_t)(FACETS *facets, FACET_ROW *row, void *data);
63 FACET_KEY *facets_register_dynamic_key_name(FACETS *facets, const char *key, FACET_KEY_OPTIONS options, facet_dynamic_row_t cb, void *data);
64 FACET_KEY *facets_register_key_name_transformation(FACETS *facets, const char *key, FACET_KEY_OPTIONS options, facets_key_transformer_t cb, void *data);
65 +void facets_register_row_severity(FACETS *facets, facet_row_severity_t cb, void *data);
66
67 typedef enum __attribute__((packed)) {
57 - FACETS_OPTION_ALL_FACETS_VISIBLE = (1 << 0), // all facets, should be visible by default in the table
58 - FACETS_OPTION_ALL_KEYS_FTS = (1 << 1), // all keys are searchable by full text search
59 - FACETS_OPTION_DISABLE_ALL_FACETS = (1 << 2),
60 - FACETS_OPTION_DISABLE_HISTOGRAM = (1 << 3),
61 - FACETS_OPTION_DATA_ONLY = (1 << 4),
68 + FACETS_OPTION_ALL_FACETS_VISIBLE = (1 << 0), // all facets should be visible by default in the table
69 + FACETS_OPTION_ALL_KEYS_FTS = (1 << 1), // all keys are searchable by full text search
70 + FACETS_OPTION_DONT_SEND_FACETS = (1 << 2), // "facets" object will not be included in the report
71 + FACETS_OPTION_DONT_SEND_HISTOGRAM = (1 << 3), // "histogram" object will not be included in the report
72 + FACETS_OPTION_DATA_ONLY = (1 << 4),
73 + FACETS_OPTION_DONT_SEND_EMPTY_VALUE_FACETS = (1 << 5), // empty facet values will not be included in the report
74 + FACETS_OPTION_SORT_FACETS_ALPHABETICALLY = (1 << 6),
75 + FACETS_OPTION_SHOW_DELTAS = (1 << 7),
76 } FACETS_OPTIONS;
77
78 FACETS *facets_create(uint32_t items_to_return, FACETS_OPTIONS options, const char *visible_keys, const char *facet_keys, const char *non_facet_keys);
@@ -67,23 +81,37 @@ void facets_destroy(FACETS *facets);
81 void facets_accepted_param(FACETS *facets, const char *param);
82
83 void facets_rows_begin(FACETS *facets);
70 -void facets_row_finished(FACETS *facets, usec_t usec);
84 +bool facets_row_finished(FACETS *facets, usec_t usec);
85
86 FACET_KEY *facets_register_key_name(FACETS *facets, const char *key, FACET_KEY_OPTIONS options);
87 void facets_set_query(FACETS *facets, const char *query);
88 void facets_set_items(FACETS *facets, uint32_t items);
75 -void facets_set_anchor(FACETS *facets, usec_t anchor, FACETS_ANCHOR_DIRECTION direction);
89 +void facets_set_anchor(FACETS *facets, usec_t start_ut, usec_t stop_ut, FACETS_ANCHOR_DIRECTION direction);
90 +void facets_enable_slice_mode(FACETS *facets);
91 +
92 FACET_KEY *facets_register_facet_id(FACETS *facets, const char *key_id, FACET_KEY_OPTIONS options);
93 void facets_register_facet_id_filter(FACETS *facets, const char *key_id, char *value_id, FACET_KEY_OPTIONS options);
78 -void facets_set_histogram_by_id(FACETS *facets, const char *key_id, usec_t after_ut, usec_t before_ut);
79 -void facets_set_histogram_by_name(FACETS *facets, const char *key_name, usec_t after_ut, usec_t before_ut);
94 +void facets_set_timeframe_and_histogram_by_id(FACETS *facets, const char *key_id, usec_t after_ut, usec_t before_ut);
95 +void facets_set_timeframe_and_histogram_by_name(FACETS *facets, const char *key_name, usec_t after_ut, usec_t before_ut);
96
97 void facets_add_key_value(FACETS *facets, const char *key, const char *value);
98 void facets_add_key_value_length(FACETS *facets, const char *key, size_t key_len, const char *value, size_t value_len);
99
84 -void facets_report(FACETS *facets, BUFFER *wb);
100 +void facets_report(FACETS *facets, BUFFER *wb, DICTIONARY *used_hashes_registry);
101 void facets_accepted_parameters_to_json_array(FACETS *facets, BUFFER *wb, bool with_keys);
102 void facets_set_current_row_severity(FACETS *facets, FACET_ROW_SEVERITY severity);
87 -void facets_data_only_mode(FACETS *facets);
103 +void facets_set_additional_options(FACETS *facets, FACETS_OPTIONS options);
104 +
105 +bool facets_key_name_is_filter(FACETS *facets, const char *key);
106 +bool facets_key_name_is_facet(FACETS *facets, const char *key);
107 +bool facets_key_name_value_length_is_selected(FACETS *facets, const char *key, size_t key_length, const char *value, size_t value_length);
108 +void facets_add_possible_value_name_to_key(FACETS *facets, const char *key, size_t key_length, const char *value, size_t value_length);
109 +
110 +void facets_sort_and_reorder_keys(FACETS *facets);
111 +usec_t facets_row_oldest_ut(FACETS *facets);
112 +usec_t facets_row_newest_ut(FACETS *facets);
113 +uint32_t facets_rows(FACETS *facets);
114 +
115 +void facets_table_config(BUFFER *wb);
116
117 #endif
libnetdata/socket/socket.c
+34
@@ -10,6 +10,40 @@
10
11 #include "../libnetdata.h"
12
13 +bool ip_to_hostname(const char *ip, char *dst, size_t dst_len) {
14 + if(!dst || !dst_len)
15 + return false;
16 +
17 + struct sockaddr_in sa;
18 + struct sockaddr_in6 sa6;
19 + struct sockaddr *sa_ptr;
20 + int sa_len;
21 +
22 + // Try to convert the IP address to sockaddr_in (IPv4)
23 + if (inet_pton(AF_INET, ip, &(sa.sin_addr)) == 1) {
24 + sa.sin_family = AF_INET;
25 + sa_ptr = (struct sockaddr *)&sa;
26 + sa_len = sizeof(sa);
27 + }
28 + // Try to convert the IP address to sockaddr_in6 (IPv6)
29 + else if (inet_pton(AF_INET6, ip, &(sa6.sin6_addr)) == 1) {
30 + sa6.sin6_family = AF_INET6;
31 + sa_ptr = (struct sockaddr *)&sa6;
32 + sa_len = sizeof(sa6);
33 + }
34 +
35 + else {
36 + dst[0] = '\0';
37 + return false;
38 + }
39 +
40 + // Perform the reverse lookup
41 + int res = getnameinfo(sa_ptr, sa_len, dst, dst_len, NULL, 0, NI_NAMEREQD);
42 + if(res != 0)
43 + return false;
44 +
45 + return true;
46 +}
47
48 SOCKET_PEERS socket_peers(int sock_fd) {
49 SOCKET_PEERS peers;
libnetdata/socket/socket.h
+1
@@ -243,5 +243,6 @@ typedef struct socket_peers {
243 } SOCKET_PEERS;
244
245 SOCKET_PEERS socket_peers(int sock_fd);
246 +bool ip_to_hostname(const char *ip, char *dst, size_t dst_len);
247
248 #endif //NETDATA_SOCKET_H