suricata
output-json-stats.c
Go to the documentation of this file.
1/* Copyright (C) 2014-2024 Open Information Security Foundation
2 *
3 * You can copy, redistribute or modify this Program under the terms of
4 * the GNU General Public License version 2 as published by the Free
5 * Software Foundation.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * version 2 along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
15 * 02110-1301, USA.
16 */
17
18/**
19 * \file
20 *
21 * \author Tom DeCanio <td@npulsetech.com>
22 *
23 * Implements JSON stats counters logging portion of the engine.
24 */
25
26#include "suricata-common.h"
27#include "detect.h"
28#include "pkt-var.h"
29#include "conf.h"
30#include "detect-engine.h"
31
32#include "threads.h"
33#include "threadvars.h"
34#include "tm-threads.h"
35
36#include "util-print.h"
37#include "util-time.h"
38#include "util-unittest.h"
39#include "util-validate.h"
40
41#include "util-debug.h"
42#include "output.h"
43#include "util-privs.h"
44#include "util-buffer.h"
45
46#include "util-logopenfile.h"
47
48#include "output-json.h"
49#include "output-json-stats.h"
50
51#define MODULE_NAME "JsonStatsLog"
52
53extern bool stats_decoder_events;
54extern const char *stats_decoder_events_prefix;
55
56/**
57 * specify which engine info will be printed in stats log.
58 * ALL means both last reload and ruleset stats.
59 */
65
66typedef struct OutputStatsCtx_ {
68 uint8_t flags; /** Store mode */
70
76
77static json_t *EngineStats2Json(const DetectEngineCtx *de_ctx,
78 const OutputEngineInfo output)
79{
80 char timebuf[64];
81 const SigFileLoaderStat *sig_stat = NULL;
82
83 json_t *jdata = json_object();
84 if (jdata == NULL) {
85 return NULL;
86 }
87
88 if (output == OUTPUT_ENGINE_LAST_RELOAD || output == OUTPUT_ENGINE_ALL) {
90 CreateIsoTimeString(last_reload, timebuf, sizeof(timebuf));
91 json_object_set_new(jdata, "last_reload", json_string(timebuf));
92 }
93
94 sig_stat = &de_ctx->sig_stat;
95 if ((output == OUTPUT_ENGINE_RULESET || output == OUTPUT_ENGINE_ALL) &&
96 sig_stat != NULL)
97 {
98 json_object_set_new(jdata, "rules_loaded",
99 json_integer(sig_stat->good_sigs_total));
100 json_object_set_new(jdata, "rules_failed",
101 json_integer(sig_stat->bad_sigs_total));
102 json_object_set_new(jdata, "rules_skipped", json_integer(sig_stat->skipped_sigs_total));
103 }
104
105 return jdata;
106}
107
108static TmEcode OutputEngineStats2Json(json_t **jdata, const OutputEngineInfo output)
109{
111 if (de_ctx == NULL) {
112 goto err1;
113 }
114 /* Since we need to deference de_ctx pointer, we don't want to lost it. */
115 DetectEngineCtx *list = de_ctx;
116
117 json_t *js_tenant_list = json_array();
118 json_t *js_tenant = NULL;
119
120 if (js_tenant_list == NULL) {
121 goto err2;
122 }
123
124 while(list) {
125 js_tenant = json_object();
126 if (js_tenant == NULL) {
127 goto err3;
128 }
129 json_object_set_new(js_tenant, "id", json_integer(list->tenant_id));
130
131 json_t *js_stats = EngineStats2Json(list, output);
132 if (js_stats == NULL) {
133 goto err4;
134 }
135 json_object_update(js_tenant, js_stats);
136 json_array_append_new(js_tenant_list, js_tenant);
137 json_decref(js_stats);
138 list = list->next;
139 }
140
142 *jdata = js_tenant_list;
143 return TM_ECODE_OK;
144
145err4:
146 json_object_clear(js_tenant);
147 json_decref(js_tenant);
148
149err3:
150 json_object_clear(js_tenant_list);
151 json_decref(js_tenant_list);
152
153err2:
155
156err1:
157 json_object_set_new(*jdata, "message", json_string("Unable to get info"));
158 return TM_ECODE_FAILED;
159}
160
162 return OutputEngineStats2Json(jdata, OUTPUT_ENGINE_LAST_RELOAD);
163}
164
166 return OutputEngineStats2Json(jdata, OUTPUT_ENGINE_RULESET);
167}
168
169static json_t *OutputStats2Json(json_t *js, const char *key)
170{
171 void *iter;
172
173 const char *dot = strchr(key, '.');
174 if (dot == NULL)
175 return NULL;
176 if (strlen(dot) > 2) {
177 if (*(dot + 1) == '.' && *(dot + 2) != '\0')
178 dot = strchr(dot + 2, '.');
179 }
180
181 size_t predot_len = (dot - key) + 1;
182 char s[predot_len];
183 strlcpy(s, key, predot_len);
184
185 iter = json_object_iter_at(js, s);
186 const char *s2 = strchr(dot+1, '.');
187
188 json_t *value = json_object_iter_value(iter);
189 if (value == NULL) {
190 value = json_object();
191
192 if (!strncmp(s, "detect", 6)) {
193 json_t *js_engine = NULL;
194
195 TmEcode ret = OutputEngineStats2Json(&js_engine, OUTPUT_ENGINE_ALL);
196 if (ret == TM_ECODE_OK && js_engine) {
197 json_object_set_new(value, "engines", js_engine);
198 }
199 }
200 json_object_set_new(js, s, value);
201 }
202 if (s2 != NULL) {
203 return OutputStats2Json(value, &key[dot-key+1]);
204 }
205 return value;
206}
207
208/** \brief turn StatsTable into a json object
209 * \param flags JSON_STATS_* flags for controlling output
210 */
211json_t *StatsToJSON(const StatsTable *st, uint8_t flags)
212{
213 const char delta_suffix[] = "_delta";
214 struct timeval tval;
215 gettimeofday(&tval, NULL);
216
217 json_t *js_stats = json_object();
218 if (unlikely(js_stats == NULL)) {
219 return NULL;
220 }
221
222 /* Uptime, in seconds. */
223 double up_time_d = difftime(tval.tv_sec, st->start_time);
224 json_object_set_new(js_stats, "uptime",
225 json_integer((int)up_time_d));
226
227 uint32_t u = 0;
228 if (flags & JSON_STATS_TOTALS) {
229 for (u = 0; u < st->nstats; u++) {
230 if (st->stats[u].name == NULL)
231 continue;
232 if (flags & JSON_STATS_NO_ZEROES && st->stats[u].value == 0) {
233 continue;
234 }
235
236 json_t *js_type = NULL;
237 const char *stat_name = st->stats[u].short_name;
238 /*
239 * When there's no short-name, the stat is added to
240 * the "global" stats namespace, just like "uptime"
241 */
242 if (st->stats[u].short_name == NULL) {
243 stat_name = st->stats[u].name;
244 js_type = js_stats;
245 } else {
246 js_type = OutputStats2Json(js_stats, st->stats[u].name);
247 }
248 if (js_type != NULL) {
249 json_object_set_new(js_type, stat_name, json_integer(st->stats[u].value));
250
251 if (flags & JSON_STATS_DELTAS) {
252 char deltaname[strlen(stat_name) + strlen(delta_suffix) + 1];
253 snprintf(deltaname, sizeof(deltaname), "%s%s", stat_name, delta_suffix);
254 json_object_set_new(js_type, deltaname,
255 json_integer(st->stats[u].value - st->stats[u].pvalue));
256 }
257 }
258 }
259 }
260
261 /* per thread stats - stored in a "threads" object. */
262 if (st->tstats != NULL && (flags & JSON_STATS_THREADS)) {
263 /* for each thread (store) */
264 json_t *threads = json_object();
265 if (unlikely(threads == NULL)) {
266 json_decref(js_stats);
267 return NULL;
268 }
269 uint32_t x;
270 for (x = 0; x < st->ntstats; x++) {
271 uint32_t offset = x * st->nstats;
272 const char *tm_name = NULL;
273 json_t *thread = NULL;
274
275 /* for each counter */
276 for (u = offset; u < (offset + st->nstats); u++) {
277 if (st->tstats[u].name == NULL)
278 continue;
279 if (flags & JSON_STATS_NO_ZEROES && st->tstats[u].value == 0) {
280 continue;
281 }
282
283 DEBUG_VALIDATE_BUG_ON(st->tstats[u].tm_name == NULL);
284
285 if (tm_name == NULL) {
286 // First time we see a set tm_name. Remember it
287 // and allocate the stats object for this thread.
288 tm_name = st->tstats[u].tm_name;
289 thread = json_object();
290 if (unlikely(thread == NULL)) {
291 json_decref(js_stats);
292 json_decref(threads);
293 return NULL;
294 }
295 } else {
296 DEBUG_VALIDATE_BUG_ON(strcmp(tm_name, st->tstats[u].tm_name) != 0);
297 DEBUG_VALIDATE_BUG_ON(thread == NULL);
298 }
299
300 json_t *js_type = NULL;
301 const char *stat_name = st->tstats[u].short_name;
302 if (st->tstats[u].short_name == NULL) {
303 stat_name = st->tstats[u].name;
304 js_type = threads;
305 } else {
306 js_type = OutputStats2Json(thread, st->tstats[u].name);
307 }
308
309 if (js_type != NULL) {
310 json_object_set_new(js_type, stat_name, json_integer(st->tstats[u].value));
311
312 if (flags & JSON_STATS_DELTAS) {
313 char deltaname[strlen(stat_name) + strlen(delta_suffix) + 1];
314 snprintf(deltaname, sizeof(deltaname), "%s%s", stat_name, delta_suffix);
315 json_object_set_new(js_type, deltaname,
316 json_integer(st->tstats[u].value - st->tstats[u].pvalue));
317 }
318 }
319 }
320 if (tm_name != NULL) {
321 DEBUG_VALIDATE_BUG_ON(thread == NULL);
322 json_object_set_new(threads, tm_name, thread);
323 }
324 }
325 json_object_set_new(js_stats, "threads", threads);
326 }
327 return js_stats;
328}
329
330static int JsonStatsLogger(ThreadVars *tv, void *thread_data, const StatsTable *st)
331{
332 SCEnter();
333 JsonStatsLogThread *aft = (JsonStatsLogThread *)thread_data;
334
335 struct timeval tval;
336 gettimeofday(&tval, NULL);
337
338 json_t *js = json_object();
339 if (unlikely(js == NULL))
340 return 0;
341 char timebuf[64];
342 CreateIsoTimeString(SCTIME_FROM_TIMEVAL(&tval), timebuf, sizeof(timebuf));
343 json_object_set_new(js, "timestamp", json_string(timebuf));
344 json_object_set_new(js, "event_type", json_string("stats"));
345
346 json_t *js_stats = StatsToJSON(st, aft->statslog_ctx->flags);
347 if (js_stats == NULL) {
348 json_decref(js);
349 return 0;
350 }
351
352 json_object_set_new(js, "stats", js_stats);
353
354 OutputJSONBuffer(js, aft->file_ctx, &aft->buffer);
355 MemBufferReset(aft->buffer);
356
357 json_object_clear(js_stats);
358 json_object_del(js, "stats");
359 json_object_clear(js);
360 json_decref(js);
361
362 SCReturnInt(0);
363}
364
365static TmEcode JsonStatsLogThreadInit(ThreadVars *t, const void *initdata, void **data)
366{
368 if (unlikely(aft == NULL))
369 return TM_ECODE_FAILED;
370
371 if(initdata == NULL)
372 {
373 SCLogDebug("Error getting context for EveLogStats. \"initdata\" argument NULL");
374 goto error_exit;
375 }
376
378 if (aft->buffer == NULL) {
379 goto error_exit;
380 }
381
382 /* Use the Output Context (file pointer and mutex) */
383 aft->statslog_ctx = ((OutputCtx *)initdata)->data;
384
386 if (!aft->file_ctx) {
387 goto error_exit;
388 }
389
390 *data = (void *)aft;
391 return TM_ECODE_OK;
392
393error_exit:
394 if (aft->buffer != NULL) {
395 MemBufferFree(aft->buffer);
396 }
397 SCFree(aft);
398 return TM_ECODE_FAILED;
399}
400
401static TmEcode JsonStatsLogThreadDeinit(ThreadVars *t, void *data)
402{
404 if (aft == NULL) {
405 return TM_ECODE_OK;
406 }
407
408 MemBufferFree(aft->buffer);
409
410 /* clear memory */
411 memset(aft, 0, sizeof(JsonStatsLogThread));
412
413 SCFree(aft);
414 return TM_ECODE_OK;
415}
416
417static void OutputStatsLogDeinitSub(OutputCtx *output_ctx)
418{
419 OutputStatsCtx *stats_ctx = output_ctx->data;
420 SCFree(stats_ctx);
421 SCFree(output_ctx);
422}
423
424static OutputInitResult OutputStatsLogInitSub(SCConfNode *conf, OutputCtx *parent_ctx)
425{
426 OutputInitResult result = { NULL, false };
427 OutputJsonCtx *ajt = parent_ctx->data;
428
429 if (!StatsEnabled()) {
430 SCLogError("eve.stats: stats are disabled globally: set stats.enabled to true. "
431 "See %s/configuration/suricata-yaml.html#stats",
432 GetDocURL());
433 return result;
434 }
435
436 OutputStatsCtx *stats_ctx = SCMalloc(sizeof(OutputStatsCtx));
437 if (unlikely(stats_ctx == NULL))
438 return result;
439
441 strcmp(stats_decoder_events_prefix, "decoder") == 0) {
442 SCLogWarning("eve.stats will not display "
443 "all decoder events correctly. See ticket #2225. Set a prefix in "
444 "stats.decoder-events-prefix.");
445 }
446
447 stats_ctx->flags = JSON_STATS_TOTALS;
448
449 if (conf != NULL) {
450 const char *totals = SCConfNodeLookupChildValue(conf, "totals");
451 const char *threads = SCConfNodeLookupChildValue(conf, "threads");
452 const char *deltas = SCConfNodeLookupChildValue(conf, "deltas");
453 const char *zero_counters = SCConfNodeLookupChildValue(conf, "null-values");
454 SCLogDebug("totals %s threads %s deltas %s", totals, threads, deltas);
455
456 if ((totals != NULL && SCConfValIsFalse(totals)) &&
457 (threads != NULL && SCConfValIsFalse(threads))) {
458 SCFree(stats_ctx);
459 SCLogError("Cannot disable both totals and threads in stats logging");
460 return result;
461 }
462
463 if (totals != NULL && SCConfValIsFalse(totals)) {
464 stats_ctx->flags &= ~JSON_STATS_TOTALS;
465 }
466 if (threads != NULL && SCConfValIsTrue(threads)) {
467 stats_ctx->flags |= JSON_STATS_THREADS;
468 }
469 if (deltas != NULL && SCConfValIsTrue(deltas)) {
470 stats_ctx->flags |= JSON_STATS_DELTAS;
471 }
472 if (zero_counters != NULL && SCConfValIsFalse(zero_counters)) {
473 stats_ctx->flags |= JSON_STATS_NO_ZEROES;
474 }
475 SCLogDebug("stats_ctx->flags %08x", stats_ctx->flags);
476 }
477
478 OutputCtx *output_ctx = SCCalloc(1, sizeof(OutputCtx));
479 if (unlikely(output_ctx == NULL)) {
480 SCFree(stats_ctx);
481 return result;
482 }
483
484 SCLogDebug("Preparing file context for stats submodule logger");
485 /* prepared by suricata-main */
486 stats_ctx->file_ctx = LogFileEnsureExists(0, ajt->file_ctx);
487 if (!stats_ctx->file_ctx) {
488 SCFree(stats_ctx);
489 SCFree(output_ctx);
490 return result;
491 }
492
493 output_ctx->data = stats_ctx;
494 output_ctx->DeInit = OutputStatsLogDeinitSub;
495
496 result.ctx = output_ctx;
497 result.ok = true;
498 return result;
499}
500
502 /* register as child of eve-log */
504 OutputStatsLogInitSub, JsonStatsLogger, JsonStatsLogThreadInit,
505 JsonStatsLogThreadDeinit);
506}
507
508#ifdef UNITTESTS
510#endif
int SCConfValIsTrue(const char *val)
Check if a value is true.
Definition conf.c:551
const char * SCConfNodeLookupChildValue(const SCConfNode *node, const char *name)
Lookup the value of a child configuration node by name.
Definition conf.c:824
int SCConfValIsFalse(const char *val)
Check if a value is false.
Definition conf.c:576
bool StatsEnabled(void)
Definition counters.c:118
uint8_t flags
Definition decode-gre.h:0
void DetectEngineDeReference(DetectEngineCtx **de_ctx)
DetectEngineCtx * DetectEngineGetCurrent(void)
ThreadVars * tv
DetectEngineCtx * de_ctx
const char * stats_decoder_events_prefix
Definition counters.c:102
bool stats_decoder_events
Definition counters.c:101
TmEcode OutputEngineStatsReloadTime(json_t **jdata)
#define MODULE_NAME
void JsonStatsLogRegister(void)
struct OutputStatsCtx_ OutputStatsCtx
struct JsonStatsLogThread_ JsonStatsLogThread
OutputEngineInfo_
@ OUTPUT_ENGINE_ALL
@ OUTPUT_ENGINE_LAST_RELOAD
@ OUTPUT_ENGINE_RULESET
json_t * StatsToJSON(const StatsTable *st, uint8_t flags)
turn StatsTable into a json object
TmEcode OutputEngineStatsRuleset(json_t **jdata)
enum OutputEngineInfo_ OutputEngineInfo
#define JSON_STATS_TOTALS
#define JSON_STATS_THREADS
#define JSON_STATS_NO_ZEROES
#define JSON_STATS_DELTAS
int OutputJSONBuffer(json_t *js, LogFileCtx *file_ctx, MemBuffer **buffer)
#define JSON_OUTPUT_BUFFER_SIZE
Definition output-json.h:56
void OutputRegisterStatsSubModule(LoggerId id, const char *parent_name, const char *name, const char *conf_name, OutputInitSubFunc InitFunc, StatsLogger StatsLogFunc, ThreadInitFunc ThreadInit, ThreadDeinitFunc ThreadDeinit)
Register a stats data output sub-module.
Definition output.c:606
main detection engine ctx
Definition detect.h:932
uint32_t tenant_id
Definition detect.h:939
SigFileLoaderStat sig_stat
Definition detect.h:1102
struct DetectEngineCtx_ * next
Definition detect.h:1058
struct timeval last_reload
Definition detect.h:1099
OutputStatsCtx * statslog_ctx
void * data
Definition tm-modules.h:91
void(* DeInit)(struct OutputCtx_ *)
Definition tm-modules.h:94
OutputCtx * ctx
Definition output.h:47
LogFileCtx * file_ctx
Definition output-json.h:76
LogFileCtx * file_ctx
Signature loader statistics.
Definition detect.h:876
const char * name
const char * tm_name
int64_t pvalue
const char * short_name
int64_t value
time_t start_time
StatsRecord * stats
StatsRecord * tstats
uint32_t nstats
uint32_t ntstats
Per thread variable structure.
Definition threadvars.h:58
@ LOGGER_JSON_STATS
size_t strlcpy(char *dst, const char *src, size_t siz)
const char * GetDocURL(void)
Definition suricata.c:1165
@ TM_ECODE_FAILED
@ TM_ECODE_OK
MemBuffer * MemBufferCreateNew(uint32_t size)
Definition util-buffer.c:32
void MemBufferFree(MemBuffer *buffer)
Definition util-buffer.c:86
#define SCEnter(...)
Definition util-debug.h:277
#define SCLogDebug(...)
Definition util-debug.h:275
#define SCReturnInt(x)
Definition util-debug.h:281
#define SCLogWarning(...)
Macro used to log WARNING messages.
Definition util-debug.h:255
#define SCLogError(...)
Macro used to log ERROR messages.
Definition util-debug.h:267
LogFileCtx * LogFileEnsureExists(ThreadId thread_id, LogFileCtx *parent_ctx)
LogFileEnsureExists() Ensure a log file context for the thread exists.
#define SCMalloc(sz)
Definition util-mem.h:47
#define SCFree(p)
Definition util-mem.h:61
#define SCCalloc(nm, sz)
Definition util-mem.h:53
#define unlikely(expr)
uint64_t offset
void CreateIsoTimeString(const SCTime_t ts, char *str, size_t size)
Definition util-time.c:209
#define SCTIME_FROM_TIMEVAL(tv)
Definition util-time.h:79
#define DEBUG_VALIDATE_BUG_ON(exp)