xref: /aosp_15_r20/external/perfetto/src/trace_processor/importers/common/clock_tracker.cc (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 #include "src/trace_processor/importers/common/clock_tracker.h"
18 
19 #include <time.h>
20 
21 #include <algorithm>
22 #include <atomic>
23 #include <cinttypes>
24 #include <queue>
25 
26 #include "perfetto/base/logging.h"
27 #include "perfetto/ext/base/hash.h"
28 #include "src/trace_processor/storage/trace_storage.h"
29 #include "src/trace_processor/types/trace_processor_context.h"
30 
31 #include "protos/perfetto/common/builtin_clock.pbzero.h"
32 #include "protos/perfetto/trace/clock_snapshot.pbzero.h"
33 
34 namespace perfetto {
35 namespace trace_processor {
36 
37 using Clock = protos::pbzero::ClockSnapshot::Clock;
38 
ClockTracker(TraceProcessorContext * context)39 ClockTracker::ClockTracker(TraceProcessorContext* context)
40     : context_(context),
41       trace_time_clock_id_(protos::pbzero::BUILTIN_CLOCK_BOOTTIME) {}
42 
43 ClockTracker::~ClockTracker() = default;
44 
AddSnapshot(const std::vector<ClockTimestamp> & clock_timestamps)45 base::StatusOr<uint32_t> ClockTracker::AddSnapshot(
46     const std::vector<ClockTimestamp>& clock_timestamps) {
47   const auto snapshot_id = cur_snapshot_id_++;
48 
49   // Clear the cache
50   cache_.fill({});
51 
52   // Compute the fingerprint of the snapshot by hashing all clock ids. This is
53   // used by the clock pathfinding logic.
54   base::Hasher hasher;
55   for (const auto& clock_ts : clock_timestamps)
56     hasher.Update(clock_ts.clock.id);
57   const auto snapshot_hash = static_cast<SnapshotHash>(hasher.digest());
58 
59   // Add a new entry in each clock's snapshot vector.
60   for (const auto& clock_ts : clock_timestamps) {
61     ClockId clock_id = clock_ts.clock.id;
62     ClockDomain& domain = clocks_[clock_id];
63 
64     if (domain.snapshots.empty()) {
65       if (clock_ts.clock.is_incremental &&
66           !IsConvertedSequenceClock(clock_id)) {
67         context_->storage->IncrementStats(stats::invalid_clock_snapshots);
68         return base::ErrStatus(
69             "Clock sync error: the global clock with id=%" PRId64
70             " cannot use incremental encoding; this is only "
71             "supported for sequence-scoped clocks.",
72             clock_id);
73       }
74       domain.unit_multiplier_ns = clock_ts.clock.unit_multiplier_ns;
75       domain.is_incremental = clock_ts.clock.is_incremental;
76     } else if (PERFETTO_UNLIKELY(domain.unit_multiplier_ns !=
77                                      clock_ts.clock.unit_multiplier_ns ||
78                                  domain.is_incremental !=
79                                      clock_ts.clock.is_incremental)) {
80       context_->storage->IncrementStats(stats::invalid_clock_snapshots);
81       return base::ErrStatus(
82           "Clock sync error: the clock domain with id=%" PRId64
83           " (unit=%" PRId64
84           ", incremental=%d), was previously registered with "
85           "different properties (unit=%" PRId64 ", incremental=%d).",
86           clock_id, clock_ts.clock.unit_multiplier_ns,
87           clock_ts.clock.is_incremental, domain.unit_multiplier_ns,
88           domain.is_incremental);
89     }
90     const int64_t timestamp_ns = clock_ts.timestamp * domain.unit_multiplier_ns;
91     domain.last_timestamp_ns = timestamp_ns;
92 
93     ClockSnapshots& vect = domain.snapshots[snapshot_hash];
94     if (!vect.snapshot_ids.empty() &&
95         PERFETTO_UNLIKELY(vect.snapshot_ids.back() == snapshot_id)) {
96       context_->storage->IncrementStats(stats::invalid_clock_snapshots);
97       return base::ErrStatus(
98           "Clock sync error: duplicate clock domain with id=%" PRId64
99           " at snapshot %" PRIu32 ".",
100           clock_id, snapshot_id);
101     }
102 
103     // Clock ids in the range [64, 128) are sequence-scoped and must be
104     // translated to global ids via SeqScopedClockIdToGlobal() before calling
105     // this function.
106     PERFETTO_DCHECK(!IsSequenceClock(clock_id));
107 
108     // Snapshot IDs must be always monotonic.
109     PERFETTO_DCHECK(vect.snapshot_ids.empty() ||
110                     vect.snapshot_ids.back() < snapshot_id);
111 
112     if (!vect.timestamps_ns.empty() &&
113         timestamp_ns < vect.timestamps_ns.back()) {
114       // Clock is not monotonic.
115 
116       if (clock_id == trace_time_clock_id_) {
117         context_->storage->IncrementStats(stats::invalid_clock_snapshots);
118         // The trace clock cannot be non-monotonic.
119         return base::ErrStatus("Clock sync error: the trace clock (id=%" PRId64
120                                ") is not monotonic at snapshot %" PRIu32
121                                ". %" PRId64 " not >= %" PRId64 ".",
122                                clock_id, snapshot_id, timestamp_ns,
123                                vect.timestamps_ns.back());
124       }
125 
126       PERFETTO_DLOG("Detected non-monotonic clock with ID %" PRId64, clock_id);
127 
128       // For the other clocks the best thing we can do is mark it as
129       // non-monotonic and refuse to use it as a source clock in the resolution
130       // graph. We can still use it as a target clock, but not viceversa.
131       // The concrete example is the CLOCK_REALTIME going 1h backwards during
132       // daylight saving. We can still answer the question "what was the
133       // REALTIME timestamp when BOOTTIME was X?" but we can't answer the
134       // opposite question because there can be two valid BOOTTIME(s) for the
135       // same REALTIME instant because of the 1:many relationship.
136       non_monotonic_clocks_.insert(clock_id);
137 
138       // Erase all edges from the graph that start from this clock (but keep the
139       // ones that end on this clock).
140       auto begin = graph_.lower_bound(ClockGraphEdge{clock_id, 0, 0});
141       auto end = graph_.lower_bound(ClockGraphEdge{clock_id + 1, 0, 0});
142       graph_.erase(begin, end);
143     }
144     vect.snapshot_ids.emplace_back(snapshot_id);
145     vect.timestamps_ns.emplace_back(timestamp_ns);
146   }
147 
148   // Create graph edges for all the possible tuples of clocks in this snapshot.
149   // If the snapshot contains clock a, b, c, d create edges [ab, ac, ad, bc, bd,
150   // cd] and the symmetrical ones [ba, ca, da, bc, db, dc].
151   // This is to store the information: Clock A is syncable to Clock B via the
152   // snapshots of type (hash).
153   // Clocks that were previously marked as non-monotonic won't be added as
154   // valid sources.
155   for (auto it1 = clock_timestamps.begin(); it1 != clock_timestamps.end();
156        ++it1) {
157     auto it2 = it1;
158     ++it2;
159     for (; it2 != clock_timestamps.end(); ++it2) {
160       if (!non_monotonic_clocks_.count(it1->clock.id))
161         graph_.emplace(it1->clock.id, it2->clock.id, snapshot_hash);
162 
163       if (!non_monotonic_clocks_.count(it2->clock.id))
164         graph_.emplace(it2->clock.id, it1->clock.id, snapshot_hash);
165     }
166   }
167 
168   return snapshot_id;
169 }
170 
171 // Finds the shortest clock resolution path in the graph that allows to
172 // translate a timestamp from |src| to |target| clocks.
173 // The return value looks like the following: "If you want to convert a
174 // timestamp from clock C1 to C2 you need to first convert C1 -> C3 using the
175 // snapshot hash A, then convert C3 -> C2 via snapshot hash B".
FindPath(ClockId src,ClockId target)176 ClockTracker::ClockPath ClockTracker::FindPath(ClockId src, ClockId target) {
177   PERFETTO_CHECK(src != target);
178 
179   // If we've never heard of the clock before there is no hope:
180   if (clocks_.find(target) == clocks_.end()) {
181     return ClockPath();
182   }
183   if (clocks_.find(src) == clocks_.end()) {
184     return ClockPath();
185   }
186 
187   // This is a classic breadth-first search. Each node in the queue holds also
188   // the full path to reach that node.
189   // We assume the graph is acyclic, if it isn't the ClockPath::kMaxLen will
190   // stop the search anyways.
191   std::queue<ClockPath> queue;
192   queue.emplace(src);
193 
194   while (!queue.empty()) {
195     ClockPath cur_path = queue.front();
196     queue.pop();
197 
198     const ClockId cur_clock_id = cur_path.last;
199     if (cur_clock_id == target)
200       return cur_path;
201 
202     if (cur_path.len >= ClockPath::kMaxLen)
203       continue;
204 
205     // Expore all the adjacent clocks.
206     // The lower_bound() below returns an iterator to the first edge that starts
207     // on |cur_clock_id|. The edges are sorted by (src, target, hash).
208     for (auto it = graph_.lower_bound(ClockGraphEdge(cur_clock_id, 0, 0));
209          it != graph_.end() && std::get<0>(*it) == cur_clock_id; ++it) {
210       ClockId next_clock_id = std::get<1>(*it);
211       SnapshotHash hash = std::get<2>(*it);
212       queue.push(ClockPath(cur_path, next_clock_id, hash));
213     }
214   }
215   return ClockPath();  // invalid path.
216 }
217 
ToTraceTimeFromSnapshot(const std::vector<ClockTimestamp> & snapshot)218 std::optional<int64_t> ClockTracker::ToTraceTimeFromSnapshot(
219     const std::vector<ClockTimestamp>& snapshot) {
220   auto maybe_found_trace_time_clock = std::find_if(
221       snapshot.begin(), snapshot.end(),
222       [this](const ClockTimestamp& clock_timestamp) {
223         return clock_timestamp.clock.id == this->trace_time_clock_id_;
224       });
225 
226   if (maybe_found_trace_time_clock == snapshot.end())
227     return std::nullopt;
228 
229   return maybe_found_trace_time_clock->timestamp;
230 }
231 
ConvertSlowpath(ClockId src_clock_id,int64_t src_timestamp,ClockId target_clock_id)232 base::StatusOr<int64_t> ClockTracker::ConvertSlowpath(ClockId src_clock_id,
233                                                       int64_t src_timestamp,
234                                                       ClockId target_clock_id) {
235   PERFETTO_DCHECK(!IsSequenceClock(src_clock_id));
236   PERFETTO_DCHECK(!IsSequenceClock(target_clock_id));
237 
238   context_->storage->IncrementStats(stats::clock_sync_cache_miss);
239 
240   ClockPath path = FindPath(src_clock_id, target_clock_id);
241 
242   if (!path.valid()) {
243     // Too many logs maybe emitted when path is invalid.
244     context_->storage->IncrementStats(stats::clock_sync_failure);
245     return base::ErrStatus("No path from clock %" PRId64 " to %" PRId64
246                            " at timestamp %" PRId64,
247                            src_clock_id, target_clock_id, src_timestamp);
248   }
249 
250   // We can cache only single-path resolutions between two clocks.
251   // Caching multi-path resolutions is harder because the (src,target) tuple
252   // is not enough as a cache key: at any step the |ns| value can yield to a
253   // different choice of the next snapshot. Multi-path resolutions don't seem
254   // too frequent these days, so we focus only on caching the more frequent
255   // one-step resolutions (typically from any clock to the trace clock).
256   const bool cacheable = path.len == 1;
257   CachedClockPath cache_entry{};
258 
259   // Iterate trough the path found and translate timestamps onto the new clock
260   // domain on each step, until the target domain is reached.
261   ClockDomain* src_domain = GetClock(src_clock_id);
262   int64_t ns = src_domain->ToNs(src_timestamp);
263   for (uint32_t i = 0; i < path.len; ++i) {
264     const ClockGraphEdge edge = path.at(i);
265     ClockDomain* cur_clock = GetClock(std::get<0>(edge));
266     ClockDomain* next_clock = GetClock(std::get<1>(edge));
267     const SnapshotHash hash = std::get<2>(edge);
268 
269     // Find the closest timestamp within the snapshots of the source clock.
270     const ClockSnapshots& cur_snap = cur_clock->GetSnapshot(hash);
271     const auto& ts_vec = cur_snap.timestamps_ns;
272     auto it = std::upper_bound(ts_vec.begin(), ts_vec.end(), ns);
273     if (it != ts_vec.begin())
274       --it;
275 
276     // Now lookup the snapshot id that matches the closest timestamp.
277     size_t index = static_cast<size_t>(std::distance(ts_vec.begin(), it));
278     PERFETTO_DCHECK(index < ts_vec.size());
279     PERFETTO_DCHECK(cur_snap.snapshot_ids.size() == ts_vec.size());
280     uint32_t snapshot_id = cur_snap.snapshot_ids[index];
281 
282     // And use that to retrieve the corresponding time in the next clock domain.
283     // The snapshot id must exist in the target clock domain. If it doesn't
284     // either the hash logic or the pathfinding logic are bugged.
285     // This can also happen if the checks in AddSnapshot fail and we skip part
286     // of the snapshot.
287     const ClockSnapshots& next_snap = next_clock->GetSnapshot(hash);
288 
289     // Using std::lower_bound because snapshot_ids is sorted, so we can do
290     // a binary search. std::find would do a linear scan.
291     auto next_it = std::lower_bound(next_snap.snapshot_ids.begin(),
292                                     next_snap.snapshot_ids.end(), snapshot_id);
293     if (next_it == next_snap.snapshot_ids.end() || *next_it != snapshot_id) {
294       PERFETTO_DFATAL("Snapshot does not exist in clock domain.");
295       continue;
296     }
297     size_t next_index = static_cast<size_t>(
298         std::distance(next_snap.snapshot_ids.begin(), next_it));
299     PERFETTO_DCHECK(next_index < next_snap.snapshot_ids.size());
300     int64_t next_timestamp_ns = next_snap.timestamps_ns[next_index];
301 
302     // The translated timestamp is the relative delta of the source timestamp
303     // from the closest snapshot found (ns - *it), plus the timestamp in
304     // the new clock domain for the same snapshot id.
305     const int64_t adj = next_timestamp_ns - *it;
306     ns += adj;
307 
308     // On the first iteration, keep track of the bounds for the cache entry.
309     // This will allow future Convert() calls to skip the pathfinder logic
310     // as long as the query stays within the bound.
311     if (cacheable) {
312       PERFETTO_DCHECK(i == 0);
313       const int64_t kInt64Min = std::numeric_limits<int64_t>::min();
314       const int64_t kInt64Max = std::numeric_limits<int64_t>::max();
315       cache_entry.min_ts_ns = it == ts_vec.begin() ? kInt64Min : *it;
316       auto ubound = it + 1;
317       cache_entry.max_ts_ns = ubound == ts_vec.end() ? kInt64Max : *ubound;
318       cache_entry.translation_ns = adj;
319     }
320 
321     // The last clock in the path must be the target clock.
322     PERFETTO_DCHECK(i < path.len - 1 || std::get<1>(edge) == target_clock_id);
323   }
324 
325   if (cacheable) {
326     cache_entry.src = src_clock_id;
327     cache_entry.src_domain = src_domain;
328     cache_entry.target = target_clock_id;
329     cache_[rnd_() % cache_.size()] = cache_entry;
330   }
331 
332   return ns;
333 }
334 
335 }  // namespace trace_processor
336 }  // namespace perfetto
337