1 /* Copyright (C) 2014 Open Information Security Foundation
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
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.
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
21 * \author Tom DeCanio <td@npulsetech.com>
23 * Implements JSON stats counters logging portion of the engine.
26 #include "suricata-common.h"
33 #include "threadvars.h"
34 #include "tm-threads.h"
36 #include "util-print.h"
37 #include "util-unittest.h"
39 #include "util-debug.h"
41 #include "util-privs.h"
42 #include "util-buffer.h"
44 #include "util-logopenfile.h"
45 #include "util-crypt.h"
47 #include "output-json.h"
48 #include "output-json-stats.h"
50 #define MODULE_NAME "JsonStatsLog"
52 #ifdef HAVE_LIBJANSSON
54 typedef struct OutputStatsCtx_
{
56 uint32_t flags
; /** Store mode */
59 typedef struct JsonStatsLogThread_
{
60 OutputStatsCtx
*statslog_ctx
;
64 static json_t
*OutputStats2Json(json_t
*js
, const char *key
)
68 const char *dot
= index(key
, '.');
71 if (strlen(dot
) > 2) {
72 if (*(dot
+ 1) == '.' && *(dot
+ 2) != '\0')
73 dot
= index(dot
+ 2, '.');
76 size_t predot_len
= (dot
- key
) + 1;
78 strlcpy(s
, key
, predot_len
);
80 iter
= json_object_iter_at(js
, s
);
81 const char *s2
= index(dot
+1, '.');
83 json_t
*value
= json_object_iter_value(iter
);
85 value
= json_object();
86 json_object_set_new(js
, s
, value
);
89 return OutputStats2Json(value
, &key
[dot
-key
+1]);
94 /** \brief turn StatsTable into a json object
95 * \param flags JSON_STATS_* flags for controlling output
97 json_t
*StatsToJSON(const StatsTable
*st
, uint8_t flags
)
99 const char delta_suffix
[] = "_delta";
101 gettimeofday(&tval
, NULL
);
103 json_t
*js_stats
= json_object();
104 if (unlikely(js_stats
== NULL
)) {
108 /* Uptime, in seconds. */
109 double up_time_d
= difftime(tval
.tv_sec
, st
->start_time
);
110 json_object_set_new(js_stats
, "uptime",
111 json_integer((int)up_time_d
));
114 if (flags
& JSON_STATS_TOTALS
) {
115 for (u
= 0; u
< st
->nstats
; u
++) {
116 if (st
->stats
[u
].name
== NULL
)
118 const char *name
= st
->stats
[u
].name
;
119 const char *shortname
= name
;
120 if (rindex(name
, '.') != NULL
) {
121 shortname
= &name
[rindex(name
, '.') - name
+ 1];
123 json_t
*js_type
= OutputStats2Json(js_stats
, name
);
124 if (js_type
!= NULL
) {
125 json_object_set_new(js_type
, shortname
,
126 json_integer(st
->stats
[u
].value
));
128 if (flags
& JSON_STATS_DELTAS
) {
129 char deltaname
[strlen(shortname
) + strlen(delta_suffix
) + 1];
130 snprintf(deltaname
, sizeof(deltaname
), "%s%s", shortname
,
132 json_object_set_new(js_type
, deltaname
,
133 json_integer(st
->stats
[u
].value
- st
->stats
[u
].pvalue
));
139 /* per thread stats - stored in a "threads" object. */
140 if (st
->tstats
!= NULL
&& (flags
& JSON_STATS_THREADS
)) {
141 /* for each thread (store) */
142 json_t
*threads
= json_object();
143 if (unlikely(threads
== NULL
)) {
144 json_decref(js_stats
);
148 for (x
= 0; x
< st
->ntstats
; x
++) {
149 uint32_t offset
= x
* st
->nstats
;
151 /* for each counter */
152 for (u
= offset
; u
< (offset
+ st
->nstats
); u
++) {
153 if (st
->tstats
[u
].name
== NULL
)
157 snprintf(str
, sizeof(str
), "%s.%s", st
->tstats
[u
].tm_name
, st
->tstats
[u
].name
);
158 char *shortname
= &str
[rindex(str
, '.') - str
+ 1];
159 json_t
*js_type
= OutputStats2Json(threads
, str
);
161 if (js_type
!= NULL
) {
162 json_object_set_new(js_type
, shortname
, json_integer(st
->tstats
[u
].value
));
164 if (flags
& JSON_STATS_DELTAS
) {
165 char deltaname
[strlen(shortname
) + strlen(delta_suffix
) + 1];
166 snprintf(deltaname
, sizeof(deltaname
), "%s%s",
167 shortname
, delta_suffix
);
168 json_object_set_new(js_type
, deltaname
,
169 json_integer(st
->tstats
[u
].value
- st
->tstats
[u
].pvalue
));
174 json_object_set_new(js_stats
, "threads", threads
);
179 static int JsonStatsLogger(ThreadVars
*tv
, void *thread_data
, const StatsTable
*st
)
182 JsonStatsLogThread
*aft
= (JsonStatsLogThread
*)thread_data
;
185 gettimeofday(&tval
, NULL
);
187 json_t
*js
= json_object();
188 if (unlikely(js
== NULL
))
191 CreateIsoTimeString(&tval
, timebuf
, sizeof(timebuf
));
192 json_object_set_new(js
, "timestamp", json_string(timebuf
));
193 json_object_set_new(js
, "event_type", json_string("stats"));
195 json_t
*js_stats
= StatsToJSON(st
, aft
->statslog_ctx
->flags
);
196 if (js_stats
== NULL
) {
201 json_object_set_new(js
, "stats", js_stats
);
203 OutputJSONBuffer(js
, aft
->statslog_ctx
->file_ctx
, &aft
->buffer
);
204 MemBufferReset(aft
->buffer
);
206 json_object_clear(js_stats
);
207 json_object_del(js
, "stats");
208 json_object_clear(js
);
214 #define OUTPUT_BUFFER_SIZE 65535
215 static TmEcode
JsonStatsLogThreadInit(ThreadVars
*t
, const void *initdata
, void **data
)
217 JsonStatsLogThread
*aft
= SCMalloc(sizeof(JsonStatsLogThread
));
218 if (unlikely(aft
== NULL
))
219 return TM_ECODE_FAILED
;
220 memset(aft
, 0, sizeof(JsonStatsLogThread
));
224 SCLogDebug("Error getting context for EveLogStats. \"initdata\" argument NULL");
226 return TM_ECODE_FAILED
;
229 /* Use the Ouptut Context (file pointer and mutex) */
230 aft
->statslog_ctx
= ((OutputCtx
*)initdata
)->data
;
232 aft
->buffer
= MemBufferCreateNew(OUTPUT_BUFFER_SIZE
);
233 if (aft
->buffer
== NULL
) {
235 return TM_ECODE_FAILED
;
242 static TmEcode
JsonStatsLogThreadDeinit(ThreadVars
*t
, void *data
)
244 JsonStatsLogThread
*aft
= (JsonStatsLogThread
*)data
;
249 MemBufferFree(aft
->buffer
);
252 memset(aft
, 0, sizeof(JsonStatsLogThread
));
258 static void OutputStatsLogDeinit(OutputCtx
*output_ctx
)
261 OutputStatsCtx
*stats_ctx
= output_ctx
->data
;
262 LogFileCtx
*logfile_ctx
= stats_ctx
->file_ctx
;
263 LogFileFreeCtx(logfile_ctx
);
268 #define DEFAULT_LOG_FILENAME "stats.json"
269 static OutputCtx
*OutputStatsLogInit(ConfNode
*conf
)
271 LogFileCtx
*file_ctx
= LogFileNewCtx();
272 if(file_ctx
== NULL
) {
273 SCLogError(SC_ERR_STATS_LOG_GENERIC
, "couldn't create new file_ctx");
277 if (SCConfLogOpenGeneric(conf
, file_ctx
, DEFAULT_LOG_FILENAME
, 1) < 0) {
278 LogFileFreeCtx(file_ctx
);
282 OutputStatsCtx
*stats_ctx
= SCMalloc(sizeof(OutputStatsCtx
));
283 if (unlikely(stats_ctx
== NULL
)) {
284 LogFileFreeCtx(file_ctx
);
287 stats_ctx
->flags
= JSON_STATS_TOTALS
;
290 const char *totals
= ConfNodeLookupChildValue(conf
, "totals");
291 const char *threads
= ConfNodeLookupChildValue(conf
, "threads");
292 const char *deltas
= ConfNodeLookupChildValue(conf
, "deltas");
293 SCLogDebug("totals %s threads %s deltas %s", totals
, threads
, deltas
);
295 if (totals
!= NULL
&& ConfValIsFalse(totals
)) {
296 stats_ctx
->flags
&= ~JSON_STATS_TOTALS
;
298 if (threads
!= NULL
&& ConfValIsTrue(threads
)) {
299 stats_ctx
->flags
|= JSON_STATS_THREADS
;
301 if (deltas
!= NULL
&& ConfValIsTrue(deltas
)) {
302 stats_ctx
->flags
|= JSON_STATS_DELTAS
;
304 SCLogDebug("stats_ctx->flags %08x", stats_ctx
->flags
);
307 OutputCtx
*output_ctx
= SCCalloc(1, sizeof(OutputCtx
));
308 if (unlikely(output_ctx
== NULL
)) {
309 LogFileFreeCtx(file_ctx
);
314 stats_ctx
->file_ctx
= file_ctx
;
316 output_ctx
->data
= stats_ctx
;
317 output_ctx
->DeInit
= OutputStatsLogDeinit
;
322 static void OutputStatsLogDeinitSub(OutputCtx
*output_ctx
)
324 OutputStatsCtx
*stats_ctx
= output_ctx
->data
;
329 static OutputCtx
*OutputStatsLogInitSub(ConfNode
*conf
, OutputCtx
*parent_ctx
)
331 AlertJsonThread
*ajt
= parent_ctx
->data
;
333 OutputStatsCtx
*stats_ctx
= SCMalloc(sizeof(OutputStatsCtx
));
334 if (unlikely(stats_ctx
== NULL
))
337 stats_ctx
->flags
= JSON_STATS_TOTALS
;
340 const char *totals
= ConfNodeLookupChildValue(conf
, "totals");
341 const char *threads
= ConfNodeLookupChildValue(conf
, "threads");
342 const char *deltas
= ConfNodeLookupChildValue(conf
, "deltas");
343 SCLogDebug("totals %s threads %s deltas %s", totals
, threads
, deltas
);
345 if ((totals
!= NULL
&& ConfValIsFalse(totals
)) &&
346 (threads
!= NULL
&& ConfValIsFalse(threads
))) {
348 SCLogError(SC_ERR_JSON_STATS_LOG_NEGATED
,
349 "Cannot disable both totals and threads in stats logging");
353 if (totals
!= NULL
&& ConfValIsFalse(totals
)) {
354 stats_ctx
->flags
&= ~JSON_STATS_TOTALS
;
356 if (threads
!= NULL
&& ConfValIsTrue(threads
)) {
357 stats_ctx
->flags
|= JSON_STATS_THREADS
;
359 if (deltas
!= NULL
&& ConfValIsTrue(deltas
)) {
360 stats_ctx
->flags
|= JSON_STATS_DELTAS
;
362 SCLogDebug("stats_ctx->flags %08x", stats_ctx
->flags
);
365 OutputCtx
*output_ctx
= SCCalloc(1, sizeof(OutputCtx
));
366 if (unlikely(output_ctx
== NULL
)) {
371 stats_ctx
->file_ctx
= ajt
->file_ctx
;
373 output_ctx
->data
= stats_ctx
;
374 output_ctx
->DeInit
= OutputStatsLogDeinitSub
;
379 void JsonStatsLogRegister(void) {
380 /* register as separate module */
381 OutputRegisterStatsModule(LOGGER_JSON_STATS
, MODULE_NAME
, "stats-json",
382 OutputStatsLogInit
, JsonStatsLogger
, JsonStatsLogThreadInit
,
383 JsonStatsLogThreadDeinit
, NULL
);
385 /* also register as child of eve-log */
386 OutputRegisterStatsSubModule(LOGGER_JSON_STATS
, "eve-log", MODULE_NAME
,
387 "eve-log.stats", OutputStatsLogInitSub
, JsonStatsLogger
,
388 JsonStatsLogThreadInit
, JsonStatsLogThreadDeinit
, NULL
);
393 void JsonStatsLogRegister (void)