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 
34 /**
35  * Extra data for rule profiling.
36  */
37 typedef struct SCProfileSghData_ {
38  uint64_t checks;
39 
42 
45 
47 
48 typedef struct SCProfileSghDetectCtx_ {
49  uint32_t cnt;
51  pthread_mutex_t data_m;
53 
54 static int profiling_sghs_output_to_file = 0;
56 static char profiling_file_name[PATH_MAX];
57 static const char *profiling_file_mode = "a";
58 static int profiling_rulegroup_json = 0;
59 
61 {
62  SCConfNode *conf;
63 
64  conf = SCConfGetNode("profiling.rulegroups");
65  if (conf != NULL) {
66  if (SCConfNodeChildValueIsTrue(conf, "enabled")) {
68  const char *filename = SCConfNodeLookupChildValue(conf, "filename");
69  if (filename != NULL) {
70  if (PathIsAbsolute(filename)) {
71  strlcpy(profiling_file_name, filename, sizeof(profiling_file_name));
72  } else {
73  const char *log_dir = SCConfigGetLogDirectory();
74  snprintf(profiling_file_name, sizeof(profiling_file_name), "%s/%s", log_dir,
75  filename);
76  }
77 
78  const char *v = SCConfNodeLookupChildValue(conf, "append");
79  if (v == NULL || SCConfValIsTrue(v)) {
80  profiling_file_mode = "a";
81  } else {
82  profiling_file_mode = "w";
83  }
84 
85  profiling_sghs_output_to_file = 1;
86  }
87  if (SCConfNodeChildValueIsTrue(conf, "json")) {
88  profiling_rulegroup_json = 1;
89  }
90  }
91  }
92 }
93 
94 static void DoDumpJSON(SCProfileSghDetectCtx *rules_ctx, FILE *fp, const char *name)
95 {
96  char timebuf[64];
97  uint32_t i;
98  struct timeval tval;
99 
100  json_t *js = json_object();
101  if (js == NULL)
102  return;
103  json_t *jsa = json_array();
104  if (jsa == NULL) {
105  json_decref(js);
106  return;
107  }
108 
109  gettimeofday(&tval, NULL);
110  CreateIsoTimeString(SCTIME_FROM_TIMEVAL(&tval), timebuf, sizeof(timebuf));
111  json_object_set_new(js, "timestamp", json_string(timebuf));
112 
113  for (i = 0; i < rules_ctx->cnt; i++) {
114  SCProfileSghData *d = &rules_ctx->data[i];
115  if (d == NULL || d->checks == 0)
116  continue;
117 
118  double avgsigs = 0;
119  double avgmpms = 0;
120 
121  if (d->post_prefilter_sigs_total && d->checks) {
122  avgsigs = (double)((double)d->post_prefilter_sigs_total / (double)d->checks);
123  }
124  if (d->mpm_match_cnt_total && d->checks) {
125  avgmpms = (double)((double)d->mpm_match_cnt_total / (double)d->checks);
126  }
127 
128  json_t *jsm = json_object();
129  if (jsm) {
130  json_object_set_new(jsm, "id", json_integer(i));
131  json_object_set_new(jsm, "checks", json_integer(d->checks));
132  json_object_set_new(jsm, "avgmpms", json_real(avgmpms));
133  json_object_set_new(jsm, "mpm_match_cnt_max", json_integer(d->mpm_match_cnt_max));
134  json_object_set_new(jsm, "avgsigs", json_real(avgsigs));
135  json_object_set_new(jsm, "post_prefilter_sigs_max", json_integer(d->post_prefilter_sigs_max));
136  json_array_append_new(jsa, jsm);
137  }
138  }
139  json_object_set_new(js, "rule_groups", jsa);
140 
141  char *js_s = json_dumps(js,
142  JSON_PRESERVE_ORDER|JSON_COMPACT|JSON_ENSURE_ASCII|
144  if (likely(js_s != NULL)) {
145  fprintf(fp, "%s", js_s);
146  free(js_s);
147  }
148  json_decref(js);
149 }
150 
151 static void DoDump(SCProfileSghDetectCtx *rules_ctx, FILE *fp, const char *name)
152 {
153  uint32_t i;
154  struct timeval tval;
155  struct tm *tms;
156  struct tm local_tm;
157 
158  gettimeofday(&tval, NULL);
159  tms = SCLocalTime(tval.tv_sec, &local_tm);
160 
161  fprintf(fp, " ----------------------------------------------"
162  "------------------------------------------------------"
163  "----------------------------\n");
164  fprintf(fp, " Date: %" PRId32 "/%" PRId32 "/%04d -- "
165  "%02d:%02d:%02d\n", tms->tm_mon + 1, tms->tm_mday, tms->tm_year + 1900,
166  tms->tm_hour,tms->tm_min, tms->tm_sec);
167 
168  fprintf(fp, " ----------------------------------------------"
169  "------------------------------------------------------"
170  "----------------------------\n");
171  fprintf(fp, " Stats for: %s %u\n", name, rules_ctx->cnt);
172  fprintf(fp, " ----------------------------------------------"
173  "------------------------------------------------------"
174  "----------------------------\n");
175  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");
176  fprintf(fp, " ---------------- "
177  "--------------- "
178  "--------------- "
179  "--------------- "
180  "--------------- "
181  "--------------- "
182  "--------------- "
183  "--------------- "
184  "\n");
185  for (i = 0; i < rules_ctx->cnt; i++) {
186  SCProfileSghData *d = &rules_ctx->data[i];
187  if (d == NULL || d->checks == 0)
188  continue;
189 
190  double avgsigs = 0;
191  double avgmpms = 0;
192 
193  if (d->post_prefilter_sigs_total && d->checks) {
194  avgsigs = (double)((double)d->post_prefilter_sigs_total / (double)d->checks);
195  }
196  if (d->mpm_match_cnt_total && d->checks) {
197  avgmpms = (double)((double)d->mpm_match_cnt_total / (double)d->checks);
198  }
199 
200  fprintf(fp, " %-16u %-15" PRIu64 " %-15.2f %-15" PRIu64 " %-15.2f %-15" PRIu64 "\n", i,
201  d->checks, avgmpms, d->mpm_match_cnt_max, avgsigs, d->post_prefilter_sigs_max);
202  }
203  fprintf(fp,"\n");
204 }
205 
206 static void
207 SCProfilingSghDump(DetectEngineCtx *de_ctx)
208 {
209  FILE *fp;
210 
211  if (profiling_sghs_enabled == 0)
212  return;
213 
214  if (profiling_sghs_output_to_file == 1) {
215  SCLogDebug("file %s mode %s", profiling_file_name, profiling_file_mode);
216 
217  fp = fopen(profiling_file_name, profiling_file_mode);
218 
219  if (fp == NULL) {
220  SCLogError("failed to open %s: %s", profiling_file_name, strerror(errno));
221  return;
222  }
223  } else {
224  fp = stdout;
225  }
226 
227  if (profiling_rulegroup_json) {
228  DoDumpJSON(de_ctx->profile_sgh_ctx, fp, "rule groups");
229  } else {
230  DoDump(de_ctx->profile_sgh_ctx, fp, "rule groups");
231  }
232 
233  if (fp != stdout)
234  fclose(fp);
235 
236  SCLogPerf("Done dumping rulegroup profiling data.");
237 }
238 
239 /**
240  * \brief Update a rule counter.
241  *
242  * \param id The ID of this counter.
243  * \param ticks Number of CPU ticks for this rule.
244  * \param match Did the rule match?
245  */
246 void
248 {
249  if (det_ctx != NULL && sgh != NULL && det_ctx->sgh_perf_data != NULL &&
250  sgh->id < det_ctx->de_ctx->sgh_array_cnt) {
251  SCProfileSghData *p = &det_ctx->sgh_perf_data[sgh->id];
252  p->checks++;
253 
254  p->post_prefilter_sigs_total += det_ctx->match_array_cnt;
255  if (det_ctx->match_array_cnt > p->post_prefilter_sigs_max)
256  p->post_prefilter_sigs_max = det_ctx->match_array_cnt;
257  p->mpm_match_cnt_total += det_ctx->pmq.rule_id_array_cnt;
258  if (det_ctx->pmq.rule_id_array_cnt > p->mpm_match_cnt_max)
259  p->mpm_match_cnt_max = det_ctx->pmq.rule_id_array_cnt;
260  }
261 }
262 
263 static 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 
275 static 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 
307 static 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  */
347 void
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 
357  BUG_ON(de_ctx->profile_sgh_ctx->data == NULL);
358 
360 
361  SCLogPerf("Registered %"PRIu32" rulegroup profiling counters.", de_ctx->sgh_array_cnt);
362 }
363 
364 #endif /* PROFILING */
SCProfilingSghThreadCleanup
void SCProfilingSghThreadCleanup(DetectEngineThreadCtx *det_ctx)
Definition: util-profiling-rulegroups.c:329
SCConfValIsTrue
int SCConfValIsTrue(const char *val)
Check if a value is true.
Definition: conf.c:577
DetectEngineThreadCtx_::match_array_cnt
SigIntId match_array_cnt
Definition: detect.h:1415
SCProfilingSghThreadSetup
void SCProfilingSghThreadSetup(SCProfileSghDetectCtx *ctx, DetectEngineThreadCtx *det_ctx)
Definition: util-profiling-rulegroups.c:294
CreateIsoTimeString
void CreateIsoTimeString(const SCTime_t ts, char *str, size_t size)
Definition: util-time.c:209
PrefilterRuleStore_::rule_id_array_cnt
uint32_t rule_id_array_cnt
Definition: util-prefilter.h:40
SigGroupHead_
Container for matching data for a signature group.
Definition: detect.h:1730
DetectEngineThreadCtx_::sgh_perf_data
struct SCProfileSghData_ * sgh_perf_data
Definition: detect.h:1487
SCProfileSghDetectCtx_
Definition: util-profiling-rulegroups.c:48
SCProfileSghDetectCtx_::data_m
pthread_mutex_t data_m
Definition: util-profiling-rulegroups.c:51
SCLogDebug
#define SCLogDebug(...)
Definition: util-debug.h:282
name
const char * name
Definition: detect-engine-proto.c:47
DetectEngineThreadCtx_::pmq
PrefilterRuleStore pmq
Definition: detect.h:1429
SCProfilingSghInitCounters
void SCProfilingSghInitCounters(DetectEngineCtx *de_ctx)
Register the keyword profiling counters.
Definition: util-profiling-rulegroups.c:348
ctx
struct Thresholds ctx
DetectEngineCtx_
main detection engine ctx
Definition: detect.h:995
SCConfNodeChildValueIsTrue
int SCConfNodeChildValueIsTrue(const SCConfNode *node, const char *key)
Test if a configuration node has a true value.
Definition: conf.c:921
DetectEngineCtx_::profile_sgh_ctx
struct SCProfileSghDetectCtx_ * profile_sgh_ctx
Definition: detect.h:1118
p
Packet * p
Definition: fuzz_dataset.c:30
JSON_ESCAPE_SLASH
#define JSON_ESCAPE_SLASH
Definition: suricata-common.h:295
SCConfNodeLookupChildValue
const char * SCConfNodeLookupChildValue(const SCConfNode *node, const char *name)
Lookup the value of a child configuration node by name.
Definition: conf.c:877
SCProfileSghData_::mpm_match_cnt_total
uint64_t mpm_match_cnt_total
Definition: util-profiling-rulegroups.c:43
SCProfileSghData_::checks
uint64_t checks
Definition: util-profiling-rulegroups.c:38
strlcpy
size_t strlcpy(char *dst, const char *src, size_t siz)
Definition: util-strlcpyu.c:43
SCProfileSghData_::post_prefilter_sigs_max
uint64_t post_prefilter_sigs_max
Definition: util-profiling-rulegroups.c:41
de_ctx
DetectEngineCtx * de_ctx
Definition: fuzz_siginit.c:22
profiling_sghs_enabled
int profiling_sghs_enabled
Definition: util-profiling-rulegroups.c:55
DetectEngineThreadCtx_
Definition: detect.h:1316
SCProfileSghData_
Definition: util-profiling-rulegroups.c:37
SCTIME_FROM_TIMEVAL
#define SCTIME_FROM_TIMEVAL(tv)
Definition: util-time.h:79
BUG_ON
#define BUG_ON(x)
Definition: suricata-common.h:322
util-profiling.h
SCLocalTime
struct tm * SCLocalTime(time_t timep, struct tm *result)
Definition: util-time.c:267
SCConfigGetLogDirectory
const char * SCConfigGetLogDirectory(void)
Definition: util-conf.c:38
DetectEngineCtx_::sgh_array_cnt
uint32_t sgh_array_cnt
Definition: detect.h:1068
SCProfileSghData
struct SCProfileSghData_ SCProfileSghData
util-conf.h
suricata-common.h
util-path.h
SCProfileSghDetectCtx
struct SCProfileSghDetectCtx_ SCProfileSghDetectCtx
SCLogPerf
#define SCLogPerf(...)
Definition: util-debug.h:241
PathIsAbsolute
int PathIsAbsolute(const char *path)
Check if a path is absolute.
Definition: util-path.c:44
FatalError
#define FatalError(...)
Definition: util-debug.h:517
SCConfGetNode
SCConfNode * SCConfGetNode(const char *name)
Get a SCConfNode by name.
Definition: conf.c:183
SCLogError
#define SCLogError(...)
Macro used to log ERROR messages.
Definition: util-debug.h:274
SCFree
#define SCFree(p)
Definition: util-mem.h:61
ADD
#define ADD(name)
SCProfilingSghDestroyCtx
void SCProfilingSghDestroyCtx(DetectEngineCtx *de_ctx)
Definition: util-profiling-rulegroups.c:285
SCProfileSghData_::mpm_match_cnt_max
uint64_t mpm_match_cnt_max
Definition: util-profiling-rulegroups.c:44
DetectEngineThreadCtx_::de_ctx
DetectEngineCtx * de_ctx
Definition: detect.h:1444
SCProfileSghDetectCtx_::cnt
uint32_t cnt
Definition: util-profiling-rulegroups.c:49
likely
#define likely(expr)
Definition: util-optimize.h:32
SCProfilingSghUpdateCounter
void SCProfilingSghUpdateCounter(DetectEngineThreadCtx *det_ctx, const SigGroupHead *sgh)
Update a rule counter.
Definition: util-profiling-rulegroups.c:247
SCProfilingSghsGlobalInit
void SCProfilingSghsGlobalInit(void)
Definition: util-profiling-rulegroups.c:60
SCCalloc
#define SCCalloc(nm, sz)
Definition: util-mem.h:53
SCConfNode_
Definition: conf.h:37
SigGroupHead_::id
uint32_t id
Definition: detect.h:1738
SCProfileSghDetectCtx_::data
SCProfileSghData * data
Definition: util-profiling-rulegroups.c:50
SCProfileSghData_::post_prefilter_sigs_total
uint64_t post_prefilter_sigs_total
Definition: util-profiling-rulegroups.c:40