libtcspc C++ API
Streaming TCSPC and time tag data processing
Loading...
Searching...
No Matches
introspect.hpp
1/*
2 * This file is part of libtcspc
3 * Copyright 2019-2026 Board of Regents of the University of Wisconsin System
4 * SPDX-License-Identifier: MIT
5 */
6
7#pragma once
8
9#include <algorithm>
10#include <array>
11#include <charconv>
12#include <cstdint>
13#include <cstdlib>
14#include <iterator>
15#include <memory>
16#include <stdexcept>
17#include <string>
18#include <typeindex>
19#include <utility>
20#include <vector>
21
22#ifdef __GNUC__
23#if __has_include(<cxxabi.h>)
24#define LIBTCSPC_HAVE_CXA_DEMANGLE
25#include <cxxabi.h>
26#else
27#warning introspected type names will be mangled (cxxabi.h not available)
28#endif
29#endif
30
31namespace tcspc {
32
33namespace internal {
34
35[[nodiscard]] inline auto maybe_demangle(char const *mangled) -> std::string {
36#ifdef LIBTCSPC_HAVE_CXA_DEMANGLE
37 int status{-1};
38 auto demangled = std::unique_ptr<char, void (*)(void *)>(
39 abi::__cxa_demangle(mangled, nullptr, nullptr, &status), std::free);
40 return status == 0 ? demangled.get() : mangled;
41#else
42 return mangled;
43#endif
44}
45
46} // namespace internal
47
59 std::uintptr_t addr;
60 std::type_index typ;
61 std::string nm;
62
63 public:
75 template <typename Processor>
76 explicit processor_info(Processor const *processor, std::string name)
77 : addr(std::uintptr_t(processor)), typ(typeid(Processor)),
78 nm(std::move(name)) {}
79
88 [[nodiscard]] auto address() const -> std::uintptr_t { return addr; }
89
102 [[nodiscard]] auto type_name() const -> std::string {
103 return internal::maybe_demangle(typ.name());
104 }
105
111 [[nodiscard]] auto name() const -> std::string { return nm; }
112
114 friend auto operator==(processor_info const &lhs,
115 processor_info const &rhs) -> bool = default;
116};
117
130 // The processor address is not sufficient as a unique id, because some
131 // processors have their downstream as the first data member (resulting in
132 // the downstream having the same address). Pairing with the type id fixes
133 // this issue, because a data member cannot have the same type as its
134 // containing class.
135 std::uintptr_t addr;
136 std::type_index typ;
137
138 public:
146 template <typename Processor>
147 explicit processor_node_id(Processor const *processor)
148 : addr(std::uintptr_t(processor)), typ(typeid(Processor)) {}
149
151 friend auto operator==(processor_node_id const &lhs,
152 processor_node_id const &rhs) -> bool = default;
153
155 friend auto operator<(processor_node_id const &lhs,
156 processor_node_id const &rhs) -> bool {
157 return lhs.addr < rhs.addr ||
158 (lhs.addr == rhs.addr && lhs.typ < rhs.typ);
159 }
160
162 friend auto operator>(processor_node_id const &lhs,
163 processor_node_id const &rhs) -> bool {
164 return lhs.addr > rhs.addr ||
165 (lhs.addr == rhs.addr && lhs.typ > rhs.typ);
166 }
167
169 friend auto operator<=(processor_node_id const &lhs,
170 processor_node_id const &rhs) -> bool {
171 return not(lhs > rhs);
172 }
173
175 friend auto operator>=(processor_node_id const &lhs,
176 processor_node_id const &rhs) -> bool {
177 return not(lhs < rhs);
178 }
179};
180
199 struct node_type {
201 processor_info info;
202 };
203
204 // All vectors kept sorted individually.
205 std::vector<node_type> nds;
206 std::vector<std::pair<processor_node_id, processor_node_id>> edgs;
207 std::vector<processor_node_id> entrypts;
208
209 public:
228 template <typename Processor>
229 auto push_entry_point(Processor const *processor) -> processor_graph & {
230 if (entrypts.size() > 1) {
231 throw std::logic_error(
232 "processor_graph can only push entry point when it has at most one entry point");
233 }
234
235 auto const id = processor_node_id(processor);
236 auto node = node_type{id, processor->introspect_node()};
237 auto [node_lower, node_upper] = std::equal_range(
238 nds.begin(), nds.end(), node,
239 [](auto const &l, auto const &r) { return l.id < r.id; });
240 if (node_lower != node_upper) {
241 throw std::logic_error(
242 "processor_graph cannot push entry point that already exists");
243 }
244 nds.insert(node_upper, std::move(node));
245
246 if (entrypts.empty()) {
247 entrypts.push_back(id);
248 } else {
249 auto const edge = std::pair{id, entrypts[0]};
250 auto edge_inspt = std::upper_bound(edgs.begin(), edgs.end(), edge);
251 edgs.insert(edge_inspt, edge);
252 entrypts[0] = id;
253 }
254
255 return *this;
256 }
257
263 [[nodiscard]] auto nodes() const -> std::vector<processor_node_id> {
264 std::vector<processor_node_id> ret;
265 ret.reserve(nds.size());
266 std::transform(nds.begin(), nds.end(), std::back_inserter(ret),
267 [](auto const &n) { return n.id; });
268 return ret;
269 }
270
278 [[nodiscard]] auto edges() const
279 -> std::vector<std::pair<processor_node_id, processor_node_id>> {
280 return edgs;
281 }
282
288 [[nodiscard]] auto entry_points() const -> std::vector<processor_node_id> {
289 return entrypts;
290 }
291
299 [[nodiscard]] auto is_entry_point(processor_node_id id) const -> bool {
300 return std::find(entrypts.begin(), entrypts.end(), id) !=
301 entrypts.end();
302 }
303
314 [[nodiscard]] auto node_index(processor_node_id id) const -> std::size_t {
315 auto it = std::find_if(nds.begin(), nds.end(),
316 [id](auto const &n) { return n.id == id; });
317 if (it == nds.end())
318 throw std::out_of_range("no such node id in processor_graph");
319 return std::size_t(std::distance(nds.begin(), it));
320 }
321
329 [[nodiscard]] auto node_info(processor_node_id id) const
330 -> processor_info {
331 auto it = std::find_if(nds.begin(), nds.end(),
332 [id](auto const &n) { return n.id == id; });
333 if (it == nds.end())
334 throw std::out_of_range("no such node id in processor_graph");
335 return it->info;
336 }
337
338 friend auto merge_processor_graphs(processor_graph const &a,
339 processor_graph const &b)
341};
342
357[[nodiscard]] inline auto merge_processor_graphs(processor_graph const &a,
358 processor_graph const &b)
359 -> processor_graph {
361
362 c.nds.reserve(a.nds.size() + b.nds.size());
363 std::set_union(a.nds.begin(), a.nds.end(), b.nds.begin(), b.nds.end(),
364 std::back_inserter(c.nds),
365 [](auto const &l, auto const &r) { return l.id < r.id; });
366
367 c.edgs.reserve(a.edgs.size() + b.edgs.size());
368 std::set_union(a.edgs.begin(), a.edgs.end(), b.edgs.begin(), b.edgs.end(),
369 std::back_inserter(c.edgs));
370
371 c.entrypts.reserve(a.entrypts.size() + b.entrypts.size());
372 std::set_union(a.entrypts.begin(), a.entrypts.end(), b.entrypts.begin(),
373 b.entrypts.end(), std::back_inserter(c.entrypts));
374
375 return c;
376}
377
378namespace internal {
379
380inline auto format_hex_addr(std::uintptr_t p) -> std::string {
381 std::array<char, sizeof(void *) * 2 + 3> buf{};
382 std::fill(buf.begin(), buf.end(), '0');
383 // NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic)
384 auto const r =
385 std::to_chars(buf.data(), buf.data() + buf.size() - 1, p, 16);
386 *r.ptr = '\0';
387 std::rotate(buf.begin(),
388 std::next(buf.begin(), std::distance(buf.data(), r.ptr) + 1),
389 buf.end());
390 // NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic)
391 buf[1] = 'x';
392 return buf.data();
393};
394
395} // namespace internal
396
406[[nodiscard]] inline auto
407graphviz_from_processor_graph(processor_graph const &graph) -> std::string {
408 std::string dot;
409 dot += "digraph G {\n";
410 for (auto const &node : graph.nodes()) {
411 processor_info const info = graph.node_info(node);
412 dot += " n";
413 dot += std::to_string(graph.node_index(node));
414 dot += " [shape=box label=\"";
415 dot += info.name();
416 dot += "\" tooltip=\"";
417 dot += info.type_name();
418 dot += " at ";
419 dot += internal::format_hex_addr(info.address());
420 dot += "\"];\n";
421 }
422 for (auto const &edge : graph.edges()) {
423 dot += " n";
424 dot += std::to_string(graph.node_index(edge.first));
425 dot += " -> n";
426 dot += std::to_string(graph.node_index(edge.second));
427 dot += ";\n";
428 }
429 dot += "}\n";
430 return dot;
431}
432
433} // namespace tcspc
Value type representing a directed acyclic graph of processors.
Definition introspect.hpp:198
auto node_info(processor_node_id id) const -> processor_info
Return metadata for the processor represented by the given node.
Definition introspect.hpp:329
auto edges() const -> std::vector< std::pair< processor_node_id, processor_node_id > >
Return all of the edges of this graph.
Definition introspect.hpp:278
auto node_index(processor_node_id id) const -> std::size_t
Return the numerical index of the given node in this graph.
Definition introspect.hpp:314
auto nodes() const -> std::vector< processor_node_id >
Return all of the nodes of this graph.
Definition introspect.hpp:263
auto entry_points() const -> std::vector< processor_node_id >
Return all of the entry points of this graph.
Definition introspect.hpp:288
auto is_entry_point(processor_node_id id) const -> bool
Return whether the given node is an entry point of this graph.
Definition introspect.hpp:299
friend auto merge_processor_graphs(processor_graph const &a, processor_graph const &b) -> processor_graph
Create a new processor graph by merging two existing ones.
Definition introspect.hpp:357
auto push_entry_point(Processor const *processor) -> processor_graph &
Add a processor node to this graph, upstream of the current entry point (if any), making it the new e...
Definition introspect.hpp:229
Value type representing metadata of a processor.
Definition introspect.hpp:58
auto type_name() const -> std::string
Return the C++ type name of the processor.
Definition introspect.hpp:102
auto address() const -> std::uintptr_t
Return the address of the processor.
Definition introspect.hpp:88
processor_info(Processor const *processor, std::string name)
Construct with pointer to processor and name.
Definition introspect.hpp:76
friend auto operator==(processor_info const &lhs, processor_info const &rhs) -> bool=default
Equality comparison operator.
auto name() const -> std::string
Return the simple name of the processor.
Definition introspect.hpp:111
Value type representing processor identity within a graph.
Definition introspect.hpp:129
friend auto operator>(processor_node_id const &lhs, processor_node_id const &rhs) -> bool
Greater-than operator.
Definition introspect.hpp:162
friend auto operator==(processor_node_id const &lhs, processor_node_id const &rhs) -> bool=default
Equality comparison operator.
friend auto operator<(processor_node_id const &lhs, processor_node_id const &rhs) -> bool
Less-than operator.
Definition introspect.hpp:155
friend auto operator>=(processor_node_id const &lhs, processor_node_id const &rhs) -> bool
Greater-than-or-equal-to operator.
Definition introspect.hpp:175
friend auto operator<=(processor_node_id const &lhs, processor_node_id const &rhs) -> bool
Less-than-or-equal-to operator.
Definition introspect.hpp:169
processor_node_id(Processor const *processor)
Construct with a pointer to a processor.
Definition introspect.hpp:147
Concept that is satisfied when a processor handles the given event types and flush.
Definition processor.hpp:143
auto graphviz_from_processor_graph(processor_graph const &graph) -> std::string
Return a Graphviz dot representation of a processor graph.
Definition introspect.hpp:407
auto merge_processor_graphs(processor_graph const &a, processor_graph const &b) -> processor_graph
Create a new processor graph by merging two existing ones.
Definition introspect.hpp:357
libtcspc namespace.
Definition acquire.hpp:29