diff --git a/SilKit/IntegrationTests/ITest_NetSimCan.cpp b/SilKit/IntegrationTests/ITest_NetSimCan.cpp index b411aa08c..cbfb26e8f 100644 --- a/SilKit/IntegrationTests/ITest_NetSimCan.cpp +++ b/SilKit/IntegrationTests/ITest_NetSimCan.cpp @@ -28,6 +28,9 @@ struct ITest_NetSimCan : ITest_NetSim frame.flags = {}; frame.canId = 0x12; frame.dlc = 1; + frame.sdt = 0; + frame.vcid = 0; + frame.af = 0; frame.dataField = SilKit::Util::MakeSpan(dataBytes); if (now < _sendUntilMs) @@ -140,6 +143,7 @@ void MySimulatedCanController::OnFrameRequest(const CanFrameRequest& frameReques // Distribute the frame to all controllers in the network CanFrameEvent frameEvent; + frameEvent.timestamp = std::chrono::nanoseconds{0}; frameEvent.direction = TransmitDirection::RX; frameEvent.frame = frameRequest.frame; frameEvent.userContext = frameRequest.userContext; diff --git a/SilKit/IntegrationTests/ITest_NetSimEthernet.cpp b/SilKit/IntegrationTests/ITest_NetSimEthernet.cpp index 9b102a8fb..b534ea210 100644 --- a/SilKit/IntegrationTests/ITest_NetSimEthernet.cpp +++ b/SilKit/IntegrationTests/ITest_NetSimEthernet.cpp @@ -128,6 +128,7 @@ void MySimulatedEthernetController::OnFrameRequest(const EthernetFrameRequest& f // Distribute the frame to all controllers in the network EthernetFrameEvent frameEvent; + frameEvent.timestamp = std::chrono::nanoseconds{0}; frameEvent.direction = TransmitDirection::RX; frameEvent.frame = frameRequest.ethernetFrame; frameEvent.userContext = frameRequest.userContext; diff --git a/SilKit/source/core/vasio/Test_ParticipantVersion.cpp b/SilKit/source/core/vasio/Test_ParticipantVersion.cpp index 5a59bd823..962062b4c 100644 --- a/SilKit/source/core/vasio/Test_ParticipantVersion.cpp +++ b/SilKit/source/core/vasio/Test_ParticipantVersion.cpp @@ -104,6 +104,10 @@ class Test_ParticipantVersion : public testing::Test SilKit::Services::Can::CanFrame frame; frame.canId = 5; + frame.flags = 0; + frame.sdt = 0; + frame.vcid = 0; + frame.af = 0; frame.dataField = SilKit::Util::MakeSpan(frameDataField); frame.dlc = static_cast(frame.dataField.size()); sendCan->SendFrame(frame); diff --git a/SilKit/source/services/metrics/MetricsJsonSink.cpp b/SilKit/source/services/metrics/MetricsJsonSink.cpp index e56756527..cc38275b9 100644 --- a/SilKit/source/services/metrics/MetricsJsonSink.cpp +++ b/SilKit/source/services/metrics/MetricsJsonSink.cpp @@ -18,6 +18,8 @@ struct MetricKindString { switch (self.kind) { + case VSilKit::MetricKind::ATTRIBUTE: + return ostream << "ATTRIBUTE"; case VSilKit::MetricKind::COUNTER: return ostream << "COUNTER"; case VSilKit::MetricKind::STATISTIC: @@ -45,9 +47,24 @@ void MetricsJsonSink::Process(const std::string& origin, const MetricsUpdate& me for (const auto& data : metricsUpdate.metrics) { - *_ostream << R"({"ts":)" << data.timestamp << R"(,"pn":")" << SilKit::Util::EscapedJsonString{origin} - << R"(","mn":")" << SilKit::Util::EscapedJsonString{data.name} << R"(","mk":")" - << MetricKindString{data.kind} << R"(","mv":)" << data.value << R"(})" << '\n'; + *_ostream << R"({"ts":)" << data.timestamp + << R"(,"pn":")" << SilKit::Util::EscapedJsonString{origin} << R"(")" + << R"(,"mn":")" << SilKit::Util::EscapedJsonString{data.name} << R"(")" + << R"(,"mk":")" << MetricKindString{data.kind} << R"(")" + << R"(,"mv":)"; + + if (data.kind == MetricKind::ATTRIBUTE) + { + // Quotes and escape the value for attributes. + // For other metric kinds, the value is already a json-formatted string, so we can write it as is. + *_ostream << R"(")" << SilKit::Util::EscapedJsonString{data.value} << R"(")"; + } + else + { + *_ostream << data.value; + } + + *_ostream << "}\n"; } *_ostream << std::flush; diff --git a/SilKit/source/services/metrics/Test_MetricsJsonSink.cpp b/SilKit/source/services/metrics/Test_MetricsJsonSink.cpp index 5f3806169..4b31d4562 100644 --- a/SilKit/source/services/metrics/Test_MetricsJsonSink.cpp +++ b/SilKit/source/services/metrics/Test_MetricsJsonSink.cpp @@ -43,6 +43,11 @@ bool read(const ryml::ConstNodeRef& node, MetricData* obj) node["mv"] >> stringList; obj->value = SilKit::Config::SerializeAsJson(stringList); } + else if (kind == "ATTRIBUTE") + { + obj->kind = MetricKind::ATTRIBUTE; + node["mv"] >> obj->value; + } else throw SilKit::ConfigurationError{"Invalid MetricData.kind " + kind}; return true; @@ -80,6 +85,12 @@ TEST(Test_MetricsJsonSink, test_json_escaping_and_structure) const std::string mv3b{"D\tE\nF\tG"}; const std::string mv3{fmt::format(R"(["{}","{}"])", EscapeString(mv3a), EscapeString(mv3b))}; + const MetricTimestamp ts4{4}; + const std::string mn4{"Metric\rName\n4"}; + const auto mk4 = MetricKind::ATTRIBUTE; + const std::string mv4{"Attribute\tValue\nWith\"Special\\Characters"}; + + MetricsUpdate update; update.metrics.emplace_back(MetricData{ ts1, @@ -99,6 +110,12 @@ TEST(Test_MetricsJsonSink, test_json_escaping_and_structure) mk3, mv3, }); + update.metrics.emplace_back(MetricData{ + ts4, + mn4, + mk4, + mv4, + }); auto ownedOstream = std::make_unique(); auto& ostream = *ownedOstream; @@ -127,7 +144,7 @@ TEST(Test_MetricsJsonSink, test_json_escaping_and_structure) // checks - ASSERT_EQ(nodes.size(), 3u); + ASSERT_EQ(nodes.size(), 4u); ASSERT_EQ(nodes[0].timestamp, ts1); ASSERT_EQ(nodes[0].name, mn1); @@ -143,6 +160,11 @@ TEST(Test_MetricsJsonSink, test_json_escaping_and_structure) ASSERT_EQ(nodes[2].name, mn3); ASSERT_EQ(nodes[2].kind, MetricKind::STRING_LIST); ASSERT_EQ(nodes[2].value, mv3); + + ASSERT_EQ(nodes[3].timestamp, ts4); + ASSERT_EQ(nodes[3].name, mn4); + ASSERT_EQ(nodes[3].kind, MetricKind::ATTRIBUTE); + ASSERT_EQ(nodes[3].value, mv4); } diff --git a/docs/configuration/experimental-configuration.rst b/docs/configuration/experimental-configuration.rst index 8a408521e..960a708af 100644 --- a/docs/configuration/experimental-configuration.rst +++ b/docs/configuration/experimental-configuration.rst @@ -83,6 +83,130 @@ They are collected and distributed to the configured sinks at the given update i * - updateInterval - The time between sending batches of metrics to the registry in seconds. +Available Metrics +~~~~~~~~~~~~~~~~~ + +Metric names use ``/`` as a hierarchy separator. +Some metrics contain runtime-specific path elements such as the simulation name or the +name of a connected peer participant. + +.. list-table:: Available Participant Metrics + :widths: 35 15 50 + :header-rows: 1 + + * - Metric name + - Kind + - Description + * - ``SilKit/System/OperatingSystem`` + - ``ATTRIBUTE`` + - Operating system reported by the participant host. + * - ``SilKit/System/Hostname`` + - ``ATTRIBUTE`` + - Host name of the participant host. + * - ``SilKit/System/PageSize`` + - ``ATTRIBUTE`` + - Memory page size reported by the participant host. + * - ``SilKit/System/ProcessorCount`` + - ``ATTRIBUTE`` + - Number of processors reported by the participant host. + * - ``SilKit/System/ProcessorArchitecture`` + - ``ATTRIBUTE`` + - Processor architecture reported by the participant host. + * - ``SilKit/System/PhysicalMemory`` + - ``ATTRIBUTE`` + - Total physical memory of the participant host. + * - ``SilKit/Process/Executable`` + - ``ATTRIBUTE`` + - Path of the running participant executable. + * - ``SilKit/Process/Username`` + - ``ATTRIBUTE`` + - User name under which the participant process runs. + * - ``SilKit/Participant/JsonConfig`` + - ``ATTRIBUTE`` + - Participant configuration serialized as JSON. + * - ``TcpAcceptors`` + - ``STRING_LIST`` + - TCP endpoints on which the participant accepts connections. + * - ``LocalAcceptors`` + - ``STRING_LIST`` + - Local domain socket endpoints on which the participant accepts connections. + * - ``Peer///LocalEndpoint`` + - ``STRING_LIST`` + - Local endpoint addresses used for the connection to the peer participant. + * - ``Peer///RemoteEndpoint`` + - ``STRING_LIST`` + - Remote endpoint addresses used for the connection to the peer participant. + * - ``Peer///tx_bytes/[bytes]`` + - ``COUNTER`` + - Total number of transmitted bytes for the peer connection. + * - ``Peer///tx_packets/[count]`` + - ``COUNTER`` + - Total number of transmitted packets for the peer connection. + * - ``Peer///tx_bandwidth/[Bps]`` + - ``STATISTIC`` + - Statistics over transmitted payload sizes, labeled in bytes per second. + * - ``Peer///rx_bytes/[bytes]`` + - ``COUNTER`` + - Total number of received bytes for the peer connection. + * - ``Peer///rx_packets/[count]`` + - ``COUNTER`` + - Total number of received packets for the peer connection. + * - ``Peer///tx_queue_size/[count]`` + - ``STATISTIC`` + - Statistics over the transmit queue size for the peer connection. + * - ``Peer///rx_bandwidth/[Bps]`` + - ``STATISTIC`` + - Statistics over received payload sizes, labeled in bytes per second. + * - ``SimStepCount`` + - ``COUNTER`` + - Number of completed simulation steps. + * - ``SimStep/execution_duration/[s]`` + - ``STATISTIC`` + - Statistics over simulation step handler execution time in seconds. + * - ``SimStep/completion_duration/[s]`` + - ``STATISTIC`` + - Statistics over asynchronous simulation step completion time in seconds. + * - ``SimStep/waiting_duration/[s]`` + - ``STATISTIC`` + - Statistics over waiting time between simulation steps in seconds. + +Metric Value Encoding +~~~~~~~~~~~~~~~~~~~~~ + +The metric kind determines the format of the metric value: + +.. list-table:: Metric Value Encoding + :widths: 15 85 + :header-rows: 1 + + * - Kind + - Value format + * - ``COUNTER`` + - A single integer value. + * - ``STATISTIC`` + - A JSON array ``[mean, stddev, min, max]``. + * - ``STRING_LIST`` + - A JSON array of strings. + * - ``ATTRIBUTE`` + - A string value. + +Example JSON Output +~~~~~~~~~~~~~~~~~~~ + +The ``JsonFile`` sink writes one JSON object per line. +Each object contains the timestamp in nanoseconds (``ts``), the participant name (``pn``), +the metric name (``mn``), the metric kind (``mk``), and the metric value (``mv``). + +.. code-block:: json + + {"ts":1716200000000000000,"pn":"Participant1","mn":"SimStepCount","mk":"COUNTER","mv":42} + {"ts":1716200000000100000,"pn":"Participant1","mn":"SimStep/execution_duration/[s]","mk":"STATISTIC","mv":[0.0017,0.0004,0.0012,0.0025]} + {"ts":1716200000000200000,"pn":"Participant1","mn":"Peer/Sim1/Participant2/tx_bytes/[bytes]","mk":"COUNTER","mv":32768} + {"ts":1716200000000300000,"pn":"Participant1","mn":"Peer/Sim1/Participant2/RemoteEndpoint","mk":"STRING_LIST","mv":["tcp://10.0.0.2:8500"]} + +The exact set of emitted metrics depends on which SIL Kit services are used and which peer +connections are established during the simulation run. + .. _sec:cfg-registry-experimental: Metrics for the registry @@ -108,4 +232,4 @@ Refer to the documentation of the `SIL Kit Dashboard