GeneratedEventTimestamp_plugin.cc
Go to the documentation of this file.
1 /**
2  * @file GeneratedEventTimestamp_plugin.cc
3  * @brief Assigns an empty event a time stamp from the clock
4  * @author Gianluca Petrillo (petrillo@fnal.gov)
5  *
6  * This file defines a plug in for art framework; no interface is needed since
7  * users will interface with the abstract base class.
8  */
9 
10 // C/C++ standard libraries
11 #include <chrono>
12 #include <random>
13 
14 // framework libraries
19 
20 // Event generation namespace
21 namespace evgen {
22 
23  /**
24  * @brief Plugin to assign an empty event a time stamp from the clock
25  * @see `art::EmptyEventTimestampPlugin`
26  *
27  * The plug in returns a time stamp that is taken from the current time
28  * on the execution node, in nanoseconds.
29  *
30  * The time is currently defined as absolute from the UNIX "epoch" (first day
31  * of year 1970), but its absolute precision should not be relied upon.
32  *
33  * Also note that the time is not guaranteed to be monotonic, that is,
34  * generating two events in sequence, it is not guaranteed that the second one
35  * has a time stamp larger than the previous. This may for example happen if
36  * the clock relies on a CPU internal counter, on a machine with multiple CPUs
37  * (that is probably all of them).
38  *
39  *
40  * Configuration
41  * --------------
42  *
43  * None so far.
44  *
45  */
47  public:
48 
49  /// Constructor: nothing specific
51 
52 
53  /// Returns the time stamp for the specified event
54  virtual art::Timestamp eventTimestamp(art::EventID const& id) override;
55 
56  /// Resets the status; since this plug in is stateless, this is a no-op
57  virtual void rewind() override {}
58 
59 
60  private:
61  /// Offset to be added to the chosen clock to get an absolute time.
63 
64  }; // class GeneratedEventTimestamp
65 
66 
67 
68 
69 } // namespace evgen
70 
71 //------------------------------------------------------------------------------
72 //--- Implementation
73 //---
74 //---
75 
76 namespace evgen {
77  namespace details {
78 
79  //--------------------------------------------------------------------------
80  template <typename T>
81  struct Average {
82  using data_t = T;
83 
84  void clear() { fTotal = data_t(0); fN = 0U; }
85  void insert(data_t value) { fTotal += value; ++fN; }
86 
87  data_t n() const { return fN; }
88  data_t average() const { return fTotal / fN; }
89 
90  private:
91  unsigned int fN = 0U;
92  data_t fTotal = data_t(0);
93 
94  }; // class Average
95 
96 
97  //--------------------------------------------------------------------------
98  /// Returns the multiple of `period` closest to `value`.
99  template <typename T>
100  auto discretize(T value, T period) {
101  auto const excess = value % period;
102  auto const base = value - excess;
103  return (excess < (period / T(2)))? base: base + period;
104  } // discretize()
105 
106 
107  //--------------------------------------------------------------------------
108  /// Class reading a `Clock` and converting the value to a specific `Unit`.
109  template <typename Clock, typename Unit>
111  public:
112 
113  /// Type of the time duration as returned by this class.
115 
116  /// Reads and returns the current time the clock.
117  duration_t operator() () { return read_clock(); }
118 
119  /// Reads and returns the current time the clock.
120  static duration_t read_clock() { return timeFromEpoch(Clock::now()); }
121 
122  /// Computes an approximation of the offset of the current time from the
123  /// epoch.
124  static duration_t currentOffsetFromEpoch();
125 
126  protected:
127 
128  /// Converts a `std::chrono::duration` into our duration metric.
129  template <typename TimeInterval>
130  static constexpr duration_t toDuration(TimeInterval dt)
131  {
132  return static_cast<duration_t>
133  (std::chrono::duration_cast<Unit>(dt).count());
134  }
135 
136  /// Returns the duration (`duration_t`) of a period type.
137  template <typename Rep, typename Period>
138  static constexpr auto periodToDuration()
139  { return toDuration(std::chrono::duration<Rep, Period>(1)); }
140 
141  /// Returns the time elapsed from the epoch to `t`.
142  template <typename TimePoint>
143  static duration_t timeFromEpoch(TimePoint t)
144  { return toDuration(t.time_since_epoch()); }
145 
146  }; // class TimeInUnitsBase<>
147 
148 
149  template <typename Clock, typename Duration>
151  -> duration_t
152  {
153  /*
154  * The plan is to compare the `Clock` we use with the system clock, which
155  * is guaranteed by the C++20 standard to refer to a well defined absolute
156  * time point (the UNIX epoch, January 1, 1970).
157  * Chances are that the resolution of the system clock is not as good as
158  * the one of `Clock`. If the difference between the two clocks is less
159  * than a few seconds, we attribute the difference to chance and we don't
160  * correct for it.
161  *
162  * Otherwise, the same time (almost!) is taken from the two clocks, and
163  * the difference in `Duration` units is used as a correction.
164  *
165  */
166  using clock_t = Clock;
167  using system_clock_t = std::chrono::system_clock;
168  using namespace std::chrono_literals;
169 
170  // no point in doing the exercise if we are already using the system clock
171  if (std::is_same<clock_t, system_clock_t>()) {
172  MF_LOG_DEBUG("GeneratedEventTimestamp")
173  << "Using system clock for timestamp: no offset needed.";
174  return static_cast<duration_t>(0);
175  }
176 
177  auto clock_time = clock_t::now();
178  auto sys_clock_time = system_clock_t::now();
179 
180  // if the system clock is close to our clock, of if it is ahead of it,
181  // use no offset (the latter stems from the consideration that that the
182  // two clocks are equivalent although they suffer from some jitter)
183  if (
184  (timeFromEpoch(sys_clock_time) - timeFromEpoch(clock_time))
185  < toDuration(5s)
186  )
187  {
188  MF_LOG_DEBUG("GeneratedEventTimestamp")
189  << "Offset with system clock is small ("
190  << (timeFromEpoch(sys_clock_time) - timeFromEpoch(clock_time))
191  << ", " << timeFromEpoch(sys_clock_time)
192  << " vs. " << timeFromEpoch(clock_time)
193  << "): no offset needed."
194  ;
195  return static_cast<duration_t>(0);
196  }
197 
198  //
199  // pick the largest of the resolutions for the comparison
200  //
201  using clock_period_t = typename clock_t::period;
202  using system_clock_period_t = system_clock_t::period;
203  using largest_period_t = std::conditional_t<
204  (
205  clock_period_t::num * system_clock_period_t::den
206  > system_clock_period_t::num * clock_period_t::den
207  ),
208  clock_period_t,
209  system_clock_period_t
210  >;
211  // this is the period expressed in the Duration unit
212  constexpr auto largest_period
213  = periodToDuration<typename clock_t::rep, largest_period_t>();
214 
215  //
216  // compare and round
217  //
218  constexpr unsigned int times = 10U; // average 10 samples
219  Average<duration_t> offset;
220  for (unsigned int i = 0; i < times; ++i) {
221 
222  offset.insert
223  (timeFromEpoch(sys_clock_time) - timeFromEpoch(clock_time));
224  clock_time = clock_t::now();
225  sys_clock_time = system_clock_t::now();
226 
227  } // for
228 
229  MF_LOG_DEBUG("GeneratedEventTimestamp")
230  << "System clock period: "
231  << periodToDuration<typename clock_t::rep, system_clock_period_t>()
232  << "\nUser clock period: "
233  << periodToDuration<typename clock_t::rep, clock_period_t>()
234  << "\nOffset: " << offset.average()
235  << " (rounded to: " << largest_period << ")"
236  ;
237 
238  // round off the offset with one "largest period"
239  return discretize(offset.average(), largest_period);
240 
241  } // TimeInUnitsBase<>::currentOffsetFromEpoch()
242 
243 
244  //--------------------------------------------------------------------------
245  /// Class reading a clock and converting the value to a specific unit;
246  /// if the unit is more precise than the clock, random padding fills the gap
247  template <typename Clock, typename Duration, typename = void>
248  class TimeInUnits: public TimeInUnitsBase<Clock, Duration> {};
249 
250 
251  // Implementation of the random-gap-filling version
252  template <typename Clock, typename Duration>
253  class TimeInUnits<Clock, Duration, typename std::enable_if<
254  (Clock::period::num * Duration::period::den > Duration::period::num * Clock::period::den)
255  >::type
256  >
257  : public TimeInUnitsBase<Clock, Duration>
258  {
260  using typename Base_t::duration_t;
261 
262  // if the period of the clock is larger than the unit we are requested,
263  // there will be some padding
264  using ClockPeriod = typename Clock::period;
265  using ReqPeriod = typename Duration::period;
266  // requested clock / unit:
267  using PeriodRatio = std::ratio<
268  ClockPeriod::num * ReqPeriod::den, ReqPeriod::num * ClockPeriod::den
269  >;
270  static constexpr intmax_t paddingfactor
271  = PeriodRatio::num / PeriodRatio::den; // > 1 enforced by enable_if<>
272 
273  public:
274  TimeInUnits(): engine(), flat(0, paddingfactor - 1) {}
275 
276  /// Return the clock value with random padding added
277  duration_t operator() ()
278  { return Base_t::read_clock() + flat(engine); }
279 
280  private:
281  std::default_random_engine engine;
282  std::uniform_int_distribution<duration_t> flat;
283  }; // class TimeInUnits<> (padded)
284 
285 
287  <std::chrono::high_resolution_clock, std::chrono::nanoseconds>;
288 
289  //--------------------------------------------------------------------------
290 
291  } // namespace details
292 } // namespace evgen
293 
294 
295 //------------------------------------------------------------------------------
297  (fhicl::ParameterSet const& pset)
300 {
301 
302  mf::LogInfo("GeneratedEventTimestamp")
303  << "Timestamp plugin: timestamp from local clock time in nanoseconds";
304  if (fOffsetFromEpoch != 0) {
305  MF_LOG_TRACE("GeneratedEventTimestamp")
306  << " Time offset from epoch: " << fOffsetFromEpoch << " ns";
307  }
308 
309 } // evgen::GeneratedEventTimestamp::GeneratedEventTimestamp()
310 
311 
312 //------------------------------------------------------------------------------
314  (art::EventID const& id)
315 {
316  // obtain from the high resolution clock the current time, from the "epoch",
317  // in nanoseconds; if the clock is less precise than the nanosecond,
318  // the precision gap is filled with randomness
319  details::ns_clock_t get_time;
320 
321  const long long int now_ns = fOffsetFromEpoch + get_time();
322 
323  // convert into a timestamp
324  art::Timestamp ts(now_ns);
325 
326  mf::LogTrace("GeneratedEventTimestamp")
327  << "Generated time stamp: " << ts.value() << " for event " << id;
328 
329  return ts;
330 } // evgen::GeneratedEventTimestamp::eventTimestamp()
331 
332 // make art aware that we have a plugin
334 
std::ratio< ClockPeriod::num *ReqPeriod::den, ReqPeriod::num *ClockPeriod::den > PeriodRatio
int64_t intmax_t
Definition: stdint.h:169
MaybeLogger_< ELseverityLevel::ELsev_info, false > LogInfo
virtual void rewind() override
Resets the status; since this plug in is stateless, this is a no-op.
auto discretize(T value, T period)
Returns the multiple of period closest to value.
Plugin to assign an empty event a time stamp from the clock.
STL namespace.
static constexpr auto periodToDuration()
Returns the duration (duration_t) of a period type.
constexpr TimeValue_t value() const
Definition: Timestamp.h:23
Class reading a Clock and converting the value to a specific Unit.
static duration_t read_clock()
Reads and returns the current time the clock.
GeneratedEventTimestamp(fhicl::ParameterSet const &pset)
Constructor: nothing specific.
#define MF_LOG_TRACE(id)
std::uniform_int_distribution< duration_t > flat
art::TimeValue_t const fOffsetFromEpoch
Offset to be added to the chosen clock to get an absolute time.
#define DEFINE_ART_EMPTYEVENTTIMESTAMP_PLUGIN(klass)
static constexpr duration_t toDuration(TimeInterval dt)
Converts a std::chrono::duration into our duration metric.
std::uint64_t TimeValue_t
Definition: Timestamp.h:8
const GenericPointer< typename T::ValueType > T2 value
Definition: pointer.h:1225
virtual art::Timestamp eventTimestamp(art::EventID const &id) override
Returns the time stamp for the specified event.
#define MF_LOG_DEBUG(id)
art::TimeValue_t duration_t
Type of the time duration as returned by this class.
art::TimeValue_t duration_t
Type of the time duration as returned by this class.
MaybeLogger_< ELseverityLevel::ELsev_success, true > LogTrace
Event Generation using GENIE, cosmics or single particles.
static duration_t timeFromEpoch(TimePoint t)
Returns the time elapsed from the epoch to t.
static QCString * s
Definition: config.cpp:1042
nanosecond nanoseconds
Alias for common language habits.
Definition: spacetime.h:134