Zth (libzth)
Loading...
Searching...
No Matches
perf.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2019-2026 Jochem Rutgers
3 *
4 * SPDX-License-Identifier: MPL-2.0
5 */
6
7#include <libzth/perf.h>
8
9#include <libzth/allocator.h>
10#include <libzth/fiber.h>
11#include <libzth/worker.h>
12
13#include <cstdlib>
14#include <cstring>
15#include <set>
16#include <unistd.h>
17
18#ifndef ZTH_OS_BAREMETAL
19# define ZTH_VCD_USE_FILE_IO
20#endif
21
22namespace zth {
23
24
25
27// Buffer
28//
29
30static void perf_time(Timestamp const& t = Timestamp());
31static void perf_dt(Timestamp const& t = Timestamp());
32
42
43class PerfBuffer final {
45public:
46 typedef void* Known;
48
49 PerfBuffer() noexcept
50 : m_buffer()
51 , m_size()
52 , m_running()
53 , m_done()
54 , m_dump()
55 {}
56
57 ~PerfBuffer() noexcept
58 {
59 deinit();
60 }
61
62 bool enabled() const noexcept
63 {
64 return m_buffer;
65 }
66
67 static constexpr size_t capacity() noexcept
68 {
70 }
71
72 int init() noexcept
73 {
74 if(!m_buffer) {
75 m_buffer = allocate_noexcept<char>(capacity());
76 if(!m_buffer)
77 return ENOMEM;
78 }
79
80 release();
81 return 0;
82 }
83
84 void deinit() noexcept
85 {
86 m_size = 0;
87
88 if(m_buffer) {
89 deallocate<char>(const_cast<char*>(m_buffer), capacity());
90 m_buffer = nullptr;
91 }
92 }
93
94 size_t size() const noexcept
95 {
96 return std::min(capacity(), (size_t)m_size);
97 }
98
99 size_t space() const noexcept
100 {
101 if(!m_buffer)
102 return 0;
103
104 zth_assert(size() < capacity());
105 return capacity() - size() - 1;
106 }
107
108 bool full() const noexcept
109 {
110 return m_size >= capacity() - Config::PerfEventBufferSpare;
111 }
112
113 void check() noexcept
114 {
115 if(full())
116 stop();
117 }
118
119 char const* data() noexcept
120 {
121 return (char const*)m_buffer;
122 }
123
124 char volatile* reserve(size_t s) noexcept
125 {
126 if(!running())
127 return nullptr;
128
129 if(unlikely(space() < s)) {
130 stop();
131 return nullptr;
132 }
133
134 size_t pend =
135#if GCC_VERSION < 40802L
136 __sync_add_and_fetch(&m_size, s);
137#else
138 __atomic_add_fetch(&m_size, s, __ATOMIC_RELAXED);
139#endif
140
141 size_t pstart = pend - s;
142 char volatile* p = &m_buffer[pstart];
143
144 if(unlikely(pend >= capacity())) {
145 // Hit a race. Terminate the buffer.
146 if(pstart < capacity())
147 *p = 0;
148
149 p = nullptr;
150 }
151
152 return p;
153 }
154
155 void release() noexcept
156 {
157 stop();
158 m_size = 0;
159
161 // In case of threads, fill the buffer with 0, such that even in case of a
162 // race during reserve()/append, the buffer is always terminated properly.
163 // Without threads, an IRQ can still do async append, but that will always
164 // complete before the main context can resume again. So, it is not possible
165 // to see a partially filled buffer in that case.
166 memset(const_cast<char*>(m_buffer), 0, capacity());
167 }
168
169 m_known.clear();
170 }
171
172 Timestamp const& t() const noexcept
173 {
174 return m_t;
175 }
176
177 void t(Timestamp const& t) noexcept
178 {
179 m_t = t;
180 }
181
183 {
184 m_dump = f;
185 }
186
188 {
189 return m_dump;
190 }
191
193 {
194 m_done = f;
195 }
196
198 {
199 return m_done;
200 }
201
202 void start(zth_perf_done_callback_t* f = nullptr) noexcept
203 {
204 if(m_running)
205 return;
206 if(!m_buffer)
207 return;
208
209 done_callback(f);
210 m_running = true;
211 perf_time();
212
213 zth_dbg(perf, "[%s] Start", currentWorker().id_str());
214 }
215
216 void stop() noexcept;
217
218 bool running() const noexcept
219 {
220 return m_running;
221 }
222
223 bool knows(void* x) const noexcept
224 {
225 return m_known.find(x) != m_known.end();
226 }
227
228 void know(void* x) noexcept
229 {
230 try {
231 m_known.insert(x);
232 } catch(std::bad_alloc const&) { // NOLINT
233 // Ignore.
234 }
235 }
236
237private:
238 char volatile* m_buffer;
239 size_t volatile m_size;
240 bool volatile m_running;
241 Timestamp m_t;
242 KnownSet m_known;
245};
246
247// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
248ZTH_TLS_STATIC(PerfBuffer*, perf_buffer, nullptr)
249
250
251
252
253// Event collection
254//
255
256
263void perf_start(zth_perf_done_callback_t* f) noexcept
264{
265 if(perf_buffer)
266 perf_buffer->start(f);
267}
268
276void perf_stop() noexcept
277{
278 if(perf_buffer)
279 perf_buffer->stop();
280}
281
282void PerfBuffer::stop() noexcept
283{
284 if(perf_buffer != this)
285 // Called from another thread?
286 return;
287
288 if(!m_running)
289 return;
290
291 m_running = false;
292 zth_dbg(perf, "[%s] Stop", currentWorker().id_str());
293
294 if(done_callback())
295 done_callback()();
296}
297
298typedef char leb128_buf_t[9];
299
300static size_t leb128_encode(leb128_buf_t& buf, uint64_t x)
301{
302 size_t len = 0;
303
304 while(true) {
305 buf[len] = (char)(x & 0x7FU);
306 x >>= 7U;
307 if(!x)
308 return len + 1U;
309 buf[len] = (char)((unsigned)buf[len] | 0x80U);
310 len++;
311 }
312}
313
314static size_t leb128_len(char const* buf)
315{
316 size_t len = 1;
317 for(; *buf & 0x80; buf++, len++)
318 ;
319 return len;
320}
321
327static void perf_time(Timestamp const& t)
328{
329 if(!perf_buffer)
330 return;
331
332 Timestamp t_now;
333 Timestamp const& t_ = t.isNull() ? (t_now = Timestamp::now()) : t;
334
335 struct timespec const& ts = t_.ts();
336 leb128_buf_t s;
337 size_t s_len = leb128_encode(s, (uint64_t)ts.tv_sec);
338
339 zth_assert(ts.tv_nsec < TimeInterval::BILLION);
340 leb128_buf_t ns;
341 size_t ns_len = leb128_encode(ns, (uint64_t)ts.tv_nsec);
342
343 char volatile* p = perf_buffer->reserve(s_len + ns_len + 1);
344 if(!p)
345 return;
346
347 memcpy(const_cast<char*>(p + 1), s, s_len);
348 memcpy(const_cast<char*>(p + 1 + s_len), ns, ns_len);
349 *p = PerfEventTime;
350 perf_buffer->t(t_);
351}
352
356static void perf_dt(Timestamp const& t)
357{
358 if(!perf_buffer)
359 return;
360
361 Timestamp t_now;
362 Timestamp const& t_ = t.isNull() ? (t_now = Timestamp::now()) : t;
363
364 Timestamp const& t0 = perf_buffer->t();
365 TimeInterval d = t_ - t0;
366 if(d.hasPassed())
367 // We only go forward in time.
368 return;
369
370 if(d.ts().tv_sec) {
371 // Substantially long ago. Emit full timestamp.
372 perf_time(t_);
373 return;
374 }
375
376 zth_assert(d.ts().tv_nsec < TimeInterval::BILLION);
377
378 leb128_buf_t buf;
379 size_t len = leb128_encode(buf, (uint64_t)d.ts().tv_nsec);
380
381 char volatile* p = perf_buffer->reserve(len + 1);
382 if(!p)
383 return;
384
385 memcpy(const_cast<char*>(p + 1), buf, len);
387 perf_buffer->t(t_);
388}
389
394void perf_mark(char const* m, Timestamp const& t) noexcept
395{
396 perf_dt(t);
397
398 zth_perf_async_handle_t h = {perf_buffer};
399 perf_mark_async(m, &h);
400 perf_buffer->check();
401}
402
409{
410 if(!handle)
411 return;
412
413 handle->p = static_cast<void*>(perf_buffer);
414}
415
424void perf_mark_async(char const* marker, zth_perf_async_handle_t* handle) noexcept
425{
426 if(!handle || !handle->p)
427 return;
428
429 PerfBuffer* pb = static_cast<PerfBuffer*>(handle->p);
430 char volatile* p = pb->reserve(sizeof(marker) + 1);
431 if(!p)
432 return;
433
434 // NOLINTNEXTLINE(bugprone-multi-level-implicit-pointer-conversion)
435 memcpy(const_cast<char*>(p + 1), &marker, sizeof(marker));
436 *p = PerfEventMarker;
437}
438
443void perf_log(char const* fmt, ...) noexcept
444{
445 va_list args;
446 va_start(args, fmt);
447 perf_logv(fmt, args);
448 va_end(args);
449}
450
455void perf_log(Timestamp const& t, char const* fmt, ...) noexcept
456{
457 va_list args;
458 va_start(args, fmt);
459 perf_logv(fmt, args, t);
460 va_end(args);
461}
462
467void perf_logv(char const* fmt, va_list args, Timestamp const& t) noexcept
468{
469 if(!perf_buffer || !perf_buffer->running())
470 return;
471
472 va_list args2;
473 va_copy(args2, args);
474 int len = vsnprintf(nullptr, 0, fmt, args2);
475 va_end(args2);
476
477 if(len < 0) {
478 perf_mark(fmt, t);
479 return;
480 }
481
482 perf_dt(t);
483 char volatile* p = perf_buffer->reserve((size_t)len + 2);
484 if(!p)
485 return;
486
487 int len2 = vsnprintf(const_cast<char*>(p + 1), (size_t)len + 1U, fmt, args);
488 zth_assert(len == len2);
489 (void)len2;
490
491 p[0] = (char)PerfEventLog;
492 perf_buffer->check();
493}
494
500void perf_fiber(Fiber& f) noexcept
501{
502 if(!perf_buffer || !perf_buffer->running())
503 return;
504
505 perf_buffer->know(&f);
506
507 leb128_buf_t id;
508 size_t id_len = leb128_encode(id, f.id());
509
510 char const* name = f.name().c_str();
511 size_t name_len = f.name().size() + 1;
512
513 char volatile* p = perf_buffer->reserve(id_len + name_len + 1);
514 if(!p)
515 return;
516
517 memcpy(const_cast<char*>(p + 1), id, id_len);
518 memcpy(const_cast<char*>(p + 1 + id_len), name, name_len);
519 p[0] = PerfEventFiber;
520
522 perf_buffer->check();
523}
524
528void perf_fiber_state(Fiber& f, int state, Timestamp const& t) noexcept
529{
530 if(!perf_buffer || !perf_buffer->running())
531 return;
532
533 perf_dt(t);
534
535 if(!perf_buffer->knows(&f))
536 perf_fiber(f);
537
538 leb128_buf_t id;
539 size_t id_len = leb128_encode(id, f.id());
540
541 char volatile* p = perf_buffer->reserve(id_len + 2);
542 if(!p)
543 return;
544
545 memcpy(const_cast<char*>(p + 1), id, id_len);
546 p[0] = PerfEventFiberState;
547 if(state < 0)
548 state = (int)f.state();
549 zth_assert(state >= 0 && state < 0x100);
550 p[1 + id_len] = (char)state;
551
552 perf_buffer->check();
553}
554
573{
574 if(!perf_buffer)
575 return;
576
577 char const* p = perf_buffer->data();
578 if(!p)
579 return;
580
581 char const* end = p + perf_buffer->size();
582
583 if(!f)
584 goto done;
585
586 while(p < end) {
587 switch(p[0]) {
589 goto done;
590 case PerfEventTime: {
591 size_t len = 1 + leb128_len(p + 1);
592 len += leb128_len(p + len);
593 f(p, len);
594 p += len;
595 break;
596 }
597 case PerfEventTimeDelta: {
598 size_t len = leb128_len(p + 1) + 1U;
599 f(p, len);
600 p += len;
601 break;
602 }
603 case PerfEventMarker: {
604 // Convert the passed pointer to an actual string.
605 char const* const __attribute__((aligned(1)))* s =
606 reinterpret_cast<char const* const __attribute__((aligned(1)))*>(
607 p + 1);
608 size_t len = strlen(*s);
609 char e = PerfEventLog;
610 f(&e, 1);
611 f(*s, len + 1U); // including \0
612 p += sizeof(*s) + 1U;
613 break;
614 }
615 case PerfEventLog: {
616 size_t len = strlen(p + 1);
617 f(p, len + 2U); // including *p and \0
618 p += len + 2U;
619 break;
620 }
621 case PerfEventFiber: {
622 size_t len = 1 + leb128_len(p + 1U);
623 len += strlen(p + len) + 1U; // including \0
624 f(p, len);
625 p += len;
626 break;
627 }
628 case PerfEventFiberState: {
629 size_t len = 1 + leb128_len(p + 1) + 1U;
630 f(p, len);
631 p += len;
632 break;
633 }
634 default:
635 // Unknown. Abort.
636 goto done;
637 }
638 }
639done:
640 perf_buffer->release();
641}
642
643static void perf_continue()
644{
645 zth_dbg(perf, "[%s] Dump", currentWorker().id_str());
646
647 zth_assert(perf_buffer && perf_buffer->enabled() && !perf_buffer->running());
648
649 zth_perf_dump_callback_t* f = perf_buffer->dump_callback();
650 zth_assert(f);
651
652 Timestamp t_now = Timestamp::now();
653 char id = PerfEventTime;
654 f(&id, 1);
655
656 leb128_buf_t buf;
657 f(buf, leb128_encode(buf, (uint64_t)t_now.ts().tv_sec));
658 f(buf, leb128_encode(buf, (uint64_t)t_now.ts().tv_nsec));
659
660 perf_dump(f);
661
662 id = PerfEventLog;
663 f(&id, 1);
664 f("perf_dump", 10);
665
666 if(perf_buffer->done_callback() == perf_continue)
667 perf_start(perf_continue);
668}
669
686{
687 if(!perf_buffer || !perf_buffer->enabled())
688 return;
689 if(!f)
690 return;
691
692 perf_buffer->dump_callback(f);
693 perf_start(perf_continue);
694}
695
700void perf_abort() noexcept
701{
702 if(!perf_buffer)
703 return;
704
705 zth_perf_done_callback_t* f = perf_buffer->done_callback();
706 perf_buffer->done_callback(nullptr);
707 perf_buffer->stop();
708 if(f)
709 f();
710}
711
712#ifndef ZTH_VCD_USE_FILE_IO
713void perf_run_dump(char const* path) noexcept
714{
715 (void)path;
716}
717
718#else // ZTH_VCD_USE_FILE_IO
719
720// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
721ZTH_TLS_STATIC(FILE*, perf_dump_file, nullptr)
722
723static void perf_run_dump_callback(void const* buf, size_t len)
724{
725 if(!perf_dump_file)
726 return;
727 if(!buf || !len)
728 return;
729
730 if(fwrite(buf, len, 1, perf_dump_file) != 1) {
731 zth_log("Cannot write to perf dump file");
732 (void)fclose(perf_dump_file);
733 perf_dump_file = nullptr;
734 if(perf_buffer)
735 perf_buffer->dump_callback(nullptr);
736 perf_abort();
737 }
738}
739
744void perf_run_dump(char const* path) noexcept
745{
746 if(!path) {
747 // NOLINTNEXTLINE(concurrency-mt-unsafe)
748 path = getenv("ZTH_PERF_FILE");
749
750 try {
751 string p =
752 format("%s.%u-%u.perf", path ? path : "zth", (unsigned)getpid(),
753 (unsigned)currentWorker().id());
754
755 perf_run_dump(p.c_str());
756 } catch(...) {
757 perf_run_dump(path);
758 }
759 return;
760 }
761
762 if(perf_dump_file) {
763 (void)fclose(perf_dump_file);
764 perf_dump_file = nullptr;
765 }
766
767 perf_dump_file = fopen(path, "w+b");
768 if(!perf_dump_file) {
769 string e = err(errno);
770 zth_log("Cannot open perf dump file %s; %s", path, e.c_str());
771 return;
772 }
773
774 zth_dbg(perf, "[%s] Perf dump to %s", currentWorker().id_str(), path);
775 perf_run(perf_run_dump_callback);
776}
777#endif // ZTH_VCD_USE_FILE_IO
778
783{
784 if(!Config::EnablePerfEvent)
785 return 0;
786
787 if(!perf_buffer) {
788 try {
789 perf_buffer = new_alloc<PerfBuffer>();
790 } catch(std::bad_alloc const&) {
791 return 0;
792 }
793 }
794
795 perf_buffer->init();
796
797 if(zth_config(DoPerfEvent))
799
800 return 0;
801}
802
804{
805 if(!perf_buffer)
806 return;
807
808 bool running = perf_buffer->running();
809 (void)running;
810 perf_abort();
811
812#ifdef ZTH_VCD_USE_FILE_IO
813 if(running && zth_config(DoPerfEvent)) {
814 zth_dbg(perf, "[%s] Generate VCD", currentWorker().id_str());
815 int res = perf_vcd();
816 if(res)
817 zth_dbg(perf, "[%s] Cannot generate VCD; %s", currentWorker().id_str(),
818 err(res).c_str());
819 }
820#endif // ZTH_VCD_USE_FILE_IO
821
822 perf_buffer->release();
823 perf_buffer->deinit();
824
825 delete_alloc(perf_buffer);
826 perf_buffer = nullptr;
827}
828
829
830
832// VCD conversion
833//
834
835#ifndef ZTH_VCD_USE_FILE_IO
836// As there is no file system, we cannot generate VCD files. So, just return ENOSYS.
837
838int perf_vcd(char const* perf, char const* vcd) noexcept
839{
840 (void)perf;
841 (void)vcd;
842 return ENOSYS;
843}
844int perf_vcdf(FILE* perf, FILE* vcd) noexcept
845{
846 (void)perf;
847 (void)vcd;
848 return ENOSYS;
849}
850
851#else // Implement VCD conversion using file I/O.
852
860int perf_vcd(char const* perf, char const* vcd) noexcept
861{
862 FILE* fperf = nullptr;
863 bool fperf_reuse = false;
864 FILE* fvcd = nullptr;
865 int res = 0;
866
867 if(!perf) {
868 fperf = perf_dump_file;
869 if(!fperf)
870 return EINVAL;
871
872 if(fflush(fperf))
873 return errno;
874
875 fperf_reuse = true;
876 } else {
877 fperf = fopen(perf, "rb");
878 if(!fperf)
879 return errno;
880 }
881
882 if(!vcd) {
883 // NOLINTNEXTLINE(concurrency-mt-unsafe)
884 vcd = getenv("ZTH_PERF_VCD");
885 }
886
887 if(!vcd) {
888 try {
889 string svcd;
890
891 if(perf) {
892 svcd = perf;
893 size_t len = svcd.size();
894
895 if(len >= 5 && strcmp(svcd.c_str() + len - 5, ".perf") == 0)
896 svcd.resize(len - 5);
897
898 svcd.append(".vcd");
899 } else {
900 Worker const* w = Worker::instance();
901 if(w) {
902 svcd =
903 format("zth.%u-%u.vcd", (unsigned)getpid(),
904 (unsigned)w->id());
905 } else {
906 svcd = format("zth.%u.vcd", (unsigned)getpid());
907 }
908 }
909
910 fvcd = fopen(svcd.c_str(), "w");
911 } catch(std::bad_alloc const&) {
912 res = ENOMEM;
913 goto cleanup;
914 } catch(...) {
915 res = EINVAL;
916 goto cleanup;
917 }
918 } else {
919 fvcd = fopen(vcd, "w");
920 }
921
922 if(!fvcd) {
923 res = errno;
924 goto cleanup;
925 }
926
927 res = perf_vcdf(fperf, fvcd);
928
929cleanup:
930 if(fperf) {
931 if(fperf_reuse) {
932 (void)fseek(fperf, 0, SEEK_END);
933 } else {
934 (void)fclose(fperf);
935 }
936 }
937
938 if(fvcd) {
939 if(fclose(fvcd))
940 if(res == 0)
941 res = errno;
942 }
943
944 return res;
945}
946
949public:
950 explicit VCDGenerator(FILE* vcd)
951 : m_vcd(vcd)
952 , m_fiber()
953 {}
954
955 virtual ~VCDGenerator() noexcept is_default
956
957 int parse(FILE* perf)
958 {
959 if(!vcd())
960 return EINVAL;
961 if(!perf)
962 return EINVAL;
963
964 if(fseek(perf, 0, SEEK_SET))
965 return errno;
966
967 int res = 0;
968 if((res = start()))
969 return res;
970
971 if((res = parseFile(perf)))
972 return res;
973
974 return end();
975 }
976
977 virtual int start()
978 {
979 return 0;
980 }
981
982 virtual int end()
983 {
984 return 0;
985 }
986
987 virtual int handleFiber(uint64_t fiber, char const* name, size_t size)
988 {
989 (void)fiber;
990 (void)name;
991 (void)size;
992 return 0;
993 }
994
995 virtual int handleLog(char const* log)
996 {
997 (void)log;
998 return 0;
999 }
1000
1001 virtual int handleFiberState(int state)
1002 {
1003 (void)state;
1004 return 0;
1005 }
1006
1007 string const& vcdId(uint64_t fiber)
1008 {
1009 std::pair<decltype(m_vcdIds.begin()), bool> i =
1010 m_vcdIds.insert(std::make_pair(fiber, string()));
1011 if(likely(!i.second))
1012 // Already in the map. Return the value.
1013 return i.first->second;
1014
1015 // Just added with empty string. Generate a proper VCD identifier.
1016 // Identifier is a string of ASCII 34-126. Use 33 (!) as special character.
1017 // Do a radix-93 convert.
1018 uint64_t id = fiber;
1019 char buf[16]; // the string cannot be longer than 10 chars, as 93^10 > 2^64.
1020 char* s = &buf[sizeof(buf) - 1];
1021 *s = 0;
1022
1023 // NOLINTNEXTLINE(cppcoreguidelines-avoid-do-while)
1024 do {
1025 *--s = (char)(id % 93 + 34);
1026 id /= 93;
1027 } while(id);
1028
1029 return i.first->second = s;
1030 }
1031
1032 void vcdIdRelease(uint64_t fiber)
1033 {
1034 decltype(m_vcdIds.begin()) it = m_vcdIds.find(fiber);
1035 if(it != m_vcdIds.end())
1036 m_vcdIds.erase(it);
1037 }
1038
1039protected:
1040 FILE* vcd() const
1041 {
1042 return m_vcd;
1043 }
1044
1045 Timestamp const& t() const
1046 {
1047 return m_t;
1048 }
1049
1050 uint64_t const& fiber() const
1051 {
1052 return m_fiber;
1053 }
1054
1055 __attribute__((format(ZTH_ATTR_PRINTF, 2, 3))) int write(char const* fmt, ...)
1056 {
1057 va_list args;
1058 va_start(args, fmt);
1059 int res = writev(fmt, args);
1060 va_end(args);
1061 return res;
1062 }
1063
1064 __attribute__((format(ZTH_ATTR_PRINTF, 2, 0))) int writev(char const* fmt, va_list args)
1065 {
1066 int res = vfprintf(vcd(), fmt, args);
1067 if(res > 0)
1068 return 0;
1069
1070# ifdef ZTH_OS_POSIX
1071 return errno;
1072# else
1073 return EIO;
1074# endif
1075 }
1076
1077private:
1078 static int read(FILE* f, void* buf, size_t size)
1079 {
1080 size_t res = fread(buf, size, 1, f);
1081 if(res == 1)
1082 return 0;
1083# ifdef ZTH_OS_POSIX
1084 if(ferror(f))
1085 return errno;
1086# endif
1087 return EIO;
1088 }
1089
1090 int parseFile(FILE* f)
1091 {
1092 while(!feof(f)) {
1093 int res = parseEntry(f);
1094 if(res)
1095 return res;
1096 }
1097 return 0;
1098 }
1099
1100 int parseEntry(FILE* f)
1101 {
1102 // cppcheck-suppress unreadVariable
1103 int res = 0;
1104 char type = 0;
1105 if((res = parseType(f, type)))
1106 return feof(f) ? 0 : res;
1107
1108 switch(type) {
1109 case PerfEventTerminate:
1110 if(fseek(f, 0, SEEK_END))
1111 return errno;
1112 break;
1113 case PerfEventTime:
1114 if((res = parseTime(f)))
1115 return res;
1116 break;
1117 case PerfEventTimeDelta:
1118 if((res = parseTimeDelta(f)))
1119 return res;
1120 break;
1121 case PerfEventLog:
1122 if((res = parseLog(f)))
1123 return res;
1124 break;
1125 case PerfEventFiber:
1126 if((res = parseFiber(f)))
1127 return res;
1128 break;
1130 if((res = parseFiberState(f)))
1131 return res;
1132 break;
1133 case PerfEventMarker:
1134 default:
1135 return EFAULT;
1136 }
1137
1138 return 0;
1139 }
1140
1141 static int parseType(FILE* f, char& x)
1142 {
1143 return read(f, &x, sizeof(x));
1144 }
1145
1146 static int parseLeb128(FILE* f, uint64_t& x)
1147 {
1148 int i = 0;
1149 x = 0;
1150 unsigned char c = 0x80U;
1151 while(c & 0x80U) {
1152 int res = read(f, &c, 1);
1153 if(res)
1154 return res;
1155 if(i >= 64)
1156 return EOVERFLOW;
1157 x |= (uint64_t)(c & 0x7FU) << i;
1158 i += 7;
1159 }
1160 return 0;
1161 }
1162
1163 int parseTime(FILE* f)
1164 {
1165 int res = 0;
1166 struct timespec ts = {};
1167 uint64_t x = 0;
1168
1169 if((res = parseLeb128(f, x)))
1170 return res;
1171 ts.tv_sec = (time_t)x;
1172
1173 if((res = parseLeb128(f, x)))
1174 return res;
1175 if(x >= TimeInterval::BILLION)
1176 return EOVERFLOW;
1177 ts.tv_nsec = (long)x;
1178 m_t = ts;
1179
1180 return 0;
1181 }
1182
1183 int parseTimeDelta(FILE* f)
1184 {
1185 // cppcheck-suppress unreadVariable
1186 int res = 0;
1187 uint64_t ns = 0;
1188
1189 if((res = parseLeb128(f, ns)))
1190 return res;
1191 if(ns >= TimeInterval::BILLION)
1192 return EOVERFLOW;
1193
1194 m_t += TimeInterval(0, (long)ns);
1195 return 0;
1196 }
1197
1198 static int parseString(FILE* f, string& s)
1199 {
1200 char c = 0;
1201 while(true) {
1202 int res = read(f, &c, 1);
1203 if(res)
1204 return res;
1205 if(c == '\0')
1206 return 0;
1207 s.append(1, c);
1208 }
1209 }
1210
1211 int parseLog(FILE* f)
1212 {
1213 string s;
1214 int res = parseString(f, s);
1215 if(res)
1216 return res;
1217
1218 return handleLog(s.c_str());
1219 }
1220
1221 int parseFiber(FILE* f)
1222 {
1223 int res = 0;
1224 if((res = parseLeb128(f, m_fiber)))
1225 return res;
1226
1227 string name;
1228 if((res = parseString(f, name)))
1229 return res;
1230
1231 return handleFiber(m_fiber, name.data(), name.size());
1232 }
1233
1234 int parseFiberState(FILE* f)
1235 {
1236 int res = 0;
1237 if((res = parseLeb128(f, m_fiber)))
1238 return res;
1239
1240 unsigned char state = 0;
1241 if((res = read(f, &state, 1)))
1242 return res;
1243
1244 return handleFiberState((int)state);
1245 }
1246
1247private:
1248 FILE* m_vcd;
1249 Timestamp m_t;
1250 uint64_t m_fiber;
1251 map_type<uint64_t, string>::type m_vcdIds;
1252};
1253
1254class VCDHeaderGenerator final : public VCDGenerator {
1256
1257 typedef map_type<uint64_t, string>::type FibersMap;
1258
1259public:
1260 explicit VCDHeaderGenerator(FILE* vcd)
1261 : VCDGenerator(vcd)
1262 {}
1263
1264 virtual ~VCDHeaderGenerator() noexcept override is_default
1265
1266 virtual int start() override
1267 {
1268 int res = 0;
1269 time_t t_now = -1;
1270 if(time(&t_now) != -1) {
1271# if defined(ZTH_OS_LINUX) || defined(ZTH_OS_MAC)
1272 char dateBuf[128];
1273 char const* strnow = ctime_r(&t_now, dateBuf);
1274# else
1275 // Possibly not thread-safe.
1276 char const* strnow = ctime(&t_now);
1277# endif
1278 size_t len = strnow ? strlen(strnow) : 0;
1279 while(len > 0 && (strnow[len - 1] == '\n' || strnow[len - 1] == '\r'))
1280 len--;
1281 if(strnow)
1282 if((res = write("$date %.*s $end\n", (int)len, strnow)))
1283 return res;
1284 }
1285
1286 if((res = write(
1287 "$version %s $end\n$timescale 1 ns $end\n$scope module top $end\n",
1288 banner())))
1289 return res;
1290
1291 return 0;
1292 }
1293
1294 virtual int end() override
1295 {
1296 int res = 0;
1297
1298 for(FibersMap::iterator it = m_fibers.begin(); it != m_fibers.end(); ++it)
1299 if((res = outputFiber(it->first, it->second)))
1300 return res;
1301
1302 if((res = write("$upscope $end\n$enddefinitions $end\n")))
1303 return res;
1304
1305 return 0;
1306 }
1307
1308 virtual int handleFiber(uint64_t fiber, char const* name, size_t size) override
1309 {
1310 FibersMap::iterator it = m_fibers.find(fiber);
1311 if(it != m_fibers.end())
1312 it->second = string(name, size);
1313 else
1314 it = m_fibers.insert(std::make_pair(fiber, string(name, size))).first;
1315
1316 string& s = it->second;
1317 for(size_t i = 0; i < s.size(); ++i) {
1318 if(s[i] < 33 || s[i] > 126)
1319 s[i] = '_';
1320 }
1321
1322 return 0;
1323 }
1324
1325private:
1326 int outputFiber(uint64_t fiber, string const& name)
1327 {
1328 string const& id = vcdId(fiber);
1329
1330 if(!name.empty()) {
1331 return write(
1332 "$var wire 1 %s \\#%s_%s $end\n$var real 0 %s! "
1333 "\\#%s_%s/log $end\n",
1334 id.c_str(), str(fiber).c_str(), name.c_str(), id.c_str(),
1335 str(fiber).c_str(), name.c_str());
1336 } else {
1337 return write(
1338 "$var wire 1 %s \\#%s_Fiber $end\n$var real 0 "
1339 "%s! \\#%s_Fiber_log $end\n",
1340 id.c_str(), str(fiber).c_str(), id.c_str(), str(fiber).c_str());
1341 }
1342 }
1343
1344private:
1345 FibersMap m_fibers;
1346};
1347
1348class VCDDataGenerator final : public VCDGenerator {
1350public:
1351 explicit VCDDataGenerator(FILE* vcd)
1352 : VCDGenerator(vcd)
1353 {}
1354
1355 virtual ~VCDDataGenerator() noexcept override is_default
1356
1357 virtual int handleLog(char const* log) override
1358 {
1359 int res = 0;
1360 if((res =
1361 write("#%s%09u\ns", str(t().ts().tv_sec).c_str(),
1362 (unsigned)t().ts().tv_nsec))) {
1363 return res;
1364 }
1365
1366 char const* chunkStart = log;
1367 char const* chunkEnd = chunkStart;
1368 int len = 0;
1369 while(chunkEnd[1]) {
1370 if(*chunkEnd < 33 || *chunkEnd > 126) {
1371 if((res =
1372 write("%.*s\\x%02x", len, chunkStart,
1373 (unsigned)(unsigned char)*chunkEnd))) {
1374 return res;
1375 }
1376 chunkStart = chunkEnd = chunkEnd + 1;
1377 len = 0;
1378 } else {
1379 chunkEnd++;
1380 len++;
1381 }
1382 }
1383
1384 if((res = write("%s %s!\n", chunkStart, vcdId(fiber()).c_str())))
1385 return res;
1386
1387 return 0;
1388 }
1389
1390 virtual int handleFiberState(int state) override
1391 {
1392 char x = '-';
1393 bool doCleanup = false;
1394 switch(state) {
1395 case Fiber::New:
1396 x = 'L';
1397 break;
1398 case Fiber::Ready:
1399 x = '0';
1400 break;
1401 case Fiber::Running:
1402 x = '1';
1403 break;
1404 case Fiber::Waiting:
1405 x = 'W';
1406 break;
1407 case Fiber::Suspended:
1408 x = 'Z';
1409 break;
1410 case Fiber::Dead:
1411 x = 'X';
1412 break;
1413 default:
1414 x = '-';
1415 doCleanup = true;
1416 }
1417
1418 static_assert(sizeof(unsigned) >= 4, "");
1419
1420 int res = 0;
1421 if((res =
1422 write("#%s%09u\n%c%s\n", str(t().ts().tv_sec).c_str(),
1423 (unsigned)t().ts().tv_nsec, x, vcdId(fiber()).c_str()))) {
1424 return res;
1425 }
1426
1427 if(doCleanup)
1429
1430 return 0;
1431 }
1432};
1433
1440int perf_vcdf(FILE* perf, FILE* vcd) noexcept
1441{
1442 if(Config::UseLimitedFormatSpecifiers)
1443 return ENOSYS;
1444
1445 if(!perf || !vcd)
1446 return EINVAL;
1447
1448 fpos_t perf_pos;
1449 if(fgetpos(perf, &perf_pos))
1450 return errno;
1451
1452 int res = 0;
1453 try {
1454 res = VCDHeaderGenerator(vcd).parse(perf);
1455 if(!res)
1456 res = VCDDataGenerator(vcd).parse(perf);
1457 } catch(std::bad_alloc const&) {
1458 res = ENOMEM;
1459 } catch(...) {
1460 res = EFAULT;
1461 }
1462
1463 (void)fsetpos(perf, &perf_pos);
1464 return res;
1465}
1466#endif // VCD output support
1467
1468} // namespace zth
The fiber.
Definition fiber.h:62
@ Suspended
Definition fiber.h:82
@ Waiting
Definition fiber.h:82
@ Ready
Definition fiber.h:82
@ Running
Definition fiber.h:82
void stop() noexcept
Definition perf.cpp:282
~PerfBuffer() noexcept
Definition perf.cpp:57
bool knows(void *x) const noexcept
Definition perf.cpp:223
Timestamp const & t() const noexcept
Definition perf.cpp:172
zth_perf_done_callback_t * done_callback() const noexcept
Definition perf.cpp:197
char const * data() noexcept
Definition perf.cpp:119
void dump_callback(zth_perf_dump_callback_t *f) noexcept
Definition perf.cpp:182
zth_perf_dump_callback_t * dump_callback() const noexcept
Definition perf.cpp:187
int init() noexcept
Definition perf.cpp:72
size_t size() const noexcept
Definition perf.cpp:94
void done_callback(zth_perf_done_callback_t *f) noexcept
Definition perf.cpp:192
void check() noexcept
Definition perf.cpp:113
char volatile * reserve(size_t s) noexcept
Definition perf.cpp:124
void start(zth_perf_done_callback_t *f=nullptr) noexcept
Definition perf.cpp:202
set_type< Known >::type KnownSet
Definition perf.cpp:47
bool enabled() const noexcept
Definition perf.cpp:62
bool full() const noexcept
Definition perf.cpp:108
void t(Timestamp const &t) noexcept
Definition perf.cpp:177
size_t space() const noexcept
Definition perf.cpp:99
void know(void *x) noexcept
Definition perf.cpp:228
static constexpr size_t capacity() noexcept
Definition perf.cpp:67
bool running() const noexcept
Definition perf.cpp:218
void * Known
Definition perf.cpp:46
void deinit() noexcept
Definition perf.cpp:84
PerfBuffer() noexcept
Definition perf.cpp:49
void release() noexcept
Definition perf.cpp:155
static long const BILLION
Definition time.h:89
Convenient wrapper around struct timespec that contains an absolute timestamp.
Definition time.h:629
uint64_t id() const noexcept
Definition util.h:936
VCDDataGenerator(FILE *vcd)
Definition perf.cpp:1351
virtual int handleLog(char const *log) override
Definition perf.cpp:1357
virtual ~VCDDataGenerator() noexcept override=default
virtual int handleFiberState(int state) override
Definition perf.cpp:1390
virtual ~VCDGenerator() noexcept=default
Timestamp const & t() const
Definition perf.cpp:1045
uint64_t const & fiber() const
Definition perf.cpp:1050
int parse(FILE *perf)
Definition perf.cpp:957
virtual int handleLog(char const *log)
Definition perf.cpp:995
virtual int handleFiber(uint64_t fiber, char const *name, size_t size)
Definition perf.cpp:987
VCDGenerator(FILE *vcd)
Definition perf.cpp:950
int write(char const *fmt,...)
Definition perf.cpp:1055
virtual int handleFiberState(int state)
Definition perf.cpp:1001
virtual int start()
Definition perf.cpp:977
FILE * vcd() const
Definition perf.cpp:1040
virtual int end()
Definition perf.cpp:982
int writev(char const *fmt, va_list args)
Definition perf.cpp:1064
void vcdIdRelease(uint64_t fiber)
Definition perf.cpp:1032
string const & vcdId(uint64_t fiber)
Definition perf.cpp:1007
virtual ~VCDHeaderGenerator() noexcept override=default
VCDHeaderGenerator(FILE *vcd)
Definition perf.cpp:1260
virtual int start() override
Definition perf.cpp:1266
virtual int handleFiber(uint64_t fiber, char const *name, size_t size) override
Definition perf.cpp:1308
virtual int end() override
Definition perf.cpp:1294
The class that manages the fibers within this thread.
Definition worker.h:35
char const * c_str() const
Definition util.h:411
void zth_log(char const *fmt,...)
Logs a given printf()-like formatted string.
Definition util.h:1735
#define zth_config(name)
Checks if the given zth::Config field is enabled.
Definition config.h:46
Worker & currentWorker() noexcept
Return the (thread-local) singleton Worker instance.
Definition worker.h:417
void perf_stop() noexcept
Stops recording perf events.
Definition perf.cpp:276
void perf_async_handle(zth_perf_async_handle_t *handle) noexcept
Returns the handle of the current thread's perf buffer.
Definition perf.cpp:408
void perf_mark(char const *marker, Timestamp const &t=Timestamp()) noexcept
Put a string marker into the perf output.
Definition perf.cpp:394
void perf_log(char const *fmt,...) noexcept
Put a formatted log string into the perf output.
Definition perf.cpp:443
void perf_abort() noexcept
Abort a zth::perf_run().
Definition perf.cpp:700
void perf_run(zth_perf_dump_callback_t *f) noexcept
Setup the system to automatically perform perf event recording, dumping, and resuming.
Definition perf.cpp:685
void perf_start(zth_perf_done_callback_t *f=nullptr) noexcept
Starts recording perf events.
Definition perf.cpp:263
void perf_dump(zth_perf_dump_callback_t *f) noexcept
Passes collected perf data to f.
Definition perf.cpp:572
void perf_logv(char const *fmt, va_list args, Timestamp const &t=Timestamp()) noexcept
Put a formatted log string into the perf output.
Definition perf.cpp:467
void perf_run_dump(char const *path=nullptr) noexcept
Like zth::perf_run(), but dump the contents to file immediately.
Definition perf.cpp:744
void perf_mark_async(char const *marker, zth_perf_async_handle_t *handle) noexcept
Async-/thread-safe zth::perf_mark().
Definition perf.cpp:424
#define zth_dbg(group, fmt, a...)
Debug printf()-like function.
Definition util.h:194
std::basic_string< char, std::char_traits< char >, Config::Allocator< char >::type > string
std::string type using Config::Allocator::type.
Definition util.h:330
char const * banner() noexcept
Returns a banner line with version and configuration information.
Definition util.cpp:38
void log(char const *fmt,...)
Logs a given printf()-like formatted string.
Definition util.h:318
#define ZTH_TLS_STATIC(type, var, init)
Definition macros.h:102
#define is_default
Definition macros.h:223
#define ZTH_ATTR_PRINTF
Definition macros.h:64
void perf_fiber_state(Fiber &f, int state=-1, Timestamp const &t=Timestamp()) noexcept
Record the current fiber state.
Definition perf.cpp:528
cow_string str(T value)
Returns an zth::string representation of the given value.
Definition util.h:512
void perf_deinit()
Definition perf.cpp:803
int perf_init()
Initializes the per-thread perf event buffer.
Definition perf.cpp:782
PerfEvent
Definition perf.cpp:33
@ PerfEventTimeDelta
Definition perf.cpp:36
@ PerfEventFiber
Definition perf.cpp:39
@ PerfEventLog
Definition perf.cpp:38
@ PerfEventMarker
Definition perf.cpp:37
@ PerfEventTerminate
Definition perf.cpp:34
@ PerfEventTime
Definition perf.cpp:35
@ PerfEventFiberState
Definition perf.cpp:40
string err(int e)
Return a string like strerror() does, but as a zth::string.
Definition util.h:701
int perf_vcd(char const *perf=nullptr, char const *vcd=nullptr) noexcept
Convert a perf file into VCD.
Definition perf.cpp:860
char leb128_buf_t[9]
Definition perf.cpp:298
string format(char const *fmt,...)
Format like sprintf(), but save the result in an zth::string.
Definition util.h:498
void perf_fiber(Fiber &f) noexcept
Write fiber ID/name to the perf buffer.
Definition perf.cpp:500
int perf_vcdf(FILE *perf, FILE *vcd) noexcept
Convert a perf file into VCD.
Definition perf.cpp:1440
void() zth_perf_dump_callback_t(void const *, size_t)
Definition perf.h:31
void() zth_perf_done_callback_t()
Definition perf.h:30
static size_t const PerfEventBufferSize
Buffer size for perf events.
Definition config.h:239
static bool const EnableThreads
Add (Worker) thread support when true.
Definition config.h:106
static size_t const PerfEventBufferSpare
Minimum remaining space before perf event collection is stopped.
Definition config.h:249
std::map< Key, T, Compare, typename Config::Allocator< std::pair< const Key, T > >::type > type
Definition allocator.h:206
std::set< Key, Compare, typename Config::Allocator< Key >::type > type
Definition allocator.h:215
#define EOVERFLOW
Definition sync.h:1685
#define zth_assert(expr)
assert(), but better integrated in Zth.
Definition util.h:217
#define likely(expr)
Marks the given expression to likely be evaluated to true.
Definition util.h:45
#define ZTH_CLASS_NOCOPY(Class)
Definition util.h:234
#define unlikely(expr)
Marks the given expression to likely be evaluated to true.
Definition util.h:60