Skip to content

Commit 81f6f0d

Browse files
committed
Add simple benchmarking
Starting a function name with 'bench_' will now also store it and make clar run it, but only when invoked with the '-b' flag. In order to reduce how much clar overhead we measure, we put the timing code around the function call, which means we end up timing every function, but we only store the elapsed time in benchmark mode. The overhead from calling the timer is minimal, so it shouldn't present an issue to add these calls for every test. This is not meant for micro-benchmarks, but for situations where we expect the operations to take at least tens or hundreds of milliseconds and we expect the operations not to be easily mapped into the "repeat N times" method to suss out the noise, so we don't include this functionality. This is for "perform very expensive operation" to track high-scale changes though time. Thus we also provide cl_reset_timer() to allow the function under test to perform specific setup steps that it does not want to count towards the timing.
1 parent 7fed323 commit 81f6f0d

File tree

8 files changed

+229
-14
lines changed

8 files changed

+229
-14
lines changed

clar.c

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,15 @@ struct clar_error {
106106
struct clar_error *next;
107107
};
108108

109+
struct clar_timing {
110+
const char *test;
111+
const char *suite;
112+
113+
double elapsed;
114+
115+
struct clar_timing *next;
116+
};
117+
109118
static struct {
110119
int argc;
111120
char **argv;
@@ -124,9 +133,16 @@ static struct {
124133
int exit_on_error;
125134
int report_suite_names;
126135

136+
int run_benchmarks;
137+
double timing_start;
138+
double timing_end;
139+
127140
struct clar_error *errors;
128141
struct clar_error *last_error;
129142

143+
struct clar_timing *timings;
144+
struct clar_timing *last_timing;
145+
130146
void (*local_cleanup)(void *);
131147
void *local_cleanup_payload;
132148

@@ -141,6 +157,7 @@ static struct {
141157
struct clar_func {
142158
const char *name;
143159
void (*ptr)(void);
160+
int is_bench;
144161
};
145162

146163
struct clar_suite {
@@ -156,6 +173,7 @@ struct clar_suite {
156173
static void clar_print_init(int test_count, int suite_count, const char *suite_names);
157174
static void clar_print_shutdown(int test_count, int suite_count, int error_count);
158175
static void clar_print_error(int num, const struct clar_error *error);
176+
static void clar_print_timing(const struct clar_timing *timing);
159177
static void clar_print_ontest(const char *test_name, int test_number, enum cl_test_status failed);
160178
static void clar_print_onsuite(const char *suite_name, int suite_index);
161179
static void clar_print_onabort(const char *msg, ...);
@@ -164,6 +182,12 @@ static void clar_print_onabort(const char *msg, ...);
164182
static void clar_unsandbox(void);
165183
static int clar_sandbox(void);
166184

185+
/* From time.h */
186+
/**
187+
* Return the time from a monotonic timer.
188+
*/
189+
static double clar_timer(void);
190+
167191
/* Load the declarations for the test suite */
168192
#include "clar.suite"
169193

@@ -203,6 +227,23 @@ clar_report_errors(void)
203227
_clar.errors = _clar.last_error = NULL;
204228
}
205229

230+
static void
231+
clar_report_timings(void)
232+
{
233+
struct clar_timing *timing, *next;
234+
235+
timing = _clar.timings;
236+
237+
while (timing != NULL) {
238+
next = timing->next;
239+
clar_print_timing(timing);
240+
free(timing);
241+
timing = next;
242+
}
243+
244+
_clar.timings = _clar.last_timing = NULL;
245+
}
246+
206247
static void
207248
clar_run_test(
208249
const struct clar_func *test,
@@ -219,7 +260,9 @@ clar_run_test(
219260
initialize->ptr();
220261

221262
CL_TRACE(CL_TRACE__TEST__RUN_BEGIN);
263+
_clar.timing_start = clar_timer();
222264
test->ptr();
265+
_clar.timing_end = clar_timer();
223266
CL_TRACE(CL_TRACE__TEST__RUN_END);
224267
}
225268

@@ -246,6 +289,8 @@ clar_run_test(
246289
}
247290
}
248291

292+
static void clar_store_timing(void);
293+
249294
static void
250295
clar_run_suite(const struct clar_suite *suite, const char *filter)
251296
{
@@ -282,13 +327,23 @@ clar_run_suite(const struct clar_suite *suite, const char *filter)
282327
if (filter && strncmp(test[i].name, filter, matchlen))
283328
continue;
284329

330+
if (test[i].is_bench != _clar.run_benchmarks)
331+
continue;
332+
285333
_clar.active_test = test[i].name;
286334
clar_run_test(&test[i], &suite->initialize, &suite->cleanup);
287335

288336
if (_clar.exit_on_error && _clar.total_errors)
289337
return;
338+
339+
if (test[i].is_bench) {
340+
clar_store_timing();
341+
}
290342
}
291343

344+
puts("");
345+
clar_report_timings();
346+
292347
_clar.active_test = NULL;
293348
CL_TRACE(CL_TRACE__SUITE_END);
294349
}
@@ -305,6 +360,7 @@ clar_usage(const char *arg)
305360
printf(" -q \tOnly report tests that had an error\n");
306361
printf(" -Q \tQuit as soon as a test fails\n");
307362
printf(" -l \tPrint suite names\n");
363+
printf(" -b \tRun benchmarks instead of tests\n");
308364
exit(-1);
309365
}
310366

@@ -363,6 +419,10 @@ clar_parse_args(int argc, char **argv)
363419
break;
364420
}
365421

422+
case 'b':
423+
_clar.run_benchmarks = 1;
424+
break;
425+
366426
case 'q':
367427
_clar.report_errors_only = 1;
368428
break;
@@ -447,6 +507,23 @@ clar_test(int argc, char **argv)
447507
return errors;
448508
}
449509

510+
static void clar_store_timing(void)
511+
{
512+
struct clar_timing *timing = calloc(1, sizeof(struct clar_timing));
513+
514+
if (_clar.timings == NULL)
515+
_clar.timings = timing;
516+
517+
if (_clar.last_timing != NULL)
518+
_clar.last_timing->next = timing;
519+
520+
_clar.last_timing = timing;
521+
522+
timing->elapsed = _clar.timing_end - _clar.timing_start;
523+
timing->test = _clar.active_test;
524+
timing->suite = _clar.active_suite;
525+
}
526+
450527
static void abort_test(void)
451528
{
452529
if (!_clar.trampoline_enabled) {
@@ -460,6 +537,11 @@ static void abort_test(void)
460537
longjmp(_clar.trampoline, -1);
461538
}
462539

540+
void clar__reset_timer(void)
541+
{
542+
_clar.timing_start = clar_timer();
543+
}
544+
463545
void clar__skip(void)
464546
{
465547
_clar.test_status = CL_TEST_SKIP;
@@ -640,3 +722,4 @@ void cl_set_cleanup(void (*cleanup)(void *), void *opaque)
640722
#include "clar/fixtures.h"
641723
#include "clar/fs.h"
642724
#include "clar/print.h"
725+
#include "clar/time.h"

clar.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,11 @@ void cl_fixture_cleanup(const char *fixture_name);
110110

111111
#define cl_skip() clar__skip()
112112

113+
/**
114+
* Timer-related functions
115+
*/
116+
#define cl_reset_timer() clar__reset_timer()
117+
113118
/**
114119
* Typed assertion macros
115120
*/
@@ -133,6 +138,8 @@ void cl_fixture_cleanup(const char *fixture_name);
133138

134139
#define cl_assert_equal_p(p1,p2) clar__assert_equal(__FILE__,__LINE__,"Pointer mismatch: " #p1 " != " #p2, 1, "%p", (p1), (p2))
135140

141+
void clar__reset_timer(void);
142+
136143
void clar__skip(void);
137144

138145
void clar__fail(

clar/print.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ static void clar_print_error(int num, const struct clar_error *error)
3535
fflush(stdout);
3636
}
3737

38+
static void clar_print_timing(const struct clar_timing *timing)
39+
{
40+
printf("Benchmark %s::%s %f\n", timing->suite, timing->test, timing->elapsed);
41+
fflush(stdout);
42+
}
43+
3844
static void clar_print_ontest(const char *test_name, int test_number, enum cl_test_status status)
3945
{
4046
(void)test_name;

clar/time.h

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
#ifdef GIT_WIN32
2+
3+
double clar_timer(void)
4+
{
5+
/* We need the initial tick count to detect if the tick
6+
* count has rolled over. */
7+
static DWORD initial_tick_count = 0;
8+
9+
/* GetTickCount returns the number of milliseconds that have
10+
* elapsed since the system was started. */
11+
DWORD count = GetTickCount();
12+
13+
if(initial_tick_count == 0) {
14+
initial_tick_count = count;
15+
} else if (count < initial_tick_count) {
16+
/* The tick count has rolled over - adjust for it. */
17+
count = (0xFFFFFFFF - initial_tick_count) + count;
18+
}
19+
20+
return (double count / (double 1000;
21+
}
22+
23+
#elif __APPLE__
24+
25+
#include <mach/mach_time.h>
26+
27+
double clar_timer(void)
28+
{
29+
uint64_t time = mach_absolute_time();
30+
static double scaling_factor = 0;
31+
32+
if (scaling_factor == 0) {
33+
mach_timebase_info_data_t info;
34+
(void)mach_timebase_info(&info);
35+
scaling_factor = (double)info.numer / (double)info.denom;
36+
}
37+
38+
return (double)time * scaling_factor / 1.0E9;
39+
}
40+
41+
#elif defined(AMIGA)
42+
43+
#include <proto/timer.h>
44+
45+
double clar_timer(void)
46+
{
47+
struct TimeVal tv;
48+
ITimer->GetUpTime(&tv);
49+
return (doubletv.Seconds + (doubletv.Microseconds / 1.0E6));
50+
}
51+
52+
#else
53+
54+
#include <sys/time.h>
55+
56+
double clar_timer(void)
57+
{
58+
struct timespec tp;
59+
60+
if (clock_gettime(CLOCK_MONOTONIC, &tp) == 0) {
61+
return (double tp.tv_sec + (double tp.tv_nsec / 1.0E9));
62+
} else {
63+
/* Fall back to using gettimeofday */
64+
struct timeval tv;
65+
struct timezone tz;
66+
gettimeofday(&tv, &tz);
67+
return (doubletv.tv_sec + (doubletv.tv_usec / 1.0E6));
68+
}
69+
}
70+
71+
#endif

generate.py

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def __init__(self, module):
1818
def _render_callback(self, cb):
1919
if not cb:
2020
return ' { NULL, NULL }'
21-
return ' { "%s", &%s }' % (cb['short_name'], cb['symbol'])
21+
return ' { "%s", &%s, %d }' % (cb['short_name'], cb['symbol'], cb['bench'])
2222

2323
class DeclarationTemplate(Template):
2424
def render(self):
@@ -79,21 +79,13 @@ def _replacer(match):
7979

8080
return re.sub(SKIP_COMMENTS_REGEX, _replacer, text)
8181

82-
def parse(self, contents):
83-
TEST_FUNC_REGEX = r"^(void\s+(test_%s__(\w+))\s*\(\s*void\s*\))\s*\{"
84-
85-
contents = self._skip_comments(contents)
86-
regex = re.compile(TEST_FUNC_REGEX % self.name, re.MULTILINE)
87-
88-
self.callbacks = []
89-
self.initialize = None
90-
self.cleanup = None
91-
92-
for (declaration, symbol, short_name) in regex.findall(contents):
82+
def _append_callbacks(self, callbacks, is_bench):
83+
for (declaration, symbol, short_name) in callbacks:
9384
data = {
9485
"short_name" : short_name,
9586
"declaration" : declaration,
96-
"symbol" : symbol
87+
"symbol" : symbol,
88+
"bench" : is_bench,
9789
}
9890

9991
if short_name == 'initialize':
@@ -103,6 +95,21 @@ def parse(self, contents):
10395
else:
10496
self.callbacks.append(data)
10597

98+
def parse(self, contents):
99+
TEST_FUNC_REGEX = r"^(void\s+(test_%s__(\w+))\s*\(\s*void\s*\))\s*\{"
100+
BENCH_FUNC_REGEX = r"^(void\s+(bench_%s__(\w+))\s*\(\s*void\s*\))\s*\{"
101+
102+
contents = self._skip_comments(contents)
103+
test_regex = re.compile(TEST_FUNC_REGEX % self.name, re.MULTILINE)
104+
bench_regex = re.compile(BENCH_FUNC_REGEX % self.name, re.MULTILINE)
105+
106+
self.callbacks = []
107+
self.initialize = None
108+
self.cleanup = None
109+
110+
self._append_callbacks(test_regex.findall(contents), False)
111+
self._append_callbacks(bench_regex.findall(contents), True)
112+
106113
return self.callbacks != []
107114

108115
def refresh(self, path):

test/clar_test.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@
1212

1313
/* Your custom shared includes / defines here */
1414
extern int global_test_counter;
15+
extern int global_is_bench;
1516

1617
#endif

test/main.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
*/
1919

2020
int global_test_counter = 0;
21+
int global_is_bench = 0;
2122

2223
#ifdef _WIN32
2324
int __cdecl main(int argc, char *argv[])
@@ -34,7 +35,10 @@ int main(int argc, char *argv[])
3435
ret = clar_test(argc, argv);
3536

3637
/* Your custom cleanup here */
37-
cl_assert_equal_i(8, global_test_counter);
38+
if (global_is_bench)
39+
cl_assert_equal_i(3, global_test_counter);
40+
else
41+
cl_assert_equal_i(8, global_test_counter);
3842

3943
return ret;
4044
}

0 commit comments

Comments
 (0)