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