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