OpenVPN 3 Core Library
Loading...
Searching...
No Matches
test_cpu_time.cpp
Go to the documentation of this file.
1// OpenVPN -- An application to securely tunnel IP networks
2// over a single port, with support for SSL/TLS-based
3// session authentication and key exchange,
4// packet encryption, packet authentication, and
5// packet compression.
6//
7// Copyright (C) 2019- OpenVPN Inc.
8//
9// SPDX-License-Identifier: MPL-2.0 OR AGPL-3.0-only WITH openvpn3-openssl-exception
10//
11
12
13// We have two set of measurements for these tests
14//
15// 1. A coarse measurement based on time()
16// These are tracked in 'chk_start' and 'chk_end'
17//
18// 2. A fine grained measurement from cpu_time()
19// These are tracked in 'start' and 'end'
20//
21// We calculate the difference before and after a
22// a workload has run, to measure how long it ran.
23// This is done for both measurement approaches.
24// The runtime is saved in runtime and chk_runtime
25//
26// To pass this test, absolute difference between
27// runtime and chk_runtime must be less than 1 second.
28//
29
30// #define DEBUG // Define this macro to get more details
31
32#include "test_common.hpp"
33#include <cstdint>
34#include <unistd.h>
35#include <memory>
36#include <mutex>
38#include <algorithm>
39
40#ifdef DEBUG
41#define DEBUG_DUMP(msg, st, en, rt, chst, chen, chrt, md) \
42 std::cout << msg << std::endl \
43 << "start = " << std::to_string(st) << std::endl \
44 << "end = " << std::to_string(en) << std::endl \
45 << "runtime = " << std::to_string(rt) << std::endl \
46 << "chk_start = " << std::to_string(chst) << std::endl \
47 << "chk_end = " << std::to_string(chen) << std::endl \
48 << "chk_runtime = " << std::to_string(chrt) << std::endl \
49 << "measurement difference = " \
50 << std::to_string(md) << std::endl \
51 << "--------------------------------------" << std::endl
52
53#else
54#define DEBUG_DUMP(msg, st, en, rt, chst, chen, chrt, md) \
55 { \
56 }
57#endif
58
59#define MEASURE(v, chkv, thread) \
60 double v = openvpn::cpu_time(thread); \
61 ASSERT_GE(v, 0); \
62 double chkv = static_cast<double>(time(NULL))
63
64#define CALCULATE(msg, st, en, rt, chst, chen, chrt, md) \
65 double rt = en - st; \
66 double chrt = chen - chst; \
67 double md = std::max(rt, chrt) - std::min(rt, chrt); \
68 DEBUG_DUMP(msg, st, en, rt, chst, chen, chrt, md)
69
70
71typedef std::shared_ptr<std::thread> ThreadPtr;
72
73
74// For simplicty, keep the total thread runtime
75// in a global variable, protected by a mutex
76// on updates
77std::mutex update_guard;
78double thread_runtime = 0;
79
80void update_thread_runtime(double val)
81{
82 std::lock_guard<std::mutex> ug(update_guard);
83 thread_runtime += val;
84}
85
86
87namespace unittests {
88static void workload(const uint16_t multiplier)
89{
90 // A very simple busy loop workload
91 //
92 // We can't use sleep() or any similar timing
93 // as this does not increase the tracked runtime
94 // in the kernel; the process does not really run.
95 //
96
97 std::random_device rd;
98 std::mt19937 gen(rd()); // Standard mersenne_twister_engine seeded with rd()
99
100
101 double d = 0;
102 for (unsigned int i = UINT16_MAX * multiplier; i > 0; i--)
103 {
104 d += static_cast<double>(gen());
105 }
106 (void)d;
107}
108
109
110TEST(CPUTime, cpu_time_pid)
111{
112 // Measure the runtime of the workload
113 MEASURE(start, chk_start, false);
114 workload(400);
115 MEASURE(end, chk_end, false);
116
117 // Calculate runtimes and differences
118 CALCULATE("single PID",
119 start,
120 end,
121 runtime,
122 chk_start,
123 chk_end,
124 chk_runtime,
125 measurement_diff);
126
127 ASSERT_LT(measurement_diff, 10);
128}
129
130
131void worker_thread(const uint8_t id)
132{
133 MEASURE(thr_start, chk_thr_start, true);
134 workload(400);
135 MEASURE(thr_end, chk_thr_end, true);
136
137 CALCULATE("Worker thread " << std::to_string(id),
138 thr_start,
139 thr_end,
140 thr_runtime,
141 chk_thr_start,
142 chk_thr_end,
143 chk_thr_runtime,
144 thr_measurement_diff);
145 update_thread_runtime(thr_runtime);
146
147 // Since the chk_thr_runtime (chk_thr_end - chk_thr_start)
148 // is based on epoc seconds of the system, this doesn't
149 // give a too good number when running multiple threads.
150 //
151 // If more threads are running on the same CPU core,
152 // one of the threads might be preempted. The clock time
153 // (chk_thr_runtime) will continue to tick, but the real
154 // runtime (thr_runtime) will not. Which will increase
155 // the difference between the measured runtimes.
156 //
157 // The value of 5 is just an educated guess of what
158 // we might find acceptable. This might be too high
159 // on an idle system, but too low on a loaded system.
160 //
161 ASSERT_LT(thr_measurement_diff, 5);
162}
163
164
165void run_threads(const uint8_t num_threads)
166{
167 std::vector<ThreadPtr> threads;
168 for (uint8_t i = 0; i < num_threads; i++)
169 {
170 ThreadPtr tp;
171 tp = std::make_shared<std::thread>([id = i]()
172 { worker_thread(id); });
173 threads.push_back(std::move(tp));
174 }
175
176 for (const auto &t : threads)
177 {
178 t->join();
179 }
180}
181
182
183TEST(CPUTime, cpu_time_thread_1)
184{
185 // Meassure running a single worker thread
186 MEASURE(parent_start, chk_parent_start, false);
187 run_threads(1);
188 MEASURE(parent_end, chk_parent_end, false);
189
190 CALCULATE("Parent thread - 1 child thread",
191 parent_start,
192 parent_end,
193 runtime,
194 chk_parent_start,
195 chk_parent_end,
196 chk_runtime,
197 parent_diff);
198
199 ASSERT_LT(parent_diff, 10);
200}
201
202
203TEST(CPUTime, cpu_time_thread_numcores)
204{
205 // Use number of available cores
206 auto num_cores = std::min(std::thread::hardware_concurrency(), 1u);
207
208 // Meassure running a single worker thread
209 MEASURE(parent_start, chk_parent_start, false);
210 run_threads(static_cast<uint8_t>(num_cores));
211 MEASURE(parent_end, chk_parent_end, false);
212
213 CALCULATE("Parent thread - " << std::to_string(num_cores) << " child thread",
214 parent_start,
215 parent_end,
216 runtime,
217 chk_parent_start,
218 chk_parent_end,
219 chk_runtime,
220 parent_diff);
221#ifdef DEBUG
222 std::cout << "Total thread runtime: " << std::to_string(thread_runtime) << std::endl;
223#endif
224
225 // The main process (this PID) will have a total runtime
226 // which accounts for all runtime of the running threads.
227 // We still give a bit extra slack, to reduce the risk of
228 // false positives, due to the possibility of premption
229 // (see comment in worker_thread() for details). But
230 // the difference should not neccesarily deviate as much
231 // here.
232 ASSERT_LT(parent_diff, 3 + thread_runtime);
233}
234} // namespace unittests
static void worker_thread()
Definition cli.cpp:822
TEST(CPUTime, cpu_time_pid)
static void workload(const uint16_t multiplier)
void run_threads(const uint8_t num_threads)
std::mutex update_guard
double thread_runtime
#define MEASURE(v, chkv, thread)
void update_thread_runtime(double val)
#define CALCULATE(msg, st, en, rt, chst, chen, chrt, md)
std::shared_ptr< std::thread > ThreadPtr