/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 | } |