Skip to content

Commit

Permalink
Latency test with maximum stats and custom process evaluation (#773)
Browse files Browse the repository at this point in the history
Updates the latency test of hub_test:
- refactors the stats printing code into the Stats class
- adds max statistic to the class
- adds the latency consumer object that can be included in a product to verify the application level latency.

===

* Adds a debug print function to the stats object.
This will be usable for printing a summary of the stats for a developer printout like a log statement.

* Adds maximum to the statistics object.

* Adds a consumer object for event based testing.

* Adds hook to the latency test consumer. This allows testing latency
of arbitrary internal node processing.

* Adds documentation comment to latency test consumer.

* Fix comment.

* Fixes data type of max.
  • Loading branch information
balazsracz committed Feb 5, 2024
1 parent 8262386 commit b946516
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 3 deletions.
3 changes: 1 addition & 2 deletions applications/hub_test/targets/linux.x86/main.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -414,8 +414,7 @@ class Receiver : public CanHubPortInterface
numFrames_ = numUnknownFrames_ = numInOrder_ = numOutOfOrder_ =
numMissed_ = 0;

ret += StringPrintf("|RTT %.1f msec +- %.1f\n",
rttUsec_.favg()/1000, rttUsec_.stddev() / 1000);
ret += StringPrintf("|RTT %s\n", rttUsec_.debug_string().c_str());

rttUsec_.clear();
return ret;
Expand Down
133 changes: 133 additions & 0 deletions src/openlcb/LatencyTestConsumer.hxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/** \copyright
* Copyright (c) 2024, Balazs Racz
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* \file LatencyTestConsumer.hxx
*
* Utility class for determining the response latency of a node.
*
* @author Balazs Racz
* @date 2 Feb 2024
*/

#ifndef _OPENLCB_LATENCYTESTCONSUMER_HXX_
#define _OPENLCB_LATENCYTESTCONSUMER_HXX_

#include "openlcb/EventHandlerTemplates.hxx"

namespace openlcb
{

/// This event consumer works together with the hub_test application in order
/// to detect the response latency of a node. The theory of operation is that
/// hub_test sends out an identify consumer message with a given event ID, and
/// this consumer is going to respond. The hub_test then measures the response
/// latency. There is a big event range being advertised (32 bits), and
/// hub_test will send events with different IDs to be able to match request to
/// response.
///
/// Here in the consumer the only thing we need to do is respond to identify
/// consumer messages with consumer identified.
///
/// An additional feature is to be able to measure an arbitrary processing step
/// inside the node. For this purpose we have a hook function. When the
/// identify producer message comes in, we call the hook. When the measured
/// process completes, it should notify the given notifiable. Only thereafter
/// the consumer will reply on the bus. Requests' handling is not
/// parallelized. If the hook process cannot complete the requests fast enough,
/// the node will run out of memory and crash.
class LatencyTestConsumer : public SimpleEventHandler
{
public:
/// To complete the hook, call the notifiable.
using HookFn = std::function<void(Notifiable *)>;

LatencyTestConsumer(Node *node, HookFn hook = nullptr)
: node_(node)
, hook_(hook)
{
EventRegistry::instance()->register_handler(
EventRegistryEntry(this, EVENT_BASE), 32);
}

void handle_identify_global(const EventRegistryEntry &registry_entry,
EventReport *event, BarrierNotifiable *done) override
{
AutoNotify an(done);
if (event->dst_node && event->dst_node != node_)
{
return;
}
event->event_write_helper<1>()->WriteAsync(node_,
Defs::MTI_CONSUMER_IDENTIFIED_RANGE, WriteHelper::global(),
eventid_to_buffer(EncodeRange(EVENT_BASE, 0xffffffff)),
done->new_child());
}

void handle_identify_consumer(const EventRegistryEntry &entry,
EventReport *event, BarrierNotifiable *done) override
{
event_ = event;
done_ = done;
if (hook_)
{
hook_(new TempNotifiable([this]() { reply(); }));
}
else
{
reply();
}
}

private:
void reply()
{
AutoNotify an(done_);
event_->event_write_helper<1>()->WriteAsync(node_,
Defs::MTI_CONSUMER_IDENTIFIED_UNKNOWN, WriteHelper::global(),
eventid_to_buffer(event_->event), done_->new_child());
}

/// This is within NMRA ID 1, which is not assigned. Lower four bytes are
/// the id.
static constexpr EventId EVENT_BASE = 0x0900013900000000;

/// Which node should be sending responses.
Node *node_;

/// The owner's hook will be invoked and run (asynchronous) before
/// replying..
HookFn hook_;

/// Incoming event report we are working on.
EventReport *event_;

/// Will notify this after sending the reply.
BarrierNotifiable *done_ {nullptr};
};

} // namespace openlcb

#endif // _OPENLCB_LATENCYTESTCONSUMER_HXX_
43 changes: 43 additions & 0 deletions src/utils/Stats.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/** \copyright
* Copyright (c) 2023, Balazs Racz
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* \file Stats.hxx
*
* Utility class for collecting statistics.
*
* @author Balazs Racz
* @date 28 Dec 2023
*/

#include "utils/Stats.hxx"

#include "utils/StringPrintf.hxx"

std::string Stats::debug_string()
{
return StringPrintf("%.1f msec +- %.1f, max %.1f\n", favg() / 1000,
stddev() / 1000, ((FloatType)max_) / 1000);
}
15 changes: 14 additions & 1 deletion src/utils/Stats.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,14 @@

#include <math.h>
#include <stdint.h>
#include <string>
#include <limits>

class Stats
{
public:
using FloatType = double;
using ValueType = int32_t;

Stats()
{
Expand All @@ -54,16 +57,21 @@ public:
sum_ = 0;
count_ = 0;
qsum_ = 0;
max_ = std::numeric_limits<ValueType>::min();
}

/// Appends a data point to the statistics.
/// @param value the data point.
void add(int32_t value)
void add(ValueType value)
{
++count_;
sum_ += value;
FloatType fval = value;
qsum_ += fval * fval;
if (value > max_)
{
max_ = value;
}
}

/// @return the average
Expand All @@ -86,9 +94,14 @@ public:
return sqrt(qsum_ * count_ - sum * sum) / count_;
}

/// Creates a half-a-line printout of this stats object for debug purposes.
std::string debug_string();

private:
/// Number of samples added.
uint32_t count_;
/// Maximum value found since the last clear.
ValueType max_;
/// Sum of sample values added.
int64_t sum_;
/// Sum of squares of sample values added.
Expand Down
1 change: 1 addition & 0 deletions src/utils/sources
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ CXXSRCS += \
Queue.cxx \
ReflashBootloader.cxx \
ServiceLocator.cxx \
Stats.cxx \
SocketCan.cxx \
SocketClient.cxx \
constants.cxx \
Expand Down

0 comments on commit b946516

Please sign in to comment.