From 4185965d926e9722914b6ea6282cf5412ef326ef Mon Sep 17 00:00:00 2001 From: Ila Hadi-Assar Date: Fri, 17 Apr 2026 12:48:24 +0200 Subject: [PATCH 1/9] squash tracing poc --- .github/workflows/build-ubuntu.yml | 6 + .github/workflows/build-windows.yml | 7 + cmake/submodule_dependencies.cmake | 1 + conanfile.py | 7 +- ecal/core/CMakeLists.txt | 18 ++ ecal/core/src/ecal.cpp | 2 + ecal/core/src/ecal_global_accessors.cpp | 19 ++ ecal/core/src/ecal_global_accessors.h | 10 + ecal/core/src/pubsub/ecal_publisher.cpp | 3 +- ecal/core/src/pubsub/ecal_publisher_impl.cpp | 40 +++ ecal/core/src/pubsub/ecal_publisher_impl.h | 1 + ecal/core/src/pubsub/ecal_subscriber_impl.cpp | 39 ++- ecal/core/src/tracing/span.cpp | 68 ++++ ecal/core/src/tracing/span.h | 54 ++++ ecal/core/src/tracing/trace_provider.cpp | 104 +++++++ ecal/core/src/tracing/trace_provider.h | 93 ++++++ ecal/core/src/tracing/tracing.h | 114 +++++++ ecal/core/src/tracing/tracing_writer.h | 46 +++ .../core/src/tracing/tracing_writer_jsonl.cpp | 147 +++++++++ ecal/core/src/tracing/tracing_writer_jsonl.h | 64 ++++ ecal/tests/CMakeLists.txt | 2 +- ecal/tests/cpp/tracing_test/CMakeLists.txt | 68 ++++ ecal/tests/cpp/tracing_test/TESTS.md | 135 ++++++++ .../tracing_integration_test.cpp | 193 ++++++++++++ .../integrationtest/tracing_scale_test.cpp | 235 ++++++++++++++ .../tracing_thread_safety_test.cpp | 231 ++++++++++++++ .../systemtest/tracing_pubsub_stress_test.cpp | 262 ++++++++++++++++ .../tracing_test/src/tracing_test_helpers.h | 128 ++++++++ .../src/unittest/tracing_edge_case_test.cpp | 192 ++++++++++++ .../src/unittest/tracing_layer_type_test.cpp | 97 ++++++ .../src/unittest/tracing_provider_test.cpp | 236 ++++++++++++++ .../src/unittest/tracing_span_test.cpp | 206 +++++++++++++ .../src/unittest/tracing_types_test.cpp | 117 +++++++ .../src/unittest/tracing_writer_test.cpp | 291 ++++++++++++++++++ .../nlohmann_json/build-nlohmann_json.cmake | 13 + 35 files changed, 3243 insertions(+), 6 deletions(-) create mode 100644 ecal/core/src/tracing/span.cpp create mode 100644 ecal/core/src/tracing/span.h create mode 100644 ecal/core/src/tracing/trace_provider.cpp create mode 100644 ecal/core/src/tracing/trace_provider.h create mode 100644 ecal/core/src/tracing/tracing.h create mode 100644 ecal/core/src/tracing/tracing_writer.h create mode 100644 ecal/core/src/tracing/tracing_writer_jsonl.cpp create mode 100644 ecal/core/src/tracing/tracing_writer_jsonl.h create mode 100644 ecal/tests/cpp/tracing_test/CMakeLists.txt create mode 100644 ecal/tests/cpp/tracing_test/TESTS.md create mode 100644 ecal/tests/cpp/tracing_test/src/integrationtest/tracing_integration_test.cpp create mode 100644 ecal/tests/cpp/tracing_test/src/integrationtest/tracing_scale_test.cpp create mode 100644 ecal/tests/cpp/tracing_test/src/integrationtest/tracing_thread_safety_test.cpp create mode 100644 ecal/tests/cpp/tracing_test/src/systemtest/tracing_pubsub_stress_test.cpp create mode 100644 ecal/tests/cpp/tracing_test/src/tracing_test_helpers.h create mode 100644 ecal/tests/cpp/tracing_test/src/unittest/tracing_edge_case_test.cpp create mode 100644 ecal/tests/cpp/tracing_test/src/unittest/tracing_layer_type_test.cpp create mode 100644 ecal/tests/cpp/tracing_test/src/unittest/tracing_provider_test.cpp create mode 100644 ecal/tests/cpp/tracing_test/src/unittest/tracing_span_test.cpp create mode 100644 ecal/tests/cpp/tracing_test/src/unittest/tracing_types_test.cpp create mode 100644 ecal/tests/cpp/tracing_test/src/unittest/tracing_writer_test.cpp create mode 100644 thirdparty/nlohmann_json/build-nlohmann_json.cmake diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml index 7c7c1683a9..8256b2d2d7 100644 --- a/.github/workflows/build-ubuntu.yml +++ b/.github/workflows/build-ubuntu.yml @@ -169,6 +169,12 @@ jobs: # Always save cache if configure succeeded (even if the build failed) if: ${{ always() && steps.cmake-configure.outcome == 'success' }} + - name: Create tracing output directory + run: | + ECAL_TRACING_DATA_DIR="${{ runner.temp }}/ecal_tracing" + mkdir -p "$ECAL_TRACING_DATA_DIR" + echo "ECAL_TRACING_DATA_DIR=$ECAL_TRACING_DATA_DIR" >> "$GITHUB_ENV" + - name: Run Tests run: ctest -V --test-dir _build diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 88ccc74afb..e0b9dfc3ec 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -200,6 +200,13 @@ jobs: run: cmake --build "${{ runner.workspace }}/_build/csharp" --config Release shell: cmd + - name: Create tracing output directory + run: | + $TRACING_DIR = "${{ runner.temp }}\ecal_tracing" + mkdir $TRACING_DIR + echo "ECAL_TRACING_DATA_DIR=$TRACING_DIR" >> $Env:GITHUB_ENV + shell: powershell + - name: Run C# Tests run: ctest -C Release -V --test-dir "${{ runner.workspace }}/_build/csharp" diff --git a/cmake/submodule_dependencies.cmake b/cmake/submodule_dependencies.cmake index 622a81b21c..c411c57239 100644 --- a/cmake/submodule_dependencies.cmake +++ b/cmake/submodule_dependencies.cmake @@ -15,6 +15,7 @@ set(ecal_submodule_dependencies HDF5 #libssh2 nanobind + nlohmann_json Protobuf protozero qwt diff --git a/conanfile.py b/conanfile.py index e0584304f6..a04a9f19bd 100644 --- a/conanfile.py +++ b/conanfile.py @@ -17,6 +17,7 @@ def build_requirements(self): def requirements(self): self.requires("hdf5/1.10.6") self.requires("protobuf/3.17.1") + self.requires("nlohmann_json/3.11.2") self.requires("libcurl/7.78.0") self.requires("qt/5.15.2") self.requires("spdlog/1.9.2") @@ -51,6 +52,6 @@ def generate(self): else: tc.variables["Protobuf_PROTOC_EXECUTABLE"] = os.path.join(self.deps_cpp_info["protobuf"].rootpath, "bin", "protoc") tc.generate() - - - + + + diff --git a/ecal/core/CMakeLists.txt b/ecal/core/CMakeLists.txt index d4d0864391..c695a06559 100644 --- a/ecal/core/CMakeLists.txt +++ b/ecal/core/CMakeLists.txt @@ -43,6 +43,7 @@ find_package(asio REQUIRED) find_package(Threads REQUIRED) find_package(ecaludp REQUIRED) find_package(protozero REQUIRED) +find_package(nlohmann_json REQUIRED) if (ECAL_CORE_CONFIGURATION) find_package(yaml-cpp REQUIRED) @@ -459,6 +460,20 @@ if(ECAL_CORE_TIMEPLUGIN) ) endif() +###################################### +# tracing +###################################### +set(ecal_tracing_src + src/tracing/tracing.h + src/tracing/span.cpp + src/tracing/span.h + src/tracing/trace_provider.cpp + src/tracing/trace_provider.h + src/tracing/tracing_writer.h + src/tracing/tracing_writer_jsonl.cpp + src/tracing/tracing_writer_jsonl.h +) + ###################################### # util ###################################### @@ -637,6 +652,7 @@ set(ecal_sources ${ecal_serialization_src} ${ecal_service_src} ${ecal_time_src} + ${ecal_tracing_src} ${ecal_util_src} ${ecal_cmn_src} ${ecal_builder_src} @@ -717,6 +733,8 @@ target_link_libraries(ecal_core_private $<$:tcp_pubsub::tcp_pubsub> $<$:ecaltime> ecaludp::ecaludp + nlohmann_json::nlohmann_json + $<$,$>>:dl> ${CMAKE_DL_LIBS} $<$,$>,$>>:rt> $<$,$>,$>>:atomic> diff --git a/ecal/core/src/ecal.cpp b/ecal/core/src/ecal.cpp index 21834768a6..12066e9cfe 100644 --- a/ecal/core/src/ecal.cpp +++ b/ecal/core/src/ecal.cpp @@ -118,6 +118,7 @@ namespace eCAL SetGlobalUnitName(unit_name_.c_str()); if ((components_ & Init::Logging) != 0u) InitializeLogging(config_); + InitializeTracing(); auto globals_instance = CreateGlobalsInstance(); if (!globals_instance) return false; @@ -169,6 +170,7 @@ namespace eCAL ResetGlobalEcalConfiguration(); ResetLogging(); + ResetTracing(); return finalized; } diff --git a/ecal/core/src/ecal_global_accessors.cpp b/ecal/core/src/ecal_global_accessors.cpp index 2970bf58b5..ebedb86fe6 100644 --- a/ecal/core/src/ecal_global_accessors.cpp +++ b/ecal/core/src/ecal_global_accessors.cpp @@ -32,6 +32,7 @@ #include "config/builder/logging_attribute_builder.h" #include "logging/ecal_log_provider.h" #include "logging/ecal_log_receiver.h" +#include "tracing/trace_provider.h" #include #include @@ -57,6 +58,8 @@ namespace eCAL std::shared_ptr g_log_provider_instance; std::shared_ptr g_log_receiver_instance; + std::shared_ptr g_trace_provider_instance; + void SetGlobalUnitName(const char *unit_name_) { if(unit_name_ != nullptr) g_unit_name = unit_name_; @@ -118,6 +121,22 @@ namespace eCAL return nullptr; } + void InitializeTracing() + { + g_trace_provider_instance = tracing::CTraceProvider::Create(); + } + + void ResetTracing() + { + g_trace_provider_instance.reset(); + } + + std::shared_ptr g_trace_provider() + { + if (auto provider = g_trace_provider_instance; provider) return provider; + return nullptr; + } + std::shared_ptr g_globals() { if (auto globals_instance = g_globals_instance; globals_instance) diff --git a/ecal/core/src/ecal_global_accessors.h b/ecal/core/src/ecal_global_accessors.h index a4562f6383..fd7a6d89e0 100644 --- a/ecal/core/src/ecal_global_accessors.h +++ b/ecal/core/src/ecal_global_accessors.h @@ -43,6 +43,11 @@ namespace eCAL class CLogReceiver; } + namespace tracing + { + class CTraceProvider; + } + #if ECAL_CORE_MONITORING class CMonitoring; #endif @@ -80,6 +85,9 @@ namespace eCAL void InitializeLogging(const eCAL::Configuration& config_); void ResetLogging(); + void InitializeTracing(); + void ResetTracing(); + // Declaration of getter functions for globally accessible variable instances std::shared_ptr g_globals(); #if ECAL_CORE_MONITORING @@ -110,6 +118,8 @@ namespace eCAL std::shared_ptr g_logging_provider(); std::shared_ptr g_logging_receiver(); + std::shared_ptr g_trace_provider(); + // declaration of globally accessible variables extern std::string g_default_ini_file; extern Configuration g_ecal_configuration; diff --git a/ecal/core/src/pubsub/ecal_publisher.cpp b/ecal/core/src/pubsub/ecal_publisher.cpp index 7f70ff41f8..00cf8bd629 100644 --- a/ecal/core/src/pubsub/ecal_publisher.cpp +++ b/ecal/core/src/pubsub/ecal_publisher.cpp @@ -98,9 +98,10 @@ namespace eCAL } bool CPublisher::Send(CPayloadWriter& payload_, long long time_) - { + { auto publisher_impl = m_publisher_impl.lock(); if (!publisher_impl) return false; + // in an optimization case the // publisher can send an empty package // or we do not have any subscription at all diff --git a/ecal/core/src/pubsub/ecal_publisher_impl.cpp b/ecal/core/src/pubsub/ecal_publisher_impl.cpp index 437217e9d8..2d4bc5b925 100644 --- a/ecal/core/src/pubsub/ecal_publisher_impl.cpp +++ b/ecal/core/src/pubsub/ecal_publisher_impl.cpp @@ -46,6 +46,10 @@ #include "registration/ecal_registration_provider.h" +#include "tracing/tracing.h" +#include "tracing/span.h" +#include "tracing/trace_provider.h" + #include #include #include @@ -122,6 +126,19 @@ namespace eCAL m_topic_id.topic_id.host_name = m_attributes.host_name; m_topic_id.topic_id.process_id = m_attributes.process_id; + // record topic metadata for tracing + { + eCAL::tracing::STopicMetadata meta; + meta.entity_id = m_publisher_id; + meta.process_id = m_attributes.process_id; + meta.host_name = m_attributes.host_name; + meta.topic_name = m_attributes.topic_name; + meta.encoding = m_topic_info.encoding; + meta.type_name = m_topic_info.name; + meta.direction = eCAL::tracing::topic_direction::publisher; + if (auto provider = g_trace_provider(); provider) provider->addTopicMetadata(meta); + } + // mark as created m_created = true; } @@ -179,6 +196,29 @@ namespace eCAL // prepare counter and internal states const size_t snd_hash = PrepareWrite(filter_id_, payload_buf_size); + // determine active transport layer for tracing + eCAL::tracing::eTracingLayerType active_layer = eCAL::tracing::tl_trace_none; + { +#if ECAL_CORE_TRANSPORT_SHM + if (m_writer_shm) { active_layer = static_cast(active_layer | eCAL::tracing::tl_trace_shm); } +#endif +#if ECAL_CORE_TRANSPORT_UDP + if (m_writer_udp) { active_layer = static_cast(active_layer | eCAL::tracing::tl_trace_udp); } +#endif +#if ECAL_CORE_TRANSPORT_TCP + if (m_writer_tcp) { active_layer = static_cast(active_layer | eCAL::tracing::tl_trace_tcp); } +#endif + } + + // create tracing span for the send operation + eCAL::tracing::CSpan send_span( + m_topic_id, + m_clock, + active_layer, + payload_buf_size, + eCAL::tracing::operation_type::send + ); + // did we write anything bool written(false); diff --git a/ecal/core/src/pubsub/ecal_publisher_impl.h b/ecal/core/src/pubsub/ecal_publisher_impl.h index 1f0a37216b..43820a72ec 100644 --- a/ecal/core/src/pubsub/ecal_publisher_impl.h +++ b/ecal/core/src/pubsub/ecal_publisher_impl.h @@ -118,6 +118,7 @@ namespace eCAL void GetRegistrationSample(Registration::Sample& sample); void GetUnregistrationSample(Registration::Sample& sample); + long long GetClock() const { return m_clock; } bool StartUdpLayer(); bool StartShmLayer(); diff --git a/ecal/core/src/pubsub/ecal_subscriber_impl.cpp b/ecal/core/src/pubsub/ecal_subscriber_impl.cpp index dea6e0b20a..ecd4cecd82 100644 --- a/ecal/core/src/pubsub/ecal_subscriber_impl.cpp +++ b/ecal/core/src/pubsub/ecal_subscriber_impl.cpp @@ -23,6 +23,9 @@ **/ #include "ecal_subscriber_impl.h" +#include "tracing/tracing.h" +#include "tracing/span.h" +#include "tracing/trace_provider.h" #include #include #include @@ -93,6 +96,19 @@ namespace eCAL m_topic_id.topic_id.host_name = m_attributes.host_name; m_topic_id.topic_id.process_id = m_attributes.process_id; + // record topic metadata for tracing + { + eCAL::tracing::STopicMetadata meta; + meta.entity_id = m_subscriber_id; + meta.process_id = m_attributes.process_id; + meta.host_name = m_attributes.host_name; + meta.topic_name = m_attributes.topic_name; + meta.encoding = m_topic_info.encoding; + meta.type_name = m_topic_info.name; + meta.direction = eCAL::tracing::topic_direction::subscriber; + if (auto provider = g_trace_provider(); provider) provider->addTopicMetadata(meta); + } + // start transport layers InitializeLayers(); StartTransportLayer(); @@ -373,6 +389,16 @@ namespace eCAL size_t CSubscriberImpl::ApplySample(const Payload::TopicInfo& topic_info_, const char* payload_, size_t size_, long long id_, long long clock_, long long time_, size_t /*hash_*/, eTLayerType layer_) { + + eCAL::tracing::CSpan receive_span( + m_subscriber_id, + topic_info_, + clock_, + eCAL::tracing::toTracingLayerType(layer_), + size_, + eCAL::tracing::operation_type::receive + ); + // ensure thread safety const std::lock_guard lock(m_receive_callback_mutex); if (!m_created) return(0); @@ -450,7 +476,18 @@ namespace eCAL // execute it const std::lock_guard exec_lock(m_connection_map_mtx); - (m_receive_callback)(topic_id, m_connection_map[pub_info].data_type_info, cb_data); + { + eCAL::tracing::CSpan receive_span( + m_subscriber_id, + topic_info_, + clock_, + eCAL::tracing::toTracingLayerType(layer_), + size_, + eCAL::tracing::operation_type::callback_execution + ); + + + (m_receive_callback)(topic_id, m_connection_map[pub_info].data_type_info, cb_data); } processed = true; } } diff --git a/ecal/core/src/tracing/span.cpp b/ecal/core/src/tracing/span.cpp new file mode 100644 index 0000000000..bba54e8e24 --- /dev/null +++ b/ecal/core/src/tracing/span.cpp @@ -0,0 +1,68 @@ +/* ========================= eCAL LICENSE ================================= + * + * Copyright (C) 2016 - 2025 Continental Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ========================= eCAL LICENSE ================================= +*/ + +#include "span.h" +#include "trace_provider.h" +#include "../ecal_global_accessors.h" + +#include + +using namespace std::chrono; + +namespace eCAL +{ +namespace tracing +{ + + // Send span constructor + CSpan::CSpan(const STopicId& topic_id, long long clock, eTracingLayerType layer, size_t payload_size, operation_type op_type) + { + auto now = system_clock::now(); + data.start_ns = duration_cast(now.time_since_epoch()).count(); + data.entity_id = topic_id.topic_id.entity_id; + data.process_id = topic_id.topic_id.process_id; + data.payload_size = payload_size; + data.clock = clock; + data.layer = layer; + data.op_type = op_type; + } + + // Receive span constructor + CSpan::CSpan(EntityIdT entity_id, const eCAL::Payload::TopicInfo& topic_info, long long clock, eTracingLayerType layer, size_t payload_size, operation_type op_type) + { + auto now = system_clock::now(); + data.start_ns = duration_cast(now.time_since_epoch()).count(); + data.entity_id = entity_id; + data.topic_id = topic_info.topic_id; + data.process_id = topic_info.process_id; + data.payload_size = payload_size; + data.clock = clock; + data.layer = layer; + data.op_type = op_type; + } + + CSpan::~CSpan() + { + auto now = system_clock::now(); + data.end_ns = duration_cast(now.time_since_epoch()).count(); + if (auto provider = g_trace_provider(); provider) provider->bufferSpan(data); + } + +} // namespace tracing +} // namespace eCAL diff --git a/ecal/core/src/tracing/span.h b/ecal/core/src/tracing/span.h new file mode 100644 index 0000000000..e20a1c1935 --- /dev/null +++ b/ecal/core/src/tracing/span.h @@ -0,0 +1,54 @@ +/* ========================= eCAL LICENSE ================================= + * + * Copyright (C) 2016 - 2025 Continental Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ========================= eCAL LICENSE ================================= +*/ + +#pragma once + +#include "tracing.h" + +#include +#include +#include + +namespace eCAL +{ +namespace tracing +{ + + // RAII span — records start_ns on construction, end_ns + buffer on destruction. + // Overloaded constructors cover send and receive use cases. + class CSpan { + public: + // Send span (publisher) + CSpan(const STopicId& topic_id, long long clock, eTracingLayerType layer, size_t payload_size, operation_type op_type); + // Receive span (subscriber) + CSpan(EntityIdT entity_id, const eCAL::Payload::TopicInfo& topic_info, long long clock, eTracingLayerType layer, size_t payload_size, operation_type op_type); + + ~CSpan(); + + CSpan(const CSpan&) = delete; + CSpan& operator=(const CSpan&) = delete; + CSpan(CSpan&&) = delete; + CSpan& operator=(CSpan&&) = delete; + + private: + SSpanData data; + }; + +} // namespace tracing +} // namespace eCAL diff --git a/ecal/core/src/tracing/trace_provider.cpp b/ecal/core/src/tracing/trace_provider.cpp new file mode 100644 index 0000000000..89639aee74 --- /dev/null +++ b/ecal/core/src/tracing/trace_provider.cpp @@ -0,0 +1,104 @@ +/* ========================= eCAL LICENSE ================================= + * + * Copyright (C) 2016 - 2025 Continental Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ========================= eCAL LICENSE ================================= +*/ + +#include "trace_provider.h" +#include "tracing_writer.h" +#include "tracing_writer_jsonl.h" +#include "util/single_instance_helper.h" + +#include +#include +#include + +namespace eCAL +{ +namespace tracing +{ + + std::shared_ptr CTraceProvider::Create(std::unique_ptr writer, size_t batch_size) + { + try + { + return Util::CSingleInstanceHelper::Create(std::move(writer), batch_size); + } + catch (const std::exception& e) + { + return nullptr; + } + } + + CTraceProvider::CTraceProvider(std::unique_ptr writer, size_t batch_size) + : batch_size_(batch_size), writer_(std::move(writer)) + { + writer_thread_ = std::thread(&CTraceProvider::writerThreadLoop, this); + } + + CTraceProvider::~CTraceProvider() + { + { + std::lock_guard lock(thread_mutex); + stop_thread_ = true; + write_cv_.notify_all(); + } + writer_thread_.join(); + } + + + void CTraceProvider::bufferSpan(const SSpanData& span_data) + { + std::lock_guard lock(thread_mutex); + span_buffer_.push_back(span_data); + if (span_buffer_.size() >= batch_size_) + { + write_cv_.notify_one(); + } + } + + void CTraceProvider::writerThreadLoop() + { + std::vector span_flusher; + while (true) + { + { + std::unique_lock lock(thread_mutex); + write_cv_.wait(lock, [this]() + { + return stop_thread_|| (span_buffer_.size() >= batch_size_); + }); + if (stop_thread_ && span_buffer_.empty()) + { + break; + } + span_flusher.swap(span_buffer_); + } + if (!span_flusher.empty()) + { + writer_->writeBatchSpans(span_flusher); + span_flusher.clear(); + } + } + } + + void CTraceProvider::addTopicMetadata(const STopicMetadata& metadata) + { + writer_->writeTopicMetadata(metadata); + } + +} // namespace tracing +} // namespace eCAL diff --git a/ecal/core/src/tracing/trace_provider.h b/ecal/core/src/tracing/trace_provider.h new file mode 100644 index 0000000000..71df408bf1 --- /dev/null +++ b/ecal/core/src/tracing/trace_provider.h @@ -0,0 +1,93 @@ +/* ========================= eCAL LICENSE ================================= + * + * Copyright (C) 2016 - 2025 Continental Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ========================= eCAL LICENSE ================================= +*/ + +#pragma once + +#include "tracing.h" +#include "tracing_writer.h" +#include "tracing_writer_jsonl.h" +#include "util/single_instance_helper.h" + +#include +#include +#include +#include +#include +#include + +namespace eCAL +{ +namespace tracing +{ + + class CTraceProvider + { + friend class Util::CSingleInstanceHelper; + + public: + + static std::shared_ptr Create(std::unique_ptr writer = std::make_unique(), size_t batch_size = kDefaultTracingBatchSize); + + CTraceProvider(const CTraceProvider&) = delete; + CTraceProvider& operator=(const CTraceProvider&) = delete; + CTraceProvider(CTraceProvider&&) = delete; + CTraceProvider& operator=(CTraceProvider&&) = delete; + + ~CTraceProvider(); + + // Add span data to buffer + void bufferSpan(const SSpanData& span_data); + + // Topic metadata — written directly to file (no buffering) + void addTopicMetadata(const STopicMetadata& metadata); + + // Get buffered spans + std::vector getSpans() + { + std::lock_guard lock(thread_mutex); + return span_buffer_; + } + + // Synchronously flush all buffered spans to the writer + void forceFlush() + { + std::vector to_write; + { + std::lock_guard lock(thread_mutex); + to_write.swap(span_buffer_); + } + if (!to_write.empty()) + writer_->writeBatchSpans(to_write); + } + + private: + CTraceProvider(std::unique_ptr writer, size_t batch_size); + void writerThreadLoop(); + + std::atomic batch_size_{kDefaultTracingBatchSize}; + std::vector span_buffer_; + mutable std::mutex thread_mutex; + std::condition_variable write_cv_; + bool stop_thread_{false}; + std::thread writer_thread_; + std::unique_ptr writer_; + }; + +} // namespace tracing +} // namespace eCAL diff --git a/ecal/core/src/tracing/tracing.h b/ecal/core/src/tracing/tracing.h new file mode 100644 index 0000000000..745ed291b5 --- /dev/null +++ b/ecal/core/src/tracing/tracing.h @@ -0,0 +1,114 @@ +/* ========================= eCAL LICENSE ================================= + * + * Copyright (C) 2016 - 2025 Continental Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ========================= eCAL LICENSE ================================= +*/ + +/** + * @file tracing/tracing.h + * @brief Shared tracing types, enums, and constants used across the tracing subsystem. +**/ + +#pragma once + +#include +#include +#include +#include + +namespace eCAL +{ +namespace tracing +{ + + // Version of the tracing implementation. + constexpr const char* kTracingVersion = "1.0.0"; + + // Default batch size for span buffering before flushing to backend (jsonl file) + constexpr size_t kDefaultTracingBatchSize = 500; + + // Specifies the type of operation being traced + enum operation_type + { + send = 0, + receive = 1, + callback_execution = 2 + }; + + // Specifies the direction of the topic (publisher or subscriber) + enum topic_direction + { + publisher = 0, + subscriber = 1 + }; + + // Bitmask enum for active transport layers used in tracing spans. + // These use power-of-two values so combinations can be expressed with bitwise OR. + enum eTracingLayerType : uint64_t + { + tl_trace_none = 0, + tl_trace_shm = 1 << 0, // 1 + tl_trace_udp = 1 << 1, // 2 + tl_trace_tcp = 1 << 2, // 4 + tl_trace_shm_udp = tl_trace_shm | tl_trace_udp, // 3 + tl_trace_shm_tcp = tl_trace_shm | tl_trace_tcp, // 5 + tl_trace_udp_tcp = tl_trace_udp | tl_trace_tcp, // 6 + tl_trace_all = tl_trace_shm | tl_trace_udp | tl_trace_tcp, // 7 + }; + + // Convert from eTLayerType (used in core APIs) to eTracingLayerType. + inline eTracingLayerType toTracingLayerType(eTLayerType layer) + { + switch (layer) + { + case tl_ecal_shm: return tl_trace_shm; + case tl_ecal_udp: return tl_trace_udp; + case tl_ecal_tcp: return tl_trace_tcp; + case tl_all: return tl_trace_all; + default: return tl_trace_none; + } + } + + // Metadata captured when a topic is created + struct STopicMetadata + { + std::string tracing_version{kTracingVersion}; // tracing format version + uint64_t entity_id; // unique entity id + int32_t process_id; // PID of the owning process + std::string host_name; // host that created the topic + std::string topic_name; // topic name used for pub/sub matching + std::string encoding; // datatype encoding (e.g. protobuf) + std::string type_name; // datatype name + topic_direction direction; // publisher or subscriber + }; + + // Unified span data structure. + // All span types share the same struct; + struct SSpanData + { + uint64_t entity_id; + uint64_t topic_id{0}; // receive-only (0 for send spans) + uint64_t process_id; + size_t payload_size; + long long clock; + uint64_t layer; + long long start_ns; // start timestamp in nanoseconds + long long end_ns; // end timestamp in nanoseconds + operation_type op_type; + }; + +} // namespace tracing +} // namespace eCAL diff --git a/ecal/core/src/tracing/tracing_writer.h b/ecal/core/src/tracing/tracing_writer.h new file mode 100644 index 0000000000..09c518790a --- /dev/null +++ b/ecal/core/src/tracing/tracing_writer.h @@ -0,0 +1,46 @@ +/* ========================= eCAL LICENSE ================================= + * + * Copyright (C) 2016 - 2025 Continental Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ========================= eCAL LICENSE ================================= +*/ + +#pragma once + +#include +#include + +#include "tracing.h" + +namespace eCAL +{ +namespace tracing +{ + + // Interface for tracing writers. + class TracingWriter + { + public: + virtual ~TracingWriter() = default; + + // Write a batch of spans + virtual void writeBatchSpans(const std::vector& batch) = 0; + + // Write a single topic metadata entry + virtual void writeTopicMetadata(const STopicMetadata& metadata) = 0; + }; + +} // namespace tracing +} // namespace eCAL diff --git a/ecal/core/src/tracing/tracing_writer_jsonl.cpp b/ecal/core/src/tracing/tracing_writer_jsonl.cpp new file mode 100644 index 0000000000..97a4e855e9 --- /dev/null +++ b/ecal/core/src/tracing/tracing_writer_jsonl.cpp @@ -0,0 +1,147 @@ +/* ========================= eCAL LICENSE ================================= + * + * Copyright (C) 2016 - 2025 Continental Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ========================= eCAL LICENSE ================================= +*/ + +#include "tracing_writer_jsonl.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include +using json = nlohmann::json; + +namespace eCAL +{ +namespace tracing +{ + + static std::string getCurrentTimestamp() + { + std::time_t now = std::time(nullptr); + std::tm* tm_info = std::localtime(&now); + std::ostringstream oss; + oss << std::put_time(tm_info, "%Y%m%d_%H%M%S"); + return oss.str(); + } + + CTracingWriterJSONL::CTracingWriterJSONL() + : timestamp_(getCurrentTimestamp()) + {} + + std::string CTracingWriterJSONL::getSpansFilePath() const + { + const char* data_dir = std::getenv("ECAL_TRACING_DATA_DIR"); + if (data_dir == nullptr) + { + std::cerr << "Fatal: Mandatory environment variable ECAL_TRACING_DATA_DIR is not set." << std::endl; + std::abort(); + } + return std::string(data_dir) + "/ecal_spans_" + std::to_string(eCAL::Process::GetProcessID()) + "_" + timestamp_ + ".jsonl"; + } + + std::string CTracingWriterJSONL::getTopicMetadataFilePath() const + { + const char* data_dir = std::getenv("ECAL_TRACING_DATA_DIR"); + if (data_dir == nullptr) + { + std::cerr << "Fatal: Mandatory environment variable ECAL_TRACING_DATA_DIR is not set." << std::endl; + std::abort(); + } + return std::string(data_dir) + "/ecal_topic_metadata_" + std::to_string(eCAL::Process::GetProcessID()) + "_" + timestamp_ + ".jsonl"; + } + + void CTracingWriterJSONL::writeBatchSpans(const std::vector& batch) + { + try + { + std::lock_guard lock(spans_mutex_); + std::string filepath = getSpansFilePath(); + + std::ofstream output_file(filepath, std::ios::app); + if (output_file.is_open()) + { + for (const auto& span : batch) + { + json span_obj; + span_obj["entity_id"] = span.entity_id; + span_obj["topic_id"] = span.topic_id; + span_obj["process_id"] = span.process_id; + span_obj["payload_size"] = span.payload_size; + span_obj["clock"] = span.clock; + span_obj["layer"] = span.layer; + span_obj["start_ns"] = span.start_ns; + span_obj["end_ns"] = span.end_ns; + span_obj["op_type"] = span.op_type; + + output_file << span_obj.dump() << "\n"; + } + output_file.close(); + } + else + { + std::cerr << "Warning: Could not open spans file: " << filepath << std::endl; + } + } + catch (const std::exception& e) + { + std::cerr << "Error writing spans to JSONL: " << e.what() << std::endl; + } + } + + void CTracingWriterJSONL::writeTopicMetadata(const STopicMetadata& metadata) + { + try + { + std::lock_guard lock(metadata_mutex_); + std::string filepath = getTopicMetadataFilePath(); + + json obj; + obj["tracing_version"] = metadata.tracing_version; + obj["entity_id"] = metadata.entity_id; + obj["process_id"] = metadata.process_id; + obj["host_name"] = metadata.host_name; + obj["topic_name"] = metadata.topic_name; + obj["encoding"] = metadata.encoding; + obj["type_name"] = metadata.type_name; + obj["direction"] = (metadata.direction == topic_direction::publisher) ? "publisher" : "subscriber"; + + std::ofstream output_file(filepath, std::ios::app); + if (output_file.is_open()) + { + output_file << obj.dump() << "\n"; + output_file.close(); + } + else + { + std::cerr << "Warning: Could not open topic metadata file: " << filepath << std::endl; + } + } + catch (const std::exception& e) + { + std::cerr << "Error writing topic metadata to JSONL: " << e.what() << std::endl; + } + } + +} // namespace tracing +} // namespace eCAL diff --git a/ecal/core/src/tracing/tracing_writer_jsonl.h b/ecal/core/src/tracing/tracing_writer_jsonl.h new file mode 100644 index 0000000000..37203257bb --- /dev/null +++ b/ecal/core/src/tracing/tracing_writer_jsonl.h @@ -0,0 +1,64 @@ +/* ========================= eCAL LICENSE ================================= + * + * Copyright (C) 2016 - 2025 Continental Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ========================= eCAL LICENSE ================================= +*/ + +#pragma once + +#include +#include +#include + +#include "tracing_writer.h" +#include "tracing.h" + +namespace eCAL +{ +namespace tracing +{ + + // Responsible for serializing span and metadata to JSONL files. + // Separated from CTraceProvider to isolate the I/O concern. + class CTracingWriterJSONL : public TracingWriter + { + public: + CTracingWriterJSONL(); + ~CTracingWriterJSONL() override = default; + + CTracingWriterJSONL(const CTracingWriterJSONL&) = delete; + CTracingWriterJSONL& operator=(const CTracingWriterJSONL&) = delete; + CTracingWriterJSONL(CTracingWriterJSONL&&) = delete; + CTracingWriterJSONL& operator=(CTracingWriterJSONL&&) = delete; + + // Write a batch of spans to the JSONL spans file + void writeBatchSpans(const std::vector& batch) override; + + // Write a single topic metadata entry to the JSONL metadata file + void writeTopicMetadata(const STopicMetadata& metadata) override; + + // File path accessors (path is fixed at construction time) + std::string getSpansFilePath() const; + std::string getTopicMetadataFilePath() const; + + private: + mutable std::mutex spans_mutex_; + mutable std::mutex metadata_mutex_; + std::string timestamp_; + }; + +} // namespace tracing +} // namespace eCAL diff --git a/ecal/tests/CMakeLists.txt b/ecal/tests/CMakeLists.txt index 298e02e01e..438d79fbdb 100644 --- a/ecal/tests/CMakeLists.txt +++ b/ecal/tests/CMakeLists.txt @@ -27,7 +27,7 @@ add_subdirectory(cpp/process_test) add_subdirectory(cpp/descgate_test) add_subdirectory(cpp/config_test) - +add_subdirectory(cpp/tracing_test) if(ECAL_CORE_REGISTRATION) add_subdirectory(cpp/registration_test) diff --git a/ecal/tests/cpp/tracing_test/CMakeLists.txt b/ecal/tests/cpp/tracing_test/CMakeLists.txt new file mode 100644 index 0000000000..da348991be --- /dev/null +++ b/ecal/tests/cpp/tracing_test/CMakeLists.txt @@ -0,0 +1,68 @@ +# ========================= eCAL LICENSE ================================= +# +# Copyright (C) 2016 - 2025 Continental Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ========================= eCAL LICENSE ================================= + +project(test_tracing) + +find_package(Threads REQUIRED) +find_package(GTest REQUIRED) +find_package(nlohmann_json REQUIRED) + +set(tracing_test_src + src/tracing_test_helpers.h + # Unit tests — individual classes in isolation + src/unittest/tracing_types_test.cpp + src/unittest/tracing_layer_type_test.cpp + src/unittest/tracing_provider_test.cpp + src/unittest/tracing_span_test.cpp + src/unittest/tracing_writer_test.cpp + src/unittest/tracing_edge_case_test.cpp + # Integration tests — component interactions and concurrency + src/integrationtest/tracing_integration_test.cpp + src/integrationtest/tracing_thread_safety_test.cpp + src/integrationtest/tracing_scale_test.cpp + # System tests — full eCAL runtime, real pub/sub + src/systemtest/tracing_pubsub_stress_test.cpp +) + +ecal_add_gtest(${PROJECT_NAME} ${tracing_test_src}) + +target_include_directories(${PROJECT_NAME} PRIVATE + $ + ${CMAKE_CURRENT_LIST_DIR}/src + ${ECAL_CORE_PROJECT_ROOT}/core/src + ${ECAL_CORE_PROJECT_ROOT}/core/src/serialization +) + +target_link_libraries(${PROJECT_NAME} + PRIVATE + ecal_core_private + Threads::Threads + nlohmann_json::nlohmann_json +) + +target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_17) + +target_compile_definitions(${PROJECT_NAME} PRIVATE ECAL_CORE_COMMAND_LINE) + +ecal_install_gtest(${PROJECT_NAME}) + +set_property(TARGET ${PROJECT_NAME} PROPERTY FOLDER tests/cpp/tracing) + +source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" FILES + ${tracing_test_src} +) diff --git a/ecal/tests/cpp/tracing_test/TESTS.md b/ecal/tests/cpp/tracing_test/TESTS.md new file mode 100644 index 0000000000..b39e6d3911 --- /dev/null +++ b/ecal/tests/cpp/tracing_test/TESTS.md @@ -0,0 +1,135 @@ +# Tracing Test Summary + +**File:** `ecal/tests/cpp/tracing_test/src/tracing_test.cpp` +**Total:** 69 tests across 10 test suites + +--- + +## TracingTypesTest (8 tests) + +| Test | What it validates | +|---|---| +| `TracingVersionConstant` | `kTracingVersion` equals `"1.0.0"` | +| `DefaultTracingBatchSize` | `kDefaultTracingBatchSize` equals `10` | +| `SSpanDataDefaultInitialization` | All `SSpanData` fields are zero after value-initialization | +| `SSpanDataAssignment` | All `SSpanData` fields can be assigned and read back correctly | +| `STopicMetadataDefaultVersion` | `STopicMetadata::tracing_version` defaults to `kTracingVersion` | +| `STopicMetadataAssignment` | All `STopicMetadata` fields can be assigned and read back correctly | +| `OperationTypeEnum` | `send=0`, `receive=1`, `callback_execution=2` | +| `TopicDirectionEnum` | `publisher=0`, `subscriber=1` | + +## TracingLayerTypeTest (8 tests) + +| Test | What it validates | +|---|---| +| `LayerTypeValues` | Enum values: `none=0`, `shm=1`, `udp=2`, `tcp=4`, combined values `3,5,6,7` | +| `LayerTypeBitwise` | Layer types combine correctly with bitwise OR | +| `ToTracingLayerTypeSHM` | `toTracingLayerType(tl_ecal_shm)` → `tl_trace_shm` | +| `ToTracingLayerTypeUDP` | `toTracingLayerType(tl_ecal_udp)` → `tl_trace_udp` | +| `ToTracingLayerTypeTCP` | `toTracingLayerType(tl_ecal_tcp)` → `tl_trace_tcp` | +| `ToTracingLayerTypeAll` | `toTracingLayerType(tl_all)` → `tl_trace_all` | +| `ToTracingLayerTypeUnknown` | Invalid `eTLayerType(999)` → `tl_trace_none` | +| `ToTracingLayerTypeNone` | `toTracingLayerType(tl_none)` → `tl_trace_none` | + +## TracingProviderTest (11 tests) + +| Test | What it validates | +|---|---| +| `SingletonInstance` | `getInstance()` returns the same address on repeated calls | +| `DefaultBatchSize` | Buffering 9 spans doesn't trigger auto-flush (batch size = 10) | +| `SetBatchSizeAffectsAutoFlush` | Changing batch size to 3 triggers flush on the 3rd span | +| `BufferSpan` | A single span is buffered with correct field values | +| `BufferMultipleSpans` | 5 spans are buffered in order with correct entity IDs | +| `FlushSpans` | `flushSpans()` empties the buffer | +| `FlushEmptyBuffer` | Flushing an empty buffer doesn't throw | +| `AutoFlushOnBatchSize` | Buffer empties automatically when batch size is reached | +| `AutoFlushMultipleBatches` | Multiple auto-flush cycles work correctly, residual spans stay buffered | +| `AddTopicMetadata` | Metadata is written to JSONL file with all fields (publisher direction) | +| `AddTopicMetadataSubscriber` | Metadata with subscriber direction is written correctly | + +## SpanTest (7 tests) + +| Test | What it validates | +|---|---| +| `SendSpanConstruction` | RAII `CSpan` (send) buffers correct entity_id, process_id, payload_size, clock, layer, op_type; topic_id=0 for send | +| `SendSpanTimestamps` | `start_ns < end_ns`, both > 0, duration ≥ 0.5ms (with 1ms sleep) | +| `ReceiveSpanConstruction` | RAII `CSpan` (receive) buffers correct entity_id, topic_id, process_id, payload_size, clock, layer, op_type | +| `ReceiveSpanTimestamps` | Receive span has valid start/end timestamps with `start < end` | +| `CallbackExecutionSpan` | `CSpan` with `callback_execution` op_type records correctly | +| `MultipleOperationTypes` | Two spans with different op_types are buffered in order | +| `MultipleLayerTypes` | Four spans with SHM, UDP, TCP, SHM+UDP layers are buffered correctly | + +## TracingWriterTest (11 tests) + +| Test | What it validates | +|---|---| +| `WriterConstruction` | `CTracingWriter` constructs without throwing | +| `WriteBatchSpansVerifyContent` | 3-span batch written to JSONL; all 9 fields verified per line | +| `WriteBatchSpansReceive` | Receive span written with correct op_type, topic_id, layer | +| `WriteBatchSpansAppends` | Two sequential writes append (2 lines in file, not 1) | +| `WriteEmptyBatch` | Empty batch produces no output (file empty or absent) | +| `WriteTopicMetadataVerifyContent` | Metadata JSONL contains all 8 fields (publisher direction) | +| `WriteTopicMetadataSubscriber` | Subscriber direction is serialized as `"subscriber"` | +| `WriteMultipleMetadataAppends` | 3 metadata writes append correctly with alternating directions | +| `EmptyStringMetadataFields` | Empty strings and zero IDs are written as `""` / `0` | +| `WriteBatchSpansCallbackExecution` | `callback_execution` op_type and TCP layer written correctly | +| `WriteBatchSpansAllLayerTypes` | All 8 layer type values (none through all) written and read back | + +## TracingIntegrationTest (4 tests) + +| Test | What it validates | +|---|---| +| `SpanBufferingAndFlushing` | CSpan → buffer → getSpans → flushSpans → empty buffer | +| `MixedSendAndReceiveSpans` | Send and receive spans coexist in buffer with correct fields | +| `CompleteTracingFlow` | Full flow: addTopicMetadata → CSpan creation → buffer verify → flush → JSONL file verify | +| `AutoFlushWritesToFile` | Auto-flush (batch size 3) writes spans to JSONL file and empties buffer | + +## ThreadSafetyTest (5 tests) + +| Test | What it validates | +|---|---| +| `ConcurrentSpanBuffering` | 4 threads × 25 CSpan RAII objects → exactly 100 spans buffered (no data loss) | +| `ConcurrentMetadataWriting` | 4 threads × 5 metadata writes → 20 valid JSONL lines (no corruption) | +| `ConcurrentBufferingWithAutoFlush` | 4 threads × 20 spans with batch size 5 → all 80 spans written to file | +| `ConcurrentGetSpans` | 3 reader threads + 1 writer thread → no crashes, correct final count (100 spans) | +| `ConcurrentWriteBatchSpansIntegrity` | **Regression:** 8 threads × 20 batches × 5 spans → 800 valid JSONL lines (tests `spans_mutex_` fix) | + +## EdgeCaseTest (7 tests) + +| Test | What it validates | +|---|---| +| `AllLayerTypesCombined` | CSpan with `tl_trace_all` (7) stores correct layer value | +| `NoneLayerType` | CSpan with `tl_trace_none` (0) and payload 0 stores correctly | +| `EmptyStringMetadata` | Empty-string metadata fields are written without error | +| `BatchSizeOfOne` | Batch size = 1 triggers immediate auto-flush on every span | +| `RapidFlushCycles` | 10 rapid buffer-then-flush cycles work correctly | +| `MaxEntityAndProcessIds` | `UINT64_MAX` for entity_id, process_id, topic_id written and read back correctly | +| `ReceiveSpanViaProviderFlush` | Full path: CSpan receive → buffer → verify fields → flush → verify JSONL output | + +## ScaleTest (5 tests) + +| Test | What it validates | +|---|---| +| `HighFanoutProducers` | 500 threads × 200 spans via `bufferSpan()` → 100,000 spans, zero data loss | +| `HighFanoutViaSpanRAII` | 200 threads × 100 CSpan RAII objects → 20,000 spans written to file | +| `MixedPubSubMetadataAndSpans` | 200 threads each registering metadata + 50 spans → 200 metadata + 10,000 spans | +| `BatchSizeSweep` | Same load tested at batch sizes 1, 5, 10, 50, 100, 500 — correctness is batch-size-independent | +| `SustainedBurst` | 100 threads × 500 spans with batch size 25 — stresses mutex + file I/O under sustained load | + +## PubSubStressTest (3 tests) + +| Test | What it validates | +|---|---| +| `TwoPubTwoSubHighFrequency` | 2 real publishers + 2 subscribers, 500 messages each (concurrent) → exactly 1,000 send spans; valid timestamps on all spans | +| `MultiTopicHighFrequency` | 3 publishers on 3 topics, 300 messages each (concurrent) → 900 send spans; all use SHM layer | +| `HighFrequencyWithSlowCallback` | 1 pub + 1 sub, 200 messages, 50µs callback delay → 200 send spans; callback_execution spans have duration ≥ 40µs | + +--- + +## Bug Fixes Validated by These Tests + +| Bug | Fix | Regression test | +|---|---|---| +| Data race in `bufferSpan()` — `span_buffer_.size()` read outside lock | Moved size check inside lock, use local `should_flush` flag | `ConcurrentBufferingWithAutoFlush`, `HighFanoutProducers` | +| Thread-unsafe `getSpans()` — returned buffer without holding mutex | Added `lock_guard` before returning `span_buffer_` | `ConcurrentGetSpans` | +| No mutex on `writeBatchSpans()` — concurrent flushes produced corrupted JSONL | Added `spans_mutex_` to `CTracingWriter` | `ConcurrentWriteBatchSpansIntegrity` | diff --git a/ecal/tests/cpp/tracing_test/src/integrationtest/tracing_integration_test.cpp b/ecal/tests/cpp/tracing_test/src/integrationtest/tracing_integration_test.cpp new file mode 100644 index 0000000000..e96168eadb --- /dev/null +++ b/ecal/tests/cpp/tracing_test/src/integrationtest/tracing_integration_test.cpp @@ -0,0 +1,193 @@ +/* ========================= eCAL LICENSE ================================= + * + * Copyright (C) 2016 - 2025 Continental Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ========================= eCAL LICENSE ================================= +*/ + +#include "tracing_test_helpers.h" + +using namespace eCAL::tracing; + +// ============ Integration Tests ============ + +class TracingIntegrationTest : public ::testing::Test +{ +protected: + void SetUp() override + { + setenv("ECAL_TRACING_DATA_DIR", tempDir().c_str(), 1); + if (auto provider = eCAL::g_trace_provider(); provider) + { + triggerFlush(provider.get()); + } + removeFile(spansFilePath()); + removeFile(metadataFilePath()); + } + + void TearDown() override + { + if (auto provider = eCAL::g_trace_provider(); provider) + { + triggerFlush(provider.get()); + } + removeFile(spansFilePath()); + removeFile(metadataFilePath()); + } +}; + +TEST_F(TracingIntegrationTest, SpanBufferingAndFlushing) +{ + auto provider = eCAL::g_trace_provider(); + if (!provider) return; + + eCAL::STopicId topic_id; + topic_id.topic_id.entity_id = 1; + topic_id.topic_id.process_id = 2; + + for (int i = 0; i < 3; ++i) + { + CSpan span(topic_id, i, tl_trace_shm, 256 * (i + 1), send); + } + + auto spans = provider->getSpans(); + ASSERT_EQ(spans.size(), 3); + + for (int i = 0; i < 3; ++i) + { + EXPECT_EQ(spans[i].clock, i); + EXPECT_EQ(spans[i].payload_size, 256 * (i + 1)); + } + + triggerFlush(provider.get()); + EXPECT_EQ(provider->getSpans().size(), 0); +} + +TEST_F(TracingIntegrationTest, MixedSendAndReceiveSpans) +{ + auto provider = eCAL::g_trace_provider(); + if (!provider) return; + + eCAL::STopicId send_topic_id; + send_topic_id.topic_id.entity_id = 1; + send_topic_id.topic_id.process_id = 2; + + eCAL::Payload::TopicInfo recv_topic_info; + recv_topic_info.topic_id = 10; + recv_topic_info.process_id = 20; + + { + CSpan send_span(send_topic_id, 100, tl_trace_shm, 256, send); + } + { + CSpan recv_span(30, recv_topic_info, 200, tl_trace_udp, 512, receive); + } + + auto spans = provider->getSpans(); + ASSERT_EQ(spans.size(), 2); + + EXPECT_EQ(spans[0].op_type, send); + EXPECT_EQ(spans[0].topic_id, 0); + EXPECT_EQ(spans[0].entity_id, 1); + EXPECT_EQ(spans[0].process_id, 2); + + EXPECT_EQ(spans[1].op_type, receive); + EXPECT_EQ(spans[1].topic_id, 10); + EXPECT_EQ(spans[1].entity_id, 30); + EXPECT_EQ(spans[1].process_id, 20); +} + +TEST_F(TracingIntegrationTest, CompleteTracingFlow) +{ + auto provider = eCAL::g_trace_provider(); + if (!provider) return; + + STopicMetadata metadata; + metadata.entity_id = 111; + metadata.process_id = eCAL::Process::GetProcessID(); + metadata.host_name = "integration-host"; + metadata.topic_name = "integration_test_topic"; + metadata.encoding = "protobuf"; + metadata.type_name = "IntegrationData"; + metadata.direction = publisher; + + provider->addTopicMetadata(metadata); + + eCAL::STopicId topic_id; + topic_id.topic_id.entity_id = 111; + topic_id.topic_id.process_id = eCAL::Process::GetProcessID(); + + for (int i = 0; i < 5; ++i) + { + CSpan span(topic_id, i * 10, tl_trace_shm, 256 * (i + 1), send); + } + + auto spans = provider->getSpans(); + ASSERT_EQ(spans.size(), 5); + + for (int i = 0; i < 5; ++i) + { + EXPECT_EQ(spans[i].entity_id, 111); + EXPECT_EQ(spans[i].process_id, static_cast(eCAL::Process::GetProcessID())); + EXPECT_EQ(spans[i].payload_size, 256 * (i + 1)); + EXPECT_EQ(spans[i].clock, i * 10); + } + + triggerFlush(provider.get()); + EXPECT_EQ(provider->getSpans().size(), 0); + + auto metadata_lines = readJsonLines(metadataFilePath()); + ASSERT_GE(metadata_lines.size(), 1); + EXPECT_EQ(metadata_lines.back()["topic_name"].get(), "integration_test_topic"); + EXPECT_EQ(metadata_lines.back()["direction"].get(), "publisher"); + + auto span_lines = readJsonLines(spansFilePath()); + ASSERT_GE(span_lines.size(), 5); + + size_t matching = 0; + for (const auto& line : span_lines) + { + if (line["entity_id"].get() == 111) + ++matching; + } + EXPECT_EQ(matching, 5u); +} + +TEST_F(TracingIntegrationTest, AutoFlushWritesToFile) +{ + auto provider = eCAL::g_trace_provider(); + if (!provider) return; + + eCAL::STopicId topic_id; + topic_id.topic_id.entity_id = 42; + topic_id.topic_id.process_id = 43; + + for (int i = 0; i < 3; ++i) + { + CSpan span(topic_id, i, tl_trace_shm, 100, send); + } + + provider->forceFlush(); + EXPECT_EQ(provider->getSpans().size(), 0); + + auto lines = readJsonLines(spansFilePath()); + ASSERT_GE(lines.size(), 3); + for (int i = 0; i < 3; ++i) + { + size_t idx = lines.size() - 3 + i; + EXPECT_EQ(lines[idx]["entity_id"].get(), 42); + EXPECT_EQ(lines[idx]["clock"].get(), i); + } +} diff --git a/ecal/tests/cpp/tracing_test/src/integrationtest/tracing_scale_test.cpp b/ecal/tests/cpp/tracing_test/src/integrationtest/tracing_scale_test.cpp new file mode 100644 index 0000000000..3a5a1293f1 --- /dev/null +++ b/ecal/tests/cpp/tracing_test/src/integrationtest/tracing_scale_test.cpp @@ -0,0 +1,235 @@ +/* ========================= eCAL LICENSE ================================= + * + * Copyright (C) 2016 - 2025 Continental Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ========================= eCAL LICENSE ================================= +*/ + +#include "tracing_test_helpers.h" + +using namespace eCAL::tracing; + +// ============ Scale / Stress Tests ============ + +class ScaleTest : public ::testing::Test +{ +protected: + void SetUp() override + { + setenv("ECAL_TRACING_DATA_DIR", tempDir().c_str(), 1); + if (auto provider = eCAL::g_trace_provider(); provider) + { + triggerFlush(provider.get()); + } + removeFile(spansFilePath()); + removeFile(metadataFilePath()); + } + + void TearDown() override + { + if (auto provider = eCAL::g_trace_provider(); provider) + { + triggerFlush(provider.get()); + } + removeFile(spansFilePath()); + removeFile(metadataFilePath()); + } +}; + +// Simulate hundreds of publishers/subscribers buffering spans concurrently. +// Validates that no spans are lost under high contention. +TEST_F(ScaleTest, HighFanoutProducers) +{ + auto provider = eCAL::g_trace_provider(); + if (!provider) return; + + const int num_producers = 500; + const int spans_per_producer = 200; + std::vector threads; + + for (int p = 0; p < num_producers; ++p) + { + threads.emplace_back([&provider, p, spans_per_producer]() + { + for (int i = 0; i < spans_per_producer; ++i) + { + SSpanData span{}; + span.entity_id = static_cast(p); + span.clock = i; + span.op_type = (p % 2 == 0) ? send : receive; + provider->bufferSpan(span); + } + }); + } + + for (auto& t : threads) t.join(); + + triggerFlush(provider.get()); + EXPECT_EQ(provider->getSpans().size(), 0); + + auto lines = readJsonLines(spansFilePath()); + EXPECT_GE(lines.size(), static_cast(num_producers) * spans_per_producer); +} + +// Same high fan-out but using CSpan RAII objects (the real API path). +TEST_F(ScaleTest, HighFanoutViaSpanRAII) +{ + auto provider = eCAL::g_trace_provider(); + if (!provider) return; + + const int num_publishers = 200; + const int messages_per_pub = 100; + std::vector threads; + + for (int p = 0; p < num_publishers; ++p) + { + threads.emplace_back([p, messages_per_pub]() + { + eCAL::STopicId topic_id; + topic_id.topic_id.entity_id = static_cast(p); + topic_id.topic_id.process_id = static_cast(eCAL::Process::GetProcessID()); + + for (int i = 0; i < messages_per_pub; ++i) + { + CSpan span(topic_id, i, tl_trace_shm, 256, send); + } + }); + } + + for (auto& t : threads) t.join(); + + triggerFlush(provider.get()); + + auto lines = readJsonLines(spansFilePath()); + EXPECT_GE(lines.size(), static_cast(num_publishers) * messages_per_pub); +} + +// Simulate mixed publisher and subscriber registration at scale. +TEST_F(ScaleTest, MixedPubSubMetadataAndSpans) +{ + auto provider = eCAL::g_trace_provider(); + if (!provider) return; + + const int num_endpoints = 200; + const int spans_per_endpoint = 50; + std::vector threads; + + for (int e = 0; e < num_endpoints; ++e) + { + threads.emplace_back([&provider, e, spans_per_endpoint]() + { + STopicMetadata metadata; + metadata.entity_id = static_cast(e); + metadata.process_id = eCAL::Process::GetProcessID(); + metadata.host_name = "scale-host"; + metadata.topic_name = "topic_" + std::to_string(e); + metadata.encoding = "protobuf"; + metadata.type_name = "ScaleMsg"; + metadata.direction = (e % 2 == 0) ? publisher : subscriber; + provider->addTopicMetadata(metadata); + + for (int i = 0; i < spans_per_endpoint; ++i) + { + SSpanData span{}; + span.entity_id = static_cast(e); + span.clock = i; + span.op_type = (e % 2 == 0) ? send : receive; + provider->bufferSpan(span); + } + }); + } + + for (auto& t : threads) t.join(); + + triggerFlush(provider.get()); + + auto meta_lines = readJsonLines(metadataFilePath()); + EXPECT_EQ(meta_lines.size(), static_cast(num_endpoints)); + + auto span_lines = readJsonLines(spansFilePath()); + EXPECT_GE(span_lines.size(), static_cast(num_endpoints) * spans_per_endpoint); +} + +// Vary batch sizes under constant load to verify correctness is independent of batch tuning. +TEST_F(ScaleTest, HighThroughputNoDataLoss) +{ + auto provider = eCAL::g_trace_provider(); + if (!provider) return; + + const int num_producers = 50; + const int spans_per_producer = 100; + const size_t expected_total = static_cast(num_producers) * spans_per_producer; + + removeFile(spansFilePath()); + + std::vector threads; + for (int p = 0; p < num_producers; ++p) + { + threads.emplace_back([&provider, p, spans_per_producer]() + { + for (int i = 0; i < spans_per_producer; ++i) + { + SSpanData span{}; + span.entity_id = static_cast(p); + span.clock = i; + span.op_type = send; + provider->bufferSpan(span); + } + }); + } + + for (auto& t : threads) t.join(); + + triggerFlush(provider.get()); + + auto lines = readJsonLines(spansFilePath()); + EXPECT_GE(lines.size(), expected_total); +} + +// Sustained burst: rapidly produce large volumes to stress the mutex + file I/O path. +TEST_F(ScaleTest, SustainedBurst) +{ + auto provider = eCAL::g_trace_provider(); + if (!provider) return; + + const int num_threads = 100; + const int spans_per_thread = 500; + std::vector threads; + + for (int t = 0; t < num_threads; ++t) + { + threads.emplace_back([&provider, t, spans_per_thread]() + { + for (int i = 0; i < spans_per_thread; ++i) + { + SSpanData span{}; + span.entity_id = static_cast(t); + span.topic_id = static_cast(t % 50); + span.process_id = static_cast(eCAL::Process::GetProcessID()); + span.clock = i; + span.layer = tl_trace_shm; + span.op_type = send; + provider->bufferSpan(span); + } + }); + } + + for (auto& t : threads) t.join(); + + triggerFlush(provider.get()); + + auto lines = readJsonLines(spansFilePath()); + EXPECT_GE(lines.size(), static_cast(num_threads) * spans_per_thread); +} diff --git a/ecal/tests/cpp/tracing_test/src/integrationtest/tracing_thread_safety_test.cpp b/ecal/tests/cpp/tracing_test/src/integrationtest/tracing_thread_safety_test.cpp new file mode 100644 index 0000000000..d80849b44e --- /dev/null +++ b/ecal/tests/cpp/tracing_test/src/integrationtest/tracing_thread_safety_test.cpp @@ -0,0 +1,231 @@ +/* ========================= eCAL LICENSE ================================= + * + * Copyright (C) 2016 - 2025 Continental Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ========================= eCAL LICENSE ================================= +*/ + +#include "tracing_test_helpers.h" + +using namespace eCAL::tracing; + +// ============ Thread Safety Tests ============ + +class ThreadSafetyTest : public ::testing::Test +{ +protected: + std::unique_ptr writer_; + + void SetUp() override + { + setenv("ECAL_TRACING_DATA_DIR", tempDir().c_str(), 1); + if (auto provider = eCAL::g_trace_provider(); provider) + { + triggerFlush(provider.get()); + } + writer_ = std::make_unique(); + removeFile(spansFilePath()); + removeFile(writer_->getSpansFilePath()); + removeFile(writer_->getTopicMetadataFilePath()); + } + + void TearDown() override + { + if (auto provider = eCAL::g_trace_provider(); provider) + { + triggerFlush(provider.get()); + } + removeFile(spansFilePath()); + removeFile(writer_->getSpansFilePath()); + removeFile(writer_->getTopicMetadataFilePath()); + } +}; + +TEST_F(ThreadSafetyTest, ConcurrentSpanBuffering) +{ + auto provider = eCAL::g_trace_provider(); + if (!provider) return; + + const int num_threads = 4; + const int spans_per_thread = 25; + std::vector threads; + + for (int t = 0; t < num_threads; ++t) + { + threads.emplace_back([&provider, t, spans_per_thread]() + { + eCAL::STopicId topic_id; + topic_id.topic_id.entity_id = t; + topic_id.topic_id.process_id = eCAL::Process::GetProcessID(); + + for (int i = 0; i < spans_per_thread; ++i) + { + CSpan span(topic_id, i, tl_trace_shm, 256, send); + } + }); + } + + for (auto& t : threads) t.join(); + + auto spans = provider->getSpans(); + EXPECT_EQ(spans.size(), num_threads * spans_per_thread); +} + +TEST_F(ThreadSafetyTest, ConcurrentMetadataWriting) +{ + const int num_threads = 4; + const int metadata_per_thread = 5; + std::vector threads; + + for (int t = 0; t < num_threads; ++t) + { + threads.emplace_back([this, t, metadata_per_thread]() + { + for (int i = 0; i < metadata_per_thread; ++i) + { + STopicMetadata metadata; + metadata.entity_id = t * 100 + i; + metadata.process_id = eCAL::Process::GetProcessID(); + metadata.host_name = "host-" + std::to_string(t); + metadata.topic_name = "topic_" + std::to_string(t) + "_" + std::to_string(i); + metadata.encoding = "protobuf"; + metadata.type_name = "TestMessage"; + metadata.direction = publisher; + + EXPECT_NO_THROW(writer_->writeTopicMetadata(metadata)); + } + }); + } + + for (auto& t : threads) t.join(); + + auto lines = readJsonLines(writer_->getTopicMetadataFilePath()); + EXPECT_EQ(lines.size(), num_threads * metadata_per_thread); +} + +TEST_F(ThreadSafetyTest, ConcurrentBufferingWithForceFlush) +{ + auto provider = eCAL::g_trace_provider(); + if (!provider) return; + + const int num_threads = 4; + const int spans_per_thread = 20; + std::vector threads; + + for (int t = 0; t < num_threads; ++t) + { + threads.emplace_back([&provider, t, spans_per_thread]() + { + for (int i = 0; i < spans_per_thread; ++i) + { + SSpanData span{}; + span.entity_id = t * 1000 + i; + span.process_id = eCAL::Process::GetProcessID(); + span.op_type = send; + provider->bufferSpan(span); + } + }); + } + + for (auto& t : threads) t.join(); + + triggerFlush(provider.get()); + + EXPECT_EQ(provider->getSpans().size(), 0); + + auto lines = readJsonLines(spansFilePath()); + EXPECT_GE(lines.size(), static_cast(num_threads * spans_per_thread)); +} + +TEST_F(ThreadSafetyTest, ConcurrentGetSpans) +{ + auto provider = eCAL::g_trace_provider(); + if (!provider) return; + + for (int i = 0; i < 50; ++i) + { + SSpanData span{}; + span.entity_id = i; + span.op_type = send; + provider->bufferSpan(span); + } + + const int num_readers = 3; + std::vector threads; + + for (int t = 0; t < num_readers; ++t) + { + threads.emplace_back([&provider]() + { + for (int i = 0; i < 100; ++i) + { + auto spans = provider->getSpans(); + EXPECT_GE(spans.size(), 0u); + } + }); + } + + threads.emplace_back([&provider]() + { + for (int i = 0; i < 50; ++i) + { + SSpanData span{}; + span.entity_id = 1000 + i; + span.op_type = send; + provider->bufferSpan(span); + } + }); + + for (auto& t : threads) t.join(); + + auto spans = provider->getSpans(); + EXPECT_EQ(spans.size(), 100u); +} + +TEST_F(ThreadSafetyTest, ConcurrentWriteBatchSpansIntegrity) +{ + // Regression: writeBatchSpans previously had no mutex, so concurrent + // flushes interleaved writes producing corrupted JSONL lines. + const int num_threads = 8; + const int batches_per_thread = 20; + const int spans_per_batch = 5; + std::vector threads; + + for (int t = 0; t < num_threads; ++t) + { + threads.emplace_back([this, t, batches_per_thread, spans_per_batch]() + { + for (int b = 0; b < batches_per_thread; ++b) + { + std::vector batch; + for (int s = 0; s < spans_per_batch; ++s) + { + SSpanData span{}; + span.entity_id = static_cast(t); + span.clock = b * spans_per_batch + s; + span.op_type = send; + batch.push_back(span); + } + writer_->writeBatchSpans(batch); + } + }); + } + + for (auto& t : threads) t.join(); + + auto lines = readJsonLines(writer_->getSpansFilePath()); + EXPECT_EQ(lines.size(), + static_cast(num_threads) * batches_per_thread * spans_per_batch); +} diff --git a/ecal/tests/cpp/tracing_test/src/systemtest/tracing_pubsub_stress_test.cpp b/ecal/tests/cpp/tracing_test/src/systemtest/tracing_pubsub_stress_test.cpp new file mode 100644 index 0000000000..4cefb33761 --- /dev/null +++ b/ecal/tests/cpp/tracing_test/src/systemtest/tracing_pubsub_stress_test.cpp @@ -0,0 +1,262 @@ +/* ========================= eCAL LICENSE ================================= + * + * Copyright (C) 2016 - 2025 Continental Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ========================= eCAL LICENSE ================================= +*/ + +#include "tracing_test_helpers.h" + +using namespace eCAL::tracing; + +// ============ Real Pub/Sub Stress Tests ============ +// +// These tests use actual eCAL publishers and subscribers exchanging data at +// high frequency. They validate that the tracing subsystem generates the +// correct number/type of spans under realistic load. + +class PubSubStressTest : public ::testing::Test +{ +protected: + void SetUp() override + { + eCAL::Configuration config; + config.registration.registration_refresh = 100; + config.registration.registration_timeout = 200; + + config.publisher.layer.shm.enable = true; + config.publisher.layer.udp.enable = false; + config.publisher.layer.tcp.enable = false; + config.subscriber.layer.shm.enable = true; + config.subscriber.layer.udp.enable = false; + config.subscriber.layer.tcp.enable = false; + + eCAL::Initialize(config, "tracing_pubsub_stress"); + + setenv("ECAL_TRACING_DATA_DIR", tempDir().c_str(), 1); + if (auto provider = eCAL::g_trace_provider(); provider) + { + triggerFlush(provider.get()); + } + removeFile(spansFilePath()); + removeFile(metadataFilePath()); + } + + void TearDown() override + { + if (auto provider = eCAL::g_trace_provider(); provider) + { + triggerFlush(provider.get()); + } + removeFile(spansFilePath()); + removeFile(metadataFilePath()); + + eCAL::Finalize(); + } + + static void waitForMatch(const std::vector& subs, int timeout_ms = 2000) + { + auto deadline = std::chrono::steady_clock::now() + + std::chrono::milliseconds(timeout_ms); + while (std::chrono::steady_clock::now() < deadline) + { + bool all_matched = true; + for (auto* s : subs) + { + if (s->GetPublisherCount() == 0) + { + all_matched = false; + break; + } + } + if (all_matched) return; + eCAL::Process::SleepMS(50); + } + } +}; + +// 2 publishers, 2 subscribers on the same topic, high-frequency send. +TEST_F(PubSubStressTest, TwoPubTwoSubHighFrequency) +{ + const std::string topic = "stress_topic_a"; + const int num_messages = 500; + const std::string payload(128, 'X'); + + eCAL::CPublisher pub1(topic); + eCAL::CPublisher pub2(topic); + eCAL::CSubscriber sub1(topic); + eCAL::CSubscriber sub2(topic); + + std::atomic recv_count1{0}; + std::atomic recv_count2{0}; + + sub1.SetReceiveCallback([&recv_count1](auto&&...) { recv_count1.fetch_add(1, std::memory_order_relaxed); }); + sub2.SetReceiveCallback([&recv_count2](auto&&...) { recv_count2.fetch_add(1, std::memory_order_relaxed); }); + + std::vector subs = {&sub1, &sub2}; + waitForMatch(subs); + + std::thread t1([&]() { for (int i = 0; i < num_messages; ++i) pub1.Send(payload); }); + std::thread t2([&]() { for (int i = 0; i < num_messages; ++i) pub2.Send(payload); }); + t1.join(); + t2.join(); + + eCAL::Process::SleepMS(500); + + if (auto provider = eCAL::g_trace_provider(); provider) + triggerFlush(provider.get()); + + auto lines = readJsonLines(spansFilePath()); + + size_t send_spans = 0, receive_spans = 0, callback_spans = 0; + for (const auto& line : lines) + { + if (line["entity_id"].get() == 0) continue; + int op = line["op_type"].get(); + if (op == send) ++send_spans; + else if (op == receive) ++receive_spans; + else if (op == callback_execution) ++callback_spans; + } + + EXPECT_EQ(send_spans, 2u * num_messages); + EXPECT_GE(receive_spans, 0u); + EXPECT_GE(callback_spans, 0u); + + for (const auto& line : lines) + { + if (line["entity_id"].get() != 0) + { + EXPECT_GT(line["start_ns"].get(), 0); + EXPECT_GT(line["end_ns"].get(), 0); + } + } +} + +// 3 publishers on different topics, 1 subscriber per topic. +TEST_F(PubSubStressTest, MultiTopicHighFrequency) +{ + const int num_topics = 3; + const int num_messages = 300; + const std::string payload(64, 'Y'); + + std::vector> pubs; + std::vector> subs; + std::vector> recv_counts(num_topics); + for (auto& c : recv_counts) c.store(0); + + for (int t = 0; t < num_topics; ++t) + { + std::string topic = "stress_multi_" + std::to_string(t); + pubs.push_back(std::make_unique(topic)); + subs.push_back(std::make_unique(topic)); + subs.back()->SetReceiveCallback([&recv_counts, t](auto&&...) { + recv_counts[t].fetch_add(1, std::memory_order_relaxed); + }); + } + + std::vector sub_ptrs; + for (auto& s : subs) sub_ptrs.push_back(s.get()); + waitForMatch(sub_ptrs); + + std::vector threads; + for (int t = 0; t < num_topics; ++t) + { + threads.emplace_back([&pubs, &payload, t, num_messages]() { + for (int i = 0; i < num_messages; ++i) + pubs[t]->Send(payload); + }); + } + for (auto& th : threads) th.join(); + + eCAL::Process::SleepMS(500); + + if (auto provider = eCAL::g_trace_provider(); provider) + triggerFlush(provider.get()); + + auto lines = readJsonLines(spansFilePath()); + + size_t send_spans = 0; + for (const auto& line : lines) + { + if (line["entity_id"].get() == 0) continue; + if (line["op_type"].get() == send) + ++send_spans; + } + + EXPECT_EQ(send_spans, static_cast(num_topics) * num_messages); + + for (const auto& line : lines) + { + if (line["entity_id"].get() == 0) continue; + if (line["op_type"].get() == send) + { + EXPECT_EQ(line["layer"].get(), tl_trace_shm); + } + } +} + +// Sustained high-frequency publishing with subscriber callback overhead. +TEST_F(PubSubStressTest, HighFrequencyWithSlowCallback) +{ + const std::string topic = "stress_slow_cb"; + const int num_messages = 200; + const std::string payload(256, 'Z'); + + eCAL::CPublisher pub(topic); + eCAL::CSubscriber sub(topic); + + std::atomic recv_count{0}; + + sub.SetReceiveCallback([&recv_count](auto&&...) { + recv_count.fetch_add(1, std::memory_order_relaxed); + std::this_thread::sleep_for(std::chrono::microseconds(50)); + }); + + std::vector sub_ptrs = {&sub}; + waitForMatch(sub_ptrs); + + for (int i = 0; i < num_messages; ++i) + { + pub.Send(payload); + std::this_thread::sleep_for(std::chrono::microseconds(100)); + } + + eCAL::Process::SleepMS(1000); + + if (auto provider = eCAL::g_trace_provider(); provider) + triggerFlush(provider.get()); + + auto lines = readJsonLines(spansFilePath()); + + size_t send_spans = 0; + for (const auto& line : lines) + { + if (line["entity_id"].get() == 0) continue; + if (line["op_type"].get() == send) + ++send_spans; + } + EXPECT_EQ(send_spans, static_cast(num_messages)); + + for (const auto& line : lines) + { + if (line["op_type"].get() == callback_execution) + { + long long duration_ns = line["end_ns"].get() + - line["start_ns"].get(); + EXPECT_GE(duration_ns, 40000) + << "callback_execution span duration too short"; + } + } +} diff --git a/ecal/tests/cpp/tracing_test/src/tracing_test_helpers.h b/ecal/tests/cpp/tracing_test/src/tracing_test_helpers.h new file mode 100644 index 0000000000..a6406fd39e --- /dev/null +++ b/ecal/tests/cpp/tracing_test/src/tracing_test_helpers.h @@ -0,0 +1,128 @@ +/* ========================= eCAL LICENSE ================================= + * + * Copyright (C) 2016 - 2025 Continental Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ========================= eCAL LICENSE ================================= +*/ + +#pragma once + +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#ifdef _WIN32 +#include +inline int setenv(const char* name, const char* value, int /*overwrite*/) +{ + return _putenv_s(name, value); +} +#endif + +using json = nlohmann::json; + +// Return a platform-appropriate temporary directory. +inline std::string tempDir() +{ +#ifdef _WIN32 + const char* dir = std::getenv("TEMP"); + if (dir) return dir; + dir = std::getenv("TMP"); + if (dir) return dir; + return "C:\\Temp"; +#else + return "/tmp"; +#endif +} + +// Remove a file if it exists. +inline void removeFile(const std::string& path) +{ + std::remove(path.c_str()); +} + +// Read a JSONL file and return a vector of parsed JSON objects. +inline std::vector readJsonLines(const std::string& path) +{ + std::vector result; + std::ifstream file(path); + std::string line; + while (std::getline(file, line)) + { + if (!line.empty()) + { + try { result.push_back(json::parse(line)); } + catch (...) {} + } + } + return result; +} + +// Convenience accessors for the provider's output file paths. +inline std::string spansFilePath() +{ + auto p = eCAL::g_trace_provider(); + if (p) return p->getSpansFilePath(); + return {}; +} + +inline std::string metadataFilePath() +{ + auto p = eCAL::g_trace_provider(); + if (p) return p->getTopicMetadataFilePath(); + return {}; +} + +// Wait for the provider's background writer thread to drain the span buffer. +inline bool waitForBufferDrain(eCAL::tracing::CTraceProvider* provider, int timeout_ms = 500) +{ + auto deadline = std::chrono::steady_clock::now() + + std::chrono::milliseconds(timeout_ms); + while (!provider->getSpans().empty() + && std::chrono::steady_clock::now() < deadline) + { + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + } + return provider->getSpans().empty(); +} + +// Force the provider to synchronously flush all buffered spans. +// Intended for setUp/tearDown cleanup. +inline void triggerFlush(eCAL::tracing::CTraceProvider* provider) +{ + provider->forceFlush(); +} diff --git a/ecal/tests/cpp/tracing_test/src/unittest/tracing_edge_case_test.cpp b/ecal/tests/cpp/tracing_test/src/unittest/tracing_edge_case_test.cpp new file mode 100644 index 0000000000..ad2597cdb1 --- /dev/null +++ b/ecal/tests/cpp/tracing_test/src/unittest/tracing_edge_case_test.cpp @@ -0,0 +1,192 @@ +/* ========================= eCAL LICENSE ================================= + * + * Copyright (C) 2016 - 2025 Continental Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ========================= eCAL LICENSE ================================= +*/ + +#include "tracing_test_helpers.h" + +using namespace eCAL::tracing; + +// ============ Edge Case Tests ============ + +class EdgeCaseTest : public ::testing::Test +{ +protected: + void SetUp() override + { + setenv("ECAL_TRACING_DATA_DIR", tempDir().c_str(), 1); + if (auto provider = eCAL::g_trace_provider(); provider) + { + triggerFlush(provider.get()); + } + removeFile(spansFilePath()); + removeFile(metadataFilePath()); + } + + void TearDown() override + { + if (auto provider = eCAL::g_trace_provider(); provider) + { + triggerFlush(provider.get()); + } + removeFile(spansFilePath()); + removeFile(metadataFilePath()); + } +}; + +TEST_F(EdgeCaseTest, AllLayerTypesCombined) +{ + eCAL::STopicId topic_id; + topic_id.topic_id.entity_id = 1; + topic_id.topic_id.process_id = 2; + + eTracingLayerType all_layers = tl_trace_all; + + { + CSpan span(topic_id, 100, all_layers, 256, send); + } + + auto provider = eCAL::g_trace_provider(); + if (!provider) return; + auto spans = provider->getSpans(); + ASSERT_EQ(spans.size(), 1); + EXPECT_EQ(spans[0].layer, static_cast(all_layers)); +} + +TEST_F(EdgeCaseTest, NoneLayerType) +{ + eCAL::STopicId topic_id; + topic_id.topic_id.entity_id = 1; + topic_id.topic_id.process_id = 2; + + { + CSpan span(topic_id, 0, tl_trace_none, 0, send); + } + + auto provider = eCAL::g_trace_provider(); + if (!provider) return; + auto spans = provider->getSpans(); + ASSERT_EQ(spans.size(), 1); + EXPECT_EQ(spans[0].layer, tl_trace_none); +} + +TEST_F(EdgeCaseTest, EmptyStringMetadata) +{ + STopicMetadata metadata; + metadata.host_name = ""; + metadata.topic_name = ""; + metadata.encoding = ""; + metadata.type_name = ""; + metadata.entity_id = 0; + metadata.process_id = 0; + metadata.direction = publisher; + + CTracingWriter writer; + EXPECT_NO_THROW(writer.writeTopicMetadata(metadata)); + + auto lines = readJsonLines(writer.getTopicMetadataFilePath()); + ASSERT_EQ(lines.size(), 1); + EXPECT_EQ(lines[0]["host_name"].get(), ""); + EXPECT_EQ(lines[0]["topic_name"].get(), ""); +} + +TEST_F(EdgeCaseTest, ForceFlushSingleSpan) +{ + auto provider = eCAL::g_trace_provider(); + if (!provider) return; + + SSpanData span{}; + span.entity_id = 42; + span.op_type = send; + provider->bufferSpan(span); + + provider->forceFlush(); + EXPECT_EQ(provider->getSpans().size(), 0); +} + +TEST_F(EdgeCaseTest, RapidForceFlushCycles) +{ + auto provider = eCAL::g_trace_provider(); + if (!provider) return; + + for (int cycle = 0; cycle < 10; ++cycle) + { + SSpanData span{}; + span.entity_id = cycle; + span.op_type = send; + provider->bufferSpan(span); + provider->forceFlush(); + EXPECT_EQ(provider->getSpans().size(), 0); + } +} + +TEST_F(EdgeCaseTest, MaxEntityAndProcessIds) +{ + SSpanData span{}; + span.entity_id = UINT64_MAX; + span.process_id = UINT64_MAX; + span.topic_id = UINT64_MAX; + span.op_type = send; + + CTracingWriter writer; + writer.writeBatchSpans({span}); + + auto lines = readJsonLines(writer.getSpansFilePath()); + ASSERT_EQ(lines.size(), 1); + EXPECT_EQ(lines[0]["entity_id"].get(), UINT64_MAX); + EXPECT_EQ(lines[0]["process_id"].get(), UINT64_MAX); + EXPECT_EQ(lines[0]["topic_id"].get(), UINT64_MAX); +} + +TEST_F(EdgeCaseTest, ReceiveSpanViaProviderFlush) +{ + auto provider = eCAL::g_trace_provider(); + if (!provider) return; + + eCAL::Payload::TopicInfo topic_info; + topic_info.topic_id = 555; + topic_info.process_id = 666; + + { + CSpan span(777, topic_info, 42, tl_trace_tcp, 2048, receive); + } + + auto spans = provider->getSpans(); + ASSERT_EQ(spans.size(), 1); + EXPECT_EQ(spans[0].entity_id, 777); + EXPECT_EQ(spans[0].topic_id, 555); + EXPECT_EQ(spans[0].process_id, 666); + EXPECT_EQ(spans[0].op_type, receive); + + triggerFlush(provider.get()); + EXPECT_EQ(provider->getSpans().size(), 0); + + auto lines = readJsonLines(spansFilePath()); + ASSERT_GE(lines.size(), 1); + bool found = false; + for (const auto& line : lines) + { + if (line["entity_id"].get() == 777) + { + EXPECT_EQ(line["topic_id"].get(), 555); + EXPECT_EQ(line["op_type"].get(), receive); + found = true; + break; + } + } + EXPECT_TRUE(found); +} diff --git a/ecal/tests/cpp/tracing_test/src/unittest/tracing_layer_type_test.cpp b/ecal/tests/cpp/tracing_test/src/unittest/tracing_layer_type_test.cpp new file mode 100644 index 0000000000..a027918f4f --- /dev/null +++ b/ecal/tests/cpp/tracing_test/src/unittest/tracing_layer_type_test.cpp @@ -0,0 +1,97 @@ +/* ========================= eCAL LICENSE ================================= + * + * Copyright (C) 2016 - 2025 Continental Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ========================= eCAL LICENSE ================================= +*/ + +#include "tracing_test_helpers.h" + +using namespace eCAL::tracing; + +// ============ Tests for Layer Type Enum Values and Conversion ============ + +class TracingLayerTypeTest : public ::testing::Test +{ +protected: + void SetUp() override {} +}; + +TEST_F(TracingLayerTypeTest, LayerTypeValues) +{ + EXPECT_EQ(tl_trace_none, 0); + EXPECT_EQ(tl_trace_shm, 1); + EXPECT_EQ(tl_trace_udp, 2); + EXPECT_EQ(tl_trace_tcp, 4); + EXPECT_EQ(tl_trace_shm_udp, 3); // 1 | 2 + EXPECT_EQ(tl_trace_shm_tcp, 5); // 1 | 4 + EXPECT_EQ(tl_trace_udp_tcp, 6); // 2 | 4 + EXPECT_EQ(tl_trace_all, 7); // 1 | 2 | 4 +} + +TEST_F(TracingLayerTypeTest, LayerTypeBitwise) +{ + uint64_t combined = tl_trace_shm | tl_trace_udp; + EXPECT_EQ(combined, tl_trace_shm_udp); + EXPECT_EQ(combined, 3); + + combined = tl_trace_shm | tl_trace_tcp; + EXPECT_EQ(combined, tl_trace_shm_tcp); + EXPECT_EQ(combined, 5); + + combined = tl_trace_udp | tl_trace_tcp; + EXPECT_EQ(combined, tl_trace_udp_tcp); + EXPECT_EQ(combined, 6); + + combined = tl_trace_shm | tl_trace_udp | tl_trace_tcp; + EXPECT_EQ(combined, tl_trace_all); + EXPECT_EQ(combined, 7); +} + +TEST_F(TracingLayerTypeTest, ToTracingLayerTypeSHM) +{ + eTracingLayerType result = toTracingLayerType(eCAL::tl_ecal_shm); + EXPECT_EQ(result, tl_trace_shm); +} + +TEST_F(TracingLayerTypeTest, ToTracingLayerTypeUDP) +{ + eTracingLayerType result = toTracingLayerType(eCAL::tl_ecal_udp); + EXPECT_EQ(result, tl_trace_udp); +} + +TEST_F(TracingLayerTypeTest, ToTracingLayerTypeTCP) +{ + eTracingLayerType result = toTracingLayerType(eCAL::tl_ecal_tcp); + EXPECT_EQ(result, tl_trace_tcp); +} + +TEST_F(TracingLayerTypeTest, ToTracingLayerTypeAll) +{ + eTracingLayerType result = toTracingLayerType(eCAL::tl_all); + EXPECT_EQ(result, tl_trace_all); +} + +TEST_F(TracingLayerTypeTest, ToTracingLayerTypeUnknown) +{ + eTracingLayerType result = toTracingLayerType(static_cast(999)); + EXPECT_EQ(result, tl_trace_none); +} + +TEST_F(TracingLayerTypeTest, ToTracingLayerTypeNone) +{ + eTracingLayerType result = toTracingLayerType(eCAL::tl_none); + EXPECT_EQ(result, tl_trace_none); +} diff --git a/ecal/tests/cpp/tracing_test/src/unittest/tracing_provider_test.cpp b/ecal/tests/cpp/tracing_test/src/unittest/tracing_provider_test.cpp new file mode 100644 index 0000000000..7fc88a53f9 --- /dev/null +++ b/ecal/tests/cpp/tracing_test/src/unittest/tracing_provider_test.cpp @@ -0,0 +1,236 @@ +/* ========================= eCAL LICENSE ================================= + * + * Copyright (C) 2016 - 2025 Continental Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ========================= eCAL LICENSE ================================= +*/ + +#include "tracing_test_helpers.h" + +using namespace eCAL::tracing; + +// ============ Tests for CTraceProvider Singleton and Buffering ============ + +class TracingProviderTest : public ::testing::Test +{ +protected: + void SetUp() override + { + setenv("ECAL_TRACING_DATA_DIR", tempDir().c_str(), 1); + if (auto provider = eCAL::g_trace_provider(); provider) + { + triggerFlush(provider.get()); + } + } + + void TearDown() override + { + if (auto provider = eCAL::g_trace_provider(); provider) + { + triggerFlush(provider.get()); + } + } +}; + +TEST_F(TracingProviderTest, SingletonInstance) +{ + auto provider = eCAL::g_trace_provider(); + if (!provider) return; + + CTraceProvider& provider1 = *provider; + CTraceProvider& provider2 = *provider; + + EXPECT_EQ(&provider1, &provider2); +} + +TEST_F(TracingProviderTest, DefaultBatchSize) +{ + auto provider = eCAL::g_trace_provider(); + if (!provider) return; + + for (size_t i = 0; i < kDefaultTracingBatchSize - 1; ++i) + { + SSpanData span{}; + span.entity_id = i; + provider->bufferSpan(span); + } + EXPECT_EQ(provider->getSpans().size(), kDefaultTracingBatchSize - 1); +} + +TEST_F(TracingProviderTest, BufferSpan) +{ + auto provider = eCAL::g_trace_provider(); + if (!provider) return; + + SSpanData span{}; + span.entity_id = 111; + span.process_id = 222; + span.payload_size = 512; + + provider->bufferSpan(span); + + auto spans = provider->getSpans(); + ASSERT_EQ(spans.size(), 1); + EXPECT_EQ(spans[0].entity_id, 111); + EXPECT_EQ(spans[0].process_id, 222); + EXPECT_EQ(spans[0].payload_size, 512); +} + +TEST_F(TracingProviderTest, BufferMultipleSpans) +{ + auto provider = eCAL::g_trace_provider(); + if (!provider) return; + + for (int i = 0; i < 5; ++i) + { + SSpanData span{}; + span.entity_id = i; + provider->bufferSpan(span); + } + + auto spans = provider->getSpans(); + ASSERT_EQ(spans.size(), 5); + + for (int i = 0; i < 5; ++i) + { + EXPECT_EQ(spans[i].entity_id, static_cast(i)); + } +} + +TEST_F(TracingProviderTest, ForceFlushDrainsBuffer) +{ + auto provider = eCAL::g_trace_provider(); + if (!provider) return; + + SSpanData span{}; + span.entity_id = 999; + provider->bufferSpan(span); + + provider->forceFlush(); + EXPECT_EQ(provider->getSpans().size(), 0); +} + +TEST_F(TracingProviderTest, EmptyBufferStaysEmpty) +{ + auto provider = eCAL::g_trace_provider(); + if (!provider) return; + + EXPECT_EQ(provider->getSpans().size(), 0); +} + +TEST_F(TracingProviderTest, BufferFlushedOnForceFlush) +{ + auto provider = eCAL::g_trace_provider(); + if (!provider) return; + + for (int i = 0; i < 3; ++i) + { + SSpanData span{}; + span.entity_id = i; + provider->bufferSpan(span); + } + + EXPECT_EQ(provider->getSpans().size(), 3); + + provider->forceFlush(); + EXPECT_EQ(provider->getSpans().size(), 0); +} + +TEST_F(TracingProviderTest, MultipleForceFlushCycles) +{ + auto provider = eCAL::g_trace_provider(); + if (!provider) return; + + for (int i = 0; i < 2; ++i) + { + SSpanData span{}; + span.entity_id = i; + provider->bufferSpan(span); + } + provider->forceFlush(); + EXPECT_EQ(provider->getSpans().size(), 0); + + for (int i = 10; i < 12; ++i) + { + SSpanData span{}; + span.entity_id = i; + provider->bufferSpan(span); + } + provider->forceFlush(); + EXPECT_EQ(provider->getSpans().size(), 0); + + SSpanData span{}; + span.entity_id = 99; + provider->bufferSpan(span); + EXPECT_EQ(provider->getSpans().size(), 1); +} + +TEST_F(TracingProviderTest, AddTopicMetadata) +{ + removeFile(metadataFilePath()); + + auto provider = eCAL::g_trace_provider(); + if (!provider) return; + + STopicMetadata metadata; + metadata.entity_id = 777; + metadata.process_id = 888; + metadata.host_name = "provider-host"; + metadata.topic_name = "provider_topic"; + metadata.encoding = "protobuf"; + metadata.type_name = "ProviderMsg"; + metadata.direction = publisher; + + EXPECT_NO_THROW(provider->addTopicMetadata(metadata)); + + auto lines = readJsonLines(metadataFilePath()); + ASSERT_GE(lines.size(), 1); + const auto& obj = lines.back(); + EXPECT_EQ(obj["entity_id"].get(), 777); + EXPECT_EQ(obj["process_id"].get(), 888); + EXPECT_EQ(obj["host_name"].get(), "provider-host"); + EXPECT_EQ(obj["topic_name"].get(), "provider_topic"); + EXPECT_EQ(obj["encoding"].get(), "protobuf"); + EXPECT_EQ(obj["type_name"].get(), "ProviderMsg"); + EXPECT_EQ(obj["direction"].get(), "publisher"); + EXPECT_EQ(obj["tracing_version"].get(), kTracingVersion); + + removeFile(metadataFilePath()); +} + +TEST_F(TracingProviderTest, AddTopicMetadataSubscriber) +{ + removeFile(metadataFilePath()); + + auto provider = eCAL::g_trace_provider(); + if (!provider) return; + + STopicMetadata metadata; + metadata.entity_id = 100; + metadata.process_id = 200; + metadata.host_name = "sub-host"; + metadata.topic_name = "sub_topic"; + metadata.encoding = "flatbuffers"; + metadata.type_name = "SubMsg"; + metadata.direction = subscriber; + + provider->addTopicMetadata(metadata); + + auto lines = readJsonLines(metadataFilePath()); + ASSERT_GE(lines.size(), 1); + EXPECT_EQ(lines.back()["direction"].get(), "subscriber"); + + removeFile(metadataFilePath()); +} diff --git a/ecal/tests/cpp/tracing_test/src/unittest/tracing_span_test.cpp b/ecal/tests/cpp/tracing_test/src/unittest/tracing_span_test.cpp new file mode 100644 index 0000000000..e6db437d2f --- /dev/null +++ b/ecal/tests/cpp/tracing_test/src/unittest/tracing_span_test.cpp @@ -0,0 +1,206 @@ +/* ========================= eCAL LICENSE ================================= + * + * Copyright (C) 2016 - 2025 Continental Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ========================= eCAL LICENSE ================================= +*/ + +#include "tracing_test_helpers.h" + +using namespace eCAL::tracing; + +// ============ Tests for CSpan RAII Wrapper ============ + +class SpanTest : public ::testing::Test +{ +protected: + void SetUp() override + { + setenv("ECAL_TRACING_DATA_DIR", tempDir().c_str(), 1); + if (auto provider = eCAL::g_trace_provider(); provider) + { + triggerFlush(provider.get()); + } + } + + void TearDown() override + { + if (auto provider = eCAL::g_trace_provider(); provider) + { + triggerFlush(provider.get()); + } + } +}; + +TEST_F(SpanTest, SendSpanConstruction) +{ + eCAL::STopicId topic_id; + topic_id.topic_id.entity_id = 111; + topic_id.topic_id.process_id = 222; + + { + CSpan span(topic_id, 100, tl_trace_shm, 256, send); + } + + if (auto provider = eCAL::g_trace_provider(); provider) + { + auto spans = provider->getSpans(); + ASSERT_EQ(spans.size(), 1); + + EXPECT_EQ(spans[0].entity_id, 111); + EXPECT_EQ(spans[0].process_id, 222); + EXPECT_EQ(spans[0].payload_size, 256); + EXPECT_EQ(spans[0].clock, 100); + EXPECT_EQ(spans[0].layer, tl_trace_shm); + EXPECT_EQ(spans[0].op_type, send); + EXPECT_EQ(spans[0].topic_id, 0); // Send spans have topic_id = 0 + } +} + +TEST_F(SpanTest, SendSpanTimestamps) +{ + eCAL::STopicId topic_id; + topic_id.topic_id.entity_id = 111; + topic_id.topic_id.process_id = 222; + + { + CSpan span(topic_id, 100, tl_trace_shm, 256, send); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + + if (auto provider = eCAL::g_trace_provider(); provider) + { + auto spans = provider->getSpans(); + ASSERT_EQ(spans.size(), 1); + + EXPECT_GT(spans[0].start_ns, 0); + EXPECT_GT(spans[0].end_ns, 0); + EXPECT_LT(spans[0].start_ns, spans[0].end_ns); + EXPECT_GE(spans[0].end_ns - spans[0].start_ns, 500000); // at least 0.5 ms + } +} + +TEST_F(SpanTest, ReceiveSpanConstruction) +{ + eCAL::Payload::TopicInfo topic_info; + topic_info.topic_id = 999; + topic_info.process_id = 333; + + { + CSpan span(444, topic_info, 200, tl_trace_udp, 512, receive); + } + + if (auto provider = eCAL::g_trace_provider(); provider) + { + auto spans = provider->getSpans(); + ASSERT_EQ(spans.size(), 1); + + EXPECT_EQ(spans[0].entity_id, 444); + EXPECT_EQ(spans[0].topic_id, 999); + EXPECT_EQ(spans[0].process_id, 333); + EXPECT_EQ(spans[0].payload_size, 512); + EXPECT_EQ(spans[0].clock, 200); + EXPECT_EQ(spans[0].layer, tl_trace_udp); + EXPECT_EQ(spans[0].op_type, receive); + } +} + +TEST_F(SpanTest, ReceiveSpanTimestamps) +{ + eCAL::Payload::TopicInfo topic_info; + topic_info.topic_id = 999; + topic_info.process_id = 333; + + { + CSpan span(444, topic_info, 200, tl_trace_udp, 512, receive); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + + if (auto provider = eCAL::g_trace_provider(); provider) + { + auto spans = provider->getSpans(); + ASSERT_EQ(spans.size(), 1); + + EXPECT_GT(spans[0].start_ns, 0); + EXPECT_GT(spans[0].end_ns, 0); + EXPECT_LT(spans[0].start_ns, spans[0].end_ns); + } +} + +TEST_F(SpanTest, CallbackExecutionSpan) +{ + eCAL::STopicId topic_id; + topic_id.topic_id.entity_id = 50; + topic_id.topic_id.process_id = 60; + + { + CSpan span(topic_id, 300, tl_trace_tcp, 1024, callback_execution); + } + + if (auto provider = eCAL::g_trace_provider(); provider) + { + auto spans = provider->getSpans(); + ASSERT_EQ(spans.size(), 1); + EXPECT_EQ(spans[0].op_type, callback_execution); + EXPECT_EQ(spans[0].layer, tl_trace_tcp); + EXPECT_EQ(spans[0].payload_size, 1024); + } +} + +TEST_F(SpanTest, MultipleOperationTypes) +{ + eCAL::STopicId topic_id; + topic_id.topic_id.entity_id = 111; + topic_id.topic_id.process_id = 222; + + { + CSpan span(topic_id, 100, tl_trace_shm, 256, send); + } + { + CSpan span(topic_id, 100, tl_trace_shm, 256, callback_execution); + } + + if (auto provider = eCAL::g_trace_provider(); provider) + { + auto spans = provider->getSpans(); + ASSERT_EQ(spans.size(), 2); + + EXPECT_EQ(spans[0].op_type, send); + EXPECT_EQ(spans[1].op_type, callback_execution); + } +} + +TEST_F(SpanTest, MultipleLayerTypes) +{ + eCAL::STopicId topic_id; + topic_id.topic_id.entity_id = 111; + topic_id.topic_id.process_id = 222; + + { CSpan span1(topic_id, 100, tl_trace_shm, 256, send); } + { CSpan span2(topic_id, 100, tl_trace_udp, 256, send); } + { CSpan span3(topic_id, 100, tl_trace_tcp, 256, send); } + { CSpan span4(topic_id, 100, tl_trace_shm_udp, 256, send); } + + if (auto provider = eCAL::g_trace_provider(); provider) + { + auto spans = provider->getSpans(); + ASSERT_EQ(spans.size(), 4); + + EXPECT_EQ(spans[0].layer, tl_trace_shm); + EXPECT_EQ(spans[1].layer, tl_trace_udp); + EXPECT_EQ(spans[2].layer, tl_trace_tcp); + EXPECT_EQ(spans[3].layer, tl_trace_shm_udp); + } +} diff --git a/ecal/tests/cpp/tracing_test/src/unittest/tracing_types_test.cpp b/ecal/tests/cpp/tracing_test/src/unittest/tracing_types_test.cpp new file mode 100644 index 0000000000..ea994901fc --- /dev/null +++ b/ecal/tests/cpp/tracing_test/src/unittest/tracing_types_test.cpp @@ -0,0 +1,117 @@ +/* ========================= eCAL LICENSE ================================= + * + * Copyright (C) 2016 - 2025 Continental Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ========================= eCAL LICENSE ================================= +*/ + +#include "tracing_test_helpers.h" + +using namespace eCAL::tracing; + +// ============ Tests for Enums and Constants ============ + +class TracingTypesTest : public ::testing::Test +{ +protected: + void SetUp() override {} + void TearDown() override {} +}; + +TEST_F(TracingTypesTest, TracingVersionConstant) +{ + EXPECT_STREQ(kTracingVersion, "1.0.0"); +} + +TEST_F(TracingTypesTest, DefaultTracingBatchSize) +{ + EXPECT_EQ(kDefaultTracingBatchSize, 500); +} + +TEST_F(TracingTypesTest, SSpanDataDefaultInitialization) +{ + SSpanData span{}; + EXPECT_EQ(span.topic_id, 0); + EXPECT_EQ(span.entity_id, 0); + EXPECT_EQ(span.process_id, 0); + EXPECT_EQ(span.payload_size, 0); + EXPECT_EQ(span.clock, 0); + EXPECT_EQ(span.layer, 0); + EXPECT_EQ(span.start_ns, 0); + EXPECT_EQ(span.end_ns, 0); +} + +TEST_F(TracingTypesTest, SSpanDataAssignment) +{ + SSpanData span; + span.entity_id = 12345; + span.topic_id = 67890; + span.process_id = 111; + span.payload_size = 256; + span.clock = 100; + span.layer = tl_trace_shm; + span.start_ns = 1000000; + span.end_ns = 2000000; + span.op_type = send; + + EXPECT_EQ(span.entity_id, 12345); + EXPECT_EQ(span.topic_id, 67890); + EXPECT_EQ(span.process_id, 111); + EXPECT_EQ(span.payload_size, 256); + EXPECT_EQ(span.clock, 100); + EXPECT_EQ(span.layer, tl_trace_shm); + EXPECT_EQ(span.start_ns, 1000000); + EXPECT_EQ(span.end_ns, 2000000); + EXPECT_EQ(span.op_type, send); +} + +TEST_F(TracingTypesTest, STopicMetadataDefaultVersion) +{ + STopicMetadata metadata; + EXPECT_STREQ(metadata.tracing_version.c_str(), kTracingVersion); +} + +TEST_F(TracingTypesTest, STopicMetadataAssignment) +{ + STopicMetadata metadata; + metadata.entity_id = 999; + metadata.process_id = 42; + metadata.host_name = "test-host"; + metadata.topic_name = "test_topic"; + metadata.encoding = "protobuf"; + metadata.type_name = "TestType"; + metadata.direction = publisher; + + EXPECT_EQ(metadata.entity_id, 999); + EXPECT_EQ(metadata.process_id, 42); + EXPECT_EQ(metadata.host_name, "test-host"); + EXPECT_EQ(metadata.topic_name, "test_topic"); + EXPECT_EQ(metadata.encoding, "protobuf"); + EXPECT_EQ(metadata.type_name, "TestType"); + EXPECT_EQ(metadata.direction, publisher); +} + +TEST_F(TracingTypesTest, OperationTypeEnum) +{ + EXPECT_EQ(send, 0); + EXPECT_EQ(receive, 1); + EXPECT_EQ(callback_execution, 2); +} + +TEST_F(TracingTypesTest, TopicDirectionEnum) +{ + EXPECT_EQ(publisher, 0); + EXPECT_EQ(subscriber, 1); +} diff --git a/ecal/tests/cpp/tracing_test/src/unittest/tracing_writer_test.cpp b/ecal/tests/cpp/tracing_test/src/unittest/tracing_writer_test.cpp new file mode 100644 index 0000000000..3e76c3d127 --- /dev/null +++ b/ecal/tests/cpp/tracing_test/src/unittest/tracing_writer_test.cpp @@ -0,0 +1,291 @@ +/* ========================= eCAL LICENSE ================================= + * + * Copyright (C) 2016 - 2025 Continental Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ========================= eCAL LICENSE ================================= +*/ + +#include "tracing_test_helpers.h" + +using namespace eCAL::tracing; + +// ============ Tests for CTracingWriter ============ + +class TracingWriterTest : public ::testing::Test +{ +protected: + std::unique_ptr writer_; + + void SetUp() override + { + setenv("ECAL_TRACING_DATA_DIR", tempDir().c_str(), 1); + if (auto provider = eCAL::g_trace_provider(); provider) + { + triggerFlush(provider.get()); + } + writer_ = std::make_unique(); + removeFile(writer_->getSpansFilePath()); + removeFile(writer_->getTopicMetadataFilePath()); + } + + void TearDown() override + { + if (auto provider = eCAL::g_trace_provider(); provider) + { + triggerFlush(provider.get()); + } + removeFile(writer_->getSpansFilePath()); + removeFile(writer_->getTopicMetadataFilePath()); + } +}; + +TEST_F(TracingWriterTest, WriterConstruction) +{ + SUCCEED(); +} + +TEST_F(TracingWriterTest, WriteBatchSpansVerifyContent) +{ + std::vector batch; + + for (int i = 0; i < 3; ++i) + { + SSpanData span{}; + span.entity_id = 100 + i; + span.topic_id = 200 + i; + span.process_id = 300 + i; + span.payload_size = 256 * (i + 1); + span.clock = 10 * i; + span.layer = tl_trace_shm; + span.start_ns = 1000000 + i * 100; + span.end_ns = 2000000 + i * 100; + span.op_type = send; + batch.push_back(span); + } + + writer_->writeBatchSpans(batch); + + auto lines = readJsonLines(writer_->getSpansFilePath()); + ASSERT_EQ(lines.size(), 3); + + for (int i = 0; i < 3; ++i) + { + EXPECT_EQ(lines[i]["entity_id"].get(), 100 + i); + EXPECT_EQ(lines[i]["topic_id"].get(), 200 + i); + EXPECT_EQ(lines[i]["process_id"].get(), 300 + i); + EXPECT_EQ(lines[i]["payload_size"].get(), 256 * (i + 1)); + EXPECT_EQ(lines[i]["clock"].get(), 10 * i); + EXPECT_EQ(lines[i]["layer"].get(), tl_trace_shm); + EXPECT_EQ(lines[i]["start_ns"].get(), 1000000 + i * 100); + EXPECT_EQ(lines[i]["end_ns"].get(), 2000000 + i * 100); + EXPECT_EQ(lines[i]["op_type"].get(), send); + } +} + +TEST_F(TracingWriterTest, WriteBatchSpansReceive) +{ + SSpanData span{}; + span.entity_id = 10; + span.topic_id = 20; + span.process_id = 30; + span.payload_size = 512; + span.clock = 5; + span.layer = tl_trace_udp; + span.start_ns = 5000000; + span.end_ns = 6000000; + span.op_type = receive; + + writer_->writeBatchSpans({span}); + + auto lines = readJsonLines(writer_->getSpansFilePath()); + ASSERT_EQ(lines.size(), 1); + EXPECT_EQ(lines[0]["op_type"].get(), receive); + EXPECT_EQ(lines[0]["topic_id"].get(), 20); + EXPECT_EQ(lines[0]["layer"].get(), tl_trace_udp); +} + +TEST_F(TracingWriterTest, WriteBatchSpansAppends) +{ + SSpanData span1{}; + span1.entity_id = 1; + span1.op_type = send; + writer_->writeBatchSpans({span1}); + + SSpanData span2{}; + span2.entity_id = 2; + span2.op_type = send; + writer_->writeBatchSpans({span2}); + + auto lines = readJsonLines(writer_->getSpansFilePath()); + ASSERT_EQ(lines.size(), 2); + EXPECT_EQ(lines[0]["entity_id"].get(), 1); + EXPECT_EQ(lines[1]["entity_id"].get(), 2); +} + +TEST_F(TracingWriterTest, WriteEmptyBatch) +{ + std::vector batch; + + EXPECT_NO_THROW(writer_->writeBatchSpans(batch)); + + auto lines = readJsonLines(writer_->getSpansFilePath()); + EXPECT_EQ(lines.size(), 0); +} + +TEST_F(TracingWriterTest, WriteTopicMetadataVerifyContent) +{ + STopicMetadata metadata; + metadata.entity_id = 555; + metadata.process_id = 666; + metadata.host_name = "test-host"; + metadata.topic_name = "test_topic"; + metadata.encoding = "protobuf"; + metadata.type_name = "TestMessage"; + metadata.direction = publisher; + + writer_->writeTopicMetadata(metadata); + + auto lines = readJsonLines(writer_->getTopicMetadataFilePath()); + ASSERT_EQ(lines.size(), 1); + + const auto& obj = lines[0]; + EXPECT_EQ(obj["tracing_version"].get(), kTracingVersion); + EXPECT_EQ(obj["entity_id"].get(), 555); + EXPECT_EQ(obj["process_id"].get(), 666); + EXPECT_EQ(obj["host_name"].get(), "test-host"); + EXPECT_EQ(obj["topic_name"].get(), "test_topic"); + EXPECT_EQ(obj["encoding"].get(), "protobuf"); + EXPECT_EQ(obj["type_name"].get(), "TestMessage"); + EXPECT_EQ(obj["direction"].get(), "publisher"); +} + +TEST_F(TracingWriterTest, WriteTopicMetadataSubscriber) +{ + STopicMetadata metadata; + metadata.entity_id = 10; + metadata.process_id = 20; + metadata.host_name = "sub-host"; + metadata.topic_name = "sub_topic"; + metadata.encoding = "raw"; + metadata.type_name = "RawData"; + metadata.direction = subscriber; + + writer_->writeTopicMetadata(metadata); + + auto lines = readJsonLines(writer_->getTopicMetadataFilePath()); + ASSERT_EQ(lines.size(), 1); + EXPECT_EQ(lines[0]["direction"].get(), "subscriber"); +} + +TEST_F(TracingWriterTest, WriteMultipleMetadataAppends) +{ + for (int i = 0; i < 3; ++i) + { + STopicMetadata metadata; + metadata.entity_id = 555 + i; + metadata.process_id = 666 + i; + metadata.host_name = "host-" + std::to_string(i); + metadata.topic_name = "topic_" + std::to_string(i); + metadata.encoding = "protobuf"; + metadata.type_name = "TestMessage"; + metadata.direction = (i % 2 == 0) ? publisher : subscriber; + + writer_->writeTopicMetadata(metadata); + } + + auto lines = readJsonLines(writer_->getTopicMetadataFilePath()); + ASSERT_EQ(lines.size(), 3); + + for (int i = 0; i < 3; ++i) + { + EXPECT_EQ(lines[i]["entity_id"].get(), 555 + i); + EXPECT_EQ(lines[i]["process_id"].get(), 666 + i); + EXPECT_EQ(lines[i]["host_name"].get(), "host-" + std::to_string(i)); + EXPECT_EQ(lines[i]["topic_name"].get(), "topic_" + std::to_string(i)); + std::string expected_dir = (i % 2 == 0) ? "publisher" : "subscriber"; + EXPECT_EQ(lines[i]["direction"].get(), expected_dir); + } +} + +TEST_F(TracingWriterTest, EmptyStringMetadataFields) +{ + STopicMetadata metadata; + metadata.host_name = ""; + metadata.topic_name = ""; + metadata.encoding = ""; + metadata.type_name = ""; + metadata.entity_id = 0; + metadata.process_id = 0; + metadata.direction = publisher; + + writer_->writeTopicMetadata(metadata); + + auto lines = readJsonLines(writer_->getTopicMetadataFilePath()); + ASSERT_EQ(lines.size(), 1); + EXPECT_EQ(lines[0]["host_name"].get(), ""); + EXPECT_EQ(lines[0]["topic_name"].get(), ""); + EXPECT_EQ(lines[0]["encoding"].get(), ""); + EXPECT_EQ(lines[0]["type_name"].get(), ""); + EXPECT_EQ(lines[0]["entity_id"].get(), 0); + EXPECT_EQ(lines[0]["process_id"].get(), 0); +} + +TEST_F(TracingWriterTest, WriteBatchSpansCallbackExecution) +{ + SSpanData span{}; + span.entity_id = 7; + span.topic_id = 0; + span.process_id = 8; + span.payload_size = 128; + span.clock = 42; + span.layer = tl_trace_tcp; + span.start_ns = 100; + span.end_ns = 200; + span.op_type = callback_execution; + + writer_->writeBatchSpans({span}); + + auto lines = readJsonLines(writer_->getSpansFilePath()); + ASSERT_EQ(lines.size(), 1); + EXPECT_EQ(lines[0]["op_type"].get(), callback_execution); + EXPECT_EQ(lines[0]["layer"].get(), tl_trace_tcp); +} + +TEST_F(TracingWriterTest, WriteBatchSpansAllLayerTypes) +{ + const std::vector layer_types = { + tl_trace_none, tl_trace_shm, tl_trace_udp, tl_trace_tcp, + tl_trace_shm_udp, tl_trace_shm_tcp, tl_trace_udp_tcp, tl_trace_all + }; + + std::vector batch; + for (auto layer : layer_types) + { + SSpanData span{}; + span.layer = layer; + span.op_type = send; + batch.push_back(span); + } + + writer_->writeBatchSpans(batch); + + auto lines = readJsonLines(writer_->getSpansFilePath()); + ASSERT_EQ(lines.size(), layer_types.size()); + + for (size_t i = 0; i < layer_types.size(); ++i) + { + EXPECT_EQ(lines[i]["layer"].get(), layer_types[i]); + } +} diff --git a/thirdparty/nlohmann_json/build-nlohmann_json.cmake b/thirdparty/nlohmann_json/build-nlohmann_json.cmake new file mode 100644 index 0000000000..ae2984d190 --- /dev/null +++ b/thirdparty/nlohmann_json/build-nlohmann_json.cmake @@ -0,0 +1,13 @@ +include_guard(GLOBAL) +include(FetchContent) + +FetchContent_Declare( + nlohmann_json + URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz + URL_HASH SHA256=d6c65aca6b1ed68e7a182f4757257b107ae403032760ed6ef121c9d55e81757d +) + +set(JSON_BuildTests OFF CACHE BOOL "" FORCE) +set(JSON_Install OFF CACHE BOOL "" FORCE) + +FetchContent_MakeAvailable(nlohmann_json) From 1e2e3a1821c40647c0535653820030a60fe75cd7 Mon Sep 17 00:00:00 2001 From: Ila Hadi-Assar Date: Fri, 17 Apr 2026 14:46:19 +0200 Subject: [PATCH 2/9] added per operation spantypes with variant --- ecal/core/src/pubsub/ecal_publisher_impl.cpp | 4 +- ecal/core/src/pubsub/ecal_subscriber_impl.cpp | 6 +- ecal/core/src/tracing/span.cpp | 15 +++-- ecal/core/src/tracing/span.h | 40 ++++++++----- ecal/core/src/tracing/trace_provider.cpp | 14 ++--- ecal/core/src/tracing/trace_provider.h | 31 ++-------- ecal/core/src/tracing/tracing.h | 25 +++++++-- ecal/core/src/tracing/tracing_writer.h | 6 +- .../core/src/tracing/tracing_writer_jsonl.cpp | 56 ++++++++++++------- ecal/core/src/tracing/tracing_writer_jsonl.h | 8 +-- 10 files changed, 120 insertions(+), 85 deletions(-) diff --git a/ecal/core/src/pubsub/ecal_publisher_impl.cpp b/ecal/core/src/pubsub/ecal_publisher_impl.cpp index 2d4bc5b925..95bc9effd1 100644 --- a/ecal/core/src/pubsub/ecal_publisher_impl.cpp +++ b/ecal/core/src/pubsub/ecal_publisher_impl.cpp @@ -136,7 +136,7 @@ namespace eCAL meta.encoding = m_topic_info.encoding; meta.type_name = m_topic_info.name; meta.direction = eCAL::tracing::topic_direction::publisher; - if (auto provider = g_trace_provider(); provider) provider->addTopicMetadata(meta); + if (auto provider = g_trace_provider(); provider) provider->WriteMetadata(meta); } // mark as created @@ -211,7 +211,7 @@ namespace eCAL } // create tracing span for the send operation - eCAL::tracing::CSpan send_span( + eCAL::tracing::CPublisherSpan send_span( m_topic_id, m_clock, active_layer, diff --git a/ecal/core/src/pubsub/ecal_subscriber_impl.cpp b/ecal/core/src/pubsub/ecal_subscriber_impl.cpp index ecd4cecd82..0a50a85c98 100644 --- a/ecal/core/src/pubsub/ecal_subscriber_impl.cpp +++ b/ecal/core/src/pubsub/ecal_subscriber_impl.cpp @@ -106,7 +106,7 @@ namespace eCAL meta.encoding = m_topic_info.encoding; meta.type_name = m_topic_info.name; meta.direction = eCAL::tracing::topic_direction::subscriber; - if (auto provider = g_trace_provider(); provider) provider->addTopicMetadata(meta); + if (auto provider = g_trace_provider(); provider) provider->WriteMetadata(meta); } // start transport layers @@ -390,7 +390,7 @@ namespace eCAL size_t CSubscriberImpl::ApplySample(const Payload::TopicInfo& topic_info_, const char* payload_, size_t size_, long long id_, long long clock_, long long time_, size_t /*hash_*/, eTLayerType layer_) { - eCAL::tracing::CSpan receive_span( + eCAL::tracing::CSubscriberSpan receive_span( m_subscriber_id, topic_info_, clock_, @@ -477,7 +477,7 @@ namespace eCAL // execute it const std::lock_guard exec_lock(m_connection_map_mtx); { - eCAL::tracing::CSpan receive_span( + eCAL::tracing::CSubscriberSpan callback_span( m_subscriber_id, topic_info_, clock_, diff --git a/ecal/core/src/tracing/span.cpp b/ecal/core/src/tracing/span.cpp index bba54e8e24..eaed55be40 100644 --- a/ecal/core/src/tracing/span.cpp +++ b/ecal/core/src/tracing/span.cpp @@ -31,7 +31,7 @@ namespace tracing { // Send span constructor - CSpan::CSpan(const STopicId& topic_id, long long clock, eTracingLayerType layer, size_t payload_size, operation_type op_type) + CPublisherSpan::CPublisherSpan(const STopicId& topic_id, long long clock, eTracingLayerType layer, size_t payload_size, operation_type op_type) { auto now = system_clock::now(); data.start_ns = duration_cast(now.time_since_epoch()).count(); @@ -43,8 +43,15 @@ namespace tracing data.op_type = op_type; } + CPublisherSpan::~CPublisherSpan() + { + auto now = system_clock::now(); + data.end_ns = duration_cast(now.time_since_epoch()).count(); + if (auto provider = g_trace_provider(); provider) provider->WriteSpan(data); + } + // Receive span constructor - CSpan::CSpan(EntityIdT entity_id, const eCAL::Payload::TopicInfo& topic_info, long long clock, eTracingLayerType layer, size_t payload_size, operation_type op_type) + CSubscriberSpan::CSubscriberSpan(EntityIdT entity_id, const eCAL::Payload::TopicInfo& topic_info, long long clock, eTracingLayerType layer, size_t payload_size, operation_type op_type) { auto now = system_clock::now(); data.start_ns = duration_cast(now.time_since_epoch()).count(); @@ -57,11 +64,11 @@ namespace tracing data.op_type = op_type; } - CSpan::~CSpan() + CSubscriberSpan::~CSubscriberSpan() { auto now = system_clock::now(); data.end_ns = duration_cast(now.time_since_epoch()).count(); - if (auto provider = g_trace_provider(); provider) provider->bufferSpan(data); + if (auto provider = g_trace_provider(); provider) provider->WriteSpan(data); } } // namespace tracing diff --git a/ecal/core/src/tracing/span.h b/ecal/core/src/tracing/span.h index e20a1c1935..18e86758b6 100644 --- a/ecal/core/src/tracing/span.h +++ b/ecal/core/src/tracing/span.h @@ -30,24 +30,38 @@ namespace eCAL namespace tracing { - // RAII span — records start_ns on construction, end_ns + buffer on destruction. - // Overloaded constructors cover send and receive use cases. - class CSpan { + // RAII span for send (publisher) operations. + // Records start_ns on construction, end_ns + buffer on destruction. + class CPublisherSpan { public: - // Send span (publisher) - CSpan(const STopicId& topic_id, long long clock, eTracingLayerType layer, size_t payload_size, operation_type op_type); - // Receive span (subscriber) - CSpan(EntityIdT entity_id, const eCAL::Payload::TopicInfo& topic_info, long long clock, eTracingLayerType layer, size_t payload_size, operation_type op_type); + CPublisherSpan(const STopicId& topic_id, long long clock, eTracingLayerType layer, size_t payload_size, operation_type op_type); - ~CSpan(); + ~CPublisherSpan(); - CSpan(const CSpan&) = delete; - CSpan& operator=(const CSpan&) = delete; - CSpan(CSpan&&) = delete; - CSpan& operator=(CSpan&&) = delete; + CPublisherSpan(const CPublisherSpan&) = delete; + CPublisherSpan& operator=(const CPublisherSpan&) = delete; + CPublisherSpan(CPublisherSpan&&) = delete; + CPublisherSpan& operator=(CPublisherSpan&&) = delete; private: - SSpanData data; + SPublisherSpanData data{}; + }; + + // RAII span for receive (subscriber) operations. + // Records start_ns on construction, end_ns + buffer on destruction. + class CSubscriberSpan { + public: + CSubscriberSpan(EntityIdT entity_id, const eCAL::Payload::TopicInfo& topic_info, long long clock, eTracingLayerType layer, size_t payload_size, operation_type op_type); + + ~CSubscriberSpan(); + + CSubscriberSpan(const CSubscriberSpan&) = delete; + CSubscriberSpan& operator=(const CSubscriberSpan&) = delete; + CSubscriberSpan(CSubscriberSpan&&) = delete; + CSubscriberSpan& operator=(CSubscriberSpan&&) = delete; + + private: + SSubscriberSpanData data{}; }; } // namespace tracing diff --git a/ecal/core/src/tracing/trace_provider.cpp b/ecal/core/src/tracing/trace_provider.cpp index 89639aee74..66d68e27cd 100644 --- a/ecal/core/src/tracing/trace_provider.cpp +++ b/ecal/core/src/tracing/trace_provider.cpp @@ -46,7 +46,7 @@ namespace tracing CTraceProvider::CTraceProvider(std::unique_ptr writer, size_t batch_size) : batch_size_(batch_size), writer_(std::move(writer)) { - writer_thread_ = std::thread(&CTraceProvider::writerThreadLoop, this); + writer_thread_ = std::thread(&CTraceProvider::WriterThreadLoop, this); } CTraceProvider::~CTraceProvider() @@ -60,7 +60,7 @@ namespace tracing } - void CTraceProvider::bufferSpan(const SSpanData& span_data) + void CTraceProvider::WriteSpan(const SpanDataVariant& span_data) { std::lock_guard lock(thread_mutex); span_buffer_.push_back(span_data); @@ -70,9 +70,9 @@ namespace tracing } } - void CTraceProvider::writerThreadLoop() + void CTraceProvider::WriterThreadLoop() { - std::vector span_flusher; + std::vector span_flusher; while (true) { { @@ -89,15 +89,15 @@ namespace tracing } if (!span_flusher.empty()) { - writer_->writeBatchSpans(span_flusher); + writer_->WriteSpansToFile(span_flusher); span_flusher.clear(); } } } - void CTraceProvider::addTopicMetadata(const STopicMetadata& metadata) + void CTraceProvider::WriteMetadata(const STopicMetadata& metadata) { - writer_->writeTopicMetadata(metadata); + writer_->WriteMetadataToFile(metadata); } } // namespace tracing diff --git a/ecal/core/src/tracing/trace_provider.h b/ecal/core/src/tracing/trace_provider.h index 71df408bf1..ddbcd631fb 100644 --- a/ecal/core/src/tracing/trace_provider.h +++ b/ecal/core/src/tracing/trace_provider.h @@ -51,37 +51,18 @@ namespace tracing ~CTraceProvider(); - // Add span data to buffer - void bufferSpan(const SSpanData& span_data); + // Write span data to buffer (accepts any span type via variant) + void WriteSpan(const SpanDataVariant& span_data); - // Topic metadata — written directly to file (no buffering) - void addTopicMetadata(const STopicMetadata& metadata); + // metadata — written directly to file (no buffering) + void WriteMetadata(const STopicMetadata& metadata); - // Get buffered spans - std::vector getSpans() - { - std::lock_guard lock(thread_mutex); - return span_buffer_; - } - - // Synchronously flush all buffered spans to the writer - void forceFlush() - { - std::vector to_write; - { - std::lock_guard lock(thread_mutex); - to_write.swap(span_buffer_); - } - if (!to_write.empty()) - writer_->writeBatchSpans(to_write); - } - private: CTraceProvider(std::unique_ptr writer, size_t batch_size); - void writerThreadLoop(); + void WriterThreadLoop(); std::atomic batch_size_{kDefaultTracingBatchSize}; - std::vector span_buffer_; + std::vector span_buffer_; mutable std::mutex thread_mutex; std::condition_variable write_cv_; bool stop_thread_{false}; diff --git a/ecal/core/src/tracing/tracing.h b/ecal/core/src/tracing/tracing.h index 745ed291b5..9ce39e483d 100644 --- a/ecal/core/src/tracing/tracing.h +++ b/ecal/core/src/tracing/tracing.h @@ -26,6 +26,7 @@ #include #include +#include #include #include @@ -38,7 +39,7 @@ namespace tracing constexpr const char* kTracingVersion = "1.0.0"; // Default batch size for span buffering before flushing to backend (jsonl file) - constexpr size_t kDefaultTracingBatchSize = 500; + constexpr size_t kDefaultTracingBatchSize = 10; // Specifies the type of operation being traced enum operation_type @@ -95,20 +96,34 @@ namespace tracing topic_direction direction; // publisher or subscriber }; - // Unified span data structure. - // All span types share the same struct; - struct SSpanData + struct SPublisherSpanData { + operation_type op_type; uint64_t entity_id; - uint64_t topic_id{0}; // receive-only (0 for send spans) uint64_t process_id; size_t payload_size; long long clock; uint64_t layer; long long start_ns; // start timestamp in nanoseconds long long end_ns; // end timestamp in nanoseconds + }; + + struct SSubscriberSpanData + { operation_type op_type; + uint64_t entity_id; + uint64_t topic_id; + uint64_t process_id; + size_t payload_size; + long long clock; + uint64_t layer; + long long start_ns; // start timestamp in nanoseconds + long long end_ns; // end timestamp in nanoseconds }; + // Variant type for buffering heterogeneous span data. + // Extend this variant when new span types are added (e.g. client/server). + using SpanDataVariant = std::variant; + } // namespace tracing } // namespace eCAL diff --git a/ecal/core/src/tracing/tracing_writer.h b/ecal/core/src/tracing/tracing_writer.h index 09c518790a..0f09e431b6 100644 --- a/ecal/core/src/tracing/tracing_writer.h +++ b/ecal/core/src/tracing/tracing_writer.h @@ -35,11 +35,11 @@ namespace tracing public: virtual ~TracingWriter() = default; - // Write a batch of spans - virtual void writeBatchSpans(const std::vector& batch) = 0; + // Write a batch of spans (heterogeneous via variant) + virtual void WriteSpansToFile(const std::vector& batch) = 0; // Write a single topic metadata entry - virtual void writeTopicMetadata(const STopicMetadata& metadata) = 0; + virtual void WriteMetadataToFile(const STopicMetadata& metadata) = 0; }; } // namespace tracing diff --git a/ecal/core/src/tracing/tracing_writer_jsonl.cpp b/ecal/core/src/tracing/tracing_writer_jsonl.cpp index 97a4e855e9..9e8627aa87 100644 --- a/ecal/core/src/tracing/tracing_writer_jsonl.cpp +++ b/ecal/core/src/tracing/tracing_writer_jsonl.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include @@ -36,7 +37,7 @@ namespace eCAL namespace tracing { - static std::string getCurrentTimestamp() + static std::string GetCurrentTimestamp() { std::time_t now = std::time(nullptr); std::tm* tm_info = std::localtime(&now); @@ -46,10 +47,10 @@ namespace tracing } CTracingWriterJSONL::CTracingWriterJSONL() - : timestamp_(getCurrentTimestamp()) + : timestamp_(GetCurrentTimestamp()) {} - std::string CTracingWriterJSONL::getSpansFilePath() const + std::string CTracingWriterJSONL::GetSpansFilePath() const { const char* data_dir = std::getenv("ECAL_TRACING_DATA_DIR"); if (data_dir == nullptr) @@ -60,7 +61,7 @@ namespace tracing return std::string(data_dir) + "/ecal_spans_" + std::to_string(eCAL::Process::GetProcessID()) + "_" + timestamp_ + ".jsonl"; } - std::string CTracingWriterJSONL::getTopicMetadataFilePath() const + std::string CTracingWriterJSONL::GetTopicMetadataFilePath() const { const char* data_dir = std::getenv("ECAL_TRACING_DATA_DIR"); if (data_dir == nullptr) @@ -71,29 +72,46 @@ namespace tracing return std::string(data_dir) + "/ecal_topic_metadata_" + std::to_string(eCAL::Process::GetProcessID()) + "_" + timestamp_ + ".jsonl"; } - void CTracingWriterJSONL::writeBatchSpans(const std::vector& batch) + void CTracingWriterJSONL::WriteSpansToFile(const std::vector& batch) { try { std::lock_guard lock(spans_mutex_); - std::string filepath = getSpansFilePath(); + std::string filepath = GetSpansFilePath(); std::ofstream output_file(filepath, std::ios::app); if (output_file.is_open()) { - for (const auto& span : batch) + for (const auto& span_variant : batch) { json span_obj; - span_obj["entity_id"] = span.entity_id; - span_obj["topic_id"] = span.topic_id; - span_obj["process_id"] = span.process_id; - span_obj["payload_size"] = span.payload_size; - span_obj["clock"] = span.clock; - span_obj["layer"] = span.layer; - span_obj["start_ns"] = span.start_ns; - span_obj["end_ns"] = span.end_ns; - span_obj["op_type"] = span.op_type; - + std::visit([&span_obj](const auto& span) + { + using T = std::decay_t; + if constexpr (std::is_same_v) + { + span_obj["entity_id"] = span.entity_id; + span_obj["process_id"] = span.process_id; + span_obj["payload_size"] = span.payload_size; + span_obj["clock"] = span.clock; + span_obj["layer"] = span.layer; + span_obj["start_ns"] = span.start_ns; + span_obj["end_ns"] = span.end_ns; + span_obj["op_type"] = span.op_type; + } + else if constexpr (std::is_same_v) + { + span_obj["entity_id"] = span.entity_id; + span_obj["topic_id"] = span.topic_id; + span_obj["process_id"] = span.process_id; + span_obj["payload_size"] = span.payload_size; + span_obj["clock"] = span.clock; + span_obj["layer"] = span.layer; + span_obj["start_ns"] = span.start_ns; + span_obj["end_ns"] = span.end_ns; + span_obj["op_type"] = span.op_type; + } + }, span_variant); output_file << span_obj.dump() << "\n"; } output_file.close(); @@ -109,12 +127,12 @@ namespace tracing } } - void CTracingWriterJSONL::writeTopicMetadata(const STopicMetadata& metadata) + void CTracingWriterJSONL::WriteMetadataToFile(const STopicMetadata& metadata) { try { std::lock_guard lock(metadata_mutex_); - std::string filepath = getTopicMetadataFilePath(); + std::string filepath = GetTopicMetadataFilePath(); json obj; obj["tracing_version"] = metadata.tracing_version; diff --git a/ecal/core/src/tracing/tracing_writer_jsonl.h b/ecal/core/src/tracing/tracing_writer_jsonl.h index 37203257bb..a14269bba7 100644 --- a/ecal/core/src/tracing/tracing_writer_jsonl.h +++ b/ecal/core/src/tracing/tracing_writer_jsonl.h @@ -45,14 +45,14 @@ namespace tracing CTracingWriterJSONL& operator=(CTracingWriterJSONL&&) = delete; // Write a batch of spans to the JSONL spans file - void writeBatchSpans(const std::vector& batch) override; + void WriteSpansToFile(const std::vector& batch) override; // Write a single topic metadata entry to the JSONL metadata file - void writeTopicMetadata(const STopicMetadata& metadata) override; + void WriteMetadataToFile(const STopicMetadata& metadata) override; // File path accessors (path is fixed at construction time) - std::string getSpansFilePath() const; - std::string getTopicMetadataFilePath() const; + std::string GetSpansFilePath() const; + std::string GetTopicMetadataFilePath() const; private: mutable std::mutex spans_mutex_; From ae5b7266a96bc8147a5e05a5e4612ff78aca26be Mon Sep 17 00:00:00 2001 From: Ila Hadi-Assar Date: Tue, 21 Apr 2026 12:03:13 +0200 Subject: [PATCH 3/9] refactored tracing tests --- ecal/tests/cpp/tracing_test/CMakeLists.txt | 16 +- ecal/tests/cpp/tracing_test/TESTS.md | 135 -------- .../tracing_integration_test.cpp | 193 ------------ .../integrationtest/tracing_scale_test.cpp | 235 -------------- .../tracing_thread_safety_test.cpp | 231 -------------- .../systemtest/tracing_pubsub_stress_test.cpp | 262 ---------------- .../tracing_test/src/trace_provider_test.cpp | 127 ++++++++ .../tracing_test/src/tracing_test_helpers.h | 184 ++++++----- .../tracing_test/src/tracing_writer_test.cpp | 235 ++++++++++++++ .../src/unittest/tracing_edge_case_test.cpp | 192 ------------ .../src/unittest/tracing_layer_type_test.cpp | 97 ------ .../src/unittest/tracing_provider_test.cpp | 236 -------------- .../src/unittest/tracing_span_test.cpp | 206 ------------- .../src/unittest/tracing_types_test.cpp | 117 ------- .../src/unittest/tracing_writer_test.cpp | 291 ------------------ 15 files changed, 472 insertions(+), 2285 deletions(-) delete mode 100644 ecal/tests/cpp/tracing_test/TESTS.md delete mode 100644 ecal/tests/cpp/tracing_test/src/integrationtest/tracing_integration_test.cpp delete mode 100644 ecal/tests/cpp/tracing_test/src/integrationtest/tracing_scale_test.cpp delete mode 100644 ecal/tests/cpp/tracing_test/src/integrationtest/tracing_thread_safety_test.cpp delete mode 100644 ecal/tests/cpp/tracing_test/src/systemtest/tracing_pubsub_stress_test.cpp create mode 100644 ecal/tests/cpp/tracing_test/src/trace_provider_test.cpp create mode 100644 ecal/tests/cpp/tracing_test/src/tracing_writer_test.cpp delete mode 100644 ecal/tests/cpp/tracing_test/src/unittest/tracing_edge_case_test.cpp delete mode 100644 ecal/tests/cpp/tracing_test/src/unittest/tracing_layer_type_test.cpp delete mode 100644 ecal/tests/cpp/tracing_test/src/unittest/tracing_provider_test.cpp delete mode 100644 ecal/tests/cpp/tracing_test/src/unittest/tracing_span_test.cpp delete mode 100644 ecal/tests/cpp/tracing_test/src/unittest/tracing_types_test.cpp delete mode 100644 ecal/tests/cpp/tracing_test/src/unittest/tracing_writer_test.cpp diff --git a/ecal/tests/cpp/tracing_test/CMakeLists.txt b/ecal/tests/cpp/tracing_test/CMakeLists.txt index da348991be..4953253852 100644 --- a/ecal/tests/cpp/tracing_test/CMakeLists.txt +++ b/ecal/tests/cpp/tracing_test/CMakeLists.txt @@ -24,19 +24,8 @@ find_package(nlohmann_json REQUIRED) set(tracing_test_src src/tracing_test_helpers.h - # Unit tests — individual classes in isolation - src/unittest/tracing_types_test.cpp - src/unittest/tracing_layer_type_test.cpp - src/unittest/tracing_provider_test.cpp - src/unittest/tracing_span_test.cpp - src/unittest/tracing_writer_test.cpp - src/unittest/tracing_edge_case_test.cpp - # Integration tests — component interactions and concurrency - src/integrationtest/tracing_integration_test.cpp - src/integrationtest/tracing_thread_safety_test.cpp - src/integrationtest/tracing_scale_test.cpp - # System tests — full eCAL runtime, real pub/sub - src/systemtest/tracing_pubsub_stress_test.cpp + src/trace_provider_test.cpp + src/tracing_writer_test.cpp ) ecal_add_gtest(${PROJECT_NAME} ${tracing_test_src}) @@ -51,6 +40,7 @@ target_include_directories(${PROJECT_NAME} PRIVATE target_link_libraries(${PROJECT_NAME} PRIVATE ecal_core_private + eCAL::ecal-utils Threads::Threads nlohmann_json::nlohmann_json ) diff --git a/ecal/tests/cpp/tracing_test/TESTS.md b/ecal/tests/cpp/tracing_test/TESTS.md deleted file mode 100644 index b39e6d3911..0000000000 --- a/ecal/tests/cpp/tracing_test/TESTS.md +++ /dev/null @@ -1,135 +0,0 @@ -# Tracing Test Summary - -**File:** `ecal/tests/cpp/tracing_test/src/tracing_test.cpp` -**Total:** 69 tests across 10 test suites - ---- - -## TracingTypesTest (8 tests) - -| Test | What it validates | -|---|---| -| `TracingVersionConstant` | `kTracingVersion` equals `"1.0.0"` | -| `DefaultTracingBatchSize` | `kDefaultTracingBatchSize` equals `10` | -| `SSpanDataDefaultInitialization` | All `SSpanData` fields are zero after value-initialization | -| `SSpanDataAssignment` | All `SSpanData` fields can be assigned and read back correctly | -| `STopicMetadataDefaultVersion` | `STopicMetadata::tracing_version` defaults to `kTracingVersion` | -| `STopicMetadataAssignment` | All `STopicMetadata` fields can be assigned and read back correctly | -| `OperationTypeEnum` | `send=0`, `receive=1`, `callback_execution=2` | -| `TopicDirectionEnum` | `publisher=0`, `subscriber=1` | - -## TracingLayerTypeTest (8 tests) - -| Test | What it validates | -|---|---| -| `LayerTypeValues` | Enum values: `none=0`, `shm=1`, `udp=2`, `tcp=4`, combined values `3,5,6,7` | -| `LayerTypeBitwise` | Layer types combine correctly with bitwise OR | -| `ToTracingLayerTypeSHM` | `toTracingLayerType(tl_ecal_shm)` → `tl_trace_shm` | -| `ToTracingLayerTypeUDP` | `toTracingLayerType(tl_ecal_udp)` → `tl_trace_udp` | -| `ToTracingLayerTypeTCP` | `toTracingLayerType(tl_ecal_tcp)` → `tl_trace_tcp` | -| `ToTracingLayerTypeAll` | `toTracingLayerType(tl_all)` → `tl_trace_all` | -| `ToTracingLayerTypeUnknown` | Invalid `eTLayerType(999)` → `tl_trace_none` | -| `ToTracingLayerTypeNone` | `toTracingLayerType(tl_none)` → `tl_trace_none` | - -## TracingProviderTest (11 tests) - -| Test | What it validates | -|---|---| -| `SingletonInstance` | `getInstance()` returns the same address on repeated calls | -| `DefaultBatchSize` | Buffering 9 spans doesn't trigger auto-flush (batch size = 10) | -| `SetBatchSizeAffectsAutoFlush` | Changing batch size to 3 triggers flush on the 3rd span | -| `BufferSpan` | A single span is buffered with correct field values | -| `BufferMultipleSpans` | 5 spans are buffered in order with correct entity IDs | -| `FlushSpans` | `flushSpans()` empties the buffer | -| `FlushEmptyBuffer` | Flushing an empty buffer doesn't throw | -| `AutoFlushOnBatchSize` | Buffer empties automatically when batch size is reached | -| `AutoFlushMultipleBatches` | Multiple auto-flush cycles work correctly, residual spans stay buffered | -| `AddTopicMetadata` | Metadata is written to JSONL file with all fields (publisher direction) | -| `AddTopicMetadataSubscriber` | Metadata with subscriber direction is written correctly | - -## SpanTest (7 tests) - -| Test | What it validates | -|---|---| -| `SendSpanConstruction` | RAII `CSpan` (send) buffers correct entity_id, process_id, payload_size, clock, layer, op_type; topic_id=0 for send | -| `SendSpanTimestamps` | `start_ns < end_ns`, both > 0, duration ≥ 0.5ms (with 1ms sleep) | -| `ReceiveSpanConstruction` | RAII `CSpan` (receive) buffers correct entity_id, topic_id, process_id, payload_size, clock, layer, op_type | -| `ReceiveSpanTimestamps` | Receive span has valid start/end timestamps with `start < end` | -| `CallbackExecutionSpan` | `CSpan` with `callback_execution` op_type records correctly | -| `MultipleOperationTypes` | Two spans with different op_types are buffered in order | -| `MultipleLayerTypes` | Four spans with SHM, UDP, TCP, SHM+UDP layers are buffered correctly | - -## TracingWriterTest (11 tests) - -| Test | What it validates | -|---|---| -| `WriterConstruction` | `CTracingWriter` constructs without throwing | -| `WriteBatchSpansVerifyContent` | 3-span batch written to JSONL; all 9 fields verified per line | -| `WriteBatchSpansReceive` | Receive span written with correct op_type, topic_id, layer | -| `WriteBatchSpansAppends` | Two sequential writes append (2 lines in file, not 1) | -| `WriteEmptyBatch` | Empty batch produces no output (file empty or absent) | -| `WriteTopicMetadataVerifyContent` | Metadata JSONL contains all 8 fields (publisher direction) | -| `WriteTopicMetadataSubscriber` | Subscriber direction is serialized as `"subscriber"` | -| `WriteMultipleMetadataAppends` | 3 metadata writes append correctly with alternating directions | -| `EmptyStringMetadataFields` | Empty strings and zero IDs are written as `""` / `0` | -| `WriteBatchSpansCallbackExecution` | `callback_execution` op_type and TCP layer written correctly | -| `WriteBatchSpansAllLayerTypes` | All 8 layer type values (none through all) written and read back | - -## TracingIntegrationTest (4 tests) - -| Test | What it validates | -|---|---| -| `SpanBufferingAndFlushing` | CSpan → buffer → getSpans → flushSpans → empty buffer | -| `MixedSendAndReceiveSpans` | Send and receive spans coexist in buffer with correct fields | -| `CompleteTracingFlow` | Full flow: addTopicMetadata → CSpan creation → buffer verify → flush → JSONL file verify | -| `AutoFlushWritesToFile` | Auto-flush (batch size 3) writes spans to JSONL file and empties buffer | - -## ThreadSafetyTest (5 tests) - -| Test | What it validates | -|---|---| -| `ConcurrentSpanBuffering` | 4 threads × 25 CSpan RAII objects → exactly 100 spans buffered (no data loss) | -| `ConcurrentMetadataWriting` | 4 threads × 5 metadata writes → 20 valid JSONL lines (no corruption) | -| `ConcurrentBufferingWithAutoFlush` | 4 threads × 20 spans with batch size 5 → all 80 spans written to file | -| `ConcurrentGetSpans` | 3 reader threads + 1 writer thread → no crashes, correct final count (100 spans) | -| `ConcurrentWriteBatchSpansIntegrity` | **Regression:** 8 threads × 20 batches × 5 spans → 800 valid JSONL lines (tests `spans_mutex_` fix) | - -## EdgeCaseTest (7 tests) - -| Test | What it validates | -|---|---| -| `AllLayerTypesCombined` | CSpan with `tl_trace_all` (7) stores correct layer value | -| `NoneLayerType` | CSpan with `tl_trace_none` (0) and payload 0 stores correctly | -| `EmptyStringMetadata` | Empty-string metadata fields are written without error | -| `BatchSizeOfOne` | Batch size = 1 triggers immediate auto-flush on every span | -| `RapidFlushCycles` | 10 rapid buffer-then-flush cycles work correctly | -| `MaxEntityAndProcessIds` | `UINT64_MAX` for entity_id, process_id, topic_id written and read back correctly | -| `ReceiveSpanViaProviderFlush` | Full path: CSpan receive → buffer → verify fields → flush → verify JSONL output | - -## ScaleTest (5 tests) - -| Test | What it validates | -|---|---| -| `HighFanoutProducers` | 500 threads × 200 spans via `bufferSpan()` → 100,000 spans, zero data loss | -| `HighFanoutViaSpanRAII` | 200 threads × 100 CSpan RAII objects → 20,000 spans written to file | -| `MixedPubSubMetadataAndSpans` | 200 threads each registering metadata + 50 spans → 200 metadata + 10,000 spans | -| `BatchSizeSweep` | Same load tested at batch sizes 1, 5, 10, 50, 100, 500 — correctness is batch-size-independent | -| `SustainedBurst` | 100 threads × 500 spans with batch size 25 — stresses mutex + file I/O under sustained load | - -## PubSubStressTest (3 tests) - -| Test | What it validates | -|---|---| -| `TwoPubTwoSubHighFrequency` | 2 real publishers + 2 subscribers, 500 messages each (concurrent) → exactly 1,000 send spans; valid timestamps on all spans | -| `MultiTopicHighFrequency` | 3 publishers on 3 topics, 300 messages each (concurrent) → 900 send spans; all use SHM layer | -| `HighFrequencyWithSlowCallback` | 1 pub + 1 sub, 200 messages, 50µs callback delay → 200 send spans; callback_execution spans have duration ≥ 40µs | - ---- - -## Bug Fixes Validated by These Tests - -| Bug | Fix | Regression test | -|---|---|---| -| Data race in `bufferSpan()` — `span_buffer_.size()` read outside lock | Moved size check inside lock, use local `should_flush` flag | `ConcurrentBufferingWithAutoFlush`, `HighFanoutProducers` | -| Thread-unsafe `getSpans()` — returned buffer without holding mutex | Added `lock_guard` before returning `span_buffer_` | `ConcurrentGetSpans` | -| No mutex on `writeBatchSpans()` — concurrent flushes produced corrupted JSONL | Added `spans_mutex_` to `CTracingWriter` | `ConcurrentWriteBatchSpansIntegrity` | diff --git a/ecal/tests/cpp/tracing_test/src/integrationtest/tracing_integration_test.cpp b/ecal/tests/cpp/tracing_test/src/integrationtest/tracing_integration_test.cpp deleted file mode 100644 index e96168eadb..0000000000 --- a/ecal/tests/cpp/tracing_test/src/integrationtest/tracing_integration_test.cpp +++ /dev/null @@ -1,193 +0,0 @@ -/* ========================= eCAL LICENSE ================================= - * - * Copyright (C) 2016 - 2025 Continental Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * ========================= eCAL LICENSE ================================= -*/ - -#include "tracing_test_helpers.h" - -using namespace eCAL::tracing; - -// ============ Integration Tests ============ - -class TracingIntegrationTest : public ::testing::Test -{ -protected: - void SetUp() override - { - setenv("ECAL_TRACING_DATA_DIR", tempDir().c_str(), 1); - if (auto provider = eCAL::g_trace_provider(); provider) - { - triggerFlush(provider.get()); - } - removeFile(spansFilePath()); - removeFile(metadataFilePath()); - } - - void TearDown() override - { - if (auto provider = eCAL::g_trace_provider(); provider) - { - triggerFlush(provider.get()); - } - removeFile(spansFilePath()); - removeFile(metadataFilePath()); - } -}; - -TEST_F(TracingIntegrationTest, SpanBufferingAndFlushing) -{ - auto provider = eCAL::g_trace_provider(); - if (!provider) return; - - eCAL::STopicId topic_id; - topic_id.topic_id.entity_id = 1; - topic_id.topic_id.process_id = 2; - - for (int i = 0; i < 3; ++i) - { - CSpan span(topic_id, i, tl_trace_shm, 256 * (i + 1), send); - } - - auto spans = provider->getSpans(); - ASSERT_EQ(spans.size(), 3); - - for (int i = 0; i < 3; ++i) - { - EXPECT_EQ(spans[i].clock, i); - EXPECT_EQ(spans[i].payload_size, 256 * (i + 1)); - } - - triggerFlush(provider.get()); - EXPECT_EQ(provider->getSpans().size(), 0); -} - -TEST_F(TracingIntegrationTest, MixedSendAndReceiveSpans) -{ - auto provider = eCAL::g_trace_provider(); - if (!provider) return; - - eCAL::STopicId send_topic_id; - send_topic_id.topic_id.entity_id = 1; - send_topic_id.topic_id.process_id = 2; - - eCAL::Payload::TopicInfo recv_topic_info; - recv_topic_info.topic_id = 10; - recv_topic_info.process_id = 20; - - { - CSpan send_span(send_topic_id, 100, tl_trace_shm, 256, send); - } - { - CSpan recv_span(30, recv_topic_info, 200, tl_trace_udp, 512, receive); - } - - auto spans = provider->getSpans(); - ASSERT_EQ(spans.size(), 2); - - EXPECT_EQ(spans[0].op_type, send); - EXPECT_EQ(spans[0].topic_id, 0); - EXPECT_EQ(spans[0].entity_id, 1); - EXPECT_EQ(spans[0].process_id, 2); - - EXPECT_EQ(spans[1].op_type, receive); - EXPECT_EQ(spans[1].topic_id, 10); - EXPECT_EQ(spans[1].entity_id, 30); - EXPECT_EQ(spans[1].process_id, 20); -} - -TEST_F(TracingIntegrationTest, CompleteTracingFlow) -{ - auto provider = eCAL::g_trace_provider(); - if (!provider) return; - - STopicMetadata metadata; - metadata.entity_id = 111; - metadata.process_id = eCAL::Process::GetProcessID(); - metadata.host_name = "integration-host"; - metadata.topic_name = "integration_test_topic"; - metadata.encoding = "protobuf"; - metadata.type_name = "IntegrationData"; - metadata.direction = publisher; - - provider->addTopicMetadata(metadata); - - eCAL::STopicId topic_id; - topic_id.topic_id.entity_id = 111; - topic_id.topic_id.process_id = eCAL::Process::GetProcessID(); - - for (int i = 0; i < 5; ++i) - { - CSpan span(topic_id, i * 10, tl_trace_shm, 256 * (i + 1), send); - } - - auto spans = provider->getSpans(); - ASSERT_EQ(spans.size(), 5); - - for (int i = 0; i < 5; ++i) - { - EXPECT_EQ(spans[i].entity_id, 111); - EXPECT_EQ(spans[i].process_id, static_cast(eCAL::Process::GetProcessID())); - EXPECT_EQ(spans[i].payload_size, 256 * (i + 1)); - EXPECT_EQ(spans[i].clock, i * 10); - } - - triggerFlush(provider.get()); - EXPECT_EQ(provider->getSpans().size(), 0); - - auto metadata_lines = readJsonLines(metadataFilePath()); - ASSERT_GE(metadata_lines.size(), 1); - EXPECT_EQ(metadata_lines.back()["topic_name"].get(), "integration_test_topic"); - EXPECT_EQ(metadata_lines.back()["direction"].get(), "publisher"); - - auto span_lines = readJsonLines(spansFilePath()); - ASSERT_GE(span_lines.size(), 5); - - size_t matching = 0; - for (const auto& line : span_lines) - { - if (line["entity_id"].get() == 111) - ++matching; - } - EXPECT_EQ(matching, 5u); -} - -TEST_F(TracingIntegrationTest, AutoFlushWritesToFile) -{ - auto provider = eCAL::g_trace_provider(); - if (!provider) return; - - eCAL::STopicId topic_id; - topic_id.topic_id.entity_id = 42; - topic_id.topic_id.process_id = 43; - - for (int i = 0; i < 3; ++i) - { - CSpan span(topic_id, i, tl_trace_shm, 100, send); - } - - provider->forceFlush(); - EXPECT_EQ(provider->getSpans().size(), 0); - - auto lines = readJsonLines(spansFilePath()); - ASSERT_GE(lines.size(), 3); - for (int i = 0; i < 3; ++i) - { - size_t idx = lines.size() - 3 + i; - EXPECT_EQ(lines[idx]["entity_id"].get(), 42); - EXPECT_EQ(lines[idx]["clock"].get(), i); - } -} diff --git a/ecal/tests/cpp/tracing_test/src/integrationtest/tracing_scale_test.cpp b/ecal/tests/cpp/tracing_test/src/integrationtest/tracing_scale_test.cpp deleted file mode 100644 index 3a5a1293f1..0000000000 --- a/ecal/tests/cpp/tracing_test/src/integrationtest/tracing_scale_test.cpp +++ /dev/null @@ -1,235 +0,0 @@ -/* ========================= eCAL LICENSE ================================= - * - * Copyright (C) 2016 - 2025 Continental Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * ========================= eCAL LICENSE ================================= -*/ - -#include "tracing_test_helpers.h" - -using namespace eCAL::tracing; - -// ============ Scale / Stress Tests ============ - -class ScaleTest : public ::testing::Test -{ -protected: - void SetUp() override - { - setenv("ECAL_TRACING_DATA_DIR", tempDir().c_str(), 1); - if (auto provider = eCAL::g_trace_provider(); provider) - { - triggerFlush(provider.get()); - } - removeFile(spansFilePath()); - removeFile(metadataFilePath()); - } - - void TearDown() override - { - if (auto provider = eCAL::g_trace_provider(); provider) - { - triggerFlush(provider.get()); - } - removeFile(spansFilePath()); - removeFile(metadataFilePath()); - } -}; - -// Simulate hundreds of publishers/subscribers buffering spans concurrently. -// Validates that no spans are lost under high contention. -TEST_F(ScaleTest, HighFanoutProducers) -{ - auto provider = eCAL::g_trace_provider(); - if (!provider) return; - - const int num_producers = 500; - const int spans_per_producer = 200; - std::vector threads; - - for (int p = 0; p < num_producers; ++p) - { - threads.emplace_back([&provider, p, spans_per_producer]() - { - for (int i = 0; i < spans_per_producer; ++i) - { - SSpanData span{}; - span.entity_id = static_cast(p); - span.clock = i; - span.op_type = (p % 2 == 0) ? send : receive; - provider->bufferSpan(span); - } - }); - } - - for (auto& t : threads) t.join(); - - triggerFlush(provider.get()); - EXPECT_EQ(provider->getSpans().size(), 0); - - auto lines = readJsonLines(spansFilePath()); - EXPECT_GE(lines.size(), static_cast(num_producers) * spans_per_producer); -} - -// Same high fan-out but using CSpan RAII objects (the real API path). -TEST_F(ScaleTest, HighFanoutViaSpanRAII) -{ - auto provider = eCAL::g_trace_provider(); - if (!provider) return; - - const int num_publishers = 200; - const int messages_per_pub = 100; - std::vector threads; - - for (int p = 0; p < num_publishers; ++p) - { - threads.emplace_back([p, messages_per_pub]() - { - eCAL::STopicId topic_id; - topic_id.topic_id.entity_id = static_cast(p); - topic_id.topic_id.process_id = static_cast(eCAL::Process::GetProcessID()); - - for (int i = 0; i < messages_per_pub; ++i) - { - CSpan span(topic_id, i, tl_trace_shm, 256, send); - } - }); - } - - for (auto& t : threads) t.join(); - - triggerFlush(provider.get()); - - auto lines = readJsonLines(spansFilePath()); - EXPECT_GE(lines.size(), static_cast(num_publishers) * messages_per_pub); -} - -// Simulate mixed publisher and subscriber registration at scale. -TEST_F(ScaleTest, MixedPubSubMetadataAndSpans) -{ - auto provider = eCAL::g_trace_provider(); - if (!provider) return; - - const int num_endpoints = 200; - const int spans_per_endpoint = 50; - std::vector threads; - - for (int e = 0; e < num_endpoints; ++e) - { - threads.emplace_back([&provider, e, spans_per_endpoint]() - { - STopicMetadata metadata; - metadata.entity_id = static_cast(e); - metadata.process_id = eCAL::Process::GetProcessID(); - metadata.host_name = "scale-host"; - metadata.topic_name = "topic_" + std::to_string(e); - metadata.encoding = "protobuf"; - metadata.type_name = "ScaleMsg"; - metadata.direction = (e % 2 == 0) ? publisher : subscriber; - provider->addTopicMetadata(metadata); - - for (int i = 0; i < spans_per_endpoint; ++i) - { - SSpanData span{}; - span.entity_id = static_cast(e); - span.clock = i; - span.op_type = (e % 2 == 0) ? send : receive; - provider->bufferSpan(span); - } - }); - } - - for (auto& t : threads) t.join(); - - triggerFlush(provider.get()); - - auto meta_lines = readJsonLines(metadataFilePath()); - EXPECT_EQ(meta_lines.size(), static_cast(num_endpoints)); - - auto span_lines = readJsonLines(spansFilePath()); - EXPECT_GE(span_lines.size(), static_cast(num_endpoints) * spans_per_endpoint); -} - -// Vary batch sizes under constant load to verify correctness is independent of batch tuning. -TEST_F(ScaleTest, HighThroughputNoDataLoss) -{ - auto provider = eCAL::g_trace_provider(); - if (!provider) return; - - const int num_producers = 50; - const int spans_per_producer = 100; - const size_t expected_total = static_cast(num_producers) * spans_per_producer; - - removeFile(spansFilePath()); - - std::vector threads; - for (int p = 0; p < num_producers; ++p) - { - threads.emplace_back([&provider, p, spans_per_producer]() - { - for (int i = 0; i < spans_per_producer; ++i) - { - SSpanData span{}; - span.entity_id = static_cast(p); - span.clock = i; - span.op_type = send; - provider->bufferSpan(span); - } - }); - } - - for (auto& t : threads) t.join(); - - triggerFlush(provider.get()); - - auto lines = readJsonLines(spansFilePath()); - EXPECT_GE(lines.size(), expected_total); -} - -// Sustained burst: rapidly produce large volumes to stress the mutex + file I/O path. -TEST_F(ScaleTest, SustainedBurst) -{ - auto provider = eCAL::g_trace_provider(); - if (!provider) return; - - const int num_threads = 100; - const int spans_per_thread = 500; - std::vector threads; - - for (int t = 0; t < num_threads; ++t) - { - threads.emplace_back([&provider, t, spans_per_thread]() - { - for (int i = 0; i < spans_per_thread; ++i) - { - SSpanData span{}; - span.entity_id = static_cast(t); - span.topic_id = static_cast(t % 50); - span.process_id = static_cast(eCAL::Process::GetProcessID()); - span.clock = i; - span.layer = tl_trace_shm; - span.op_type = send; - provider->bufferSpan(span); - } - }); - } - - for (auto& t : threads) t.join(); - - triggerFlush(provider.get()); - - auto lines = readJsonLines(spansFilePath()); - EXPECT_GE(lines.size(), static_cast(num_threads) * spans_per_thread); -} diff --git a/ecal/tests/cpp/tracing_test/src/integrationtest/tracing_thread_safety_test.cpp b/ecal/tests/cpp/tracing_test/src/integrationtest/tracing_thread_safety_test.cpp deleted file mode 100644 index d80849b44e..0000000000 --- a/ecal/tests/cpp/tracing_test/src/integrationtest/tracing_thread_safety_test.cpp +++ /dev/null @@ -1,231 +0,0 @@ -/* ========================= eCAL LICENSE ================================= - * - * Copyright (C) 2016 - 2025 Continental Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * ========================= eCAL LICENSE ================================= -*/ - -#include "tracing_test_helpers.h" - -using namespace eCAL::tracing; - -// ============ Thread Safety Tests ============ - -class ThreadSafetyTest : public ::testing::Test -{ -protected: - std::unique_ptr writer_; - - void SetUp() override - { - setenv("ECAL_TRACING_DATA_DIR", tempDir().c_str(), 1); - if (auto provider = eCAL::g_trace_provider(); provider) - { - triggerFlush(provider.get()); - } - writer_ = std::make_unique(); - removeFile(spansFilePath()); - removeFile(writer_->getSpansFilePath()); - removeFile(writer_->getTopicMetadataFilePath()); - } - - void TearDown() override - { - if (auto provider = eCAL::g_trace_provider(); provider) - { - triggerFlush(provider.get()); - } - removeFile(spansFilePath()); - removeFile(writer_->getSpansFilePath()); - removeFile(writer_->getTopicMetadataFilePath()); - } -}; - -TEST_F(ThreadSafetyTest, ConcurrentSpanBuffering) -{ - auto provider = eCAL::g_trace_provider(); - if (!provider) return; - - const int num_threads = 4; - const int spans_per_thread = 25; - std::vector threads; - - for (int t = 0; t < num_threads; ++t) - { - threads.emplace_back([&provider, t, spans_per_thread]() - { - eCAL::STopicId topic_id; - topic_id.topic_id.entity_id = t; - topic_id.topic_id.process_id = eCAL::Process::GetProcessID(); - - for (int i = 0; i < spans_per_thread; ++i) - { - CSpan span(topic_id, i, tl_trace_shm, 256, send); - } - }); - } - - for (auto& t : threads) t.join(); - - auto spans = provider->getSpans(); - EXPECT_EQ(spans.size(), num_threads * spans_per_thread); -} - -TEST_F(ThreadSafetyTest, ConcurrentMetadataWriting) -{ - const int num_threads = 4; - const int metadata_per_thread = 5; - std::vector threads; - - for (int t = 0; t < num_threads; ++t) - { - threads.emplace_back([this, t, metadata_per_thread]() - { - for (int i = 0; i < metadata_per_thread; ++i) - { - STopicMetadata metadata; - metadata.entity_id = t * 100 + i; - metadata.process_id = eCAL::Process::GetProcessID(); - metadata.host_name = "host-" + std::to_string(t); - metadata.topic_name = "topic_" + std::to_string(t) + "_" + std::to_string(i); - metadata.encoding = "protobuf"; - metadata.type_name = "TestMessage"; - metadata.direction = publisher; - - EXPECT_NO_THROW(writer_->writeTopicMetadata(metadata)); - } - }); - } - - for (auto& t : threads) t.join(); - - auto lines = readJsonLines(writer_->getTopicMetadataFilePath()); - EXPECT_EQ(lines.size(), num_threads * metadata_per_thread); -} - -TEST_F(ThreadSafetyTest, ConcurrentBufferingWithForceFlush) -{ - auto provider = eCAL::g_trace_provider(); - if (!provider) return; - - const int num_threads = 4; - const int spans_per_thread = 20; - std::vector threads; - - for (int t = 0; t < num_threads; ++t) - { - threads.emplace_back([&provider, t, spans_per_thread]() - { - for (int i = 0; i < spans_per_thread; ++i) - { - SSpanData span{}; - span.entity_id = t * 1000 + i; - span.process_id = eCAL::Process::GetProcessID(); - span.op_type = send; - provider->bufferSpan(span); - } - }); - } - - for (auto& t : threads) t.join(); - - triggerFlush(provider.get()); - - EXPECT_EQ(provider->getSpans().size(), 0); - - auto lines = readJsonLines(spansFilePath()); - EXPECT_GE(lines.size(), static_cast(num_threads * spans_per_thread)); -} - -TEST_F(ThreadSafetyTest, ConcurrentGetSpans) -{ - auto provider = eCAL::g_trace_provider(); - if (!provider) return; - - for (int i = 0; i < 50; ++i) - { - SSpanData span{}; - span.entity_id = i; - span.op_type = send; - provider->bufferSpan(span); - } - - const int num_readers = 3; - std::vector threads; - - for (int t = 0; t < num_readers; ++t) - { - threads.emplace_back([&provider]() - { - for (int i = 0; i < 100; ++i) - { - auto spans = provider->getSpans(); - EXPECT_GE(spans.size(), 0u); - } - }); - } - - threads.emplace_back([&provider]() - { - for (int i = 0; i < 50; ++i) - { - SSpanData span{}; - span.entity_id = 1000 + i; - span.op_type = send; - provider->bufferSpan(span); - } - }); - - for (auto& t : threads) t.join(); - - auto spans = provider->getSpans(); - EXPECT_EQ(spans.size(), 100u); -} - -TEST_F(ThreadSafetyTest, ConcurrentWriteBatchSpansIntegrity) -{ - // Regression: writeBatchSpans previously had no mutex, so concurrent - // flushes interleaved writes producing corrupted JSONL lines. - const int num_threads = 8; - const int batches_per_thread = 20; - const int spans_per_batch = 5; - std::vector threads; - - for (int t = 0; t < num_threads; ++t) - { - threads.emplace_back([this, t, batches_per_thread, spans_per_batch]() - { - for (int b = 0; b < batches_per_thread; ++b) - { - std::vector batch; - for (int s = 0; s < spans_per_batch; ++s) - { - SSpanData span{}; - span.entity_id = static_cast(t); - span.clock = b * spans_per_batch + s; - span.op_type = send; - batch.push_back(span); - } - writer_->writeBatchSpans(batch); - } - }); - } - - for (auto& t : threads) t.join(); - - auto lines = readJsonLines(writer_->getSpansFilePath()); - EXPECT_EQ(lines.size(), - static_cast(num_threads) * batches_per_thread * spans_per_batch); -} diff --git a/ecal/tests/cpp/tracing_test/src/systemtest/tracing_pubsub_stress_test.cpp b/ecal/tests/cpp/tracing_test/src/systemtest/tracing_pubsub_stress_test.cpp deleted file mode 100644 index 4cefb33761..0000000000 --- a/ecal/tests/cpp/tracing_test/src/systemtest/tracing_pubsub_stress_test.cpp +++ /dev/null @@ -1,262 +0,0 @@ -/* ========================= eCAL LICENSE ================================= - * - * Copyright (C) 2016 - 2025 Continental Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * ========================= eCAL LICENSE ================================= -*/ - -#include "tracing_test_helpers.h" - -using namespace eCAL::tracing; - -// ============ Real Pub/Sub Stress Tests ============ -// -// These tests use actual eCAL publishers and subscribers exchanging data at -// high frequency. They validate that the tracing subsystem generates the -// correct number/type of spans under realistic load. - -class PubSubStressTest : public ::testing::Test -{ -protected: - void SetUp() override - { - eCAL::Configuration config; - config.registration.registration_refresh = 100; - config.registration.registration_timeout = 200; - - config.publisher.layer.shm.enable = true; - config.publisher.layer.udp.enable = false; - config.publisher.layer.tcp.enable = false; - config.subscriber.layer.shm.enable = true; - config.subscriber.layer.udp.enable = false; - config.subscriber.layer.tcp.enable = false; - - eCAL::Initialize(config, "tracing_pubsub_stress"); - - setenv("ECAL_TRACING_DATA_DIR", tempDir().c_str(), 1); - if (auto provider = eCAL::g_trace_provider(); provider) - { - triggerFlush(provider.get()); - } - removeFile(spansFilePath()); - removeFile(metadataFilePath()); - } - - void TearDown() override - { - if (auto provider = eCAL::g_trace_provider(); provider) - { - triggerFlush(provider.get()); - } - removeFile(spansFilePath()); - removeFile(metadataFilePath()); - - eCAL::Finalize(); - } - - static void waitForMatch(const std::vector& subs, int timeout_ms = 2000) - { - auto deadline = std::chrono::steady_clock::now() - + std::chrono::milliseconds(timeout_ms); - while (std::chrono::steady_clock::now() < deadline) - { - bool all_matched = true; - for (auto* s : subs) - { - if (s->GetPublisherCount() == 0) - { - all_matched = false; - break; - } - } - if (all_matched) return; - eCAL::Process::SleepMS(50); - } - } -}; - -// 2 publishers, 2 subscribers on the same topic, high-frequency send. -TEST_F(PubSubStressTest, TwoPubTwoSubHighFrequency) -{ - const std::string topic = "stress_topic_a"; - const int num_messages = 500; - const std::string payload(128, 'X'); - - eCAL::CPublisher pub1(topic); - eCAL::CPublisher pub2(topic); - eCAL::CSubscriber sub1(topic); - eCAL::CSubscriber sub2(topic); - - std::atomic recv_count1{0}; - std::atomic recv_count2{0}; - - sub1.SetReceiveCallback([&recv_count1](auto&&...) { recv_count1.fetch_add(1, std::memory_order_relaxed); }); - sub2.SetReceiveCallback([&recv_count2](auto&&...) { recv_count2.fetch_add(1, std::memory_order_relaxed); }); - - std::vector subs = {&sub1, &sub2}; - waitForMatch(subs); - - std::thread t1([&]() { for (int i = 0; i < num_messages; ++i) pub1.Send(payload); }); - std::thread t2([&]() { for (int i = 0; i < num_messages; ++i) pub2.Send(payload); }); - t1.join(); - t2.join(); - - eCAL::Process::SleepMS(500); - - if (auto provider = eCAL::g_trace_provider(); provider) - triggerFlush(provider.get()); - - auto lines = readJsonLines(spansFilePath()); - - size_t send_spans = 0, receive_spans = 0, callback_spans = 0; - for (const auto& line : lines) - { - if (line["entity_id"].get() == 0) continue; - int op = line["op_type"].get(); - if (op == send) ++send_spans; - else if (op == receive) ++receive_spans; - else if (op == callback_execution) ++callback_spans; - } - - EXPECT_EQ(send_spans, 2u * num_messages); - EXPECT_GE(receive_spans, 0u); - EXPECT_GE(callback_spans, 0u); - - for (const auto& line : lines) - { - if (line["entity_id"].get() != 0) - { - EXPECT_GT(line["start_ns"].get(), 0); - EXPECT_GT(line["end_ns"].get(), 0); - } - } -} - -// 3 publishers on different topics, 1 subscriber per topic. -TEST_F(PubSubStressTest, MultiTopicHighFrequency) -{ - const int num_topics = 3; - const int num_messages = 300; - const std::string payload(64, 'Y'); - - std::vector> pubs; - std::vector> subs; - std::vector> recv_counts(num_topics); - for (auto& c : recv_counts) c.store(0); - - for (int t = 0; t < num_topics; ++t) - { - std::string topic = "stress_multi_" + std::to_string(t); - pubs.push_back(std::make_unique(topic)); - subs.push_back(std::make_unique(topic)); - subs.back()->SetReceiveCallback([&recv_counts, t](auto&&...) { - recv_counts[t].fetch_add(1, std::memory_order_relaxed); - }); - } - - std::vector sub_ptrs; - for (auto& s : subs) sub_ptrs.push_back(s.get()); - waitForMatch(sub_ptrs); - - std::vector threads; - for (int t = 0; t < num_topics; ++t) - { - threads.emplace_back([&pubs, &payload, t, num_messages]() { - for (int i = 0; i < num_messages; ++i) - pubs[t]->Send(payload); - }); - } - for (auto& th : threads) th.join(); - - eCAL::Process::SleepMS(500); - - if (auto provider = eCAL::g_trace_provider(); provider) - triggerFlush(provider.get()); - - auto lines = readJsonLines(spansFilePath()); - - size_t send_spans = 0; - for (const auto& line : lines) - { - if (line["entity_id"].get() == 0) continue; - if (line["op_type"].get() == send) - ++send_spans; - } - - EXPECT_EQ(send_spans, static_cast(num_topics) * num_messages); - - for (const auto& line : lines) - { - if (line["entity_id"].get() == 0) continue; - if (line["op_type"].get() == send) - { - EXPECT_EQ(line["layer"].get(), tl_trace_shm); - } - } -} - -// Sustained high-frequency publishing with subscriber callback overhead. -TEST_F(PubSubStressTest, HighFrequencyWithSlowCallback) -{ - const std::string topic = "stress_slow_cb"; - const int num_messages = 200; - const std::string payload(256, 'Z'); - - eCAL::CPublisher pub(topic); - eCAL::CSubscriber sub(topic); - - std::atomic recv_count{0}; - - sub.SetReceiveCallback([&recv_count](auto&&...) { - recv_count.fetch_add(1, std::memory_order_relaxed); - std::this_thread::sleep_for(std::chrono::microseconds(50)); - }); - - std::vector sub_ptrs = {&sub}; - waitForMatch(sub_ptrs); - - for (int i = 0; i < num_messages; ++i) - { - pub.Send(payload); - std::this_thread::sleep_for(std::chrono::microseconds(100)); - } - - eCAL::Process::SleepMS(1000); - - if (auto provider = eCAL::g_trace_provider(); provider) - triggerFlush(provider.get()); - - auto lines = readJsonLines(spansFilePath()); - - size_t send_spans = 0; - for (const auto& line : lines) - { - if (line["entity_id"].get() == 0) continue; - if (line["op_type"].get() == send) - ++send_spans; - } - EXPECT_EQ(send_spans, static_cast(num_messages)); - - for (const auto& line : lines) - { - if (line["op_type"].get() == callback_execution) - { - long long duration_ns = line["end_ns"].get() - - line["start_ns"].get(); - EXPECT_GE(duration_ns, 40000) - << "callback_execution span duration too short"; - } - } -} diff --git a/ecal/tests/cpp/tracing_test/src/trace_provider_test.cpp b/ecal/tests/cpp/tracing_test/src/trace_provider_test.cpp new file mode 100644 index 0000000000..0ad76b366f --- /dev/null +++ b/ecal/tests/cpp/tracing_test/src/trace_provider_test.cpp @@ -0,0 +1,127 @@ +/* ========================= eCAL LICENSE ================================= + * + * Copyright (C) 2016 - 2025 Continental Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ========================= eCAL LICENSE ================================= +*/ + +#include "tracing_test_helpers.h" + +#include +#include + +#include + +#include + +#include +#include +#include + +TEST(TestTraceProvider, ConcurrentSpanWrites) +{ + constexpr size_t num_threads = 100; + constexpr size_t spans_per_thread = 100; + constexpr size_t total_spans = num_threads * spans_per_thread; // 10000 + constexpr size_t batch_size = 500; // Flush every 500 spans + + MockTracingWriter mock_writer; + + auto provider = eCAL::tracing::CTraceProvider::Create( + std::make_unique(mock_writer), batch_size); + ASSERT_NE(provider, nullptr); + + Barrier barrier(num_threads); + + std::vector threads; + threads.reserve(num_threads); + + for (size_t t = 0; t < num_threads; ++t) + { + threads.emplace_back([&, t]() + { + barrier.wait(); + for (size_t i = 0; i < spans_per_thread; ++i) + { + eCAL::tracing::SPublisherSpanData span{}; + span.op_type = eCAL::tracing::operation_type::send; + span.entity_id = static_cast(t * spans_per_thread + i); + span.process_id = 1; + span.payload_size = 42; + span.clock = static_cast(i); + span.layer = eCAL::tracing::tl_trace_shm; + span.start_ns = 1000 + static_cast(i); + span.end_ns = 2000 + static_cast(i); + provider->WriteSpan(span); + } + }); + } + + // Join all writer threads. + for (auto& th : threads) + th.join(); + + // Destroy the provider to flush any remaining buffered spans. + provider.reset(); + + // Every span must be written exactly once — no duplicates, no drops. + EXPECT_EQ(mock_writer.SpanCount(), total_spans); +} + +TEST(TestTraceProvider, ConcurrentMetadataWrites) +{ + constexpr size_t num_threads = 100; + constexpr size_t metadata_per_thread = 100; + constexpr size_t total_metadata = num_threads * metadata_per_thread; // 10000 + constexpr size_t batch_size = 500; + + MockTracingWriter mock_writer; + + auto provider = eCAL::tracing::CTraceProvider::Create( + std::make_unique(mock_writer), batch_size); + ASSERT_NE(provider, nullptr); + + Barrier barrier(num_threads); + + std::vector threads; + threads.reserve(num_threads); + + for (size_t t = 0; t < num_threads; ++t) + { + threads.emplace_back([&, t]() + { + barrier.wait(); + for (size_t i = 0; i < metadata_per_thread; ++i) + { + eCAL::tracing::STopicMetadata metadata{}; + metadata.entity_id = static_cast(t * metadata_per_thread + i); + metadata.process_id = 1; + metadata.host_name = "test_host"; + metadata.topic_name = "topic_" + std::to_string(t) + "_" + std::to_string(i); + metadata.encoding = "protobuf"; + metadata.type_name = "TestType"; + metadata.direction = eCAL::tracing::topic_direction::publisher; + provider->WriteMetadata(metadata); + } + }); + } + + for (auto& th : threads) + th.join(); + + provider.reset(); + + EXPECT_EQ(mock_writer.MetadataCount(), total_metadata); +} \ No newline at end of file diff --git a/ecal/tests/cpp/tracing_test/src/tracing_test_helpers.h b/ecal/tests/cpp/tracing_test/src/tracing_test_helpers.h index a6406fd39e..faa12befb2 100644 --- a/ecal/tests/cpp/tracing_test/src/tracing_test_helpers.h +++ b/ecal/tests/cpp/tracing_test/src/tracing_test_helpers.h @@ -19,110 +19,140 @@ #pragma once -#include - -#include -#include -#include - #include -#include -#include #include +#include #include -#include -#include -#include -#include +#include #include -#include +#include #include -#include #include -#include -#include - -#ifdef _WIN32 -#include -inline int setenv(const char* name, const char* value, int /*overwrite*/) +// Mock writer that counts spans and metadata for test assertions. +class MockTracingWriter : public eCAL::tracing::TracingWriter { - return _putenv_s(name, value); -} -#endif +public: + void WriteSpansToFile(const std::vector& batch) override + { + span_count_ += batch.size(); + } -using json = nlohmann::json; + void WriteMetadataToFile(const eCAL::tracing::STopicMetadata& /*metadata*/) override + { + metadata_count_++; + } -// Return a platform-appropriate temporary directory. -inline std::string tempDir() -{ -#ifdef _WIN32 - const char* dir = std::getenv("TEMP"); - if (dir) return dir; - dir = std::getenv("TMP"); - if (dir) return dir; - return "C:\\Temp"; -#else - return "/tmp"; -#endif -} + size_t SpanCount() const + { + return span_count_.load(); + } + + size_t MetadataCount() const + { + return metadata_count_.load(); + } -// Remove a file if it exists. -inline void removeFile(const std::string& path) + void Clear() + { + span_count_ = 0; + metadata_count_ = 0; + + } + +private: + mutable std::mutex mutex_; + std::atomic span_count_{0}; + std::atomic metadata_count_{0}; +}; + +// This allows the mock to outlive the CTraceProvider that only owns this proxy. +class ProxyTracingWriter : public eCAL::tracing::TracingWriter { - std::remove(path.c_str()); -} +public: + explicit ProxyTracingWriter(MockTracingWriter& target) : target_(target) {} + + void WriteSpansToFile(const std::vector& batch) override + { + target_.WriteSpansToFile(batch); + } + + void WriteMetadataToFile(const eCAL::tracing::STopicMetadata& metadata) override + { + target_.WriteMetadataToFile(metadata); + } + +private: + MockTracingWriter& target_; +}; -// Read a JSONL file and return a vector of parsed JSON objects. -inline std::vector readJsonLines(const std::string& path) +// Count the number of lines in a file and validate each line is valid JSON. +inline size_t CountAndValidateJsonlLines(const std::string& filepath) { - std::vector result; - std::ifstream file(path); + std::ifstream file(filepath); + EXPECT_TRUE(file.is_open()) << "Failed to open: " << filepath; + + size_t count = 0; std::string line; while (std::getline(file, line)) { - if (!line.empty()) - { - try { result.push_back(json::parse(line)); } - catch (...) {} - } + if (line.empty()) continue; + // Every line must be valid JSON — a corrupted/interleaved write would break parsing. + EXPECT_NO_THROW(nlohmann::json::parse(line)) + << "Invalid JSON on line " << (count + 1) << ": " << line; + ++count; } - return result; + return count; } -// Convenience accessors for the provider's output file paths. -inline std::string spansFilePath() +// Cross-platform helpers for environment variable manipulation. +inline void SetEnv(const std::string& name, const std::string& value) { - auto p = eCAL::g_trace_provider(); - if (p) return p->getSpansFilePath(); - return {}; +#ifdef _WIN32 + _putenv_s(name.c_str(), value.c_str()); +#else + setenv(name.c_str(), value.c_str(), 1); +#endif } -inline std::string metadataFilePath() +inline void UnsetEnv(const std::string& name) { - auto p = eCAL::g_trace_provider(); - if (p) return p->getTopicMetadataFilePath(); - return {}; +#ifdef _WIN32 + _putenv_s(name.c_str(), ""); +#else + unsetenv(name.c_str()); +#endif } -// Wait for the provider's background writer thread to drain the span buffer. -inline bool waitForBufferDrain(eCAL::tracing::CTraceProvider* provider, int timeout_ms = 500) +// RAII helper to set and restore an environment variable. +class ScopedEnvVar { - auto deadline = std::chrono::steady_clock::now() - + std::chrono::milliseconds(timeout_ms); - while (!provider->getSpans().empty() - && std::chrono::steady_clock::now() < deadline) - { - std::this_thread::sleep_for(std::chrono::milliseconds(5)); - } - return provider->getSpans().empty(); -} +public: + ScopedEnvVar(const char* name, const std::string& value) + : name_(name) + { + const char* old = std::getenv(name); + had_old_ = (old != nullptr); + if (had_old_) old_value_ = old; + SetEnv(name_, value); + } + + ~ScopedEnvVar() + { + if (had_old_) + SetEnv(name_, old_value_); + else + UnsetEnv(name_); + } + + ScopedEnvVar(const ScopedEnvVar&) = delete; + ScopedEnvVar& operator=(const ScopedEnvVar&) = delete; + +private: + std::string name_; + std::string old_value_; + bool had_old_{false}; +}; -// Force the provider to synchronously flush all buffered spans. -// Intended for setUp/tearDown cleanup. -inline void triggerFlush(eCAL::tracing::CTraceProvider* provider) -{ - provider->forceFlush(); -} diff --git a/ecal/tests/cpp/tracing_test/src/tracing_writer_test.cpp b/ecal/tests/cpp/tracing_test/src/tracing_writer_test.cpp new file mode 100644 index 0000000000..98ae5dcbc7 --- /dev/null +++ b/ecal/tests/cpp/tracing_test/src/tracing_writer_test.cpp @@ -0,0 +1,235 @@ +/* ========================= eCAL LICENSE ================================= + * + * Copyright (C) 2016 - 2025 Continental Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ========================= eCAL LICENSE ================================= +*/ + +#include "tracing_test_helpers.h" + +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include + +TEST(TestTracingWriterJSONL, ConcurrentSpanWrites) +{ + constexpr size_t num_threads = 100; + constexpr size_t batches_per_thread = 50; + constexpr size_t spans_per_batch = 500; + constexpr size_t total_spans = num_threads * batches_per_thread * spans_per_batch; + + // Use a temporary directory for output files. + auto tmp_dir = std::filesystem::temp_directory_path() / "ecal_tracing_writer_test_spans"; + std::filesystem::create_directories(tmp_dir); + ScopedEnvVar env("ECAL_TRACING_DATA_DIR", tmp_dir.string()); + + { + eCAL::tracing::CTracingWriterJSONL writer; + + Barrier barrier(num_threads); + + std::vector threads; + threads.reserve(num_threads); + + for (size_t t = 0; t < num_threads; ++t) + { + threads.emplace_back([&, t]() + { + barrier.wait(); + + for (size_t b = 0; b < batches_per_thread; ++b) + { + std::vector batch; + batch.reserve(spans_per_batch); + + for (size_t i = 0; i < spans_per_batch; ++i) + { + eCAL::tracing::SPublisherSpanData span{}; + span.op_type = eCAL::tracing::operation_type::send; + span.entity_id = static_cast(t * batches_per_thread * spans_per_batch + b * spans_per_batch + i); + span.process_id = 1; + span.payload_size = 64; + span.clock = static_cast(i); + span.layer = eCAL::tracing::tl_trace_shm; + span.start_ns = 1000; + span.end_ns = 2000; + batch.push_back(span); + } + writer.WriteSpansToFile(batch); + } + }); + } + + for (auto& th : threads) th.join(); + + // Verify: every span line must be valid JSON and the total count must match. + std::string spans_path = writer.GetSpansFilePath(); + size_t line_count = CountAndValidateJsonlLines(spans_path); + EXPECT_EQ(line_count, total_spans); + } + + // Clean up temp files. + std::filesystem::remove_all(tmp_dir); +} + +TEST(TestTracingWriterJSONL, ConcurrentMetadataWrites) +{ + constexpr size_t num_threads = 100; + constexpr size_t metadata_per_thread = 200; + constexpr size_t total_metadata = num_threads * metadata_per_thread; + + auto tmp_dir = std::filesystem::temp_directory_path() / "ecal_tracing_writer_test_metadata"; + std::filesystem::create_directories(tmp_dir); + ScopedEnvVar env("ECAL_TRACING_DATA_DIR", tmp_dir.string()); + + { + eCAL::tracing::CTracingWriterJSONL writer; + + Barrier barrier(num_threads); + + std::vector threads; + threads.reserve(num_threads); + + for (size_t t = 0; t < num_threads; ++t) + { + threads.emplace_back([&, t]() + { + barrier.wait(); + + for (size_t i = 0; i < metadata_per_thread; ++i) + { + eCAL::tracing::STopicMetadata metadata{}; + metadata.entity_id = static_cast(t * metadata_per_thread + i); + metadata.process_id = 1; + metadata.host_name = "test_host"; + metadata.topic_name = "topic_" + std::to_string(t) + "_" + std::to_string(i); + metadata.encoding = "protobuf"; + metadata.type_name = "TestType"; + metadata.direction = eCAL::tracing::topic_direction::publisher; + writer.WriteMetadataToFile(metadata); + } + }); + } + + for (auto& th : threads) th.join(); + + std::string metadata_path = writer.GetTopicMetadataFilePath(); + size_t line_count = CountAndValidateJsonlLines(metadata_path); + EXPECT_EQ(line_count, total_metadata); + } + + std::filesystem::remove_all(tmp_dir); +} + +TEST(TestTracingWriterJSONL, PublisherSpanJsonFields) +{ + auto tmp_dir = std::filesystem::temp_directory_path() / "ecal_tracing_writer_test_pub_fields"; + std::filesystem::create_directories(tmp_dir); + ScopedEnvVar env("ECAL_TRACING_DATA_DIR", tmp_dir.string()); + + { + eCAL::tracing::CTracingWriterJSONL writer; + + eCAL::tracing::SPublisherSpanData span{}; + span.op_type = eCAL::tracing::operation_type::send; + span.entity_id = 42; + span.process_id = 123; + span.payload_size = 256; + span.clock = 7; + span.layer = eCAL::tracing::tl_trace_udp; + span.start_ns = 1000; + span.end_ns = 2000; + + std::vector batch; + batch.push_back(span); + writer.WriteSpansToFile(batch); + + std::ifstream file(writer.GetSpansFilePath()); + ASSERT_TRUE(file.is_open()); + + std::string line; + ASSERT_TRUE(std::getline(file, line)); + auto j = nlohmann::json::parse(line); + + EXPECT_EQ(j.at("op_type"), static_cast(eCAL::tracing::operation_type::send)); + EXPECT_EQ(j.at("entity_id"), 42u); + EXPECT_EQ(j.at("process_id"), 123u); + EXPECT_EQ(j.at("payload_size"), 256u); + EXPECT_EQ(j.at("clock"), 7); + EXPECT_EQ(j.at("layer"), static_cast(eCAL::tracing::tl_trace_udp)); + EXPECT_EQ(j.at("start_ns"), 1000); + EXPECT_EQ(j.at("end_ns"), 2000); + } + + std::filesystem::remove_all(tmp_dir); +} + +TEST(TestTracingWriterJSONL, SubscriberSpanJsonFields) +{ + auto tmp_dir = std::filesystem::temp_directory_path() / "ecal_tracing_writer_test_sub_fields"; + std::filesystem::create_directories(tmp_dir); + ScopedEnvVar env("ECAL_TRACING_DATA_DIR", tmp_dir.string()); + + { + eCAL::tracing::CTracingWriterJSONL writer; + + eCAL::tracing::SSubscriberSpanData span{}; + span.op_type = eCAL::tracing::operation_type::receive; + span.entity_id = 99; + span.topic_id = 55; + span.process_id = 456; + span.payload_size = 512; + span.clock = 3; + span.layer = eCAL::tracing::tl_trace_tcp; + span.start_ns = 3000; + span.end_ns = 4000; + + std::vector batch; + batch.push_back(span); + writer.WriteSpansToFile(batch); + + std::ifstream file(writer.GetSpansFilePath()); + ASSERT_TRUE(file.is_open()); + + std::string line; + ASSERT_TRUE(std::getline(file, line)); + auto j = nlohmann::json::parse(line); + + EXPECT_EQ(j.at("op_type"), static_cast(eCAL::tracing::operation_type::receive)); + EXPECT_EQ(j.at("entity_id"), 99u); + EXPECT_EQ(j.at("topic_id"), 55u); + EXPECT_EQ(j.at("process_id"), 456u); + EXPECT_EQ(j.at("payload_size"), 512u); + EXPECT_EQ(j.at("clock"), 3); + EXPECT_EQ(j.at("layer"), static_cast(eCAL::tracing::tl_trace_tcp)); + EXPECT_EQ(j.at("start_ns"), 3000); + EXPECT_EQ(j.at("end_ns"), 4000); + } + + std::filesystem::remove_all(tmp_dir); +} + diff --git a/ecal/tests/cpp/tracing_test/src/unittest/tracing_edge_case_test.cpp b/ecal/tests/cpp/tracing_test/src/unittest/tracing_edge_case_test.cpp deleted file mode 100644 index ad2597cdb1..0000000000 --- a/ecal/tests/cpp/tracing_test/src/unittest/tracing_edge_case_test.cpp +++ /dev/null @@ -1,192 +0,0 @@ -/* ========================= eCAL LICENSE ================================= - * - * Copyright (C) 2016 - 2025 Continental Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * ========================= eCAL LICENSE ================================= -*/ - -#include "tracing_test_helpers.h" - -using namespace eCAL::tracing; - -// ============ Edge Case Tests ============ - -class EdgeCaseTest : public ::testing::Test -{ -protected: - void SetUp() override - { - setenv("ECAL_TRACING_DATA_DIR", tempDir().c_str(), 1); - if (auto provider = eCAL::g_trace_provider(); provider) - { - triggerFlush(provider.get()); - } - removeFile(spansFilePath()); - removeFile(metadataFilePath()); - } - - void TearDown() override - { - if (auto provider = eCAL::g_trace_provider(); provider) - { - triggerFlush(provider.get()); - } - removeFile(spansFilePath()); - removeFile(metadataFilePath()); - } -}; - -TEST_F(EdgeCaseTest, AllLayerTypesCombined) -{ - eCAL::STopicId topic_id; - topic_id.topic_id.entity_id = 1; - topic_id.topic_id.process_id = 2; - - eTracingLayerType all_layers = tl_trace_all; - - { - CSpan span(topic_id, 100, all_layers, 256, send); - } - - auto provider = eCAL::g_trace_provider(); - if (!provider) return; - auto spans = provider->getSpans(); - ASSERT_EQ(spans.size(), 1); - EXPECT_EQ(spans[0].layer, static_cast(all_layers)); -} - -TEST_F(EdgeCaseTest, NoneLayerType) -{ - eCAL::STopicId topic_id; - topic_id.topic_id.entity_id = 1; - topic_id.topic_id.process_id = 2; - - { - CSpan span(topic_id, 0, tl_trace_none, 0, send); - } - - auto provider = eCAL::g_trace_provider(); - if (!provider) return; - auto spans = provider->getSpans(); - ASSERT_EQ(spans.size(), 1); - EXPECT_EQ(spans[0].layer, tl_trace_none); -} - -TEST_F(EdgeCaseTest, EmptyStringMetadata) -{ - STopicMetadata metadata; - metadata.host_name = ""; - metadata.topic_name = ""; - metadata.encoding = ""; - metadata.type_name = ""; - metadata.entity_id = 0; - metadata.process_id = 0; - metadata.direction = publisher; - - CTracingWriter writer; - EXPECT_NO_THROW(writer.writeTopicMetadata(metadata)); - - auto lines = readJsonLines(writer.getTopicMetadataFilePath()); - ASSERT_EQ(lines.size(), 1); - EXPECT_EQ(lines[0]["host_name"].get(), ""); - EXPECT_EQ(lines[0]["topic_name"].get(), ""); -} - -TEST_F(EdgeCaseTest, ForceFlushSingleSpan) -{ - auto provider = eCAL::g_trace_provider(); - if (!provider) return; - - SSpanData span{}; - span.entity_id = 42; - span.op_type = send; - provider->bufferSpan(span); - - provider->forceFlush(); - EXPECT_EQ(provider->getSpans().size(), 0); -} - -TEST_F(EdgeCaseTest, RapidForceFlushCycles) -{ - auto provider = eCAL::g_trace_provider(); - if (!provider) return; - - for (int cycle = 0; cycle < 10; ++cycle) - { - SSpanData span{}; - span.entity_id = cycle; - span.op_type = send; - provider->bufferSpan(span); - provider->forceFlush(); - EXPECT_EQ(provider->getSpans().size(), 0); - } -} - -TEST_F(EdgeCaseTest, MaxEntityAndProcessIds) -{ - SSpanData span{}; - span.entity_id = UINT64_MAX; - span.process_id = UINT64_MAX; - span.topic_id = UINT64_MAX; - span.op_type = send; - - CTracingWriter writer; - writer.writeBatchSpans({span}); - - auto lines = readJsonLines(writer.getSpansFilePath()); - ASSERT_EQ(lines.size(), 1); - EXPECT_EQ(lines[0]["entity_id"].get(), UINT64_MAX); - EXPECT_EQ(lines[0]["process_id"].get(), UINT64_MAX); - EXPECT_EQ(lines[0]["topic_id"].get(), UINT64_MAX); -} - -TEST_F(EdgeCaseTest, ReceiveSpanViaProviderFlush) -{ - auto provider = eCAL::g_trace_provider(); - if (!provider) return; - - eCAL::Payload::TopicInfo topic_info; - topic_info.topic_id = 555; - topic_info.process_id = 666; - - { - CSpan span(777, topic_info, 42, tl_trace_tcp, 2048, receive); - } - - auto spans = provider->getSpans(); - ASSERT_EQ(spans.size(), 1); - EXPECT_EQ(spans[0].entity_id, 777); - EXPECT_EQ(spans[0].topic_id, 555); - EXPECT_EQ(spans[0].process_id, 666); - EXPECT_EQ(spans[0].op_type, receive); - - triggerFlush(provider.get()); - EXPECT_EQ(provider->getSpans().size(), 0); - - auto lines = readJsonLines(spansFilePath()); - ASSERT_GE(lines.size(), 1); - bool found = false; - for (const auto& line : lines) - { - if (line["entity_id"].get() == 777) - { - EXPECT_EQ(line["topic_id"].get(), 555); - EXPECT_EQ(line["op_type"].get(), receive); - found = true; - break; - } - } - EXPECT_TRUE(found); -} diff --git a/ecal/tests/cpp/tracing_test/src/unittest/tracing_layer_type_test.cpp b/ecal/tests/cpp/tracing_test/src/unittest/tracing_layer_type_test.cpp deleted file mode 100644 index a027918f4f..0000000000 --- a/ecal/tests/cpp/tracing_test/src/unittest/tracing_layer_type_test.cpp +++ /dev/null @@ -1,97 +0,0 @@ -/* ========================= eCAL LICENSE ================================= - * - * Copyright (C) 2016 - 2025 Continental Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * ========================= eCAL LICENSE ================================= -*/ - -#include "tracing_test_helpers.h" - -using namespace eCAL::tracing; - -// ============ Tests for Layer Type Enum Values and Conversion ============ - -class TracingLayerTypeTest : public ::testing::Test -{ -protected: - void SetUp() override {} -}; - -TEST_F(TracingLayerTypeTest, LayerTypeValues) -{ - EXPECT_EQ(tl_trace_none, 0); - EXPECT_EQ(tl_trace_shm, 1); - EXPECT_EQ(tl_trace_udp, 2); - EXPECT_EQ(tl_trace_tcp, 4); - EXPECT_EQ(tl_trace_shm_udp, 3); // 1 | 2 - EXPECT_EQ(tl_trace_shm_tcp, 5); // 1 | 4 - EXPECT_EQ(tl_trace_udp_tcp, 6); // 2 | 4 - EXPECT_EQ(tl_trace_all, 7); // 1 | 2 | 4 -} - -TEST_F(TracingLayerTypeTest, LayerTypeBitwise) -{ - uint64_t combined = tl_trace_shm | tl_trace_udp; - EXPECT_EQ(combined, tl_trace_shm_udp); - EXPECT_EQ(combined, 3); - - combined = tl_trace_shm | tl_trace_tcp; - EXPECT_EQ(combined, tl_trace_shm_tcp); - EXPECT_EQ(combined, 5); - - combined = tl_trace_udp | tl_trace_tcp; - EXPECT_EQ(combined, tl_trace_udp_tcp); - EXPECT_EQ(combined, 6); - - combined = tl_trace_shm | tl_trace_udp | tl_trace_tcp; - EXPECT_EQ(combined, tl_trace_all); - EXPECT_EQ(combined, 7); -} - -TEST_F(TracingLayerTypeTest, ToTracingLayerTypeSHM) -{ - eTracingLayerType result = toTracingLayerType(eCAL::tl_ecal_shm); - EXPECT_EQ(result, tl_trace_shm); -} - -TEST_F(TracingLayerTypeTest, ToTracingLayerTypeUDP) -{ - eTracingLayerType result = toTracingLayerType(eCAL::tl_ecal_udp); - EXPECT_EQ(result, tl_trace_udp); -} - -TEST_F(TracingLayerTypeTest, ToTracingLayerTypeTCP) -{ - eTracingLayerType result = toTracingLayerType(eCAL::tl_ecal_tcp); - EXPECT_EQ(result, tl_trace_tcp); -} - -TEST_F(TracingLayerTypeTest, ToTracingLayerTypeAll) -{ - eTracingLayerType result = toTracingLayerType(eCAL::tl_all); - EXPECT_EQ(result, tl_trace_all); -} - -TEST_F(TracingLayerTypeTest, ToTracingLayerTypeUnknown) -{ - eTracingLayerType result = toTracingLayerType(static_cast(999)); - EXPECT_EQ(result, tl_trace_none); -} - -TEST_F(TracingLayerTypeTest, ToTracingLayerTypeNone) -{ - eTracingLayerType result = toTracingLayerType(eCAL::tl_none); - EXPECT_EQ(result, tl_trace_none); -} diff --git a/ecal/tests/cpp/tracing_test/src/unittest/tracing_provider_test.cpp b/ecal/tests/cpp/tracing_test/src/unittest/tracing_provider_test.cpp deleted file mode 100644 index 7fc88a53f9..0000000000 --- a/ecal/tests/cpp/tracing_test/src/unittest/tracing_provider_test.cpp +++ /dev/null @@ -1,236 +0,0 @@ -/* ========================= eCAL LICENSE ================================= - * - * Copyright (C) 2016 - 2025 Continental Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * ========================= eCAL LICENSE ================================= -*/ - -#include "tracing_test_helpers.h" - -using namespace eCAL::tracing; - -// ============ Tests for CTraceProvider Singleton and Buffering ============ - -class TracingProviderTest : public ::testing::Test -{ -protected: - void SetUp() override - { - setenv("ECAL_TRACING_DATA_DIR", tempDir().c_str(), 1); - if (auto provider = eCAL::g_trace_provider(); provider) - { - triggerFlush(provider.get()); - } - } - - void TearDown() override - { - if (auto provider = eCAL::g_trace_provider(); provider) - { - triggerFlush(provider.get()); - } - } -}; - -TEST_F(TracingProviderTest, SingletonInstance) -{ - auto provider = eCAL::g_trace_provider(); - if (!provider) return; - - CTraceProvider& provider1 = *provider; - CTraceProvider& provider2 = *provider; - - EXPECT_EQ(&provider1, &provider2); -} - -TEST_F(TracingProviderTest, DefaultBatchSize) -{ - auto provider = eCAL::g_trace_provider(); - if (!provider) return; - - for (size_t i = 0; i < kDefaultTracingBatchSize - 1; ++i) - { - SSpanData span{}; - span.entity_id = i; - provider->bufferSpan(span); - } - EXPECT_EQ(provider->getSpans().size(), kDefaultTracingBatchSize - 1); -} - -TEST_F(TracingProviderTest, BufferSpan) -{ - auto provider = eCAL::g_trace_provider(); - if (!provider) return; - - SSpanData span{}; - span.entity_id = 111; - span.process_id = 222; - span.payload_size = 512; - - provider->bufferSpan(span); - - auto spans = provider->getSpans(); - ASSERT_EQ(spans.size(), 1); - EXPECT_EQ(spans[0].entity_id, 111); - EXPECT_EQ(spans[0].process_id, 222); - EXPECT_EQ(spans[0].payload_size, 512); -} - -TEST_F(TracingProviderTest, BufferMultipleSpans) -{ - auto provider = eCAL::g_trace_provider(); - if (!provider) return; - - for (int i = 0; i < 5; ++i) - { - SSpanData span{}; - span.entity_id = i; - provider->bufferSpan(span); - } - - auto spans = provider->getSpans(); - ASSERT_EQ(spans.size(), 5); - - for (int i = 0; i < 5; ++i) - { - EXPECT_EQ(spans[i].entity_id, static_cast(i)); - } -} - -TEST_F(TracingProviderTest, ForceFlushDrainsBuffer) -{ - auto provider = eCAL::g_trace_provider(); - if (!provider) return; - - SSpanData span{}; - span.entity_id = 999; - provider->bufferSpan(span); - - provider->forceFlush(); - EXPECT_EQ(provider->getSpans().size(), 0); -} - -TEST_F(TracingProviderTest, EmptyBufferStaysEmpty) -{ - auto provider = eCAL::g_trace_provider(); - if (!provider) return; - - EXPECT_EQ(provider->getSpans().size(), 0); -} - -TEST_F(TracingProviderTest, BufferFlushedOnForceFlush) -{ - auto provider = eCAL::g_trace_provider(); - if (!provider) return; - - for (int i = 0; i < 3; ++i) - { - SSpanData span{}; - span.entity_id = i; - provider->bufferSpan(span); - } - - EXPECT_EQ(provider->getSpans().size(), 3); - - provider->forceFlush(); - EXPECT_EQ(provider->getSpans().size(), 0); -} - -TEST_F(TracingProviderTest, MultipleForceFlushCycles) -{ - auto provider = eCAL::g_trace_provider(); - if (!provider) return; - - for (int i = 0; i < 2; ++i) - { - SSpanData span{}; - span.entity_id = i; - provider->bufferSpan(span); - } - provider->forceFlush(); - EXPECT_EQ(provider->getSpans().size(), 0); - - for (int i = 10; i < 12; ++i) - { - SSpanData span{}; - span.entity_id = i; - provider->bufferSpan(span); - } - provider->forceFlush(); - EXPECT_EQ(provider->getSpans().size(), 0); - - SSpanData span{}; - span.entity_id = 99; - provider->bufferSpan(span); - EXPECT_EQ(provider->getSpans().size(), 1); -} - -TEST_F(TracingProviderTest, AddTopicMetadata) -{ - removeFile(metadataFilePath()); - - auto provider = eCAL::g_trace_provider(); - if (!provider) return; - - STopicMetadata metadata; - metadata.entity_id = 777; - metadata.process_id = 888; - metadata.host_name = "provider-host"; - metadata.topic_name = "provider_topic"; - metadata.encoding = "protobuf"; - metadata.type_name = "ProviderMsg"; - metadata.direction = publisher; - - EXPECT_NO_THROW(provider->addTopicMetadata(metadata)); - - auto lines = readJsonLines(metadataFilePath()); - ASSERT_GE(lines.size(), 1); - const auto& obj = lines.back(); - EXPECT_EQ(obj["entity_id"].get(), 777); - EXPECT_EQ(obj["process_id"].get(), 888); - EXPECT_EQ(obj["host_name"].get(), "provider-host"); - EXPECT_EQ(obj["topic_name"].get(), "provider_topic"); - EXPECT_EQ(obj["encoding"].get(), "protobuf"); - EXPECT_EQ(obj["type_name"].get(), "ProviderMsg"); - EXPECT_EQ(obj["direction"].get(), "publisher"); - EXPECT_EQ(obj["tracing_version"].get(), kTracingVersion); - - removeFile(metadataFilePath()); -} - -TEST_F(TracingProviderTest, AddTopicMetadataSubscriber) -{ - removeFile(metadataFilePath()); - - auto provider = eCAL::g_trace_provider(); - if (!provider) return; - - STopicMetadata metadata; - metadata.entity_id = 100; - metadata.process_id = 200; - metadata.host_name = "sub-host"; - metadata.topic_name = "sub_topic"; - metadata.encoding = "flatbuffers"; - metadata.type_name = "SubMsg"; - metadata.direction = subscriber; - - provider->addTopicMetadata(metadata); - - auto lines = readJsonLines(metadataFilePath()); - ASSERT_GE(lines.size(), 1); - EXPECT_EQ(lines.back()["direction"].get(), "subscriber"); - - removeFile(metadataFilePath()); -} diff --git a/ecal/tests/cpp/tracing_test/src/unittest/tracing_span_test.cpp b/ecal/tests/cpp/tracing_test/src/unittest/tracing_span_test.cpp deleted file mode 100644 index e6db437d2f..0000000000 --- a/ecal/tests/cpp/tracing_test/src/unittest/tracing_span_test.cpp +++ /dev/null @@ -1,206 +0,0 @@ -/* ========================= eCAL LICENSE ================================= - * - * Copyright (C) 2016 - 2025 Continental Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * ========================= eCAL LICENSE ================================= -*/ - -#include "tracing_test_helpers.h" - -using namespace eCAL::tracing; - -// ============ Tests for CSpan RAII Wrapper ============ - -class SpanTest : public ::testing::Test -{ -protected: - void SetUp() override - { - setenv("ECAL_TRACING_DATA_DIR", tempDir().c_str(), 1); - if (auto provider = eCAL::g_trace_provider(); provider) - { - triggerFlush(provider.get()); - } - } - - void TearDown() override - { - if (auto provider = eCAL::g_trace_provider(); provider) - { - triggerFlush(provider.get()); - } - } -}; - -TEST_F(SpanTest, SendSpanConstruction) -{ - eCAL::STopicId topic_id; - topic_id.topic_id.entity_id = 111; - topic_id.topic_id.process_id = 222; - - { - CSpan span(topic_id, 100, tl_trace_shm, 256, send); - } - - if (auto provider = eCAL::g_trace_provider(); provider) - { - auto spans = provider->getSpans(); - ASSERT_EQ(spans.size(), 1); - - EXPECT_EQ(spans[0].entity_id, 111); - EXPECT_EQ(spans[0].process_id, 222); - EXPECT_EQ(spans[0].payload_size, 256); - EXPECT_EQ(spans[0].clock, 100); - EXPECT_EQ(spans[0].layer, tl_trace_shm); - EXPECT_EQ(spans[0].op_type, send); - EXPECT_EQ(spans[0].topic_id, 0); // Send spans have topic_id = 0 - } -} - -TEST_F(SpanTest, SendSpanTimestamps) -{ - eCAL::STopicId topic_id; - topic_id.topic_id.entity_id = 111; - topic_id.topic_id.process_id = 222; - - { - CSpan span(topic_id, 100, tl_trace_shm, 256, send); - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - } - - if (auto provider = eCAL::g_trace_provider(); provider) - { - auto spans = provider->getSpans(); - ASSERT_EQ(spans.size(), 1); - - EXPECT_GT(spans[0].start_ns, 0); - EXPECT_GT(spans[0].end_ns, 0); - EXPECT_LT(spans[0].start_ns, spans[0].end_ns); - EXPECT_GE(spans[0].end_ns - spans[0].start_ns, 500000); // at least 0.5 ms - } -} - -TEST_F(SpanTest, ReceiveSpanConstruction) -{ - eCAL::Payload::TopicInfo topic_info; - topic_info.topic_id = 999; - topic_info.process_id = 333; - - { - CSpan span(444, topic_info, 200, tl_trace_udp, 512, receive); - } - - if (auto provider = eCAL::g_trace_provider(); provider) - { - auto spans = provider->getSpans(); - ASSERT_EQ(spans.size(), 1); - - EXPECT_EQ(spans[0].entity_id, 444); - EXPECT_EQ(spans[0].topic_id, 999); - EXPECT_EQ(spans[0].process_id, 333); - EXPECT_EQ(spans[0].payload_size, 512); - EXPECT_EQ(spans[0].clock, 200); - EXPECT_EQ(spans[0].layer, tl_trace_udp); - EXPECT_EQ(spans[0].op_type, receive); - } -} - -TEST_F(SpanTest, ReceiveSpanTimestamps) -{ - eCAL::Payload::TopicInfo topic_info; - topic_info.topic_id = 999; - topic_info.process_id = 333; - - { - CSpan span(444, topic_info, 200, tl_trace_udp, 512, receive); - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - } - - if (auto provider = eCAL::g_trace_provider(); provider) - { - auto spans = provider->getSpans(); - ASSERT_EQ(spans.size(), 1); - - EXPECT_GT(spans[0].start_ns, 0); - EXPECT_GT(spans[0].end_ns, 0); - EXPECT_LT(spans[0].start_ns, spans[0].end_ns); - } -} - -TEST_F(SpanTest, CallbackExecutionSpan) -{ - eCAL::STopicId topic_id; - topic_id.topic_id.entity_id = 50; - topic_id.topic_id.process_id = 60; - - { - CSpan span(topic_id, 300, tl_trace_tcp, 1024, callback_execution); - } - - if (auto provider = eCAL::g_trace_provider(); provider) - { - auto spans = provider->getSpans(); - ASSERT_EQ(spans.size(), 1); - EXPECT_EQ(spans[0].op_type, callback_execution); - EXPECT_EQ(spans[0].layer, tl_trace_tcp); - EXPECT_EQ(spans[0].payload_size, 1024); - } -} - -TEST_F(SpanTest, MultipleOperationTypes) -{ - eCAL::STopicId topic_id; - topic_id.topic_id.entity_id = 111; - topic_id.topic_id.process_id = 222; - - { - CSpan span(topic_id, 100, tl_trace_shm, 256, send); - } - { - CSpan span(topic_id, 100, tl_trace_shm, 256, callback_execution); - } - - if (auto provider = eCAL::g_trace_provider(); provider) - { - auto spans = provider->getSpans(); - ASSERT_EQ(spans.size(), 2); - - EXPECT_EQ(spans[0].op_type, send); - EXPECT_EQ(spans[1].op_type, callback_execution); - } -} - -TEST_F(SpanTest, MultipleLayerTypes) -{ - eCAL::STopicId topic_id; - topic_id.topic_id.entity_id = 111; - topic_id.topic_id.process_id = 222; - - { CSpan span1(topic_id, 100, tl_trace_shm, 256, send); } - { CSpan span2(topic_id, 100, tl_trace_udp, 256, send); } - { CSpan span3(topic_id, 100, tl_trace_tcp, 256, send); } - { CSpan span4(topic_id, 100, tl_trace_shm_udp, 256, send); } - - if (auto provider = eCAL::g_trace_provider(); provider) - { - auto spans = provider->getSpans(); - ASSERT_EQ(spans.size(), 4); - - EXPECT_EQ(spans[0].layer, tl_trace_shm); - EXPECT_EQ(spans[1].layer, tl_trace_udp); - EXPECT_EQ(spans[2].layer, tl_trace_tcp); - EXPECT_EQ(spans[3].layer, tl_trace_shm_udp); - } -} diff --git a/ecal/tests/cpp/tracing_test/src/unittest/tracing_types_test.cpp b/ecal/tests/cpp/tracing_test/src/unittest/tracing_types_test.cpp deleted file mode 100644 index ea994901fc..0000000000 --- a/ecal/tests/cpp/tracing_test/src/unittest/tracing_types_test.cpp +++ /dev/null @@ -1,117 +0,0 @@ -/* ========================= eCAL LICENSE ================================= - * - * Copyright (C) 2016 - 2025 Continental Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * ========================= eCAL LICENSE ================================= -*/ - -#include "tracing_test_helpers.h" - -using namespace eCAL::tracing; - -// ============ Tests for Enums and Constants ============ - -class TracingTypesTest : public ::testing::Test -{ -protected: - void SetUp() override {} - void TearDown() override {} -}; - -TEST_F(TracingTypesTest, TracingVersionConstant) -{ - EXPECT_STREQ(kTracingVersion, "1.0.0"); -} - -TEST_F(TracingTypesTest, DefaultTracingBatchSize) -{ - EXPECT_EQ(kDefaultTracingBatchSize, 500); -} - -TEST_F(TracingTypesTest, SSpanDataDefaultInitialization) -{ - SSpanData span{}; - EXPECT_EQ(span.topic_id, 0); - EXPECT_EQ(span.entity_id, 0); - EXPECT_EQ(span.process_id, 0); - EXPECT_EQ(span.payload_size, 0); - EXPECT_EQ(span.clock, 0); - EXPECT_EQ(span.layer, 0); - EXPECT_EQ(span.start_ns, 0); - EXPECT_EQ(span.end_ns, 0); -} - -TEST_F(TracingTypesTest, SSpanDataAssignment) -{ - SSpanData span; - span.entity_id = 12345; - span.topic_id = 67890; - span.process_id = 111; - span.payload_size = 256; - span.clock = 100; - span.layer = tl_trace_shm; - span.start_ns = 1000000; - span.end_ns = 2000000; - span.op_type = send; - - EXPECT_EQ(span.entity_id, 12345); - EXPECT_EQ(span.topic_id, 67890); - EXPECT_EQ(span.process_id, 111); - EXPECT_EQ(span.payload_size, 256); - EXPECT_EQ(span.clock, 100); - EXPECT_EQ(span.layer, tl_trace_shm); - EXPECT_EQ(span.start_ns, 1000000); - EXPECT_EQ(span.end_ns, 2000000); - EXPECT_EQ(span.op_type, send); -} - -TEST_F(TracingTypesTest, STopicMetadataDefaultVersion) -{ - STopicMetadata metadata; - EXPECT_STREQ(metadata.tracing_version.c_str(), kTracingVersion); -} - -TEST_F(TracingTypesTest, STopicMetadataAssignment) -{ - STopicMetadata metadata; - metadata.entity_id = 999; - metadata.process_id = 42; - metadata.host_name = "test-host"; - metadata.topic_name = "test_topic"; - metadata.encoding = "protobuf"; - metadata.type_name = "TestType"; - metadata.direction = publisher; - - EXPECT_EQ(metadata.entity_id, 999); - EXPECT_EQ(metadata.process_id, 42); - EXPECT_EQ(metadata.host_name, "test-host"); - EXPECT_EQ(metadata.topic_name, "test_topic"); - EXPECT_EQ(metadata.encoding, "protobuf"); - EXPECT_EQ(metadata.type_name, "TestType"); - EXPECT_EQ(metadata.direction, publisher); -} - -TEST_F(TracingTypesTest, OperationTypeEnum) -{ - EXPECT_EQ(send, 0); - EXPECT_EQ(receive, 1); - EXPECT_EQ(callback_execution, 2); -} - -TEST_F(TracingTypesTest, TopicDirectionEnum) -{ - EXPECT_EQ(publisher, 0); - EXPECT_EQ(subscriber, 1); -} diff --git a/ecal/tests/cpp/tracing_test/src/unittest/tracing_writer_test.cpp b/ecal/tests/cpp/tracing_test/src/unittest/tracing_writer_test.cpp deleted file mode 100644 index 3e76c3d127..0000000000 --- a/ecal/tests/cpp/tracing_test/src/unittest/tracing_writer_test.cpp +++ /dev/null @@ -1,291 +0,0 @@ -/* ========================= eCAL LICENSE ================================= - * - * Copyright (C) 2016 - 2025 Continental Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * ========================= eCAL LICENSE ================================= -*/ - -#include "tracing_test_helpers.h" - -using namespace eCAL::tracing; - -// ============ Tests for CTracingWriter ============ - -class TracingWriterTest : public ::testing::Test -{ -protected: - std::unique_ptr writer_; - - void SetUp() override - { - setenv("ECAL_TRACING_DATA_DIR", tempDir().c_str(), 1); - if (auto provider = eCAL::g_trace_provider(); provider) - { - triggerFlush(provider.get()); - } - writer_ = std::make_unique(); - removeFile(writer_->getSpansFilePath()); - removeFile(writer_->getTopicMetadataFilePath()); - } - - void TearDown() override - { - if (auto provider = eCAL::g_trace_provider(); provider) - { - triggerFlush(provider.get()); - } - removeFile(writer_->getSpansFilePath()); - removeFile(writer_->getTopicMetadataFilePath()); - } -}; - -TEST_F(TracingWriterTest, WriterConstruction) -{ - SUCCEED(); -} - -TEST_F(TracingWriterTest, WriteBatchSpansVerifyContent) -{ - std::vector batch; - - for (int i = 0; i < 3; ++i) - { - SSpanData span{}; - span.entity_id = 100 + i; - span.topic_id = 200 + i; - span.process_id = 300 + i; - span.payload_size = 256 * (i + 1); - span.clock = 10 * i; - span.layer = tl_trace_shm; - span.start_ns = 1000000 + i * 100; - span.end_ns = 2000000 + i * 100; - span.op_type = send; - batch.push_back(span); - } - - writer_->writeBatchSpans(batch); - - auto lines = readJsonLines(writer_->getSpansFilePath()); - ASSERT_EQ(lines.size(), 3); - - for (int i = 0; i < 3; ++i) - { - EXPECT_EQ(lines[i]["entity_id"].get(), 100 + i); - EXPECT_EQ(lines[i]["topic_id"].get(), 200 + i); - EXPECT_EQ(lines[i]["process_id"].get(), 300 + i); - EXPECT_EQ(lines[i]["payload_size"].get(), 256 * (i + 1)); - EXPECT_EQ(lines[i]["clock"].get(), 10 * i); - EXPECT_EQ(lines[i]["layer"].get(), tl_trace_shm); - EXPECT_EQ(lines[i]["start_ns"].get(), 1000000 + i * 100); - EXPECT_EQ(lines[i]["end_ns"].get(), 2000000 + i * 100); - EXPECT_EQ(lines[i]["op_type"].get(), send); - } -} - -TEST_F(TracingWriterTest, WriteBatchSpansReceive) -{ - SSpanData span{}; - span.entity_id = 10; - span.topic_id = 20; - span.process_id = 30; - span.payload_size = 512; - span.clock = 5; - span.layer = tl_trace_udp; - span.start_ns = 5000000; - span.end_ns = 6000000; - span.op_type = receive; - - writer_->writeBatchSpans({span}); - - auto lines = readJsonLines(writer_->getSpansFilePath()); - ASSERT_EQ(lines.size(), 1); - EXPECT_EQ(lines[0]["op_type"].get(), receive); - EXPECT_EQ(lines[0]["topic_id"].get(), 20); - EXPECT_EQ(lines[0]["layer"].get(), tl_trace_udp); -} - -TEST_F(TracingWriterTest, WriteBatchSpansAppends) -{ - SSpanData span1{}; - span1.entity_id = 1; - span1.op_type = send; - writer_->writeBatchSpans({span1}); - - SSpanData span2{}; - span2.entity_id = 2; - span2.op_type = send; - writer_->writeBatchSpans({span2}); - - auto lines = readJsonLines(writer_->getSpansFilePath()); - ASSERT_EQ(lines.size(), 2); - EXPECT_EQ(lines[0]["entity_id"].get(), 1); - EXPECT_EQ(lines[1]["entity_id"].get(), 2); -} - -TEST_F(TracingWriterTest, WriteEmptyBatch) -{ - std::vector batch; - - EXPECT_NO_THROW(writer_->writeBatchSpans(batch)); - - auto lines = readJsonLines(writer_->getSpansFilePath()); - EXPECT_EQ(lines.size(), 0); -} - -TEST_F(TracingWriterTest, WriteTopicMetadataVerifyContent) -{ - STopicMetadata metadata; - metadata.entity_id = 555; - metadata.process_id = 666; - metadata.host_name = "test-host"; - metadata.topic_name = "test_topic"; - metadata.encoding = "protobuf"; - metadata.type_name = "TestMessage"; - metadata.direction = publisher; - - writer_->writeTopicMetadata(metadata); - - auto lines = readJsonLines(writer_->getTopicMetadataFilePath()); - ASSERT_EQ(lines.size(), 1); - - const auto& obj = lines[0]; - EXPECT_EQ(obj["tracing_version"].get(), kTracingVersion); - EXPECT_EQ(obj["entity_id"].get(), 555); - EXPECT_EQ(obj["process_id"].get(), 666); - EXPECT_EQ(obj["host_name"].get(), "test-host"); - EXPECT_EQ(obj["topic_name"].get(), "test_topic"); - EXPECT_EQ(obj["encoding"].get(), "protobuf"); - EXPECT_EQ(obj["type_name"].get(), "TestMessage"); - EXPECT_EQ(obj["direction"].get(), "publisher"); -} - -TEST_F(TracingWriterTest, WriteTopicMetadataSubscriber) -{ - STopicMetadata metadata; - metadata.entity_id = 10; - metadata.process_id = 20; - metadata.host_name = "sub-host"; - metadata.topic_name = "sub_topic"; - metadata.encoding = "raw"; - metadata.type_name = "RawData"; - metadata.direction = subscriber; - - writer_->writeTopicMetadata(metadata); - - auto lines = readJsonLines(writer_->getTopicMetadataFilePath()); - ASSERT_EQ(lines.size(), 1); - EXPECT_EQ(lines[0]["direction"].get(), "subscriber"); -} - -TEST_F(TracingWriterTest, WriteMultipleMetadataAppends) -{ - for (int i = 0; i < 3; ++i) - { - STopicMetadata metadata; - metadata.entity_id = 555 + i; - metadata.process_id = 666 + i; - metadata.host_name = "host-" + std::to_string(i); - metadata.topic_name = "topic_" + std::to_string(i); - metadata.encoding = "protobuf"; - metadata.type_name = "TestMessage"; - metadata.direction = (i % 2 == 0) ? publisher : subscriber; - - writer_->writeTopicMetadata(metadata); - } - - auto lines = readJsonLines(writer_->getTopicMetadataFilePath()); - ASSERT_EQ(lines.size(), 3); - - for (int i = 0; i < 3; ++i) - { - EXPECT_EQ(lines[i]["entity_id"].get(), 555 + i); - EXPECT_EQ(lines[i]["process_id"].get(), 666 + i); - EXPECT_EQ(lines[i]["host_name"].get(), "host-" + std::to_string(i)); - EXPECT_EQ(lines[i]["topic_name"].get(), "topic_" + std::to_string(i)); - std::string expected_dir = (i % 2 == 0) ? "publisher" : "subscriber"; - EXPECT_EQ(lines[i]["direction"].get(), expected_dir); - } -} - -TEST_F(TracingWriterTest, EmptyStringMetadataFields) -{ - STopicMetadata metadata; - metadata.host_name = ""; - metadata.topic_name = ""; - metadata.encoding = ""; - metadata.type_name = ""; - metadata.entity_id = 0; - metadata.process_id = 0; - metadata.direction = publisher; - - writer_->writeTopicMetadata(metadata); - - auto lines = readJsonLines(writer_->getTopicMetadataFilePath()); - ASSERT_EQ(lines.size(), 1); - EXPECT_EQ(lines[0]["host_name"].get(), ""); - EXPECT_EQ(lines[0]["topic_name"].get(), ""); - EXPECT_EQ(lines[0]["encoding"].get(), ""); - EXPECT_EQ(lines[0]["type_name"].get(), ""); - EXPECT_EQ(lines[0]["entity_id"].get(), 0); - EXPECT_EQ(lines[0]["process_id"].get(), 0); -} - -TEST_F(TracingWriterTest, WriteBatchSpansCallbackExecution) -{ - SSpanData span{}; - span.entity_id = 7; - span.topic_id = 0; - span.process_id = 8; - span.payload_size = 128; - span.clock = 42; - span.layer = tl_trace_tcp; - span.start_ns = 100; - span.end_ns = 200; - span.op_type = callback_execution; - - writer_->writeBatchSpans({span}); - - auto lines = readJsonLines(writer_->getSpansFilePath()); - ASSERT_EQ(lines.size(), 1); - EXPECT_EQ(lines[0]["op_type"].get(), callback_execution); - EXPECT_EQ(lines[0]["layer"].get(), tl_trace_tcp); -} - -TEST_F(TracingWriterTest, WriteBatchSpansAllLayerTypes) -{ - const std::vector layer_types = { - tl_trace_none, tl_trace_shm, tl_trace_udp, tl_trace_tcp, - tl_trace_shm_udp, tl_trace_shm_tcp, tl_trace_udp_tcp, tl_trace_all - }; - - std::vector batch; - for (auto layer : layer_types) - { - SSpanData span{}; - span.layer = layer; - span.op_type = send; - batch.push_back(span); - } - - writer_->writeBatchSpans(batch); - - auto lines = readJsonLines(writer_->getSpansFilePath()); - ASSERT_EQ(lines.size(), layer_types.size()); - - for (size_t i = 0; i < layer_types.size(); ++i) - { - EXPECT_EQ(lines[i]["layer"].get(), layer_types[i]); - } -} From f3d412e78b4305ec977cb2645e93cbb20360950e Mon Sep 17 00:00:00 2001 From: Ila Hadi-Assar Date: Thu, 23 Apr 2026 11:47:01 +0200 Subject: [PATCH 4/9] added enable/disable configuration option for tracing --- ecal/core/CMakeLists.txt | 3 + ecal/core/include/ecal/config/configuration.h | 2 + ecal/core/include/ecal/config/tracing.h | 36 ++++++ .../core/src/config/configuration_to_yaml.cpp | 24 ++++ ecal/core/src/config/configuration_to_yaml.h | 16 +++ .../core/src/config/default_configuration.cpp | 5 + ecal/core/src/ecal.cpp | 2 +- ecal/core/src/ecal_global_accessors.cpp | 10 +- ecal/core/src/ecal_global_accessors.h | 6 +- ecal/core/src/tracing/trace_provider.cpp | 81 ++------------ ecal/core/src/tracing/trace_provider.h | 49 +++------ .../src/tracing/trace_provider_default.cpp | 104 ++++++++++++++++++ .../core/src/tracing/trace_provider_default.h | 75 +++++++++++++ ecal/core/src/tracing/trace_provider_noop.h | 37 +++++++ .../config_test/src/yaml_processing_test.cpp | 4 + .../tracing_test/src/trace_provider_test.cpp | 6 +- 16 files changed, 340 insertions(+), 120 deletions(-) create mode 100644 ecal/core/include/ecal/config/tracing.h create mode 100644 ecal/core/src/tracing/trace_provider_default.cpp create mode 100644 ecal/core/src/tracing/trace_provider_default.h create mode 100644 ecal/core/src/tracing/trace_provider_noop.h diff --git a/ecal/core/CMakeLists.txt b/ecal/core/CMakeLists.txt index c695a06559..5e3ed110c7 100644 --- a/ecal/core/CMakeLists.txt +++ b/ecal/core/CMakeLists.txt @@ -469,6 +469,9 @@ set(ecal_tracing_src src/tracing/span.h src/tracing/trace_provider.cpp src/tracing/trace_provider.h + src/tracing/trace_provider_default.cpp + src/tracing/trace_provider_default.h + src/tracing/trace_provider_noop.h src/tracing/tracing_writer.h src/tracing/tracing_writer_jsonl.cpp src/tracing/tracing_writer_jsonl.h diff --git a/ecal/core/include/ecal/config/configuration.h b/ecal/core/include/ecal/config/configuration.h index 77728641a5..437cf535db 100644 --- a/ecal/core/include/ecal/config/configuration.h +++ b/ecal/core/include/ecal/config/configuration.h @@ -30,6 +30,7 @@ #include #include #include +#include #include @@ -58,6 +59,7 @@ namespace eCAL Time::Configuration timesync; Application::Configuration application; Logging::Configuration logging; + Tracing::Configuration tracing; eCommunicationMode communication_mode { eCommunicationMode::local }; /*!< eCAL components communication mode: local: local host only communication (default) diff --git a/ecal/core/include/ecal/config/tracing.h b/ecal/core/include/ecal/config/tracing.h new file mode 100644 index 0000000000..92e3f58b11 --- /dev/null +++ b/ecal/core/include/ecal/config/tracing.h @@ -0,0 +1,36 @@ +/* ========================= eCAL LICENSE ================================= + * + * Copyright (C) 2016 - 2025 Continental Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ========================= eCAL LICENSE ================================= +*/ + +/** + * @file config/tracing.h + * @brief eCAL tracing configuration +**/ + +#pragma once + +namespace eCAL +{ + namespace Tracing + { + struct Configuration + { + bool enabled { false }; //!< Enable tracing (Default: false) + }; + } +} diff --git a/ecal/core/src/config/configuration_to_yaml.cpp b/ecal/core/src/config/configuration_to_yaml.cpp index 530ef3f7b4..c6ebb3d456 100644 --- a/ecal/core/src/config/configuration_to_yaml.cpp +++ b/ecal/core/src/config/configuration_to_yaml.cpp @@ -671,6 +671,28 @@ namespace YAML } + /* + ______ _ + /_ __/______ _____(_)__ ___ _ + / / / __/ _ `/ __/ / _ \/ _ `/ + /_/ /_/ \_,_/\__/_/_//_/\_, / + /___/ + */ + + Node convert::encode(const eCAL::Tracing::Configuration& config_) + { + Node node; + node["enabled"] = config_.enabled; + return node; + } + + bool convert::decode(const Node& node_, eCAL::Tracing::Configuration& config_) + { + AssignValue(config_.enabled, node_, "enabled"); + return true; + } + + /* __ ___ _ ____ __ _ / |/ /__ _(_)__ _______ ___ / _(_)__ ___ _________ _/ /_(_)__ ___ @@ -689,6 +711,7 @@ namespace YAML node["time"] = config_.timesync; node["application"] = config_.application; node["logging"] = config_.logging; + node["tracing"] = config_.tracing; node["communication_mode"] = config_.communication_mode == eCAL::eCommunicationMode::network ? "network" : "local"; return node; @@ -703,6 +726,7 @@ namespace YAML AssignValue(config_.timesync, node_, "time"); AssignValue(config_.application, node_, "application"); AssignValue(config_.logging, node_, "logging"); + AssignValue(config_.tracing, node_, "tracing"); std::string communication_mode; AssignValue(communication_mode, node_, "communication_mode"); diff --git a/ecal/core/src/config/configuration_to_yaml.h b/ecal/core/src/config/configuration_to_yaml.h index 012c3e2a0d..8ad27f25f2 100644 --- a/ecal/core/src/config/configuration_to_yaml.h +++ b/ecal/core/src/config/configuration_to_yaml.h @@ -343,6 +343,22 @@ namespace YAML }; + /* + ______ _ + /_ __/______ _____(_)__ ___ _ + / / / __/ _ `/ __/ / _ \/ _ `/ + /_/ /_/ \_,_/\__/_/_//_/\_, / + /___/ + */ + template<> + struct convert + { + static Node encode(const eCAL::Tracing::Configuration& config_); + + static bool decode(const Node& node_, eCAL::Tracing::Configuration& config_); + }; + + /* __ ___ _ ____ __ _ / |/ /__ _(_)__ _______ ___ / _(_)__ ___ _________ _/ /_(_)__ ___ diff --git a/ecal/core/src/config/default_configuration.cpp b/ecal/core/src/config/default_configuration.cpp index 0de9e9c7ed..f69d49d2f2 100644 --- a/ecal/core/src/config/default_configuration.cpp +++ b/ecal/core/src/config/default_configuration.cpp @@ -377,6 +377,11 @@ namespace eCAL ss << R"( # UDP Port for sending logging data)" << "\n"; ss << R"( port: )" << config_.logging.receiver.udp_config.port << "\n"; ss << R"()" << "\n"; + ss << R"(# Tracing configuration)" << "\n"; + ss << R"(tracing:)" << "\n"; + ss << R"( # Enable tracing (Default: false))" << "\n"; + ss << R"( enabled: )" << config_.tracing.enabled << "\n"; + ss << R"()" << "\n"; return ss; } diff --git a/ecal/core/src/ecal.cpp b/ecal/core/src/ecal.cpp index 12066e9cfe..e9e927a243 100644 --- a/ecal/core/src/ecal.cpp +++ b/ecal/core/src/ecal.cpp @@ -118,7 +118,7 @@ namespace eCAL SetGlobalUnitName(unit_name_.c_str()); if ((components_ & Init::Logging) != 0u) InitializeLogging(config_); - InitializeTracing(); + InitializeTracing(config_); auto globals_instance = CreateGlobalsInstance(); if (!globals_instance) return false; diff --git a/ecal/core/src/ecal_global_accessors.cpp b/ecal/core/src/ecal_global_accessors.cpp index ebedb86fe6..33e20fd128 100644 --- a/ecal/core/src/ecal_global_accessors.cpp +++ b/ecal/core/src/ecal_global_accessors.cpp @@ -33,6 +33,8 @@ #include "logging/ecal_log_provider.h" #include "logging/ecal_log_receiver.h" #include "tracing/trace_provider.h" +#include "tracing/trace_provider_default.h" +#include "tracing/trace_provider_noop.h" #include #include @@ -58,7 +60,7 @@ namespace eCAL std::shared_ptr g_log_provider_instance; std::shared_ptr g_log_receiver_instance; - std::shared_ptr g_trace_provider_instance; + std::shared_ptr g_trace_provider_instance; void SetGlobalUnitName(const char *unit_name_) { @@ -121,9 +123,9 @@ namespace eCAL return nullptr; } - void InitializeTracing() + void InitializeTracing(const eCAL::Configuration& config_) { - g_trace_provider_instance = tracing::CTraceProvider::Create(); + g_trace_provider_instance = tracing::TraceProvider::Create(config_.tracing); } void ResetTracing() @@ -131,7 +133,7 @@ namespace eCAL g_trace_provider_instance.reset(); } - std::shared_ptr g_trace_provider() + std::shared_ptr g_trace_provider() { if (auto provider = g_trace_provider_instance; provider) return provider; return nullptr; diff --git a/ecal/core/src/ecal_global_accessors.h b/ecal/core/src/ecal_global_accessors.h index fd7a6d89e0..3fd8ba8ee4 100644 --- a/ecal/core/src/ecal_global_accessors.h +++ b/ecal/core/src/ecal_global_accessors.h @@ -45,7 +45,7 @@ namespace eCAL namespace tracing { - class CTraceProvider; + class TraceProvider; } #if ECAL_CORE_MONITORING @@ -85,7 +85,7 @@ namespace eCAL void InitializeLogging(const eCAL::Configuration& config_); void ResetLogging(); - void InitializeTracing(); + void InitializeTracing(const eCAL::Configuration& config_); void ResetTracing(); // Declaration of getter functions for globally accessible variable instances @@ -118,7 +118,7 @@ namespace eCAL std::shared_ptr g_logging_provider(); std::shared_ptr g_logging_receiver(); - std::shared_ptr g_trace_provider(); + std::shared_ptr g_trace_provider(); // declaration of globally accessible variables extern std::string g_default_ini_file; diff --git a/ecal/core/src/tracing/trace_provider.cpp b/ecal/core/src/tracing/trace_provider.cpp index 66d68e27cd..4b9c74b9b4 100644 --- a/ecal/core/src/tracing/trace_provider.cpp +++ b/ecal/core/src/tracing/trace_provider.cpp @@ -18,87 +18,22 @@ */ #include "trace_provider.h" -#include "tracing_writer.h" -#include "tracing_writer_jsonl.h" -#include "util/single_instance_helper.h" - -#include -#include -#include +#include "trace_provider_default.h" +#include "trace_provider_noop.h" namespace eCAL { namespace tracing { - std::shared_ptr CTraceProvider::Create(std::unique_ptr writer, size_t batch_size) - { - try - { - return Util::CSingleInstanceHelper::Create(std::move(writer), batch_size); - } - catch (const std::exception& e) - { - return nullptr; - } - } - - CTraceProvider::CTraceProvider(std::unique_ptr writer, size_t batch_size) - : batch_size_(batch_size), writer_(std::move(writer)) - { - writer_thread_ = std::thread(&CTraceProvider::WriterThreadLoop, this); - } - - CTraceProvider::~CTraceProvider() - { - { - std::lock_guard lock(thread_mutex); - stop_thread_ = true; - write_cv_.notify_all(); - } - writer_thread_.join(); - } - - - void CTraceProvider::WriteSpan(const SpanDataVariant& span_data) - { - std::lock_guard lock(thread_mutex); - span_buffer_.push_back(span_data); - if (span_buffer_.size() >= batch_size_) - { - write_cv_.notify_one(); - } - } - - void CTraceProvider::WriterThreadLoop() - { - std::vector span_flusher; - while (true) - { - { - std::unique_lock lock(thread_mutex); - write_cv_.wait(lock, [this]() - { - return stop_thread_|| (span_buffer_.size() >= batch_size_); - }); - if (stop_thread_ && span_buffer_.empty()) - { - break; - } - span_flusher.swap(span_buffer_); - } - if (!span_flusher.empty()) - { - writer_->WriteSpansToFile(span_flusher); - span_flusher.clear(); - } - } - } - - void CTraceProvider::WriteMetadata(const STopicMetadata& metadata) + std::shared_ptr TraceProvider::Create(const eCAL::Tracing::Configuration& config_) + { + if (config_.enabled) { - writer_->WriteMetadataToFile(metadata); + return CTraceProviderDefault::Create(); } + return std::make_shared(); + } } // namespace tracing } // namespace eCAL diff --git a/ecal/core/src/tracing/trace_provider.h b/ecal/core/src/tracing/trace_provider.h index ddbcd631fb..6eeb182173 100644 --- a/ecal/core/src/tracing/trace_provider.h +++ b/ecal/core/src/tracing/trace_provider.h @@ -20,54 +20,31 @@ #pragma once #include "tracing.h" -#include "tracing_writer.h" -#include "tracing_writer_jsonl.h" -#include "util/single_instance_helper.h" -#include -#include +#include + #include -#include -#include -#include namespace eCAL { namespace tracing { - class CTraceProvider + class TraceProvider { - friend class Util::CSingleInstanceHelper; - - public: - - static std::shared_ptr Create(std::unique_ptr writer = std::make_unique(), size_t batch_size = kDefaultTracingBatchSize); - - CTraceProvider(const CTraceProvider&) = delete; - CTraceProvider& operator=(const CTraceProvider&) = delete; - CTraceProvider(CTraceProvider&&) = delete; - CTraceProvider& operator=(CTraceProvider&&) = delete; + public: + TraceProvider() = default; + virtual ~TraceProvider() = default; - ~CTraceProvider(); - - // Write span data to buffer (accepts any span type via variant) - void WriteSpan(const SpanDataVariant& span_data); + TraceProvider(const TraceProvider&) = delete; + TraceProvider& operator=(const TraceProvider&) = delete; + TraceProvider(TraceProvider&&) = delete; + TraceProvider& operator=(TraceProvider&&) = delete; - // metadata — written directly to file (no buffering) - void WriteMetadata(const STopicMetadata& metadata); - - private: - CTraceProvider(std::unique_ptr writer, size_t batch_size); - void WriterThreadLoop(); + static std::shared_ptr Create(const eCAL::Tracing::Configuration& config_); - std::atomic batch_size_{kDefaultTracingBatchSize}; - std::vector span_buffer_; - mutable std::mutex thread_mutex; - std::condition_variable write_cv_; - bool stop_thread_{false}; - std::thread writer_thread_; - std::unique_ptr writer_; + virtual void WriteSpan(const SpanDataVariant& span_data) = 0; + virtual void WriteMetadata(const STopicMetadata& metadata) = 0; }; } // namespace tracing diff --git a/ecal/core/src/tracing/trace_provider_default.cpp b/ecal/core/src/tracing/trace_provider_default.cpp new file mode 100644 index 0000000000..794d163fcb --- /dev/null +++ b/ecal/core/src/tracing/trace_provider_default.cpp @@ -0,0 +1,104 @@ +/* ========================= eCAL LICENSE ================================= + * + * Copyright (C) 2016 - 2025 Continental Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ========================= eCAL LICENSE ================================= +*/ + +#include "trace_provider_default.h" +#include "tracing_writer.h" +#include "tracing_writer_jsonl.h" +#include "util/single_instance_helper.h" + +#include +#include +#include + +namespace eCAL +{ +namespace tracing +{ + + std::shared_ptr CTraceProviderDefault::Create(std::unique_ptr writer, size_t batch_size) + { + try + { + return Util::CSingleInstanceHelper::Create(std::move(writer), batch_size); + } + catch (const std::exception& e) + { + return nullptr; + } + } + + CTraceProviderDefault::CTraceProviderDefault(std::unique_ptr writer, size_t batch_size) + : batch_size_(batch_size), writer_(std::move(writer)) + { + writer_thread_ = std::thread(&CTraceProviderDefault::WriterThreadLoop, this); + } + + CTraceProviderDefault::~CTraceProviderDefault() + { + { + std::lock_guard lock(thread_mutex); + stop_thread_ = true; + write_cv_.notify_all(); + } + writer_thread_.join(); + } + + + void CTraceProviderDefault::WriteSpan(const SpanDataVariant& span_data) + { + std::lock_guard lock(thread_mutex); + span_buffer_.push_back(span_data); + if (span_buffer_.size() >= batch_size_) + { + write_cv_.notify_one(); + } + } + + void CTraceProviderDefault::WriterThreadLoop() + { + std::vector span_flusher; + while (true) + { + { + std::unique_lock lock(thread_mutex); + write_cv_.wait(lock, [this]() + { + return stop_thread_|| (span_buffer_.size() >= batch_size_); + }); + if (stop_thread_ && span_buffer_.empty()) + { + break; + } + span_flusher.swap(span_buffer_); + } + if (!span_flusher.empty()) + { + writer_->WriteSpansToFile(span_flusher); + span_flusher.clear(); + } + } + } + + void CTraceProviderDefault::WriteMetadata(const STopicMetadata& metadata) + { + writer_->WriteMetadataToFile(metadata); + } + +} // namespace tracing +} // namespace eCAL diff --git a/ecal/core/src/tracing/trace_provider_default.h b/ecal/core/src/tracing/trace_provider_default.h new file mode 100644 index 0000000000..ad1242d279 --- /dev/null +++ b/ecal/core/src/tracing/trace_provider_default.h @@ -0,0 +1,75 @@ +/* ========================= eCAL LICENSE ================================= + * + * Copyright (C) 2016 - 2025 Continental Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ========================= eCAL LICENSE ================================= +*/ + +#pragma once + +#include "trace_provider.h" +#include "tracing.h" +#include "tracing_writer.h" +#include "tracing_writer_jsonl.h" +#include "util/single_instance_helper.h" + +#include +#include +#include +#include +#include +#include + +namespace eCAL +{ +namespace tracing +{ + + class CTraceProviderDefault : public TraceProvider + { + friend class Util::CSingleInstanceHelper; + + public: + + static std::shared_ptr Create(std::unique_ptr writer = std::make_unique(), size_t batch_size = kDefaultTracingBatchSize); + + CTraceProviderDefault(const CTraceProviderDefault&) = delete; + CTraceProviderDefault& operator=(const CTraceProviderDefault&) = delete; + CTraceProviderDefault(CTraceProviderDefault&&) = delete; + CTraceProviderDefault& operator=(CTraceProviderDefault&&) = delete; + + ~CTraceProviderDefault() override; + + // Write span data to buffer (accepts any span type via variant) + void WriteSpan(const SpanDataVariant& span_data) override; + + // metadata — written directly to file (no buffering) + void WriteMetadata(const STopicMetadata& metadata) override; + + private: + CTraceProviderDefault(std::unique_ptr writer, size_t batch_size); + void WriterThreadLoop(); + + std::atomic batch_size_{kDefaultTracingBatchSize}; + std::vector span_buffer_; + mutable std::mutex thread_mutex; + std::condition_variable write_cv_; + bool stop_thread_{false}; + std::thread writer_thread_; + std::unique_ptr writer_; + }; + +} // namespace tracing +} // namespace eCAL diff --git a/ecal/core/src/tracing/trace_provider_noop.h b/ecal/core/src/tracing/trace_provider_noop.h new file mode 100644 index 0000000000..9fa0b140a4 --- /dev/null +++ b/ecal/core/src/tracing/trace_provider_noop.h @@ -0,0 +1,37 @@ +/* ========================= eCAL LICENSE ================================= + * + * Copyright (C) 2016 - 2025 Continental Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ========================= eCAL LICENSE ================================= +*/ + +#pragma once + +#include "trace_provider.h" + +namespace eCAL +{ +namespace tracing +{ + + class CNoOpTraceProvider : public TraceProvider + { + public: + void WriteSpan(const SpanDataVariant& /*span_data*/) override {} + void WriteMetadata(const STopicMetadata& /*metadata*/) override {} + }; + +} // namespace tracing +} // namespace eCAL diff --git a/ecal/tests/cpp/config_test/src/yaml_processing_test.cpp b/ecal/tests/cpp/config_test/src/yaml_processing_test.cpp index 7a8a0002e8..ee942f01bb 100644 --- a/ecal/tests/cpp/config_test/src/yaml_processing_test.cpp +++ b/ecal/tests/cpp/config_test/src/yaml_processing_test.cpp @@ -90,6 +90,8 @@ TEST(core_cpp_config_yaml /*unused*/, yaml_processing_comparison /*unused*/) config.application.startup.terminal_emulator = "term_emulator"; config.application.sys.filter_excl = "filter_excl"; + config.tracing.enabled = true; + config.logging.provider.console.enable = false; config.logging.provider.console.log_level = eCAL::Logging::eLogLevel::log_level_debug1; config.logging.provider.file.enable = true; @@ -159,6 +161,7 @@ TEST(core_cpp_config_yaml /*unused*/, yaml_processing_comparison /*unused*/) EXPECT_EQ(config.logging.provider.udp_config.port, config_from_yaml.logging.provider.udp_config.port); EXPECT_EQ(config.logging.receiver.enable, config_from_yaml.logging.receiver.enable); EXPECT_EQ(config.logging.receiver.udp_config.port, config_from_yaml.logging.receiver.udp_config.port); + EXPECT_EQ(config.tracing.enabled, config_from_yaml.tracing.enabled); auto yaml_from_config = YAML::Node(config); eCAL::Configuration config_from_yaml_config = yaml_from_config.as(); @@ -217,6 +220,7 @@ TEST(core_cpp_config_yaml /*unused*/, yaml_processing_comparison /*unused*/) EXPECT_EQ(config.logging.provider.udp_config.port, config_from_yaml_config.logging.provider.udp_config.port); EXPECT_EQ(config.logging.receiver.enable, config_from_yaml_config.logging.receiver.enable); EXPECT_EQ(config.logging.receiver.udp_config.port, config_from_yaml_config.logging.receiver.udp_config.port); + EXPECT_EQ(config.tracing.enabled, config_from_yaml_config.tracing.enabled); } TEST(core_cpp_config /*unused*/, read_write_file_test /*unused*/) diff --git a/ecal/tests/cpp/tracing_test/src/trace_provider_test.cpp b/ecal/tests/cpp/tracing_test/src/trace_provider_test.cpp index 0ad76b366f..eed25aad91 100644 --- a/ecal/tests/cpp/tracing_test/src/trace_provider_test.cpp +++ b/ecal/tests/cpp/tracing_test/src/trace_provider_test.cpp @@ -19,7 +19,7 @@ #include "tracing_test_helpers.h" -#include +#include #include #include @@ -39,7 +39,7 @@ TEST(TestTraceProvider, ConcurrentSpanWrites) MockTracingWriter mock_writer; - auto provider = eCAL::tracing::CTraceProvider::Create( + auto provider = eCAL::tracing::CTraceProviderDefault::Create( std::make_unique(mock_writer), batch_size); ASSERT_NE(provider, nullptr); @@ -89,7 +89,7 @@ TEST(TestTraceProvider, ConcurrentMetadataWrites) MockTracingWriter mock_writer; - auto provider = eCAL::tracing::CTraceProvider::Create( + auto provider = eCAL::tracing::CTraceProviderDefault::Create( std::make_unique(mock_writer), batch_size); ASSERT_NE(provider, nullptr); From cf05484ea2cd3da21bc037b1f27efe4d248ff822 Mon Sep 17 00:00:00 2001 From: Ila Hadi-Assar Date: Mon, 27 Apr 2026 10:51:15 +0200 Subject: [PATCH 5/9] mirrored logging path behaviour --- .github/workflows/build-ubuntu.yml | 6 - .github/workflows/build-windows.yml | 7 - ecal/core/include/ecal/config/tracing.h | 5 +- ecal/core/include/ecal/util.h | 19 +++ .../core/src/config/configuration_to_yaml.cpp | 2 + .../core/src/config/default_configuration.cpp | 2 + ecal/core/src/config/ecal_path_processing.cpp | 26 ++++ ecal/core/src/config/ecal_path_processing.h | 19 +++ ecal/core/src/ecal_def.h | 2 + ecal/core/src/ecal_util.cpp | 5 + .../core/src/tracing/tracing_writer_jsonl.cpp | 18 +-- ecal/core/src/tracing/tracing_writer_jsonl.h | 1 + .../config_test/src/path_processing_test.cpp | 71 +++++++++ .../config_test/src/yaml_processing_test.cpp | 3 + .../tracing_test/src/tracing_test_helpers.h | 50 ------ .../tracing_test/src/tracing_writer_test.cpp | 144 ++++++++++-------- 16 files changed, 241 insertions(+), 139 deletions(-) diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml index 8256b2d2d7..7c7c1683a9 100644 --- a/.github/workflows/build-ubuntu.yml +++ b/.github/workflows/build-ubuntu.yml @@ -169,12 +169,6 @@ jobs: # Always save cache if configure succeeded (even if the build failed) if: ${{ always() && steps.cmake-configure.outcome == 'success' }} - - name: Create tracing output directory - run: | - ECAL_TRACING_DATA_DIR="${{ runner.temp }}/ecal_tracing" - mkdir -p "$ECAL_TRACING_DATA_DIR" - echo "ECAL_TRACING_DATA_DIR=$ECAL_TRACING_DATA_DIR" >> "$GITHUB_ENV" - - name: Run Tests run: ctest -V --test-dir _build diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index e0b9dfc3ec..88ccc74afb 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -200,13 +200,6 @@ jobs: run: cmake --build "${{ runner.workspace }}/_build/csharp" --config Release shell: cmd - - name: Create tracing output directory - run: | - $TRACING_DIR = "${{ runner.temp }}\ecal_tracing" - mkdir $TRACING_DIR - echo "ECAL_TRACING_DATA_DIR=$TRACING_DIR" >> $Env:GITHUB_ENV - shell: powershell - - name: Run C# Tests run: ctest -C Release -V --test-dir "${{ runner.workspace }}/_build/csharp" diff --git a/ecal/core/include/ecal/config/tracing.h b/ecal/core/include/ecal/config/tracing.h index 92e3f58b11..46894deccb 100644 --- a/ecal/core/include/ecal/config/tracing.h +++ b/ecal/core/include/ecal/config/tracing.h @@ -24,13 +24,16 @@ #pragma once +#include + namespace eCAL { namespace Tracing { struct Configuration { - bool enabled { false }; //!< Enable tracing (Default: false) + bool enabled { false }; //!< Enable tracing (Default: false) + std::string path { "" }; //!< Path to trace output directory (Default: "") }; } } diff --git a/ecal/core/include/ecal/util.h b/ecal/core/include/ecal/util.h index d957336326..76e2fc32e4 100644 --- a/ecal/core/include/ecal/util.h +++ b/ecal/core/include/ecal/util.h @@ -64,6 +64,25 @@ namespace eCAL **/ ECAL_API std::string GeteCALLogDir(); + /** + * @brief Returns the path to the eCAL trace directory. + * + * Searches in following order: + * 1. Environment variable ECAL_TRACE_DIR + * 2. Environment variable ECAL_DATA (also checking for traces subdirectory) + * 3. The path provided from the configuration + * 4. The path where ecal.yaml was loaded from (also checking for traces subdirectory) + * 5. The temporary directory (e.g. /tmp [unix], Appdata/local/Temp [win]) + * 6. Fallback path /ecal_tmp + * + * In case of 5/6, a unique temporary folder will be created. + * + * @returns The path to the eCAL trace directory. + * The subdirectory traces might not exist yet. + * Returns empty string if no root path could be found. + **/ + ECAL_API std::string GeteCALTraceDir(); + /** * @brief Send shutdown event to specified local user process using it's unit name. * diff --git a/ecal/core/src/config/configuration_to_yaml.cpp b/ecal/core/src/config/configuration_to_yaml.cpp index c6ebb3d456..27e5b83f48 100644 --- a/ecal/core/src/config/configuration_to_yaml.cpp +++ b/ecal/core/src/config/configuration_to_yaml.cpp @@ -683,12 +683,14 @@ namespace YAML { Node node; node["enabled"] = config_.enabled; + node["path"] = config_.path; return node; } bool convert::decode(const Node& node_, eCAL::Tracing::Configuration& config_) { AssignValue(config_.enabled, node_, "enabled"); + AssignValue(config_.path, node_, "path"); return true; } diff --git a/ecal/core/src/config/default_configuration.cpp b/ecal/core/src/config/default_configuration.cpp index f69d49d2f2..6864608391 100644 --- a/ecal/core/src/config/default_configuration.cpp +++ b/ecal/core/src/config/default_configuration.cpp @@ -381,6 +381,8 @@ namespace eCAL ss << R"(tracing:)" << "\n"; ss << R"( # Enable tracing (Default: false))" << "\n"; ss << R"( enabled: )" << config_.tracing.enabled << "\n"; + ss << R"( # Trace output directory)" << "\n"; + ss << R"( path: )" << quoteString(config_.tracing.path) << "\n"; ss << R"()" << "\n"; return ss; diff --git a/ecal/core/src/config/ecal_path_processing.cpp b/ecal/core/src/config/ecal_path_processing.cpp index 1db61189ba..75159a111a 100644 --- a/ecal/core/src/config/ecal_path_processing.cpp +++ b/ecal/core/src/config/ecal_path_processing.cpp @@ -365,6 +365,32 @@ namespace eCAL return dir_provider_.uniqueTmpDir(dir_manager_); } + std::string GeteCALTraceDirImpl(const Util::IDirProvider& dir_provider_ /* = Util::DirProvider() */, const Util::IDirManager& dir_manager_ /* = Util::DirManager() */, const eCAL::Configuration& config_ /* = eCAL::GetConfiguration() */) + { + const std::string config_file_dir = dir_manager_.getDirectoryPath(eCAL::GetConfiguration().GetConfigurationFilePath()); + const std::string ecal_data_env_dir = dir_provider_.eCALEnvVar(ECAL_DATA_VAR); + + const std::vector trace_paths = { + dir_provider_.eCALEnvVar(ECAL_TRACE_VAR), + buildPath(ecal_data_env_dir, ECAL_FOLDER_NAME_TRACE), + ecal_data_env_dir, + config_.tracing.path, + buildPath(config_file_dir, ECAL_FOLDER_NAME_TRACE), + config_file_dir + }; + + for (const auto& path : trace_paths) + { + if (!path.empty() && dir_manager_.dirExists(path) && dir_manager_.canWriteToDirectory(path)) + { + return path; + } + } + + // if no path is available, we create temp directories for tracing + return dir_provider_.uniqueTmpDir(dir_manager_); + } + std::string checkForValidConfigFilePath(const std::string& config_file_, const Util::DirProvider& dir_provider_ /* = Util::DirProvider() */, const Util::DirManager& dir_manager_ /* = Util::DirManager() */) { const std::string cwd_directory_path = EcalUtils::Filesystem::CurrentWorkingDir(); diff --git a/ecal/core/src/config/ecal_path_processing.h b/ecal/core/src/config/ecal_path_processing.h index 75147618a0..a3c727863b 100644 --- a/ecal/core/src/config/ecal_path_processing.h +++ b/ecal/core/src/config/ecal_path_processing.h @@ -221,6 +221,25 @@ namespace eCAL */ std::string GeteCALLogDirImpl(const Util::IDirProvider& dir_provider_ = Util::DirProvider(), const Util::IDirManager& dir_manager_ = Util::DirManager(), const eCAL::Configuration& config_ = eCAL::GetConfiguration()); + /** + * @brief Returns the path to the eCAL trace directory. + * + * Searches in following order: + * 1. Environment variable ECAL_TRACE_DIR + * 2. Environment variable ECAL_DATA (also checking for traces subdirectory) + * 3. The path provided from the configuration + * 4. The path where ecal.yaml was loaded from (also checking for traces subdirectory) + * 5. The temporary directory (e.g. /tmp [unix], Appdata/local/Temp [win]) + * 6. Fallback path /ecal_tmp + * + * In case of 5/6, a unique temporary folder will be created. + * + * @returns The path to the eCAL trace directory. + * The subdirectory traces might not exist yet. + * Returns empty string if no root path could be found. + */ + std::string GeteCALTraceDirImpl(const Util::IDirProvider& dir_provider_ = Util::DirProvider(), const Util::IDirManager& dir_manager_ = Util::DirManager(), const eCAL::Configuration& config_ = eCAL::GetConfiguration()); + /** * @brief Returns the path to the eCAL data directory. Searches in following order: * diff --git a/ecal/core/src/ecal_def.h b/ecal/core/src/ecal_def.h index d77747bf7d..18c82aa6bb 100644 --- a/ecal/core/src/ecal_def.h +++ b/ecal/core/src/ecal_def.h @@ -35,6 +35,7 @@ constexpr const char* ECAL_FOLDER_NAME_WINDOWS = "eCAL"; constexpr const char* ECAL_FOLDER_NAME_LINUX = "ecal"; constexpr const char* ECAL_FOLDER_NAME_HOME_LINUX = ".ecal"; constexpr const char* ECAL_FOLDER_NAME_LOG = "logs"; +constexpr const char* ECAL_FOLDER_NAME_TRACE = "traces"; constexpr const char* ECAL_FOLDER_NAME_TMP_WINDOWS = "Temp"; #ifdef ECAL_OS_WINDOWS @@ -51,6 +52,7 @@ constexpr const char* ECAL_DEFAULT_CFG = "ecal.yaml"; /* environment variables */ constexpr const char* ECAL_DATA_VAR = "ECAL_DATA"; constexpr const char* ECAL_LOG_VAR = "ECAL_LOG_DIR"; +constexpr const char* ECAL_TRACE_VAR = "ECAL_TRACE_DIR"; constexpr const char* ECAL_LINUX_HOME_VAR = "HOME"; constexpr const char* ECAL_LINUX_TMP_VAR = "TMPDIR"; diff --git a/ecal/core/src/ecal_util.cpp b/ecal/core/src/ecal_util.cpp index ae9c4815ba..5cb42ffb18 100644 --- a/ecal/core/src/ecal_util.cpp +++ b/ecal/core/src/ecal_util.cpp @@ -46,6 +46,11 @@ namespace eCAL return eCAL::Config::GeteCALLogDirImpl(); } + std::string GeteCALTraceDir() + { + return eCAL::Config::GeteCALTraceDirImpl(); + } + #if ECAL_CORE_MONITORING // take monitoring snapshot static Monitoring::SMonitoring GetMonitoring() diff --git a/ecal/core/src/tracing/tracing_writer_jsonl.cpp b/ecal/core/src/tracing/tracing_writer_jsonl.cpp index 9e8627aa87..99587bb4c2 100644 --- a/ecal/core/src/tracing/tracing_writer_jsonl.cpp +++ b/ecal/core/src/tracing/tracing_writer_jsonl.cpp @@ -28,6 +28,7 @@ #include #include +#include #include using json = nlohmann::json; @@ -48,28 +49,17 @@ namespace tracing CTracingWriterJSONL::CTracingWriterJSONL() : timestamp_(GetCurrentTimestamp()) + , trace_dir_(eCAL::Util::GeteCALTraceDir()) {} std::string CTracingWriterJSONL::GetSpansFilePath() const { - const char* data_dir = std::getenv("ECAL_TRACING_DATA_DIR"); - if (data_dir == nullptr) - { - std::cerr << "Fatal: Mandatory environment variable ECAL_TRACING_DATA_DIR is not set." << std::endl; - std::abort(); - } - return std::string(data_dir) + "/ecal_spans_" + std::to_string(eCAL::Process::GetProcessID()) + "_" + timestamp_ + ".jsonl"; + return trace_dir_ + "/ecal_spans_" + std::to_string(eCAL::Process::GetProcessID()) + "_" + timestamp_ + ".jsonl"; } std::string CTracingWriterJSONL::GetTopicMetadataFilePath() const { - const char* data_dir = std::getenv("ECAL_TRACING_DATA_DIR"); - if (data_dir == nullptr) - { - std::cerr << "Fatal: Mandatory environment variable ECAL_TRACING_DATA_DIR is not set." << std::endl; - std::abort(); - } - return std::string(data_dir) + "/ecal_topic_metadata_" + std::to_string(eCAL::Process::GetProcessID()) + "_" + timestamp_ + ".jsonl"; + return trace_dir_ + "/ecal_topic_metadata_" + std::to_string(eCAL::Process::GetProcessID()) + "_" + timestamp_ + ".jsonl"; } void CTracingWriterJSONL::WriteSpansToFile(const std::vector& batch) diff --git a/ecal/core/src/tracing/tracing_writer_jsonl.h b/ecal/core/src/tracing/tracing_writer_jsonl.h index a14269bba7..8f01b43e45 100644 --- a/ecal/core/src/tracing/tracing_writer_jsonl.h +++ b/ecal/core/src/tracing/tracing_writer_jsonl.h @@ -58,6 +58,7 @@ namespace tracing mutable std::mutex spans_mutex_; mutable std::mutex metadata_mutex_; std::string timestamp_; + std::string trace_dir_; }; } // namespace tracing diff --git a/ecal/tests/cpp/config_test/src/path_processing_test.cpp b/ecal/tests/cpp/config_test/src/path_processing_test.cpp index 68f7644f16..d3cacc75cd 100644 --- a/ecal/tests/cpp/config_test/src/path_processing_test.cpp +++ b/ecal/tests/cpp/config_test/src/path_processing_test.cpp @@ -338,3 +338,74 @@ TEST(core_cpp_path_processing /*unused*/, ecal_log_order_test /*unused*/) EXPECT_EQ(eCAL::Config::GeteCALLogDirImpl(mock_dir_provider, mock_dir_manager, config), ecal_yaml_dir); EXPECT_EQ(eCAL::Config::GeteCALLogDirImpl(mock_dir_provider, mock_dir_manager, config), unique_tmp_dir); } + +TEST(core_cpp_path_processing /*unused*/, ecal_trace_order_test /*unused*/) +{ + const std::string ecal_trace_env_var = "/ecal/trace/env"; + const std::string ecal_data_env_var = "/ecal/data/env"; + const std::string ecal_data_env_trace_var = ecal_data_env_var + path_separator + ECAL_FOLDER_NAME_TRACE; + const std::string ecal_config_trace_dir = "/config/tracing/dir"; + const std::string ecal_yaml_dir = "/dir/to/current/yaml"; + const std::string ecal_yaml_trace_dir = ecal_yaml_dir + path_separator + ECAL_FOLDER_NAME_TRACE; + const std::string unique_tmp_dir = "/tmp/unique"; + + const MockDirProvider mock_dir_provider; + const NiceMock mock_dir_manager; + + EXPECT_CALL(mock_dir_provider, eCALEnvVar(ECAL_TRACE_VAR)) + .Times(7) + .WillOnce(testing::Return(ecal_trace_env_var)) + .WillRepeatedly(testing::Return("")); + EXPECT_CALL(mock_dir_provider, eCALEnvVar(ECAL_DATA_VAR)) + .Times(7) + .WillRepeatedly(testing::Return(ecal_data_env_var)); + + EXPECT_CALL(mock_dir_manager, getDirectoryPath(testing::_)) + .Times(7) + .WillRepeatedly(testing::Return(ecal_yaml_dir)); + + EXPECT_CALL(mock_dir_manager, dirExists(ecal_trace_env_var)) + .Times(1) + .WillOnce(testing::Return(true)); + EXPECT_CALL(mock_dir_manager, dirExists(ecal_data_env_trace_var)) + .Times(6) + .WillOnce(testing::Return(true)) + .WillRepeatedly(testing::Return(false)); + EXPECT_CALL(mock_dir_manager, dirExists(ecal_data_env_var)) + .Times(5) + .WillOnce(testing::Return(true)) + .WillRepeatedly(testing::Return(false)); + EXPECT_CALL(mock_dir_manager, dirExists(ecal_config_trace_dir)) + .Times(1) + .WillOnce(testing::Return(true)) + .WillRepeatedly(testing::Return(false)); + EXPECT_CALL(mock_dir_manager, dirExists(ecal_yaml_trace_dir)) + .Times(3) + .WillOnce(testing::Return(true)) + .WillRepeatedly(testing::Return(false)); + EXPECT_CALL(mock_dir_manager, dirExists(ecal_yaml_dir)) + .Times(2) + .WillOnce(testing::Return(true)) + .WillRepeatedly(testing::Return(false)); + EXPECT_CALL(mock_dir_manager, dirExistsOrCreate(testing::_)) + .Times(0); + + EXPECT_CALL(mock_dir_provider, uniqueTmpDir(::testing::Ref(mock_dir_manager))) + .Times(1) + .WillRepeatedly(testing::Return(unique_tmp_dir)); + + ON_CALL(mock_dir_manager, dirExists(testing::_)).WillByDefault(testing::Return(false)); + ON_CALL(mock_dir_manager, canWriteToDirectory(testing::_)).WillByDefault(testing::Return(true)); + + auto config = eCAL::GetConfiguration(); + config.tracing.path = ecal_config_trace_dir; + + EXPECT_EQ(eCAL::Config::GeteCALTraceDirImpl(mock_dir_provider, mock_dir_manager, config), ecal_trace_env_var); + EXPECT_EQ(eCAL::Config::GeteCALTraceDirImpl(mock_dir_provider, mock_dir_manager, config), ecal_data_env_trace_var); + EXPECT_EQ(eCAL::Config::GeteCALTraceDirImpl(mock_dir_provider, mock_dir_manager, config), ecal_data_env_var); + EXPECT_EQ(eCAL::Config::GeteCALTraceDirImpl(mock_dir_provider, mock_dir_manager, config), ecal_config_trace_dir); + config.tracing.path = ""; + EXPECT_EQ(eCAL::Config::GeteCALTraceDirImpl(mock_dir_provider, mock_dir_manager, config), ecal_yaml_trace_dir); + EXPECT_EQ(eCAL::Config::GeteCALTraceDirImpl(mock_dir_provider, mock_dir_manager, config), ecal_yaml_dir); + EXPECT_EQ(eCAL::Config::GeteCALTraceDirImpl(mock_dir_provider, mock_dir_manager, config), unique_tmp_dir); +} diff --git a/ecal/tests/cpp/config_test/src/yaml_processing_test.cpp b/ecal/tests/cpp/config_test/src/yaml_processing_test.cpp index ee942f01bb..9a7ded1942 100644 --- a/ecal/tests/cpp/config_test/src/yaml_processing_test.cpp +++ b/ecal/tests/cpp/config_test/src/yaml_processing_test.cpp @@ -91,6 +91,7 @@ TEST(core_cpp_config_yaml /*unused*/, yaml_processing_comparison /*unused*/) config.application.sys.filter_excl = "filter_excl"; config.tracing.enabled = true; + config.tracing.path = "trace_config_path"; config.logging.provider.console.enable = false; config.logging.provider.console.log_level = eCAL::Logging::eLogLevel::log_level_debug1; @@ -162,6 +163,7 @@ TEST(core_cpp_config_yaml /*unused*/, yaml_processing_comparison /*unused*/) EXPECT_EQ(config.logging.receiver.enable, config_from_yaml.logging.receiver.enable); EXPECT_EQ(config.logging.receiver.udp_config.port, config_from_yaml.logging.receiver.udp_config.port); EXPECT_EQ(config.tracing.enabled, config_from_yaml.tracing.enabled); + EXPECT_EQ(config.tracing.path, config_from_yaml.tracing.path); auto yaml_from_config = YAML::Node(config); eCAL::Configuration config_from_yaml_config = yaml_from_config.as(); @@ -221,6 +223,7 @@ TEST(core_cpp_config_yaml /*unused*/, yaml_processing_comparison /*unused*/) EXPECT_EQ(config.logging.receiver.enable, config_from_yaml_config.logging.receiver.enable); EXPECT_EQ(config.logging.receiver.udp_config.port, config_from_yaml_config.logging.receiver.udp_config.port); EXPECT_EQ(config.tracing.enabled, config_from_yaml_config.tracing.enabled); + EXPECT_EQ(config.tracing.path, config_from_yaml_config.tracing.path); } TEST(core_cpp_config /*unused*/, read_write_file_test /*unused*/) diff --git a/ecal/tests/cpp/tracing_test/src/tracing_test_helpers.h b/ecal/tests/cpp/tracing_test/src/tracing_test_helpers.h index faa12befb2..d467b61373 100644 --- a/ecal/tests/cpp/tracing_test/src/tracing_test_helpers.h +++ b/ecal/tests/cpp/tracing_test/src/tracing_test_helpers.h @@ -25,7 +25,6 @@ #include #include -#include #include #include #include @@ -107,52 +106,3 @@ inline size_t CountAndValidateJsonlLines(const std::string& filepath) return count; } -// Cross-platform helpers for environment variable manipulation. -inline void SetEnv(const std::string& name, const std::string& value) -{ -#ifdef _WIN32 - _putenv_s(name.c_str(), value.c_str()); -#else - setenv(name.c_str(), value.c_str(), 1); -#endif -} - -inline void UnsetEnv(const std::string& name) -{ -#ifdef _WIN32 - _putenv_s(name.c_str(), ""); -#else - unsetenv(name.c_str()); -#endif -} - -// RAII helper to set and restore an environment variable. -class ScopedEnvVar -{ -public: - ScopedEnvVar(const char* name, const std::string& value) - : name_(name) - { - const char* old = std::getenv(name); - had_old_ = (old != nullptr); - if (had_old_) old_value_ = old; - SetEnv(name_, value); - } - - ~ScopedEnvVar() - { - if (had_old_) - SetEnv(name_, old_value_); - else - UnsetEnv(name_); - } - - ScopedEnvVar(const ScopedEnvVar&) = delete; - ScopedEnvVar& operator=(const ScopedEnvVar&) = delete; - -private: - std::string name_; - std::string old_value_; - bool had_old_{false}; -}; - diff --git a/ecal/tests/cpp/tracing_test/src/tracing_writer_test.cpp b/ecal/tests/cpp/tracing_test/src/tracing_writer_test.cpp index 98ae5dcbc7..e456241829 100644 --- a/ecal/tests/cpp/tracing_test/src/tracing_writer_test.cpp +++ b/ecal/tests/cpp/tracing_test/src/tracing_writer_test.cpp @@ -19,6 +19,8 @@ #include "tracing_test_helpers.h" +#include + #include #include @@ -27,7 +29,7 @@ #include #include -#include +#include #include #include #include @@ -35,20 +37,30 @@ #include +namespace +{ +eCAL::Configuration GetTracingConfiguration(const std::string& path) +{ + eCAL::Configuration config; + config.tracing.path = path; + return config; +} +} + TEST(TestTracingWriterJSONL, ConcurrentSpanWrites) { constexpr size_t num_threads = 100; constexpr size_t batches_per_thread = 50; constexpr size_t spans_per_batch = 500; constexpr size_t total_spans = num_threads * batches_per_thread * spans_per_batch; + auto ecal_config = GetTracingConfiguration("./"); + ASSERT_TRUE(eCAL::Initialize(ecal_config, "", eCAL::Init::None)); - // Use a temporary directory for output files. - auto tmp_dir = std::filesystem::temp_directory_path() / "ecal_tracing_writer_test_spans"; - std::filesystem::create_directories(tmp_dir); - ScopedEnvVar env("ECAL_TRACING_DATA_DIR", tmp_dir.string()); + std::string spans_path; { eCAL::tracing::CTracingWriterJSONL writer; + spans_path = writer.GetSpansFilePath(); Barrier barrier(num_threads); @@ -85,15 +97,15 @@ TEST(TestTracingWriterJSONL, ConcurrentSpanWrites) } for (auto& th : threads) th.join(); - - // Verify: every span line must be valid JSON and the total count must match. - std::string spans_path = writer.GetSpansFilePath(); - size_t line_count = CountAndValidateJsonlLines(spans_path); - EXPECT_EQ(line_count, total_spans); } - // Clean up temp files. - std::filesystem::remove_all(tmp_dir); + eCAL::Finalize(); + + // Verify: every span line must be valid JSON and the total count must match. + size_t line_count = CountAndValidateJsonlLines(spans_path); + EXPECT_EQ(line_count, total_spans); + + if (!spans_path.empty()) std::remove(spans_path.c_str()); } TEST(TestTracingWriterJSONL, ConcurrentMetadataWrites) @@ -101,13 +113,14 @@ TEST(TestTracingWriterJSONL, ConcurrentMetadataWrites) constexpr size_t num_threads = 100; constexpr size_t metadata_per_thread = 200; constexpr size_t total_metadata = num_threads * metadata_per_thread; + auto ecal_config = GetTracingConfiguration("./"); + ASSERT_TRUE(eCAL::Initialize(ecal_config, "", eCAL::Init::None)); - auto tmp_dir = std::filesystem::temp_directory_path() / "ecal_tracing_writer_test_metadata"; - std::filesystem::create_directories(tmp_dir); - ScopedEnvVar env("ECAL_TRACING_DATA_DIR", tmp_dir.string()); + std::string metadata_path; { eCAL::tracing::CTracingWriterJSONL writer; + metadata_path = writer.GetTopicMetadataFilePath(); Barrier barrier(num_threads); @@ -136,23 +149,26 @@ TEST(TestTracingWriterJSONL, ConcurrentMetadataWrites) } for (auto& th : threads) th.join(); - - std::string metadata_path = writer.GetTopicMetadataFilePath(); - size_t line_count = CountAndValidateJsonlLines(metadata_path); - EXPECT_EQ(line_count, total_metadata); } - std::filesystem::remove_all(tmp_dir); + eCAL::Finalize(); + + size_t line_count = CountAndValidateJsonlLines(metadata_path); + EXPECT_EQ(line_count, total_metadata); + + if (!metadata_path.empty()) std::remove(metadata_path.c_str()); } TEST(TestTracingWriterJSONL, PublisherSpanJsonFields) { - auto tmp_dir = std::filesystem::temp_directory_path() / "ecal_tracing_writer_test_pub_fields"; - std::filesystem::create_directories(tmp_dir); - ScopedEnvVar env("ECAL_TRACING_DATA_DIR", tmp_dir.string()); + auto ecal_config = GetTracingConfiguration("./"); + ASSERT_TRUE(eCAL::Initialize(ecal_config, "", eCAL::Init::None)); + + std::string spans_path; { eCAL::tracing::CTracingWriterJSONL writer; + spans_path = writer.GetSpansFilePath(); eCAL::tracing::SPublisherSpanData span{}; span.op_type = eCAL::tracing::operation_type::send; @@ -167,35 +183,39 @@ TEST(TestTracingWriterJSONL, PublisherSpanJsonFields) std::vector batch; batch.push_back(span); writer.WriteSpansToFile(batch); - - std::ifstream file(writer.GetSpansFilePath()); - ASSERT_TRUE(file.is_open()); - - std::string line; - ASSERT_TRUE(std::getline(file, line)); - auto j = nlohmann::json::parse(line); - - EXPECT_EQ(j.at("op_type"), static_cast(eCAL::tracing::operation_type::send)); - EXPECT_EQ(j.at("entity_id"), 42u); - EXPECT_EQ(j.at("process_id"), 123u); - EXPECT_EQ(j.at("payload_size"), 256u); - EXPECT_EQ(j.at("clock"), 7); - EXPECT_EQ(j.at("layer"), static_cast(eCAL::tracing::tl_trace_udp)); - EXPECT_EQ(j.at("start_ns"), 1000); - EXPECT_EQ(j.at("end_ns"), 2000); } - std::filesystem::remove_all(tmp_dir); + eCAL::Finalize(); + + std::ifstream file(spans_path); + ASSERT_TRUE(file.is_open()); + + std::string line; + ASSERT_TRUE(std::getline(file, line)); + auto j = nlohmann::json::parse(line); + + EXPECT_EQ(j.at("op_type"), static_cast(eCAL::tracing::operation_type::send)); + EXPECT_EQ(j.at("entity_id"), 42u); + EXPECT_EQ(j.at("process_id"), 123u); + EXPECT_EQ(j.at("payload_size"), 256u); + EXPECT_EQ(j.at("clock"), 7); + EXPECT_EQ(j.at("layer"), static_cast(eCAL::tracing::tl_trace_udp)); + EXPECT_EQ(j.at("start_ns"), 1000); + EXPECT_EQ(j.at("end_ns"), 2000); + + if (!spans_path.empty()) std::remove(spans_path.c_str()); } TEST(TestTracingWriterJSONL, SubscriberSpanJsonFields) { - auto tmp_dir = std::filesystem::temp_directory_path() / "ecal_tracing_writer_test_sub_fields"; - std::filesystem::create_directories(tmp_dir); - ScopedEnvVar env("ECAL_TRACING_DATA_DIR", tmp_dir.string()); + auto ecal_config = GetTracingConfiguration("./"); + ASSERT_TRUE(eCAL::Initialize(ecal_config, "", eCAL::Init::None)); + + std::string spans_path; { eCAL::tracing::CTracingWriterJSONL writer; + spans_path = writer.GetSpansFilePath(); eCAL::tracing::SSubscriberSpanData span{}; span.op_type = eCAL::tracing::operation_type::receive; @@ -211,25 +231,27 @@ TEST(TestTracingWriterJSONL, SubscriberSpanJsonFields) std::vector batch; batch.push_back(span); writer.WriteSpansToFile(batch); - - std::ifstream file(writer.GetSpansFilePath()); - ASSERT_TRUE(file.is_open()); - - std::string line; - ASSERT_TRUE(std::getline(file, line)); - auto j = nlohmann::json::parse(line); - - EXPECT_EQ(j.at("op_type"), static_cast(eCAL::tracing::operation_type::receive)); - EXPECT_EQ(j.at("entity_id"), 99u); - EXPECT_EQ(j.at("topic_id"), 55u); - EXPECT_EQ(j.at("process_id"), 456u); - EXPECT_EQ(j.at("payload_size"), 512u); - EXPECT_EQ(j.at("clock"), 3); - EXPECT_EQ(j.at("layer"), static_cast(eCAL::tracing::tl_trace_tcp)); - EXPECT_EQ(j.at("start_ns"), 3000); - EXPECT_EQ(j.at("end_ns"), 4000); } - std::filesystem::remove_all(tmp_dir); + eCAL::Finalize(); + + std::ifstream file(spans_path); + ASSERT_TRUE(file.is_open()); + + std::string line; + ASSERT_TRUE(std::getline(file, line)); + auto j = nlohmann::json::parse(line); + + EXPECT_EQ(j.at("op_type"), static_cast(eCAL::tracing::operation_type::receive)); + EXPECT_EQ(j.at("entity_id"), 99u); + EXPECT_EQ(j.at("topic_id"), 55u); + EXPECT_EQ(j.at("process_id"), 456u); + EXPECT_EQ(j.at("payload_size"), 512u); + EXPECT_EQ(j.at("clock"), 3); + EXPECT_EQ(j.at("layer"), static_cast(eCAL::tracing::tl_trace_tcp)); + EXPECT_EQ(j.at("start_ns"), 3000); + EXPECT_EQ(j.at("end_ns"), 4000); + + if (!spans_path.empty()) std::remove(spans_path.c_str()); } From 7c3e0424ccce8793a0b4a7a6a35e966ed20736f4 Mon Sep 17 00:00:00 2001 From: Ila Hadi-Assar Date: Tue, 28 Apr 2026 16:34:18 +0200 Subject: [PATCH 6/9] removed trace dir config parameter --- ecal/core/include/ecal/config/tracing.h | 3 +- ecal/core/include/ecal/util.h | 9 ++- .../core/src/config/configuration_to_yaml.cpp | 2 - .../core/src/config/default_configuration.cpp | 2 - ecal/core/src/config/ecal_path_processing.cpp | 69 ++++++++----------- ecal/core/src/config/ecal_path_processing.h | 11 ++- .../config_test/src/path_processing_test.cpp | 19 ++--- .../config_test/src/yaml_processing_test.cpp | 3 - .../tracing_test/src/tracing_test_helpers.h | 46 +++++++++++++ .../tracing_test/src/tracing_writer_test.cpp | 22 +++--- 10 files changed, 97 insertions(+), 89 deletions(-) diff --git a/ecal/core/include/ecal/config/tracing.h b/ecal/core/include/ecal/config/tracing.h index 46894deccb..7f2ec86aa1 100644 --- a/ecal/core/include/ecal/config/tracing.h +++ b/ecal/core/include/ecal/config/tracing.h @@ -32,8 +32,7 @@ namespace eCAL { struct Configuration { - bool enabled { false }; //!< Enable tracing (Default: false) - std::string path { "" }; //!< Path to trace output directory (Default: "") + bool enabled { false }; //!< Enable tracing (Default: false) }; } } diff --git a/ecal/core/include/ecal/util.h b/ecal/core/include/ecal/util.h index 76e2fc32e4..1b5793ee6a 100644 --- a/ecal/core/include/ecal/util.h +++ b/ecal/core/include/ecal/util.h @@ -70,12 +70,11 @@ namespace eCAL * Searches in following order: * 1. Environment variable ECAL_TRACE_DIR * 2. Environment variable ECAL_DATA (also checking for traces subdirectory) - * 3. The path provided from the configuration - * 4. The path where ecal.yaml was loaded from (also checking for traces subdirectory) - * 5. The temporary directory (e.g. /tmp [unix], Appdata/local/Temp [win]) - * 6. Fallback path /ecal_tmp + * 3. The path where ecal.yaml was loaded from (also checking for traces subdirectory) + * 4. The temporary directory (e.g. /tmp [unix], Appdata/local/Temp [win]) + * 5. Fallback path /ecal_tmp * - * In case of 5/6, a unique temporary folder will be created. + * In case of 4/5, a unique temporary folder will be created. * * @returns The path to the eCAL trace directory. * The subdirectory traces might not exist yet. diff --git a/ecal/core/src/config/configuration_to_yaml.cpp b/ecal/core/src/config/configuration_to_yaml.cpp index 27e5b83f48..c6ebb3d456 100644 --- a/ecal/core/src/config/configuration_to_yaml.cpp +++ b/ecal/core/src/config/configuration_to_yaml.cpp @@ -683,14 +683,12 @@ namespace YAML { Node node; node["enabled"] = config_.enabled; - node["path"] = config_.path; return node; } bool convert::decode(const Node& node_, eCAL::Tracing::Configuration& config_) { AssignValue(config_.enabled, node_, "enabled"); - AssignValue(config_.path, node_, "path"); return true; } diff --git a/ecal/core/src/config/default_configuration.cpp b/ecal/core/src/config/default_configuration.cpp index 6864608391..f69d49d2f2 100644 --- a/ecal/core/src/config/default_configuration.cpp +++ b/ecal/core/src/config/default_configuration.cpp @@ -381,8 +381,6 @@ namespace eCAL ss << R"(tracing:)" << "\n"; ss << R"( # Enable tracing (Default: false))" << "\n"; ss << R"( enabled: )" << config_.tracing.enabled << "\n"; - ss << R"( # Trace output directory)" << "\n"; - ss << R"( path: )" << quoteString(config_.tracing.path) << "\n"; ss << R"()" << "\n"; return ss; diff --git a/ecal/core/src/config/ecal_path_processing.cpp b/ecal/core/src/config/ecal_path_processing.cpp index 75159a111a..32843ca01a 100644 --- a/ecal/core/src/config/ecal_path_processing.cpp +++ b/ecal/core/src/config/ecal_path_processing.cpp @@ -178,6 +178,31 @@ namespace #endif } + + std::string GeteCALOutputDirImpl(const eCAL::Util::IDirProvider& dir_provider_, const eCAL::Util::IDirManager& dir_manager_, const std::string& env_var_name_, const std::string& output_subdirectory_, const std::string& config_file_path_, const std::string& config_output_dir_) + { + const std::string config_file_dir = dir_manager_.getDirectoryPath(config_file_path_); + const std::string ecal_data_env_dir = dir_provider_.eCALEnvVar(ECAL_DATA_VAR); + + const std::vector output_paths = { + dir_provider_.eCALEnvVar(env_var_name_), + buildPath(ecal_data_env_dir, output_subdirectory_), + ecal_data_env_dir, + config_output_dir_, + buildPath(config_file_dir, output_subdirectory_), + config_file_dir + }; + + for (const auto& path : output_paths) + { + if (!path.empty() && dir_manager_.dirExists(path) && dir_manager_.canWriteToDirectory(path)) + { + return path; + } + } + + return dir_provider_.uniqueTmpDir(dir_manager_); + } } namespace eCAL @@ -340,55 +365,15 @@ namespace eCAL { std::string GeteCALLogDirImpl(const Util::IDirProvider& dir_provider_ /* = Util::DirProvider() */, const Util::IDirManager& dir_manager_ /* = Util::DirManager() */, const eCAL::Configuration& config_ /* = eCAL::GetConfiguration() */) { - const std::string config_file_dir = dir_manager_.getDirectoryPath(eCAL::GetConfiguration().GetConfigurationFilePath()); - const std::string ecal_data_env_dir = dir_provider_.eCALEnvVar(ECAL_DATA_VAR); - - const std::vector log_paths = { - dir_provider_.eCALEnvVar(ECAL_LOG_VAR), - buildPath(ecal_data_env_dir, ECAL_FOLDER_NAME_LOG), - ecal_data_env_dir, - config_.logging.provider.file_config.path, - buildPath(config_file_dir, ECAL_FOLDER_NAME_LOG), - config_file_dir - }; - - for (const auto& path : log_paths) - { - if (!path.empty() && dir_manager_.dirExists(path) && dir_manager_.canWriteToDirectory(path)) - { - return path; - } - } - // if no path is available, we create temp directories for logging // check now for a tmp directory and return - return dir_provider_.uniqueTmpDir(dir_manager_); + return GeteCALOutputDirImpl(dir_provider_, dir_manager_, ECAL_LOG_VAR, ECAL_FOLDER_NAME_LOG, config_.GetConfigurationFilePath(), config_.logging.provider.file_config.path); } std::string GeteCALTraceDirImpl(const Util::IDirProvider& dir_provider_ /* = Util::DirProvider() */, const Util::IDirManager& dir_manager_ /* = Util::DirManager() */, const eCAL::Configuration& config_ /* = eCAL::GetConfiguration() */) { - const std::string config_file_dir = dir_manager_.getDirectoryPath(eCAL::GetConfiguration().GetConfigurationFilePath()); - const std::string ecal_data_env_dir = dir_provider_.eCALEnvVar(ECAL_DATA_VAR); - - const std::vector trace_paths = { - dir_provider_.eCALEnvVar(ECAL_TRACE_VAR), - buildPath(ecal_data_env_dir, ECAL_FOLDER_NAME_TRACE), - ecal_data_env_dir, - config_.tracing.path, - buildPath(config_file_dir, ECAL_FOLDER_NAME_TRACE), - config_file_dir - }; - - for (const auto& path : trace_paths) - { - if (!path.empty() && dir_manager_.dirExists(path) && dir_manager_.canWriteToDirectory(path)) - { - return path; - } - } - // if no path is available, we create temp directories for tracing - return dir_provider_.uniqueTmpDir(dir_manager_); + return GeteCALOutputDirImpl(dir_provider_, dir_manager_, ECAL_TRACE_VAR, ECAL_FOLDER_NAME_TRACE, config_.GetConfigurationFilePath(), {}); } std::string checkForValidConfigFilePath(const std::string& config_file_, const Util::DirProvider& dir_provider_ /* = Util::DirProvider() */, const Util::DirManager& dir_manager_ /* = Util::DirManager() */) diff --git a/ecal/core/src/config/ecal_path_processing.h b/ecal/core/src/config/ecal_path_processing.h index a3c727863b..a0143e4bbc 100644 --- a/ecal/core/src/config/ecal_path_processing.h +++ b/ecal/core/src/config/ecal_path_processing.h @@ -227,18 +227,17 @@ namespace eCAL * Searches in following order: * 1. Environment variable ECAL_TRACE_DIR * 2. Environment variable ECAL_DATA (also checking for traces subdirectory) - * 3. The path provided from the configuration - * 4. The path where ecal.yaml was loaded from (also checking for traces subdirectory) - * 5. The temporary directory (e.g. /tmp [unix], Appdata/local/Temp [win]) - * 6. Fallback path /ecal_tmp + * 3. The path where ecal.yaml was loaded from (also checking for traces subdirectory) + * 4. The temporary directory (e.g. /tmp [unix], Appdata/local/Temp [win]) + * 5. Fallback path /ecal_tmp * - * In case of 5/6, a unique temporary folder will be created. + * In case of 4/5, a unique temporary folder will be created. * * @returns The path to the eCAL trace directory. * The subdirectory traces might not exist yet. * Returns empty string if no root path could be found. */ - std::string GeteCALTraceDirImpl(const Util::IDirProvider& dir_provider_ = Util::DirProvider(), const Util::IDirManager& dir_manager_ = Util::DirManager(), const eCAL::Configuration& config_ = eCAL::GetConfiguration()); + std::string GeteCALTraceDirImpl(const Util::IDirProvider& dir_provider_ = Util::DirProvider(), const Util::IDirManager& dir_manager_ = Util::DirManager(), const eCAL::Configuration& config_ = eCAL::GetConfiguration()); /** * @brief Returns the path to the eCAL data directory. Searches in following order: diff --git a/ecal/tests/cpp/config_test/src/path_processing_test.cpp b/ecal/tests/cpp/config_test/src/path_processing_test.cpp index d3cacc75cd..c57085f022 100644 --- a/ecal/tests/cpp/config_test/src/path_processing_test.cpp +++ b/ecal/tests/cpp/config_test/src/path_processing_test.cpp @@ -344,7 +344,6 @@ TEST(core_cpp_path_processing /*unused*/, ecal_trace_order_test /*unused*/) const std::string ecal_trace_env_var = "/ecal/trace/env"; const std::string ecal_data_env_var = "/ecal/data/env"; const std::string ecal_data_env_trace_var = ecal_data_env_var + path_separator + ECAL_FOLDER_NAME_TRACE; - const std::string ecal_config_trace_dir = "/config/tracing/dir"; const std::string ecal_yaml_dir = "/dir/to/current/yaml"; const std::string ecal_yaml_trace_dir = ecal_yaml_dir + path_separator + ECAL_FOLDER_NAME_TRACE; const std::string unique_tmp_dir = "/tmp/unique"; @@ -353,32 +352,29 @@ TEST(core_cpp_path_processing /*unused*/, ecal_trace_order_test /*unused*/) const NiceMock mock_dir_manager; EXPECT_CALL(mock_dir_provider, eCALEnvVar(ECAL_TRACE_VAR)) - .Times(7) + .Times(6) .WillOnce(testing::Return(ecal_trace_env_var)) .WillRepeatedly(testing::Return("")); EXPECT_CALL(mock_dir_provider, eCALEnvVar(ECAL_DATA_VAR)) - .Times(7) + .Times(6) .WillRepeatedly(testing::Return(ecal_data_env_var)); EXPECT_CALL(mock_dir_manager, getDirectoryPath(testing::_)) - .Times(7) + .Times(6) .WillRepeatedly(testing::Return(ecal_yaml_dir)); EXPECT_CALL(mock_dir_manager, dirExists(ecal_trace_env_var)) .Times(1) .WillOnce(testing::Return(true)); EXPECT_CALL(mock_dir_manager, dirExists(ecal_data_env_trace_var)) - .Times(6) - .WillOnce(testing::Return(true)) - .WillRepeatedly(testing::Return(false)); - EXPECT_CALL(mock_dir_manager, dirExists(ecal_data_env_var)) .Times(5) .WillOnce(testing::Return(true)) .WillRepeatedly(testing::Return(false)); - EXPECT_CALL(mock_dir_manager, dirExists(ecal_config_trace_dir)) - .Times(1) + EXPECT_CALL(mock_dir_manager, dirExists(ecal_data_env_var)) + .Times(4) .WillOnce(testing::Return(true)) .WillRepeatedly(testing::Return(false)); + EXPECT_CALL(mock_dir_manager, dirExists(ecal_yaml_trace_dir)) .Times(3) .WillOnce(testing::Return(true)) @@ -398,13 +394,10 @@ TEST(core_cpp_path_processing /*unused*/, ecal_trace_order_test /*unused*/) ON_CALL(mock_dir_manager, canWriteToDirectory(testing::_)).WillByDefault(testing::Return(true)); auto config = eCAL::GetConfiguration(); - config.tracing.path = ecal_config_trace_dir; EXPECT_EQ(eCAL::Config::GeteCALTraceDirImpl(mock_dir_provider, mock_dir_manager, config), ecal_trace_env_var); EXPECT_EQ(eCAL::Config::GeteCALTraceDirImpl(mock_dir_provider, mock_dir_manager, config), ecal_data_env_trace_var); EXPECT_EQ(eCAL::Config::GeteCALTraceDirImpl(mock_dir_provider, mock_dir_manager, config), ecal_data_env_var); - EXPECT_EQ(eCAL::Config::GeteCALTraceDirImpl(mock_dir_provider, mock_dir_manager, config), ecal_config_trace_dir); - config.tracing.path = ""; EXPECT_EQ(eCAL::Config::GeteCALTraceDirImpl(mock_dir_provider, mock_dir_manager, config), ecal_yaml_trace_dir); EXPECT_EQ(eCAL::Config::GeteCALTraceDirImpl(mock_dir_provider, mock_dir_manager, config), ecal_yaml_dir); EXPECT_EQ(eCAL::Config::GeteCALTraceDirImpl(mock_dir_provider, mock_dir_manager, config), unique_tmp_dir); diff --git a/ecal/tests/cpp/config_test/src/yaml_processing_test.cpp b/ecal/tests/cpp/config_test/src/yaml_processing_test.cpp index 9a7ded1942..ee942f01bb 100644 --- a/ecal/tests/cpp/config_test/src/yaml_processing_test.cpp +++ b/ecal/tests/cpp/config_test/src/yaml_processing_test.cpp @@ -91,7 +91,6 @@ TEST(core_cpp_config_yaml /*unused*/, yaml_processing_comparison /*unused*/) config.application.sys.filter_excl = "filter_excl"; config.tracing.enabled = true; - config.tracing.path = "trace_config_path"; config.logging.provider.console.enable = false; config.logging.provider.console.log_level = eCAL::Logging::eLogLevel::log_level_debug1; @@ -163,7 +162,6 @@ TEST(core_cpp_config_yaml /*unused*/, yaml_processing_comparison /*unused*/) EXPECT_EQ(config.logging.receiver.enable, config_from_yaml.logging.receiver.enable); EXPECT_EQ(config.logging.receiver.udp_config.port, config_from_yaml.logging.receiver.udp_config.port); EXPECT_EQ(config.tracing.enabled, config_from_yaml.tracing.enabled); - EXPECT_EQ(config.tracing.path, config_from_yaml.tracing.path); auto yaml_from_config = YAML::Node(config); eCAL::Configuration config_from_yaml_config = yaml_from_config.as(); @@ -223,7 +221,6 @@ TEST(core_cpp_config_yaml /*unused*/, yaml_processing_comparison /*unused*/) EXPECT_EQ(config.logging.receiver.enable, config_from_yaml_config.logging.receiver.enable); EXPECT_EQ(config.logging.receiver.udp_config.port, config_from_yaml_config.logging.receiver.udp_config.port); EXPECT_EQ(config.tracing.enabled, config_from_yaml_config.tracing.enabled); - EXPECT_EQ(config.tracing.path, config_from_yaml_config.tracing.path); } TEST(core_cpp_config /*unused*/, read_write_file_test /*unused*/) diff --git a/ecal/tests/cpp/tracing_test/src/tracing_test_helpers.h b/ecal/tests/cpp/tracing_test/src/tracing_test_helpers.h index d467b61373..cc86564517 100644 --- a/ecal/tests/cpp/tracing_test/src/tracing_test_helpers.h +++ b/ecal/tests/cpp/tracing_test/src/tracing_test_helpers.h @@ -19,17 +19,63 @@ #pragma once +#include + #include #include #include #include +#include +#include #include #include #include #include +class ScopedTraceDirOverride +{ +public: + explicit ScopedTraceDirOverride(const std::string& path) + { + if (const char* previous_value = std::getenv(kTraceDirEnvVar)) + { + had_previous_value_ = true; + previous_value_ = previous_value; + } + +#ifdef _WIN32 + _putenv_s(kTraceDirEnvVar, path.c_str()); +#else + setenv(kTraceDirEnvVar, path.c_str(), 1); +#endif + } + + ~ScopedTraceDirOverride() + { +#ifdef _WIN32 + _putenv_s(kTraceDirEnvVar, had_previous_value_ ? previous_value_.c_str() : ""); +#else + if (had_previous_value_) + setenv(kTraceDirEnvVar, previous_value_.c_str(), 1); + else + unsetenv(kTraceDirEnvVar); +#endif + } + +private: + static constexpr const char* kTraceDirEnvVar = "ECAL_TRACE_DIR"; + + bool had_previous_value_{false}; + std::string previous_value_; +}; + +inline eCAL::Configuration GetTracingConfiguration() +{ + return eCAL::Configuration{}; +} + // Mock writer that counts spans and metadata for test assertions. class MockTracingWriter : public eCAL::tracing::TracingWriter { diff --git a/ecal/tests/cpp/tracing_test/src/tracing_writer_test.cpp b/ecal/tests/cpp/tracing_test/src/tracing_writer_test.cpp index e456241829..1a8c2203df 100644 --- a/ecal/tests/cpp/tracing_test/src/tracing_writer_test.cpp +++ b/ecal/tests/cpp/tracing_test/src/tracing_writer_test.cpp @@ -37,23 +37,14 @@ #include -namespace -{ -eCAL::Configuration GetTracingConfiguration(const std::string& path) -{ - eCAL::Configuration config; - config.tracing.path = path; - return config; -} -} - TEST(TestTracingWriterJSONL, ConcurrentSpanWrites) { constexpr size_t num_threads = 100; constexpr size_t batches_per_thread = 50; constexpr size_t spans_per_batch = 500; constexpr size_t total_spans = num_threads * batches_per_thread * spans_per_batch; - auto ecal_config = GetTracingConfiguration("./"); + ScopedTraceDirOverride trace_dir_override("./"); + auto ecal_config = GetTracingConfiguration(); ASSERT_TRUE(eCAL::Initialize(ecal_config, "", eCAL::Init::None)); std::string spans_path; @@ -113,7 +104,8 @@ TEST(TestTracingWriterJSONL, ConcurrentMetadataWrites) constexpr size_t num_threads = 100; constexpr size_t metadata_per_thread = 200; constexpr size_t total_metadata = num_threads * metadata_per_thread; - auto ecal_config = GetTracingConfiguration("./"); + ScopedTraceDirOverride trace_dir_override("./"); + auto ecal_config = GetTracingConfiguration(); ASSERT_TRUE(eCAL::Initialize(ecal_config, "", eCAL::Init::None)); std::string metadata_path; @@ -161,7 +153,8 @@ TEST(TestTracingWriterJSONL, ConcurrentMetadataWrites) TEST(TestTracingWriterJSONL, PublisherSpanJsonFields) { - auto ecal_config = GetTracingConfiguration("./"); + ScopedTraceDirOverride trace_dir_override("./"); + auto ecal_config = GetTracingConfiguration(); ASSERT_TRUE(eCAL::Initialize(ecal_config, "", eCAL::Init::None)); std::string spans_path; @@ -208,7 +201,8 @@ TEST(TestTracingWriterJSONL, PublisherSpanJsonFields) TEST(TestTracingWriterJSONL, SubscriberSpanJsonFields) { - auto ecal_config = GetTracingConfiguration("./"); + ScopedTraceDirOverride trace_dir_override("./"); + auto ecal_config = GetTracingConfiguration(); ASSERT_TRUE(eCAL::Initialize(ecal_config, "", eCAL::Init::None)); std::string spans_path; From f86eb6fc4d8ca3470a40c7ee33b4eb4de2af0d2c Mon Sep 17 00:00:00 2001 From: Ila Hadi-Assar <254492175+IlaHadiAssar@users.noreply.github.com> Date: Mon, 11 May 2026 11:19:01 +0200 Subject: [PATCH 7/9] Apply static code check suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- ecal/core/include/ecal/config/tracing.h | 1 - ecal/core/src/pubsub/ecal_publisher_impl.cpp | 2 +- ecal/core/src/pubsub/ecal_subscriber_impl.cpp | 4 ++-- ecal/core/src/tracing/span.cpp | 1 - ecal/core/src/tracing/span.h | 1 - ecal/core/src/tracing/trace_provider.cpp | 1 - ecal/core/src/tracing/trace_provider.h | 1 - ecal/core/src/tracing/trace_provider_default.cpp | 5 ++--- ecal/core/src/tracing/trace_provider_default.h | 1 - ecal/core/src/tracing/trace_provider_noop.h | 1 - ecal/core/src/tracing/tracing.h | 1 - ecal/core/src/tracing/tracing_writer.h | 1 - 12 files changed, 5 insertions(+), 15 deletions(-) diff --git a/ecal/core/include/ecal/config/tracing.h b/ecal/core/include/ecal/config/tracing.h index 7f2ec86aa1..ff74aa501d 100644 --- a/ecal/core/include/ecal/config/tracing.h +++ b/ecal/core/include/ecal/config/tracing.h @@ -27,7 +27,6 @@ #include namespace eCAL -{ namespace Tracing { struct Configuration diff --git a/ecal/core/src/pubsub/ecal_publisher_impl.cpp b/ecal/core/src/pubsub/ecal_publisher_impl.cpp index 95bc9effd1..d2d41b0891 100644 --- a/ecal/core/src/pubsub/ecal_publisher_impl.cpp +++ b/ecal/core/src/pubsub/ecal_publisher_impl.cpp @@ -211,7 +211,7 @@ namespace eCAL } // create tracing span for the send operation - eCAL::tracing::CPublisherSpan send_span( + eCAL::tracing::CPublisherSpan const send_span( m_topic_id, m_clock, active_layer, diff --git a/ecal/core/src/pubsub/ecal_subscriber_impl.cpp b/ecal/core/src/pubsub/ecal_subscriber_impl.cpp index 0a50a85c98..1f0d62f329 100644 --- a/ecal/core/src/pubsub/ecal_subscriber_impl.cpp +++ b/ecal/core/src/pubsub/ecal_subscriber_impl.cpp @@ -390,7 +390,7 @@ namespace eCAL size_t CSubscriberImpl::ApplySample(const Payload::TopicInfo& topic_info_, const char* payload_, size_t size_, long long id_, long long clock_, long long time_, size_t /*hash_*/, eTLayerType layer_) { - eCAL::tracing::CSubscriberSpan receive_span( + eCAL::tracing::CSubscriberSpan const receive_span( m_subscriber_id, topic_info_, clock_, @@ -477,7 +477,7 @@ namespace eCAL // execute it const std::lock_guard exec_lock(m_connection_map_mtx); { - eCAL::tracing::CSubscriberSpan callback_span( + eCAL::tracing::CSubscriberSpan const callback_span( m_subscriber_id, topic_info_, clock_, diff --git a/ecal/core/src/tracing/span.cpp b/ecal/core/src/tracing/span.cpp index eaed55be40..b113fd9ecd 100644 --- a/ecal/core/src/tracing/span.cpp +++ b/ecal/core/src/tracing/span.cpp @@ -26,7 +26,6 @@ using namespace std::chrono; namespace eCAL -{ namespace tracing { diff --git a/ecal/core/src/tracing/span.h b/ecal/core/src/tracing/span.h index 18e86758b6..b870c57f9b 100644 --- a/ecal/core/src/tracing/span.h +++ b/ecal/core/src/tracing/span.h @@ -26,7 +26,6 @@ #include namespace eCAL -{ namespace tracing { diff --git a/ecal/core/src/tracing/trace_provider.cpp b/ecal/core/src/tracing/trace_provider.cpp index 4b9c74b9b4..7d06a9aa29 100644 --- a/ecal/core/src/tracing/trace_provider.cpp +++ b/ecal/core/src/tracing/trace_provider.cpp @@ -22,7 +22,6 @@ #include "trace_provider_noop.h" namespace eCAL -{ namespace tracing { diff --git a/ecal/core/src/tracing/trace_provider.h b/ecal/core/src/tracing/trace_provider.h index 6eeb182173..dddad4e9e5 100644 --- a/ecal/core/src/tracing/trace_provider.h +++ b/ecal/core/src/tracing/trace_provider.h @@ -26,7 +26,6 @@ #include namespace eCAL -{ namespace tracing { diff --git a/ecal/core/src/tracing/trace_provider_default.cpp b/ecal/core/src/tracing/trace_provider_default.cpp index 794d163fcb..85c4f4bc23 100644 --- a/ecal/core/src/tracing/trace_provider_default.cpp +++ b/ecal/core/src/tracing/trace_provider_default.cpp @@ -27,7 +27,6 @@ #include namespace eCAL -{ namespace tracing { @@ -52,7 +51,7 @@ namespace tracing CTraceProviderDefault::~CTraceProviderDefault() { { - std::lock_guard lock(thread_mutex); + std::lock_guard const lock(thread_mutex); stop_thread_ = true; write_cv_.notify_all(); } @@ -62,7 +61,7 @@ namespace tracing void CTraceProviderDefault::WriteSpan(const SpanDataVariant& span_data) { - std::lock_guard lock(thread_mutex); + std::lock_guard const lock(thread_mutex); span_buffer_.push_back(span_data); if (span_buffer_.size() >= batch_size_) { diff --git a/ecal/core/src/tracing/trace_provider_default.h b/ecal/core/src/tracing/trace_provider_default.h index ad1242d279..e8ea7e9ded 100644 --- a/ecal/core/src/tracing/trace_provider_default.h +++ b/ecal/core/src/tracing/trace_provider_default.h @@ -33,7 +33,6 @@ #include namespace eCAL -{ namespace tracing { diff --git a/ecal/core/src/tracing/trace_provider_noop.h b/ecal/core/src/tracing/trace_provider_noop.h index 9fa0b140a4..5377d7bca2 100644 --- a/ecal/core/src/tracing/trace_provider_noop.h +++ b/ecal/core/src/tracing/trace_provider_noop.h @@ -22,7 +22,6 @@ #include "trace_provider.h" namespace eCAL -{ namespace tracing { diff --git a/ecal/core/src/tracing/tracing.h b/ecal/core/src/tracing/tracing.h index 9ce39e483d..350b3207b9 100644 --- a/ecal/core/src/tracing/tracing.h +++ b/ecal/core/src/tracing/tracing.h @@ -31,7 +31,6 @@ #include namespace eCAL -{ namespace tracing { diff --git a/ecal/core/src/tracing/tracing_writer.h b/ecal/core/src/tracing/tracing_writer.h index 0f09e431b6..0f9d0194af 100644 --- a/ecal/core/src/tracing/tracing_writer.h +++ b/ecal/core/src/tracing/tracing_writer.h @@ -25,7 +25,6 @@ #include "tracing.h" namespace eCAL -{ namespace tracing { From f0a786283b04cfb0b6cc5cdb0b75d96d7b6f3a53 Mon Sep 17 00:00:00 2001 From: Ila Hadi-Assar Date: Mon, 11 May 2026 13:18:14 +0200 Subject: [PATCH 8/9] Revert "Apply static code check suggestions from code review" This reverts commit f86eb6fc4d8ca3470a40c7ee33b4eb4de2af0d2c. --- ecal/core/include/ecal/config/tracing.h | 1 + ecal/core/src/pubsub/ecal_publisher_impl.cpp | 2 +- ecal/core/src/pubsub/ecal_subscriber_impl.cpp | 4 ++-- ecal/core/src/tracing/span.cpp | 1 + ecal/core/src/tracing/span.h | 1 + ecal/core/src/tracing/trace_provider.cpp | 1 + ecal/core/src/tracing/trace_provider.h | 1 + ecal/core/src/tracing/trace_provider_default.cpp | 5 +++-- ecal/core/src/tracing/trace_provider_default.h | 1 + ecal/core/src/tracing/trace_provider_noop.h | 1 + ecal/core/src/tracing/tracing.h | 1 + ecal/core/src/tracing/tracing_writer.h | 1 + 12 files changed, 15 insertions(+), 5 deletions(-) diff --git a/ecal/core/include/ecal/config/tracing.h b/ecal/core/include/ecal/config/tracing.h index ff74aa501d..7f2ec86aa1 100644 --- a/ecal/core/include/ecal/config/tracing.h +++ b/ecal/core/include/ecal/config/tracing.h @@ -27,6 +27,7 @@ #include namespace eCAL +{ namespace Tracing { struct Configuration diff --git a/ecal/core/src/pubsub/ecal_publisher_impl.cpp b/ecal/core/src/pubsub/ecal_publisher_impl.cpp index d2d41b0891..95bc9effd1 100644 --- a/ecal/core/src/pubsub/ecal_publisher_impl.cpp +++ b/ecal/core/src/pubsub/ecal_publisher_impl.cpp @@ -211,7 +211,7 @@ namespace eCAL } // create tracing span for the send operation - eCAL::tracing::CPublisherSpan const send_span( + eCAL::tracing::CPublisherSpan send_span( m_topic_id, m_clock, active_layer, diff --git a/ecal/core/src/pubsub/ecal_subscriber_impl.cpp b/ecal/core/src/pubsub/ecal_subscriber_impl.cpp index 1f0d62f329..0a50a85c98 100644 --- a/ecal/core/src/pubsub/ecal_subscriber_impl.cpp +++ b/ecal/core/src/pubsub/ecal_subscriber_impl.cpp @@ -390,7 +390,7 @@ namespace eCAL size_t CSubscriberImpl::ApplySample(const Payload::TopicInfo& topic_info_, const char* payload_, size_t size_, long long id_, long long clock_, long long time_, size_t /*hash_*/, eTLayerType layer_) { - eCAL::tracing::CSubscriberSpan const receive_span( + eCAL::tracing::CSubscriberSpan receive_span( m_subscriber_id, topic_info_, clock_, @@ -477,7 +477,7 @@ namespace eCAL // execute it const std::lock_guard exec_lock(m_connection_map_mtx); { - eCAL::tracing::CSubscriberSpan const callback_span( + eCAL::tracing::CSubscriberSpan callback_span( m_subscriber_id, topic_info_, clock_, diff --git a/ecal/core/src/tracing/span.cpp b/ecal/core/src/tracing/span.cpp index b113fd9ecd..eaed55be40 100644 --- a/ecal/core/src/tracing/span.cpp +++ b/ecal/core/src/tracing/span.cpp @@ -26,6 +26,7 @@ using namespace std::chrono; namespace eCAL +{ namespace tracing { diff --git a/ecal/core/src/tracing/span.h b/ecal/core/src/tracing/span.h index b870c57f9b..18e86758b6 100644 --- a/ecal/core/src/tracing/span.h +++ b/ecal/core/src/tracing/span.h @@ -26,6 +26,7 @@ #include namespace eCAL +{ namespace tracing { diff --git a/ecal/core/src/tracing/trace_provider.cpp b/ecal/core/src/tracing/trace_provider.cpp index 7d06a9aa29..4b9c74b9b4 100644 --- a/ecal/core/src/tracing/trace_provider.cpp +++ b/ecal/core/src/tracing/trace_provider.cpp @@ -22,6 +22,7 @@ #include "trace_provider_noop.h" namespace eCAL +{ namespace tracing { diff --git a/ecal/core/src/tracing/trace_provider.h b/ecal/core/src/tracing/trace_provider.h index dddad4e9e5..6eeb182173 100644 --- a/ecal/core/src/tracing/trace_provider.h +++ b/ecal/core/src/tracing/trace_provider.h @@ -26,6 +26,7 @@ #include namespace eCAL +{ namespace tracing { diff --git a/ecal/core/src/tracing/trace_provider_default.cpp b/ecal/core/src/tracing/trace_provider_default.cpp index 85c4f4bc23..794d163fcb 100644 --- a/ecal/core/src/tracing/trace_provider_default.cpp +++ b/ecal/core/src/tracing/trace_provider_default.cpp @@ -27,6 +27,7 @@ #include namespace eCAL +{ namespace tracing { @@ -51,7 +52,7 @@ namespace tracing CTraceProviderDefault::~CTraceProviderDefault() { { - std::lock_guard const lock(thread_mutex); + std::lock_guard lock(thread_mutex); stop_thread_ = true; write_cv_.notify_all(); } @@ -61,7 +62,7 @@ namespace tracing void CTraceProviderDefault::WriteSpan(const SpanDataVariant& span_data) { - std::lock_guard const lock(thread_mutex); + std::lock_guard lock(thread_mutex); span_buffer_.push_back(span_data); if (span_buffer_.size() >= batch_size_) { diff --git a/ecal/core/src/tracing/trace_provider_default.h b/ecal/core/src/tracing/trace_provider_default.h index e8ea7e9ded..ad1242d279 100644 --- a/ecal/core/src/tracing/trace_provider_default.h +++ b/ecal/core/src/tracing/trace_provider_default.h @@ -33,6 +33,7 @@ #include namespace eCAL +{ namespace tracing { diff --git a/ecal/core/src/tracing/trace_provider_noop.h b/ecal/core/src/tracing/trace_provider_noop.h index 5377d7bca2..9fa0b140a4 100644 --- a/ecal/core/src/tracing/trace_provider_noop.h +++ b/ecal/core/src/tracing/trace_provider_noop.h @@ -22,6 +22,7 @@ #include "trace_provider.h" namespace eCAL +{ namespace tracing { diff --git a/ecal/core/src/tracing/tracing.h b/ecal/core/src/tracing/tracing.h index 350b3207b9..9ce39e483d 100644 --- a/ecal/core/src/tracing/tracing.h +++ b/ecal/core/src/tracing/tracing.h @@ -31,6 +31,7 @@ #include namespace eCAL +{ namespace tracing { diff --git a/ecal/core/src/tracing/tracing_writer.h b/ecal/core/src/tracing/tracing_writer.h index 0f9d0194af..0f09e431b6 100644 --- a/ecal/core/src/tracing/tracing_writer.h +++ b/ecal/core/src/tracing/tracing_writer.h @@ -25,6 +25,7 @@ #include "tracing.h" namespace eCAL +{ namespace tracing { From 4724d8886694edbb7953a203e0a3089a431c9514 Mon Sep 17 00:00:00 2001 From: Ila Hadi-Assar Date: Mon, 11 May 2026 15:11:42 +0200 Subject: [PATCH 9/9] style changes and updated license --- ecal/core/src/pubsub/ecal_publisher.cpp | 3 +- ecal/core/src/pubsub/ecal_publisher_impl.cpp | 21 ++- ecal/core/src/pubsub/ecal_subscriber_impl.cpp | 21 ++- ecal/core/src/tracing/span.cpp | 61 +++--- ecal/core/src/tracing/span.h | 49 ++--- ecal/core/src/tracing/trace_provider.cpp | 23 ++- ecal/core/src/tracing/trace_provider.h | 45 +++-- .../src/tracing/trace_provider_default.cpp | 86 +++++---- .../core/src/tracing/trace_provider_default.h | 62 +++---- ecal/core/src/tracing/trace_provider_noop.h | 25 ++- ecal/core/src/tracing/tracing.h | 91 +++++---- ecal/core/src/tracing/tracing_writer.h | 37 ++-- .../core/src/tracing/tracing_writer_jsonl.cpp | 175 +++++++++--------- ecal/core/src/tracing/tracing_writer_jsonl.h | 61 +++--- 14 files changed, 377 insertions(+), 383 deletions(-) diff --git a/ecal/core/src/pubsub/ecal_publisher.cpp b/ecal/core/src/pubsub/ecal_publisher.cpp index 00cf8bd629..7f70ff41f8 100644 --- a/ecal/core/src/pubsub/ecal_publisher.cpp +++ b/ecal/core/src/pubsub/ecal_publisher.cpp @@ -98,10 +98,9 @@ namespace eCAL } bool CPublisher::Send(CPayloadWriter& payload_, long long time_) - { + { auto publisher_impl = m_publisher_impl.lock(); if (!publisher_impl) return false; - // in an optimization case the // publisher can send an empty package // or we do not have any subscription at all diff --git a/ecal/core/src/pubsub/ecal_publisher_impl.cpp b/ecal/core/src/pubsub/ecal_publisher_impl.cpp index 95bc9effd1..28f9152da4 100644 --- a/ecal/core/src/pubsub/ecal_publisher_impl.cpp +++ b/ecal/core/src/pubsub/ecal_publisher_impl.cpp @@ -128,15 +128,18 @@ namespace eCAL // record topic metadata for tracing { - eCAL::tracing::STopicMetadata meta; - meta.entity_id = m_publisher_id; - meta.process_id = m_attributes.process_id; - meta.host_name = m_attributes.host_name; - meta.topic_name = m_attributes.topic_name; - meta.encoding = m_topic_info.encoding; - meta.type_name = m_topic_info.name; - meta.direction = eCAL::tracing::topic_direction::publisher; - if (auto provider = g_trace_provider(); provider) provider->WriteMetadata(meta); + if (auto provider = g_trace_provider(); provider) { + eCAL::tracing::STopicMetadata meta; + meta.entity_id = m_publisher_id; + meta.process_id = m_attributes.process_id; + meta.host_name = m_attributes.host_name; + meta.topic_name = m_attributes.topic_name; + meta.encoding = m_topic_info.encoding; + meta.type_name = m_topic_info.name; + meta.direction = eCAL::tracing::topic_direction::publisher; + + provider->WriteMetadata(meta); + } } // mark as created diff --git a/ecal/core/src/pubsub/ecal_subscriber_impl.cpp b/ecal/core/src/pubsub/ecal_subscriber_impl.cpp index 0a50a85c98..108e3405fd 100644 --- a/ecal/core/src/pubsub/ecal_subscriber_impl.cpp +++ b/ecal/core/src/pubsub/ecal_subscriber_impl.cpp @@ -98,15 +98,18 @@ namespace eCAL // record topic metadata for tracing { - eCAL::tracing::STopicMetadata meta; - meta.entity_id = m_subscriber_id; - meta.process_id = m_attributes.process_id; - meta.host_name = m_attributes.host_name; - meta.topic_name = m_attributes.topic_name; - meta.encoding = m_topic_info.encoding; - meta.type_name = m_topic_info.name; - meta.direction = eCAL::tracing::topic_direction::subscriber; - if (auto provider = g_trace_provider(); provider) provider->WriteMetadata(meta); + if (auto provider = g_trace_provider(); provider) { + eCAL::tracing::STopicMetadata meta; + meta.entity_id = m_subscriber_id; + meta.process_id = m_attributes.process_id; + meta.host_name = m_attributes.host_name; + meta.topic_name = m_attributes.topic_name; + meta.encoding = m_topic_info.encoding; + meta.type_name = m_topic_info.name; + meta.direction = eCAL::tracing::topic_direction::subscriber; + + provider->WriteMetadata(meta); + } } // start transport layers diff --git a/ecal/core/src/tracing/span.cpp b/ecal/core/src/tracing/span.cpp index eaed55be40..506e9f3478 100644 --- a/ecal/core/src/tracing/span.cpp +++ b/ecal/core/src/tracing/span.cpp @@ -1,13 +1,14 @@ /* ========================= eCAL LICENSE ================================= * * Copyright (C) 2016 - 2025 Continental Corporation + * Copyright 2025 AUMOVIO and subsidiaries. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -27,49 +28,47 @@ using namespace std::chrono; namespace eCAL { -namespace tracing -{ - + namespace tracing + { // Send span constructor CPublisherSpan::CPublisherSpan(const STopicId& topic_id, long long clock, eTracingLayerType layer, size_t payload_size, operation_type op_type) { - auto now = system_clock::now(); - data.start_ns = duration_cast(now.time_since_epoch()).count(); - data.entity_id = topic_id.topic_id.entity_id; - data.process_id = topic_id.topic_id.process_id; - data.payload_size = payload_size; - data.clock = clock; - data.layer = layer; - data.op_type = op_type; + auto now = system_clock::now(); + data.start_ns = duration_cast(now.time_since_epoch()).count(); + data.entity_id = topic_id.topic_id.entity_id; + data.process_id = topic_id.topic_id.process_id; + data.payload_size = payload_size; + data.clock = clock; + data.layer = layer; + data.op_type = op_type; } CPublisherSpan::~CPublisherSpan() { - auto now = system_clock::now(); - data.end_ns = duration_cast(now.time_since_epoch()).count(); - if (auto provider = g_trace_provider(); provider) provider->WriteSpan(data); + auto now = system_clock::now(); + data.end_ns = duration_cast(now.time_since_epoch()).count(); + if (auto provider = g_trace_provider(); provider) provider->WriteSpan(data); } // Receive span constructor CSubscriberSpan::CSubscriberSpan(EntityIdT entity_id, const eCAL::Payload::TopicInfo& topic_info, long long clock, eTracingLayerType layer, size_t payload_size, operation_type op_type) { - auto now = system_clock::now(); - data.start_ns = duration_cast(now.time_since_epoch()).count(); - data.entity_id = entity_id; - data.topic_id = topic_info.topic_id; - data.process_id = topic_info.process_id; - data.payload_size = payload_size; - data.clock = clock; - data.layer = layer; - data.op_type = op_type; + auto now = system_clock::now(); + data.start_ns = duration_cast(now.time_since_epoch()).count(); + data.entity_id = entity_id; + data.topic_id = topic_info.topic_id; + data.process_id = topic_info.process_id; + data.payload_size = payload_size; + data.clock = clock; + data.layer = layer; + data.op_type = op_type; } CSubscriberSpan::~CSubscriberSpan() { - auto now = system_clock::now(); - data.end_ns = duration_cast(now.time_since_epoch()).count(); - if (auto provider = g_trace_provider(); provider) provider->WriteSpan(data); + auto now = system_clock::now(); + data.end_ns = duration_cast(now.time_since_epoch()).count(); + if (auto provider = g_trace_provider(); provider) provider->WriteSpan(data); } - -} // namespace tracing -} // namespace eCAL + } +} diff --git a/ecal/core/src/tracing/span.h b/ecal/core/src/tracing/span.h index 18e86758b6..06f6187055 100644 --- a/ecal/core/src/tracing/span.h +++ b/ecal/core/src/tracing/span.h @@ -1,13 +1,14 @@ /* ========================= eCAL LICENSE ================================= * * Copyright (C) 2016 - 2025 Continental Corporation + * Copyright 2025 AUMOVIO and subsidiaries. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -27,42 +28,42 @@ namespace eCAL { -namespace tracing -{ - + namespace tracing + { // RAII span for send (publisher) operations. // Records start_ns on construction, end_ns + buffer on destruction. - class CPublisherSpan { + class CPublisherSpan + { public: - CPublisherSpan(const STopicId& topic_id, long long clock, eTracingLayerType layer, size_t payload_size, operation_type op_type); + CPublisherSpan(const STopicId& topic_id, long long clock, eTracingLayerType layer, size_t payload_size, operation_type op_type); - ~CPublisherSpan(); + ~CPublisherSpan(); - CPublisherSpan(const CPublisherSpan&) = delete; - CPublisherSpan& operator=(const CPublisherSpan&) = delete; - CPublisherSpan(CPublisherSpan&&) = delete; - CPublisherSpan& operator=(CPublisherSpan&&) = delete; + CPublisherSpan(const CPublisherSpan&) = delete; + CPublisherSpan& operator=(const CPublisherSpan&) = delete; + CPublisherSpan(CPublisherSpan&&) = delete; + CPublisherSpan& operator=(CPublisherSpan&&) = delete; private: - SPublisherSpanData data{}; + SPublisherSpanData data{}; }; // RAII span for receive (subscriber) operations. // Records start_ns on construction, end_ns + buffer on destruction. - class CSubscriberSpan { + class CSubscriberSpan + { public: - CSubscriberSpan(EntityIdT entity_id, const eCAL::Payload::TopicInfo& topic_info, long long clock, eTracingLayerType layer, size_t payload_size, operation_type op_type); + CSubscriberSpan(EntityIdT entity_id, const eCAL::Payload::TopicInfo& topic_info, long long clock, eTracingLayerType layer, size_t payload_size, operation_type op_type); - ~CSubscriberSpan(); + ~CSubscriberSpan(); - CSubscriberSpan(const CSubscriberSpan&) = delete; - CSubscriberSpan& operator=(const CSubscriberSpan&) = delete; - CSubscriberSpan(CSubscriberSpan&&) = delete; - CSubscriberSpan& operator=(CSubscriberSpan&&) = delete; + CSubscriberSpan(const CSubscriberSpan&) = delete; + CSubscriberSpan& operator=(const CSubscriberSpan&) = delete; + CSubscriberSpan(CSubscriberSpan&&) = delete; + CSubscriberSpan& operator=(CSubscriberSpan&&) = delete; private: - SSubscriberSpanData data{}; + SSubscriberSpanData data{}; }; - -} // namespace tracing -} // namespace eCAL + } +} diff --git a/ecal/core/src/tracing/trace_provider.cpp b/ecal/core/src/tracing/trace_provider.cpp index 4b9c74b9b4..71b5cd3a90 100644 --- a/ecal/core/src/tracing/trace_provider.cpp +++ b/ecal/core/src/tracing/trace_provider.cpp @@ -1,13 +1,14 @@ /* ========================= eCAL LICENSE ================================= * * Copyright (C) 2016 - 2025 Continental Corporation + * Copyright 2025 AUMOVIO and subsidiaries. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -23,17 +24,15 @@ namespace eCAL { -namespace tracing -{ - - std::shared_ptr TraceProvider::Create(const eCAL::Tracing::Configuration& config_) + namespace tracing { - if (config_.enabled) + std::shared_ptr TraceProvider::Create(const eCAL::Tracing::Configuration& config_) { - return CTraceProviderDefault::Create(); + if (config_.enabled) + { + return CTraceProviderDefault::Create(); + } + return std::make_shared(); } - return std::make_shared(); } - -} // namespace tracing -} // namespace eCAL +} diff --git a/ecal/core/src/tracing/trace_provider.h b/ecal/core/src/tracing/trace_provider.h index 6eeb182173..98df533c62 100644 --- a/ecal/core/src/tracing/trace_provider.h +++ b/ecal/core/src/tracing/trace_provider.h @@ -1,13 +1,14 @@ /* ========================= eCAL LICENSE ================================= * * Copyright (C) 2016 - 2025 Continental Corporation + * Copyright 2025 AUMOVIO and subsidiaries. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -27,25 +28,23 @@ namespace eCAL { -namespace tracing -{ - - class TraceProvider + namespace tracing { - public: - TraceProvider() = default; - virtual ~TraceProvider() = default; - - TraceProvider(const TraceProvider&) = delete; - TraceProvider& operator=(const TraceProvider&) = delete; - TraceProvider(TraceProvider&&) = delete; - TraceProvider& operator=(TraceProvider&&) = delete; - - static std::shared_ptr Create(const eCAL::Tracing::Configuration& config_); - - virtual void WriteSpan(const SpanDataVariant& span_data) = 0; - virtual void WriteMetadata(const STopicMetadata& metadata) = 0; - }; - -} // namespace tracing -} // namespace eCAL + class TraceProvider + { + public: + TraceProvider() = default; + virtual ~TraceProvider() = default; + + TraceProvider(const TraceProvider&) = delete; + TraceProvider& operator=(const TraceProvider&) = delete; + TraceProvider(TraceProvider&&) = delete; + TraceProvider& operator=(TraceProvider&&) = delete; + + static std::shared_ptr Create(const eCAL::Tracing::Configuration& config_); + + virtual void WriteSpan(const SpanDataVariant& span_data) = 0; + virtual void WriteMetadata(const STopicMetadata& metadata) = 0; + }; + } +} diff --git a/ecal/core/src/tracing/trace_provider_default.cpp b/ecal/core/src/tracing/trace_provider_default.cpp index 794d163fcb..1627d80c5e 100644 --- a/ecal/core/src/tracing/trace_provider_default.cpp +++ b/ecal/core/src/tracing/trace_provider_default.cpp @@ -1,13 +1,14 @@ /* ========================= eCAL LICENSE ================================= * * Copyright (C) 2016 - 2025 Continental Corporation + * Copyright 2025 AUMOVIO and subsidiaries. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -28,9 +29,8 @@ namespace eCAL { -namespace tracing -{ - + namespace tracing + { std::shared_ptr CTraceProviderDefault::Create(std::unique_ptr writer, size_t batch_size) { try @@ -44,61 +44,59 @@ namespace tracing } CTraceProviderDefault::CTraceProviderDefault(std::unique_ptr writer, size_t batch_size) - : batch_size_(batch_size), writer_(std::move(writer)) + : batch_size_(batch_size), writer_(std::move(writer)) { - writer_thread_ = std::thread(&CTraceProviderDefault::WriterThreadLoop, this); + writer_thread_ = std::thread(&CTraceProviderDefault::WriterThreadLoop, this); } - + CTraceProviderDefault::~CTraceProviderDefault() { - { - std::lock_guard lock(thread_mutex); - stop_thread_ = true; - write_cv_.notify_all(); - } - writer_thread_.join(); + { + std::lock_guard lock(thread_mutex); + stop_thread_ = true; + write_cv_.notify_all(); + } + writer_thread_.join(); } - void CTraceProviderDefault::WriteSpan(const SpanDataVariant& span_data) { - std::lock_guard lock(thread_mutex); - span_buffer_.push_back(span_data); - if (span_buffer_.size() >= batch_size_) - { - write_cv_.notify_one(); - } + std::lock_guard lock(thread_mutex); + span_buffer_.push_back(span_data); + if (span_buffer_.size() >= batch_size_) + { + write_cv_.notify_one(); + } } void CTraceProviderDefault::WriterThreadLoop() { - std::vector span_flusher; - while (true) + std::vector span_flusher; + while (true) + { + { + std::unique_lock lock(thread_mutex); + write_cv_.wait(lock, [this]() + { + return stop_thread_ || (span_buffer_.size() >= batch_size_); + }); + if (stop_thread_ && span_buffer_.empty()) + { + break; + } + span_flusher.swap(span_buffer_); + } + if (!span_flusher.empty()) { - { - std::unique_lock lock(thread_mutex); - write_cv_.wait(lock, [this]() - { - return stop_thread_|| (span_buffer_.size() >= batch_size_); - }); - if (stop_thread_ && span_buffer_.empty()) - { - break; - } - span_flusher.swap(span_buffer_); - } - if (!span_flusher.empty()) - { - writer_->WriteSpansToFile(span_flusher); - span_flusher.clear(); - } + writer_->WriteSpansToFile(span_flusher); + span_flusher.clear(); } + } } void CTraceProviderDefault::WriteMetadata(const STopicMetadata& metadata) { - writer_->WriteMetadataToFile(metadata); + writer_->WriteMetadataToFile(metadata); } - -} // namespace tracing -} // namespace eCAL + } +} diff --git a/ecal/core/src/tracing/trace_provider_default.h b/ecal/core/src/tracing/trace_provider_default.h index ad1242d279..4b14529c07 100644 --- a/ecal/core/src/tracing/trace_provider_default.h +++ b/ecal/core/src/tracing/trace_provider_default.h @@ -1,13 +1,14 @@ /* ========================= eCAL LICENSE ================================= * * Copyright (C) 2016 - 2025 Continental Corporation + * Copyright 2025 AUMOVIO and subsidiaries. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -34,42 +35,39 @@ namespace eCAL { -namespace tracing -{ - - class CTraceProviderDefault : public TraceProvider + namespace tracing { - friend class Util::CSingleInstanceHelper; + class CTraceProviderDefault : public TraceProvider + { + friend class Util::CSingleInstanceHelper; public: + static std::shared_ptr Create(std::unique_ptr writer = std::make_unique(), size_t batch_size = kDefaultTracingBatchSize); - static std::shared_ptr Create(std::unique_ptr writer = std::make_unique(), size_t batch_size = kDefaultTracingBatchSize); + CTraceProviderDefault(const CTraceProviderDefault&) = delete; + CTraceProviderDefault& operator=(const CTraceProviderDefault&) = delete; + CTraceProviderDefault(CTraceProviderDefault&&) = delete; + CTraceProviderDefault& operator=(CTraceProviderDefault&&) = delete; - CTraceProviderDefault(const CTraceProviderDefault&) = delete; - CTraceProviderDefault& operator=(const CTraceProviderDefault&) = delete; - CTraceProviderDefault(CTraceProviderDefault&&) = delete; - CTraceProviderDefault& operator=(CTraceProviderDefault&&) = delete; + ~CTraceProviderDefault() override; - ~CTraceProviderDefault() override; - - // Write span data to buffer (accepts any span type via variant) - void WriteSpan(const SpanDataVariant& span_data) override; + // Write span data to buffer (accepts any span type via variant) + void WriteSpan(const SpanDataVariant& span_data) override; - // metadata — written directly to file (no buffering) - void WriteMetadata(const STopicMetadata& metadata) override; - - private: - CTraceProviderDefault(std::unique_ptr writer, size_t batch_size); - void WriterThreadLoop(); + // metadata — written directly to file (no buffering) + void WriteMetadata(const STopicMetadata& metadata) override; - std::atomic batch_size_{kDefaultTracingBatchSize}; - std::vector span_buffer_; - mutable std::mutex thread_mutex; - std::condition_variable write_cv_; - bool stop_thread_{false}; - std::thread writer_thread_; - std::unique_ptr writer_; - }; + private: + CTraceProviderDefault(std::unique_ptr writer, size_t batch_size); + void WriterThreadLoop(); -} // namespace tracing -} // namespace eCAL + std::atomic batch_size_{kDefaultTracingBatchSize}; + std::vector span_buffer_; + mutable std::mutex thread_mutex; + std::condition_variable write_cv_; + bool stop_thread_{false}; + std::thread writer_thread_; + std::unique_ptr writer_; + }; + } +} diff --git a/ecal/core/src/tracing/trace_provider_noop.h b/ecal/core/src/tracing/trace_provider_noop.h index 9fa0b140a4..3e696e98ea 100644 --- a/ecal/core/src/tracing/trace_provider_noop.h +++ b/ecal/core/src/tracing/trace_provider_noop.h @@ -1,13 +1,14 @@ /* ========================= eCAL LICENSE ================================= * * Copyright (C) 2016 - 2025 Continental Corporation + * Copyright 2025 AUMOVIO and subsidiaries. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -23,15 +24,13 @@ namespace eCAL { -namespace tracing -{ - - class CNoOpTraceProvider : public TraceProvider + namespace tracing { - public: - void WriteSpan(const SpanDataVariant& /*span_data*/) override {} - void WriteMetadata(const STopicMetadata& /*metadata*/) override {} - }; - -} // namespace tracing -} // namespace eCAL + class CNoOpTraceProvider : public TraceProvider + { + public: + void WriteSpan(const SpanDataVariant& /*span_data*/) override {} + void WriteMetadata(const STopicMetadata& /*metadata*/) override {} + }; + } +} diff --git a/ecal/core/src/tracing/tracing.h b/ecal/core/src/tracing/tracing.h index 9ce39e483d..829c09a31a 100644 --- a/ecal/core/src/tracing/tracing.h +++ b/ecal/core/src/tracing/tracing.h @@ -1,13 +1,14 @@ /* ========================= eCAL LICENSE ================================= * * Copyright (C) 2016 - 2025 Continental Corporation + * Copyright 2025 AUMOVIO and subsidiaries. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -32,9 +33,8 @@ namespace eCAL { -namespace tracing -{ - + namespace tracing + { // Version of the tracing implementation. constexpr const char* kTracingVersion = "1.0.0"; @@ -44,30 +44,30 @@ namespace tracing // Specifies the type of operation being traced enum operation_type { - send = 0, - receive = 1, - callback_execution = 2 + send = 0, + receive = 1, + callback_execution = 2 }; // Specifies the direction of the topic (publisher or subscriber) enum topic_direction { - publisher = 0, - subscriber = 1 + publisher = 0, + subscriber = 1 }; // Bitmask enum for active transport layers used in tracing spans. // These use power-of-two values so combinations can be expressed with bitwise OR. enum eTracingLayerType : uint64_t { - tl_trace_none = 0, - tl_trace_shm = 1 << 0, // 1 - tl_trace_udp = 1 << 1, // 2 - tl_trace_tcp = 1 << 2, // 4 - tl_trace_shm_udp = tl_trace_shm | tl_trace_udp, // 3 - tl_trace_shm_tcp = tl_trace_shm | tl_trace_tcp, // 5 - tl_trace_udp_tcp = tl_trace_udp | tl_trace_tcp, // 6 - tl_trace_all = tl_trace_shm | tl_trace_udp | tl_trace_tcp, // 7 + tl_trace_none = 0, + tl_trace_shm = 1 << 0, // 1 + tl_trace_udp = 1 << 1, // 2 + tl_trace_tcp = 1 << 2, // 4 + tl_trace_shm_udp = tl_trace_shm | tl_trace_udp, // 3 + tl_trace_shm_tcp = tl_trace_shm | tl_trace_tcp, // 5 + tl_trace_udp_tcp = tl_trace_udp | tl_trace_tcp, // 6 + tl_trace_all = tl_trace_shm | tl_trace_udp | tl_trace_tcp, // 7 }; // Convert from eTLayerType (used in core APIs) to eTracingLayerType. @@ -86,44 +86,43 @@ namespace tracing // Metadata captured when a topic is created struct STopicMetadata { - std::string tracing_version{kTracingVersion}; // tracing format version - uint64_t entity_id; // unique entity id - int32_t process_id; // PID of the owning process - std::string host_name; // host that created the topic - std::string topic_name; // topic name used for pub/sub matching - std::string encoding; // datatype encoding (e.g. protobuf) - std::string type_name; // datatype name - topic_direction direction; // publisher or subscriber + std::string tracing_version{kTracingVersion}; // tracing format version + uint64_t entity_id; // unique entity id + int32_t process_id; // PID of the owning process + std::string host_name; // host that created the topic + std::string topic_name; // topic name used for pub/sub matching + std::string encoding; // datatype encoding (e.g. protobuf) + std::string type_name; // datatype name + topic_direction direction; // publisher or subscriber }; struct SPublisherSpanData { - operation_type op_type; - uint64_t entity_id; - uint64_t process_id; - size_t payload_size; - long long clock; - uint64_t layer; - long long start_ns; // start timestamp in nanoseconds - long long end_ns; // end timestamp in nanoseconds + operation_type op_type; + uint64_t entity_id; + uint64_t process_id; + size_t payload_size; + long long clock; + uint64_t layer; + long long start_ns; // start timestamp in nanoseconds + long long end_ns; // end timestamp in nanoseconds }; struct SSubscriberSpanData { - operation_type op_type; - uint64_t entity_id; - uint64_t topic_id; - uint64_t process_id; - size_t payload_size; - long long clock; - uint64_t layer; - long long start_ns; // start timestamp in nanoseconds - long long end_ns; // end timestamp in nanoseconds + operation_type op_type; + uint64_t entity_id; + uint64_t topic_id; + uint64_t process_id; + size_t payload_size; + long long clock; + uint64_t layer; + long long start_ns; // start timestamp in nanoseconds + long long end_ns; // end timestamp in nanoseconds }; // Variant type for buffering heterogeneous span data. // Extend this variant when new span types are added (e.g. client/server). using SpanDataVariant = std::variant; - -} // namespace tracing -} // namespace eCAL + } +} diff --git a/ecal/core/src/tracing/tracing_writer.h b/ecal/core/src/tracing/tracing_writer.h index 0f09e431b6..7ff26e0332 100644 --- a/ecal/core/src/tracing/tracing_writer.h +++ b/ecal/core/src/tracing/tracing_writer.h @@ -1,13 +1,14 @@ /* ========================= eCAL LICENSE ================================= * * Copyright (C) 2016 - 2025 Continental Corporation + * Copyright 2025 AUMOVIO and subsidiaries. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -26,21 +27,19 @@ namespace eCAL { -namespace tracing -{ - - // Interface for tracing writers. - class TracingWriter + namespace tracing { - public: - virtual ~TracingWriter() = default; - - // Write a batch of spans (heterogeneous via variant) - virtual void WriteSpansToFile(const std::vector& batch) = 0; - - // Write a single topic metadata entry - virtual void WriteMetadataToFile(const STopicMetadata& metadata) = 0; - }; - -} // namespace tracing -} // namespace eCAL + // Interface for tracing writers. + class TracingWriter + { + public: + virtual ~TracingWriter() = default; + + // Write a batch of spans (heterogeneous via variant) + virtual void WriteSpansToFile(const std::vector& batch) = 0; + + // Write a single topic metadata entry + virtual void WriteMetadataToFile(const STopicMetadata& metadata) = 0; + }; + } +} diff --git a/ecal/core/src/tracing/tracing_writer_jsonl.cpp b/ecal/core/src/tracing/tracing_writer_jsonl.cpp index 99587bb4c2..8a35ea235d 100644 --- a/ecal/core/src/tracing/tracing_writer_jsonl.cpp +++ b/ecal/core/src/tracing/tracing_writer_jsonl.cpp @@ -1,13 +1,14 @@ /* ========================= eCAL LICENSE ================================= * * Copyright (C) 2016 - 2025 Continental Corporation + * Copyright 2025 AUMOVIO and subsidiaries. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -35,121 +36,119 @@ using json = nlohmann::json; namespace eCAL { -namespace tracing -{ - + namespace tracing + { static std::string GetCurrentTimestamp() { - std::time_t now = std::time(nullptr); - std::tm* tm_info = std::localtime(&now); - std::ostringstream oss; - oss << std::put_time(tm_info, "%Y%m%d_%H%M%S"); - return oss.str(); + std::time_t now = std::time(nullptr); + std::tm* tm_info = std::localtime(&now); + std::ostringstream oss; + oss << std::put_time(tm_info, "%Y%m%d_%H%M%S"); + return oss.str(); } CTracingWriterJSONL::CTracingWriterJSONL() - : timestamp_(GetCurrentTimestamp()) - , trace_dir_(eCAL::Util::GeteCALTraceDir()) + : timestamp_(GetCurrentTimestamp()) + , trace_dir_(eCAL::Util::GeteCALTraceDir()) {} std::string CTracingWriterJSONL::GetSpansFilePath() const { - return trace_dir_ + "/ecal_spans_" + std::to_string(eCAL::Process::GetProcessID()) + "_" + timestamp_ + ".jsonl"; + return trace_dir_ + "/ecal_spans_" + std::to_string(eCAL::Process::GetProcessID()) + "_" + timestamp_ + ".jsonl"; } std::string CTracingWriterJSONL::GetTopicMetadataFilePath() const { - return trace_dir_ + "/ecal_topic_metadata_" + std::to_string(eCAL::Process::GetProcessID()) + "_" + timestamp_ + ".jsonl"; + return trace_dir_ + "/ecal_topic_metadata_" + std::to_string(eCAL::Process::GetProcessID()) + "_" + timestamp_ + ".jsonl"; } void CTracingWriterJSONL::WriteSpansToFile(const std::vector& batch) { - try - { - std::lock_guard lock(spans_mutex_); - std::string filepath = GetSpansFilePath(); + try + { + std::lock_guard lock(spans_mutex_); + std::string filepath = GetSpansFilePath(); - std::ofstream output_file(filepath, std::ios::app); - if (output_file.is_open()) - { - for (const auto& span_variant : batch) - { - json span_obj; - std::visit([&span_obj](const auto& span) - { - using T = std::decay_t; - if constexpr (std::is_same_v) - { - span_obj["entity_id"] = span.entity_id; - span_obj["process_id"] = span.process_id; - span_obj["payload_size"] = span.payload_size; - span_obj["clock"] = span.clock; - span_obj["layer"] = span.layer; - span_obj["start_ns"] = span.start_ns; - span_obj["end_ns"] = span.end_ns; - span_obj["op_type"] = span.op_type; - } - else if constexpr (std::is_same_v) - { - span_obj["entity_id"] = span.entity_id; - span_obj["topic_id"] = span.topic_id; - span_obj["process_id"] = span.process_id; - span_obj["payload_size"] = span.payload_size; - span_obj["clock"] = span.clock; - span_obj["layer"] = span.layer; - span_obj["start_ns"] = span.start_ns; - span_obj["end_ns"] = span.end_ns; - span_obj["op_type"] = span.op_type; - } - }, span_variant); - output_file << span_obj.dump() << "\n"; - } - output_file.close(); - } - else + std::ofstream output_file(filepath, std::ios::app); + if (output_file.is_open()) + { + for (const auto& span_variant : batch) + { + json span_obj; + std::visit([&span_obj](const auto& span) { - std::cerr << "Warning: Could not open spans file: " << filepath << std::endl; - } + using T = std::decay_t; + if constexpr (std::is_same_v) + { + span_obj["entity_id"] = span.entity_id; + span_obj["process_id"] = span.process_id; + span_obj["payload_size"] = span.payload_size; + span_obj["clock"] = span.clock; + span_obj["layer"] = span.layer; + span_obj["start_ns"] = span.start_ns; + span_obj["end_ns"] = span.end_ns; + span_obj["op_type"] = span.op_type; + } + else if constexpr (std::is_same_v) + { + span_obj["entity_id"] = span.entity_id; + span_obj["topic_id"] = span.topic_id; + span_obj["process_id"] = span.process_id; + span_obj["payload_size"] = span.payload_size; + span_obj["clock"] = span.clock; + span_obj["layer"] = span.layer; + span_obj["start_ns"] = span.start_ns; + span_obj["end_ns"] = span.end_ns; + span_obj["op_type"] = span.op_type; + } + }, span_variant); + output_file << span_obj.dump() << "\n"; + } + output_file.close(); } - catch (const std::exception& e) + else { - std::cerr << "Error writing spans to JSONL: " << e.what() << std::endl; + std::cerr << "Warning: Could not open spans file: " << filepath << std::endl; } + } + catch (const std::exception& e) + { + std::cerr << "Error writing spans to JSONL: " << e.what() << std::endl; + } } void CTracingWriterJSONL::WriteMetadataToFile(const STopicMetadata& metadata) { - try - { - std::lock_guard lock(metadata_mutex_); - std::string filepath = GetTopicMetadataFilePath(); + try + { + std::lock_guard lock(metadata_mutex_); + std::string filepath = GetTopicMetadataFilePath(); - json obj; - obj["tracing_version"] = metadata.tracing_version; - obj["entity_id"] = metadata.entity_id; - obj["process_id"] = metadata.process_id; - obj["host_name"] = metadata.host_name; - obj["topic_name"] = metadata.topic_name; - obj["encoding"] = metadata.encoding; - obj["type_name"] = metadata.type_name; - obj["direction"] = (metadata.direction == topic_direction::publisher) ? "publisher" : "subscriber"; + json obj; + obj["tracing_version"] = metadata.tracing_version; + obj["entity_id"] = metadata.entity_id; + obj["process_id"] = metadata.process_id; + obj["host_name"] = metadata.host_name; + obj["topic_name"] = metadata.topic_name; + obj["encoding"] = metadata.encoding; + obj["type_name"] = metadata.type_name; + obj["direction"] = (metadata.direction == topic_direction::publisher) ? "publisher" : "subscriber"; - std::ofstream output_file(filepath, std::ios::app); - if (output_file.is_open()) - { - output_file << obj.dump() << "\n"; - output_file.close(); - } - else - { - std::cerr << "Warning: Could not open topic metadata file: " << filepath << std::endl; - } + std::ofstream output_file(filepath, std::ios::app); + if (output_file.is_open()) + { + output_file << obj.dump() << "\n"; + output_file.close(); } - catch (const std::exception& e) + else { - std::cerr << "Error writing topic metadata to JSONL: " << e.what() << std::endl; + std::cerr << "Warning: Could not open topic metadata file: " << filepath << std::endl; } + } + catch (const std::exception& e) + { + std::cerr << "Error writing topic metadata to JSONL: " << e.what() << std::endl; + } } - -} // namespace tracing -} // namespace eCAL + } +} diff --git a/ecal/core/src/tracing/tracing_writer_jsonl.h b/ecal/core/src/tracing/tracing_writer_jsonl.h index 8f01b43e45..a09c77ca9a 100644 --- a/ecal/core/src/tracing/tracing_writer_jsonl.h +++ b/ecal/core/src/tracing/tracing_writer_jsonl.h @@ -1,13 +1,14 @@ /* ========================= eCAL LICENSE ================================= * * Copyright (C) 2016 - 2025 Continental Corporation + * Copyright 2025 AUMOVIO and subsidiaries. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -28,38 +29,36 @@ namespace eCAL { -namespace tracing -{ - - // Responsible for serializing span and metadata to JSONL files. - // Separated from CTraceProvider to isolate the I/O concern. - class CTracingWriterJSONL : public TracingWriter + namespace tracing { - public: - CTracingWriterJSONL(); - ~CTracingWriterJSONL() override = default; - - CTracingWriterJSONL(const CTracingWriterJSONL&) = delete; - CTracingWriterJSONL& operator=(const CTracingWriterJSONL&) = delete; - CTracingWriterJSONL(CTracingWriterJSONL&&) = delete; - CTracingWriterJSONL& operator=(CTracingWriterJSONL&&) = delete; + // Responsible for serializing span and metadata to JSONL files. + // Separated from CTraceProvider to isolate the I/O concern. + class CTracingWriterJSONL : public TracingWriter + { + public: + CTracingWriterJSONL(); + ~CTracingWriterJSONL() override = default; - // Write a batch of spans to the JSONL spans file - void WriteSpansToFile(const std::vector& batch) override; + CTracingWriterJSONL(const CTracingWriterJSONL&) = delete; + CTracingWriterJSONL& operator=(const CTracingWriterJSONL&) = delete; + CTracingWriterJSONL(CTracingWriterJSONL&&) = delete; + CTracingWriterJSONL& operator=(CTracingWriterJSONL&&) = delete; - // Write a single topic metadata entry to the JSONL metadata file - void WriteMetadataToFile(const STopicMetadata& metadata) override; + // Write a batch of spans to the JSONL spans file + void WriteSpansToFile(const std::vector& batch) override; - // File path accessors (path is fixed at construction time) - std::string GetSpansFilePath() const; - std::string GetTopicMetadataFilePath() const; + // Write a single topic metadata entry to the JSONL metadata file + void WriteMetadataToFile(const STopicMetadata& metadata) override; - private: - mutable std::mutex spans_mutex_; - mutable std::mutex metadata_mutex_; - std::string timestamp_; - std::string trace_dir_; - }; + // File path accessors (path is fixed at construction time) + std::string GetSpansFilePath() const; + std::string GetTopicMetadataFilePath() const; -} // namespace tracing -} // namespace eCAL + private: + mutable std::mutex spans_mutex_; + mutable std::mutex metadata_mutex_; + std::string timestamp_; + std::string trace_dir_; + }; + } +}