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