Add definitions and helpers for timing reports
Add helpers for computing statistics. This requires us to add the
timing report definition too. These will be used shortly for building
timing reports.
Change-Id: I944ab60d84fd05f99faaa0b9cd0f9c8dc99c4429
diff --git a/aos/events/BUILD b/aos/events/BUILD
index 3377d67..9f841df 100644
--- a/aos/events/BUILD
+++ b/aos/events/BUILD
@@ -10,6 +10,15 @@
)
flatbuffer_cc_library(
+ name = "event_loop_fbs",
+ srcs = ["event_loop.fbs"],
+ gen_reflections = 1,
+ includes = [
+ "//aos:configuration_fbs_includes",
+ ],
+)
+
+flatbuffer_cc_library(
name = "ping_fbs",
srcs = ["ping.fbs"],
gen_reflections = 1,
@@ -142,6 +151,28 @@
)
cc_library(
+ name = "timing_statistics",
+ srcs = ["timing_statistics.cc"],
+ hdrs = ["timing_statistics.h"],
+ deps = [
+ ":event_loop_fbs",
+ "//aos:configuration",
+ "@com_github_google_glog//:glog",
+ ],
+)
+
+cc_test(
+ name = "timing_statistics_test",
+ srcs = ["timing_statistics_test.cc"],
+ deps = [
+ ":timing_statistics",
+ "//aos:configuration",
+ "//aos:flatbuffers",
+ "//aos/testing:googletest",
+ ],
+)
+
+cc_library(
name = "shm_event_loop",
srcs = ["shm_event_loop.cc"],
hdrs = ["shm_event_loop.h"],
diff --git a/aos/events/event_loop.fbs b/aos/events/event_loop.fbs
new file mode 100644
index 0000000..4053a75
--- /dev/null
+++ b/aos/events/event_loop.fbs
@@ -0,0 +1,88 @@
+include "aos/configuration.fbs";
+
+namespace aos.timing;
+
+// Holds statistics for a time or size sample.
+table Statistic {
+ average:float = nan;
+ min:float = nan;
+ max:float = nan;
+ standard_deviation:float = nan;
+}
+
+table Sender {
+ // Index into the channel config for this event loop.
+ channel_index:int = -1;
+
+ // Number of messages published.
+ count:uint;
+ // Statistics on the size of messages published.
+ size:Statistic;
+
+ // Channel for this sender. Not filled out by default.
+ channel:Channel;
+}
+
+table Watcher {
+ // Index into the channel config for this event loop.
+ channel_index:int = -1;
+
+ // Number of messages received since the last report.
+ count:uint;
+
+ // Latency measurement from when the event was generated (send time), and when
+ // the handler was started.
+ wakeup_latency:Statistic;
+ // Statistics on the execution time of the handler.
+ handler_time:Statistic;
+
+ // Channel for this watcher. Not filled out by default.
+ channel:Channel;
+}
+
+table Fetcher {
+ // Index into the channel config for this event loop.
+ channel_index:int = -1;
+
+ // Number of messages fetched since the last time this was published.
+ count:uint;
+ // Latency measurement from when the event was generated (send time), and when
+ // the message was fetched.
+ latency:Statistic;
+
+ // Channel for this fetcher. Not filled out by default.
+ channel:Channel;
+}
+
+table Timer {
+ name:string;
+
+ // Number of wakeups since the last report.
+ count:uint;
+
+ // Latency measurement from when the event was generated (send time), and when
+ // the handler was started.
+ wakeup_latency:Statistic;
+ // Statistics on the execution time of the handler.
+ handler_time:Statistic;
+
+ // Maximum number of cycles missed.
+}
+
+table Report {
+ // Name of the event loop which is publishing this report.
+ name:string;
+ // Identifier for the event loop. This should change every time a process
+ // gets restarted.
+ pid:int;
+
+ // List of statistics for each watcher, sender, fetcher, timer, and
+ // phased loop.
+ watchers:[Watcher];
+ senders:[Sender];
+ fetchers:[Fetcher];
+ timers:[Timer];
+ phased_loops:[Timer];
+}
+
+root_type Report;
diff --git a/aos/events/timing_statistics.cc b/aos/events/timing_statistics.cc
new file mode 100644
index 0000000..cdeb588
--- /dev/null
+++ b/aos/events/timing_statistics.cc
@@ -0,0 +1,45 @@
+#include "aos/events/timing_statistics.h"
+
+#include "aos/events/event_loop_generated.h"
+#include "glog/logging.h"
+
+namespace aos {
+namespace internal {
+
+void RawFetcherTiming::set_timing_report(timing::Fetcher *new_fetcher) {
+ CHECK_NOTNULL(new_fetcher);
+ fetcher = new_fetcher;
+ latency.set_statistic(fetcher->mutable_latency());
+}
+
+void RawFetcherTiming::ResetTimingReport() {
+ latency.Reset();
+ fetcher->mutate_count(0);
+}
+
+void RawSenderTiming::set_timing_report(timing::Sender *new_sender) {
+ CHECK_NOTNULL(new_sender);
+ sender = new_sender;
+ size.set_statistic(sender->mutable_size());
+}
+
+void RawSenderTiming::ResetTimingReport() {
+ size.Reset();
+ sender->mutate_count(0);
+}
+
+void TimerTiming::set_timing_report(timing::Timer *new_timer) {
+ CHECK_NOTNULL(new_timer);
+ timer = new_timer;
+ wakeup_latency.set_statistic(timer->mutable_wakeup_latency());
+ handler_time.set_statistic(timer->mutable_handler_time());
+}
+
+void TimerTiming::ResetTimingReport() {
+ wakeup_latency.Reset();
+ handler_time.Reset();
+ timer->mutate_count(0);
+}
+
+} // namespace internal
+} // namespace aos
diff --git a/aos/events/timing_statistics.h b/aos/events/timing_statistics.h
new file mode 100644
index 0000000..ad0534a
--- /dev/null
+++ b/aos/events/timing_statistics.h
@@ -0,0 +1,97 @@
+#ifndef AOS_EVENTS_TIMING_STATISTICS_H_
+#define AOS_EVENTS_TIMING_STATISTICS_H_
+
+#include <cmath>
+
+#include "aos/events/event_loop_generated.h"
+
+namespace aos {
+namespace internal {
+
+// Class to compute statistics for the timing report.
+class TimingStatistic {
+ public:
+ TimingStatistic() {}
+
+ // Sets the flatbuffer to mutate.
+ void set_statistic(timing::Statistic *statistic) { statistic_ = statistic; }
+
+ // Adds a sample to the statistic.
+ void Add(float sample) {
+ ++count_;
+ if (count_ == 1) {
+ statistic_->mutate_average(sample);
+ statistic_->mutate_min(sample);
+ statistic_->mutate_max(sample);
+ statistic_->mutate_standard_deviation(0.0);
+ } else {
+ // https://en.wikipedia.org/wiki/Standard_deviation#Rapid_calculation_methods
+ const float prior_average = statistic_->average();
+ const float average = prior_average + (sample - prior_average) / count_;
+ statistic_->mutate_average(average);
+ statistic_->mutate_max(std::max(statistic_->max(), sample));
+ statistic_->mutate_min(std::min(statistic_->min(), sample));
+
+ Q_ = Q_ + (sample - prior_average) * (sample - average);
+ statistic_->mutate_standard_deviation(std::sqrt(Q_ / (count_ - 1)));
+ }
+ }
+
+ // Clears any accumulated statistics.
+ void Reset() {
+ statistic_->mutate_average(std::numeric_limits<float>::quiet_NaN());
+ statistic_->mutate_min(std::numeric_limits<float>::quiet_NaN());
+ statistic_->mutate_max(std::numeric_limits<float>::quiet_NaN());
+
+ statistic_->mutate_standard_deviation(
+ std::numeric_limits<float>::quiet_NaN());
+ Q_ = 0;
+ count_ = 0;
+ }
+
+ private:
+ timing::Statistic *statistic_ = nullptr;
+ // Number of samples accumulated.
+ size_t count_ = 0;
+ // State Q from wikipedia.
+ float Q_ = 0.0;
+};
+
+// Class to hold timing information for a raw fetcher.
+struct RawFetcherTiming {
+ RawFetcherTiming(int new_channel_index) : channel_index(new_channel_index) {}
+
+ void set_timing_report(timing::Fetcher *fetcher);
+ void ResetTimingReport();
+
+ const int channel_index;
+ timing::Fetcher *fetcher = nullptr;
+ internal::TimingStatistic latency;
+};
+
+// Class to hold timing information for a raw sender.
+struct RawSenderTiming {
+ RawSenderTiming(int new_channel_index) : channel_index(new_channel_index) {}
+
+ void set_timing_report(timing::Sender *sender);
+ void ResetTimingReport();
+
+ const int channel_index;
+ timing::Sender *sender = nullptr;
+ internal::TimingStatistic size;
+};
+
+// Class to hold timing information for timers.
+struct TimerTiming {
+ void set_timing_report(timing::Timer *timer);
+ void ResetTimingReport();
+
+ internal::TimingStatistic wakeup_latency;
+ internal::TimingStatistic handler_time;
+ timing::Timer *timer = nullptr;
+};
+
+} // namespace internal
+} // namespace aos
+
+#endif // AOS_EVENTS_TIMING_STATISTICS_H_
diff --git a/aos/events/timing_statistics_test.cc b/aos/events/timing_statistics_test.cc
new file mode 100644
index 0000000..480cce3
--- /dev/null
+++ b/aos/events/timing_statistics_test.cc
@@ -0,0 +1,71 @@
+#include "aos/events/timing_statistics.h"
+
+#include "aos/flatbuffers.h"
+#include "gtest/gtest.h"
+
+namespace aos {
+namespace internal {
+namespace testing {
+
+TEST(TimingStatistic, StatisticsTest) {
+ flatbuffers::FlatBufferBuilder fbb;
+ fbb.Finish(timing::CreateStatistic(fbb));
+
+ FlatbufferDetachedBuffer<timing::Statistic> statistic(fbb.Release());
+
+ TimingStatistic ts;
+ ts.set_statistic(statistic.mutable_message());
+
+ // Make sure we can add 2 numbers and get the expected result.
+ ts.Add(5.0);
+
+ EXPECT_EQ(statistic.message().average(), 5.0);
+ EXPECT_EQ(statistic.message().min(), 5.0);
+ EXPECT_EQ(statistic.message().max(), 5.0);
+ EXPECT_EQ(statistic.message().standard_deviation(), 0.0);
+
+ ts.Add(5.0);
+
+ EXPECT_EQ(statistic.message().average(), 5.0);
+ EXPECT_EQ(statistic.message().min(), 5.0);
+ EXPECT_EQ(statistic.message().max(), 5.0);
+ EXPECT_EQ(statistic.message().standard_deviation(), 0.0);
+
+ // Make sure reset works.
+ ts.Reset();
+
+ // And all the results are nan.
+ EXPECT_TRUE(std::isnan(statistic.message().average()));
+ EXPECT_TRUE(std::isnan(statistic.message().min()));
+ EXPECT_TRUE(std::isnan(statistic.message().max()));
+ EXPECT_TRUE(std::isnan(statistic.message().standard_deviation()));
+
+ ts.Add(7.0);
+
+ EXPECT_EQ(statistic.message().average(), 7.0);
+ EXPECT_EQ(statistic.message().min(), 7.0);
+ EXPECT_EQ(statistic.message().max(), 7.0);
+ EXPECT_EQ(statistic.message().standard_deviation(), 0.0);
+
+ ts.Reset();
+
+ // Now add a predetermined set of data and make sure we get a result which
+ // agrees with online calculators.
+ ts.Add(10);
+ ts.Add(12);
+ ts.Add(23);
+ ts.Add(23);
+ ts.Add(16);
+ ts.Add(23);
+ ts.Add(21);
+ ts.Add(16);
+
+ EXPECT_EQ(statistic.message().average(), 18.0);
+ EXPECT_EQ(statistic.message().min(), 10.0);
+ EXPECT_EQ(statistic.message().max(), 23.0);
+ EXPECT_NEAR(statistic.message().standard_deviation(), 5.2372293656638, 1e-6);
+}
+
+} // namespace testing
+} // namespace internal
+} // namespace aos