Primiano Tucci | de23d68 | 2019-08-17 08:09:04 +0200 | [diff] [blame] | 1 | # Synchronization of multiple clock domains |
| 2 | |
Primiano Tucci | a662485 | 2020-05-21 19:12:50 +0100 | [diff] [blame] | 3 | As per [6756fb05][6756fb05] Perfetto handles events using different |
Primiano Tucci | de23d68 | 2019-08-17 08:09:04 +0200 | [diff] [blame] | 4 | clock domains. On top of the default set of builtin clock domains, new clock |
| 5 | domains can be dynamically created at trace-time. |
| 6 | |
| 7 | Clock domains are allowed to drift from each other. |
Primiano Tucci | a662485 | 2020-05-21 19:12:50 +0100 | [diff] [blame] | 8 | At import time, Perfetto's [Trace Processor](/docs/analysis/trace-processor.md) is able |
Primiano Tucci | de23d68 | 2019-08-17 08:09:04 +0200 | [diff] [blame] | 9 | to rebuild the clock graph and use that to re-synchronize events on a global |
Primiano Tucci | a662485 | 2020-05-21 19:12:50 +0100 | [diff] [blame] | 10 | trace time, as long as the [ClockSnapshot][clock_snapshot] packets are present in |
Primiano Tucci | de23d68 | 2019-08-17 08:09:04 +0200 | [diff] [blame] | 11 | the trace. |
| 12 | |
Primiano Tucci | a662485 | 2020-05-21 19:12:50 +0100 | [diff] [blame] | 13 | ## Problem statement |
| 14 | |
Primiano Tucci | de23d68 | 2019-08-17 08:09:04 +0200 | [diff] [blame] | 15 | In a complex multi-producer scenario, different data source can emit events |
| 16 | using different clock domains. |
| 17 | |
| 18 | Some examples: |
| 19 | |
| 20 | * On Linux/Android, Ftrace events are emitted using the `CLOCK_BOOTTIME` clock, |
| 21 | but the Android event log uses `CLOCK_REALTIME`. |
| 22 | Some other data sources can use `CLOCK_MONOTONIC`. |
| 23 | These clocks can drift over time from each other due to suspend/resume. |
Primiano Tucci | a662485 | 2020-05-21 19:12:50 +0100 | [diff] [blame] | 24 | |
Primiano Tucci | de23d68 | 2019-08-17 08:09:04 +0200 | [diff] [blame] | 25 | * Graphics-related events are typically timestamped by the GPU, which can use a |
| 26 | hardware clock source that drifts from the system clock. |
| 27 | |
| 28 | At trace-time, the data sources might not be able to use `CLOCK_BOOTTIME` (or |
| 29 | even when possible, doing so might be prohibitively expensive). |
| 30 | |
| 31 | To solve this, we allow events to be recorded with different clock domains and |
| 32 | re-synchronize them at import time using clock snapshots. |
| 33 | |
Primiano Tucci | a662485 | 2020-05-21 19:12:50 +0100 | [diff] [blame] | 34 | ## Trace proto syntax |
Primiano Tucci | de23d68 | 2019-08-17 08:09:04 +0200 | [diff] [blame] | 35 | |
| 36 | Clock synchronization is based on two elements of the trace: |
| 37 | |
Primiano Tucci | a662485 | 2020-05-21 19:12:50 +0100 | [diff] [blame] | 38 | 1. [The timestamp_clock_id field of TracePacket](#timestamp_clock_id) |
| 39 | 2. [The ClockSnapshot trace packet](#clock_snapshot) |
| 40 | |
| 41 | ### {#timestamp_clock_id} The timestamp_clock_id field of TracePacket |
Primiano Tucci | de23d68 | 2019-08-17 08:09:04 +0200 | [diff] [blame] | 42 | |
Primiano Tucci | dfa4813 | 2020-04-20 20:17:16 +0100 | [diff] [blame] | 43 | ```protobuf |
Primiano Tucci | de23d68 | 2019-08-17 08:09:04 +0200 | [diff] [blame] | 44 | message TracePacket { |
| 45 | optional uint64 timestamp = 8; |
| 46 | |
| 47 | // Specifies the ID of the clock used for the TracePacket |timestamp|. Can be |
| 48 | // one of the built-in types from ClockSnapshot::BuiltinClocks, or a |
| 49 | // producer-defined clock id. |
| 50 | // If unspecified it defaults to BuiltinClocks::BOOTTIME. |
| 51 | optional uint32 timestamp_clock_id = 58; |
| 52 | |
| 53 | ``` |
| 54 | |
| 55 | This (optional) field determines the clock domain for the packet. |
| 56 | If omitted it refers to the default clock domain of the trace |
| 57 | (`CLOCK_BOOTTIME` for Linux/Android). |
| 58 | It present, this field can be set to either: |
| 59 | |
| 60 | * One of the [builtin clocks defined in clock_snapshot.proto][builtin_clocks] |
| 61 | (e.g., `CLOCK_BOOTTIME`, `CLOCK_REALTIME`, `CLOCK_MONOTONIC`). These clocks |
| 62 | have an ID <= 63. |
| 63 | * A custom sequence-scoped clock, with 64 <= ID < 128 |
| 64 | * A custom globally-scoped clock, with 128 <= ID < 2**32 |
| 65 | |
| 66 | #### Builtin clocks |
| 67 | Builtin clocks cover the most common case of data sources using one of the |
| 68 | POSIX clocks (see `man clock_gettime`). These clocks are periodically |
| 69 | snapshotted by the `traced` service. The producer doesn't need to do anything |
Primiano Tucci | a662485 | 2020-05-21 19:12:50 +0100 | [diff] [blame] | 70 | other than set the `timestamp_clock_id` field in order to emit events |
| 71 | that use these clocks. |
Primiano Tucci | de23d68 | 2019-08-17 08:09:04 +0200 | [diff] [blame] | 72 | |
| 73 | #### Sequence-scoped clocks |
| 74 | Sequence-scoped clocks are application-defined clock domains that are valid only |
| 75 | within the sequence of TracePacket(s) written by the same `TraceWriter` |
| 76 | (i.e. TracePacket that have the same `trusted_packet_sequence_id` field). |
| 77 | In most cases this really means *"events emitted by the same data source on |
| 78 | the same thread"*. |
| 79 | |
| 80 | This covers the most common use case of a clock domain that is used only within |
| 81 | a data source and not shared across different data sources. |
| 82 | The main advantage of sequence-scoped clocks is that avoids the ID |
Primiano Tucci | a662485 | 2020-05-21 19:12:50 +0100 | [diff] [blame] | 83 | disambiguation problem and JustWorks™ for the most simple case. |
Primiano Tucci | de23d68 | 2019-08-17 08:09:04 +0200 | [diff] [blame] | 84 | |
| 85 | In order to make use of a custom sequence-scoped clock domain a data source |
| 86 | must: |
| 87 | |
| 88 | * Emit its packets with a `timestamp_clock_id` in the range [64, 127] |
| 89 | * Emit at least once a [`ClockSnapshot`][clock_snapshot] packet. |
| 90 | |
| 91 | Such `ClockSnapshot`: |
| 92 | |
| 93 | * Must be emitted on the same sequence (i.e. by the same `TraceWriter`) that is |
| 94 | used to emit other `TracePacket`(s) that refer to such `timestamp_clock_id`. |
| 95 | * Must be emitted before the custom clock is referred to by any `TracePacket` |
| 96 | written by the same `TraceWriter`. |
| 97 | * Must contain a snapshot of: (i) the custom clock id [64, 127] and (ii) another |
| 98 | clock domain that can be resolved, at import time, against the default trace |
| 99 | clock domain (`CLOCK_BOOTTIME`) (see the [Operation section](#operation) |
| 100 | below). |
| 101 | |
| 102 | Collisions of `timestamp_clock_id` across two different `TraceWriter` sequences |
| 103 | are okay. E.g., two data sources, unaware of each other, can both use clock ID |
| 104 | 64 to refer to two different clock domains. |
| 105 | |
| 106 | #### Globally-scoped clocks |
| 107 | Globally-scoped clock domains work similarly to sequence-scoped clock domains, |
| 108 | with the only difference that their scope is global and applies to all |
| 109 | `TracePacket`(s) of the trace. |
| 110 | |
| 111 | The same `ClockSnapshot` rules as above apply. The only difference is that once |
| 112 | a `ClockSnapshot` defines a clock domain with ID >= 128, that clock domain can |
| 113 | be referred to by any `TracePacket` written by any `TraceWriter` sequence. |
| 114 | |
| 115 | Care must be taken to avoid collisions between global clock domains defined by |
| 116 | different data sources unaware of each other. |
| 117 | |
| 118 | As such, it is **strongly discouraged** to just use the ID 128 (or any other |
| 119 | arbitrarily chosen value). Instead the recommended pattern is: |
| 120 | |
| 121 | * Chose a fully qualified name for the clock domain |
| 122 | (e.g. `com.example.my_subsystem`) |
Rob Clark | 8e0e3e5 | 2021-03-25 08:21:10 -0700 | [diff] [blame] | 123 | * Chose the clock ID as `HASH("com.example.my_subsystem") | 0x80000000` |
Primiano Tucci | de23d68 | 2019-08-17 08:09:04 +0200 | [diff] [blame] | 124 | where `HASH(x)` is the FNV-1a hash of the fully qualified clock domain name. |
| 125 | |
Primiano Tucci | a662485 | 2020-05-21 19:12:50 +0100 | [diff] [blame] | 126 | ### {#clock_snapshot} The ClockSnapshot trace packet |
Primiano Tucci | de23d68 | 2019-08-17 08:09:04 +0200 | [diff] [blame] | 127 | |
| 128 | The [`ClockSnapshot`][clock_snapshot] packet defines sync points between two or |
| 129 | more clock domains. It conveys the notion *"at this point in time, the timestamp |
| 130 | of the clock domains X,Y,Z was 1000, 2000, 3000."*. |
| 131 | |
Primiano Tucci | a662485 | 2020-05-21 19:12:50 +0100 | [diff] [blame] | 132 | The trace importer ([Trace Processor](/docs/analysis/trace-processor.md)) uses this |
Primiano Tucci | de23d68 | 2019-08-17 08:09:04 +0200 | [diff] [blame] | 133 | information to establish a mapping between these clock domain. For instance, |
| 134 | to realize that 1042 on clock domain X == 3042 on clock domain Z. |
| 135 | |
| 136 | The `traced` service automatically emits `ClockSnapshot` packets for the builtin |
| 137 | clock domains on a regular basis. |
| 138 | |
| 139 | A data source should emit `ClockSnapshot` packets only when using custom clock |
| 140 | domains, either sequence-scoped or globally-scoped. |
| 141 | |
| 142 | It is *not* mandatory that the `ClockSnapshot` for a custom clock domain |
| 143 | contains also a snapshot of `CLOCK_BOOTTIME` (although it is advisable to do |
| 144 | so when possible). The Trace Processor can deal with multi-path clock domain |
| 145 | resolution based on graph traversal (see the [Operation](#operation) section). |
| 146 | |
| 147 | ## Operation |
| 148 | |
| 149 | At import time Trace Processor will attempt to convert the timestamp of each |
| 150 | TracePacket down to the trace clock domain (`CLOCK_BOOTTIME`) using the |
| 151 | `ClockSnapshot` packets seen until then using nearest neighbor approximation. |
| 152 | |
| 153 | For instance, assume that the trace contains `ClockSnapshot` for |
| 154 | `CLOCK_BOOTTIME` and `CLOCK_MONOTONIC` as follows: |
| 155 | |
| 156 | ```python |
| 157 | CLOCK_MONOTONIC 1000 1100 1200 1900 ... 2000 2100 |
| 158 | CLOCK_BOOTTIME 2000 2100 2200 2900 ... 3500 3600 |
| 159 | ``` |
| 160 | |
| 161 | In this example `CLOCK_MONOTONIC` is 1000 ns ahead of `CLOCK_BOOTTIME` until |
| 162 | T=2900. Then the two clocks go out of sync (e.g. the device is suspended) and, |
| 163 | on the next snapshot, the two clocks are 1500 ns apart. |
| 164 | |
| 165 | If a `TracePacket` with `timestamp_clock_id=CLOCK_MONOTONIC` and |
| 166 | `timestamp=1104` is seen, the clock sync logic will: |
| 167 | |
| 168 | 1. Find the latest snapshot for `CLOCK_MONOTONIC` <= 1104 (in the example above |
| 169 | the 2nd one with `CLOCK_MONOTONIC=1100`) |
| 170 | 2. Compute the clock domain conversion to `CLOCK_BOOTTIME` by applying the |
| 171 | delta (1104 - 1100) to the corresponding `CLOCK_BOOTTIME` snapshot |
| 172 | (2100, so 2100 + (1104 - 1100) -> 2104). |
| 173 | |
| 174 | The example above is rather simple, because the source clock domain (i.e. the |
| 175 | one specified by the `timestamp_clock_id` field) and the target clock domain |
| 176 | (i.e. the trace time, `CLOCK_BOTTIME`) are snapshotted within the same |
| 177 | `ClockSnapshot` packets. |
| 178 | |
| 179 | Clock domain conversion is possible also in more complex scenarios where the |
| 180 | two domains are not directly connected, as long as a path exist between the two. |
| 181 | |
| 182 | In this sense `ClockSnapshot` packets define edges of an acyclic graph that is |
| 183 | queried to perform clock domain conversions. All types of clock domains can be |
| 184 | used in the graph search. |
| 185 | |
| 186 | In the more general case, the clock domain conversion logic operates as follows: |
| 187 | |
| 188 | * The shortest path between the source and target clock domains is identified, |
| 189 | using a breadth first search in the graph. |
| 190 | * For each clock domain of the path identified, the timestamp is converted using |
| 191 | the aforementioned nearest neighbor resolution. |
| 192 | |
| 193 | This allows to deal with complex scenarios as follows: |
| 194 | |
| 195 | ```python |
| 196 | CUSTOM_CLOCK 1000 3000 |
| 197 | CLOCK_MONOTONIC 1100 1200 3200 4000 |
| 198 | CLOCK_BOOTTIME 5200 9000 |
| 199 | ``` |
| 200 | |
| 201 | In the example above, there is no snapshot that directly links `CUSTOM_CLOCK` |
| 202 | and `CLOCK_BOOTTIME`. However there is an indirect path that allows a conversion |
| 203 | via `CUSTOM_CLOCK -> CLOCK_MONOTONIC -> CLOCK_BOOTTIME`. |
| 204 | |
| 205 | This allows to synchronize a hypothetical `TracePacket` that has |
| 206 | `timestamp_clock_id=CUSTOM_CLOCK` and `timestamp=3503` as follows: |
| 207 | |
| 208 | ```python |
| 209 | #Step 1 |
| 210 | CUSTOM_CLOCK = 3503 |
| 211 | Nearest snapshot: {CUSTOM_CLOCK:3000, CLOCK_MONOTONIC:3200} |
| 212 | CLOCK_MONOTONIC = (3503 - 3000) + 3200 = 3703 |
| 213 | |
| 214 | #Step 2 |
| 215 | CLOCK_MONOTONIC = 3703 |
| 216 | Nearest snapshot: {CLOCK_MONOTONIC:1200, CLOCK_BOOTTIME:5200} |
| 217 | CLOCK_BOOTTIME = (3703 - 1200) + 5200 = 7703 |
| 218 | ``` |
| 219 | |
Primiano Tucci | a662485 | 2020-05-21 19:12:50 +0100 | [diff] [blame] | 220 | ## Caveats |
| 221 | |
Primiano Tucci | de23d68 | 2019-08-17 08:09:04 +0200 | [diff] [blame] | 222 | Clock resolution between two domains (A,B) is allowed only as long as all the |
| 223 | clock domains in the A -> B path are monotonic (or at least look so in the |
| 224 | `ClockSnapshot` packets). |
| 225 | If non-monotonicity is detected at import time, the clock domain is excluded as |
| 226 | a source path in the graph search and is allowed only as a target path. |
| 227 | |
| 228 | For instance, imagine capturing a trace that has both `CLOCK_BOOTTIME` |
| 229 | and `CLOCK_REALTIME` in the night when daylight saving is applied, when the |
| 230 | real-time clock jumps back from 3AM to 2AM. |
| 231 | |
| 232 | Such a trace would contain several snapshots that break bijectivity between the |
| 233 | two clock domains. In this case converting a `CLOCK_BOOTTIME` timestamp to |
| 234 | `CLOCK_REALTIME` is always possible without ambiguities (eventually two distinct |
| 235 | timestamps can be resolved against the same `CLOCK_REALTIME` timestamp). |
| 236 | The opposite is not allowed, because `CLOCK_REALTIME` timestamps between 2AM |
| 237 | and 3AM are ambiguous and could be resolved against two different |
| 238 | `CLOCK_BOOTTIME` timestamps). |
| 239 | |
| 240 | [6756fb05]: https://android-review.googlesource.com/c/platform/external/perfetto/+/1101915/ |
Lalit Maganti | 9e0146e | 2023-07-06 23:15:24 +0100 | [diff] [blame] | 241 | [clock_snapshot]: https://android.googlesource.com/platform/external/perfetto/+/refs/heads/main/protos/perfetto/trace/clock_snapshot.proto |
Primiano Tucci | de23d68 | 2019-08-17 08:09:04 +0200 | [diff] [blame] | 242 | [timestamp_clock_id]: https://android.googlesource.com/platform/external/perfetto/+/3e7ca4f5893f7d762ec24a2eac9a47343b226c6c/protos/perfetto/trace/trace_packet.proto#68 |
| 243 | [builtin_clocks]: https://android.googlesource.com/platform/external/perfetto/+/3e7ca4f5893f7d762ec24a2eac9a47343b226c6c/protos/perfetto/trace/clock_snapshot.proto#25 |