MailJDTourDownloadDocsQualityTestsCoverage

Which source lines and branches the tests execute. Lines 91.5 %, branches 75.0 % (minimum 85 % and 72 %). main @ 399ffe3d · 21 October 2026 · pipeline 7238.

GCC Code Coverage Report


Directory: src/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 95.3% 287 / 0 / 301
Functions: 100.0% 34 / 0 / 34
Branches: 85.9% 238 / 0 / 277

performance/PerformanceTrace.cpp
Line Branch Exec Source
1 #include "performance/PerformanceTrace.h"
2
3 #include <QDir>
4 #include <QFile>
5 #include <QFileInfo>
6 #include <QJsonDocument>
7 #include <QJsonValue>
8 #include <QRegularExpression>
9 #include <QTimer>
10
11 #include <algorithm>
12 #include <chrono>
13 #include <utility>
14
15 namespace MailJD::Performance {
16
17 namespace {
18
19 2814 QJsonValue optionalCount(qint64 value) {
20
2/2
✓ Branch 2 → 3 taken 2372 times.
✓ Branch 2 → 4 taken 442 times.
2814 return value < 0 ? QJsonValue(QJsonValue::Null) : QJsonValue(value);
21 }
22
23 1407 QJsonValue optionalOutcome(const QString &value) {
24
2/2
✓ Branch 3 → 4 taken 1146 times.
✓ Branch 3 → 5 taken 261 times.
1407 return value.isEmpty() ? QJsonValue(QJsonValue::Null) : QJsonValue(value);
25 }
26
27 } // namespace
28
29 219 Operation::Operation(std::shared_ptr<State> state) : m_state(std::move(state)) {}
30
31 16016 Operation::~Operation() {
32
8/8
✓ Branch 3 → 4 taken 43 times.
✓ Branch 3 → 10 taken 15973 times.
✓ Branch 5 → 6 taken 15 times.
✓ Branch 5 → 10 taken 28 times.
✓ Branch 8 → 9 taken 1 time.
✓ Branch 8 → 10 taken 14 times.
✓ Branch 11 → 12 taken 1 time.
✓ Branch 11 → 13 taken 16015 times.
16016 if (m_state && m_state.use_count() == 1 && !m_state->terminal.load())
33 1 finish(Outcome::Cancelled);
34 16016 }
35
36 45800 bool Operation::isValid() const {
37
4/6
✓ Branch 3 → 4 taken 2155 times.
✓ Branch 3 → 10 taken 43645 times.
✓ Branch 5 → 6 taken 2155 times.
✗ Branch 5 → 10 not taken.
✓ Branch 8 → 9 taken 2155 times.
✗ Branch 8 → 10 not taken.
45800 return m_state && m_state->trace && !m_state->operationId.isEmpty();
38 }
39
40 18 QString Operation::id() const {
41
2/2
✓ Branch 3 → 4 taken 17 times.
✓ Branch 3 → 6 taken 1 time.
18 return m_state ? m_state->operationId : QString();
42 }
43
44 1 QString Operation::scenario() const {
45
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 6 taken 1 time.
1 return m_state ? m_state->scenarioName : QString();
46 }
47
48 59 bool Operation::mark(const QString &phase, qint64 itemCount,
49 qint64 byteCount) const {
50
5/6
✓ Branch 3 → 4 taken 59 times.
✗ Branch 3 → 9 not taken.
✓ Branch 6 → 7 taken 55 times.
✓ Branch 6 → 9 taken 4 times.
✓ Branch 11 → 12 taken 10 times.
✓ Branch 11 → 13 taken 49 times.
114 if (!isValid() || m_state->terminal.load() ||
51
2/2
✓ Branch 8 → 9 taken 6 times.
✓ Branch 8 → 10 taken 49 times.
55 !PerformanceTrace::isSafeToken(phase))
52 10 return false;
53
2/2
✓ Branch 16 → 17 taken 49 times.
✓ Branch 23 → 24 taken 49 times.
98 m_state->trace->enqueue(m_state->trace->baseRecord(
54
1/1
✓ Branch 22 → 23 taken 49 times.
49 m_state->operationId, m_state->scenarioName, phase,
55 98 QStringLiteral("marker"), PerformanceTrace::monotonicNowNs(), 0, {},
56 itemCount, byteCount));
57 49 return true;
58 }
59
60 412 bool Operation::startPhase(const QString &phase, qint64 itemCount,
61 qint64 byteCount) const {
62
5/6
✓ Branch 3 → 4 taken 412 times.
✗ Branch 3 → 9 not taken.
✓ Branch 6 → 7 taken 408 times.
✓ Branch 6 → 9 taken 4 times.
✓ Branch 11 → 12 taken 8 times.
✓ Branch 11 → 13 taken 404 times.
820 if (!isValid() || m_state->terminal.load() ||
63
3/3
✓ Branch 7 → 8 taken 408 times.
✓ Branch 8 → 9 taken 4 times.
✓ Branch 8 → 10 taken 404 times.
408 !PerformanceTrace::isSafeToken(phase))
64 8 return false;
65
66
1/1
✓ Branch 13 → 14 taken 404 times.
404 const qint64 now = PerformanceTrace::monotonicNowNs();
67 {
68 404 QMutexLocker lock(&m_state->mutex);
69
2/2
✓ Branch 18 → 19 taken 2 times.
✓ Branch 18 → 20 taken 402 times.
404 if (m_state->openPhases.contains(phase))
70 2 return false;
71
1/1
✓ Branch 21 → 22 taken 402 times.
402 m_state->openPhases.insert(phase, now);
72 404 }
73
1/1
✓ Branch 36 → 37 taken 402 times.
804 m_state->trace->enqueue(m_state->trace->baseRecord(
74
1/1
✓ Branch 34 → 36 taken 402 times.
402 m_state->operationId, m_state->scenarioName, phase,
75 804 QStringLiteral("span_start"), now, 0, {}, itemCount, byteCount));
76 402 return true;
77 }
78
79 386 bool Operation::endPhase(const QString &phase, qint64 itemCount,
80 qint64 byteCount) const {
81
5/6
✓ Branch 3 → 4 taken 386 times.
✗ Branch 3 → 9 not taken.
✓ Branch 6 → 7 taken 382 times.
✓ Branch 6 → 9 taken 4 times.
✓ Branch 11 → 12 taken 4 times.
✓ Branch 11 → 13 taken 382 times.
768 if (!isValid() || m_state->terminal.load() ||
82
1/2
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 382 times.
382 !PerformanceTrace::isSafeToken(phase))
83 4 return false;
84
85 382 qint64 start = 0;
86 {
87 382 QMutexLocker lock(&m_state->mutex);
88
1/1
✓ Branch 16 → 17 taken 382 times.
382 auto it = m_state->openPhases.find(phase);
89
2/2
✓ Branch 20 → 21 taken 32 times.
✓ Branch 20 → 22 taken 350 times.
382 if (it == m_state->openPhases.end())
90 32 return false;
91 350 start = it.value();
92
1/1
✓ Branch 25 → 26 taken 350 times.
350 m_state->openPhases.erase(it);
93 382 }
94 350 const qint64 now = PerformanceTrace::monotonicNowNs();
95
1/1
✓ Branch 42 → 43 taken 350 times.
1050 m_state->trace->enqueue(m_state->trace->baseRecord(
96
1/1
✓ Branch 40 → 42 taken 350 times.
350 m_state->operationId, m_state->scenarioName, phase,
97 1050 QStringLiteral("span_end"), now, std::max<qint64>(0, now - start), {},
98 itemCount, byteCount));
99 350 return true;
100 }
101
102 210 bool Operation::finish(Outcome outcome, qint64 itemCount,
103 qint64 byteCount) const {
104
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 210 times.
210 if (!isValid())
105 ✗ return false;
106 210 bool expected = false;
107
2/2
✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 9 taken 209 times.
210 if (!m_state->terminal.compare_exchange_strong(expected, true))
108 1 return false;
109
110
1/1
✓ Branch 9 → 10 taken 209 times.
209 const qint64 now = PerformanceTrace::monotonicNowNs();
111 209 QList<QPair<QString, qint64>> unfinished;
112 {
113 209 QMutexLocker lock(&m_state->mutex);
114
1/1
✓ Branch 14 → 15 taken 209 times.
209 unfinished.reserve(m_state->openPhases.size());
115 209 for (auto it = m_state->openPhases.cbegin();
116
2/2
✓ Branch 26 → 17 taken 52 times.
✓ Branch 26 → 27 taken 209 times.
261 it != m_state->openPhases.cend(); ++it) {
117
1/1
✓ Branch 20 → 21 taken 52 times.
52 unfinished.append({it.key(), it.value()});
118 }
119 209 m_state->openPhases.clear();
120 209 }
121
3/3
✓ Branch 30 → 31 taken 209 times.
✓ Branch 31 → 32 taken 209 times.
✓ Branch 32 → 33 taken 209 times.
209 std::sort(unfinished.begin(), unfinished.end(),
122 8 [](const auto &left, const auto &right) {
123 8 return left.first < right.first;
124 });
125
4/4
✓ Branch 33 → 34 taken 209 times.
✓ Branch 34 → 35 taken 209 times.
✓ Branch 56 → 36 taken 52 times.
✓ Branch 56 → 57 taken 209 times.
261 for (const auto &[phase, start] : unfinished) {
126
1/1
✓ Branch 49 → 50 taken 52 times.
156 m_state->trace->enqueue(m_state->trace->baseRecord(
127
1/1
✓ Branch 48 → 49 taken 52 times.
52 m_state->operationId, m_state->scenarioName, phase,
128 156 QStringLiteral("span_end"), now, std::max<qint64>(0, now - start),
129
1/1
✓ Branch 41 → 42 taken 52 times.
104 PerformanceTrace::outcomeName(Outcome::Cancelled), -1, -1));
130 }
131
1/1
✓ Branch 69 → 70 taken 209 times.
627 m_state->trace->enqueue(m_state->trace->baseRecord(
132
1/1
✓ Branch 68 → 69 taken 209 times.
209 m_state->operationId, m_state->scenarioName, m_state->terminalPhase,
133 418 QStringLiteral("terminal"), now,
134 209 std::max<qint64>(0, now - m_state->startedNs),
135
1/1
✓ Branch 59 → 60 taken 209 times.
418 PerformanceTrace::outcomeName(outcome), itemCount, byteCount));
136 209 return true;
137 209 }
138
139 30197 PerformanceTrace &PerformanceTrace::instance() {
140
4/7
✓ Branch 2 → 3 taken 44 times.
✓ Branch 2 → 8 taken 30153 times.
✓ Branch 4 → 5 taken 44 times.
✗ Branch 4 → 8 not taken.
✓ Branch 5 → 6 taken 44 times.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 12 not taken.
30197 static PerformanceTrace trace;
141 30197 return trace;
142 }
143
144 44 PerformanceTrace::~PerformanceTrace() { shutdown(); }
145
146 6226 qint64 PerformanceTrace::monotonicNowNs() {
147 6226 return std::chrono::duration_cast<std::chrono::nanoseconds>(
148 12452 std::chrono::steady_clock::now().time_since_epoch())
149 6226 .count();
150 }
151
152 261 QString PerformanceTrace::outcomeName(Outcome outcome) {
153
4/5
✓ Branch 2 → 3 taken 119 times.
✓ Branch 2 → 8 taken 66 times.
✓ Branch 2 → 13 taken 74 times.
✓ Branch 2 → 18 taken 2 times.
✗ Branch 2 → 23 not taken.
261 switch (outcome) {
154 119 case Outcome::Success:
155 119 return QStringLiteral("success");
156 66 case Outcome::Failed:
157 66 return QStringLiteral("failed");
158 74 case Outcome::Cancelled:
159 74 return QStringLiteral("cancelled");
160 2 case Outcome::Timeout:
161 2 return QStringLiteral("timeout");
162 }
163 ✗ return QStringLiteral("failed");
164 }
165
166 2827 bool PerformanceTrace::isSafeToken(const QString &value) {
167 static const QRegularExpression safe(
168
4/7
✓ Branch 2 → 3 taken 10 times.
✓ Branch 2 → 15 taken 2817 times.
✓ Branch 4 → 5 taken 10 times.
✗ Branch 4 → 15 not taken.
✓ Branch 9 → 10 taken 10 times.
✗ Branch 29 → 30 not taken.
✗ Branch 29 → 31 not taken.
2837 QStringLiteral("^[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$"));
169
2/2
✓ Branch 16 → 17 taken 2827 times.
✓ Branch 17 → 18 taken 2827 times.
2827 return safe.match(value).hasMatch();
170 }
171
172 189 bool PerformanceTrace::validateContext(const TraceContext &context,
173 QString *error) const {
174 const QList<QPair<QString, QString>> fields = {
175 378 {QStringLiteral("run_id"), context.runId},
176 378 {QStringLiteral("data_profile"), context.dataProfile},
177 378 {QStringLiteral("cache_state"), context.cacheState},
178 378 {QStringLiteral("network_profile"), context.networkProfile},
179 378 {QStringLiteral("build_sha"), context.buildSha},
180 378 {QStringLiteral("build_type"), context.buildType},
181 378 {QStringLiteral("selected_scenario"), context.selectedScenario},
182
2/4
✓ Branch 32 → 33 taken 1323 times.
✓ Branch 32 → 34 taken 189 times.
✗ Branch 74 → 75 not taken.
✗ Branch 74 → 76 not taken.
1512 };
183
2/2
✓ Branch 68 → 50 taken 1296 times.
✓ Branch 68 → 69 taken 180 times.
1476 for (const auto &[name, value] : fields) {
184
3/3
✓ Branch 53 → 54 taken 1296 times.
✓ Branch 54 → 55 taken 9 times.
✓ Branch 54 → 66 taken 1287 times.
1296 if (!isSafeToken(value)) {
185
2/2
✓ Branch 55 → 56 taken 8 times.
✓ Branch 55 → 65 taken 1 time.
9 if (error)
186 16 *error = QStringLiteral("Unsafe or invalid performance context field: ") +
187
1/1
✓ Branch 59 → 60 taken 8 times.
8 name;
188 9 return false;
189 }
190 }
191 180 return true;
192 1701 }
193
194 191 bool PerformanceTrace::configure(const QString &path,
195 const TraceContext &context, QString *error) {
196
1/1
✓ Branch 2 → 3 taken 191 times.
191 shutdown();
197
2/2
✓ Branch 4 → 5 taken 2 times.
✓ Branch 4 → 14 taken 189 times.
191 if (path.isEmpty()) {
198
2/2
✓ Branch 5 → 6 taken 1 time.
✓ Branch 5 → 13 taken 1 time.
2 if (error)
199 1 *error = QStringLiteral("Performance trace path is empty");
200 2 return false;
201 }
202
3/3
✓ Branch 14 → 15 taken 189 times.
✓ Branch 15 → 16 taken 9 times.
✓ Branch 15 → 17 taken 180 times.
189 if (!validateContext(context, error))
203 9 return false;
204
205
1/1
✓ Branch 17 → 18 taken 180 times.
180 QFileInfo info(path);
206
4/4
✓ Branch 18 → 19 taken 180 times.
✓ Branch 19 → 20 taken 180 times.
✓ Branch 21 → 22 taken 2 times.
✓ Branch 21 → 31 taken 178 times.
180 if (!info.dir().exists()) {
207
2/2
✓ Branch 22 → 23 taken 1 time.
✓ Branch 22 → 30 taken 1 time.
2 if (error)
208 1 *error = QStringLiteral("Performance trace parent directory does not exist");
209 2 return false;
210 }
211
1/1
✓ Branch 31 → 32 taken 178 times.
178 QFile probe(path);
212
3/3
✓ Branch 33 → 34 taken 178 times.
✓ Branch 34 → 35 taken 2 times.
✓ Branch 34 → 48 taken 176 times.
178 if (!probe.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
213
2/2
✓ Branch 35 → 36 taken 1 time.
✓ Branch 35 → 47 taken 1 time.
2 if (error)
214 2 *error = QStringLiteral("Cannot create performance trace: ") +
215
2/2
✓ Branch 36 → 37 taken 1 time.
✓ Branch 40 → 41 taken 1 time.
3 probe.errorString();
216 2 return false;
217 }
218
1/1
✓ Branch 48 → 49 taken 176 times.
176 probe.close();
219
220 {
221
1/1
✓ Branch 49 → 50 taken 176 times.
176 std::lock_guard lock(m_mutex);
222 176 m_context = context;
223
1/1
✓ Branch 51 → 52 taken 176 times.
176 m_path = info.absoluteFilePath();
224 176 m_error.clear();
225 176 m_queue.clear();
226 176 m_stopping = false;
227 176 m_enabled = true;
228 176 m_nextOperation.store(1);
229 176 }
230
1/1
✓ Branch 86 → 87 taken 176 times.
352 m_writer = std::thread([this]() { writerLoop(); });
231 176 return true;
232 180 }
233
234 498 void PerformanceTrace::shutdown() {
235 {
236
1/1
✓ Branch 2 → 3 taken 498 times.
498 std::lock_guard lock(m_mutex);
237
5/6
✓ Branch 3 → 4 taken 322 times.
✓ Branch 3 → 7 taken 176 times.
✓ Branch 5 → 6 taken 322 times.
✗ Branch 5 → 7 not taken.
✓ Branch 8 → 9 taken 322 times.
✓ Branch 8 → 10 taken 176 times.
498 if (!m_enabled && !m_writer.joinable())
238 322 return;
239 176 m_enabled = false;
240 176 m_stopping = true;
241 498 }
242 176 m_condition.notify_all();
243
1/2
✓ Branch 16 → 18 taken 176 times.
✗ Branch 16 → 19 not taken.
176 if (m_writer.joinable())
244
1/1
✓ Branch 18 → 19 taken 176 times.
176 m_writer.join();
245
1/1
✓ Branch 19 → 20 taken 176 times.
176 std::lock_guard lock(m_mutex);
246 176 m_stopping = false;
247 176 }
248
249 29017 bool PerformanceTrace::isEnabled() const {
250
1/1
✓ Branch 2 → 3 taken 29017 times.
29017 std::lock_guard lock(m_mutex);
251 29017 return m_enabled;
252 29017 }
253
254 14 QString PerformanceTrace::outputPath() const {
255
1/1
✓ Branch 2 → 3 taken 14 times.
14 std::lock_guard lock(m_mutex);
256 28 return m_path;
257 14 }
258
259 1 QString PerformanceTrace::lastError() const {
260
1/1
✓ Branch 2 → 3 taken 1 time.
1 std::lock_guard lock(m_mutex);
261 2 return m_error;
262 1 }
263
264 769 QString PerformanceTrace::selectedScenario() const {
265
1/1
✓ Branch 2 → 3 taken 769 times.
769 std::lock_guard lock(m_mutex);
266 1538 return m_context.selectedScenario;
267 769 }
268
269 13 Operation PerformanceTrace::startOperation(const QString &scenario,
270 qint64 itemCount,
271 qint64 byteCount, qint64 startNs) {
272
8/8
✓ Branch 2 → 3 taken 13 times.
✓ Branch 3 → 4 taken 12 times.
✓ Branch 3 → 6 taken 1 time.
✓ Branch 4 → 5 taken 12 times.
✓ Branch 5 → 6 taken 1 time.
✓ Branch 5 → 7 taken 11 times.
✓ Branch 8 → 9 taken 2 times.
✓ Branch 8 → 10 taken 11 times.
13 if (!isEnabled() || !isSafeToken(scenario))
273 2 return {};
274
1/1
✓ Branch 10 → 11 taken 11 times.
11 auto state = std::make_shared<Operation::State>();
275 11 state->trace = this;
276 11 state->scenarioName = scenario;
277
2/3
✓ Branch 14 → 15 taken 11 times.
✗ Branch 14 → 17 not taken.
✓ Branch 15 → 16 taken 11 times.
11 state->startedNs = startNs >= 0 ? startNs : monotonicNowNs();
278 11 state->operationId =
279
1/1
✓ Branch 25 → 26 taken 11 times.
44 QStringLiteral("op-%1").arg(m_nextOperation.fetch_add(1));
280
2/2
✓ Branch 40 → 41 taken 11 times.
✓ Branch 41 → 42 taken 11 times.
33 enqueue(baseRecord(state->operationId, scenario, QStringLiteral("operation"),
281 33 QStringLiteral("start"), state->startedNs, 0, {},
282 itemCount, byteCount));
283 11 return Operation(std::move(state));
284 11 }
285
286 211 Operation PerformanceTrace::startMetric(const QString &scenario,
287 const QString &metric, qint64 startNs,
288 qint64 itemCount, qint64 byteCount) {
289
11/11
✓ Branch 2 → 3 taken 211 times.
✓ Branch 3 → 4 taken 210 times.
✓ Branch 3 → 8 taken 1 time.
✓ Branch 4 → 5 taken 210 times.
✓ Branch 5 → 6 taken 209 times.
✓ Branch 5 → 8 taken 1 time.
✓ Branch 6 → 7 taken 209 times.
✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 9 taken 208 times.
✓ Branch 10 → 11 taken 3 times.
✓ Branch 10 → 12 taken 208 times.
211 if (!isEnabled() || !isSafeToken(scenario) || !isSafeToken(metric))
290 3 return {};
291
1/1
✓ Branch 12 → 13 taken 208 times.
208 auto state = std::make_shared<Operation::State>();
292 208 state->trace = this;
293 208 state->scenarioName = scenario;
294 208 state->terminalPhase = metric;
295
3/3
✓ Branch 18 → 19 taken 129 times.
✓ Branch 18 → 21 taken 79 times.
✓ Branch 19 → 20 taken 129 times.
208 state->startedNs = startNs >= 0 ? startNs : monotonicNowNs();
296 208 state->operationId =
297
1/1
✓ Branch 29 → 30 taken 208 times.
832 QStringLiteral("op-%1").arg(m_nextOperation.fetch_add(1));
298
2/2
✓ Branch 41 → 42 taken 208 times.
✓ Branch 42 → 43 taken 208 times.
624 enqueue(baseRecord(state->operationId, scenario, metric,
299 624 QStringLiteral("start"), state->startedNs, 0, {},
300 itemCount, byteCount));
301 208 return Operation(std::move(state));
302 208 }
303
304 130 bool PerformanceTrace::markProcessEvent(const QString &scenario,
305 const QString &phase,
306 qint64 timestampNs,
307 qint64 durationNs,
308 qint64 itemCount,
309 qint64 byteCount) {
310
8/8
✓ Branch 3 → 4 taken 128 times.
✓ Branch 3 → 8 taken 2 times.
✓ Branch 5 → 6 taken 127 times.
✓ Branch 5 → 8 taken 1 time.
✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 9 taken 126 times.
✓ Branch 10 → 11 taken 4 times.
✓ Branch 10 → 12 taken 126 times.
130 if (!isEnabled() || !isSafeToken(scenario) || !isSafeToken(phase))
311 4 return false;
312
2/2
✓ Branch 12 → 13 taken 86 times.
✓ Branch 12 → 15 taken 40 times.
126 const qint64 now = timestampNs >= 0 ? timestampNs : monotonicNowNs();
313
2/2
✓ Branch 24 → 25 taken 126 times.
✓ Branch 25 → 26 taken 126 times.
378 enqueue(baseRecord(QStringLiteral("process"), scenario, phase,
314 252 QStringLiteral("marker"), now,
315 252 std::max<qint64>(0, durationNs), {}, itemCount,
316 byteCount));
317 126 return true;
318 }
319
320 3 void PerformanceTrace::startEventLoopHeartbeat(QObject *owner,
321 int intervalMs) {
322
7/9
✓ Branch 2 → 3 taken 3 times.
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 6 taken 2 times.
✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 6 not taken.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 1 time.
✓ Branch 8 → 9 taken 2 times.
✓ Branch 8 → 10 taken 1 time.
3 if (!isEnabled() || !owner || intervalMs <= 0)
323 2 return;
324
3/6
✓ Branch 10 → 11 taken 1 time.
✓ Branch 11 → 12 taken 1 time.
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 14 taken 1 time.
✗ Branch 26 → 27 not taken.
✗ Branch 26 → 28 not taken.
1 auto *timer = new QTimer(owner);
325
1/1
✓ Branch 14 → 15 taken 1 time.
1 timer->setTimerType(Qt::PreciseTimer);
326
1/1
✓ Branch 15 → 16 taken 1 time.
1 timer->setInterval(intervalMs);
327
2/2
✓ Branch 16 → 17 taken 1 time.
✓ Branch 17 → 18 taken 1 time.
1 auto last = std::make_shared<qint64>(monotonicNowNs());
328
1/1
✓ Branch 19 → 20 taken 1 time.
1 QObject::connect(timer, &QTimer::timeout, owner,
329 2 [this, intervalMs, last]() {
330 4 const qint64 now = monotonicNowNs();
331 const qint64 expected =
332 4 *last + static_cast<qint64>(intervalMs) * 1000000;
333 4 const qint64 delay = std::max<qint64>(0, now - expected);
334
1/1
✓ Branch 11 → 12 taken 4 times.
8 markProcessEvent(QStringLiteral("PROCESS.event_loop"),
335 8 QStringLiteral("heartbeat"), now, delay);
336 4 *last = now;
337 4 });
338
1/1
✓ Branch 22 → 23 taken 1 time.
1 timer->start();
339 1 }
340
341 1407 void PerformanceTrace::enqueue(const QJsonObject &record) {
342 {
343
1/1
✓ Branch 2 → 3 taken 1407 times.
1407 std::lock_guard lock(m_mutex);
344
1/4
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 6 taken 1407 times.
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 6 not taken.
1407 if (!m_enabled && !m_stopping)
345 ✗ return;
346
1/1
✓ Branch 6 → 7 taken 1407 times.
1407 m_queue.push_back(record);
347 1407 }
348 1407 m_condition.notify_one();
349 }
350
351 1407 QJsonObject PerformanceTrace::baseRecord(
352 const QString &operationId, const QString &scenario, const QString &phase,
353 const QString &event, qint64 timestampNs, qint64 durationNs,
354 const QString &outcome, qint64 itemCount, qint64 byteCount) const {
355
1/1
✓ Branch 2 → 3 taken 1407 times.
1407 TraceContext context;
356 {
357
1/1
✓ Branch 3 → 4 taken 1407 times.
1407 std::lock_guard lock(m_mutex);
358 1407 context = m_context;
359 1407 }
360 return {
361 2814 {QStringLiteral("schema_version"), TraceSchemaVersion},
362 2814 {QStringLiteral("run_id"), context.runId},
363 2814 {QStringLiteral("operation_id"), operationId},
364 2814 {QStringLiteral("scenario"), scenario},
365 2814 {QStringLiteral("phase"), phase},
366 2814 {QStringLiteral("event"), event},
367 2814 {QStringLiteral("monotonic_ns"), timestampNs},
368 2814 {QStringLiteral("duration_ns"), durationNs},
369
1/1
✓ Branch 41 → 42 taken 1407 times.
2814 {QStringLiteral("outcome"), optionalOutcome(outcome)},
370 2814 {QStringLiteral("data_profile"), context.dataProfile},
371 2814 {QStringLiteral("cache_state"), context.cacheState},
372 2814 {QStringLiteral("network_profile"), context.networkProfile},
373
1/1
✓ Branch 58 → 59 taken 1407 times.
2814 {QStringLiteral("item_count"), optionalCount(itemCount)},
374
1/1
✓ Branch 63 → 64 taken 1407 times.
2814 {QStringLiteral("byte_count"), optionalCount(byteCount)},
375 2814 {QStringLiteral("build_sha"), context.buildSha},
376 2814 {QStringLiteral("build_type"), context.buildType},
377
2/4
✓ Branch 75 → 76 taken 22512 times.
✓ Branch 75 → 77 taken 1407 times.
✗ Branch 117 → 118 not taken.
✗ Branch 117 → 119 not taken.
25326 };
378 25326 }
379
380 176 void PerformanceTrace::writerLoop() {
381
1/1
✓ Branch 2 → 3 taken 176 times.
176 QFile file;
382 {
383
1/1
✓ Branch 3 → 4 taken 176 times.
176 std::lock_guard lock(m_mutex);
384
1/1
✓ Branch 4 → 5 taken 176 times.
176 file.setFileName(m_path);
385 176 }
386
2/3
✓ Branch 7 → 8 taken 176 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 22 taken 176 times.
176 if (!file.open(QIODevice::WriteOnly | QIODevice::Append)) {
387 ✗ std::lock_guard lock(m_mutex);
388 ✗ m_error = QStringLiteral("Cannot append performance trace: ") +
389 ✗ file.errorString();
390 ✗ m_enabled = false;
391 ✗ return;
392 ✗ }
393
394 while (true) {
395
1/1
✓ Branch 22 → 23 taken 1554 times.
1554 std::deque<QJsonObject> batch;
396 {
397
1/1
✓ Branch 23 → 24 taken 1554 times.
1554 std::unique_lock lock(m_mutex);
398
1/1
✓ Branch 24 → 25 taken 1554 times.
1554 m_condition.wait(lock,
399
4/4
✓ Branch 2 → 3 taken 2665 times.
✓ Branch 2 → 5 taken 206 times.
✓ Branch 4 → 5 taken 1348 times.
✓ Branch 4 → 6 taken 1317 times.
2871 [this]() { return m_stopping || !m_queue.empty(); });
400 1554 batch.swap(m_queue);
401
5/6
✓ Branch 27 → 28 taken 176 times.
✓ Branch 27 → 30 taken 1378 times.
✓ Branch 28 → 29 taken 176 times.
✗ Branch 28 → 30 not taken.
✓ Branch 31 → 32 taken 176 times.
✓ Branch 31 → 33 taken 1378 times.
1554 if (batch.empty() && m_stopping)
402 176 break;
403 1554 }
404
2/2
✓ Branch 65 → 40 taken 1407 times.
✓ Branch 65 → 66 taken 1378 times.
2785 for (const QJsonObject &record : batch) {
405 const QByteArray line =
406
3/3
✓ Branch 41 → 42 taken 1407 times.
✓ Branch 42 → 43 taken 1407 times.
✓ Branch 43 → 44 taken 1407 times.
1407 QJsonDocument(record).toJson(QJsonDocument::Compact) + '\n';
407
2/3
✓ Branch 46 → 47 taken 1407 times.
✗ Branch 48 → 49 not taken.
✓ Branch 48 → 62 taken 1407 times.
1407 if (file.write(line) != line.size()) {
408 ✗ std::lock_guard lock(m_mutex);
409 ✗ m_error = QStringLiteral("Performance trace write failed: ") +
410 ✗ file.errorString();
411 ✗ m_enabled = false;
412 ✗ }
413 1407 }
414
1/1
✓ Branch 66 → 67 taken 1378 times.
1378 file.flush();
415 2932 }
416
1/1
✓ Branch 72 → 73 taken 176 times.
176 file.flush();
417
1/1
✓ Branch 73 → 74 taken 176 times.
176 file.close();
418 176 }
419
420 } // namespace MailJD::Performance
421