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