• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 <map>
25 #include <optional>
26 #include <random>
27 #include <set>
28 #include <vector>
29 
30 #include "perfetto/base/logging.h"
31 #include "perfetto/ext/base/status_or.h"
32 #include "perfetto/ext/base/string_utils.h"
33 #include "src/trace_processor/importers/common/metadata_tracker.h"
34 #include "src/trace_processor/storage/trace_storage.h"
35 #include "src/trace_processor/types/trace_processor_context.h"
36 
37 #include "protos/perfetto/common/builtin_clock.pbzero.h"
38 #include "protos/perfetto/trace/clock_snapshot.pbzero.h"
39 
40 namespace perfetto {
41 namespace trace_processor {
42 
43 class ClockTrackerTest;
44 class TraceProcessorContext;
45 
46 // This class handles synchronization of timestamps across different clock
47 // domains. This includes multi-hop conversions from two clocks A and D, e.g.
48 // A->B -> B->C -> C->D, even if we never saw a snapshot that contains A and D
49 // at the same time.
50 // The API is fairly simple (but the inner operation is not):
51 // - AddSnapshot(map<clock_id, timestamp>): pushes a set of clocks that have
52 //   been snapshotted at the same time (within technical limits).
53 // - ToTraceTime(src_clock_id, src_timestamp):
54 //   converts a timestamp between clock domain and TraceTime.
55 //
56 // Concepts:
57 // - Snapshot hash:
58 //   As new snapshots are pushed via AddSnapshot() we compute a snapshot hash.
59 //   Such hash is the hash(clock_ids) (only IDs, not their timestamps) and is
60 //   used to find other snapshots that involve the same clock domains.
61 //   Two clock snapshots have the same hash iff they snapshot the same set of
62 //   clocks (the order of clocks is irrelevant).
63 //   This hash is used to efficiently go from the clock graph pathfinder to the
64 //   time-series obtained by appending the various snapshots.
65 // - Snapshot id:
66 //   A simple monotonic counter that is incremented on each AddSnapshot() call.
67 //
68 // Data structures:
69 //  - For each clock domain:
70 //    - For each snapshot hash:
71 //      - A logic vector of (snapshot_id, timestamp) tuples (physically stored
72 //        as two vectors of the same length instead of a vector of pairs).
73 // This allows to efficiently binary search timestamps within a clock domain
74 // that were obtained through a particular snapshot.
75 //
76 // - A graph of edges (source_clock, target_clock) -> snapshot hash.
77 //
78 // Operation:
79 // Upon each AddSnapshot() call, we incrementally build an unweighted, directed
80 // graph, which has clock domains as nodes.
81 // The graph is timestamp-oblivious. As long as we see one snapshot that
82 // connects two clocks, we assume we'll always be able to convert between them.
83 // This graph is queried by the Convert() function to figure out the shortest
84 // path between clock domain, possibly involving hopping through snapshots of
85 // different type (i.e. different hash).
86 //
87 // Example:
88 
89 // We see a snapshot, with hash S1, for clocks (A,B,C). We build the edges in
90 // the graph: A->B, B->C, A->C (and the symmetrical ones). In other words we
91 // keep track of the fact that we can convert between any of them using S1.
92 // Later we get another snapshot containing (C,E), this snapshot will have a
93 // different hash (S2, because Hash(C,E) != Hash(A,B,C)) and will add the edges
94 // C->E, E->C [via S2] to the graph.
95 // At this point when we are asked to convert a timestamp from A to E, or
96 // viceversa, we use a simple BFS to figure out a conversion path that is:
97 // A->C [via S1] + C->E [via S2].
98 //
99 // Visually:
100 // Assume we make the following calls:
101 //  - AddSnapshot(A:10, B:100)
102 //  - AddSnapshot(A:20, C:2000)
103 //  - AddSnapshot(B:400, C:5000)
104 //  - AddSnapshot(A:30, B:300)
105 
106 // And assume Hash(A,B) = S1, H(A,C) = S2, H(B,C) = S3
107 // The vectors in the tracker will look as follows:
108 // Clock A:
109 //   S1        {t:10, id:1}                                      {t:30, id:4}
110 //   S2        |               {t:20, id:2}                      |
111 //             |               |                                 |
112 // Clock B:    |               |                                 |
113 //   S1        {t:100, id:1}   |                                 {t:300, id:4}
114 //   S3                        |                  {t:400, id:3}
115 //                             |                  |
116 // Clock C:                    |                  |
117 //   S2                        {t: 2000, id: 2}   |
118 //   S3                                           {t:5000, id:3}
119 
120 class ClockTracker {
121  public:
122   using ClockId = int64_t;
123 
124   explicit ClockTracker(TraceProcessorContext*);
125   virtual ~ClockTracker();
126 
127   // Clock description.
128   struct Clock {
ClockClock129     explicit Clock(ClockId clock_id) : id(clock_id) {}
ClockClock130     Clock(ClockId clock_id, int64_t unit, bool incremental)
131         : id(clock_id), unit_multiplier_ns(unit), is_incremental(incremental) {}
132 
133     ClockId id;
134     int64_t unit_multiplier_ns = 1;
135     bool is_incremental = false;
136   };
137 
138   // Timestamp with clock.
139   struct ClockTimestamp {
ClockTimestampClockTimestamp140     ClockTimestamp(ClockId id, int64_t ts) : clock(id), timestamp(ts) {}
ClockTimestampClockTimestamp141     ClockTimestamp(ClockId id, int64_t ts, int64_t unit, bool incremental)
142         : clock(id, unit, incremental), timestamp(ts) {}
143 
144     Clock clock;
145     int64_t timestamp;
146   };
147 
148   // IDs in the range [64, 128) are reserved for sequence-scoped clock ids.
149   // They can't be passed directly in ClockTracker calls and must be resolved
150   // to 64-bit global clock ids by calling SeqScopedClockIdToGlobal().
IsSequenceClock(ClockId clock_id)151   static bool IsSequenceClock(ClockId clock_id) {
152     return clock_id >= 64 && clock_id < 128;
153   }
154 
155   // Converts a sequence-scoped clock ids to a global clock id that can be
156   // passed as argument to ClockTracker functions.
SeqenceToGlobalClock(uint32_t seq_id,uint32_t clock_id)157   static ClockId SeqenceToGlobalClock(uint32_t seq_id, uint32_t clock_id) {
158     PERFETTO_DCHECK(IsSequenceClock(clock_id));
159     return (static_cast<int64_t>(seq_id) << 32) | clock_id;
160   }
161 
162   // Appends a new snapshot for the given clock domains.
163   // This is typically called by the code that reads the ClockSnapshot packet.
164   // Returns the internal snapshot id of this set of clocks.
165   base::StatusOr<uint32_t> AddSnapshot(const std::vector<ClockTimestamp>&);
166 
ToTraceTime(ClockId clock_id,int64_t timestamp)167   base::StatusOr<int64_t> ToTraceTime(ClockId clock_id, int64_t timestamp) {
168     if (PERFETTO_UNLIKELY(!trace_time_clock_id_used_for_conversion_)) {
169       context_->metadata_tracker->SetMetadata(
170           metadata::trace_time_clock_id,
171           Variadic::Integer(trace_time_clock_id_));
172       trace_time_clock_id_used_for_conversion_ = true;
173     }
174     trace_time_clock_id_used_for_conversion_ = true;
175     if (clock_id == trace_time_clock_id_)
176       return timestamp;
177     return Convert(clock_id, timestamp, trace_time_clock_id_);
178   }
179 
180   // If trace clock and source clock are available in the snapshot will return
181   // the trace clock time in snapshot.
182   std::optional<int64_t> ToTraceTimeFromSnapshot(
183       const std::vector<ClockTimestamp>&);
184 
SetTraceTimeClock(ClockId clock_id)185   void SetTraceTimeClock(ClockId clock_id) {
186     PERFETTO_DCHECK(!IsSequenceClock(clock_id));
187     if (trace_time_clock_id_used_for_conversion_ &&
188         trace_time_clock_id_ != clock_id) {
189       PERFETTO_ELOG("Not updating trace time clock from %" PRIu64 " to %" PRIu64
190                     " because the old clock was already used for timestamp "
191                     "conversion - ClockSnapshot too late in trace?",
192                     trace_time_clock_id_, clock_id);
193       return;
194     }
195     trace_time_clock_id_ = clock_id;
196     context_->metadata_tracker->SetMetadata(
197         metadata::trace_time_clock_id, Variadic::Integer(trace_time_clock_id_));
198   }
199 
set_cache_lookups_disabled_for_testing(bool v)200   void set_cache_lookups_disabled_for_testing(bool v) {
201     cache_lookups_disabled_for_testing_ = v;
202   }
203 
204  private:
205   using SnapshotHash = uint32_t;
206 
207   // 0th argument is the source clock, 1st argument is the target clock.
208   using ClockGraphEdge = std::tuple<ClockId, ClockId, SnapshotHash>;
209 
210   // TODO(b/273263113): Remove in the next stages.
211   friend class ClockTrackerTest;
212 
213   // A value-type object that carries the information about the path between
214   // two clock domains. It's used by the BFS algorithm.
215   struct ClockPath {
216     static constexpr size_t kMaxLen = 4;
217     ClockPath() = default;
218     ClockPath(const ClockPath&) = default;
219 
220     // Constructs an invalid path with just a source node.
ClockPathClockPath221     explicit ClockPath(ClockId clock_id) : last(clock_id) {}
222 
223     // Constructs a path by appending a node to |prefix|.
224     // If |prefix| = [A,B] and clock_id = C, then |this| = [A,B,C].
ClockPathClockPath225     ClockPath(const ClockPath& prefix, ClockId clock_id, SnapshotHash hash) {
226       PERFETTO_DCHECK(prefix.len < kMaxLen);
227       len = prefix.len + 1;
228       path = prefix.path;
229       path[prefix.len] = ClockGraphEdge{prefix.last, clock_id, hash};
230       last = clock_id;
231     }
232 
validClockPath233     bool valid() const { return len > 0; }
atClockPath234     const ClockGraphEdge& at(uint32_t i) const {
235       PERFETTO_DCHECK(i < len);
236       return path[i];
237     }
238 
239     uint32_t len = 0;
240     ClockId last = 0;
241     std::array<ClockGraphEdge, kMaxLen> path;  // Deliberately uninitialized.
242   };
243 
244   struct ClockSnapshots {
245     // Invariant: both vectors have the same length.
246     std::vector<uint32_t> snapshot_ids;
247     std::vector<int64_t> timestamps_ns;
248   };
249 
250   struct ClockDomain {
251     // One time-series for each hash.
252     std::map<SnapshotHash, ClockSnapshots> snapshots;
253 
254     // Multiplier for timestamps given in this domain.
255     int64_t unit_multiplier_ns = 1;
256 
257     // Whether this clock domain encodes timestamps as deltas. This is only
258     // supported on sequence-local domains.
259     bool is_incremental = false;
260 
261     // If |is_incremental| is true, this stores the most recent absolute
262     // timestamp in nanoseconds.
263     int64_t last_timestamp_ns = 0;
264 
265     // Treats |timestamp| as delta timestamp if the clock uses incremental
266     // encoding, and as absolute timestamp otherwise.
ToNsClockDomain267     int64_t ToNs(int64_t timestamp) {
268       if (!is_incremental)
269         return timestamp * unit_multiplier_ns;
270 
271       int64_t delta_ns = timestamp * unit_multiplier_ns;
272       last_timestamp_ns += delta_ns;
273       return last_timestamp_ns;
274     }
275 
GetSnapshotClockDomain276     const ClockSnapshots& GetSnapshot(uint32_t hash) const {
277       auto it = snapshots.find(hash);
278       PERFETTO_DCHECK(it != snapshots.end());
279       return it->second;
280     }
281   };
282 
283   // Holds data for cached entries. At the moment only single-path resolution
284   // are cached.
285   struct CachedClockPath {
286     ClockId src;
287     ClockId target;
288     ClockDomain* src_domain;
289     int64_t min_ts_ns;
290     int64_t max_ts_ns;
291     int64_t translation_ns;
292   };
293 
294   ClockTracker(const ClockTracker&) = delete;
295   ClockTracker& operator=(const ClockTracker&) = delete;
296 
297   base::StatusOr<int64_t> ConvertSlowpath(ClockId src_clock_id,
298                                           int64_t src_timestamp,
299                                           ClockId target_clock_id);
300 
301   // Converts a timestamp between two clock domains. Tries to use the cache
302   // first (only for single-path resolutions), then falls back on path finding
303   // as described in the header.
Convert(ClockId src_clock_id,int64_t src_timestamp,ClockId target_clock_id)304   base::StatusOr<int64_t> Convert(ClockId src_clock_id,
305                                   int64_t src_timestamp,
306                                   ClockId target_clock_id) {
307     if (PERFETTO_LIKELY(!cache_lookups_disabled_for_testing_)) {
308       for (const auto& cached_clock_path : cache_) {
309         if (cached_clock_path.src != src_clock_id ||
310             cached_clock_path.target != target_clock_id)
311           continue;
312         int64_t ns = cached_clock_path.src_domain->ToNs(src_timestamp);
313         if (ns >= cached_clock_path.min_ts_ns &&
314             ns < cached_clock_path.max_ts_ns)
315           return ns + cached_clock_path.translation_ns;
316       }
317     }
318     return ConvertSlowpath(src_clock_id, src_timestamp, target_clock_id);
319   }
320 
321   // Returns whether |global_clock_id| represents a sequence-scoped clock, i.e.
322   // a ClockId returned by SeqScopedClockIdToGlobal().
IsConvertedSequenceClock(ClockId global_clock_id)323   static bool IsConvertedSequenceClock(ClockId global_clock_id) {
324     // If the id is > 2**32, this is a sequence-scoped clock id translated into
325     // the global namespace.
326     return (global_clock_id >> 32) > 0;
327   }
328 
329   ClockPath FindPath(ClockId src, ClockId target);
330 
GetClock(ClockId clock_id)331   ClockDomain* GetClock(ClockId clock_id) {
332     auto it = clocks_.find(clock_id);
333     PERFETTO_DCHECK(it != clocks_.end());
334     return &it->second;
335   }
336 
337   TraceProcessorContext* const context_;
338   ClockId trace_time_clock_id_ = 0;
339   std::map<ClockId, ClockDomain> clocks_;
340   std::set<ClockGraphEdge> graph_;
341   std::set<ClockId> non_monotonic_clocks_;
342   std::array<CachedClockPath, 2> cache_{};
343   bool cache_lookups_disabled_for_testing_ = false;
344   std::minstd_rand rnd_;  // For cache eviction.
345   uint32_t cur_snapshot_id_ = 0;
346   bool trace_time_clock_id_used_for_conversion_ = false;
347 };
348 
349 }  // namespace trace_processor
350 }  // namespace perfetto
351 
352 #endif  // SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_CLOCK_TRACKER_H_
353