tracy引入

This commit is contained in:
2026-09-04 22:35:59 +08:00
parent 0a9b694efd
commit 0d8955cdf2
5 changed files with 176 additions and 3 deletions
@@ -0,0 +1,38 @@
#pragma once
#include <boost/pfr/core.hpp>
#include <boost/pfr/core_name.hpp>
#include <cstddef>
#include <string_view>
#include <type_traits>
#include <utility>
namespace aethera::observation {
namespace detail {
void trace_serialize() = delete;
template <class Sink, class T>
concept Custom_Serializable = requires(Sink& sink, const T& value) { trace_serialize(sink, value); };
template <class Sink, class T>
void serialize_custom(Sink& sink, const T& value) {
trace_serialize(sink, value);
}
template <class Sink, class T>
void serialize_field(Sink& sink, std::string_view name, const T& value) {
using Value = std::remove_cvref_t<T>;
if constexpr (Custom_Serializable<Sink, Value>) serialize_custom(sink, value);
else sink.field(name, value);
}
template <class Sink, class T, std::size_t... Index>
void serialize_aggregate(Sink& sink, const T& value, std::index_sequence<Index...>) {
(serialize_field(sink, boost::pfr::get_name<Index, T>(), boost::pfr::get<Index>(value)), ...);
}
}
template <class Sink, class T>
void serialize(Sink& sink, const T& value) {
using Value = std::remove_cvref_t<T>;
if constexpr (detail::Custom_Serializable<Sink, Value>) {
detail::serialize_custom(sink, value);
} else {
static_assert(std::is_aggregate_v<Value>, "observation serialization requires an aggregate or trace_serialize overload");
detail::serialize_aggregate(sink, value, std::make_index_sequence<boost::pfr::tuple_size_v<Value>>{});
}
}
}
@@ -0,0 +1,91 @@
#pragma once
#include "Serialize.hpp"
#include <magic_enum/magic_enum.hpp>
#include <array>
#include <charconv>
#include <concepts>
#include <cstddef>
#include <cstring>
#include <string>
#include <string_view>
#include <system_error>
#include <type_traits>
#ifdef TRACY_ENABLE
#include <tracy/Tracy.hpp>
#endif
namespace aethera::observation {
class Tracy_Text_Sink {
public:
template <class T>
void field(std::string_view name, const T& value) {
if (size_ != 0) append(" ");
append(name);
append("=");
append_value(value);
}
[[nodiscard]] const char* data() const noexcept {
return buffer_.data();
}
[[nodiscard]] std::size_t size() const noexcept {
return size_;
}
[[nodiscard]] bool empty() const noexcept {
return size_ == 0;
}
private:
template <class>
static constexpr bool always_false = false;
void append(std::string_view value) {
const auto available = buffer_.size() - size_;
const auto count = value.size() < available ? value.size() : available;
if (count == 0) return;
std::memcpy(buffer_.data() + size_, value.data(), count);
size_ += count;
}
template <class T>
void append_number(T value) {
std::array<char, 128> text{};
const auto [end, error] = std::to_chars(text.data(), text.data() + text.size(), value);
if (error == std::errc{}) append(std::string_view(text.data(), static_cast<std::size_t>(end - text.data())));
}
template <class T>
void append_value(const T& value) {
using Value = std::remove_cvref_t<T>;
if constexpr (std::same_as<Value, bool>) {
append(value ? "true" : "false");
} else if constexpr (std::is_enum_v<Value>) {
append(magic_enum::enum_name(value));
} else if constexpr (std::integral<Value> || std::floating_point<Value>) {
append_number(value);
} else if constexpr (std::same_as<Value, std::string>) {
append(value);
} else if constexpr (std::same_as<Value, std::string_view>) {
append(value);
} else if constexpr (std::same_as<Value, char>) {
append(std::string_view(&value, 1));
} else if constexpr (std::same_as<Value, char*> || std::same_as<Value, const char*>) {
append(value == nullptr ? std::string_view("null") : std::string_view(value));
} else if constexpr (requires { std::string_view{value}; }) {
append(std::string_view{value});
} else {
static_assert(always_false<Value>, "Tracy tag field type requires a trace_serialize overload that emits supported scalar fields");
}
}
std::array<char, 1024> buffer_{};
std::size_t size_{};
};
#ifdef TRACY_ENABLE
template <class Make_Tags>
void attach_tracy_tags(tracy::ScopedZone& zone, Make_Tags&& make_tags) {
if (!zone.IsActive()) return;
Tracy_Text_Sink sink;
serialize(sink, make_tags());
if (!sink.empty()) zone.Text(sink.data(), sink.size());
}
#endif
}
#ifdef TRACY_ENABLE
#define AETHERA_TRACE_ZONE(name, ...) ZoneScopedN(name); ::aethera::observation::attach_tracy_tags(___tracy_scoped_zone, [&]() -> decltype(auto) { return (__VA_ARGS__); })
#else
#define AETHERA_TRACE_ZONE(name, ...)
#endif
@@ -0,0 +1,45 @@
#include "observation/Tracy.hpp"
#include <gtest/gtest.h>
#include <cstdint>
#include <string_view>
namespace {
enum class Test_Phase {
prepare,
publish
};
namespace custom {
struct Resource_Id {
std::uint64_t value;
};
template <class Sink>
void trace_serialize(Sink& sink, const Resource_Id& value) {
sink.field("resource", value.value);
}
}
struct Test_Tags {
std::uint64_t frame;
custom::Resource_Id resource;
Test_Phase phase;
bool dirty;
};
namespace custom {
struct Test_Tags {
std::uint64_t resource;
};
template <class Sink>
void trace_serialize(Sink& sink, const Test_Tags& value) {
sink.field("id", value.resource);
sink.field("kind", "resource");
}
}
TEST(Observation_Serialize, Aggregate_Uses_Pfr_And_Field_Customization) {
aethera::observation::Tracy_Text_Sink sink;
aethera::observation::serialize(sink, Test_Tags{17, {23}, Test_Phase::prepare, true});
EXPECT_EQ(std::string_view(sink.data(), sink.size()), "frame=17 resource=23 phase=prepare dirty=true");
}
TEST(Observation_Serialize, Adl_Serializer_Overrides_Aggregate_Fallback) {
aethera::observation::Tracy_Text_Sink sink;
aethera::observation::serialize(sink, custom::Test_Tags{41});
EXPECT_EQ(std::string_view(sink.data(), sink.size()), "id=41 kind=resource");
}
}
+2 -1
View File
@@ -1,7 +1,7 @@
rcl_init(aethera_kernel)
include(${CMAKE_CURRENT_LIST_DIR}/cmake/taskflow.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/cmake/tracy.cmake)
set(Aethera_Kernel_dependencies global::proxy aethera_kernel::tracy aethera_kernel::taskflow global::magic_enum)
set(Aethera_Kernel_dependencies global::proxy aethera_kernel::taskflow global::magic_enum global::pfr)
set(Aethera_BUILD_TESTS TRUE)
if (Aethera_BUILD_TESTS)
enable_testing()
@@ -37,6 +37,7 @@ append_glob_source(Aethera_Kernel_Core_sources "${Aethera_Kernel_source_dir}")
add_library(Aethera_Kernel_Core STATIC ${Aethera_Kernel_Core_sources})
target_include_directories(Aethera_Kernel_Core PUBLIC
"$<BUILD_INTERFACE:${Aethera_Kernel_source_dir}>"
"$<BUILD_INTERFACE:${EXTERNAL_DIR}/source/pfr/include>"
"$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/third_party>"
"$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/third_party/ratas-master/src>"
)
-2
View File
@@ -1,7 +1,6 @@
include("${CMAKE_CURRENT_LIST_DIR}/../web_server/cmake/rely.cmake")
set(Aethera_MCP_dependencies
global::drogon
global::pfr
)
rcl_add_dependency_action_targets(Aethera_MCP_env ${Aethera_MCP_dependencies})
set_target_properties(Aethera_MCP_env PROPERTIES FOLDER Aethera_MCP)
@@ -42,7 +41,6 @@ append_glob_source(Aethera_MCP_core_sources "${Aethera_MCP_core_dir}")
add_library(Aethera_MCP_Core STATIC ${Aethera_MCP_core_sources})
target_include_directories(Aethera_MCP_Core PUBLIC
"$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/..>"
"$<BUILD_INTERFACE:${EXTERNAL_DIR}/source/pfr/include>"
"$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/../web_server/third_party/include>")
target_compile_features(Aethera_MCP_Core PUBLIC cxx_std_23)
target_compile_definitions(Aethera_MCP_Core PUBLIC NOMINMAX)