aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2021-12-04 18:31:18 -0800
committer3gg <3gg@shellblade.net>2021-12-04 18:31:18 -0800
commitcc96d69ed11c60a782cd8b993d4bdf2ce8c99560 (patch)
tree96ebf632b79f609ef341f747ec9cc27373c374e1
parentf8217d240d598f39f70047f7a623dd46312542c6 (diff)
Add timer library.
-rw-r--r--CMakeLists.txt1
-rw-r--r--timer/CMakeLists.txt23
-rw-r--r--timer/README.md2
-rw-r--r--timer/include/timer.h55
-rw-r--r--timer/src/timer.c96
-rw-r--r--timer/test/test.h185
-rw-r--r--timer/test/timer_test.c95
7 files changed, 457 insertions, 0 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 36df847..9b97945 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -7,3 +7,4 @@ add_subdirectory(list)
7add_subdirectory(listpool) 7add_subdirectory(listpool)
8add_subdirectory(log) 8add_subdirectory(log)
9add_subdirectory(mempool) 9add_subdirectory(mempool)
10add_subdirectory(timer)
diff --git a/timer/CMakeLists.txt b/timer/CMakeLists.txt
new file mode 100644
index 0000000..d915163
--- /dev/null
+++ b/timer/CMakeLists.txt
@@ -0,0 +1,23 @@
1cmake_minimum_required(VERSION 3.10)
2
3project(timer)
4
5# Library
6
7set(SRC
8 src/timer.c)
9
10add_library(timer ${SRC})
11
12target_include_directories(timer PUBLIC include)
13
14target_compile_options(timer PRIVATE -Wall -Wextra)
15
16# Test
17
18add_executable(timer_test
19 test/timer_test.c)
20
21target_link_libraries(timer_test timer)
22
23target_compile_options(timer_test PRIVATE -DUNIT_TEST -DNDEBUG -Wall -Wextra)
diff --git a/timer/README.md b/timer/README.md
new file mode 100644
index 0000000..14855e5
--- /dev/null
+++ b/timer/README.md
@@ -0,0 +1,2 @@
1# timer
2Cross-platform, high precision timer.
diff --git a/timer/include/timer.h b/timer/include/timer.h
new file mode 100644
index 0000000..0274c69
--- /dev/null
+++ b/timer/include/timer.h
@@ -0,0 +1,55 @@
1#pragma once
2
3#include <stdint.h>
4
5/// A particular point in time.
6#ifdef _WIN32
7typedef uint64_t time_point;
8#else
9#include <time.h>
10typedef struct timespec time_point;
11#endif
12
13/// Time elapsed between two time points.
14typedef uint64_t time_delta;
15
16/// A high resolution timer.
17typedef struct {
18 time_point start_time; // The instant the timer was last started.
19 time_point last_tick; // The instant the timer was last ticked.
20 time_delta running_time; // Time elapsed since the timer was last started.
21 time_delta delta_time; // Time elapsed since the last tick.
22} Timer;
23
24/// Construct a new timer.
25Timer timer_make(void);
26
27/// Start the timer.
28/// This sets the time point from which time deltas are measured.
29/// Calling this multilple times resets the timer.
30void timer_start(Timer*);
31
32/// Update the timer's running and delta times.
33void timer_tick(Timer*);
34
35/// Get the current time.
36time_point time_now(void);
37
38/// Return the time elapsed between two timestamps.
39time_delta time_diff(time_point start, time_point end);
40
41/// Return the time elapsed in seconds.
42double time_delta_to_sec(time_delta dt);
43
44/// Convert the time elapsed in seconds to a time delta.
45time_delta sec_to_time_delta(double seconds);
46
47/// Put the caller thread to sleep for the given amount of time.
48void time_sleep(time_delta dt);
49
50/// The time point 0.
51#ifdef _WIN32
52static const time_point time_zero = 0;
53#else
54static const time_point time_zero = {0, 0};
55#endif
diff --git a/timer/src/timer.c b/timer/src/timer.c
new file mode 100644
index 0000000..2ee8ef5
--- /dev/null
+++ b/timer/src/timer.c
@@ -0,0 +1,96 @@
1#include "timer.h"
2
3#include <stdlib.h>
4
5#ifdef _WIN32
6static const int64_t microseconds = 1000000;
7#endif
8static const int64_t nanoseconds = 1000000000;
9
10#ifdef _WIN32
11#define WIN32_LEAN_AND_MEAN
12#include <Windows.h>
13#endif
14
15#ifdef _WIN32
16static double seconds_per_count;
17#endif
18
19static void timer_initialise() {
20#ifdef _WIN32
21 __int64 counts_per_sec;
22 QueryPerformanceFrequency((LARGE_INTEGER*)&counts_per_sec);
23 seconds_per_count = 1.0 / (double)counts_per_sec;
24#endif
25}
26
27Timer timer_make(void) {
28 timer_initialise();
29 Timer timer = {0};
30 timer_start(&timer);
31 return timer;
32}
33
34void timer_start(Timer* timer) {
35 timer->start_time = time_now();
36 timer->last_tick = timer->start_time;
37 timer->running_time = 0;
38 timer->delta_time = 0;
39}
40
41void timer_tick(Timer* timer) {
42 const time_point this_tick = time_now();
43 timer->running_time = time_diff(timer->start_time, this_tick);
44 timer->delta_time = time_diff(timer->last_tick, this_tick);
45 timer->last_tick = this_tick;
46}
47
48time_point time_now(void) {
49 time_point t;
50#ifdef _WIN32
51 QueryPerformanceCounter((LARGE_INTEGER*)&t);
52#else
53 clock_gettime(CLOCK_REALTIME, &t);
54#endif
55 return t;
56}
57
58time_delta time_diff(time_point start, time_point end) {
59#ifdef _WIN32
60 // Force nonnegative. The DXSDK's CDXUTTimer mentions that if the
61 // processor goes into a power save mode or we get shuffled to
62 // another processor, then the delta time can be negative.
63 return std::max(0, end - start);
64#else
65 return (end.tv_sec - start.tv_sec) * 1e9 + (end.tv_nsec - start.tv_nsec);
66#endif
67}
68
69double time_delta_to_sec(time_delta dt) {
70#ifdef _WIN32
71 return (double)dt * seconds_per_count;
72#else
73 return (double)dt * 1.0e-9;
74#endif
75}
76
77time_delta sec_to_time_delta(double seconds) {
78#ifdef _WIN32
79 return (time_delta)(seconds / seconds_per_count);
80#else
81 return (time_delta)(seconds * 1.0e9);
82#endif
83}
84
85void time_sleep(time_delta dt) {
86#ifdef _WIN32
87 const int64_t ms = dt / microseconds;
88 Sleep((DWORD)(ms));
89#else
90 const int64_t sec = dt / nanoseconds;
91 struct timespec ts;
92 ts.tv_sec = (long)sec;
93 ts.tv_nsec = (long)(dt % nanoseconds);
94 nanosleep(&ts, NULL);
95#endif
96}
diff --git a/timer/test/test.h b/timer/test/test.h
new file mode 100644
index 0000000..fd8dc22
--- /dev/null
+++ b/timer/test/test.h
@@ -0,0 +1,185 @@
1// SPDX-License-Identifier: MIT
2#pragma once
3
4#ifdef UNIT_TEST
5
6#include <stdbool.h>
7#include <stdio.h>
8#include <stdlib.h>
9#include <string.h>
10
11#if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || \
12 defined(__NetBSD__) || defined(__OpenBSD__)
13#define USE_SYSCTL_FOR_ARGS 1
14// clang-format off
15#include <sys/types.h>
16#include <sys/sysctl.h>
17// clang-format on
18#include <unistd.h> // getpid
19#endif
20
21struct test_file_metadata;
22
23struct test_failure {
24 bool present;
25 const char *message;
26 const char *file;
27 int line;
28};
29
30struct test_case_metadata {
31 void (*fn)(struct test_case_metadata *, struct test_file_metadata *);
32 struct test_failure failure;
33 const char *name;
34 struct test_case_metadata *next;
35};
36
37struct test_file_metadata {
38 bool registered;
39 const char *name;
40 struct test_file_metadata *next;
41 struct test_case_metadata *tests;
42};
43
44struct test_file_metadata __attribute__((weak)) * test_file_head;
45
46#define SET_FAILURE(_message) \
47 metadata->failure = (struct test_failure) { \
48 .message = _message, .file = __FILE__, .line = __LINE__, .present = true, \
49 }
50
51#define TEST_EQUAL(a, b) \
52 do { \
53 if ((a) != (b)) { \
54 SET_FAILURE(#a " != " #b); \
55 return; \
56 } \
57 } while (0)
58
59#define TEST_TRUE(a) \
60 do { \
61 if (!(a)) { \
62 SET_FAILURE(#a " is not true"); \
63 return; \
64 } \
65 } while (0)
66
67#define TEST_STREQUAL(a, b) \
68 do { \
69 if (strcmp(a, b) != 0) { \
70 SET_FAILURE(#a " != " #b); \
71 return; \
72 } \
73 } while (0)
74
75#define TEST_CASE(_name) \
76 static void __test_h_##_name(struct test_case_metadata *, \
77 struct test_file_metadata *); \
78 static struct test_file_metadata __test_h_file; \
79 static struct test_case_metadata __test_h_meta_##_name = { \
80 .name = #_name, \
81 .fn = __test_h_##_name, \
82 }; \
83 static void __attribute__((constructor(101))) __test_h_##_name##_register(void) { \
84 __test_h_meta_##_name.next = __test_h_file.tests; \
85 __test_h_file.tests = &__test_h_meta_##_name; \
86 if (!__test_h_file.registered) { \
87 __test_h_file.name = __FILE__; \
88 __test_h_file.next = test_file_head; \
89 test_file_head = &__test_h_file; \
90 __test_h_file.registered = true; \
91 } \
92 } \
93 static void __test_h_##_name( \
94 struct test_case_metadata *metadata __attribute__((unused)), \
95 struct test_file_metadata *file_metadata __attribute__((unused)))
96
97extern void __attribute__((weak)) (*test_h_unittest_setup)(void);
98/// Run defined tests, return true if all tests succeeds
99/// @param[out] tests_run if not NULL, set to whether tests were run
100static inline void __attribute__((constructor(102))) run_tests(void) {
101 bool should_run = false;
102#ifdef USE_SYSCTL_FOR_ARGS
103 int mib[] = {
104 CTL_KERN,
105#if defined(__NetBSD__) || defined(__OpenBSD__)
106 KERN_PROC_ARGS,
107 getpid(),
108 KERN_PROC_ARGV,
109#else
110 KERN_PROC,
111 KERN_PROC_ARGS,
112 getpid(),
113#endif
114 };
115 char *arg = NULL;
116 size_t arglen;
117 sysctl(mib, sizeof(mib) / sizeof(mib[0]), NULL, &arglen, NULL, 0);
118 arg = malloc(arglen);
119 sysctl(mib, sizeof(mib) / sizeof(mib[0]), arg, &arglen, NULL, 0);
120#else
121 FILE *cmdlinef = fopen("/proc/self/cmdline", "r");
122 char *arg = NULL;
123 int arglen;
124 fscanf(cmdlinef, "%ms%n", &arg, &arglen);
125 fclose(cmdlinef);
126#endif
127 for (char *pos = arg; pos < arg + arglen; pos += strlen(pos) + 1) {
128 if (strcmp(pos, "--unittest") == 0) {
129 should_run = true;
130 break;
131 }
132 }
133 free(arg);
134
135 if (!should_run) {
136 return;
137 }
138
139 if (&test_h_unittest_setup) {
140 test_h_unittest_setup();
141 }
142
143 struct test_file_metadata *i = test_file_head;
144 int failed = 0, success = 0;
145 while (i) {
146 fprintf(stderr, "Running tests from %s:\n", i->name);
147 struct test_case_metadata *j = i->tests;
148 while (j) {
149 fprintf(stderr, "\t%s ... ", j->name);
150 j->failure.present = false;
151 j->fn(j, i);
152 if (j->failure.present) {
153 fprintf(stderr, "failed (%s at %s:%d)\n", j->failure.message,
154 j->failure.file, j->failure.line);
155 failed++;
156 } else {
157 fprintf(stderr, "passed\n");
158 success++;
159 }
160 j = j->next;
161 }
162 fprintf(stderr, "\n");
163 i = i->next;
164 }
165 int total = failed + success;
166 fprintf(stderr, "Test results: passed %d/%d, failed %d/%d\n", success, total,
167 failed, total);
168 exit(failed == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
169}
170
171#else
172
173#include <stdbool.h>
174
175#define TEST_CASE(name) static void __attribute__((unused)) __test_h_##name(void)
176
177#define TEST_EQUAL(a, b) \
178 (void)(a); \
179 (void)(b)
180#define TEST_TRUE(a) (void)(a)
181#define TEST_STREQUAL(a, b) \
182 (void)(a); \
183 (void)(b)
184
185#endif
diff --git a/timer/test/timer_test.c b/timer/test/timer_test.c
new file mode 100644
index 0000000..a220ead
--- /dev/null
+++ b/timer/test/timer_test.c
@@ -0,0 +1,95 @@
1#include "timer.h"
2
3#include <stdio.h>
4
5#include "test.h"
6
7// Sleep puts the calling thread to sleep for a time >= than the given time.
8TEST_CASE(sleep) {
9 const double sleep_time_sec = 0.1;
10
11 const time_point start = time_now();
12 time_sleep(sec_to_time_delta(sleep_time_sec));
13 const time_point end = time_now();
14
15 TEST_TRUE(time_delta_to_sec(time_diff(start, end)) >= sleep_time_sec);
16}
17
18// The timer starts as soon as it is created.
19TEST_CASE(start_on_new) {
20 const time_point test_start_time = time_now();
21 Timer timer = timer_make();
22 TEST_TRUE(time_delta_to_sec(time_diff(test_start_time, timer.start_time)) >=
23 0.0);
24}
25
26// Tick updates the timer's last tick time.
27TEST_CASE(tick_updates_last_tick_time) {
28 const double sleep_time_sec = 0.1;
29
30 Timer timer = timer_make();
31 time_sleep(sec_to_time_delta(sleep_time_sec));
32 timer_tick(&timer);
33
34 TEST_TRUE(time_delta_to_sec(time_diff(timer.start_time, timer.last_tick)) >=
35 sleep_time_sec);
36}
37
38// Tick updates the timer's delta time.
39TEST_CASE(tick_updates_delta_time) {
40 const double sleep_time_sec = 0.1;
41
42 Timer timer = timer_make();
43 time_sleep(sec_to_time_delta(sleep_time_sec));
44 timer_tick(&timer);
45
46 TEST_TRUE(time_delta_to_sec(timer.delta_time) >= sleep_time_sec);
47}
48
49// Tick leaves the timer's start time unchanged.
50TEST_CASE(tick_does_not_change_start_time) {
51 Timer timer = timer_make();
52 const time_point start_time = timer.start_time;
53 time_sleep(sec_to_time_delta(0.1));
54 timer_tick(&timer);
55 TEST_TRUE(time_delta_to_sec(time_diff(start_time, timer.start_time)) == 0.0);
56}
57
58// Start starts/restarts the timer and updates the timer's start time.
59TEST_CASE(start_restarts_start_time) {
60 const double sleep_time_seconds = 0.1;
61 Timer timer = timer_make();
62 const time_point start_time = timer.start_time;
63 time_sleep(sec_to_time_delta(sleep_time_seconds));
64 timer_start(&timer);
65 TEST_TRUE(time_delta_to_sec(time_diff(start_time, timer.start_time)) >=
66 sleep_time_seconds);
67}
68
69// Count the number of hundred-seconds in a second.
70TEST_CASE(count) {
71 Timer timer = timer_make();
72
73 int hundred_seconds = 0;
74 const time_point start = timer.start_time;
75 {
76 while (time_delta_to_sec(timer.running_time) <= 1.0) {
77 hundred_seconds++;
78 time_sleep(sec_to_time_delta(0.1));
79 timer_tick(&timer);
80 TEST_TRUE(time_delta_to_sec(timer.delta_time) >= 0.1);
81 }
82 }
83 const time_point end = time_now();
84
85 const double time_elapsed = time_delta_to_sec(time_diff(start, end));
86
87 TEST_EQUAL(hundred_seconds, 10);
88 TEST_TRUE(time_elapsed >= 1.0);
89}
90
91int main(int argc, const char** argv) {
92 (void)argc; // Unused.
93 printf("Usage: %s --unittest\n", argv[0]);
94 return 0;
95}