• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OISF / suricata / 23350122333

20 Mar 2026 03:33PM UTC coverage: 76.492% (-2.8%) from 79.315%
23350122333

Pull #15053

github

web-flow
Merge f5bf69f97 into 6587e363a
Pull Request #15053: Flow queue/v3

113 of 129 new or added lines in 9 files covered. (87.6%)

9534 existing lines in 453 files now uncovered.

256601 of 335461 relevant lines covered (76.49%)

4680806.66 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.5
/src/counters.c
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 Anoop Saldanha <anoopsaldanha@gmail.com>
22
 * \author Victor Julien <victor@inliniac.net>
23
 *
24
 * Engine stats API
25
 */
26

27
#include "suricata-common.h"
28
#include "counters.h"
29

30
#include "suricata.h"
31
#include "threadvars.h"
32

33
#include "output.h"
34
#include "output-json-stats.h"
35

36
#include "util-byte.h"
37
#include "util-conf.h"
38
#include "util-hash.h"
39
#include "util-time.h"
40

41
#include "tm-threads.h"
42
#include "util-privs.h"
43

44
/* Time interval for syncing the local counters with the global ones */
45
#define STATS_WUT_TTS 3
3,190✔
46

47
/* Time interval at which the mgmt thread o/p the stats */
48
#define STATS_MGMTT_TTS 8
×
49

50
/**
51
 * \brief Different kinds of qualifier that can be used to modify the behaviour
52
 *        of the counter to be registered
53
 */
54
enum StatsType {
55
    STATS_TYPE_NORMAL = 1,
56
    STATS_TYPE_AVERAGE = 2,
57
    STATS_TYPE_MAXIMUM = 3,
58
    STATS_TYPE_FUNC = 4,
59
    STATS_TYPE_DERIVE_DIV = 5,
60
};
61

62
/**
63
 * \brief per thread store of counters
64
 */
65
typedef struct StatsThreadStore_ {
66
    /** thread name used in output */
67
    const char *name;
68

69
    StatsPublicThreadContext *ctx;
70

71
    struct StatsThreadStore_ *next;
72
} StatsThreadStore;
73

74
/**
75
 * \brief Holds the output interface context for the counter api
76
 */
77
typedef struct StatsGlobalContext_ {
78
    /** list of thread stores: one per thread plus one global */
79
    StatsThreadStore *sts;
80
    SCMutex sts_lock;
81
    int sts_cnt;
82

83
    HashTable *counters_id_hash;
84

85
    StatsPublicThreadContext global_counter_ctx;
86
} StatsGlobalContext;
87

88
static void *stats_thread_data = NULL;
89
static StatsGlobalContext *stats_ctx = NULL;
90
static time_t stats_start_time;
91
/** refresh interval in seconds */
92
static uint32_t stats_tts = STATS_MGMTT_TTS;
93
/** is the stats counter enabled? */
94
static bool stats_enabled = true;
95

96
/**< add decoder events as stats? enabled by default */
97
bool stats_decoder_events = true;
98
const char *stats_decoder_events_prefix = "decoder.event";
99
/**< add stream events as stats? disabled by default */
100
bool stats_stream_events = false;
101

102
static int StatsOutput(ThreadVars *tv);
103
static int StatsThreadRegister(const char *thread_name, StatsPublicThreadContext *);
104
static void StatsReleaseCounters(StatsCounter *head);
105
static int StatsUpdateCounterArray(StatsPrivateThreadContext *pca, StatsPublicThreadContext *pctx);
106

107
/** stats table is filled each interval and passed to the
108
 *  loggers. Initialized at first use. */
109
static StatsTable stats_table = { NULL, NULL, 0, 0, 0, {0 , 0}};
110
static SCMutex stats_table_mutex = SCMUTEX_INITIALIZER;
111
static int stats_loggers_active = 1;
112

113
static uint16_t counters_global_id = 0;
114

115
bool StatsEnabled(void)
116
{
3,056✔
117
    return stats_enabled;
3,056✔
118
}
3,056✔
119

120
static void StatsPublicThreadContextInit(StatsPublicThreadContext *t)
121
{
3,614✔
122
    SCSpinInit(&t->lock, 0);
3,614✔
123
}
3,614✔
124

125
static void StatsPublicThreadContextCleanup(StatsPublicThreadContext *t)
126
{
34,034✔
127
    SCSpinLock(&t->lock);
34,034✔
128
    SCFree(t->copy_of_private);
34,034✔
129
    SCFree(t->pc_array);
34,034✔
130
    StatsReleaseCounters(t->head);
34,034✔
131
    t->head = NULL;
34,034✔
132
    SC_ATOMIC_SET(t->sync_now, false);
34,034✔
133
    t->curr_id = 0;
34,034✔
134
    SCSpinUnlock(&t->lock);
34,034✔
135
    SCSpinDestroy(&t->lock);
34,034✔
136
}
34,034✔
137

138
/**
139
 * \brief Adds a value of type uint64_t to the local counter.
140
 *
141
 * \param id  ID of the counter as set by the API
142
 * \param pca Counter array that holds the local counter for this TM
143
 * \param x   Value to add to this local counter
144
 */
145
void StatsCounterAddI64(StatsThreadContext *stats, StatsCounterId id, int64_t x)
146
{
127,143,728✔
147
    StatsPrivateThreadContext *pca = &stats->priv;
127,143,728✔
148
#if defined (UNITTESTS) || defined (FUZZ)
805✔
149
    if (pca->initialized == 0)
805✔
150
        return;
795✔
151
#endif
10✔
152
#ifdef DEBUG
153
    BUG_ON((id.id < 1) || (id.id > pca->size));
154
#endif
155
    pca->head[id.id].v += x;
127,142,933✔
156
}
127,142,933✔
157

158
/**
159
 * \brief Increments the local counter
160
 *
161
 * \param id  Index of the counter in the counter array
162
 * \param pca Counter array that holds the local counters for this TM
163
 */
164
void StatsCounterIncr(StatsThreadContext *stats, StatsCounterId id)
165
{
530,010,397✔
166
    StatsPrivateThreadContext *pca = &stats->priv;
530,010,397✔
167
#if defined (UNITTESTS) || defined (FUZZ)
2,643✔
168
    if (pca->initialized == 0)
2,643✔
169
        return;
2,636✔
170
#endif
7✔
171
#ifdef DEBUG
172
    BUG_ON((id.id < 1) || (id.id > pca->size));
173
#endif
174
    pca->head[id.id].v++;
530,007,761✔
175
}
530,007,761✔
176

177
/**
178
 * \brief Decrements the local counter
179
 *
180
 * \param stats per thread counter structure
181
 * \param id  Index of the counter in the counter array
182
 */
183
void StatsCounterDecr(StatsThreadContext *stats, StatsCounterId id)
184
{
24,274✔
185
    StatsPrivateThreadContext *pca = &stats->priv;
24,274✔
186
#if defined(UNITTESTS) || defined(FUZZ)
187
    if (pca->initialized == 0)
188
        return;
189
#endif
190
#ifdef DEBUG
191
    BUG_ON((id.id < 1) || (id.id > pca->size));
192
#endif
193
    pca->head[id.id].v--;
24,274✔
194
}
24,274✔
195

196
/**
197
 * \brief set, so overwrite, the value of the local counter
198
 *
199
 * \param stats per thread counter structure
200
 * \param id  Index of the local counter in the counter array
201
 * \param x   The value to set for the counter
202
 */
203
void StatsCounterSetI64(StatsThreadContext *stats, StatsCounterId id, int64_t x)
204
{
22,427✔
205
    StatsPrivateThreadContext *pca = &stats->priv;
22,427✔
206
#if defined (UNITTESTS) || defined (FUZZ)
207
    if (pca->initialized == 0)
208
        return;
209
#endif
210
#ifdef DEBUG
211
    BUG_ON((id.id < 1) || (id.id > pca->size));
212
#endif
213
    pca->head[id.id].v = x;
22,427✔
214
}
22,427✔
215

216
/**
217
 * \brief update the value of the localmax counter
218
 *
219
 * \param stats per thread counter structure
220
 * \param id  Index of the local counter in the counter array
221
 * \param x   The value to set for the counter
222
 */
223
void StatsCounterMaxUpdateI64(StatsThreadContext *stats, StatsCounterMaxId id, int64_t x)
224
{
127,448,385✔
225
    StatsPrivateThreadContext *pca = &stats->priv;
127,448,385✔
226
#if defined(UNITTESTS) || defined(FUZZ)
227
    if (pca->initialized == 0)
228
        return;
229
#endif
230
#ifdef DEBUG
231
    BUG_ON((id.id < 1) || (id.id > pca->size));
232
#endif
233

234
    if ((int64_t)x > pca->head[id.id].v) {
127,448,385✔
235
        pca->head[id.id].v = x;
16,680✔
236
    }
16,680✔
237
}
127,448,385✔
238

239
void StatsCounterAvgAddI64(StatsThreadContext *stats, StatsCounterAvgId id, int64_t x)
240
{
587,579✔
241
    StatsPrivateThreadContext *pca = &stats->priv;
587,579✔
242
#if defined(UNITTESTS) || defined(FUZZ)
243
    if (pca->initialized == 0)
244
        return;
245
#endif
246
#ifdef DEBUG
247
    BUG_ON((id.id < 1) || (id.id > pca->size));
248
#endif
249

250
    pca->head[id.id].v += x;
587,579✔
251
    pca->head[id.id + 1].v++;
587,579✔
252
}
587,579✔
253

254
static SCConfNode *GetConfig(void)
255
{
3,599✔
256
    SCConfNode *stats = SCConfGetNode("stats");
3,599✔
257
    if (stats != NULL)
3,599✔
258
        return stats;
3,021✔
259

260
    SCConfNode *root = SCConfGetNode("outputs");
578✔
261
    SCConfNode *node = NULL;
578✔
262
    if (root != NULL) {
578✔
263
        TAILQ_FOREACH(node, &root->head, next) {
570✔
264
            if (strcmp(node->val, "stats") == 0) {
570✔
265
                return node->head.tqh_first;
11✔
266
            }
11✔
267
        }
570✔
268
    }
502✔
269
    return NULL;
567✔
270
}
578✔
271

272
/**
273
 * \brief Initializes stats context
274
 */
275
static void StatsInitCtxPreOutput(void)
276
{
3,599✔
277
    SCEnter();
3,599✔
278
    SCConfNode *stats = GetConfig();
3,599✔
279
    if (stats != NULL) {
3,599✔
280
        const char *enabled = SCConfNodeLookupChildValue(stats, "enabled");
3,032✔
281
        if (enabled != NULL && SCConfValIsFalse(enabled)) {
3,032✔
282
            stats_enabled = false;
2✔
283
            SCLogDebug("Stats module has been disabled");
2✔
284
            SCReturn;
2✔
285
        }
2✔
286
        /* warn if we are using legacy config to enable stats */
287
        SCConfNode *gstats = SCConfGetNode("stats");
3,030✔
288
        if (gstats == NULL) {
3,030✔
289
            SCLogWarning("global stats config is missing. "
11✔
290
                         "Stats enabled through legacy stats.log. "
11✔
291
                         "See %s/configuration/suricata-yaml.html#stats",
11✔
292
                    GetDocURL());
11✔
293
        }
11✔
294

295
        const char *interval = SCConfNodeLookupChildValue(stats, "interval");
3,030✔
296
        if (interval != NULL)
3,030✔
297
            if (StringParseUint32(&stats_tts, 10, 0, interval) < 0) {
3,005✔
298
                SCLogWarning("Invalid value for "
×
299
                             "interval: \"%s\". Resetting to %d.",
×
300
                        interval, STATS_MGMTT_TTS);
×
301
                stats_tts = STATS_MGMTT_TTS;
×
302
            }
×
303

304
        int b;
3,030✔
305
        int ret = SCConfGetChildValueBool(stats, "decoder-events", &b);
3,030✔
306
        if (ret) {
3,030✔
307
            stats_decoder_events = (b == 1);
13✔
308
        }
13✔
309
        ret = SCConfGetChildValueBool(stats, "stream-events", &b);
3,030✔
310
        if (ret) {
3,030✔
311
            stats_stream_events = (b == 1);
7✔
312
        }
7✔
313

314
        const char *prefix = NULL;
3,030✔
315
        if (SCConfGet("stats.decoder-events-prefix", &prefix) != 1) {
3,030✔
316
            prefix = "decoder.event";
3,030✔
317
        }
3,030✔
318
        stats_decoder_events_prefix = prefix;
3,030✔
319
    }
3,030✔
320
    SCReturn;
3,599✔
321
}
3,599✔
322

323
static void StatsInitCtxPostOutput(void)
324
{
3,595✔
325
    SCEnter();
3,595✔
326
    /* Store the engine start time */
327
    time(&stats_start_time);
3,595✔
328

329
    /* init the lock used by StatsThreadStore */
330
    if (SCMutexInit(&stats_ctx->sts_lock, NULL) != 0) {
3,595✔
331
        FatalError("error initializing sts mutex");
×
332
    }
×
333

334
    if (stats_enabled && !OutputStatsLoggersRegistered()) {
3,595✔
335
        stats_loggers_active = 0;
534✔
336

337
        /* if the unix command socket is enabled we do the background
338
         * stats sync just in case someone runs 'dump-counters' */
339
        if (!ConfUnixSocketIsEnable()) {
534✔
340
            SCLogWarning("stats are enabled but no loggers are active");
534✔
341
            stats_enabled = false;
534✔
342
            SCReturn;
534✔
343
        }
534✔
344
    }
534✔
345

346
    SCReturn;
3,595✔
347
}
3,595✔
348

349
/**
350
 * \brief Releases the resources allotted to the output context of the
351
 *        Stats API
352
 */
353
static void StatsReleaseCtx(void)
354
{
3,432✔
355
    if (stats_ctx == NULL) {
3,432✔
356
        SCLogDebug("Counter module has been disabled");
1✔
357
        return;
1✔
358
    }
1✔
359

360
    StatsThreadStore *sts = NULL;
3,431✔
361
    StatsThreadStore *temp = NULL;
3,431✔
362
    sts = stats_ctx->sts;
3,431✔
363

364
    while (sts != NULL) {
30,251✔
365
        temp = sts->next;
26,820✔
366
        SCFree(sts);
26,820✔
367
        sts = temp;
26,820✔
368
    }
26,820✔
369

370
    if (stats_ctx->counters_id_hash != NULL) {
3,431✔
371
        HashTableFree(stats_ctx->counters_id_hash);
3,431✔
372
        stats_ctx->counters_id_hash = NULL;
3,431✔
373
        counters_global_id = 0;
3,431✔
374
    }
3,431✔
375

376
    StatsPublicThreadContextCleanup(&stats_ctx->global_counter_ctx);
3,431✔
377
    SCFree(stats_ctx);
3,431✔
378
    stats_ctx = NULL;
3,431✔
379

380
    SCMutexLock(&stats_table_mutex);
3,431✔
381
    /* free stats table */
382
    if (stats_table.tstats != NULL) {
3,431✔
383
        SCFree(stats_table.tstats);
2,946✔
384
        stats_table.tstats = NULL;
2,946✔
385
    }
2,946✔
386

387
    if (stats_table.stats != NULL) {
3,431✔
388
        SCFree(stats_table.stats);
2,946✔
389
        stats_table.stats = NULL;
2,946✔
390
    }
2,946✔
391
    memset(&stats_table, 0, sizeof(stats_table));
3,431✔
392
    SCMutexUnlock(&stats_table_mutex);
3,431✔
393
}
3,431✔
394

395
/**
396
 * \brief management thread. This thread is responsible for writing the stats
397
 *
398
 * \param arg thread var
399
 *
400
 * \retval NULL This is the value that is always returned
401
 */
402
static void *StatsMgmtThread(void *arg)
403
{
2,946✔
404
    ThreadVars *tv_local = (ThreadVars *)arg;
2,946✔
405

406
    SCSetThreadName(tv_local->name);
2,946✔
407

408
    if (tv_local->thread_setup_flags != 0)
2,946✔
409
        TmThreadSetupOptions(tv_local);
15✔
410

411
    /* Set the threads capability */
412
    tv_local->cap_flags = 0;
2,946✔
413
    SCDropCaps(tv_local);
2,946✔
414

415
    if (stats_ctx == NULL) {
2,946✔
416
        SCLogError("Stats API not init"
×
417
                   "StatsInitCounterApi() has to be called first");
×
418
        TmThreadsSetFlag(tv_local, THV_CLOSED | THV_RUNNING_DONE);
×
419
        return NULL;
×
420
    }
×
421

422
    TmModule *tm = &tmm_modules[TMM_STATSLOGGER];
2,946✔
423
    BUG_ON(tm->ThreadInit == NULL);
2,946✔
424
    int r = tm->ThreadInit(tv_local, NULL, &stats_thread_data);
2,946✔
425
    if (r != 0 || stats_thread_data == NULL) {
2,946✔
426
        SCLogError("Stats API "
×
427
                   "ThreadInit failed");
×
428
        TmThreadsSetFlag(tv_local, THV_CLOSED | THV_RUNNING_DONE);
×
429
        return NULL;
×
430
    }
×
431
    SCLogDebug("stats_thread_data %p", &stats_thread_data);
2,946✔
432

433
    TmThreadsSetFlag(tv_local, THV_INIT_DONE | THV_RUNNING);
2,946✔
434
    bool run = TmThreadsWaitForUnpause(tv_local);
2,946✔
435
    while (run) {
3,032✔
436
        struct timeval cur_timev;
3,032✔
437
        gettimeofday(&cur_timev, NULL);
3,032✔
438
        struct timespec cond_time = FROM_TIMEVAL(cur_timev);
3,032✔
439
        cond_time.tv_sec += (stats_tts);
3,032✔
440

441
        /* wait for the set time, or until we are woken up by
442
         * the shutdown procedure */
443
        SCCtrlMutexLock(tv_local->ctrl_mutex);
3,032✔
444
        while (1) {
5,978✔
445
            if (TmThreadsCheckFlag(tv_local, THV_KILL)) {
5,978✔
446
                break;
2,946✔
447
            }
2,946✔
448
            int rc = SCCtrlCondTimedwait(tv_local->ctrl_cond, tv_local->ctrl_mutex, &cond_time);
3,032✔
449
            if (rc == ETIMEDOUT || rc < 0) {
3,032✔
450
                break;
86✔
451
            }
86✔
452
        }
3,032✔
453
        SCCtrlMutexUnlock(tv_local->ctrl_mutex);
3,032✔
454

455
        SCMutexLock(&stats_table_mutex);
3,032✔
456
        StatsOutput(tv_local);
3,032✔
457
        SCMutexUnlock(&stats_table_mutex);
3,032✔
458

459
        if (TmThreadsCheckFlag(tv_local, THV_KILL)) {
3,032✔
460
            break;
2,946✔
461
        }
2,946✔
462
    }
3,032✔
463

464
    TmThreadsSetFlag(tv_local, THV_RUNNING_DONE);
2,946✔
465
    TmThreadWaitForFlag(tv_local, THV_DEINIT);
2,946✔
466

467
    r = tm->ThreadDeinit(tv_local, stats_thread_data);
2,946✔
468
    if (r != TM_ECODE_OK) {
2,946✔
469
        SCLogError("Stats Counter API "
×
470
                   "ThreadDeinit failed");
×
471
    }
×
472

473
    TmThreadsSetFlag(tv_local, THV_CLOSED);
2,946✔
474
    return NULL;
2,946✔
475
}
2,946✔
476

477
void StatsSyncCounters(StatsThreadContext *stats)
478
{
57,813✔
479
    StatsUpdateCounterArray(&stats->priv, &stats->pub);
57,813✔
480
}
57,813✔
481

482
void StatsSyncCountersIfSignalled(StatsThreadContext *stats)
483
{
5,358,720✔
484
    if (SC_ATOMIC_GET(stats->pub.sync_now)) {
5,358,720✔
485
        StatsUpdateCounterArray(&stats->priv, &stats->pub);
1,738✔
486
    }
1,738✔
487
}
5,358,720✔
488

489
/**
490
 * \brief Wake up thread.  This thread wakes up every TTS(time to sleep) seconds
491
 *        and sets the flag for every ThreadVars' StatsPublicThreadContext
492
 *
493
 * \param arg is NULL always
494
 *
495
 * \retval NULL This is the value that is always returned
496
 */
497
static void *StatsWakeupThread(void *arg)
498
{
2,946✔
499
    ThreadVars *tv_local = (ThreadVars *)arg;
2,946✔
500

501
    SCSetThreadName(tv_local->name);
2,946✔
502

503
    if (tv_local->thread_setup_flags != 0)
2,946✔
504
        TmThreadSetupOptions(tv_local);
15✔
505

506
    /* Set the threads capability */
507
    tv_local->cap_flags = 0;
2,946✔
508
    SCDropCaps(tv_local);
2,946✔
509

510
    if (stats_ctx == NULL) {
2,946✔
511
        SCLogError("Stats API not init"
×
512
                   "StatsInitCounterApi() has to be called first");
×
513
        TmThreadsSetFlag(tv_local, THV_CLOSED | THV_RUNNING_DONE);
×
514
        return NULL;
×
515
    }
×
516

517
    TmThreadsSetFlag(tv_local, THV_INIT_DONE | THV_RUNNING);
2,946✔
518
    bool run = TmThreadsWaitForUnpause(tv_local);
2,946✔
519

520
    while (run) {
3,190✔
521
        struct timeval cur_timev;
3,190✔
522
        gettimeofday(&cur_timev, NULL);
3,190✔
523
        struct timespec cond_time = FROM_TIMEVAL(cur_timev);
3,190✔
524
        cond_time.tv_sec += STATS_WUT_TTS;
3,190✔
525

526
        /* wait for the set time, or until we are woken up by
527
         * the shutdown procedure */
528
        SCCtrlMutexLock(tv_local->ctrl_mutex);
3,190✔
529
        while (1) {
6,136✔
530
            if (TmThreadsCheckFlag(tv_local, THV_KILL)) {
6,136✔
531
                break;
2,946✔
532
            }
2,946✔
533
            int rc = SCCtrlCondTimedwait(tv_local->ctrl_cond, tv_local->ctrl_mutex, &cond_time);
3,190✔
534
            if (rc == ETIMEDOUT || rc < 0) {
3,190✔
535
                break;
244✔
536
            }
244✔
537
        }
3,190✔
538
        SCCtrlMutexUnlock(tv_local->ctrl_mutex);
3,190✔
539

540
        SCMutexLock(&tv_root_lock);
3,190✔
541
        ThreadVars *tv = tv_root[TVT_PPT];
3,190✔
542
        while (tv != NULL) {
19,126✔
543
            if (tv->stats.pub.head == NULL) {
15,936✔
544
                tv = tv->next;
×
545
                continue;
×
546
            }
×
547

548
            SC_ATOMIC_SET(tv->stats.pub.sync_now, true);
15,936✔
549

550
            if (tv->inq != NULL) {
15,936✔
551
                PacketQueue *q = tv->inq->pq;
12,043✔
552
                SCMutexLock(&q->mutex_q);
12,043✔
553
                SCCondSignal(&q->cond_q);
12,043✔
554
                SCMutexUnlock(&q->mutex_q);
12,043✔
555
            }
12,043✔
556

557
            tv = tv->next;
15,936✔
558
        }
15,936✔
559

560
        /* mgt threads for flow manager */
561
        tv = tv_root[TVT_MGMT];
3,190✔
562
        while (tv != NULL) {
15,950✔
563
            if (tv->stats.pub.head == NULL) {
12,760✔
564
                tv = tv->next;
6,380✔
565
                continue;
6,380✔
566
            }
6,380✔
567

568
            SC_ATOMIC_SET(tv->stats.pub.sync_now, true);
6,380✔
569

570
            tv = tv->next;
6,380✔
571
        }
6,380✔
572
        SCMutexUnlock(&tv_root_lock);
3,190✔
573

574
        if (TmThreadsCheckFlag(tv_local, THV_KILL)) {
3,190✔
575
            break;
2,946✔
576
        }
2,946✔
577
    }
3,190✔
578

579
    TmThreadsSetFlag(tv_local, THV_RUNNING_DONE);
2,946✔
580
    TmThreadWaitForFlag(tv_local, THV_DEINIT);
2,946✔
581
    TmThreadsSetFlag(tv_local, THV_CLOSED);
2,946✔
582
    return NULL;
2,946✔
583
}
2,946✔
584

585
/**
586
 * \brief Releases a counter
587
 *
588
 * \param pc Pointer to the StatsCounter to be freed
589
 */
590
static void StatsReleaseCounter(StatsCounter *pc)
591
{
7,337,746✔
592
    if (pc != NULL) {
7,337,746✔
593
        SCFree(pc);
7,337,746✔
594
    }
7,337,746✔
595
}
7,337,746✔
596

597
/** \internal
598
 *  \brief Get ID for counters referenced in a derive counter
599
 *  \retval id (>=1) or 0 on error
600
 */
601
static uint16_t GetIdByName(const StatsPublicThreadContext *pctx, const char *name)
602
{
33,952✔
603
    for (const StatsCounter *c = pctx->head; c != NULL; c = c->next) {
1,089,914✔
604
        if (strcmp(name, c->name) == 0) {
1,089,914✔
605
            return c->id;
33,952✔
606
        }
33,952✔
607
    }
1,089,914✔
608
    return 0;
×
609
}
33,952✔
610

611
/**
612
 * \brief Registers a counter.
613
 *
614
 * \param name    Name of the counter, to be registered
615
 * \param pctx     StatsPublicThreadContext for this tm-tv instance
616
 * \param type_q   Qualifier describing the type of counter to be registered
617
 *
618
 * \retval the counter id for the newly registered counter, or the already
619
 *         present counter on success
620
 * \retval 0 on failure
621
 */
622
static uint16_t StatsRegisterQualifiedCounter(const char *name, StatsPublicThreadContext *pctx,
623
        enum StatsType type_q, uint64_t (*Func)(void), const char *dname1, const char *dname2)
624
{
7,722,444✔
625
    StatsCounter **head = &pctx->head;
7,722,444✔
626
    StatsCounter *temp = NULL;
7,722,444✔
627
    StatsCounter *prev = NULL;
7,722,444✔
628
    StatsCounter *pc = NULL;
7,722,444✔
629

630
    if (name == NULL || pctx == NULL) {
7,722,444✔
631
        SCLogDebug("Counter name, StatsPublicThreadContext NULL");
17,029✔
632
        return 0;
17,029✔
633
    }
17,029✔
634

635
    temp = prev = *head;
7,705,415✔
636
    while (temp != NULL) {
1,708,182,425✔
637
        prev = temp;
1,700,841,655✔
638

639
        if (strcmp(name, temp->name) == 0) {
1,700,841,655✔
640
            break;
364,645✔
641
        }
364,645✔
642

643
        temp = temp->next;
1,700,477,010✔
644
    }
1,700,477,010✔
645

646
    /* We already have a counter registered by this name */
647
    if (temp != NULL)
7,705,415✔
648
        return(temp->id);
364,645✔
649

650
    uint16_t did1 = 0;
7,340,770✔
651
    uint16_t did2 = 0;
7,340,770✔
652
    if (type_q == STATS_TYPE_DERIVE_DIV) {
7,340,770✔
653
        did1 = GetIdByName(pctx, dname1);
16,976✔
654
        did2 = GetIdByName(pctx, dname2);
16,976✔
655
        if (did1 == 0 || did2 == 0) {
16,976✔
656
            return 0;
×
657
        }
×
658
    }
16,976✔
659

660
    /* if we reach this point we don't have a counter registered by this name */
661
    if ((pc = SCCalloc(1, sizeof(StatsCounter))) == NULL)
7,340,770✔
662
        return 0;
×
663

664
    /* assign a unique id to this StatsCounter.  The id is local to this
665
     * thread context.  Please note that the id start from 1, and not 0 */
666
    if (type_q == STATS_TYPE_DERIVE_DIV) {
7,340,770✔
667
        pc->id = ++pctx->derive_id;
16,976✔
668
    } else {
7,323,794✔
669
        pc->id = ++(pctx->curr_id);
7,323,794✔
670
    }
7,323,794✔
671
    /* for AVG counters we use 2 indices into the tables: one for values,
672
     * the other to track updates. */
673
    if (type_q == STATS_TYPE_AVERAGE)
7,340,770✔
674
        ++(pctx->curr_id);
20,447✔
675
    pc->name = name;
7,340,770✔
676

677
    /* Precalculate the short name */
678
    if (strrchr(name, '.') != NULL) {
7,340,770✔
679
        pc->short_name = &name[strrchr(name, '.') - name + 1];
7,340,749✔
680
    }
7,340,749✔
681

682
    pc->type = type_q;
7,340,770✔
683
    pc->Func = Func;
7,340,770✔
684
    pc->did1 = did1;
7,340,770✔
685
    pc->did2 = did2;
7,340,770✔
686

687
    /* we now add the counter to the list */
688
    if (prev == NULL)
7,340,770✔
689
        *head = pc;
28,309✔
690
    else
7,312,461✔
691
        prev->next = pc;
7,312,461✔
692

693
    return pc->id;
7,340,770✔
694
}
7,340,770✔
695

696
/**
697
 * \brief The output interface for the Stats API
698
 */
699
static int StatsOutput(ThreadVars *tv)
700
{
3,032✔
701
    const StatsThreadStore *sts = NULL;
3,032✔
702
    void *td = stats_thread_data;
3,032✔
703

704
    if (counters_global_id == 0)
3,032✔
705
        return -1;
×
706

707
    if (stats_table.nstats == 0) {
3,032✔
708
        StatsThreadRegister("Global", &stats_ctx->global_counter_ctx);
2,946✔
709

710
        uint32_t nstats = counters_global_id;
2,946✔
711

712
        stats_table.nstats = nstats;
2,946✔
713
        stats_table.stats = SCCalloc(stats_table.nstats, sizeof(StatsRecord));
2,946✔
714
        if (stats_table.stats == NULL) {
2,946✔
715
            stats_table.nstats = 0;
×
716
            SCLogError("could not alloc memory for stats");
×
717
            return -1;
×
718
        }
×
719

720
        stats_table.ntstats = stats_ctx->sts_cnt;
2,946✔
721
        uint32_t array_size = stats_table.nstats * sizeof(StatsRecord);
2,946✔
722
        stats_table.tstats = SCCalloc(stats_table.ntstats, array_size);
2,946✔
723
        if (stats_table.tstats == NULL) {
2,946✔
724
            stats_table.ntstats = 0;
×
725
            SCLogError("could not alloc memory for stats");
×
726
            return -1;
×
727
        }
×
728

729
        stats_table.start_time = stats_start_time;
2,946✔
730
    }
2,946✔
731

732
    const uint16_t max_id = counters_global_id;
3,032✔
733
    if (max_id == 0)
3,032✔
734
        return -1;
×
735

736
    /** temporary local table to merge the per thread counters,
737
     *  especially needed for the average counters */
738
    struct CountersMergeTable {
3,032✔
739
        enum StatsType type;
3,032✔
740
        int64_t value;
3,032✔
741
        uint64_t updates;
3,032✔
742
    } merge_table[max_id];
3,032✔
743
    memset(&merge_table, 0x00,
3,032✔
744
           max_id * sizeof(struct CountersMergeTable));
3,032✔
745

746
    int thread = stats_ctx->sts_cnt - 1;
3,032✔
747
    StatsRecord *table = stats_table.stats;
3,032✔
748

749
    /* Loop through the thread counter stores. The global counters
750
     * are in a separate store inside this list. */
751
    sts = stats_ctx->sts;
3,032✔
752
    SCLogDebug("sts %p", sts);
3,032✔
753
    while (sts != NULL) {
27,341✔
754
        DEBUG_VALIDATE_BUG_ON(thread < 0);
24,309✔
755

756
        SCLogDebug("Thread %d %s ctx %p", thread, sts->name, sts->ctx);
24,309✔
757

758
        /* temporary table for quickly storing the counters for this
759
         * thread store, so that we can post process them outside
760
         * of the thread store lock */
761
        struct CountersMergeTable thread_table[max_id];
24,309✔
762
        memset(&thread_table, 0x00,
24,309✔
763
                max_id * sizeof(struct CountersMergeTable));
24,309✔
764

765
        StatsLocalCounter thread_table_from_private[max_id];
24,309✔
766
        memset(&thread_table_from_private, 0x00, max_id * sizeof(StatsLocalCounter));
24,309✔
767

768
        /* copy private table to a local variable to loop it w/o lock */
769
        bool skip = false;
24,309✔
770
        SCSpinLock(&sts->ctx->lock);
24,309✔
771
        const uint16_t table_size = sts->ctx->curr_id + sts->ctx->derive_id + 1;
24,309✔
772
        if (sts->ctx->copy_of_private == NULL) {
24,309✔
773
            skip = true;
×
774
        } else {
24,309✔
775
            memcpy(&thread_table_from_private, sts->ctx->copy_of_private,
24,309✔
776
                    table_size * sizeof(StatsLocalCounter));
24,309✔
777
        }
24,309✔
778
        SCSpinUnlock(&sts->ctx->lock);
24,309✔
779
        if (skip)
24,309✔
780
            goto next;
×
781

782
        /* loop counters and handle them. This includes the global counters, which
783
         * access the StatsCounters but don't modify them. */
784
        for (uint16_t i = 1; i < table_size; i++) {
6,595,812✔
785
            const StatsCounter *pc = sts->ctx->pc_array[i];
6,571,503✔
786
            thread_table[pc->gid].type = pc->type;
6,571,503✔
787

788
            table[pc->gid].name = pc->name;
6,571,503✔
789
            table[pc->gid].short_name = pc->short_name;
6,571,503✔
790

791
            switch (pc->type) {
6,571,503✔
792
                case STATS_TYPE_FUNC:
57,608✔
793
                    if (pc->Func != NULL)
57,608✔
794
                        thread_table[pc->gid].value = pc->Func();
57,608✔
795
                    break;
57,608✔
796
                case STATS_TYPE_AVERAGE:
18,434✔
797
                    thread_table[pc->gid].value = thread_table_from_private[i].v;
18,434✔
798
                    thread_table[pc->gid].updates = thread_table_from_private[i + 1].v;
18,434✔
799
                    /* skip updates row */
800
                    i++;
18,434✔
801
                    break;
18,434✔
802
                case STATS_TYPE_DERIVE_DIV:
15,082✔
803
                    SCLogDebug("counter %u/%u is derived from counters %u / %u", pc->id, pc->gid,
15,082✔
804
                            pc->did1, pc->did2);
15,082✔
805
                    thread_table[pc->gid].value = thread_table_from_private[pc->did1].v;
15,082✔
806
                    thread_table[pc->gid].updates = thread_table_from_private[pc->did2].v;
15,082✔
807
                    break;
15,082✔
808
                default:
6,480,379✔
809
                    SCLogDebug("Counter %s (%u:%u) value %" PRIu64, pc->name, pc->id, pc->gid,
6,480,379✔
810
                            thread_table_from_private[i].v);
6,480,379✔
811

812
                    thread_table[pc->gid].value = thread_table_from_private[i].v;
6,480,379✔
813
                    break;
6,480,379✔
814
            }
6,571,503✔
815
        }
6,571,503✔
816

817
        /* update merge table */
818
        for (uint16_t c = 0; c < max_id; c++) {
12,562,820✔
819
            const struct CountersMergeTable *e = &thread_table[c];
12,538,511✔
820
            /* thread only sets type if it has a counter
821
             * of this type. */
822
            if (e->type == 0)
12,538,511✔
823
                continue;
5,967,008✔
824

825
            switch (e->type) {
6,571,503✔
826
                case STATS_TYPE_MAXIMUM:
66,415✔
827
                    if (e->value > merge_table[c].value)
66,415✔
828
                        merge_table[c].value = e->value;
8,373✔
829
                    break;
66,415✔
830
                case STATS_TYPE_FUNC:
57,608✔
831
                    merge_table[c].value = e->value;
57,608✔
832
                    break;
57,608✔
833
                case STATS_TYPE_AVERAGE:
18,434✔
834
                default:
6,447,480✔
835
                    merge_table[c].value += e->value;
6,447,480✔
836
                    break;
6,447,480✔
837
            }
6,571,503✔
838
            merge_table[c].updates += e->updates;
6,571,503✔
839
            merge_table[c].type = e->type;
6,571,503✔
840
        }
6,571,503✔
841

842
        /* update per thread stats table */
843
        for (uint16_t c = 0; c < max_id; c++) {
12,562,820✔
844
            const struct CountersMergeTable *e = &thread_table[c];
12,538,511✔
845
            /* thread only sets type if it has a counter
846
             * of this type. */
847
            if (e->type == 0)
12,538,511✔
848
                continue;
5,967,008✔
849

850
            uint32_t offset = (thread * stats_table.nstats) + c;
6,571,503✔
851
            StatsRecord *r = &stats_table.tstats[offset];
6,571,503✔
852
            /* xfer previous value to pvalue and reset value */
853
            r->pvalue = r->value;
6,571,503✔
854
            r->value = 0;
6,571,503✔
855
            r->name = table[c].name;
6,571,503✔
856
            r->short_name = table[c].short_name;
6,571,503✔
857
            r->tm_name = sts->name;
6,571,503✔
858

859
            switch (e->type) {
6,571,503✔
860
                case STATS_TYPE_AVERAGE:
18,434✔
861
                case STATS_TYPE_DERIVE_DIV:
33,516✔
862
                    if (e->value > 0 && e->updates > 0) {
33,516✔
863
                        r->value = (uint64_t)(e->value / e->updates);
9,058✔
864
                    }
9,058✔
865
                    break;
33,516✔
866
                default:
6,537,987✔
867
                    r->value = e->value;
6,537,987✔
868
                    break;
6,537,987✔
869
            }
6,571,503✔
870
        }
6,571,503✔
871

872
    next:
24,309✔
873
        sts = sts->next;
24,309✔
874
        thread--;
24,309✔
875
    }
24,309✔
876

877
    /* transfer 'merge table' to final stats table */
878
    for (uint16_t x = 0; x < max_id; x++) {
1,561,679✔
879
        /* xfer previous value to pvalue and reset value */
880
        table[x].pvalue = table[x].value;
1,558,647✔
881
        table[x].value = 0;
1,558,647✔
882
        table[x].tm_name = "Total";
1,558,647✔
883

884
        const struct CountersMergeTable *m = &merge_table[x];
1,558,647✔
885
        switch (m->type) {
1,558,647✔
886
            case STATS_TYPE_MAXIMUM:
21,224✔
887
                if (m->value > table[x].value)
21,224✔
888
                    table[x].value = m->value;
8,354✔
889
                break;
21,224✔
890
            case STATS_TYPE_AVERAGE:
6,124✔
891
            case STATS_TYPE_DERIVE_DIV:
9,156✔
892
                if (m->value > 0 && m->updates > 0) {
9,156✔
893
                    table[x].value = (uint64_t)(m->value / m->updates);
8,004✔
894
                }
8,004✔
895
                break;
9,156✔
896
            default:
1,528,267✔
897
                table[x].value += m->value;
1,528,267✔
898
                break;
1,528,267✔
899
        }
1,558,647✔
900
    }
1,558,647✔
901

902
    /* invoke logger(s) */
903
    if (stats_loggers_active) {
3,032✔
904
        OutputStatsLog(tv, td, &stats_table);
3,032✔
905
    }
3,032✔
906
    return 1;
3,032✔
907
}
3,032✔
908

909
#ifdef BUILD_UNIX_SOCKET
910
/** \brief callback for getting stats into unix socket
911
 */
912
TmEcode StatsOutputCounterSocket(json_t *cmd,
913
                               json_t *answer, void *data)
914
{
6✔
915
    json_t *message = NULL;
6✔
916
    TmEcode r = TM_ECODE_OK;
6✔
917

918
    if (!stats_enabled) {
6✔
919
        r = TM_ECODE_FAILED;
×
920
        message = json_string("stats are disabled in the config");
×
921
    } else {
6✔
922
        SCMutexLock(&stats_table_mutex);
6✔
923
        if (stats_table.start_time == 0) {
6✔
924
            r = TM_ECODE_FAILED;
×
925
            message = json_string("stats not yet synchronized");
×
926
        } else {
6✔
927
            message = StatsToJSON(&stats_table, JSON_STATS_TOTALS|JSON_STATS_THREADS);
6✔
928
        }
6✔
929
        SCMutexUnlock(&stats_table_mutex);
6✔
930
    }
6✔
931
    json_object_set_new(answer, "message", message);
6✔
932
    return r;
6✔
933
}
6✔
934
#endif /* BUILD_UNIX_SOCKET */
935

936
static void StatsLogSummary(void)
937
{
3,432✔
938
    if (!stats_enabled) {
3,432✔
939
        return;
485✔
940
    }
485✔
941
    uint64_t alerts = 0;
2,947✔
942
    SCMutexLock(&stats_table_mutex);
2,947✔
943
    if (stats_table.start_time != 0) {
2,947✔
944
        const StatsTable *st = &stats_table;
2,946✔
945
        for (uint32_t u = 0; u < st->nstats; u++) {
772,480✔
946
            const char *name = st->stats[u].name;
772,280✔
947
            if (name == NULL || strcmp(name, "detect.alert") != 0)
772,280✔
948
                continue;
769,534✔
949
            alerts = st->stats[u].value;
2,746✔
950
            break;
2,746✔
951
        }
772,280✔
952
    }
2,946✔
953
    SCMutexUnlock(&stats_table_mutex);
2,947✔
954
    SCLogInfo("Alerts: %"PRIu64, alerts);
2,947✔
955
}
2,947✔
956

957
/**
958
 * \brief Initializes the perf counter api.  Things are hard coded currently.
959
 *        More work to be done when we implement multiple interfaces
960
 */
961
void StatsInit(void)
962
{
3,614✔
963
    BUG_ON(stats_ctx != NULL);
3,614✔
964
    if ((stats_ctx = SCCalloc(1, sizeof(StatsGlobalContext))) == NULL) {
3,614✔
965
        FatalError("Fatal error encountered in StatsInitCtx. Exiting...");
×
966
    }
×
967

968
    StatsPublicThreadContextInit(&stats_ctx->global_counter_ctx);
3,614✔
969
}
3,614✔
970

971
void StatsSetupPostConfigPreOutput(void)
972
{
3,599✔
973
    StatsInitCtxPreOutput();
3,599✔
974
}
3,599✔
975

976
void StatsSetupPostConfigPostOutput(void)
977
{
3,595✔
978
    StatsInitCtxPostOutput();
3,595✔
979
}
3,595✔
980

981

982
/**
983
 * \brief Spawns the wakeup, and the management thread used by the stats api
984
 *
985
 *  The threads use the condition variable in the thread vars to control
986
 *  their wait loops to make sure the main thread can quickly kill them.
987
 */
988
void StatsSpawnThreads(void)
989
{
3,431✔
990
    SCEnter();
3,431✔
991

992
    if (!stats_enabled) {
3,431✔
993
        SCReturn;
485✔
994
    }
485✔
995

996
    ThreadVars *tv_wakeup = NULL;
2,946✔
997
    ThreadVars *tv_mgmt = NULL;
2,946✔
998

999
    /* spawn the stats wakeup thread */
1000
    tv_wakeup = TmThreadCreateMgmtThread(thread_name_counter_wakeup,
2,946✔
1001
                                         StatsWakeupThread, 1);
2,946✔
1002
    if (tv_wakeup == NULL) {
2,946✔
1003
        FatalError("TmThreadCreateMgmtThread "
×
1004
                   "failed");
×
1005
    }
×
1006

1007
    if (TmThreadSpawn(tv_wakeup) != 0) {
2,946✔
1008
        FatalError("TmThreadSpawn failed for "
×
1009
                   "StatsWakeupThread");
×
1010
    }
×
1011

1012
    /* spawn the stats mgmt thread */
1013
    tv_mgmt = TmThreadCreateMgmtThread(thread_name_counter_stats,
2,946✔
1014
                                       StatsMgmtThread, 1);
2,946✔
1015
    if (tv_mgmt == NULL) {
2,946✔
1016
        FatalError("TmThreadCreateMgmtThread failed");
×
1017
    }
×
1018

1019
    if (TmThreadSpawn(tv_mgmt) != 0) {
2,946✔
1020
        FatalError("TmThreadSpawn failed for "
×
1021
                   "StatsWakeupThread");
×
1022
    }
×
1023

1024
    SCReturn;
2,946✔
1025
}
2,946✔
1026

1027
/**
1028
 * \brief Registers a normal, unqualified counter
1029
 *
1030
 * \param name Name of the counter, to be registered
1031
 * \param tv    Pointer to the ThreadVars instance for which the counter would
1032
 *              be registered
1033
 *
1034
 * \retval id Counter id for the newly registered counter, or the already
1035
 *            present counter
1036
 */
1037
StatsCounterId StatsRegisterCounter(const char *name, StatsThreadContext *stats)
1038
{
7,541,502✔
1039
    uint16_t id =
7,541,502✔
1040
            StatsRegisterQualifiedCounter(name, &stats->pub, STATS_TYPE_NORMAL, NULL, NULL, NULL);
7,541,502✔
1041
    StatsCounterId s = { .id = id };
7,541,502✔
1042
    return s;
7,541,502✔
1043
}
7,541,502✔
1044

1045
/**
1046
 * \brief Registers a counter, whose value holds the average of all the values
1047
 *        assigned to it.
1048
 *
1049
 * \param name Name of the counter, to be registered
1050
 * \param tv    Pointer to the ThreadVars instance for which the counter would
1051
 *              be registered
1052
 *
1053
 * \retval id Counter id for the newly registered counter, or the already
1054
 *            present counter
1055
 */
1056
StatsCounterAvgId StatsRegisterAvgCounter(const char *name, StatsThreadContext *stats)
1057
{
20,540✔
1058
    uint16_t id =
20,540✔
1059
            StatsRegisterQualifiedCounter(name, &stats->pub, STATS_TYPE_AVERAGE, NULL, NULL, NULL);
20,540✔
1060
    StatsCounterAvgId s = { .id = id };
20,540✔
1061
    return s;
20,540✔
1062
}
20,540✔
1063

1064
/**
1065
 * \brief Registers a counter, whose value holds the maximum of all the values
1066
 *        assigned to it.
1067
 *
1068
 * \param name Name of the counter, to be registered
1069
 * \param tv    Pointer to the ThreadVars instance for which the counter would
1070
 *              be registered
1071
 *
1072
 * \retval the counter id for the newly registered counter, or the already
1073
 *         present counter
1074
 */
1075
StatsCounterMaxId StatsRegisterMaxCounter(const char *name, StatsThreadContext *stats)
1076
{
75,097✔
1077
    uint16_t id =
75,097✔
1078
            StatsRegisterQualifiedCounter(name, &stats->pub, STATS_TYPE_MAXIMUM, NULL, NULL, NULL);
75,097✔
1079
    StatsCounterMaxId s = { .id = id };
75,097✔
1080
    return s;
75,097✔
1081
}
75,097✔
1082

1083
/**
1084
 * \brief Registers a counter, which represents a global value
1085
 *
1086
 * \param name Name of the counter, to be registered
1087
 * \param Func  Function Pointer returning a uint64_t
1088
 *
1089
 * \retval id Counter id for the newly registered counter, or the already
1090
 *            present counter
1091
 */
1092
StatsCounterGlobalId StatsRegisterGlobalCounter(const char *name, uint64_t (*Func)(void))
1093
{
69,677✔
1094
    StatsCounterGlobalId s = { .id = 0 };
69,677✔
1095
#if defined (UNITTESTS) || defined (FUZZ)
1,482✔
1096
    if (stats_ctx == NULL)
1,482✔
1097
        return s;
1,464✔
1098
#else
1099
    BUG_ON(stats_ctx == NULL);
68,195✔
1100
#endif
68,195✔
1101
    uint16_t id = StatsRegisterQualifiedCounter(
68,213✔
1102
            name, &(stats_ctx->global_counter_ctx), STATS_TYPE_FUNC, Func, NULL, NULL);
68,213✔
1103
    s.id = id;
68,213✔
1104
    return s;
68,213✔
1105
}
69,677✔
1106

1107
/**
1108
 * \brief Registers a counter which tracks the result of the calculating the value
1109
 * of counter dname1 divided by the value of the counter dname2
1110
 *
1111
 * \param name Name of the counter, to be registered
1112
 * \param dname1 First counter name
1113
 * \param dname2 Second counter name
1114
 *
1115
 * Both counters need to already be registered in this thread.
1116
 *
1117
 * \retval id Counter id for the newly registered counter, or the already
1118
 *            present counter
1119
 */
1120
StatsCounterDeriveId StatsRegisterDeriveDivCounter(
1121
        const char *name, const char *dname1, const char *dname2, StatsThreadContext *stats)
1122
{
17,069✔
1123
    StatsCounterDeriveId s = { .id = 0 };
17,069✔
1124
#if defined(UNITTESTS) || defined(FUZZ)
1125
    if (stats_ctx == NULL)
1126
        return s;
1127
#else
1128
    BUG_ON(stats_ctx == NULL);
17,069✔
1129
#endif
17,069✔
1130
    uint16_t id = StatsRegisterQualifiedCounter(
17,069✔
1131
            name, &stats->pub, STATS_TYPE_DERIVE_DIV, NULL, dname1, dname2);
17,069✔
1132
    s.id = id;
17,069✔
1133
    return s;
17,069✔
1134
}
17,069✔
1135

1136
typedef struct CountersIdType_ {
1137
    uint16_t id;
1138
    const char *string;
1139
} CountersIdType;
1140

1141
static uint32_t CountersIdHashFunc(HashTable *ht, void *data, uint16_t datalen)
1142
{
9,066,618✔
1143
    CountersIdType *t = (CountersIdType *)data;
9,066,618✔
1144
    uint32_t hash = 0;
9,066,618✔
1145
    size_t len = strlen(t->string);
9,066,618✔
1146

1147
    for (size_t i = 0; i < len; i++)
247,709,249✔
1148
        hash += u8_tolower((unsigned char)t->string[i]);
238,642,631✔
1149

1150
    hash = hash % ht->array_size;
9,066,618✔
1151
    return hash;
9,066,618✔
1152
}
9,066,618✔
1153

1154
static char CountersIdHashCompareFunc(void *data1, uint16_t datalen1,
1155
                               void *data2, uint16_t datalen2)
1156
{
12,377,584✔
1157
    CountersIdType *t1 = (CountersIdType *)data1;
12,377,584✔
1158
    CountersIdType *t2 = (CountersIdType *)data2;
12,377,584✔
1159

1160
    if (t1 == NULL || t2 == NULL)
12,377,584✔
1161
        return 0;
×
1162

1163
    if (t1->string == NULL || t2->string == NULL)
12,377,584✔
1164
        return 0;
×
1165

1166
    return strcmp(t1->string, t2->string) == 0;
12,377,584✔
1167
}
12,377,584✔
1168

1169
static void CountersIdHashFreeFunc(void *data)
1170
{
1,744,272✔
1171
    SCFree(data);
1,744,272✔
1172
}
1,744,272✔
1173

1174
static int StatsThreadSetupPublic(StatsPublicThreadContext *pctx)
1175
{
26,839✔
1176
    size_t array_size = pctx->curr_id + pctx->derive_id + 1;
26,839✔
1177
    pctx->pc_array = SCCalloc(array_size, sizeof(StatsCounter *));
26,839✔
1178
    if (pctx->pc_array == NULL) {
26,839✔
1179
        return -1;
×
1180
    }
×
1181
    /* regular counters that get direct updates by their id as idx */
1182
    for (StatsCounter *pc = pctx->head; pc != NULL; pc = pc->next) {
7,349,201✔
1183
        if (pc->type != STATS_TYPE_DERIVE_DIV) {
7,322,362✔
1184
            SCLogDebug("pc %s gid %u id %u", pc->name, pc->gid, pc->id);
7,305,386✔
1185
            BUG_ON(pctx->pc_array[pc->id] != NULL);
7,305,386✔
1186
            pctx->pc_array[pc->id] = pc;
7,305,386✔
1187
        }
7,305,386✔
1188
    }
7,322,362✔
1189
    /* derive counters are not updated by the thread itself and will be put
1190
     * at the end of the array */
1191
    for (StatsCounter *pc = pctx->head; pc != NULL; pc = pc->next) {
7,349,201✔
1192
        if (pc->type == STATS_TYPE_DERIVE_DIV) {
7,322,362✔
1193
            uint16_t id = pctx->curr_id + pc->id;
16,976✔
1194
            SCLogDebug("STATS_TYPE_DERIVE_DIV: pc %s gid %u pc->id %u id %u", pc->name, pc->gid,
16,976✔
1195
                    pc->id, id);
16,976✔
1196
            BUG_ON(pctx->pc_array[id] != NULL);
16,976✔
1197
            pctx->pc_array[id] = pc;
16,976✔
1198
        }
16,976✔
1199
    }
7,322,362✔
1200

1201
    SCLogDebug("array_size %u memory %" PRIu64, (uint32_t)array_size,
26,839✔
1202
            (uint64_t)(array_size * sizeof(StatsLocalCounter)));
26,839✔
1203
    pctx->copy_of_private = SCCalloc(array_size, sizeof(StatsLocalCounter));
26,839✔
1204
    if (pctx->copy_of_private == NULL) {
26,839✔
1205
        return -1;
×
1206
    }
×
1207
    return 0;
26,839✔
1208
}
26,839✔
1209

1210
/** \internal
1211
 *  \brief Adds a TM to the clubbed TM table.  Multiple instances of the same TM
1212
 *         are stacked together in a PCTMI container.
1213
 *
1214
 *  \param tm_name Name of the tm to be added to the table
1215
 *  \param pctx    StatsPublicThreadContext associated with the TM tm_name
1216
 *
1217
 *  \retval 1 on success, 0 on failure
1218
 */
1219
static int StatsThreadRegister(const char *thread_name, StatsPublicThreadContext *pctx)
1220
{
26,836✔
1221
    if (stats_ctx == NULL) {
26,836✔
1222
        return 1;
3✔
1223
    }
3✔
1224

1225
    if (thread_name == NULL || pctx == NULL) {
26,833✔
UNCOV
1226
        SCLogDebug("supplied argument(s) to StatsThreadRegister NULL");
×
UNCOV
1227
        return 0;
×
UNCOV
1228
    }
×
1229

1230
    SCMutexLock(&stats_ctx->sts_lock);
26,833✔
1231
    SCLogDebug("thread %s", thread_name);
26,833✔
1232
    if (stats_ctx->counters_id_hash == NULL) {
26,833✔
1233
        stats_ctx->counters_id_hash = HashTableInit(256, CountersIdHashFunc,
3,438✔
1234
                                                              CountersIdHashCompareFunc,
3,438✔
1235
                                                              CountersIdHashFreeFunc);
3,438✔
1236
        if (stats_ctx->counters_id_hash == NULL) {
3,438✔
1237
            SCMutexUnlock(&stats_ctx->sts_lock);
×
1238
            return 0;
×
1239
        }
×
1240
    }
3,438✔
1241
    StatsCounter *pc = pctx->head;
26,833✔
1242
    while (pc != NULL) {
7,349,179✔
1243
        CountersIdType t = { 0, pc->name }, *id = NULL;
7,322,346✔
1244
        id = HashTableLookup(stats_ctx->counters_id_hash, &t, sizeof(t));
7,322,346✔
1245
        if (id == NULL) {
7,322,346✔
1246
            id = SCCalloc(1, sizeof(*id));
1,744,272✔
1247
            BUG_ON(id == NULL);
1,744,272✔
1248
            id->id = counters_global_id++;
1,744,272✔
1249
            id->string = pc->name;
1,744,272✔
1250
            int r = HashTableAdd(stats_ctx->counters_id_hash, id, sizeof(*id));
1,744,272✔
1251
            DEBUG_VALIDATE_BUG_ON(r < 0);
1,744,272✔
1252
            if (r < 0) {
1,744,272✔
1253
                SCMutexUnlock(&stats_ctx->sts_lock);
×
1254
                return 0;
×
1255
            }
×
1256
        }
1,744,272✔
1257
        pc->gid = id->id;
7,322,346✔
1258
        pc = pc->next;
7,322,346✔
1259
    }
7,322,346✔
1260

1261
    if (StatsThreadSetupPublic(pctx) != 0) {
26,833✔
1262
        SCLogDebug("failed to setup StatsThreadSetupPublic");
×
1263
        SCMutexUnlock(&stats_ctx->sts_lock);
×
1264
        return 0;
×
1265
    }
×
1266

1267
    StatsThreadStore *temp = NULL;
26,833✔
1268
    if ((temp = SCCalloc(1, sizeof(StatsThreadStore))) == NULL) {
26,833✔
1269
        SCMutexUnlock(&stats_ctx->sts_lock);
×
1270
        return 0;
×
1271
    }
×
1272

1273
    temp->ctx = pctx;
26,833✔
1274
    temp->name = thread_name;
26,833✔
1275

1276
    temp->next = stats_ctx->sts;
26,833✔
1277
    stats_ctx->sts = temp;
26,833✔
1278
    stats_ctx->sts_cnt++;
26,833✔
1279
    SCLogDebug("stats_ctx->sts %p", stats_ctx->sts);
26,833✔
1280

1281
    SCMutexUnlock(&stats_ctx->sts_lock);
26,833✔
1282
    return 1;
26,833✔
1283
}
26,833✔
1284

1285
/** \internal
1286
 *  \brief Returns a counter array for all counters registered for this tm
1287
 *         instance
1288
 *
1289
 *  \param pctx Pointer to the tv's StatsPublicThreadContext
1290
 *
1291
 *  \retval pca Pointer to a counter-array for all counter of this tm instance
1292
 *              on success; NULL on failure
1293
 */
1294
static int StatsGetAllCountersArray(
1295
        StatsPublicThreadContext *pctx, StatsPrivateThreadContext *private)
1296
{
23,897✔
1297
    if (pctx == NULL || private == NULL)
23,897✔
1298
        return -1;
1✔
1299

1300
    private->size = pctx->curr_id + 1;
23,896✔
1301

1302
    private->head = SCCalloc(private->size, sizeof(StatsLocalCounter));
23,896✔
1303
    if (private->head == NULL) {
23,896✔
1304
        return -1;
×
1305
    }
×
1306

1307
    private->initialized = 1;
23,896✔
1308
    return 0;
23,896✔
1309
}
23,896✔
1310

1311
int StatsSetupPrivate(StatsThreadContext *stats, const char *thread_name)
1312
{
23,890✔
1313
    int r = StatsGetAllCountersArray(&stats->pub, &stats->priv);
23,890✔
1314
    if (r < 0) {
23,890✔
1315
        return -1;
×
1316
    }
×
1317

1318
    r = StatsThreadRegister(thread_name, &stats->pub);
23,890✔
1319
    if (r != 1) {
23,890✔
UNCOV
1320
        return -2;
×
UNCOV
1321
    }
×
1322
    return 0;
23,890✔
1323
}
23,890✔
1324

1325
static void StatsThreadInitPublic(StatsPublicThreadContext *pctx)
1326
{
30,569✔
1327
    memset(pctx, 0x00, sizeof(*pctx));
30,569✔
1328
    SCSpinInit(&pctx->lock, 0);
30,569✔
1329
}
30,569✔
1330

1331
void StatsThreadInit(StatsThreadContext *stats)
1332
{
30,566✔
1333
    StatsThreadInitPublic(&stats->pub);
30,566✔
1334
}
30,566✔
1335

1336
/**
1337
 * \brief the private stats store with the public stats store
1338
 *
1339
 * \param pca      Pointer to the StatsPrivateThreadContext
1340
 * \param pctx     Pointer the tv's StatsPublicThreadContext
1341
 *
1342
 * \retval  1 on success
1343
 * \retval -1 on error
1344
 */
1345
static int StatsUpdateCounterArray(StatsPrivateThreadContext *pca, StatsPublicThreadContext *pctx)
1346
{
59,533✔
1347

1348
    if (pca == NULL || pctx == NULL) {
59,537✔
1349
        SCLogDebug("pca or pctx is NULL inside StatsUpdateCounterArray");
×
1350
        return -1;
×
1351
    }
×
1352

1353
    if (pca->size > 0 && pctx->copy_of_private != NULL) {
59,538✔
1354
        /* copy the whole table under lock to the public section
1355
         * and release the lock. The stats thread will copy it from
1356
         * there. */
1357
        SCSpinLock(&pctx->lock);
59,533✔
1358
        memcpy(pctx->copy_of_private, pca->head, pca->size * sizeof(StatsLocalCounter));
59,533✔
1359
        SCSpinUnlock(&pctx->lock);
59,533✔
1360
    }
59,533✔
1361
    SC_ATOMIC_SET(pctx->sync_now, false);
59,533✔
1362
    return 1;
59,533✔
1363
}
59,533✔
1364

1365
/**
1366
 * \brief Get the value of the local copy of the counter that hold this id.
1367
 *
1368
 * \param tv threadvars
1369
 * \param id The counter id.
1370
 *
1371
 * \retval  0 on success.
1372
 * \retval -1 on error.
1373
 */
1374
int64_t StatsCounterGetLocalValue(StatsThreadContext *stats, StatsCounterId id)
1375
{
84✔
1376
    StatsPrivateThreadContext *pca = &stats->priv;
84✔
1377
#ifdef DEBUG
1378
    BUG_ON((id.id < 1) || (id.id > pca->size));
1379
#endif
1380
    return pca->head[id.id].v;
84✔
1381
}
84✔
1382

1383
/**
1384
 * \brief Releases the resources allotted by the Stats API
1385
 */
1386
void StatsReleaseResources(void)
1387
{
3,432✔
1388
    StatsLogSummary();
3,432✔
1389
    StatsReleaseCtx();
3,432✔
1390
}
3,432✔
1391

1392
/**
1393
 * \brief Releases counters
1394
 *
1395
 * \param head Pointer to the head of the list of perf counters that have to
1396
 *             be freed
1397
 */
1398
static void StatsReleaseCounters(StatsCounter *head)
1399
{
34,036✔
1400
    StatsCounter *pc = NULL;
34,036✔
1401

1402
    while (head != NULL) {
7,371,782✔
1403
        pc = head;
7,337,746✔
1404
        head = head->next;
7,337,746✔
1405
        StatsReleaseCounter(pc);
7,337,746✔
1406
    }
7,337,746✔
1407
}
34,036✔
1408

1409
/** \internal
1410
 *  \brief Releases the StatsPrivateThreadContext allocated by the user,
1411
 *         for storing and updating local counter values
1412
 *
1413
 * \param pca Pointer to the StatsPrivateThreadContext
1414
 */
1415
static void StatsReleasePrivateThreadContext(StatsPrivateThreadContext *pca)
1416
{
30,603✔
1417
    if (pca != NULL) {
30,603✔
1418
        if (pca->head != NULL) {
30,603✔
1419
            SCFree(pca->head);
23,846✔
1420
            pca->head = NULL;
23,846✔
1421
            pca->size = 0;
23,846✔
1422
        }
23,846✔
1423
        pca->initialized = 0;
30,603✔
1424
    }
30,603✔
1425
}
30,603✔
1426

1427
void StatsThreadCleanup(StatsThreadContext *stats)
1428
{
30,603✔
1429
    StatsPublicThreadContextCleanup(&stats->pub);
30,603✔
1430
    StatsReleasePrivateThreadContext(&stats->priv);
30,603✔
1431
}
30,603✔
1432

1433
/*----------------------------------Unit_Tests--------------------------------*/
1434

1435
#ifdef UNITTESTS
1436
/** \internal
1437
 * \brief Registers a normal, unqualified counter
1438
 *
1439
 * \param name   Name of the counter, to be registered
1440
 * \param type    Datatype of this counter variable
1441
 * \param pctx    StatsPublicThreadContext corresponding to the tm_name key under which the
1442
 *                key has to be registered
1443
 *
1444
 * \retval id Counter id for the newly registered counter, or the already
1445
 *            present counter
1446
 */
1447
static StatsCounterId RegisterCounter(
1448
        const char *name, const char *tm_name, StatsPublicThreadContext *pctx)
1449
{
23✔
1450
    uint16_t id = StatsRegisterQualifiedCounter(name, pctx, STATS_TYPE_NORMAL, NULL, NULL, NULL);
23✔
1451
    StatsCounterId s = { .id = id };
23✔
1452
    return s;
23✔
1453
}
23✔
1454

1455
static int StatsTestCounterReg02(void)
1456
{
1✔
1457
    StatsPublicThreadContext pctx;
1✔
1458
    StatsThreadInitPublic(&pctx);
1✔
1459

1460
    StatsCounterId id = RegisterCounter(NULL, NULL, &pctx);
1✔
1461
    FAIL_IF_NOT(id.id == 0);
1✔
1462
    PASS;
1✔
1463
}
1✔
1464

1465
static int StatsTestCounterReg03(void)
1466
{
1✔
1467
    StatsPublicThreadContext pctx;
1✔
1468
    StatsThreadInitPublic(&pctx);
1✔
1469

1470
    StatsCounterId id = RegisterCounter("t1", "c1", &pctx);
1✔
1471
    FAIL_IF_NOT(id.id == 1);
1✔
1472

1473
    StatsReleaseCounters(pctx.head);
1✔
1474
    PASS;
1✔
1475
}
1✔
1476

1477
static int StatsTestCounterReg04(void)
1478
{
1✔
1479
    StatsPublicThreadContext pctx;
1✔
1480
    StatsThreadInitPublic(&pctx);
1✔
1481

1482
    StatsCounterId c1 = RegisterCounter("t1", "c1", &pctx);
1✔
1483
    FAIL_IF_NOT(c1.id == 1);
1✔
1484
    StatsCounterId c2 = RegisterCounter("t2", "c2", &pctx);
1✔
1485
    FAIL_IF_NOT(c2.id == 2);
1✔
1486
    StatsCounterId c3 = RegisterCounter("t3", "c3", &pctx);
1✔
1487
    FAIL_IF_NOT(c3.id == 3);
1✔
1488
    StatsCounterId id = RegisterCounter("t1", "c1", &pctx);
1✔
1489
    FAIL_IF_NOT(id.id == 1);
1✔
1490

1491
    StatsReleaseCounters(pctx.head);
1✔
1492
    PASS;
1✔
1493
}
1✔
1494

1495
static int StatsTestGetCntArray05(void)
1496
{
1✔
1497
    ThreadVars tv;
1✔
1498
    memset(&tv, 0, sizeof(ThreadVars));
1✔
1499
    StatsThreadInit(&tv.stats);
1✔
1500
    StatsCounterId c1 = RegisterCounter("t1", "c1", &tv.stats.pub);
1✔
1501
    FAIL_IF(c1.id != 1);
1✔
1502
    int r = StatsGetAllCountersArray(NULL, &tv.stats.priv);
1✔
1503
    FAIL_IF_NOT(r == -1);
1✔
1504
    StatsThreadCleanup(&tv.stats);
1✔
1505
    PASS;
1✔
1506
}
1✔
1507

1508
static int StatsTestGetCntArray06(void)
1509
{
1✔
1510
    ThreadVars tv;
1✔
1511
    memset(&tv, 0, sizeof(ThreadVars));
1✔
1512
    StatsThreadInit(&tv.stats);
1✔
1513
    StatsCounterId c1 = RegisterCounter("t1", "c1", &tv.stats.pub);
1✔
1514
    FAIL_IF(c1.id != 1);
1✔
1515
    StatsThreadSetupPublic(&tv.stats.pub);
1✔
1516
    int r = StatsGetAllCountersArray(&tv.stats.pub, &tv.stats.priv);
1✔
1517
    FAIL_IF_NOT(r == 0);
1✔
1518
    StatsThreadCleanup(&tv.stats);
1✔
1519
    PASS;
1✔
1520
}
1✔
1521

1522
static int StatsTestCntArraySize07(void)
1523
{
1✔
1524
    ThreadVars tv;
1✔
1525
    memset(&tv, 0, sizeof(ThreadVars));
1✔
1526
    StatsThreadInit(&tv.stats);
1✔
1527
    StatsPrivateThreadContext *pca = NULL;
1✔
1528

1529
    StatsCounterId id1 = RegisterCounter("t1", "c1", &tv.stats.pub);
1✔
1530
    StatsCounterId id2 = RegisterCounter("t2", "c2", &tv.stats.pub);
1✔
1531

1532
    StatsThreadSetupPublic(&tv.stats.pub);
1✔
1533
    StatsGetAllCountersArray(&tv.stats.pub, &tv.stats.priv);
1✔
1534
    pca = &tv.stats.priv;
1✔
1535

1536
    StatsCounterIncr(&tv.stats, id1);
1✔
1537
    StatsCounterIncr(&tv.stats, id2);
1✔
1538

1539
    FAIL_IF_NOT(pca->size == 3);
1✔
1540

1541
    StatsThreadCleanup(&tv.stats);
1✔
1542
    PASS;
1✔
1543
}
1✔
1544

1545
static int StatsTestUpdateCounter08(void)
1546
{
1✔
1547
    ThreadVars tv;
1✔
1548
    memset(&tv, 0, sizeof(ThreadVars));
1✔
1549
    StatsThreadInit(&tv.stats);
1✔
1550
    StatsCounterId c1 = RegisterCounter("t1", "c1", &tv.stats.pub);
1✔
1551
    StatsThreadSetupPublic(&tv.stats.pub);
1✔
1552
    StatsGetAllCountersArray(&tv.stats.pub, &tv.stats.priv);
1✔
1553
    StatsPrivateThreadContext *pca = &tv.stats.priv;
1✔
1554

1555
    StatsCounterIncr(&tv.stats, c1);
1✔
1556
    StatsCounterAddI64(&tv.stats, c1, 100);
1✔
1557
    FAIL_IF_NOT(pca->head[c1.id].v == 101);
1✔
1558

1559
    StatsThreadCleanup(&tv.stats);
1✔
1560
    PASS;
1✔
1561
}
1✔
1562

1563
static int StatsTestUpdateCounter09(void)
1564
{
1✔
1565
    ThreadVars tv;
1✔
1566
    memset(&tv, 0, sizeof(ThreadVars));
1✔
1567
    StatsThreadInit(&tv.stats);
1✔
1568

1569
    StatsCounterId c1 = RegisterCounter("t1", "c1", &tv.stats.pub);
1✔
1570
    RegisterCounter("t2", "c2", &tv.stats.pub);
1✔
1571
    RegisterCounter("t3", "c3", &tv.stats.pub);
1✔
1572
    RegisterCounter("t4", "c4", &tv.stats.pub);
1✔
1573
    StatsCounterId c5 = RegisterCounter("t5", "c5", &tv.stats.pub);
1✔
1574

1575
    StatsThreadSetupPublic(&tv.stats.pub);
1✔
1576
    StatsGetAllCountersArray(&tv.stats.pub, &tv.stats.priv);
1✔
1577
    StatsPrivateThreadContext *pca = &tv.stats.priv;
1✔
1578

1579
    StatsCounterIncr(&tv.stats, c5);
1✔
1580
    StatsCounterAddI64(&tv.stats, c5, 100);
1✔
1581

1582
    FAIL_IF_NOT(pca->head[c1.id].v == 0);
1✔
1583
    FAIL_IF_NOT(pca->head[c5.id].v == 101);
1✔
1584

1585
    StatsThreadCleanup(&tv.stats);
1✔
1586
    PASS;
1✔
1587
}
1✔
1588

1589
static int StatsTestUpdateGlobalCounter10(void)
1590
{
1✔
1591
    ThreadVars tv;
1✔
1592
    memset(&tv, 0, sizeof(ThreadVars));
1✔
1593
    StatsThreadInit(&tv.stats);
1✔
1594

1595
    StatsCounterId c1 = RegisterCounter("t1", "c1", &tv.stats.pub);
1✔
1596
    StatsCounterId c2 = RegisterCounter("t2", "c2", &tv.stats.pub);
1✔
1597
    StatsCounterId c3 = RegisterCounter("t3", "c3", &tv.stats.pub);
1✔
1598

1599
    StatsThreadSetupPublic(&tv.stats.pub);
1✔
1600
    StatsGetAllCountersArray(&tv.stats.pub, &tv.stats.priv);
1✔
1601
    StatsPrivateThreadContext *pca = &tv.stats.priv;
1✔
1602

1603
    StatsCounterIncr(&tv.stats, c1);
1✔
1604
    StatsCounterAddI64(&tv.stats, c2, 100);
1✔
1605
    StatsCounterIncr(&tv.stats, c3);
1✔
1606
    StatsCounterAddI64(&tv.stats, c3, 100);
1✔
1607

1608
    StatsUpdateCounterArray(pca, &tv.stats.pub);
1✔
1609

1610
    FAIL_IF_NOT(1 == tv.stats.pub.copy_of_private[c1.id].v);
1✔
1611
    FAIL_IF_NOT(100 == tv.stats.pub.copy_of_private[c2.id].v);
1✔
1612
    FAIL_IF_NOT(101 == tv.stats.pub.copy_of_private[c3.id].v);
1✔
1613

1614
    StatsThreadCleanup(&tv.stats);
1✔
1615
    PASS;
1✔
1616
}
1✔
1617

1618
static int StatsTestCounterValues11(void)
1619
{
1✔
1620
    ThreadVars tv;
1✔
1621
    memset(&tv, 0, sizeof(ThreadVars));
1✔
1622
    StatsThreadInit(&tv.stats);
1✔
1623

1624
    StatsCounterId c1 = RegisterCounter("t1", "c1", &tv.stats.pub);
1✔
1625
    StatsCounterId c2 = RegisterCounter("t2", "c2", &tv.stats.pub);
1✔
1626
    StatsCounterId c3 = RegisterCounter("t3", "c3", &tv.stats.pub);
1✔
1627
    StatsCounterId c4 = RegisterCounter("t4", "c4", &tv.stats.pub);
1✔
1628

1629
    StatsThreadSetupPublic(&tv.stats.pub);
1✔
1630
    StatsGetAllCountersArray(&tv.stats.pub, &tv.stats.priv);
1✔
1631
    StatsPrivateThreadContext *pca = &tv.stats.priv;
1✔
1632

1633
    StatsCounterIncr(&tv.stats, c1);
1✔
1634
    StatsCounterAddI64(&tv.stats, c2, 256);
1✔
1635
    StatsCounterAddI64(&tv.stats, c3, 257);
1✔
1636
    StatsCounterAddI64(&tv.stats, c4, 16843024);
1✔
1637

1638
    StatsUpdateCounterArray(pca, &tv.stats.pub);
1✔
1639

1640
    FAIL_IF_NOT(1 == tv.stats.pub.copy_of_private[c1.id].v);
1✔
1641
    FAIL_IF_NOT(256 == tv.stats.pub.copy_of_private[c2.id].v);
1✔
1642
    FAIL_IF_NOT(257 == tv.stats.pub.copy_of_private[c3.id].v);
1✔
1643
    FAIL_IF_NOT(16843024 == tv.stats.pub.copy_of_private[c4.id].v);
1✔
1644

1645
    StatsThreadCleanup(&tv.stats);
1✔
1646
    PASS;
1✔
1647
}
1✔
1648

1649
#endif
1650

1651
void StatsRegisterTests(void)
1652
{
1✔
1653
#ifdef UNITTESTS
1✔
1654
    UtRegisterTest("StatsTestCounterReg02", StatsTestCounterReg02);
1✔
1655
    UtRegisterTest("StatsTestCounterReg03", StatsTestCounterReg03);
1✔
1656
    UtRegisterTest("StatsTestCounterReg04", StatsTestCounterReg04);
1✔
1657
    UtRegisterTest("StatsTestGetCntArray05", StatsTestGetCntArray05);
1✔
1658
    UtRegisterTest("StatsTestGetCntArray06", StatsTestGetCntArray06);
1✔
1659
    UtRegisterTest("StatsTestCntArraySize07", StatsTestCntArraySize07);
1✔
1660
    UtRegisterTest("StatsTestUpdateCounter08", StatsTestUpdateCounter08);
1✔
1661
    UtRegisterTest("StatsTestUpdateCounter09", StatsTestUpdateCounter09);
1✔
1662
    UtRegisterTest("StatsTestUpdateGlobalCounter10",
1✔
1663
                   StatsTestUpdateGlobalCounter10);
1✔
1664
    UtRegisterTest("StatsTestCounterValues11", StatsTestCounterValues11);
1✔
1665
#endif
1✔
1666
}
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc