blob: 87559e072a193b519df4ea698ca24482d71bf97d [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "src/trace_processor/clock_tracker.h"
#include <inttypes.h>
#include <algorithm>
#include <queue>
#include "perfetto/base/logging.h"
#include "perfetto/ext/base/hash.h"
#include "src/trace_processor/trace_processor_context.h"
#include "src/trace_processor/trace_storage.h"
#include "protos/perfetto/trace/clock_snapshot.pbzero.h"
namespace perfetto {
namespace trace_processor {
using Clock = protos::pbzero::ClockSnapshot::Clock;
ClockTracker::ClockTracker(TraceProcessorContext* ctx)
: context_(ctx), trace_time_clock_id_(Clock::BOOTTIME) {}
ClockTracker::~ClockTracker() = default;
void ClockTracker::AddSnapshot(const std::map<ClockId, int64_t>& clocks) {
const auto snapshot_id = cur_snapshot_id_++;
// Compute the fingerprint of the snapshot by hashing all clock ids. This is
// used by the clock pathfinding logic.
base::Hash hasher;
for (const auto& id_and_ts : clocks)
hasher.Update(id_and_ts.first);
const auto snapshot_hash = static_cast<SnapshotHash>(hasher.digest());
// Add a new entry in each clock's snapshot vector.
for (const auto& id_and_ts : clocks) {
ClockId clock_id = id_and_ts.first;
ClockDomain& clock = clocks_[clock_id];
const int64_t timestamp_ns = clock.ToNs(id_and_ts.second);
ClockSnapshots& vect = clock.snapshots[snapshot_hash];
// Clock ids in the range [64, 128) are sequence-scoped and must be
// translated to global ids via SeqScopedClockIdToGlobal() before calling
// this function.
PERFETTO_DCHECK(!IsReservedSeqScopedClockId(clock_id));
// Snapshot IDs must be always monotonic.
PERFETTO_DCHECK(vect.snapshot_ids.empty() ||
vect.snapshot_ids.back() < snapshot_id);
if (!vect.timestamps_ns.empty() &&
timestamp_ns <= vect.timestamps_ns.back()) {
// Clock is not monotonic.
if (clock_id == trace_time_clock_id_) {
// The trace clock cannot be non-monotonic.
PERFETTO_ELOG(
"Clock sync error: the trace clock (id=%" PRIu64
") is not "
"monotonic at snapshot %" PRIu32 ". %" PRId64 " not > %" PRId64 ".",
clock_id, snapshot_id, timestamp_ns, vect.timestamps_ns.back());
context_->storage->IncrementStats(stats::invalid_clock_snapshots);
return;
}
// For the other clocks the best thing we can do is mark it as
// non-monotonic and refuse to use it as a source clock in the resolution
// graph. We can still use it as a target clock, but not viceversa.
// The concrete example is the CLOCK_REALTIME going 1h backwards during
// daylight saving. We can still answer the question "what was the
// REALTIME timestamp when BOOTTIME was X?" but we can't answer the
// opposite question because there can be two valid BOOTTIME(s) for the
// same REALTIME instant because of the 1:many relationship.
non_monotonic_clocks_.insert(clock_id);
// Erase all edges from the graph that start from this clock (but keep the
// ones that end on this clock).
auto begin = graph_.lower_bound(ClockGraphEdge{clock_id, 0, 0});
auto end = graph_.lower_bound(ClockGraphEdge{clock_id + 1, 0, 0});
graph_.erase(begin, end);
}
vect.snapshot_ids.emplace_back(snapshot_id);
vect.timestamps_ns.emplace_back(timestamp_ns);
}
// Create graph edges for all the possible tuples of clocks in this snapshot.
// If the snapshot contains clock a, b, c, d create edges [ab, ac, ad, bc, bd,
// cd] and the symmetrical ones [ba, ca, da, bc, db, dc].
// This is to store the information: Clock A is syncable to Clock B via the
// snapshots of type (hash).
// Clocks that were previously marked as non-monotonic won't be added as
// valid sources.
for (auto it1 = clocks.begin(); it1 != clocks.end(); ++it1) {
auto it2 = it1;
++it2;
for (; it2 != clocks.end(); ++it2) {
if (!non_monotonic_clocks_.count(it1->first))
graph_.emplace(it1->first, it2->first, snapshot_hash);
if (!non_monotonic_clocks_.count(it2->first))
graph_.emplace(it2->first, it1->first, snapshot_hash);
}
}
}
// Finds the shortest clock resolution path in the graph that allows to
// translate a timestamp from |src| to |target| clocks.
// The return value looks like the following: "If you want to convert a
// timestamp from clock C1 to C2 you need to first convert C1 -> C3 using the
// snapshot hash A, then convert C3 -> C2 via snapshot hash B".
ClockTracker::ClockPath ClockTracker::FindPath(ClockId src, ClockId target) {
// This is a classic breadth-first search. Each node in the queue holds also
// the full path to reach that node.
// We assume the graph is acyclic, if it isn't the ClockPath::kMaxLen will
// stop the search anyways.
PERFETTO_CHECK(src != target);
std::queue<ClockPath> queue;
queue.emplace(src);
while (!queue.empty()) {
ClockPath cur_path = queue.front();
queue.pop();
const ClockId cur_clock_id = cur_path.last;
if (cur_clock_id == target)
return cur_path;
if (cur_path.len >= ClockPath::kMaxLen)
continue;
// Expore all the adjacent clocks.
// The lower_bound() below returns an iterator to the first edge that starts
// on |cur_clock_id|. The edges are sorted by (src, target, hash).
for (auto it = std::lower_bound(graph_.begin(), graph_.end(),
ClockGraphEdge(cur_clock_id, 0, 0));
it != graph_.end() && std::get<0>(*it) == cur_clock_id; ++it) {
ClockId next_clock_id = std::get<1>(*it);
SnapshotHash hash = std::get<2>(*it);
queue.push(ClockPath(cur_path, next_clock_id, hash));
}
}
return ClockPath(); // invalid path.
}
base::Optional<int64_t> ClockTracker::Convert(ClockId src_clock_id,
int64_t src_timestamp,
ClockId target_clock_id) {
// TODO(primiano): optimization: I bet A simple LRU cache of the form
// (src_clock_id, target_clock_id, latest_timestamp, translation_ns) might
// speed up most conversion allowing to skip FindPath and the iterations.
PERFETTO_DCHECK(!IsReservedSeqScopedClockId(src_clock_id));
PERFETTO_DCHECK(!IsReservedSeqScopedClockId(target_clock_id));
ClockPath path = FindPath(src_clock_id, target_clock_id);
if (!path.valid()) {
context_->storage->IncrementStats(stats::clock_sync_failure);
return base::nullopt;
}
// Iterate trough the path found and translate timestamps onto the new clock
// domain on each step, until the target domain is reached.
int64_t ns = GetClock(src_clock_id).ToNs(src_timestamp);
for (uint32_t i = 0; i < path.len; ++i) {
const ClockGraphEdge edge = path.at(i);
const ClockDomain& cur_clock = GetClock(std::get<0>(edge));
const ClockDomain& next_clock = GetClock(std::get<1>(edge));
const SnapshotHash hash = std::get<2>(edge);
// Find the closest timestamp within the snapshots of the source clock.
const ClockSnapshots& cur_snap = cur_clock.GetSnapshot(hash);
const auto& ts_vec = cur_snap.timestamps_ns;
auto it = std::upper_bound(ts_vec.begin(), ts_vec.end(), ns);
if (it != ts_vec.begin())
--it;
// Now lookup the snapshot id that matches the closest timestamp.
size_t index = static_cast<size_t>(std::distance(ts_vec.begin(), it));
PERFETTO_DCHECK(index < ts_vec.size());
PERFETTO_DCHECK(cur_snap.snapshot_ids.size() == ts_vec.size());
uint32_t snapshot_id = cur_snap.snapshot_ids[index];
// And use that to retrieve the corresponding time in the next clock domain.
// The snapshot id must exist in the target clock domain. If it doesn't
// either the hash logic or the pathfinding logic are bugged.
const ClockSnapshots& next_snap = next_clock.GetSnapshot(hash);
auto next_it = std::lower_bound(next_snap.snapshot_ids.begin(),
next_snap.snapshot_ids.end(), snapshot_id);
PERFETTO_DCHECK(next_it != next_snap.snapshot_ids.end() &&
*next_it == snapshot_id);
size_t next_index = static_cast<size_t>(
std::distance(next_snap.snapshot_ids.begin(), next_it));
PERFETTO_DCHECK(next_index < next_snap.snapshot_ids.size());
int64_t next_timestamp_ns = next_snap.timestamps_ns[next_index];
// The translated timestamp is the relative delta of the source timestamp
// from the closest snapshot found (ns - *it), plus the timestamp in
// the new clock domain for the same snapshot id.
ns = (ns - *it) + next_timestamp_ns;
// The last clock in the path must be the target clock.
PERFETTO_DCHECK(i < path.len - 1 || std::get<1>(edge) == target_clock_id);
}
return ns;
}
} // namespace trace_processor
} // namespace perfetto