@cryptotaxi247 / netdata-1 / commits / 934f62026

facets: data-only queries (#15961)

Costa Tsaousis committed Sep 14, 2023 at 15:59 UTC 934f620265d2e7a2be160cb084ec01875ed3491e
7 files changed +571 -212
collectors/apps.plugin/apps_plugin.c
+33 -21
@@ -4575,7 +4575,7 @@ static int check_capabilities() {
4575 }
4576 #endif
4577
4578 -static netdata_mutex_t mutex = NETDATA_MUTEX_INITIALIZER;
4578 +static netdata_mutex_t stdout_mutex = NETDATA_MUTEX_INITIALIZER;
4579
4580 #define PROCESS_FILTER_CATEGORY "category:"
4581 #define PROCESS_FILTER_USER "user:"
@@ -4629,8 +4629,8 @@ static void get_MemTotal(void) {
4629 }
4630
4631 static void apps_plugin_function_processes_help(const char *transaction) {
4632 - pluginsd_function_result_begin_to_stdout(transaction, HTTP_RESP_OK, "text/plain", now_realtime_sec() + 3600);
4633 - fprintf(stdout, "%s",
4632 + BUFFER *wb = buffer_create(0, NULL);
4633 + buffer_sprintf(wb, "%s",
4634 "apps.plugin / processes\n"
4635 "\n"
4636 "Function `processes` presents all the currently running processes of the system.\n"
@@ -4660,7 +4660,12 @@ static void apps_plugin_function_processes_help(const char *transaction) {
4660 "\n"
4661 "Filters can be combined. Each filter can be given only one time.\n"
4662 );
4663 - pluginsd_function_result_end_to_stdout();
4663 +
4664 + netdata_mutex_lock(&stdout_mutex);
4665 + pluginsd_function_result_to_stdout(transaction, HTTP_RESP_OK, "text/plain", now_realtime_sec() + 3600, wb);
4666 + netdata_mutex_unlock(&stdout_mutex);
4667 +
4668 + buffer_free(wb);
4669 }
4670
4671 #define add_value_field_llu_with_max(wb, key, value) do { \
@@ -4696,24 +4701,31 @@ static void function_processes(const char *transaction, char *function __maybe_u
4701 if(!category && strncmp(keyword, PROCESS_FILTER_CATEGORY, strlen(PROCESS_FILTER_CATEGORY)) == 0) {
4702 category = find_target_by_name(apps_groups_root_target, &keyword[strlen(PROCESS_FILTER_CATEGORY)]);
4703 if(!category) {
4704 + netdata_mutex_lock(&stdout_mutex);
4705 pluginsd_function_json_error_to_stdout(transaction, HTTP_RESP_BAD_REQUEST,
4706 "No category with that name found.");
4707 + netdata_mutex_unlock(&stdout_mutex);
4708 +
4709 return;
4710 }
4711 }
4712 else if(!user && strncmp(keyword, PROCESS_FILTER_USER, strlen(PROCESS_FILTER_USER)) == 0) {
4713 user = find_target_by_name(users_root_target, &keyword[strlen(PROCESS_FILTER_USER)]);
4714 if(!user) {
4715 + netdata_mutex_lock(&stdout_mutex);
4716 pluginsd_function_json_error_to_stdout(transaction, HTTP_RESP_BAD_REQUEST,
4717 "No user with that name found.");
4718 + netdata_mutex_unlock(&stdout_mutex);
4719 return;
4720 }
4721 }
4722 else if(strncmp(keyword, PROCESS_FILTER_GROUP, strlen(PROCESS_FILTER_GROUP)) == 0) {
4723 group = find_target_by_name(groups_root_target, &keyword[strlen(PROCESS_FILTER_GROUP)]);
4724 if(!group) {
4725 + netdata_mutex_lock(&stdout_mutex);
4726 pluginsd_function_json_error_to_stdout(transaction, HTTP_RESP_BAD_REQUEST,
4727 "No group with that name found.");
4728 + netdata_mutex_unlock(&stdout_mutex);
4729 return;
4730 }
4731 }
@@ -4739,13 +4751,14 @@ static void function_processes(const char *transaction, char *function __maybe_u
4751 else {
4752 char msg[PLUGINSD_LINE_MAX];
4753 snprintfz(msg, PLUGINSD_LINE_MAX, "Invalid parameter '%s'", keyword);
4754 + netdata_mutex_lock(&stdout_mutex);
4755 pluginsd_function_json_error_to_stdout(transaction, HTTP_RESP_BAD_REQUEST, msg);
4756 + netdata_mutex_unlock(&stdout_mutex);
4757 return;
4758 }
4759 }
4760
4761 time_t expires = now_realtime_sec() + update_every;
4748 - pluginsd_function_result_begin_to_stdout(transaction, HTTP_RESP_OK, "application/json", expires);
4762
4763 unsigned int cpu_divisor = time_factor * RATES_DETAIL / 100;
4764 unsigned int memory_divisor = 1024;
@@ -5523,10 +5536,11 @@ static void function_processes(const char *transaction, char *function __maybe_u
5536 buffer_json_member_add_time_t(wb, "expires", expires);
5537 buffer_json_finalize(wb);
5538
5526 - fwrite(buffer_tostring(wb), buffer_strlen(wb), 1, stdout);
5527 - buffer_free(wb);
5539 + netdata_mutex_lock(&stdout_mutex);
5540 + pluginsd_function_result_to_stdout(transaction, HTTP_RESP_OK, "application/json", expires, wb);
5541 + netdata_mutex_unlock(&stdout_mutex);
5542
5529 - pluginsd_function_result_end_to_stdout();
5543 + buffer_free(wb);
5544 }
5545
5546 static bool apps_plugin_exit = false;
@@ -5560,16 +5574,14 @@ static void *reader_main(void *arg __maybe_unused) {
5574
5575 // internal_error(true, "Received function '%s', transaction '%s', timeout %d", function, transaction, timeout);
5576
5563 - netdata_mutex_lock(&mutex);
5564 -
5577 if(strncmp(function, "processes", strlen("processes")) == 0)
5578 function_processes(transaction, function, buffer, PLUGINSD_LINE_MAX + 1, timeout);
5567 - else
5579 + else {
5580 + netdata_mutex_lock(&stdout_mutex);
5581 pluginsd_function_json_error_to_stdout(transaction, HTTP_RESP_NOT_FOUND,
5582 "No function with this name found in apps.plugin.");
5570 -
5571 - fflush(stdout);
5572 - netdata_mutex_unlock(&mutex);
5583 + netdata_mutex_unlock(&stdout_mutex);
5584 + }
5585
5586 // internal_error(true, "Done with function '%s', transaction '%s', timeout %d", function, transaction, timeout);
5587 }
@@ -5692,7 +5704,7 @@ int main(int argc, char **argv) {
5704
5705 netdata_thread_t reader_thread;
5706 netdata_thread_create(&reader_thread, "APPS_READER", NETDATA_THREAD_OPTION_DONT_LOG, reader_main, NULL);
5695 - netdata_mutex_lock(&mutex);
5707 + netdata_mutex_lock(&stdout_mutex);
5708
5709 APPS_PLUGIN_GLOBAL_FUNCTIONS();
5710
@@ -5701,7 +5713,7 @@ int main(int argc, char **argv) {
5713 heartbeat_t hb;
5714 heartbeat_init(&hb);
5715 for(; !apps_plugin_exit ; global_iterations_counter++) {
5704 - netdata_mutex_unlock(&mutex);
5716 + netdata_mutex_unlock(&stdout_mutex);
5717
5718 #ifdef NETDATA_PROFILING
5719 #warning "compiling for profiling"
@@ -5712,16 +5724,16 @@ int main(int argc, char **argv) {
5724 #else
5725 usec_t dt = heartbeat_next(&hb, step);
5726 #endif
5715 - netdata_mutex_lock(&mutex);
5727 + netdata_mutex_lock(&stdout_mutex);
5728
5729 struct pollfd pollfd = { .fd = fileno(stdout), .events = POLLERR };
5730 if (unlikely(poll(&pollfd, 1, 0) < 0)) {
5719 - netdata_mutex_unlock(&mutex);
5731 + netdata_mutex_unlock(&stdout_mutex);
5732 netdata_thread_cancel(reader_thread);
5733 fatal("Cannot check if a pipe is available");
5734 }
5735 if (unlikely(pollfd.revents & POLLERR)) {
5724 - netdata_mutex_unlock(&mutex);
5736 + netdata_mutex_unlock(&stdout_mutex);
5737 netdata_thread_cancel(reader_thread);
5738 fatal("Received error on read pipe.");
5739 }
@@ -5732,7 +5744,7 @@ int main(int argc, char **argv) {
5744 if(!collect_data_for_all_processes()) {
5745 netdata_log_error("Cannot collect /proc data for running processes. Disabling apps.plugin...");
5746 printf("DISABLE\n");
5735 - netdata_mutex_unlock(&mutex);
5747 + netdata_mutex_unlock(&stdout_mutex);
5748 netdata_thread_cancel(reader_thread);
5749 exit(1);
5750 }
@@ -5769,5 +5781,5 @@ int main(int argc, char **argv) {
5781
5782 debug_log("done Loop No %zu", global_iterations_counter);
5783 }
5772 - netdata_mutex_unlock(&mutex);
5784 + netdata_mutex_unlock(&stdout_mutex);
5785 }
collectors/ebpf.plugin/ebpf_functions.c
+11 -17
@@ -37,9 +37,8 @@ ebpf_module_t *ebpf_functions_select_module(const char *thread_name) {
37 * @param transaction the transaction id that Netdata sent for this function execution
38 */
39 static void ebpf_function_thread_manipulation_help(const char *transaction) {
40 - pthread_mutex_lock(&lock);
41 - pluginsd_function_result_begin_to_stdout(transaction, HTTP_RESP_OK, "text/plain", now_realtime_sec() + 3600);
42 - fprintf(stdout, "%s",
40 + BUFFER *wb = buffer_create(0, NULL);
41 + buffer_sprintf(wb, "%s",
42 "ebpf.plugin / thread\n"
43 "\n"
44 "Function `thread` allows user to control eBPF threads.\n"
@@ -59,9 +58,12 @@ static void ebpf_function_thread_manipulation_help(const char *transaction) {
58 "Filters can be combined. Each filter can be given only one time.\n"
59 "Process thread is not controlled by functions until we finish the creation of functions per thread..\n"
60 );
62 - pluginsd_function_result_end_to_stdout();
63 - fflush(stdout);
61 +
62 + pthread_mutex_lock(&lock);
63 + pluginsd_function_result_to_stdout(transaction, HTTP_RESP_OK, "text/plain", now_realtime_sec() + 3600, wb);
64 pthread_mutex_unlock(&lock);
65 +
66 + buffer_free(wb);
67 }
68
69
@@ -79,12 +81,9 @@ static void ebpf_function_thread_manipulation_help(const char *transaction) {
81 * @param msg the error message
82 */
83 static void ebpf_function_error(const char *transaction, int code, const char *msg) {
82 - char buffer[PLUGINSD_LINE_MAX + 1];
83 - json_escape_string(buffer, msg, PLUGINSD_LINE_MAX);
84 -
85 - pluginsd_function_result_begin_to_stdout(transaction, code, "application/json", now_realtime_sec());
86 - fprintf(stdout, "{\"status\":%d,\"error_message\":\"%s\"}", code, buffer);
87 - pluginsd_function_result_end_to_stdout();
84 + pthread_mutex_lock(&lock);
85 + pluginsd_function_json_error_to_stdout(transaction, code, msg);
86 + pthread_mutex_unlock(&lock);
87 }
88
89 /*****************************************************************
@@ -350,12 +349,7 @@ static void ebpf_function_thread_manipulation(const char *transaction,
349
350 // Lock necessary to avoid race condition
351 pthread_mutex_lock(&lock);
353 - pluginsd_function_result_begin_to_stdout(transaction, HTTP_RESP_OK, "application/json", expires);
354 -
355 - fwrite(buffer_tostring(wb), buffer_strlen(wb), 1, stdout);
356 -
357 - pluginsd_function_result_end_to_stdout();
358 - fflush(stdout);
352 + pluginsd_function_result_to_stdout(transaction, HTTP_RESP_OK, "application/json", expires, wb);
353 pthread_mutex_unlock(&lock);
354
355 buffer_free(wb);
collectors/plugins.d/plugins_d.h
+8
@@ -130,6 +130,14 @@ static inline void pluginsd_function_json_error_to_stdout(const char *transactio
130 pluginsd_function_result_begin_to_stdout(transaction, code, "application/json", now_realtime_sec());
131 fprintf(stdout, "{\"status\":%d,\"error_message\":\"%s\"}", code, buffer);
132 pluginsd_function_result_end_to_stdout();
133 + fflush(stdout);
134 +}
135 +
136 +static inline void pluginsd_function_result_to_stdout(const char *transaction, int code, const char *content_type, time_t expires, BUFFER *result) {
137 + pluginsd_function_result_begin_to_stdout(transaction, code, content_type, expires);
138 + fwrite(buffer_tostring(result), buffer_strlen(result), 1, stdout);
139 + pluginsd_function_result_end_to_stdout();
140 + fflush(stdout);
141 }
142
143 #endif /* NETDATA_PLUGINS_D_H */
collectors/systemd-journal.plugin/systemd-journal.c
+318 -70
@@ -22,6 +22,7 @@
22 #define SYSTEMD_JOURNAL_MAX_PARAMS 100
23 #define SYSTEMD_JOURNAL_DEFAULT_QUERY_DURATION (3 * 3600)
24 #define SYSTEMD_JOURNAL_DEFAULT_ITEMS_PER_QUERY 200
25 +#define SYSTEMD_JOURNAL_EXCESS_ROWS_ALLOWED 50
26
27 #define JOURNAL_PARAMETER_HELP "help"
28 #define JOURNAL_PARAMETER_AFTER "after"
@@ -33,6 +34,7 @@
34 #define JOURNAL_PARAMETER_HISTOGRAM "histogram"
35 #define JOURNAL_PARAMETER_DIRECTION "direction"
36 #define JOURNAL_PARAMETER_IF_MODIFIED_SINCE "if_modified_since"
37 +#define JOURNAL_PARAMETER_DATA_ONLY "data_only"
38 #define JOURNAL_PARAMETER_SOURCE "source"
39 #define JOURNAL_PARAMETER_INFO "info"
40
@@ -53,7 +55,7 @@
55 "|IMAGE_NAME" \
56 ""
57
56 -static netdata_mutex_t mutex = NETDATA_MUTEX_INITIALIZER;
58 +static netdata_mutex_t stdout_mutex = NETDATA_MUTEX_INITIALIZER;
59 static bool plugin_should_exit = false;
60
61 DICTIONARY *uids = NULL;
@@ -61,7 +63,7 @@ DICTIONARY *gids = NULL;
63
64 // ----------------------------------------------------------------------------
65
64 -int systemd_journal_query(BUFFER *wb, FACETS *facets, usec_t after_ut, usec_t before_ut, usec_t if_modified_since, usec_t stop_monotonic_ut) {
66 +static inline sd_journal *netdata_open_systemd_journal(void) {
67 sd_journal *j = NULL;
68 int r;
69
@@ -82,36 +84,78 @@ int systemd_journal_query(BUFFER *wb, FACETS *facets, usec_t after_ut, usec_t be
84
85 if (r < 0) {
86 netdata_log_error("SYSTEMD-JOURNAL: Failed to open SystemD Journal, with error %d", r);
85 - return HTTP_RESP_INTERNAL_SERVER_ERROR;
87 + return NULL;
88 }
89
88 - facets_rows_begin(facets);
90 + return j;
91 +}
92 +
93 +typedef enum {
94 + ND_SD_JOURNAL_FAILED_TO_SEEK,
95 + ND_SD_JOURNAL_TIMED_OUT,
96 + ND_SD_JOURNAL_OK,
97 + ND_SD_JOURNAL_NOT_MODIFIED,
98 +} ND_SD_JOURNAL_STATUS;
99 +
100 +static inline bool netdata_systemd_journal_seek_to(sd_journal *j, usec_t timestamp) {
101 + if(sd_journal_seek_realtime_usec(j, timestamp) < 0) {
102 + netdata_log_error("SYSTEMD-JOURNAL: Failed to seek to %" PRIu64, timestamp);
103 + if(sd_journal_seek_tail(j) < 0) {
104 + netdata_log_error("SYSTEMD-JOURNAL: Failed to seek to journal's tail");
105 + return false;
106 + }
107 + }
108 +
109 + return true;
110 +}
111 +
112 +static inline void netdata_systemd_journal_process_row(sd_journal *j, FACETS *facets) {
113 + const void *data;
114 + size_t length;
115 + SD_JOURNAL_FOREACH_DATA(j, data, length) {
116 + const char *key = data;
117 + const char *equal = strchr(key, '=');
118 + if(unlikely(!equal))
119 + continue;
120 +
121 + const char *value = ++equal;
122 + size_t key_length = value - key; // including '\0'
123 +
124 + char key_copy[key_length];
125 + memcpy(key_copy, key, key_length - 1);
126 + key_copy[key_length - 1] = '\0';
127 +
128 + size_t value_length = length - key_length; // without '\0'
129 + facets_add_key_value_length(facets, key_copy, key_length - 1, value, value_length <= FACET_MAX_VALUE_LENGTH ? value_length : FACET_MAX_VALUE_LENGTH);
130 + }
131 +}
132 +
133 +ND_SD_JOURNAL_STATUS netdata_systemd_journal_query_full(sd_journal *j, BUFFER *wb, FACETS *facets, usec_t after_ut, usec_t before_ut, usec_t if_modified_since, usec_t stop_monotonic_ut, usec_t *last_modified) {
134 + if(!netdata_systemd_journal_seek_to(j, before_ut))
135 + return ND_SD_JOURNAL_FAILED_TO_SEEK;
136
90 - uint64_t first_msg_ut = 0;
137 + size_t errors_no_timestamp = 0;
138 + usec_t first_msg_ut = 0;
139 bool timed_out = false;
140 size_t row_counter = 0;
141
142 // the entries are not guaranteed to be sorted, so we process up to 100 entries beyond
143 // the end of the query to find possibly useful logs for our time-frame
96 - size_t excess_rows_allowed = 100;
144 + size_t excess_rows_allowed = SYSTEMD_JOURNAL_EXCESS_ROWS_ALLOWED;
145
98 - if(sd_journal_seek_realtime_usec(j, before_ut) < 0) {
99 - netdata_log_error("SYSTEMD-JOURNAL: Failed to seek to %" PRIu64, before_ut);
100 - if(sd_journal_seek_tail(j) < 0) {
101 - netdata_log_error("SYSTEMD-JOURNAL: Failed to seek to journal's tail");
102 - goto finalize;
103 - }
104 - }
146 + facets_rows_begin(facets);
147 while (sd_journal_previous(j) > 0) {
148 row_counter++;
149
108 - uint64_t msg_ut;
109 - sd_journal_get_realtime_usec(j, &msg_ut);
150 + usec_t msg_ut;
151 + if(sd_journal_get_realtime_usec(j, &msg_ut) < 0) {
152 + errors_no_timestamp++;
153 + continue;
154 + }
155
156 if(unlikely(!first_msg_ut)) {
157 if(msg_ut == if_modified_since) {
113 - sd_journal_close(j);
114 - return HTTP_RESP_NOT_MODIFIED;
158 + return ND_SD_JOURNAL_NOT_MODIFIED;
159 }
160
161 first_msg_ut = msg_ut;
@@ -127,26 +171,65 @@ int systemd_journal_query(BUFFER *wb, FACETS *facets, usec_t after_ut, usec_t be
171 continue;
172 }
173
130 - const void *data;
131 - size_t length;
132 - SD_JOURNAL_FOREACH_DATA(j, data, length) {
133 - const char *key = data;
134 - const char *equal = strchr(key, '=');
135 - if(unlikely(!equal))
136 - continue;
174 + netdata_systemd_journal_process_row(j, facets);
175 + facets_row_finished(facets, msg_ut);
176 +
177 + if((row_counter % 100) == 0 && now_monotonic_usec() > stop_monotonic_ut) {
178 + timed_out = true;
179 + break;
180 + }
181 + }
182 +
183 + if(errors_no_timestamp)
184 + netdata_log_error("SYSTEMD-JOURNAL: %zu lines did not have timestamps", errors_no_timestamp);
185 +
186 + *last_modified = first_msg_ut;
187
138 - const char *value = ++equal;
139 - size_t key_length = value - key; // including '\0'
188 + if(timed_out)
189 + return ND_SD_JOURNAL_TIMED_OUT;
190
141 - char key_copy[key_length];
142 - memcpy(key_copy, key, key_length - 1);
143 - key_copy[key_length - 1] = '\0';
191 + return ND_SD_JOURNAL_OK;
192 +}
193 +
194 +ND_SD_JOURNAL_STATUS netdata_systemd_journal_query_data_forward(sd_journal *j, BUFFER *wb, FACETS *facets, usec_t after_ut, usec_t before_ut, usec_t anchor, size_t entries, usec_t stop_monotonic_ut) {
195 + if(!netdata_systemd_journal_seek_to(j, anchor))
196 + return ND_SD_JOURNAL_FAILED_TO_SEEK;
197 +
198 + size_t errors_no_timestamp = 0;
199 + bool timed_out = false;
200 + size_t row_counter = 0;
201 + size_t rows_added = 0;
202 +
203 + // the entries are not guaranteed to be sorted, so we process up to 100 entries beyond
204 + // the end of the query to find possibly useful logs for our time-frame
205 + size_t excess_rows_allowed = SYSTEMD_JOURNAL_EXCESS_ROWS_ALLOWED;
206 +
207 + facets_rows_begin(facets);
208 + while (sd_journal_next(j) > 0) {
209 + row_counter++;
210 +
211 + usec_t msg_ut;
212 + if(sd_journal_get_realtime_usec(j, &msg_ut) < 0) {
213 + errors_no_timestamp++;
214 + continue;
215 + }
216 +
217 + if (msg_ut > before_ut || msg_ut <= anchor)
218 + continue;
219 +
220 + if (msg_ut < after_ut) {
221 + if(--excess_rows_allowed == 0)
222 + break;
223
145 - size_t value_length = length - key_length; // without '\0'
146 - facets_add_key_value_length(facets, key_copy, key_length - 1, value, value_length <= FACET_MAX_VALUE_LENGTH ? value_length : FACET_MAX_VALUE_LENGTH);
224 + continue;
225 }
226
227 + if(rows_added > entries && --excess_rows_allowed == 0)
228 + break;
229 +
230 + netdata_systemd_journal_process_row(j, facets);
231 facets_row_finished(facets, msg_ut);
232 + rows_added++;
233
234 if((row_counter % 100) == 0 && now_monotonic_usec() > stop_monotonic_ut) {
235 timed_out = true;
@@ -154,27 +237,147 @@ int systemd_journal_query(BUFFER *wb, FACETS *facets, usec_t after_ut, usec_t be
237 }
238 }
239
157 -finalize:
240 + if(errors_no_timestamp)
241 + netdata_log_error("SYSTEMD-JOURNAL: %zu lines did not have timestamps", errors_no_timestamp);
242 +
243 + if(timed_out)
244 + return ND_SD_JOURNAL_TIMED_OUT;
245 +
246 + return ND_SD_JOURNAL_OK;
247 +}
248 +
249 +ND_SD_JOURNAL_STATUS netdata_systemd_journal_query_data_backward(sd_journal *j, BUFFER *wb, FACETS *facets, usec_t after_ut, usec_t before_ut, usec_t anchor, size_t entries, usec_t stop_monotonic_ut) {
250 + if(!netdata_systemd_journal_seek_to(j, anchor))
251 + return ND_SD_JOURNAL_FAILED_TO_SEEK;
252 +
253 + size_t errors_no_timestamp = 0;
254 + bool timed_out = false;
255 + size_t row_counter = 0;
256 + size_t rows_added = 0;
257 +
258 + // the entries are not guaranteed to be sorted, so we process up to 100 entries beyond
259 + // the end of the query to find possibly useful logs for our time-frame
260 + size_t excess_rows_allowed = SYSTEMD_JOURNAL_EXCESS_ROWS_ALLOWED;
261 +
262 + facets_rows_begin(facets);
263 + while (sd_journal_previous(j) > 0) {
264 + row_counter++;
265 +
266 + usec_t msg_ut;
267 + if(sd_journal_get_realtime_usec(j, &msg_ut) < 0) {
268 + errors_no_timestamp++;
269 + continue;
270 + }
271 +
272 + if (msg_ut > before_ut || msg_ut >= anchor)
273 + continue;
274 +
275 + if (msg_ut < after_ut) {
276 + if(--excess_rows_allowed == 0)
277 + break;
278 +
279 + continue;
280 + }
281 +
282 + if(rows_added > entries && --excess_rows_allowed == 0)
283 + break;
284 +
285 + netdata_systemd_journal_process_row(j, facets);
286 + facets_row_finished(facets, msg_ut);
287 + rows_added++;
288 +
289 + if((row_counter % 100) == 0 && now_monotonic_usec() > stop_monotonic_ut) {
290 + timed_out = true;
291 + break;
292 + }
293 + }
294 +
295 + if(errors_no_timestamp)
296 + netdata_log_error("SYSTEMD-JOURNAL: %zu lines did not have timestamps", errors_no_timestamp);
297 +
298 + if(timed_out)
299 + return ND_SD_JOURNAL_TIMED_OUT;
300 +
301 + return ND_SD_JOURNAL_OK;
302 +}
303 +
304 +bool netdata_systemd_journal_check_if_modified_since(sd_journal *j, usec_t seek_to, usec_t last_modified) {
305 + // return true, if data have been modified since the timestamp
306 +
307 + if(!last_modified || !seek_to)
308 + return false;
309 +
310 + if(!netdata_systemd_journal_seek_to(j, seek_to))
311 + return false;
312 +
313 + usec_t first_msg_ut = 0;
314 + while (sd_journal_previous(j) > 0) {
315 + usec_t msg_ut;
316 + if(sd_journal_get_realtime_usec(j, &msg_ut) < 0)
317 + continue;
318 +
319 + first_msg_ut = msg_ut;
320 + break;
321 + }
322 +
323 + return first_msg_ut != last_modified;
324 +}
325 +
326 +static int netdata_systemd_journal_query(BUFFER *wb, FACETS *facets,
327 + usec_t after_ut, usec_t before_ut,
328 + usec_t anchor, FACETS_ANCHOR_DIRECTION direction, size_t entries,
329 + usec_t if_modified_since, bool data_only,
330 + usec_t stop_monotonic_ut) {
331 + sd_journal *j = netdata_open_systemd_journal();
332 + if(!j)
333 + return HTTP_RESP_INTERNAL_SERVER_ERROR;
334 +
335 + usec_t last_modified = 0;
336 +
337 + ND_SD_JOURNAL_STATUS status;
338 +
339 + if(data_only && anchor /* && !netdata_systemd_journal_check_if_modified_since(j, before_ut, if_modified_since) */) {
340 + facets_data_only_mode(facets);
341 +
342 + // we can do a data-only query
343 + if(direction == FACETS_ANCHOR_DIRECTION_FORWARD)
344 + status = netdata_systemd_journal_query_data_forward(j, wb, facets, after_ut, before_ut, anchor, entries, stop_monotonic_ut);
345 + else
346 + status = netdata_systemd_journal_query_data_backward(j, wb, facets, after_ut, before_ut, anchor, entries, stop_monotonic_ut);
347 + }
348 + else {
349 + // we have to do a full query
350 + status = netdata_systemd_journal_query_full(j, wb, facets,
351 + after_ut, before_ut, if_modified_since,
352 + stop_monotonic_ut, &last_modified);
353 + }
354 +
355 sd_journal_close(j);
356
160 - buffer_json_member_add_uint64(wb, "status", HTTP_RESP_OK);
161 - buffer_json_member_add_boolean(wb, "partial", timed_out);
357 + if(status == ND_SD_JOURNAL_NOT_MODIFIED)
358 + return HTTP_RESP_NOT_MODIFIED;
359 +
360 + buffer_json_member_add_uint64(wb, "status", status == ND_SD_JOURNAL_FAILED_TO_SEEK ? HTTP_RESP_INTERNAL_SERVER_ERROR : HTTP_RESP_OK);
361 + buffer_json_member_add_boolean(wb, "partial", status != ND_SD_JOURNAL_OK);
362 buffer_json_member_add_string(wb, "type", "table");
163 - buffer_json_member_add_time_t(wb, "update_every", 1);
164 - buffer_json_member_add_string(wb, "help", SYSTEMD_JOURNAL_FUNCTION_DESCRIPTION);
165 - buffer_json_member_add_uint64(wb, "last_modified", first_msg_ut);
363 +
364 + if(!data_only) {
365 + buffer_json_member_add_time_t(wb, "update_every", 1);
366 + buffer_json_member_add_string(wb, "help", SYSTEMD_JOURNAL_FUNCTION_DESCRIPTION);
367 + buffer_json_member_add_uint64(wb, "last_modified", last_modified);
368 + }
369
370 facets_report(facets, wb);
371
169 - buffer_json_member_add_time_t(wb, "expires", now_realtime_sec());
372 + buffer_json_member_add_time_t(wb, "expires", now_realtime_sec() + data_only ? 3600 : 0);
373 buffer_json_finalize(wb);
374
172 - return HTTP_RESP_OK;
375 + return status == ND_SD_JOURNAL_FAILED_TO_SEEK ? HTTP_RESP_INTERNAL_SERVER_ERROR : HTTP_RESP_OK;
376 }
377
175 -static void systemd_journal_function_help(const char *transaction) {
176 - pluginsd_function_result_begin_to_stdout(transaction, HTTP_RESP_OK, "text/plain", now_realtime_sec() + 3600);
177 - fprintf(stdout,
378 +static void netdata_systemd_journal_function_help(const char *transaction) {
379 + BUFFER *wb = buffer_create(0, NULL);
380 + buffer_sprintf(wb,
381 "%s / %s\n"
382 "\n"
383 "%s\n"
@@ -212,7 +415,12 @@ static void systemd_journal_function_help(const char *transaction) {
415 , -SYSTEMD_JOURNAL_DEFAULT_QUERY_DURATION
416 , SYSTEMD_JOURNAL_DEFAULT_ITEMS_PER_QUERY
417 );
215 - pluginsd_function_result_end_to_stdout();
418 +
419 + netdata_mutex_lock(&stdout_mutex);
420 + pluginsd_function_result_to_stdout(transaction, HTTP_RESP_OK, "text/plain", now_realtime_sec() + 3600, wb);
421 + netdata_mutex_unlock(&stdout_mutex);
422 +
423 + buffer_free(wb);
424 }
425
426 static const char *syslog_facility_to_name(int facility) {
@@ -255,6 +463,26 @@ static const char *syslog_priority_to_name(int priority) {
463 }
464 }
465
466 +static FACET_ROW_SEVERITY syslog_priority_to_facet_severity(int priority) {
467 + // same to
468 + // https://github.com/systemd/systemd/blob/aab9e4b2b86905a15944a1ac81e471b5b7075932/src/basic/terminal-util.c#L1501
469 + // function get_log_colors()
470 +
471 + if(priority <= LOG_ERR)
472 + return FACET_ROW_SEVERITY_CRITICAL;
473 +
474 + else if (priority <= LOG_WARNING)
475 + return FACET_ROW_SEVERITY_WARNING;
476 +
477 + else if(priority <= LOG_NOTICE)
478 + return FACET_ROW_SEVERITY_NOTICE;
479 +
480 + else if(priority >= LOG_DEBUG)
481 + return FACET_ROW_SEVERITY_DEBUG;
482 +
483 + return FACET_ROW_SEVERITY_NORMAL;
484 +}
485 +
486 static char *uid_to_username(uid_t uid, char *buffer, size_t buffer_size) {
487 struct passwd pw, *result;
488 char tmp[1024 + 1];
@@ -277,7 +505,7 @@ static char *gid_to_groupname(gid_t gid, char* buffer, size_t buffer_size) {
505 return buffer;
506 }
507
280 -static void systemd_journal_transform_syslog_facility(FACETS *facets __maybe_unused, BUFFER *wb, void *data __maybe_unused) {
508 +static void netdata_systemd_journal_transform_syslog_facility(FACETS *facets __maybe_unused, BUFFER *wb, void *data __maybe_unused) {
509 const char *v = buffer_tostring(wb);
510 if(*v && isdigit(*v)) {
511 int facility = str2i(buffer_tostring(wb));
@@ -289,7 +517,7 @@ static void systemd_journal_transform_syslog_facility(FACETS *facets __maybe_unu
517 }
518 }
519
292 -static void systemd_journal_transform_priority(FACETS *facets __maybe_unused, BUFFER *wb, void *data __maybe_unused) {
520 +static void netdata_systemd_journal_transform_priority(FACETS *facets __maybe_unused, BUFFER *wb, void *data __maybe_unused) {
521 const char *v = buffer_tostring(wb);
522 if(*v && isdigit(*v)) {
523 int priority = str2i(buffer_tostring(wb));
@@ -298,10 +526,12 @@ static void systemd_journal_transform_priority(FACETS *facets __maybe_unused, BU
526 buffer_flush(wb);
527 buffer_strcat(wb, name);
528 }
529 +
530 + facets_set_current_row_severity(facets, syslog_priority_to_facet_severity(priority));
531 }
532 }
533
304 -static void systemd_journal_transform_uid(FACETS *facets __maybe_unused, BUFFER *wb, void *data) {
534 +static void netdata_systemd_journal_transform_uid(FACETS *facets __maybe_unused, BUFFER *wb, void *data) {
535 DICTIONARY *cache = data;
536 const char *v = buffer_tostring(wb);
537 if(*v && isdigit(*v)) {
@@ -321,7 +551,7 @@ static void systemd_journal_transform_uid(FACETS *facets __maybe_unused, BUFFER
551 }
552 }
553
324 -static void systemd_journal_transform_gid(FACETS *facets __maybe_unused, BUFFER *wb, void *data) {
554 +static void netdata_systemd_journal_transform_gid(FACETS *facets __maybe_unused, BUFFER *wb, void *data) {
555 DICTIONARY *cache = data;
556 const char *v = buffer_tostring(wb);
557 if(*v && isdigit(*v)) {
@@ -341,7 +571,7 @@ static void systemd_journal_transform_gid(FACETS *facets __maybe_unused, BUFFER
571 }
572 }
573
344 -static void systemd_journal_dynamic_row_id(FACETS *facets __maybe_unused, BUFFER *json_array, FACET_ROW_KEY_VALUE *rkv, FACET_ROW *row, void *data __maybe_unused) {
574 +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) {
575 FACET_ROW_KEY_VALUE *pid_rkv = dictionary_get(row->dict, "_PID");
576 const char *pid = pid_rkv ? buffer_tostring(pid_rkv->wb) : FACET_VALUE_UNSET;
577
@@ -363,6 +593,12 @@ static void systemd_journal_dynamic_row_id(FACETS *facets __maybe_unused, BUFFER
593 buffer_json_add_array_item_string(json_array, buffer_tostring(rkv->wb));
594 }
595
596 +static void netdata_systemd_journal_rich_message(FACETS *facets __maybe_unused, BUFFER *json_array, FACET_ROW_KEY_VALUE *rkv, FACET_ROW *row, void *data __maybe_unused) {
597 + buffer_json_add_array_item_object(json_array);
598 + buffer_json_member_add_string(json_array, "value", buffer_tostring(rkv->wb));
599 + buffer_json_object_close(json_array);
600 +}
601 +
602 static void function_systemd_journal(const char *transaction, char *function, char *line_buffer __maybe_unused, int line_max __maybe_unused, int timeout __maybe_unused) {
603 BUFFER *wb = buffer_create(0, NULL);
604 buffer_flush(wb);
@@ -384,34 +620,41 @@ static void function_systemd_journal(const char *transaction, char *function, ch
620 facets_accepted_param(facets, JOURNAL_PARAMETER_FACETS);
621 facets_accepted_param(facets, JOURNAL_PARAMETER_HISTOGRAM);
622 facets_accepted_param(facets, JOURNAL_PARAMETER_IF_MODIFIED_SINCE);
623 + facets_accepted_param(facets, JOURNAL_PARAMETER_DATA_ONLY);
624
625 // register the fields in the order you want them on the dashboard
626
627 facets_register_dynamic_key_name(facets, "ND_JOURNAL_PROCESS",
628 FACET_KEY_OPTION_NEVER_FACET | FACET_KEY_OPTION_VISIBLE | FACET_KEY_OPTION_FTS,
392 - systemd_journal_dynamic_row_id, NULL);
629 + netdata_systemd_journal_dynamic_row_id, NULL);
630
631 facets_register_key_name(facets, "MESSAGE",
395 - FACET_KEY_OPTION_NEVER_FACET | FACET_KEY_OPTION_MAIN_TEXT | FACET_KEY_OPTION_VISIBLE |
396 - FACET_KEY_OPTION_FTS);
632 + FACET_KEY_OPTION_NEVER_FACET | FACET_KEY_OPTION_MAIN_TEXT |
633 + FACET_KEY_OPTION_VISIBLE | FACET_KEY_OPTION_FTS);
634 +
635 +// facets_register_dynamic_key_name(facets, "MESSAGE",
636 +// FACET_KEY_OPTION_NEVER_FACET | FACET_KEY_OPTION_MAIN_TEXT | FACET_KEY_OPTION_RICH_TEXT |
637 +// FACET_KEY_OPTION_VISIBLE | FACET_KEY_OPTION_FTS,
638 +// netdata_systemd_journal_rich_message, NULL);
639
640 facets_register_key_name_transformation(facets, "PRIORITY", FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_FTS,
399 - systemd_journal_transform_priority, NULL);
641 + netdata_systemd_journal_transform_priority, NULL);
642
643 facets_register_key_name_transformation(facets, "SYSLOG_FACILITY", FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_FTS,
402 - systemd_journal_transform_syslog_facility, NULL);
644 + netdata_systemd_journal_transform_syslog_facility, NULL);
645
646 facets_register_key_name(facets, "SYSLOG_IDENTIFIER", FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_FTS);
647 facets_register_key_name(facets, "UNIT", FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_FTS);
648 facets_register_key_name(facets, "USER_UNIT", FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_FTS);
649
650 facets_register_key_name_transformation(facets, "_UID", FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_FTS,
409 - systemd_journal_transform_uid, uids);
651 + netdata_systemd_journal_transform_uid, uids);
652
653 facets_register_key_name_transformation(facets, "_GID", FACET_KEY_OPTION_FACET | FACET_KEY_OPTION_FTS,
412 - systemd_journal_transform_gid, gids);
654 + netdata_systemd_journal_transform_gid, gids);
655
656 bool info = false;
657 + bool data_only = false;
658 time_t after_s = 0, before_s = 0;
659 usec_t anchor = 0;
660 usec_t if_modified_since = 0;
@@ -430,12 +673,15 @@ static void function_systemd_journal(const char *transaction, char *function, ch
673 if(!keyword) break;
674
675 if(strcmp(keyword, JOURNAL_PARAMETER_HELP) == 0) {
433 - systemd_journal_function_help(transaction);
676 + netdata_systemd_journal_function_help(transaction);
677 goto cleanup;
678 }
679 else if(strcmp(keyword, JOURNAL_PARAMETER_INFO) == 0) {
680 info = true;
681 }
682 + else if(strcmp(keyword, JOURNAL_PARAMETER_DATA_ONLY) == 0) {
683 + data_only = true;
684 + }
685 else if(strncmp(keyword, JOURNAL_PARAMETER_SOURCE ":", sizeof(JOURNAL_PARAMETER_SOURCE ":") - 1) == 0) {
686 source = &keyword[sizeof(JOURNAL_PARAMETER_SOURCE ":") - 1];
687 }
@@ -580,18 +826,22 @@ static void function_systemd_journal(const char *transaction, char *function, ch
826 facets_set_query(facets, query);
827 facets_set_histogram(facets, chart ? chart : "PRIORITY", after_s * USEC_PER_SEC, before_s * USEC_PER_SEC);
828
583 - response = systemd_journal_query(wb, facets, after_s * USEC_PER_SEC, before_s * USEC_PER_SEC,
584 - if_modified_since, now_monotonic_usec() + (timeout - 1) * USEC_PER_SEC);
829 + response = netdata_systemd_journal_query(wb, facets, after_s * USEC_PER_SEC, before_s * USEC_PER_SEC,
830 + anchor, direction, last,
831 + if_modified_since, data_only,
832 + now_monotonic_usec() + (timeout - 1) * USEC_PER_SEC);
833
834 if(response != HTTP_RESP_OK) {
835 + netdata_mutex_lock(&stdout_mutex);
836 pluginsd_function_json_error_to_stdout(transaction, response, "failed");
837 + netdata_mutex_unlock(&stdout_mutex);
838 goto cleanup;
839 }
840
841 output:
592 - pluginsd_function_result_begin_to_stdout(transaction, response, "application/json", expires);
593 - fwrite(buffer_tostring(wb), buffer_strlen(wb), 1, stdout);
594 - pluginsd_function_result_end_to_stdout();
842 + netdata_mutex_lock(&stdout_mutex);
843 + pluginsd_function_result_to_stdout(transaction, response, "application/json", expires, wb);
844 + netdata_mutex_unlock(&stdout_mutex);
845
846 cleanup:
847 facets_destroy(facets);
@@ -625,16 +875,14 @@ static void *reader_main(void *arg __maybe_unused) {
875 int timeout = str2i(timeout_s);
876 if(timeout <= 0) timeout = SYSTEMD_JOURNAL_DEFAULT_TIMEOUT;
877
628 - netdata_mutex_lock(&mutex);
629 -
878 if(strncmp(function, SYSTEMD_JOURNAL_FUNCTION_NAME, strlen(SYSTEMD_JOURNAL_FUNCTION_NAME)) == 0)
879 function_systemd_journal(transaction, function, buffer, PLUGINSD_LINE_MAX + 1, timeout);
632 - else
880 + else {
881 + netdata_mutex_lock(&stdout_mutex);
882 pluginsd_function_json_error_to_stdout(transaction, HTTP_RESP_NOT_FOUND,
883 "No function with this name found in systemd-journal.plugin.");
635 -
636 - fflush(stdout);
637 - netdata_mutex_unlock(&mutex);
884 + netdata_mutex_unlock(&stdout_mutex);
885 + }
886 }
887 }
888 else
@@ -691,16 +939,16 @@ int main(int argc __maybe_unused, char **argv __maybe_unused) {
939 usec_t step = 1000 * USEC_PER_MS;
940 bool tty = isatty(fileno(stderr)) == 1;
941
694 - netdata_mutex_lock(&mutex);
942 + netdata_mutex_lock(&stdout_mutex);
943 fprintf(stdout, PLUGINSD_KEYWORD_FUNCTION " GLOBAL \"%s\" %d \"%s\"\n",
944 SYSTEMD_JOURNAL_FUNCTION_NAME, SYSTEMD_JOURNAL_DEFAULT_TIMEOUT, SYSTEMD_JOURNAL_FUNCTION_DESCRIPTION);
945
946 heartbeat_t hb;
947 heartbeat_init(&hb);
948 for(iteration = 0; 1 ; iteration++) {
701 - netdata_mutex_unlock(&mutex);
949 + netdata_mutex_unlock(&stdout_mutex);
950 heartbeat_next(&hb, step);
703 - netdata_mutex_lock(&mutex);
951 + netdata_mutex_lock(&stdout_mutex);
952
953 if(!tty)
954 fprintf(stdout, "\n");
libnetdata/buffer/buffer.h
+19 -7
@@ -938,10 +938,12 @@ typedef enum __attribute__((packed)) {
938 RRDF_FIELD_OPTS_VISIBLE = (1 << 1), // the field should be visible by default
939 RRDF_FIELD_OPTS_STICKY = (1 << 2), // the field should be sticky
940 RRDF_FIELD_OPTS_FULL_WIDTH = (1 << 3), // the field should get full width
941 - RRDF_FIELD_OPTS_WRAP = (1 << 4), // the field should get full width
941 + RRDF_FIELD_OPTS_WRAP = (1 << 4), // the field should wrap
942 + RRDR_FIELD_OPTS_DUMMY = (1 << 5), // not a presentable field
943 } RRDF_FIELD_OPTIONS;
944
945 typedef enum __attribute__((packed)) {
946 + RRDF_FIELD_TYPE_NONE,
947 RRDF_FIELD_TYPE_INTEGER,
948 RRDF_FIELD_TYPE_STRING,
949 RRDF_FIELD_TYPE_DETAIL_STRING,
@@ -954,6 +956,9 @@ typedef enum __attribute__((packed)) {
956 static inline const char *rrdf_field_type_to_string(RRDF_FIELD_TYPE type) {
957 switch(type) {
958 default:
959 + case RRDF_FIELD_TYPE_NONE:
960 + return "none";
961 +
962 case RRDF_FIELD_TYPE_INTEGER:
963 return "integer";
964
@@ -978,10 +983,11 @@ static inline const char *rrdf_field_type_to_string(RRDF_FIELD_TYPE type) {
983 }
984
985 typedef enum __attribute__((packed)) {
981 - RRDF_FIELD_VISUAL_VALUE, // show the value, possibly applying a transformation
982 - RRDF_FIELD_VISUAL_BAR, // show the value and a bar, respecting the max field to fill the bar at 100%
983 - RRDF_FIELD_VISUAL_PILL, //
984 - RRDF_FIELD_VISUAL_MARKDOC, //
986 + RRDF_FIELD_VISUAL_VALUE, // show the value, possibly applying a transformation
987 + RRDF_FIELD_VISUAL_BAR, // show the value and a bar, respecting the max field to fill the bar at 100%
988 + RRDF_FIELD_VISUAL_PILL, //
989 + RRDF_FIELD_VISUAL_RICH, //
990 + RRDR_FIELD_VISUAL_ROW_OPTIONS, // this is a dummy column that is used for row options
991 } RRDF_FIELD_VISUAL;
992
993 static inline const char *rrdf_field_visual_to_string(RRDF_FIELD_VISUAL visual) {
@@ -996,8 +1002,11 @@ static inline const char *rrdf_field_visual_to_string(RRDF_FIELD_VISUAL visual)
1002 case RRDF_FIELD_VISUAL_PILL:
1003 return "pill";
1004
999 - case RRDF_FIELD_VISUAL_MARKDOC:
1000 - return "markdoc";
1005 + case RRDF_FIELD_VISUAL_RICH:
1006 + return "richValue";
1007 +
1008 + case RRDR_FIELD_VISUAL_ROW_OPTIONS:
1009 + return "rowOptions";
1010 }
1011 }
1012
@@ -1144,6 +1153,9 @@ buffer_rrdf_table_add_field(BUFFER *wb, size_t field_id, const char *key, const
1153
1154 buffer_json_member_add_boolean(wb, "full_width", options & RRDF_FIELD_OPTS_FULL_WIDTH);
1155 buffer_json_member_add_boolean(wb, "wrap", options & RRDF_FIELD_OPTS_WRAP);
1156 +
1157 + if(options & RRDR_FIELD_OPTS_DUMMY)
1158 + buffer_json_member_add_boolean(wb, "dummy", true);
1159 }
1160 buffer_json_object_close(wb);
1161 }
libnetdata/facets/facets.c
+166 -96
@@ -108,7 +108,6 @@ struct facets {
108
109 SIMPLE_PATTERN *query; // the full text search pattern
110 size_t keys_filtered_by_query; // the number of fields we do full text search (constant)
111 - size_t keys_matched_by_query; // the number of fields matched the full text search (per row)
111
112 DICTIONARY *accepted_params;
113
@@ -120,6 +119,11 @@ struct facets {
119 uint32_t max_items_to_return;
120 uint32_t order;
121
122 + struct {
123 + FACET_ROW_SEVERITY severity;
124 + size_t keys_matched_by_query; // the number of fields matched the full text search (per row)
125 + } current_row;
126 +
127 struct {
128 char *chart;
129 bool enabled;
@@ -647,7 +651,12 @@ static void facets_histogram_generate(FACETS *facets, FACET_KEY *k, BUFFER *wb)
651
652 buffer_json_member_add_object(wb, "view");
653 {
650 - buffer_json_member_add_string(wb, "title", "Events Distribution");
654 + char title[1024 + 1] = "Events Distribution";
655 + FACET_KEY *k = dictionary_get(facets->keys, facets->histogram.chart);
656 + if(k && k->name)
657 + snprintfz(title, 1024, "Events Distribution by %s", k->name);
658 +
659 + buffer_json_member_add_string(wb, "title", title);
660 buffer_json_member_add_time_t(wb, "update_every", facets->histogram.slot_width_ut / USEC_PER_SEC);
661 buffer_json_member_add_time_t(wb, "after", facets->histogram.after_ut / USEC_PER_SEC);
662 buffer_json_member_add_time_t(wb, "before", facets->histogram.before_ut / USEC_PER_SEC);
@@ -733,7 +742,7 @@ static inline bool facets_key_is_facet(FACETS *facets, FACET_KEY *k) {
742 }
743 }
744
736 - if(included && !excluded) {
745 + if(included && !excluded && !(facets->options & FACETS_OPTION_DISABLE_ALL_FACETS)) {
746 k->options |= FACET_KEY_OPTION_FACET;
747 k->options &= ~FACET_KEY_OPTION_NO_FACET;
748 return true;
@@ -988,6 +997,14 @@ void facets_register_facet_id_filter(FACETS *facets, const char *key_id, char *v
997 dictionary_set(k->values, value_ids, &tv, sizeof(tv));
998 }
999
1000 +void facets_set_current_row_severity(FACETS *facets, FACET_ROW_SEVERITY severity) {
1001 + facets->current_row.severity = severity;
1002 +}
1003 +
1004 +void facets_data_only_mode(FACETS *facets) {
1005 + facets->options |= FACETS_OPTION_DISABLE_ALL_FACETS | FACETS_OPTION_DISABLE_HISTOGRAM | FACETS_OPTION_DATA_ONLY;
1006 +}
1007 +
1008 // ----------------------------------------------------------------------------
1009
1010 static inline void facets_check_value(FACETS *facets __maybe_unused, FACET_KEY *k) {
@@ -1017,7 +1034,7 @@ static inline void facets_check_value(FACETS *facets __maybe_unused, FACET_KEY *
1034 if(facets->query && !k->current_value.empty && ((k->options & FACET_KEY_OPTION_FTS) || facets->options & FACETS_OPTION_ALL_KEYS_FTS)) {
1035 facets->operations.fts.searches++;
1036 if(simple_pattern_matches(facets->query, buffer_tostring(k->current_value.b)))
1020 - facets->keys_matched_by_query++;
1037 + facets->current_row.keys_matched_by_query++;
1038 }
1039
1040 if(k->values) {
@@ -1123,6 +1140,7 @@ static FACET_ROW *facets_row_create(FACETS *facets, usec_t usec, FACET_ROW *into
1140 facets->operations.rows.created++;
1141 }
1142
1143 + row->severity = facets->current_row.severity;
1144 row->usec = usec;
1145
1146 FACET_KEY *k;
@@ -1273,11 +1291,12 @@ void facets_rows_begin(FACETS *facets) {
1291 }
1292 // dfe_done(k);
1293
1276 - facets->keys_matched_by_query = 0;
1294 + facets->current_row.severity = FACET_ROW_SEVERITY_NORMAL;
1295 + facets->current_row.keys_matched_by_query = 0;
1296 }
1297
1298 void facets_row_finished(FACETS *facets, usec_t usec) {
1280 - if(facets->query && facets->keys_filtered_by_query && !facets->keys_matched_by_query)
1299 + if(facets->query && facets->keys_filtered_by_query && !facets->current_row.keys_matched_by_query)
1300 goto cleanup;
1301
1302 facets->operations.rows.evaluated++;
@@ -1345,6 +1364,26 @@ cleanup:
1364 // ----------------------------------------------------------------------------
1365 // output
1366
1367 +static const char *facets_severity_to_string(FACET_ROW_SEVERITY severity) {
1368 + switch(severity) {
1369 + default:
1370 + case FACET_ROW_SEVERITY_NORMAL:
1371 + return "normal";
1372 +
1373 + case FACET_ROW_SEVERITY_DEBUG:
1374 + return "debug";
1375 +
1376 + case FACET_ROW_SEVERITY_NOTICE:
1377 + return "notice";
1378 +
1379 + case FACET_ROW_SEVERITY_WARNING:
1380 + return "warning";
1381 +
1382 + case FACET_ROW_SEVERITY_CRITICAL:
1383 + return "critical";
1384 + }
1385 +}
1386 +
1387 void facets_accepted_parameters_to_json_array(FACETS *facets, BUFFER *wb, bool with_keys) {
1388 buffer_json_member_add_array(wb, "accepted_params");
1389 {
@@ -1371,104 +1410,126 @@ void facets_accepted_parameters_to_json_array(FACETS *facets, BUFFER *wb, bool w
1410 }
1411
1412 void facets_report(FACETS *facets, BUFFER *wb) {
1374 - buffer_json_member_add_boolean(wb, "show_ids", false);
1375 - buffer_json_member_add_boolean(wb, "has_history", true);
1413 + if(!(facets->options & FACETS_OPTION_DATA_ONLY)) {
1414 + buffer_json_member_add_boolean(wb, "show_ids", false); // do not show the column ids to the user
1415 + buffer_json_member_add_boolean(wb, "has_history", true); // enable date-time picker with after-before
1416
1377 - buffer_json_member_add_object(wb, "pagination");
1378 - buffer_json_member_add_boolean(wb, "enabled", true);
1379 - buffer_json_member_add_string(wb, "key", "anchor");
1380 - buffer_json_member_add_string(wb, "column", "timestamp");
1381 - buffer_json_object_close(wb);
1417 + buffer_json_member_add_object(wb, "pagination");
1418 + {
1419 + buffer_json_member_add_boolean(wb, "enabled", true);
1420 + buffer_json_member_add_string(wb, "key", "anchor");
1421 + buffer_json_member_add_string(wb, "column", "timestamp");
1422 + }
1423 + buffer_json_object_close(wb); // pagination
1424
1383 - facets_accepted_parameters_to_json_array(facets, wb, true);
1425 + facets_accepted_parameters_to_json_array(facets, wb, true);
1426 + }
1427
1385 - buffer_json_member_add_array(wb, "facets");
1386 - {
1387 - FACET_KEY *k;
1388 - dfe_start_read(facets->keys, k) {
1389 - if(!k->values)
1390 - continue;
1428 + if(!(facets->options & FACETS_OPTION_DISABLE_ALL_FACETS)) {
1429 + buffer_json_member_add_array(wb, "facets");
1430 + {
1431 + FACET_KEY *k;
1432 + dfe_start_read(facets->keys, k) {
1433 + if(!k->values)
1434 + continue;
1435
1392 - buffer_json_add_array_item_object(wb); // key
1393 - {
1394 - buffer_json_member_add_string(wb, "id", k_dfe.name);
1395 - buffer_json_member_add_string(wb, "name", k->name);
1436 + buffer_json_add_array_item_object(wb); // key
1437 + {
1438 + buffer_json_member_add_string(wb, "id", k_dfe.name);
1439 + buffer_json_member_add_string(wb, "name", k->name);
1440
1397 - if(!k->order)
1398 - k->order = facets->order++;
1441 + if(!k->order)
1442 + k->order = facets->order++;
1443
1400 - buffer_json_member_add_uint64(wb, "order", k->order);
1401 - buffer_json_member_add_array(wb, "options");
1402 - {
1403 - FACET_VALUE *v;
1404 - dfe_start_read(k->values, v) {
1405 - buffer_json_add_array_item_object(wb);
1406 - {
1407 - buffer_json_member_add_string(wb, "id", v_dfe.name);
1408 - buffer_json_member_add_string(wb, "name", v->name);
1409 - buffer_json_member_add_uint64(wb, "count", v->final_facet_value_counter);
1444 + buffer_json_member_add_uint64(wb, "order", k->order);
1445 + buffer_json_member_add_array(wb, "options");
1446 + {
1447 + FACET_VALUE *v;
1448 + dfe_start_read(k->values, v) {
1449 + buffer_json_add_array_item_object(wb);
1450 + {
1451 + buffer_json_member_add_string(wb, "id", v_dfe.name);
1452 + buffer_json_member_add_string(wb, "name", v->name);
1453 + buffer_json_member_add_uint64(wb, "count", v->final_facet_value_counter);
1454 + }
1455 + buffer_json_object_close(wb);
1456 }
1411 - buffer_json_object_close(wb);
1457 + dfe_done(v);
1458 }
1413 - dfe_done(v);
1459 + buffer_json_array_close(wb); // options
1460 }
1415 - buffer_json_array_close(wb); // options
1461 + buffer_json_object_close(wb); // key
1462 }
1417 - buffer_json_object_close(wb); // key
1463 + dfe_done(k);
1464 }
1419 - dfe_done(k);
1465 + buffer_json_array_close(wb); // facets
1466 }
1421 - buffer_json_array_close(wb); // facets
1422 -
1423 - buffer_json_member_add_object(wb, "columns");
1424 - {
1425 - size_t field_id = 0;
1426 - buffer_rrdf_table_add_field(
1427 - wb, field_id++,
1428 - "timestamp", "Timestamp",
1429 - RRDF_FIELD_TYPE_TIMESTAMP,
1430 - RRDF_FIELD_VISUAL_VALUE,
1431 - RRDF_FIELD_TRANSFORM_DATETIME_USEC, 0, NULL, NAN,
1432 - RRDF_FIELD_SORT_DESCENDING,
1433 - NULL,
1434 - RRDF_FIELD_SUMMARY_COUNT,
1435 - RRDF_FIELD_FILTER_RANGE,
1436 - RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_UNIQUE_KEY,
1437 - NULL);
1438 -
1439 - FACET_KEY *k;
1440 - dfe_start_read(facets->keys, k) {
1441 - RRDF_FIELD_OPTIONS options = RRDF_FIELD_OPTS_NONE;
1442 - bool visible = k->options & (FACET_KEY_OPTION_VISIBLE|FACET_KEY_OPTION_STICKY);
1443 -
1444 - if((facets->options & FACETS_OPTION_ALL_FACETS_VISIBLE && k->values))
1445 - visible = true;
1446 -
1447 - if(!visible)
1448 - visible = simple_pattern_matches(facets->visible_keys, k->name);
1449 -
1450 - if(visible)
1451 - options |= RRDF_FIELD_OPTS_VISIBLE;
1452 -
1453 - if(k->options & FACET_KEY_OPTION_MAIN_TEXT)
1454 - options |= RRDF_FIELD_OPTS_FULL_WIDTH | RRDF_FIELD_OPTS_WRAP;
1467
1468 + if(!(facets->options & FACETS_OPTION_DATA_ONLY)) {
1469 + buffer_json_member_add_object(wb, "columns");
1470 + {
1471 + size_t field_id = 0;
1472 buffer_rrdf_table_add_field(
1473 wb, field_id++,
1458 - k_dfe.name, k->name ? k->name : k_dfe.name,
1459 - RRDF_FIELD_TYPE_STRING,
1474 + "timestamp", "Timestamp",
1475 + RRDF_FIELD_TYPE_TIMESTAMP,
1476 RRDF_FIELD_VISUAL_VALUE,
1477 + RRDF_FIELD_TRANSFORM_DATETIME_USEC, 0, NULL, NAN,
1478 + RRDF_FIELD_SORT_DESCENDING,
1479 + NULL,
1480 + RRDF_FIELD_SUMMARY_COUNT,
1481 + RRDF_FIELD_FILTER_RANGE,
1482 + RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_UNIQUE_KEY,
1483 + NULL);
1484 +
1485 + buffer_rrdf_table_add_field(
1486 + wb, field_id++,
1487 + "rowOptions", "rowOptions",
1488 + RRDF_FIELD_TYPE_NONE,
1489 + RRDR_FIELD_VISUAL_ROW_OPTIONS,
1490 RRDF_FIELD_TRANSFORM_NONE, 0, NULL, NAN,
1462 - RRDF_FIELD_SORT_ASCENDING,
1491 + RRDF_FIELD_SORT_FIXED,
1492 NULL,
1493 RRDF_FIELD_SUMMARY_COUNT,
1465 - (k->options & FACET_KEY_OPTION_NEVER_FACET) ? RRDF_FIELD_FILTER_NONE : RRDF_FIELD_FILTER_FACET,
1466 - options,
1467 - FACET_VALUE_UNSET);
1494 + RRDF_FIELD_FILTER_NONE,
1495 + RRDR_FIELD_OPTS_DUMMY,
1496 + NULL);
1497 +
1498 + FACET_KEY *k;
1499 + dfe_start_read(facets->keys, k){
1500 + RRDF_FIELD_OPTIONS options = RRDF_FIELD_OPTS_NONE;
1501 + bool visible = k->options & (FACET_KEY_OPTION_VISIBLE | FACET_KEY_OPTION_STICKY);
1502 +
1503 + if ((facets->options & FACETS_OPTION_ALL_FACETS_VISIBLE && k->values))
1504 + visible = true;
1505 +
1506 + if (!visible)
1507 + visible = simple_pattern_matches(facets->visible_keys, k->name);
1508 +
1509 + if (visible)
1510 + options |= RRDF_FIELD_OPTS_VISIBLE;
1511 +
1512 + if (k->options & FACET_KEY_OPTION_MAIN_TEXT)
1513 + options |= RRDF_FIELD_OPTS_FULL_WIDTH | RRDF_FIELD_OPTS_WRAP;
1514 +
1515 + buffer_rrdf_table_add_field(
1516 + wb, field_id++,
1517 + k_dfe.name, k->name ? k->name : k_dfe.name,
1518 + RRDF_FIELD_TYPE_STRING,
1519 + (k->options & FACET_KEY_OPTION_RICH_TEXT) ? RRDF_FIELD_VISUAL_RICH : RRDF_FIELD_VISUAL_VALUE,
1520 + RRDF_FIELD_TRANSFORM_NONE, 0, NULL, NAN,
1521 + RRDF_FIELD_SORT_ASCENDING,
1522 + NULL,
1523 + RRDF_FIELD_SUMMARY_COUNT,
1524 + (k->options & FACET_KEY_OPTION_NEVER_FACET) ? RRDF_FIELD_FILTER_NONE
1525 + : RRDF_FIELD_FILTER_FACET,
1526 + options,
1527 + FACET_VALUE_UNSET);
1528 + }
1529 + dfe_done(k);
1530 }
1469 - dfe_done(k);
1531 + buffer_json_object_close(wb); // columns
1532 }
1471 - buffer_json_object_close(wb); // columns
1533
1534 buffer_json_member_add_array(wb, "data");
1535 {
@@ -1488,6 +1549,11 @@ void facets_report(FACETS *facets, BUFFER *wb) {
1549
1550 buffer_json_add_array_item_array(wb); // each row
1551 buffer_json_add_array_item_uint64(wb, row->usec);
1552 + buffer_json_add_array_item_object(wb);
1553 + {
1554 + buffer_json_member_add_string(wb, "severity", facets_severity_to_string(row->severity));
1555 + }
1556 + buffer_json_object_close(wb);
1557
1558 FACET_KEY *k;
1559 dfe_start_read(facets->keys, k)
@@ -1514,11 +1580,13 @@ void facets_report(FACETS *facets, BUFFER *wb) {
1580 }
1581 buffer_json_array_close(wb); // data
1582
1517 - buffer_json_member_add_string(wb, "default_sort_column", "timestamp");
1518 - buffer_json_member_add_array(wb, "default_charts");
1519 - buffer_json_array_close(wb);
1583 + if(!(facets->options & FACETS_OPTION_DATA_ONLY)) {
1584 + buffer_json_member_add_string(wb, "default_sort_column", "timestamp");
1585 + buffer_json_member_add_array(wb, "default_charts");
1586 + buffer_json_array_close(wb);
1587 + }
1588
1521 - if(facets->histogram.enabled) {
1589 + if(facets->histogram.enabled && !(facets->options & FACETS_OPTION_DISABLE_HISTOGRAM)) {
1590 const char *first_histogram = NULL;
1591 buffer_json_member_add_array(wb, "available_histograms");
1592 {
@@ -1561,16 +1629,18 @@ void facets_report(FACETS *facets, BUFFER *wb) {
1629 }
1630 }
1631
1564 - buffer_json_member_add_object(wb, "items");
1565 - {
1566 - buffer_json_member_add_uint64(wb, "evaluated", facets->operations.rows.evaluated);
1567 - buffer_json_member_add_uint64(wb, "matched", facets->operations.rows.matched);
1568 - buffer_json_member_add_uint64(wb, "returned", facets->items_to_return);
1569 - buffer_json_member_add_uint64(wb, "max_to_return", facets->max_items_to_return);
1570 - buffer_json_member_add_uint64(wb, "before", facets->operations.skips_before);
1571 - buffer_json_member_add_uint64(wb, "after", facets->operations.skips_after + facets->operations.shifts);
1632 + if(!(facets->options & FACETS_OPTION_DATA_ONLY)) {
1633 + buffer_json_member_add_object(wb, "items");
1634 + {
1635 + buffer_json_member_add_uint64(wb, "evaluated", facets->operations.rows.evaluated);
1636 + buffer_json_member_add_uint64(wb, "matched", facets->operations.rows.matched);
1637 + buffer_json_member_add_uint64(wb, "returned", facets->items_to_return);
1638 + buffer_json_member_add_uint64(wb, "max_to_return", facets->max_items_to_return);
1639 + buffer_json_member_add_uint64(wb, "before", facets->operations.skips_before);
1640 + buffer_json_member_add_uint64(wb, "after", facets->operations.skips_after + facets->operations.shifts);
1641 + }
1642 + buffer_json_object_close(wb); // items
1643 }
1573 - buffer_json_object_close(wb); // items
1644
1645 buffer_json_member_add_object(wb, "stats");
1646 {
libnetdata/facets/facets.h
+16 -1
@@ -18,9 +18,18 @@ typedef enum __attribute__((packed)) {
18 FACET_KEY_OPTION_VISIBLE = (1 << 4), // should be in the default table
19 FACET_KEY_OPTION_FTS = (1 << 5), // the key is filterable by full text search (FTS)
20 FACET_KEY_OPTION_MAIN_TEXT = (1 << 6), // full width and wrap
21 - FACET_KEY_OPTION_REORDER = (1 << 7), // give the key a new order id on first encounter
21 + FACET_KEY_OPTION_RICH_TEXT = (1 << 7),
22 + FACET_KEY_OPTION_REORDER = (1 << 8), // give the key a new order id on first encounter
23 } FACET_KEY_OPTIONS;
24
25 +typedef enum __attribute__((packed)) {
26 + FACET_ROW_SEVERITY_DEBUG, // lowest - not important
27 + FACET_ROW_SEVERITY_NORMAL, // the default
28 + FACET_ROW_SEVERITY_NOTICE, // bold
29 + FACET_ROW_SEVERITY_WARNING, // yellow + bold
30 + FACET_ROW_SEVERITY_CRITICAL, // red + bold
31 +} FACET_ROW_SEVERITY;
32 +
33 typedef struct facet_row_key_value {
34 const char *tmp;
35 BUFFER *wb;
@@ -30,6 +39,7 @@ typedef struct facet_row_key_value {
39 typedef struct facet_row {
40 usec_t usec;
41 DICTIONARY *dict;
42 + FACET_ROW_SEVERITY severity;
43 struct facet_row *prev, *next;
44 } FACET_ROW;
45
@@ -47,6 +57,9 @@ FACET_KEY *facets_register_key_name_transformation(FACETS *facets, const char *k
57 typedef enum __attribute__((packed)) {
58 FACETS_OPTION_ALL_FACETS_VISIBLE = (1 << 0), // all facets, should be visible by default in the table
59 FACETS_OPTION_ALL_KEYS_FTS = (1 << 1), // all keys are searchable by full text search
60 + FACETS_OPTION_DISABLE_ALL_FACETS = (1 << 2),
61 + FACETS_OPTION_DISABLE_HISTOGRAM = (1 << 3),
62 + FACETS_OPTION_DATA_ONLY = (1 << 4),
63 } FACETS_OPTIONS;
64
65 FACETS *facets_create(uint32_t items_to_return, FACETS_OPTIONS options, const char *visible_keys, const char *facet_keys, const char *non_facet_keys);
@@ -70,5 +83,7 @@ void facets_add_key_value_length(FACETS *facets, const char *key, size_t key_len
83
84 void facets_report(FACETS *facets, BUFFER *wb);
85 void facets_accepted_parameters_to_json_array(FACETS *facets, BUFFER *wb, bool with_keys);
86 +void facets_set_current_row_severity(FACETS *facets, FACET_ROW_SEVERITY severity);
87 +void facets_data_only_mode(FACETS *facets);
88
89 #endif