/* * 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. */ #ifndef SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_CLOCK_TRACKER_H_ #define SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_CLOCK_TRACKER_H_ #include <stdint.h> #include <array> #include <cinttypes> #include <cstdint> #include <map> #include <optional> #include <random> #include <set> #include <vector> #include "perfetto/base/logging.h" #include "perfetto/ext/base/flat_hash_map.h" #include "perfetto/ext/base/status_or.h" #include "perfetto/public/compiler.h" #include "src/trace_processor/importers/common/metadata_tracker.h" #include "src/trace_processor/types/trace_processor_context.h" #include "src/trace_processor/util/status_macros.h" namespace perfetto { namespace trace_processor { class ClockTrackerTest; class TraceProcessorContext; // This class handles synchronization of timestamps across different clock // domains. This includes multi-hop conversions from two clocks A and D, e.g. // A->B -> B->C -> C->D, even if we never saw a snapshot that contains A and D // at the same time. // The API is fairly simple (but the inner operation is not): // - AddSnapshot(map<clock_id, timestamp>): pushes a set of clocks that have // been snapshotted at the same time (within technical limits). // - ToTraceTime(src_clock_id, src_timestamp): // converts a timestamp between clock domain and TraceTime. // // Concepts: // - Snapshot hash: // As new snapshots are pushed via AddSnapshot() we compute a snapshot hash. // Such hash is the hash(clock_ids) (only IDs, not their timestamps) and is // used to find other snapshots that involve the same clock domains. // Two clock snapshots have the same hash iff they snapshot the same set of // clocks (the order of clocks is irrelevant). // This hash is used to efficiently go from the clock graph pathfinder to the // time-series obtained by appending the various snapshots. // - Snapshot id: // A simple monotonic counter that is incremented on each AddSnapshot() call. // // Data structures: // - For each clock domain: // - For each snapshot hash: // - A logic vector of (snapshot_id, timestamp) tuples (physically stored // as two vectors of the same length instead of a vector of pairs). // This allows to efficiently binary search timestamps within a clock domain // that were obtained through a particular snapshot. // // - A graph of edges (source_clock, target_clock) -> snapshot hash. // // Operation: // Upon each AddSnapshot() call, we incrementally build an unweighted, directed // graph, which has clock domains as nodes. // The graph is timestamp-oblivious. As long as we see one snapshot that // connects two clocks, we assume we'll always be able to convert between them. // This graph is queried by the Convert() function to figure out the shortest // path between clock domain, possibly involving hopping through snapshots of // different type (i.e. different hash). // // Example: // We see a snapshot, with hash S1, for clocks (A,B,C). We build the edges in // the graph: A->B, B->C, A->C (and the symmetrical ones). In other words we // keep track of the fact that we can convert between any of them using S1. // Later we get another snapshot containing (C,E), this snapshot will have a // different hash (S2, because Hash(C,E) != Hash(A,B,C)) and will add the edges // C->E, E->C [via S2] to the graph. // At this point when we are asked to convert a timestamp from A to E, or // viceversa, we use a simple BFS to figure out a conversion path that is: // A->C [via S1] + C->E [via S2]. // // Visually: // Assume we make the following calls: // - AddSnapshot(A:10, B:100) // - AddSnapshot(A:20, C:2000) // - AddSnapshot(B:400, C:5000) // - AddSnapshot(A:30, B:300) // And assume Hash(A,B) = S1, H(A,C) = S2, H(B,C) = S3 // The vectors in the tracker will look as follows: // Clock A: // S1 {t:10, id:1} {t:30, id:4} // S2 | {t:20, id:2} | // | | | // Clock B: | | | // S1 {t:100, id:1} | {t:300, id:4} // S3 | {t:400, id:3} // | | // Clock C: | | // S2 {t: 2000, id: 2} | // S3 {t:5000, id:3} class ClockTracker { … }; } // namespace trace_processor } // namespace perfetto #endif // SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_CLOCK_TRACKER_H_