Coverage Report

Created: 2025-11-16 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata/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
71
#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 = SCCalloc(1, sizeof(LogStatsLogThread));
168
0
    if (unlikely(aft == NULL))
169
0
        return TM_ECODE_FAILED;
170
171
0
    if(initdata == NULL)
172
0
    {
173
0
        SCLogDebug("Error getting context for LogStats.  \"initdata\" argument NULL");
174
0
        SCFree(aft);
175
0
        return TM_ECODE_FAILED;
176
0
    }
177
178
0
    aft->buffer = MemBufferCreateNew(OUTPUT_BUFFER_SIZE);
179
0
    if (aft->buffer == NULL) {
180
0
        SCFree(aft);
181
0
        return TM_ECODE_FAILED;
182
0
    }
183
184
    /* Use the Output Context (file pointer and mutex) */
185
0
    aft->statslog_ctx= ((OutputCtx *)initdata)->data;
186
187
0
    *data = (void *)aft;
188
0
    return TM_ECODE_OK;
189
0
}
190
191
TmEcode LogStatsLogThreadDeinit(ThreadVars *t, void *data)
192
0
{
193
0
    LogStatsLogThread *aft = (LogStatsLogThread *)data;
194
0
    if (aft == NULL) {
195
0
        return TM_ECODE_OK;
196
0
    }
197
198
0
    MemBufferFree(aft->buffer);
199
    /* clear memory */
200
0
    memset(aft, 0, sizeof(LogStatsLogThread));
201
202
0
    SCFree(aft);
203
0
    return TM_ECODE_OK;
204
0
}
205
206
/** \brief Create a new http log LogFileCtx.
207
 *  \param conf Pointer to ConfNode containing this loggers configuration.
208
 *  \return NULL if failure, LogFileCtx* to the file_ctx if successful
209
 * */
210
static OutputInitResult LogStatsLogInitCtx(SCConfNode *conf)
211
0
{
212
0
    OutputInitResult result = { NULL, false };
213
0
    LogFileCtx *file_ctx = LogFileNewCtx();
214
0
    if (file_ctx == NULL) {
215
0
        SCLogError("couldn't create new file_ctx");
216
0
        return result;
217
0
    }
218
219
0
    if (SCConfLogOpenGeneric(conf, file_ctx, DEFAULT_LOG_FILENAME, 1) < 0) {
220
0
        LogFileFreeCtx(file_ctx);
221
0
        return result;
222
0
    }
223
224
0
    LogStatsFileCtx *statslog_ctx = SCCalloc(1, sizeof(LogStatsFileCtx));
225
0
    if (unlikely(statslog_ctx == NULL)) {
226
0
        LogFileFreeCtx(file_ctx);
227
0
        return result;
228
0
    }
229
230
0
    statslog_ctx->flags = LOG_STATS_TOTALS;
231
232
0
    if (conf != NULL) {
233
0
        const char *totals = SCConfNodeLookupChildValue(conf, "totals");
234
0
        const char *threads = SCConfNodeLookupChildValue(conf, "threads");
235
0
        const char *nulls = SCConfNodeLookupChildValue(conf, "null-values");
236
0
        SCLogDebug("totals %s threads %s", totals, threads);
237
238
0
        if ((totals != NULL && SCConfValIsFalse(totals)) &&
239
0
                (threads != NULL && SCConfValIsFalse(threads))) {
240
0
            LogFileFreeCtx(file_ctx);
241
0
            SCFree(statslog_ctx);
242
0
            SCLogError("Cannot disable both totals and threads in stats logging");
243
0
            return result;
244
0
        }
245
246
0
        if (totals != NULL && SCConfValIsFalse(totals)) {
247
0
            statslog_ctx->flags &= ~LOG_STATS_TOTALS;
248
0
        }
249
0
        if (threads != NULL && SCConfValIsTrue(threads)) {
250
0
            statslog_ctx->flags |= LOG_STATS_THREADS;
251
0
        }
252
0
        if (nulls != NULL && SCConfValIsTrue(nulls)) {
253
0
            statslog_ctx->flags |= LOG_STATS_NULLS;
254
0
        }
255
0
        SCLogDebug("statslog_ctx->flags %08x", statslog_ctx->flags);
256
0
    }
257
258
0
    statslog_ctx->file_ctx = file_ctx;
259
260
0
    OutputCtx *output_ctx = SCCalloc(1, sizeof(OutputCtx));
261
0
    if (unlikely(output_ctx == NULL)) {
262
0
        LogFileFreeCtx(file_ctx);
263
0
        SCFree(statslog_ctx);
264
0
        return result;
265
0
    }
266
267
0
    output_ctx->data = statslog_ctx;
268
0
    output_ctx->DeInit = LogStatsLogDeInitCtx;
269
270
0
    SCLogDebug("STATS log output initialized");
271
272
0
    result.ctx = output_ctx;
273
0
    result.ok = true;
274
0
    return result;
275
0
}
276
277
static void LogStatsLogDeInitCtx(OutputCtx *output_ctx)
278
0
{
279
0
    LogStatsFileCtx *statslog_ctx = (LogStatsFileCtx *)output_ctx->data;
280
0
    LogFileFreeCtx(statslog_ctx->file_ctx);
281
0
    SCFree(statslog_ctx);
282
0
    SCFree(output_ctx);
283
0
}
284
285
void LogStatsLogRegister (void)
286
71
{
287
71
    OutputRegisterStatsModule(LOGGER_STATS, MODULE_NAME, "stats", LogStatsLogInitCtx,
288
71
            LogStatsLogger, LogStatsLogThreadInit, LogStatsLogThreadDeinit);
289
71
}