xref: /aosp_15_r20/external/perfetto/src/trace_processor/importers/common/clock_tracker.h (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #ifndef SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_CLOCK_TRACKER_H_
18 #define SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_CLOCK_TRACKER_H_
19 
20 #include <stdint.h>
21 
22 #include <array>
23 #include <cinttypes>
24 #include <cstdint>
25 #include <map>
26 #include <optional>
27 #include <random>
28 #include <set>
29 #include <vector>
30 
31 #include "perfetto/base/logging.h"
32 #include "perfetto/ext/base/flat_hash_map.h"
33 #include "perfetto/ext/base/status_or.h"
34 #include "perfetto/public/compiler.h"
35 #include "src/trace_processor/importers/common/metadata_tracker.h"
36 #include "src/trace_processor/types/trace_processor_context.h"
37 #include "src/trace_processor/util/status_macros.h"
38 
39 namespace perfetto {
40 namespace trace_processor {
41 
42 class ClockTrackerTest;
43 class TraceProcessorContext;
44 
45 // This class handles synchronization of timestamps across different clock
46 // domains. This includes multi-hop conversions from two clocks A and D, e.g.
47 // A->B -> B->C -> C->D, even if we never saw a snapshot that contains A and D
48 // at the same time.
49 // The API is fairly simple (but the inner operation is not):
50 // - AddSnapshot(map<clock_id, timestamp>): pushes a set of clocks that have
51 //   been snapshotted at the same time (within technical limits).
52 // - ToTraceTime(src_clock_id, src_timestamp):
53 //   converts a timestamp between clock domain and TraceTime.
54 //
55 // Concepts:
56 // - Snapshot hash:
57 //   As new snapshots are pushed via AddSnapshot() we compute a snapshot hash.
58 //   Such hash is the hash(clock_ids) (only IDs, not their timestamps) and is
59 //   used to find other snapshots that involve the same clock domains.
60 //   Two clock snapshots have the same hash iff they snapshot the same set of
61 //   clocks (the order of clocks is irrelevant).
62 //   This hash is used to efficiently go from the clock graph pathfinder to the
63 //   time-series obtained by appending the various snapshots.
64 // - Snapshot id:
65 //   A simple monotonic counter that is incremented on each AddSnapshot() call.
66 //
67 // Data structures:
68 //  - For each clock domain:
69 //    - For each snapshot hash:
70 //      - A logic vector of (snapshot_id, timestamp) tuples (physically stored
71 //        as two vectors of the same length instead of a vector of pairs).
72 // This allows to efficiently binary search timestamps within a clock domain
73 // that were obtained through a particular snapshot.
74 //
75 // - A graph of edges (source_clock, target_clock) -> snapshot hash.
76 //
77 // Operation:
78 // Upon each AddSnapshot() call, we incrementally build an unweighted, directed
79 // graph, which has clock domains as nodes.
80 // The graph is timestamp-oblivious. As long as we see one snapshot that
81 // connects two clocks, we assume we'll always be able to convert between them.
82 // This graph is queried by the Convert() function to figure out the shortest
83 // path between clock domain, possibly involving hopping through snapshots of
84 // different type (i.e. different hash).
85 //
86 // Example:
87 
88 // We see a snapshot, with hash S1, for clocks (A,B,C). We build the edges in
89 // the graph: A->B, B->C, A->C (and the symmetrical ones). In other words we
90 // keep track of the fact that we can convert between any of them using S1.
91 // Later we get another snapshot containing (C,E), this snapshot will have a
92 // different hash (S2, because Hash(C,E) != Hash(A,B,C)) and will add the edges
93 // C->E, E->C [via S2] to the graph.
94 // At this point when we are asked to convert a timestamp from A to E, or
95 // viceversa, we use a simple BFS to figure out a conversion path that is:
96 // A->C [via S1] + C->E [via S2].
97 //
98 // Visually:
99 // Assume we make the following calls:
100 //  - AddSnapshot(A:10, B:100)
101 //  - AddSnapshot(A:20, C:2000)
102 //  - AddSnapshot(B:400, C:5000)
103 //  - AddSnapshot(A:30, B:300)
104 
105 // And assume Hash(A,B) = S1, H(A,C) = S2, H(B,C) = S3
106 // The vectors in the tracker will look as follows:
107 // Clock A:
108 //   S1        {t:10, id:1}                                      {t:30, id:4}
109 //   S2        |               {t:20, id:2}                      |
110 //             |               |                                 |
111 // Clock B:    |               |                                 |
112 //   S1        {t:100, id:1}   |                                 {t:300, id:4}
113 //   S3                        |                  {t:400, id:3}
114 //                             |                  |
115 // Clock C:                    |                  |
116 //   S2                        {t: 2000, id: 2}   |
117 //   S3                                           {t:5000, id:3}
118 
119 class ClockTracker {
120  public:
121   using ClockId = int64_t;
122 
123   explicit ClockTracker(TraceProcessorContext*);
124   virtual ~ClockTracker();
125 
126   // Clock description.
127   struct Clock {
ClockClock128     explicit Clock(ClockId clock_id) : id(clock_id) {}
ClockClock129     Clock(ClockId clock_id, int64_t unit, bool incremental)
130         : id(clock_id), unit_multiplier_ns(unit), is_incremental(incremental) {}
131 
132     ClockId id;
133     int64_t unit_multiplier_ns = 1;
134     bool is_incremental = false;
135   };
136 
137   // Timestamp with clock.
138   struct ClockTimestamp {
ClockTimestampClockTimestamp139     ClockTimestamp(ClockId id, int64_t ts) : clock(id), timestamp(ts) {}
ClockTimestampClockTimestamp140     ClockTimestamp(ClockId id, int64_t ts, int64_t unit, bool incremental)
141         : clock(id, unit, incremental), timestamp(ts) {}
142 
143     Clock clock;
144     int64_t timestamp;
145   };
146 
147   // IDs in the range [64, 128) are reserved for sequence-scoped clock ids.
148   // They can't be passed directly in ClockTracker calls and must be resolved
149   // to 64-bit global clock ids by calling SeqScopedClockIdToGlobal().
IsSequenceClock(ClockId clock_id)150   static bool IsSequenceClock(ClockId clock_id) {
151     return clock_id >= 64 && clock_id < 128;
152   }
153 
154   // Converts a sequence-scoped clock ids to a global clock id that can be
155   // passed as argument to ClockTracker functions.
SequenceToGlobalClock(uint32_t seq_id,uint32_t clock_id)156   static ClockId SequenceToGlobalClock(uint32_t seq_id, uint32_t clock_id) {
157     PERFETTO_DCHECK(IsSequenceClock(clock_id));
158     return (static_cast<int64_t>(seq_id) << 32) | clock_id;
159   }
160 
set_timezone_offset(int64_t offset)161   void set_timezone_offset(int64_t offset) { timezone_offset_ = offset; }
162 
timezone_offset()163   std::optional<int64_t> timezone_offset() const { return timezone_offset_; }
164 
165   // Appends a new snapshot for the given clock domains.
166   // This is typically called by the code that reads the ClockSnapshot packet.
167   // Returns the internal snapshot id of this set of clocks.
168   base::StatusOr<uint32_t> AddSnapshot(const std::vector<ClockTimestamp>&);
169 
170   // Sets clock offset for the given clock domain to convert to the host trace
171   // time. This is typically called by the code that reads the RemoteClockSync
172   // packet. Typically only the offset of |trace_time_clock_id_| (which is
173   // CLOCK_BOOTTIME) is used.
SetClockOffset(ClockId clock_id,int64_t offset)174   void SetClockOffset(ClockId clock_id, int64_t offset) {
175     clock_offsets_[clock_id] = offset;
176   }
177 
178   // Apply the clock offset to convert remote trace times to host trace time.
ToHostTraceTime(int64_t timestamp)179   PERFETTO_ALWAYS_INLINE int64_t ToHostTraceTime(int64_t timestamp) {
180     if (PERFETTO_LIKELY(!context_->machine_id())) {
181       // No need to convert host timestamps.
182       return timestamp;
183     }
184 
185     // Find the offset for |trace_time_clock_id_| and apply the offset, or
186     // default offset 0 if not offset is found for |trace_time_clock_id_|.
187     int64_t clock_offset = clock_offsets_[trace_time_clock_id_];
188     return timestamp - clock_offset;
189   }
190 
ToTraceTime(ClockId clock_id,int64_t timestamp)191   PERFETTO_ALWAYS_INLINE base::StatusOr<int64_t> ToTraceTime(
192       ClockId clock_id,
193       int64_t timestamp) {
194     if (PERFETTO_UNLIKELY(!trace_time_clock_id_used_for_conversion_)) {
195       context_->metadata_tracker->SetMetadata(
196           metadata::trace_time_clock_id,
197           Variadic::Integer(trace_time_clock_id_));
198       trace_time_clock_id_used_for_conversion_ = true;
199     }
200     trace_time_clock_id_used_for_conversion_ = true;
201 
202     if (clock_id == trace_time_clock_id_) {
203       return ToHostTraceTime(timestamp);
204     }
205 
206     ASSIGN_OR_RETURN(int64_t ts,
207                      Convert(clock_id, timestamp, trace_time_clock_id_));
208     return ToHostTraceTime(ts);
209   }
210 
211   // If trace clock and source clock are available in the snapshot will return
212   // the trace clock time in snapshot.
213   std::optional<int64_t> ToTraceTimeFromSnapshot(
214       const std::vector<ClockTimestamp>&);
215 
SetTraceTimeClock(ClockId clock_id)216   void SetTraceTimeClock(ClockId clock_id) {
217     PERFETTO_DCHECK(!IsSequenceClock(clock_id));
218     if (trace_time_clock_id_used_for_conversion_ &&
219         trace_time_clock_id_ != clock_id) {
220       PERFETTO_ELOG("Not updating trace time clock from %" PRIu64 " to %" PRIu64
221                     " because the old clock was already used for timestamp "
222                     "conversion - ClockSnapshot too late in trace?",
223                     trace_time_clock_id_, clock_id);
224       return;
225     }
226     trace_time_clock_id_ = clock_id;
227     context_->metadata_tracker->SetMetadata(
228         metadata::trace_time_clock_id, Variadic::Integer(trace_time_clock_id_));
229   }
230 
set_cache_lookups_disabled_for_testing(bool v)231   void set_cache_lookups_disabled_for_testing(bool v) {
232     cache_lookups_disabled_for_testing_ = v;
233   }
234 
clock_offsets_for_testing()235   const base::FlatHashMap<ClockId, int64_t>& clock_offsets_for_testing() {
236     return clock_offsets_;
237   }
238 
239  private:
240   using SnapshotHash = uint32_t;
241 
242   // 0th argument is the source clock, 1st argument is the target clock.
243   using ClockGraphEdge = std::tuple<ClockId, ClockId, SnapshotHash>;
244 
245   // TODO(b/273263113): Remove in the next stages.
246   friend class ClockTrackerTest;
247 
248   // A value-type object that carries the information about the path between
249   // two clock domains. It's used by the BFS algorithm.
250   struct ClockPath {
251     static constexpr size_t kMaxLen = 4;
252     ClockPath() = default;
253     ClockPath(const ClockPath&) = default;
254 
255     // Constructs an invalid path with just a source node.
ClockPathClockPath256     explicit ClockPath(ClockId clock_id) : last(clock_id) {}
257 
258     // Constructs a path by appending a node to |prefix|.
259     // If |prefix| = [A,B] and clock_id = C, then |this| = [A,B,C].
ClockPathClockPath260     ClockPath(const ClockPath& prefix, ClockId clock_id, SnapshotHash hash) {
261       PERFETTO_DCHECK(prefix.len < kMaxLen);
262       len = prefix.len + 1;
263       path = prefix.path;
264       path[prefix.len] = ClockGraphEdge{prefix.last, clock_id, hash};
265       last = clock_id;
266     }
267 
validClockPath268     bool valid() const { return len > 0; }
atClockPath269     const ClockGraphEdge& at(uint32_t i) const {
270       PERFETTO_DCHECK(i < len);
271       return path[i];
272     }
273 
274     uint32_t len = 0;
275     ClockId last = 0;
276     std::array<ClockGraphEdge, kMaxLen> path;  // Deliberately uninitialized.
277   };
278 
279   struct ClockSnapshots {
280     // Invariant: both vectors have the same length.
281     std::vector<uint32_t> snapshot_ids;
282     std::vector<int64_t> timestamps_ns;
283   };
284 
285   struct ClockDomain {
286     // One time-series for each hash.
287     std::map<SnapshotHash, ClockSnapshots> snapshots;
288 
289     // Multiplier for timestamps given in this domain.
290     int64_t unit_multiplier_ns = 1;
291 
292     // Whether this clock domain encodes timestamps as deltas. This is only
293     // supported on sequence-local domains.
294     bool is_incremental = false;
295 
296     // If |is_incremental| is true, this stores the most recent absolute
297     // timestamp in nanoseconds.
298     int64_t last_timestamp_ns = 0;
299 
300     // Treats |timestamp| as delta timestamp if the clock uses incremental
301     // encoding, and as absolute timestamp otherwise.
ToNsClockDomain302     int64_t ToNs(int64_t timestamp) {
303       if (!is_incremental)
304         return timestamp * unit_multiplier_ns;
305 
306       int64_t delta_ns = timestamp * unit_multiplier_ns;
307       last_timestamp_ns += delta_ns;
308       return last_timestamp_ns;
309     }
310 
GetSnapshotClockDomain311     const ClockSnapshots& GetSnapshot(uint32_t hash) const {
312       auto it = snapshots.find(hash);
313       PERFETTO_DCHECK(it != snapshots.end());
314       return it->second;
315     }
316   };
317 
318   // Holds data for cached entries. At the moment only single-path resolution
319   // are cached.
320   struct CachedClockPath {
321     ClockId src;
322     ClockId target;
323     ClockDomain* src_domain;
324     int64_t min_ts_ns;
325     int64_t max_ts_ns;
326     int64_t translation_ns;
327   };
328 
329   ClockTracker(const ClockTracker&) = delete;
330   ClockTracker& operator=(const ClockTracker&) = delete;
331 
332   base::StatusOr<int64_t> ConvertSlowpath(ClockId src_clock_id,
333                                           int64_t src_timestamp,
334                                           ClockId target_clock_id);
335 
336   // Converts a timestamp between two clock domains. Tries to use the cache
337   // first (only for single-path resolutions), then falls back on path finding
338   // as described in the header.
Convert(ClockId src_clock_id,int64_t src_timestamp,ClockId target_clock_id)339   base::StatusOr<int64_t> Convert(ClockId src_clock_id,
340                                   int64_t src_timestamp,
341                                   ClockId target_clock_id) {
342     if (PERFETTO_LIKELY(!cache_lookups_disabled_for_testing_)) {
343       for (const auto& cached_clock_path : cache_) {
344         if (cached_clock_path.src != src_clock_id ||
345             cached_clock_path.target != target_clock_id)
346           continue;
347         int64_t ns = cached_clock_path.src_domain->ToNs(src_timestamp);
348         if (ns >= cached_clock_path.min_ts_ns &&
349             ns < cached_clock_path.max_ts_ns)
350           return ns + cached_clock_path.translation_ns;
351       }
352     }
353     return ConvertSlowpath(src_clock_id, src_timestamp, target_clock_id);
354   }
355 
356   // Returns whether |global_clock_id| represents a sequence-scoped clock, i.e.
357   // a ClockId returned by SeqScopedClockIdToGlobal().
IsConvertedSequenceClock(ClockId global_clock_id)358   static bool IsConvertedSequenceClock(ClockId global_clock_id) {
359     // If the id is > 2**32, this is a sequence-scoped clock id translated into
360     // the global namespace.
361     return (global_clock_id >> 32) > 0;
362   }
363 
364   ClockPath FindPath(ClockId src, ClockId target);
365 
GetClock(ClockId clock_id)366   ClockDomain* GetClock(ClockId clock_id) {
367     auto it = clocks_.find(clock_id);
368     PERFETTO_DCHECK(it != clocks_.end());
369     return &it->second;
370   }
371 
372   TraceProcessorContext* const context_;
373   ClockId trace_time_clock_id_ = 0;
374   std::map<ClockId, ClockDomain> clocks_;
375   std::set<ClockGraphEdge> graph_;
376   std::set<ClockId> non_monotonic_clocks_;
377   std::array<CachedClockPath, 2> cache_{};
378   bool cache_lookups_disabled_for_testing_ = false;
379   std::minstd_rand rnd_;  // For cache eviction.
380   uint32_t cur_snapshot_id_ = 0;
381   bool trace_time_clock_id_used_for_conversion_ = false;
382   base::FlatHashMap<ClockId, int64_t> clock_offsets_;
383   std::optional<int64_t> timezone_offset_;
384 };
385 
386 }  // namespace trace_processor
387 }  // namespace perfetto
388 
389 #endif  // SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_CLOCK_TRACKER_H_
390