1 /*
2  * Copyright (c) 2019 Simon Frasch
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are met:
6  *
7  * 1. Redistributions of source code must retain the above copyright notice,
8  *    this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  * 3. Neither the name of the copyright holder nor the names of its contributors
13  *    may be used to endorse or promote products derived from this software
14  *    without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26  * POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 #ifndef RT_GRAPH_HPP_GUARD
30 #define RT_GRAPH_HPP_GUARD
31 
32 #include <atomic>
33 #include <chrono>
34 #include <cstddef>
35 #include <deque>
36 #include <list>
37 #include <string>
38 #include <vector>
39 
40 namespace rt_graph {
41 
42 using ClockType = std::chrono::high_resolution_clock;
43 
44 // Selection of available statistics
45 enum class Stat {
46   Count,            // Number of measurements
47   Total,            // Total accumulated time
48   Mean,             // Mean time
49   Median,           // Median time
50   QuartileHigh,     // Third quartile time
51   QuartileLow,      // First quartile time
52   Min,              // Mininum time
53   Max,              // Maximum time
54   Percentage,       // Percentage of accumulated time with respect to the top-level node in graph
55   ParentPercentage  // Percentage of accumulated time with respect to the parent node in graph
56 };
57 
58 // internal helper functionality
59 namespace internal {
60 
61 enum class TimeStampType { Start, Stop, Empty };
62 
63 struct TimeStamp {
TimeStamprt_graph::internal::TimeStamp64   TimeStamp() : type(TimeStampType::Empty) {}
65 
66   // Identifier pointer must point to compile time string literal
TimeStamprt_graph::internal::TimeStamp67   TimeStamp(const char* identifier, const TimeStampType& stampType)
68       : time(ClockType::now()), identifierPtr(identifier), type(stampType) {}
69 
70   ClockType::time_point time;
71   const char* identifierPtr;
72   TimeStampType type;
73 };
74 
75 struct TimingNode {
76   std::string identifier;
77   std::vector<double> timings;
78   std::list<TimingNode> subNodes;
79 };
80 }  // namespace internal
81 
82 // Processed timings results.
83 class TimingResult {
84 public:
TimingResult(std::list<internal::TimingNode> rootNodes,std::string warnings)85   TimingResult(std::list<internal::TimingNode> rootNodes, std::string warnings)
86       : rootNodes_(std::move(rootNodes)), warnings_(std::move(warnings)) {}
87 
88   // Get json representation of the full graph with all timings. Unit of time is seconds.
89   auto json() const -> std::string;
90 
91   // Get all timings for given identifier
92   auto get_timings(const std::string& identifier) const -> std::vector<double>;
93 
94   // Print graph statistic to string.
95   auto print(std::vector<Stat> statistic = {Stat::Count, Stat::Total, Stat::Percentage,
96                                             Stat::ParentPercentage, Stat::Median, Stat::Min,
97                                             Stat::Max}) const -> std::string;
98 
99 private:
100   std::list<internal::TimingNode> rootNodes_;
101   std::string warnings_;
102 };
103 
104 class ScopedTiming;
105 
106 // Timer class, which allows to start / stop measurements with a given identifier.
107 class Timer {
108 public:
109   // reserve space for 1000'000 measurements
Timer()110   Timer() { timeStamps_.reserve(2 * 1000 * 1000); }
111 
112   // reserve space for given number of measurements
Timer(std::size_t reserveCount)113   explicit Timer(std::size_t reserveCount) { timeStamps_.reserve(2 * reserveCount); }
114 
115   // start with string literal identifier
116   template <std::size_t N>
start(const char (& identifierPtr)[N])117   inline auto start(const char (&identifierPtr)[N]) -> void {
118     atomic_signal_fence(std::memory_order_seq_cst);  // only prevents compiler reordering
119     timeStamps_.emplace_back(identifierPtr, internal::TimeStampType::Start);
120     atomic_signal_fence(std::memory_order_seq_cst);  // only prevents compiler reordering
121   }
122 
123   // start with string identifier (storing string object comes with some additional overhead)
start(std::string identifier)124   inline auto start(std::string identifier) -> void {
125     atomic_signal_fence(std::memory_order_seq_cst);  // only prevents compiler reordering
126     identifierStrings_.emplace_back(std::move(identifier));
127     timeStamps_.emplace_back(identifierStrings_.back().c_str(), internal::TimeStampType::Start);
128     atomic_signal_fence(std::memory_order_seq_cst);  // only prevents compiler reordering
129   }
130 
131   // stop with string literal identifier
132   template <std::size_t N>
stop(const char (& identifierPtr)[N])133   inline auto stop(const char (&identifierPtr)[N]) -> void {
134     atomic_signal_fence(std::memory_order_seq_cst);  // only prevents compiler reordering
135     timeStamps_.emplace_back(identifierPtr, internal::TimeStampType::Stop);
136     atomic_signal_fence(std::memory_order_seq_cst);  // only prevents compiler reordering
137   }
138 
139   // stop with string identifier (storing string object comes with some additional overhead)
stop(std::string identifier)140   inline auto stop(std::string identifier) -> void {
141     atomic_signal_fence(std::memory_order_seq_cst);  // only prevents compiler reordering
142     identifierStrings_.emplace_back(std::move(identifier));
143     timeStamps_.emplace_back(identifierStrings_.back().c_str(), internal::TimeStampType::Stop);
144     atomic_signal_fence(std::memory_order_seq_cst);  // only prevents compiler reordering
145   }
146 
147   // clear timer and reserve space for given number of new measurements.
clear(std::size_t reserveCount)148   inline auto clear(std::size_t reserveCount) -> void {
149     timeStamps_.clear();
150     identifierStrings_.clear();
151     this->reserve(reserveCount);
152   }
153 
154   // reserve space for given number of measurements. Can prevent allocations at start / stop calls.
reserve(std::size_t reserveCount)155   inline auto reserve(std::size_t reserveCount) -> void { timeStamps_.reserve(reserveCount); }
156 
157   // process timings into result type
158   auto process() const -> TimingResult;
159 
160 private:
stop_with_ptr(const char * identifierPtr)161   inline auto stop_with_ptr(const char* identifierPtr) -> void {
162     atomic_signal_fence(std::memory_order_seq_cst);  // only prevents compiler reordering
163     timeStamps_.emplace_back(identifierPtr, internal::TimeStampType::Stop);
164     atomic_signal_fence(std::memory_order_seq_cst);  // only prevents compiler reordering
165   }
166 
167   friend ScopedTiming;
168 
169   std::vector<internal::TimeStamp> timeStamps_;
170   std::deque<std::string>
171       identifierStrings_;  // pointer to elements always remain valid after push back
172 };
173 
174 // Helper class, which calls start() upon creation and stop() on timer when leaving scope with given
175 // identifier.
176 class ScopedTiming {
177 public:
178   // timer reference must be valid for the entire lifetime
179   template <std::size_t N>
ScopedTiming(const char (& identifierPtr)[N],Timer & timer)180   ScopedTiming(const char (&identifierPtr)[N], Timer& timer)
181       : identifierPtr_(identifierPtr), timer_(timer) {
182     timer_.start(identifierPtr);
183   }
184 
ScopedTiming(std::string identifier,Timer & timer)185   ScopedTiming(std::string identifier, Timer& timer)
186       : identifierPtr_(nullptr), identifier_(std::move(identifier)), timer_(timer) {
187     timer_.start(identifier_);
188   }
189 
190   ScopedTiming(const ScopedTiming&) = delete;
191   ScopedTiming(ScopedTiming&&) = delete;
192   auto operator=(const ScopedTiming&) -> ScopedTiming& = delete;
193   auto operator=(ScopedTiming &&) -> ScopedTiming& = delete;
194 
~ScopedTiming()195   ~ScopedTiming() {
196     if (identifierPtr_) {
197       timer_.stop_with_ptr(identifierPtr_);
198     } else {
199       timer_.stop(std::move(identifier_));
200     }
201   }
202 
203 private:
204   const char* identifierPtr_;
205   std::string identifier_;
206   Timer& timer_;
207 };
208 
209 }  // namespace rt_graph
210 
211 #endif
212 
213