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 <concepts>
13#include <cstdint>
14#include <cstdlib>
15#include <iterator>
16#include <memory>
17#include <stdexcept>
18#include <string>
19#include <typeindex>
20#include <utility>
21#include <vector>
22
23#ifdef __GNUC__
24#if __has_include(<cxxabi.h>)
25#define LIBTCSPC_HAVE_CXA_DEMANGLE
26#include <cxxabi.h>
27#else
28#warning introspected type names will be mangled (cxxabi.h not available)
29#endif
30#endif
31
32namespace tcspc {
33
34namespace internal {
35
36[[nodiscard]] inline auto maybe_demangle(char const *mangled) -> std::string {
37#ifdef LIBTCSPC_HAVE_CXA_DEMANGLE
38 int status{-1};
39 auto demangled = std::unique_ptr<char, void (*)(void *)>(
40 abi::__cxa_demangle(mangled, nullptr, nullptr, &status), std::free);
41 return status == 0 ? demangled.get() : mangled;
42#else
43 return mangled;
44#endif
45}
46
47} // namespace internal
48
60 std::uintptr_t addr;
61 std::type_index typ;
62 std::string nm;
63
64 public:
76 template <typename Processor>
77 explicit processor_info(Processor const *processor, std::string name)
78 : addr(std::uintptr_t(processor)), typ(typeid(Processor)),
79 nm(std::move(name)) {}
80
89 [[nodiscard]] auto address() const -> std::uintptr_t { return addr; }
90
103 [[nodiscard]] auto type_name() const -> std::string {
104 return internal::maybe_demangle(typ.name());
105 }
106
112 [[nodiscard]] auto name() const -> std::string { return nm; }
113
115 friend auto operator==(processor_info const &lhs,
116 processor_info const &rhs) -> bool = default;
117};
118
131 // The processor address is not sufficient as a unique id, because some
132 // processors have their downstream as the first data member (resulting in
133 // the downstream having the same address). Pairing with the type id fixes
134 // this issue, because a data member cannot have the same type as its
135 // containing class.
136 std::uintptr_t addr;
137 std::type_index typ;
138
139 public:
147 template <typename Processor>
148 explicit processor_node_id(Processor const *processor)
149 : addr(std::uintptr_t(processor)), typ(typeid(Processor)) {}
150
152 friend auto operator==(processor_node_id const &lhs,
153 processor_node_id const &rhs) -> bool = default;
154
156 friend auto operator<(processor_node_id const &lhs,
157 processor_node_id const &rhs) -> bool {
158 return lhs.addr < rhs.addr ||
159 (lhs.addr == rhs.addr && lhs.typ < rhs.typ);
160 }
161
163 friend auto operator>(processor_node_id const &lhs,
164 processor_node_id const &rhs) -> bool {
165 return lhs.addr > rhs.addr ||
166 (lhs.addr == rhs.addr && lhs.typ > rhs.typ);
167 }
168
170 friend auto operator<=(processor_node_id const &lhs,
171 processor_node_id const &rhs) -> bool {
172 return not(lhs > rhs);
173 }
174
176 friend auto operator>=(processor_node_id const &lhs,
177 processor_node_id const &rhs) -> bool {
178 return not(lhs < rhs);
179 }
180};
181
182class processor_graph; // forward declaration for introspectable
183
197template <typename Proc>
198concept introspectable = requires(Proc const &p) {
199 { p.introspect_node() } -> std::same_as<processor_info>;
200 { p.introspect_graph() } -> std::same_as<processor_graph>;
201};
202
221 struct node_type {
223 processor_info info;
224 };
225
226 // All vectors kept sorted individually.
227 std::vector<node_type> nds;
228 std::vector<std::pair<processor_node_id, processor_node_id>> edgs;
229 std::vector<processor_node_id> entrypts;
230
231 public:
250 template <typename Processor>
252 auto push_entry_point(Processor const *processor) -> processor_graph & {
253 if (entrypts.size() > 1) {
254 throw std::logic_error(
255 "processor_graph can only push entry point when it has at most one entry point");
256 }
257
258 auto const id = processor_node_id(processor);
259 auto node = node_type{id, processor->introspect_node()};
260 auto [node_lower, node_upper] = std::equal_range(
261 nds.begin(), nds.end(), node,
262 [](auto const &l, auto const &r) { return l.id < r.id; });
263 if (node_lower != node_upper) {
264 throw std::logic_error(
265 "processor_graph cannot push entry point that already exists");
266 }
267 nds.insert(node_upper, std::move(node));
268
269 if (entrypts.empty()) {
270 entrypts.push_back(id);
271 } else {
272 auto const edge = std::pair{id, entrypts[0]};
273 auto edge_inspt = std::upper_bound(edgs.begin(), edgs.end(), edge);
274 edgs.insert(edge_inspt, edge);
275 entrypts[0] = id;
276 }
277
278 return *this;
279 }
280
286 [[nodiscard]] auto nodes() const -> std::vector<processor_node_id> {
287 std::vector<processor_node_id> ret;
288 ret.reserve(nds.size());
289 std::transform(nds.begin(), nds.end(), std::back_inserter(ret),
290 [](auto const &n) { return n.id; });
291 return ret;
292 }
293
301 [[nodiscard]] auto edges() const
302 -> std::vector<std::pair<processor_node_id, processor_node_id>> {
303 return edgs;
304 }
305
311 [[nodiscard]] auto entry_points() const -> std::vector<processor_node_id> {
312 return entrypts;
313 }
314
322 [[nodiscard]] auto is_entry_point(processor_node_id id) const -> bool {
323 return std::find(entrypts.begin(), entrypts.end(), id) !=
324 entrypts.end();
325 }
326
337 [[nodiscard]] auto node_index(processor_node_id id) const -> std::size_t {
338 auto it = std::find_if(nds.begin(), nds.end(),
339 [id](auto const &n) { return n.id == id; });
340 if (it == nds.end())
341 throw std::out_of_range("no such node id in processor_graph");
342 return std::size_t(std::distance(nds.begin(), it));
343 }
344
352 [[nodiscard]] auto node_info(processor_node_id id) const
353 -> processor_info {
354 auto it = std::find_if(nds.begin(), nds.end(),
355 [id](auto const &n) { return n.id == id; });
356 if (it == nds.end())
357 throw std::out_of_range("no such node id in processor_graph");
358 return it->info;
359 }
360
361 friend auto merge_processor_graphs(processor_graph const &a,
362 processor_graph const &b)
364};
365
380[[nodiscard]] inline auto merge_processor_graphs(processor_graph const &a,
381 processor_graph const &b)
382 -> processor_graph {
384
385 c.nds.reserve(a.nds.size() + b.nds.size());
386 std::set_union(a.nds.begin(), a.nds.end(), b.nds.begin(), b.nds.end(),
387 std::back_inserter(c.nds),
388 [](auto const &l, auto const &r) { return l.id < r.id; });
389
390 c.edgs.reserve(a.edgs.size() + b.edgs.size());
391 std::set_union(a.edgs.begin(), a.edgs.end(), b.edgs.begin(), b.edgs.end(),
392 std::back_inserter(c.edgs));
393
394 c.entrypts.reserve(a.entrypts.size() + b.entrypts.size());
395 std::set_union(a.entrypts.begin(), a.entrypts.end(), b.entrypts.begin(),
396 b.entrypts.end(), std::back_inserter(c.entrypts));
397
398 return c;
399}
400
401namespace internal {
402
403inline auto format_hex_addr(std::uintptr_t p) -> std::string {
404 std::array<char, sizeof(void *) * 2 + 3> buf{};
405 std::fill(buf.begin(), buf.end(), '0');
406 // NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic)
407 auto const r =
408 std::to_chars(buf.data(), buf.data() + buf.size() - 1, p, 16);
409 *r.ptr = '\0';
410 std::rotate(buf.begin(),
411 std::next(buf.begin(), std::distance(buf.data(), r.ptr) + 1),
412 buf.end());
413 // NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic)
414 buf[1] = 'x';
415 return buf.data();
416};
417
418} // namespace internal
419
429[[nodiscard]] inline auto
430graphviz_from_processor_graph(processor_graph const &graph) -> std::string {
431 std::string dot;
432 dot += "digraph G {\n";
433 for (auto const &node : graph.nodes()) {
434 processor_info const info = graph.node_info(node);
435 dot += " n";
436 dot += std::to_string(graph.node_index(node));
437 dot += " [shape=box label=\"";
438 dot += info.name();
439 dot += "\" tooltip=\"";
440 dot += info.type_name();
441 dot += " at ";
442 dot += internal::format_hex_addr(info.address());
443 dot += "\"];\n";
444 }
445 for (auto const &edge : graph.edges()) {
446 dot += " n";
447 dot += std::to_string(graph.node_index(edge.first));
448 dot += " -> n";
449 dot += std::to_string(graph.node_index(edge.second));
450 dot += ";\n";
451 }
452 dot += "}\n";
453 return dot;
454}
455
456} // namespace tcspc
Value type representing a directed acyclic graph of processors.
Definition introspect.hpp:220
auto node_info(processor_node_id id) const -> processor_info
Return metadata for the processor represented by the given node.
Definition introspect.hpp:352
auto edges() const -> std::vector< std::pair< processor_node_id, processor_node_id > >
Return all of the edges of this graph.
Definition introspect.hpp:301
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:337
auto nodes() const -> std::vector< processor_node_id >
Return all of the nodes of this graph.
Definition introspect.hpp:286
auto entry_points() const -> std::vector< processor_node_id >
Return all of the entry points of this graph.
Definition introspect.hpp:311
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:322
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:380
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:252
Value type representing metadata of a processor.
Definition introspect.hpp:59
auto type_name() const -> std::string
Return the C++ type name of the processor.
Definition introspect.hpp:103
auto address() const -> std::uintptr_t
Return the address of the processor.
Definition introspect.hpp:89
processor_info(Processor const *processor, std::string name)
Construct with pointer to processor and name.
Definition introspect.hpp:77
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:112
Value type representing processor identity within a graph.
Definition introspect.hpp:130
friend auto operator>(processor_node_id const &lhs, processor_node_id const &rhs) -> bool
Greater-than operator.
Definition introspect.hpp:163
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:156
friend auto operator>=(processor_node_id const &lhs, processor_node_id const &rhs) -> bool
Greater-than-or-equal-to operator.
Definition introspect.hpp:176
friend auto operator<=(processor_node_id const &lhs, processor_node_id const &rhs) -> bool
Less-than-or-equal-to operator.
Definition introspect.hpp:170
processor_node_id(Processor const *processor)
Construct with a pointer to a processor.
Definition introspect.hpp:148
Concept that is satisfied when a processor supports introspection.
Definition introspect.hpp:198
Concept that is satisfied when a processor handles the given event types and flush.
Definition processor.hpp:119
auto graphviz_from_processor_graph(processor_graph const &graph) -> std::string
Return a Graphviz dot representation of a processor graph.
Definition introspect.hpp:430
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:380
libtcspc namespace.
Definition acquire.hpp:30