suricata
util-profiling-rulegroups.c
Go to the documentation of this file.
1/* Copyright (C) 2007-2025 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 Endace Technology Limited.
22 * \author Victor Julien <victor@inliniac.net>
23 *
24 * An API for rule profiling operations.
25 */
26
27#include "suricata-common.h"
28#include "util-profiling.h"
29
30#ifdef PROFILING
31#include "util-conf.h"
32#include "util-path.h"
33#include "util-time.h"
34
35/**
36 * Extra data for rule profiling.
37 */
48
54
55static int profiling_sghs_output_to_file = 0;
57static char profiling_file_name[PATH_MAX];
58static const char *profiling_file_mode = "a";
59static int profiling_rulegroup_json = 0;
60
62{
63 SCConfNode *conf;
64
65 conf = SCConfGetNode("profiling.rulegroups");
66 if (conf != NULL) {
67 if (SCConfNodeChildValueIsTrue(conf, "enabled")) {
69 const char *filename = SCConfNodeLookupChildValue(conf, "filename");
70 if (filename != NULL) {
71 if (PathIsAbsolute(filename)) {
72 strlcpy(profiling_file_name, filename, sizeof(profiling_file_name));
73 } else {
74 const char *log_dir = SCConfigGetLogDirectory();
75 snprintf(profiling_file_name, sizeof(profiling_file_name), "%s/%s", log_dir,
76 filename);
77 }
78
79 const char *v = SCConfNodeLookupChildValue(conf, "append");
80 if (v == NULL || SCConfValIsTrue(v)) {
81 profiling_file_mode = "a";
82 } else {
83 profiling_file_mode = "w";
84 }
85
86 profiling_sghs_output_to_file = 1;
87 }
88 if (SCConfNodeChildValueIsTrue(conf, "json")) {
89 profiling_rulegroup_json = 1;
90 }
91 }
92 }
93}
94
95static void DoDumpJSON(SCProfileSghDetectCtx *rules_ctx, FILE *fp, const char *name)
96{
97 char timebuf[64];
98 uint32_t i;
99 struct timeval tval;
100
101 json_t *js = json_object();
102 if (js == NULL)
103 return;
104 json_t *jsa = json_array();
105 if (jsa == NULL) {
106 json_decref(js);
107 return;
108 }
109
110 gettimeofday(&tval, NULL);
111 CreateIsoTimeString(SCTIME_FROM_TIMEVAL(&tval), timebuf, sizeof(timebuf));
112 json_object_set_new(js, "timestamp", json_string(timebuf));
113
114 for (i = 0; i < rules_ctx->cnt; i++) {
115 SCProfileSghData *d = &rules_ctx->data[i];
116 if (d == NULL || d->checks == 0)
117 continue;
118
119 double avgsigs = 0;
120 double avgmpms = 0;
121
122 if (d->post_prefilter_sigs_total && d->checks) {
123 avgsigs = (double)((double)d->post_prefilter_sigs_total / (double)d->checks);
124 }
125 if (d->mpm_match_cnt_total && d->checks) {
126 avgmpms = (double)((double)d->mpm_match_cnt_total / (double)d->checks);
127 }
128
129 json_t *jsm = json_object();
130 if (jsm) {
131 json_object_set_new(jsm, "id", json_integer(i));
132 json_object_set_new(jsm, "checks", json_integer(d->checks));
133 json_object_set_new(jsm, "avgmpms", json_real(avgmpms));
134 json_object_set_new(jsm, "mpm_match_cnt_max", json_integer(d->mpm_match_cnt_max));
135 json_object_set_new(jsm, "avgsigs", json_real(avgsigs));
136 json_object_set_new(jsm, "post_prefilter_sigs_max", json_integer(d->post_prefilter_sigs_max));
137 json_array_append_new(jsa, jsm);
138 }
139 }
140 json_object_set_new(js, "rule_groups", jsa);
141
142 char *js_s = json_dumps(js,
143 JSON_PRESERVE_ORDER|JSON_COMPACT|JSON_ENSURE_ASCII|
145 if (likely(js_s != NULL)) {
146 fprintf(fp, "%s", js_s);
147 free(js_s);
148 }
149 json_decref(js);
150}
151
152static void DoDump(SCProfileSghDetectCtx *rules_ctx, FILE *fp, const char *name)
153{
154 uint32_t i;
155 struct timeval tval;
156 struct tm *tms;
157 struct tm local_tm;
158
159 gettimeofday(&tval, NULL);
160 tms = SCLocalTime(tval.tv_sec, &local_tm);
161
162 fprintf(fp, " ----------------------------------------------"
163 "------------------------------------------------------"
164 "----------------------------\n");
165 fprintf(fp, " Date: %" PRId32 "/%" PRId32 "/%04d -- "
166 "%02d:%02d:%02d\n", tms->tm_mon + 1, tms->tm_mday, tms->tm_year + 1900,
167 tms->tm_hour,tms->tm_min, tms->tm_sec);
168
169 fprintf(fp, " ----------------------------------------------"
170 "------------------------------------------------------"
171 "----------------------------\n");
172 fprintf(fp, " Stats for: %s %u\n", name, rules_ctx->cnt);
173 fprintf(fp, " ----------------------------------------------"
174 "------------------------------------------------------"
175 "----------------------------\n");
176 fprintf(fp, " %-16s %-15s %-15s %-15s %-15s %-15s %-15s %-15s\n", "Sgh", "Checks", "Non-MPM(gen)", "Non-Mpm(syn)", "MPM Matches", "MPM Match Max", "Post-Filter", "Post-Filter Max");
177 fprintf(fp, " ---------------- "
178 "--------------- "
179 "--------------- "
180 "--------------- "
181 "--------------- "
182 "--------------- "
183 "--------------- "
184 "--------------- "
185 "\n");
186 for (i = 0; i < rules_ctx->cnt; i++) {
187 SCProfileSghData *d = &rules_ctx->data[i];
188 if (d == NULL || d->checks == 0)
189 continue;
190
191 double avgsigs = 0;
192 double avgmpms = 0;
193
194 if (d->post_prefilter_sigs_total && d->checks) {
195 avgsigs = (double)((double)d->post_prefilter_sigs_total / (double)d->checks);
196 }
197 if (d->mpm_match_cnt_total && d->checks) {
198 avgmpms = (double)((double)d->mpm_match_cnt_total / (double)d->checks);
199 }
200
201 fprintf(fp, " %-16u %-15" PRIu64 " %-15.2f %-15" PRIu64 " %-15.2f %-15" PRIu64 "\n", i,
202 d->checks, avgmpms, d->mpm_match_cnt_max, avgsigs, d->post_prefilter_sigs_max);
203 }
204 fprintf(fp,"\n");
205}
206
207static void
208SCProfilingSghDump(DetectEngineCtx *de_ctx)
209{
210 FILE *fp;
211
212 if (profiling_sghs_enabled == 0)
213 return;
214
215 if (profiling_sghs_output_to_file == 1) {
216 SCLogDebug("file %s mode %s", profiling_file_name, profiling_file_mode);
217
218 fp = fopen(profiling_file_name, profiling_file_mode);
219
220 if (fp == NULL) {
221 SCLogError("failed to open %s: %s", profiling_file_name, strerror(errno));
222 return;
223 }
224 } else {
225 fp = stdout;
226 }
227
228 if (profiling_rulegroup_json) {
229 DoDumpJSON(de_ctx->profile_sgh_ctx, fp, "rule groups");
230 } else {
231 DoDump(de_ctx->profile_sgh_ctx, fp, "rule groups");
232 }
233
234 if (fp != stdout)
235 fclose(fp);
236
237 SCLogPerf("Done dumping rulegroup profiling data.");
238}
239
240/**
241 * \brief Update a rule counter.
242 *
243 * \param id The ID of this counter.
244 * \param ticks Number of CPU ticks for this rule.
245 * \param match Did the rule match?
246 */
247void
249{
250 if (det_ctx != NULL && det_ctx->sgh_perf_data != NULL && sgh->id < det_ctx->de_ctx->sgh_array_cnt) {
251 SCProfileSghData *p = &det_ctx->sgh_perf_data[sgh->id];
252 p->checks++;
253
258 if (det_ctx->pmq.rule_id_array_cnt > p->mpm_match_cnt_max)
260 }
261}
262
263static SCProfileSghDetectCtx *SCProfilingSghInitCtx(void)
264{
266 if (ctx != NULL) {
267 if (pthread_mutex_init(&ctx->data_m, NULL) != 0) {
268 FatalError("Failed to initialize mutex.");
269 }
270 }
271
272 return ctx;
273}
274
275static void DetroyCtx(SCProfileSghDetectCtx *ctx)
276{
277 if (ctx) {
278 if (ctx->data != NULL)
279 SCFree(ctx->data);
280 pthread_mutex_destroy(&ctx->data_m);
281 SCFree(ctx);
282 }
283}
284
286{
287 if (de_ctx != NULL) {
288 SCProfilingSghDump(de_ctx);
289
290 DetroyCtx(de_ctx->profile_sgh_ctx);
291 }
292}
293
295{
296 if (ctx == NULL)
297 return;
298
299 uint32_t array_size = det_ctx->de_ctx->sgh_array_cnt;
300
301 SCProfileSghData *a = SCCalloc(array_size, sizeof(SCProfileSghData));
302 if (a != NULL) {
303 det_ctx->sgh_perf_data = a;
304 }
305}
306
307static void SCProfilingSghThreadMerge(DetectEngineCtx *de_ctx, const DetectEngineThreadCtx *det_ctx)
308{
309 if (de_ctx == NULL || de_ctx->profile_sgh_ctx == NULL ||
310 de_ctx->profile_sgh_ctx->data == NULL || det_ctx == NULL ||
311 det_ctx->sgh_perf_data == NULL)
312 return;
313
314#define ADD(name) de_ctx->profile_sgh_ctx->data[i].name += det_ctx->sgh_perf_data[i].name
315 uint32_t i;
316 for (i = 0; i < de_ctx->sgh_array_cnt; i++) {
317 ADD(checks);
318 ADD(post_prefilter_sigs_total);
319 ADD(mpm_match_cnt_total);
320
325 }
326#undef ADD
327}
328
330{
331 if (det_ctx == NULL || det_ctx->de_ctx == NULL || det_ctx->sgh_perf_data == NULL)
332 return;
333
334 pthread_mutex_lock(&det_ctx->de_ctx->profile_sgh_ctx->data_m);
335 SCProfilingSghThreadMerge(det_ctx->de_ctx, det_ctx);
336 pthread_mutex_unlock(&det_ctx->de_ctx->profile_sgh_ctx->data_m);
337
338 SCFree(det_ctx->sgh_perf_data);
339 det_ctx->sgh_perf_data = NULL;
340}
341
342/**
343 * \brief Register the keyword profiling counters.
344 *
345 * \param de_ctx The active DetectEngineCtx, used to get at the loaded rules.
346 */
347void
349{
350 if (profiling_sghs_enabled == 0)
351 return;
352
353 de_ctx->profile_sgh_ctx = SCProfilingSghInitCtx();
354 BUG_ON(de_ctx->profile_sgh_ctx == NULL);
355
358
360
361 SCLogPerf("Registered %"PRIu32" rulegroup profiling counters.", de_ctx->sgh_array_cnt);
362}
363
364#endif /* PROFILING */
SCConfNode * SCConfGetNode(const char *name)
Get a SCConfNode by name.
Definition conf.c:181
int SCConfNodeChildValueIsTrue(const SCConfNode *node, const char *key)
Test if a configuration node has a true value.
Definition conf.c:868
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
DetectEngineCtx * de_ctx
struct Thresholds ctx
main detection engine ctx
Definition detect.h:932
uint32_t sgh_array_cnt
Definition detect.h:1001
struct SCProfileSghDetectCtx_ * profile_sgh_ctx
Definition detect.h:1048
struct SCProfileSghData_ * sgh_perf_data
Definition detect.h:1407
SigIntId match_array_cnt
Definition detect.h:1340
DetectEngineCtx * de_ctx
Definition detect.h:1364
PrefilterRuleStore pmq
Definition detect.h:1349
Container for matching data for a signature group.
Definition detect.h:1629
uint32_t id
Definition detect.h:1637
#define BUG_ON(x)
#define JSON_ESCAPE_SLASH
size_t strlcpy(char *dst, const char *src, size_t siz)
const char * name
const char * SCConfigGetLogDirectory(void)
Definition util-conf.c:38
#define FatalError(...)
Definition util-debug.h:510
#define SCLogPerf(...)
Definition util-debug.h:234
#define SCLogDebug(...)
Definition util-debug.h:275
#define SCLogError(...)
Macro used to log ERROR messages.
Definition util-debug.h:267
#define SCFree(p)
Definition util-mem.h:61
#define SCCalloc(nm, sz)
Definition util-mem.h:53
#define likely(expr)
int PathIsAbsolute(const char *path)
Check if a path is absolute.
Definition util-path.c:44
int profiling_sghs_enabled
void SCProfilingSghUpdateCounter(DetectEngineThreadCtx *det_ctx, const SigGroupHead *sgh)
Update a rule counter.
#define ADD(name)
void SCProfilingSghInitCounters(DetectEngineCtx *de_ctx)
Register the keyword profiling counters.
void SCProfilingSghThreadCleanup(DetectEngineThreadCtx *det_ctx)
void SCProfilingSghsGlobalInit(void)
void SCProfilingSghThreadSetup(SCProfileSghDetectCtx *ctx, DetectEngineThreadCtx *det_ctx)
struct SCProfileSghData_ SCProfileSghData
void SCProfilingSghDestroyCtx(DetectEngineCtx *de_ctx)
struct SCProfileSghDetectCtx_ SCProfileSghDetectCtx
struct tm * SCLocalTime(time_t timep, struct tm *result)
Definition util-time.c:267
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