Coverage Report

Created: 2026-08-14 07:34

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata7/src/log-stats.c
Line
Count
Source
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 Victor Julien <victor@inliniac.net>
22
 *
23
 */
24
25
#include "suricata-common.h"
26
#include "detect.h"
27
#include "pkt-var.h"
28
#include "conf.h"
29
30
#include "threads.h"
31
#include "threadvars.h"
32
#include "tm-threads.h"
33
34
#include "util-print.h"
35
#include "util-unittest.h"
36
37
#include "util-debug.h"
38
39
#include "output.h"
40
#include "log-stats.h"
41
#include "util-privs.h"
42
#include "util-buffer.h"
43
44
#include "util-logopenfile.h"
45
#include "util-time.h"
46
47
0
#define DEFAULT_LOG_FILENAME "stats.log"
48
73
#define MODULE_NAME "LogStatsLog"
49
0
#define OUTPUT_BUFFER_SIZE 16384
50
51
0
#define LOG_STATS_TOTALS  (1<<0)
52
0
#define LOG_STATS_THREADS (1<<1)
53
0
#define LOG_STATS_NULLS   (1<<2)
54
55
TmEcode LogStatsLogThreadInit(ThreadVars *, const void *, void **);
56
TmEcode LogStatsLogThreadDeinit(ThreadVars *, void *);
57
static void LogStatsLogDeInitCtx(OutputCtx *);
58
59
typedef struct LogStatsFileCtx_ {
60
    LogFileCtx *file_ctx;
61
    uint32_t flags; /** Store mode */
62
} LogStatsFileCtx;
63
64
typedef struct LogStatsLogThread_ {
65
    LogStatsFileCtx *statslog_ctx;
66
    MemBuffer *buffer;
67
} LogStatsLogThread;
68
69
static int LogStatsLogger(ThreadVars *tv, void *thread_data, const StatsTable *st)
70
0
{
71
0
    SCEnter();
72
0
    LogStatsLogThread *aft = (LogStatsLogThread *)thread_data;
73
74
0
    struct timeval tval;
75
0
    struct tm *tms;
76
77
0
    gettimeofday(&tval, NULL);
78
0
    struct tm local_tm;
79
0
    tms = SCLocalTime(tval.tv_sec, &local_tm);
80
81
    /* Calculate the Engine uptime */
82
0
    double up_time_d = difftime(tval.tv_sec, st->start_time);
83
0
    int up_time = (int)up_time_d; // ignoring risk of overflow here
84
0
    int sec = up_time % 60;     // Seconds in a minute
85
0
    int in_min = up_time / 60;
86
0
    int min = in_min % 60;      // Minutes in a hour
87
0
    int in_hours = in_min / 60;
88
0
    int hours = in_hours % 24;  // Hours in a day
89
0
    int days = in_hours / 24;
90
91
0
    MemBufferWriteString(aft->buffer, "----------------------------------------------"
92
0
                                      "-----------------------------------------------------\n");
93
0
    MemBufferWriteString(aft->buffer, "Date: %" PRId32 "/%" PRId32 "/%04d -- "
94
0
            "%02d:%02d:%02d (uptime: %"PRId32"d, %02dh %02dm %02ds)\n",
95
0
            tms->tm_mon + 1, tms->tm_mday, tms->tm_year + 1900, tms->tm_hour,
96
0
            tms->tm_min, tms->tm_sec, days, hours, min, sec);
97
0
    MemBufferWriteString(aft->buffer, "----------------------------------------------"
98
0
                                      "-----------------------------------------------------\n");
99
0
    MemBufferWriteString(aft->buffer, "%-60s | %-25s | %-s\n", "Counter", "TM Name", "Value");
100
0
    MemBufferWriteString(aft->buffer, "----------------------------------------------"
101
0
                                      "-----------------------------------------------------\n");
102
103
    /* global stats */
104
0
    uint32_t u = 0;
105
0
    if (aft->statslog_ctx->flags & LOG_STATS_TOTALS) {
106
0
        for (u = 0; u < st->nstats; u++) {
107
0
            if (st->stats[u].name == NULL)
108
0
                continue;
109
110
0
            if (!(aft->statslog_ctx->flags & LOG_STATS_NULLS) && st->stats[u].value == 0)
111
0
                continue;
112
113
0
            char line[256];
114
0
            size_t len = snprintf(line, sizeof(line), "%-60s | %-25s | %-" PRIu64 "\n",
115
0
                    st->stats[u].name, st->stats[u].tm_name, st->stats[u].value);
116
117
            /* since we can have many threads, the buffer might not be big enough.
118
             * Expand if necessary. */
119
0
            if (MEMBUFFER_OFFSET(aft->buffer) + len >= MEMBUFFER_SIZE(aft->buffer)) {
120
0
                MemBufferExpand(&aft->buffer, OUTPUT_BUFFER_SIZE);
121
0
            }
122
123
0
            MemBufferWriteString(aft->buffer, "%s", line);
124
0
        }
125
0
    }
126
127
    /* per thread stats */
128
0
    if (st->tstats != NULL && aft->statslog_ctx->flags & LOG_STATS_THREADS) {
129
        /* for each thread (store) */
130
0
        uint32_t x;
131
0
        for (x = 0; x < st->ntstats; x++) {
132
0
            uint32_t offset = x * st->nstats;
133
134
            /* for each counter */
135
0
            for (u = offset; u < (offset + st->nstats); u++) {
136
0
                if (st->tstats[u].name == NULL)
137
0
                    continue;
138
139
0
                if (!(aft->statslog_ctx->flags & LOG_STATS_NULLS) && st->tstats[u].value == 0)
140
0
                    continue;
141
142
0
                char line[256];
143
0
                size_t len = snprintf(line, sizeof(line), "%-45s | %-25s | %-" PRIi64 "\n",
144
0
                        st->tstats[u].name, st->tstats[u].tm_name, st->tstats[u].value);
145
146
                /* since we can have many threads, the buffer might not be big enough.
147
                 * Expand if necessary. */
148
0
                if (MEMBUFFER_OFFSET(aft->buffer) + len >= MEMBUFFER_SIZE(aft->buffer)) {
149
0
                    MemBufferExpand(&aft->buffer, OUTPUT_BUFFER_SIZE);
150
0
                }
151
152
0
                MemBufferWriteString(aft->buffer, "%s", line);
153
0
            }
154
0
        }
155
0
    }
156
157
0
    aft->statslog_ctx->file_ctx->Write((const char *)MEMBUFFER_BUFFER(aft->buffer),
158
0
        MEMBUFFER_OFFSET(aft->buffer), aft->statslog_ctx->file_ctx);
159
160
0
    MemBufferReset(aft->buffer);
161
162
0
    SCReturnInt(0);
163
0
}
164
165
TmEcode LogStatsLogThreadInit(ThreadVars *t, const void *initdata, void **data)
166
0
{
167
0
    LogStatsLogThread *aft = SCMalloc(sizeof(LogStatsLogThread));
168
0
    if (unlikely(aft == NULL))
169
0
        return TM_ECODE_FAILED;
170
0
    memset(aft, 0, sizeof(LogStatsLogThread));
171
172
0
    if(initdata == NULL)
173
0
    {
174
0
        SCLogDebug("Error getting context for LogStats.  \"initdata\" argument NULL");
175
0
        SCFree(aft);
176
0
        return TM_ECODE_FAILED;
177
0
    }
178
179
0
    aft->buffer = MemBufferCreateNew(OUTPUT_BUFFER_SIZE);
180
0
    if (aft->buffer == NULL) {
181
0
        SCFree(aft);
182
0
        return TM_ECODE_FAILED;
183
0
    }
184
185
    /* Use the Output Context (file pointer and mutex) */
186
0
    aft->statslog_ctx= ((OutputCtx *)initdata)->data;
187
188
0
    *data = (void *)aft;
189
0
    return TM_ECODE_OK;
190
0
}
191
192
TmEcode LogStatsLogThreadDeinit(ThreadVars *t, void *data)
193
0
{
194
0
    LogStatsLogThread *aft = (LogStatsLogThread *)data;
195
0
    if (aft == NULL) {
196
0
        return TM_ECODE_OK;
197
0
    }
198
199
0
    MemBufferFree(aft->buffer);
200
    /* clear memory */
201
0
    memset(aft, 0, sizeof(LogStatsLogThread));
202
203
0
    SCFree(aft);
204
0
    return TM_ECODE_OK;
205
0
}
206
207
/** \brief Create a new http log LogFileCtx.
208
 *  \param conf Pointer to ConfNode containing this loggers configuration.
209
 *  \return NULL if failure, LogFileCtx* to the file_ctx if successful
210
 * */
211
static OutputInitResult LogStatsLogInitCtx(ConfNode *conf)
212
0
{
213
0
    OutputInitResult result = { NULL, false };
214
0
    LogFileCtx *file_ctx = LogFileNewCtx();
215
0
    if (file_ctx == NULL) {
216
0
        SCLogError("couldn't create new file_ctx");
217
0
        return result;
218
0
    }
219
220
0
    if (SCConfLogOpenGeneric(conf, file_ctx, DEFAULT_LOG_FILENAME, 1) < 0) {
221
0
        LogFileFreeCtx(file_ctx);
222
0
        return result;
223
0
    }
224
225
0
    LogStatsFileCtx *statslog_ctx = SCMalloc(sizeof(LogStatsFileCtx));
226
0
    if (unlikely(statslog_ctx == NULL)) {
227
0
        LogFileFreeCtx(file_ctx);
228
0
        return result;
229
0
    }
230
0
    memset(statslog_ctx, 0x00, sizeof(LogStatsFileCtx));
231
232
0
    statslog_ctx->flags = LOG_STATS_TOTALS;
233
234
0
    if (conf != NULL) {
235
0
        const char *totals = ConfNodeLookupChildValue(conf, "totals");
236
0
        const char *threads = ConfNodeLookupChildValue(conf, "threads");
237
0
        const char *nulls = ConfNodeLookupChildValue(conf, "null-values");
238
0
        SCLogDebug("totals %s threads %s", totals, threads);
239
240
0
        if ((totals != NULL && ConfValIsFalse(totals)) &&
241
0
                (threads != NULL && ConfValIsFalse(threads))) {
242
0
            LogFileFreeCtx(file_ctx);
243
0
            SCFree(statslog_ctx);
244
0
            SCLogError("Cannot disable both totals and threads in stats logging");
245
0
            return result;
246
0
        }
247
248
0
        if (totals != NULL && ConfValIsFalse(totals)) {
249
0
            statslog_ctx->flags &= ~LOG_STATS_TOTALS;
250
0
        }
251
0
        if (threads != NULL && ConfValIsTrue(threads)) {
252
0
            statslog_ctx->flags |= LOG_STATS_THREADS;
253
0
        }
254
0
        if (nulls != NULL && ConfValIsTrue(nulls)) {
255
0
            statslog_ctx->flags |= LOG_STATS_NULLS;
256
0
        }
257
0
        SCLogDebug("statslog_ctx->flags %08x", statslog_ctx->flags);
258
0
    }
259
260
0
    statslog_ctx->file_ctx = file_ctx;
261
262
0
    OutputCtx *output_ctx = SCCalloc(1, sizeof(OutputCtx));
263
0
    if (unlikely(output_ctx == NULL)) {
264
0
        LogFileFreeCtx(file_ctx);
265
0
        SCFree(statslog_ctx);
266
0
        return result;
267
0
    }
268
269
0
    output_ctx->data = statslog_ctx;
270
0
    output_ctx->DeInit = LogStatsLogDeInitCtx;
271
272
0
    SCLogDebug("STATS log output initialized");
273
274
0
    result.ctx = output_ctx;
275
0
    result.ok = true;
276
0
    return result;
277
0
}
278
279
static void LogStatsLogDeInitCtx(OutputCtx *output_ctx)
280
0
{
281
0
    LogStatsFileCtx *statslog_ctx = (LogStatsFileCtx *)output_ctx->data;
282
0
    LogFileFreeCtx(statslog_ctx->file_ctx);
283
0
    SCFree(statslog_ctx);
284
0
    SCFree(output_ctx);
285
0
}
286
287
void LogStatsLogRegister (void)
288
73
{
289
73
    OutputRegisterStatsModule(LOGGER_STATS, MODULE_NAME, "stats",
290
73
        LogStatsLogInitCtx, LogStatsLogger, LogStatsLogThreadInit,
291
        LogStatsLogThreadDeinit, NULL);
292
73
}