etw_tracer_test.cc 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571
  1. // Copyright The OpenTelemetry Authors
  2. // SPDX-License-Identifier: Apache-2.0
  3. #ifdef _WIN32
  4. # include <gtest/gtest.h>
  5. # include <map>
  6. # include <string>
  7. # include "opentelemetry//sdk/trace/sampler.h"
  8. # include "opentelemetry/exporters/etw/etw_tracer_exporter.h"
  9. # include "opentelemetry/sdk/trace/samplers/always_off.h"
  10. # include "opentelemetry/sdk/trace/simple_processor.h"
  11. using namespace OPENTELEMETRY_NAMESPACE;
  12. using namespace opentelemetry::exporter::etw;
  13. using namespace opentelemetry::sdk::trace;
  14. const char *kGlobalProviderName = "OpenTelemetry-ETW-TLD";
  15. std::string getTemporaryValue()
  16. {
  17. return std::string("Value from Temporary std::string");
  18. }
  19. /**
  20. * A Mock Custom Id Generator
  21. */
  22. class MockIdGenerator : public sdk::trace::IdGenerator
  23. {
  24. public:
  25. MockIdGenerator() : sdk::trace::IdGenerator(false) {}
  26. opentelemetry::trace::SpanId GenerateSpanId() noexcept override
  27. {
  28. return opentelemetry::trace::SpanId(buf_span);
  29. }
  30. opentelemetry::trace::TraceId GenerateTraceId() noexcept override
  31. {
  32. return opentelemetry::trace::TraceId(buf_trace);
  33. }
  34. uint8_t buf_span[8] = {1, 2, 3, 4, 5, 6, 7, 8};
  35. uint8_t buf_trace[16] = {1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1};
  36. };
  37. /* A Custom Sampler, implementing parent based sampler*/
  38. class MockSampler : public sdk::trace::Sampler
  39. {
  40. public:
  41. MockSampler(std::shared_ptr<Sampler> delegate_sampler) noexcept
  42. : delegate_sampler_(delegate_sampler)
  43. {}
  44. sdk::trace::SamplingResult ShouldSample(
  45. const trace_api::SpanContext &parent_context,
  46. trace_api::TraceId trace_id,
  47. nostd::string_view name,
  48. trace_api::SpanKind span_kind,
  49. const opentelemetry::common::KeyValueIterable &attributes,
  50. const trace_api::SpanContextKeyValueIterable &links) noexcept
  51. {
  52. if (!parent_context.IsValid())
  53. {
  54. // If no parent (root span) exists returns the result of the delegateSampler
  55. return delegate_sampler_->ShouldSample(parent_context, trace_id, name, span_kind, attributes,
  56. links);
  57. }
  58. // If parent exists:
  59. if (parent_context.IsSampled())
  60. {
  61. return {Decision::RECORD_AND_SAMPLE, nullptr, parent_context.trace_state()};
  62. }
  63. return {Decision::DROP, nullptr, parent_context.trace_state()};
  64. }
  65. nostd::string_view GetDescription() const noexcept { return "Custom Sampler"; }
  66. private:
  67. std::shared_ptr<Sampler> delegate_sampler_;
  68. };
  69. class AlwaysOffTailSampler : public TailSampler
  70. {
  71. public:
  72. opentelemetry::sdk::trace::SamplingResult ShouldSample(
  73. const opentelemetry::trace::Span &span) noexcept override
  74. {
  75. return {opentelemetry::sdk::trace::Decision::DROP};
  76. }
  77. };
  78. /* clang-format off */
  79. TEST(ETWTracer, TracerCheck)
  80. {
  81. // SDK customer specifies their unique ETW ProviderName. Every component or library
  82. // is assumed to have its own instrumentation name. Traces are routed to dedicated
  83. // provider. Standard hash function maps from ProviderName to ProviderGUID.
  84. //
  85. // Prominent naming examples from `logman query providers` :
  86. //
  87. // [Docker] {a3693192-9ed6-46d2-a981-f8226c8363bd}
  88. // ...
  89. // Intel-Autologger-iclsClient {B8D7E9A0-65D5-40BE-AFEA-83593FC0164E}
  90. // Intel-Autologger-iclsProxy {301B773F-50F3-4C8E-83F0-53BA9590A13E}
  91. // Intel-Autologger-PTTEKRecertification {F33E9E07-8792-47E8-B3FA-2C92AB32C5B3}
  92. // ...
  93. // NodeJS-ETW-provider {77754E9B-264B-4D8D-B981-E4135C1ECB0C}
  94. // ...
  95. // OpenSSH {C4B57D35-0636-4BC3-A262-370F249F9802}
  96. // ...
  97. // Windows Connect Now {C100BECE-D33A-4A4B-BF23-BBEF4663D017}
  98. // Windows Defender Firewall API {28C9F48F-D244-45A8-842F-DC9FBC9B6E92}
  99. // Windows Defender Firewall API - GP {0EFF663F-8B6E-4E6D-8182-087A8EAA29CB}
  100. // Windows Defender Firewall Driver {D5E09122-D0B2-4235-ADC1-C89FAAAF1069}
  101. std::string providerName = kGlobalProviderName; // supply unique instrumentation name here
  102. exporter::etw::TracerProvider tp;
  103. auto tracer = tp.GetTracer(providerName);
  104. // Span attributes
  105. Properties outer_attribs =
  106. {
  107. {"attrib1", 1},
  108. {"attrib2", 2}
  109. };
  110. // copy the outer attributes
  111. Properties inner_attribs = outer_attribs;
  112. {
  113. auto topSpan = tracer->StartSpan("MySpanTop");
  114. auto topScope = tracer->WithActiveSpan(topSpan);
  115. {
  116. auto outerSpan = tracer->StartSpan("MySpanL2", outer_attribs);
  117. auto outerScope = tracer->WithActiveSpan(outerSpan);
  118. // Create nested span. Note how we share the attributes here.
  119. // It is Okay to either reuse/share or have your own attributes.
  120. {
  121. auto innerSpan = tracer->StartSpan("MySpanL3", inner_attribs);
  122. auto innerScope = tracer->WithActiveSpan(innerSpan);
  123. // Add span attribute
  124. EXPECT_NO_THROW(outerSpan->SetAttribute("AttrName1", "AttrValue1"));
  125. // Add first event
  126. std::string eventName1 = "MyEvent1";
  127. Properties event1 =
  128. {
  129. {"uint32Key", (uint32_t)1234},
  130. {"uint64Key", (uint64_t)1234567890},
  131. {"strKey", "someValue"}
  132. };
  133. EXPECT_NO_THROW(outerSpan->AddEvent(eventName1, event1));
  134. // Add second event
  135. std::string eventName2 = "MyEvent2";
  136. Properties event2 =
  137. {
  138. {"uint32Key", (uint32_t)9876},
  139. {"uint64Key", (uint64_t)987654321},
  140. {"strKey", "anotherValue"}
  141. };
  142. EXPECT_NO_THROW(outerSpan->AddEvent(eventName2, event2));
  143. std::string eventName3= "MyEvent3";
  144. Properties event3 =
  145. {
  146. /* Extra metadata that allows event to flow to A.I. pipeline */
  147. {"metadata", "ai_event"},
  148. {"uint32Key", (uint32_t)9876},
  149. {"uint64Key", (uint64_t)987654321},
  150. // {"int32array", {{-1,0,1,2,3}} },
  151. {"tempString", getTemporaryValue() }
  152. };
  153. EXPECT_NO_THROW(innerSpan->AddEvent(eventName3, event3));
  154. EXPECT_NO_THROW(innerSpan->End());
  155. }
  156. EXPECT_NO_THROW(outerSpan->End());
  157. }
  158. EXPECT_NO_THROW(topSpan->End());
  159. }
  160. # if OPENTELEMETRY_ABI_VERSION_NO == 1
  161. EXPECT_NO_THROW(tracer->CloseWithMicroseconds(0));
  162. # endif
  163. }
  164. // Lowest decoration level -> smaller ETW event size.
  165. // Expected output in C# listener on the other side:
  166. // no ActivityID GUID, no SpanId, no TraceId.
  167. /*
  168. {
  169. "Timestamp": "2021-03-19T21:04:38.411193-07:00",
  170. "ProviderName": "OpenTelemetry-ETW-TLD",
  171. "Id": 13,
  172. "Message": null,
  173. "ProcessId": 15120,
  174. "Level": "Always",
  175. "Keywords": "0x0000000000000000",
  176. "EventName": "C.min/Stop",
  177. "ActivityID": null,
  178. "RelatedActivityID": null,
  179. "Payload": {}
  180. }
  181. */
  182. TEST(ETWTracer, TracerCheckMinDecoration)
  183. {
  184. std::string providerName = kGlobalProviderName;
  185. exporter::etw::TracerProvider tp
  186. ({
  187. {"enableTraceId", false},
  188. {"enableSpanId", false},
  189. {"enableActivityId", false},
  190. {"enableActivityTracking", true},
  191. {"enableRelatedActivityId", false},
  192. {"enableAutoParent", false}
  193. });
  194. auto tracer = tp.GetTracer(providerName);
  195. {
  196. auto aSpan = tracer->StartSpan("A.min");
  197. auto aScope = tracer->WithActiveSpan(aSpan);
  198. {
  199. auto bSpan = tracer->StartSpan("B.min");
  200. auto bScope = tracer->WithActiveSpan(bSpan);
  201. {
  202. auto cSpan = tracer->StartSpan("C.min");
  203. auto cScope = tracer->WithActiveSpan(cSpan);
  204. EXPECT_NO_THROW(cSpan->End());
  205. }
  206. EXPECT_NO_THROW(bSpan->End());
  207. }
  208. EXPECT_NO_THROW(aSpan->End());
  209. }
  210. # if OPENTELEMETRY_ABI_VERSION_NO == 1
  211. tracer->CloseWithMicroseconds(0);
  212. # endif
  213. }
  214. // Highest decoration level -> larger ETW event size
  215. // Expected output in C# listener on the other side:
  216. // ActivityID GUID (==SpanId), SpanId, TraceId.
  217. /*
  218. {
  219. "Timestamp": "2021-03-19T21:04:38.4120274-07:00",
  220. "ProviderName": "OpenTelemetry-ETW-TLD",
  221. "Id": 21,
  222. "Message": null,
  223. "ProcessId": 15120,
  224. "Level": "Always",
  225. "Keywords": "0x0000000000000000",
  226. "EventName": "C.max/Stop",
  227. "ActivityID": "d55a2c25-8033-40ab-0000-000000000000",
  228. "RelatedActivityID": null,
  229. "Payload": {
  230. "SpanId": "252c5ad53380ab40",
  231. "TraceId": "4dea2a63c188894ea5ab979e5cd7ec36"
  232. }
  233. }
  234. */
  235. TEST(ETWTracer, TracerCheckMaxDecoration)
  236. {
  237. std::string providerName = kGlobalProviderName;
  238. exporter::etw::TracerProvider tp
  239. ({
  240. {"enableTraceId", true},
  241. {"enableSpanId", true},
  242. {"enableActivityId", true},
  243. {"enableRelatedActivityId", true},
  244. {"enableAutoParent", true}
  245. });
  246. auto tracer = tp.GetTracer(providerName);
  247. {
  248. auto aSpan = tracer->StartSpan("A.max");
  249. auto aScope = tracer->WithActiveSpan(aSpan);
  250. {
  251. auto bSpan = tracer->StartSpan("B.max");
  252. auto bScope = tracer->WithActiveSpan(bSpan);
  253. {
  254. auto cSpan = tracer->StartSpan("C.max");
  255. auto cScope = tracer->WithActiveSpan(cSpan);
  256. EXPECT_NO_THROW(cSpan->End());
  257. }
  258. EXPECT_NO_THROW(bSpan->End());
  259. }
  260. EXPECT_NO_THROW(aSpan->End());
  261. }
  262. # if OPENTELEMETRY_ABI_VERSION_NO == 1
  263. tracer->CloseWithMicroseconds(0);
  264. # endif
  265. }
  266. TEST(ETWTracer, TracerCheckMsgPack)
  267. {
  268. std::string providerName = "OpenTelemetry-ETW-MsgPack";
  269. exporter::etw::TracerProvider tp
  270. ({
  271. {"enableTraceId", true},
  272. {"enableSpanId", true},
  273. {"enableActivityId", true},
  274. {"enableRelatedActivityId", true},
  275. {"enableAutoParent", true}
  276. });
  277. auto tracer = tp.GetTracer(providerName);
  278. {
  279. auto aSpan = tracer->StartSpan("A.max");
  280. auto aScope = tracer->WithActiveSpan(aSpan);
  281. {
  282. auto bSpan = tracer->StartSpan("B.max");
  283. auto bScope = tracer->WithActiveSpan(bSpan);
  284. {
  285. auto cSpan = tracer->StartSpan("C.max");
  286. auto cScope = tracer->WithActiveSpan(cSpan);
  287. std::string eventName = "MyMsgPackEvent";
  288. Properties event =
  289. {
  290. {"uint32Key", (uint32_t)1234},
  291. {"uint64Key", (uint64_t)1234567890},
  292. {"strKey", "someValue"}
  293. };
  294. cSpan->AddEvent(eventName, event);
  295. EXPECT_NO_THROW(cSpan->End());
  296. }
  297. EXPECT_NO_THROW(bSpan->End());
  298. }
  299. EXPECT_NO_THROW(aSpan->End());
  300. }
  301. # if OPENTELEMETRY_ABI_VERSION_NO == 1
  302. tracer->CloseWithMicroseconds(0);
  303. # endif
  304. }
  305. /**
  306. * @brief Global Tracer singleton may be placed in .h header and
  307. * shared across different compilation units. All would get the
  308. * same object.
  309. *
  310. * @return Single global tracer instance.
  311. */
  312. static OPENTELEMETRY_NAMESPACE::trace::TracerProvider& GetGlobalTracerProvider()
  313. {
  314. static exporter::etw::TracerProvider tp
  315. ({
  316. {"enableTraceId", true},
  317. {"enableSpanId", true},
  318. {"enableActivityId", true},
  319. {"enableRelatedActivityId", true},
  320. {"enableAutoParent", true}
  321. });
  322. return tp;
  323. }
  324. static OPENTELEMETRY_NAMESPACE::trace::Tracer& GetGlobalTracer()
  325. {
  326. static auto tracer = GetGlobalTracerProvider().GetTracer(kGlobalProviderName);
  327. return (*tracer.get());
  328. }
  329. TEST(ETWTracer, GlobalSingletonTracer)
  330. {
  331. // Obtain a global tracer using C++11 magic static.
  332. auto& globalTracer = GetGlobalTracer();
  333. auto s1 = globalTracer.StartSpan("Span1");
  334. auto traceId1 = s1->GetContext().trace_id();
  335. s1->End();
  336. /* === Span 1 - "TraceId": "182a64258fb1864ca4e1a542eecbd9bf"
  337. {
  338. "Timestamp": "2021-05-10T11:45:27.028827-07:00",
  339. "ProviderName": "OpenTelemetry-ETW-TLD",
  340. "Id": 5,
  341. "Message": null,
  342. "ProcessId": 23712,
  343. "Level": "Always",
  344. "Keywords": "0x0000000000000000",
  345. "EventName": "Span",
  346. "ActivityID": "6ed94703-6b0a-4e76-0000-000000000000",
  347. "RelatedActivityID": null,
  348. "Payload": {
  349. "Duration": 23456,
  350. "Kind": 1,
  351. "Name": "Span1",
  352. "SpanId": "0347d96e0a6b764e",
  353. "StartTime": "2021-05-10T18:45:27.066411500Z",
  354. "StatusCode": 0,
  355. "StatusMessage": "",
  356. "Success": "True",
  357. "TraceId": "182a64258fb1864ca4e1a542eecbd9bf",
  358. "_name": "Span"
  359. }
  360. }
  361. */
  362. // Obtain a different tracer withs its own trace-id.
  363. auto localTracer = GetGlobalTracerProvider().GetTracer(kGlobalProviderName);
  364. auto s2 = localTracer->StartSpan("Span2");
  365. auto traceId2 = s2->GetContext().trace_id();
  366. s2->End();
  367. /* === Span 2 - "TraceId": "334bf9a1eed98d40a873a606295a9368"
  368. {
  369. "Timestamp": "2021-05-10T11:45:27.0289654-07:00",
  370. "ProviderName": "OpenTelemetry-ETW-TLD",
  371. "Id": 5,
  372. "Message": null,
  373. "ProcessId": 23712,
  374. "Level": "Always",
  375. "Keywords": "0x0000000000000000",
  376. "EventName": "Span",
  377. "ActivityID": "3b7b2ecb-2e84-4903-0000-000000000000",
  378. "RelatedActivityID": null,
  379. "Payload": {
  380. "Duration": 03434,
  381. "Kind": 1,
  382. "Name": "Span2",
  383. "SpanId": "cb2e7b3b842e0349",
  384. "StartTime": "2021-05-10T18:45:27.066411500Z",
  385. "StatusCode": 0,
  386. "StatusMessage": "",
  387. "Success": "True",
  388. "TraceId": "334bf9a1eed98d40a873a606295a9368",
  389. "_name": "Span"
  390. }
  391. }
  392. */
  393. // Obtain the same global tracer with the same trace-id as before.
  394. auto& globalTracer2 = GetGlobalTracer();
  395. auto s3 = globalTracer2.StartSpan("Span3");
  396. auto traceId3 = s3->GetContext().trace_id();
  397. s3->End();
  398. /* === Span 3 - "TraceId": "182a64258fb1864ca4e1a542eecbd9bf"
  399. {
  400. "Timestamp": "2021-05-10T11:45:27.0290936-07:00",
  401. "ProviderName": "OpenTelemetry-ETW-TLD",
  402. "Id": 5,
  403. "Message": null,
  404. "ProcessId": 23712,
  405. "Level": "Always",
  406. "Keywords": "0x0000000000000000",
  407. "EventName": "Span",
  408. "ActivityID": "0a970247-ba0e-4d4b-0000-000000000000",
  409. "RelatedActivityID": null,
  410. "Payload": {
  411. "Duration": 12323,
  412. "Kind": 1,
  413. "Name": "Span3",
  414. "SpanId": "4702970a0eba4b4d",
  415. "StartTime": "2021-05-10T18:45:27.066411500Z",
  416. "StatusCode": 0,
  417. "StatusMessage": "",
  418. "Success": "True",
  419. "TraceId": "182a64258fb1864ca4e1a542eecbd9bf",
  420. "_name": "Span"
  421. }
  422. }
  423. */
  424. EXPECT_NE(traceId1, traceId2);
  425. EXPECT_EQ(traceId1, traceId3);
  426. # if OPENTELEMETRY_ABI_VERSION_NO == 1
  427. localTracer->CloseWithMicroseconds(0);
  428. globalTracer.CloseWithMicroseconds(0);
  429. # endif
  430. }
  431. TEST(ETWTracer, AlwayOffSampler)
  432. {
  433. std::string providerName = kGlobalProviderName; // supply unique instrumentation name here
  434. std::unique_ptr<sdk::trace::Sampler> always_off{new sdk::trace::AlwaysOffSampler()};
  435. exporter::etw::TracerProvider tp
  436. ({
  437. {"enableTraceId", true},
  438. {"enableSpanId", true},
  439. {"enableActivityId", true},
  440. {"enableRelatedActivityId", true},
  441. {"enableAutoParent", true}
  442. },
  443. std::move(always_off));
  444. auto tracer = tp.GetTracer(providerName);
  445. auto span = tracer->StartSpan("span_off");
  446. EXPECT_EQ(span->GetContext().IsValid(), true);
  447. EXPECT_EQ(span->GetContext().IsSampled(), false);
  448. }
  449. TEST(ETWTracer, AlwayOffTailSampler)
  450. {
  451. std::string providerName = kGlobalProviderName; // supply unique instrumentation name here
  452. std::unique_ptr<sdk::trace::Sampler> always_on{new sdk::trace::AlwaysOnSampler()};
  453. sdk::trace::IdGenerator *id_generator = new MockIdGenerator();
  454. std::unique_ptr<TailSampler> always_off_tail{new AlwaysOffTailSampler()};
  455. exporter::etw::TracerProvider tp
  456. ({
  457. {"enableTraceId", true},
  458. {"enableSpanId", true},
  459. {"enableActivityId", true},
  460. {"enableRelatedActivityId", true},
  461. {"enableAutoParent", true}
  462. },
  463. std::move(always_on),
  464. std::unique_ptr<sdk::trace::IdGenerator>(id_generator),
  465. std::move(always_off_tail));
  466. auto tracer = tp.GetTracer(providerName);
  467. }
  468. TEST(ETWTracer, CustomIdGenerator)
  469. {
  470. std::string providerName = kGlobalProviderName; // supply unique instrumentation name here
  471. sdk::trace::IdGenerator *id_generator = new MockIdGenerator();
  472. std::unique_ptr<sdk::trace::Sampler> always_on{new sdk::trace::AlwaysOnSampler()};
  473. exporter::etw::TracerProvider tp
  474. ({
  475. {"enableTraceId", true},
  476. {"enableSpanId", true},
  477. {"enableActivityId", true},
  478. {"enableRelatedActivityId", true},
  479. {"enableAutoParent", true}
  480. },
  481. std::move(always_on),
  482. std::unique_ptr<sdk::trace::IdGenerator>(id_generator));
  483. auto tracer = tp.GetTracer(providerName);
  484. auto span = tracer->StartSpan("span_on");
  485. EXPECT_EQ(span->GetContext().trace_id(), id_generator->GenerateTraceId());
  486. }
  487. TEST(ETWTracer, CustomSampler)
  488. {
  489. std::string providerName = kGlobalProviderName; // supply unique instrumentation name here
  490. auto parent_off = std::unique_ptr<Sampler>(new MockSampler(std::make_shared<AlwaysOnSampler>()));
  491. exporter::etw::TracerProvider tp
  492. ({
  493. {"enableTraceId", true},
  494. {"enableSpanId", true},
  495. {"enableActivityId", true},
  496. {"enableRelatedActivityId", true},
  497. {"enableAutoParent", true}
  498. },
  499. std::move(parent_off));
  500. auto tracer = tp.GetTracer(providerName);
  501. {
  502. auto span = tracer->StartSpan("span_off");
  503. EXPECT_EQ(span->GetContext().IsValid(), true);
  504. EXPECT_EQ(span->GetContext().IsSampled(), true);
  505. auto scope = tracer->WithActiveSpan(span);
  506. auto trace_id = span->GetContext().trace_id();
  507. {
  508. auto child_span = tracer->StartSpan("span on");
  509. EXPECT_EQ(child_span->GetContext().IsValid(), true);
  510. EXPECT_EQ(child_span->GetContext().IsSampled(), true);
  511. EXPECT_EQ(child_span->GetContext().trace_id(), trace_id);
  512. }
  513. }
  514. }
  515. TEST(ETWTracer, EndWithCustomTime)
  516. {
  517. // Obtain a global tracer using C++11 magic static.
  518. auto& globalTracer = GetGlobalTracer();
  519. auto s1 = globalTracer.StartSpan("Span1");
  520. auto traceId1 = s1->GetContext().trace_id();
  521. opentelemetry::trace::EndSpanOptions end;
  522. end.end_steady_time = opentelemetry::common::SteadyTimestamp(std::chrono::nanoseconds(40));
  523. s1->End(end);
  524. auto end_time = static_cast<opentelemetry::exporter::etw::Span *>(s1.get())->GetEndTime();
  525. EXPECT_EQ(end.end_steady_time.time_since_epoch(), end_time.time_since_epoch());
  526. }
  527. /* clang-format on */
  528. #endif