Skip to content

Commit e8d6a63

Browse files
committed
tp: suppress spurious clock sync errors for deferred packets
When a packet arrives with a sequence-scoped clock before its ClockSnapshot, ToTraceTime fails and records a spurious clock_sync_failure_unknown_source_clock error even though the packet will be resolved later at EOF. Fix this by plumbing a suppress_errors bool through the conversion path (ClockSynchronizer::Convert -> ConvertSlowpath). Before EOF, errors are suppressed and the packet is deferred. At EOF, errors are allowed so genuinely broken clocks are still reported. Extract the convert-or-defer logic into an inline helper ResolveTimestampToTraceTime with a clear three-state return (kResolved, kDeferred, kDropped) to make the hot path readable. Add clock_sync_failure_undeferrable_packet_loss stat (with import logging) for the rare case where conversion fails and deferral is not possible. Bug: #5163
1 parent ff73bc6 commit e8d6a63

File tree

7 files changed

+105
-31
lines changed

7 files changed

+105
-31
lines changed

src/trace_processor/importers/common/clock_tracker.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,15 @@ class ClockTracker {
6464
PERFETTO_ALWAYS_INLINE std::optional<int64_t> ToTraceTime(
6565
ClockId clock_id,
6666
int64_t timestamp,
67-
std::optional<size_t> byte_offset = std::nullopt) {
67+
std::optional<size_t> byte_offset = std::nullopt,
68+
bool suppress_errors = false) {
6869
if (PERFETTO_UNLIKELY(deferred_identity_clock_.has_value())) {
6970
FlushDeferredIdentitySync();
7071
}
7172
auto* state = context_->trace_time_state.get();
7273
++num_conversions_;
7374
auto ts = active_sync_->Convert(clock_id, timestamp, state->clock_id,
74-
byte_offset);
75+
byte_offset, suppress_errors);
7576
return ts ? std::optional(ToHostTraceTime(*ts)) : ts;
7677
}
7778

src/trace_processor/importers/proto/proto_trace_reader.cc

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,34 @@ base::Status ProtoTraceReader::ParsePacket(TraceBlobView packet) {
380380
return TimestampTokenizeAndPushToSorter(std::move(packet));
381381
}
382382

383+
ProtoTraceReader::ClockResolution ProtoTraceReader::ResolveTimestampToTraceTime(
384+
ClockTracker::ClockId clock_id,
385+
int64_t* timestamp,
386+
TraceBlobView* packet) {
387+
// Before EOF, suppress errors: the clock snapshot may arrive later and
388+
// the packet will be retried at EOF. At EOF, allow errors so genuinely
389+
// broken clocks are reported.
390+
bool suppress_errors = !received_eof_;
391+
auto trace_ts = context_->clock_tracker->ToTraceTime(
392+
clock_id, *timestamp, packet->offset(), suppress_errors);
393+
if (PERFETTO_LIKELY(trace_ts.has_value())) {
394+
*timestamp = *trace_ts;
395+
return ClockResolution::kResolved;
396+
}
397+
398+
// Conversion failed. Before EOF, try to defer for later resolution.
399+
if (!received_eof_ &&
400+
context_->sorter->SetSortingMode(TraceSorter::SortingMode::kFullSort)) {
401+
eof_deferred_packets_.push_back(std::move(*packet));
402+
return ClockResolution::kDeferred;
403+
}
404+
405+
// Cannot defer: record the error and drop the packet.
406+
context_->import_logs_tracker->RecordTokenizationError(
407+
stats::clock_sync_failure_undeferrable_packet_loss, packet->offset());
408+
return ClockResolution::kDropped;
409+
}
410+
383411
base::Status ProtoTraceReader::TimestampTokenizeAndPushToSorter(
384412
TraceBlobView packet) {
385413
protos::pbzero::TracePacket::Decoder decoder(packet.data(), packet.length());
@@ -439,23 +467,12 @@ base::Status ProtoTraceReader::TimestampTokenizeAndPushToSorter(
439467
} else {
440468
converted_clock_id = ClockId::Machine(timestamp_clock_id);
441469
}
442-
auto trace_ts = context_->clock_tracker->ToTraceTime(
443-
converted_clock_id, timestamp, packet.offset());
444-
if (!trace_ts) {
445-
// We need to switch to full sorting mode to ensure that packets with
446-
// missing timestamp are handled correctly. Don't save the packet unless
447-
// switching to full sorting mode succeeded.
448-
if (!received_eof_ && context_->sorter->SetSortingMode(
449-
TraceSorter::SortingMode::kFullSort)) {
450-
eof_deferred_packets_.push_back(std::move(packet));
451-
return base::OkStatus();
452-
}
453-
// We don't return an error here as it will cause the trace to stop
454-
// parsing. Instead, we rely on the stat increment (which happened
455-
// automatically in ToTraceTime) to inform the user about the error.
470+
auto resolution =
471+
ResolveTimestampToTraceTime(converted_clock_id, &timestamp, &packet);
472+
if (resolution == ClockResolution::kDeferred ||
473+
resolution == ClockResolution::kDropped) {
456474
return base::OkStatus();
457475
}
458-
timestamp = *trace_ts;
459476
}
460477
} else {
461478
timestamp = std::max(latest_timestamp_, context_->sorter->max_timestamp());

src/trace_processor/importers/proto/proto_trace_reader.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,20 @@ class ProtoTraceReader : public ChunkedTraceReader {
8989
uint32_t needs_incremental_state_skipped = 0;
9090
};
9191

92+
// Result of attempting to resolve a packet's timestamp to trace time.
93+
enum class ClockResolution {
94+
kResolved, // Timestamp was successfully converted.
95+
kDeferred, // Packet deferred for resolution at EOF.
96+
kDropped, // Conversion failed and packet cannot be deferred.
97+
};
98+
99+
// Hot-path: converts |timestamp| from |clock_id| to trace time. If the
100+
// clock is not yet known, defers the packet for resolution at EOF.
101+
PERFETTO_ALWAYS_INLINE ClockResolution
102+
ResolveTimestampToTraceTime(ClockTracker::ClockId clock_id,
103+
int64_t* timestamp,
104+
TraceBlobView* packet);
105+
92106
using ConstBytes = protozero::ConstBytes;
93107
base::Status ParsePacket(TraceBlobView);
94108
base::Status TimestampTokenizeAndPushToSorter(TraceBlobView);

src/trace_processor/importers/proto/proto_trace_reader_unittest.cc

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include "src/trace_processor/importers/common/metadata_tracker.h"
3737
#include "src/trace_processor/importers/proto/additional_modules.h"
3838
#include "src/trace_processor/sorter/trace_sorter.h"
39+
#include "src/trace_processor/storage/stats.h"
3940
#include "src/trace_processor/storage/trace_storage.h"
4041
#include "src/trace_processor/types/trace_processor_context.h"
4142
#include "src/trace_processor/types/trace_processor_context_ptr.h"
@@ -257,5 +258,32 @@ TEST_F(ProtoTraceReaderTest, CalculateClockOffset_MultiRounds) {
257258
ASSERT_EQ(-114375, clock_offsets[ClockId::Machine(BOOTTIME)]);
258259
}
259260

261+
TEST_F(ProtoTraceReaderTest, DeferredClockSnapshotSequenceScopedClock) {
262+
// Packet 1: has a sequence-scoped clock (64) but no clock snapshot yet.
263+
// This will fail ToTraceTime and be deferred for later processing.
264+
auto* packet1 = trace_->add_packet();
265+
packet1->set_trusted_packet_sequence_id(1);
266+
packet1->set_timestamp(1000);
267+
packet1->set_timestamp_clock_id(64);
268+
269+
// Packet 2: clock snapshot on the same sequence mapping clock 64 -> BOOTTIME.
270+
auto* packet2 = trace_->add_packet();
271+
packet2->set_trusted_packet_sequence_id(1);
272+
auto* snap = packet2->set_clock_snapshot();
273+
auto* clk1 = snap->add_clocks();
274+
clk1->set_clock_id(64);
275+
clk1->set_timestamp(10);
276+
auto* clk2 = snap->add_clocks();
277+
clk2->set_clock_id(BOOTTIME);
278+
clk2->set_timestamp(10000);
279+
280+
ASSERT_TRUE(Tokenize().ok());
281+
ASSERT_TRUE(proto_trace_reader_->OnPushDataToSorter().ok());
282+
283+
// The deferred packet should have resolved without recording an error.
284+
EXPECT_EQ(0, host_context_.storage->GetStats(
285+
stats::clock_sync_failure_unknown_source_clock));
286+
}
287+
260288
} // namespace
261289
} // namespace perfetto::trace_processor

src/trace_processor/storage/stats.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,15 @@ namespace perfetto::trace_processor::stats {
304304
"snapshots and its own for timestamp conversion. Timestamps " \
305305
"converted before the first own clock snapshot used the primary " \
306306
"trace's clocks which may differ."), \
307+
F(clock_sync_failure_undeferrable_packet_loss, kSingle, kDataLoss, \
308+
kAnalysis, \
309+
"A packet had a timestamp with a clock ID that could not be resolved " \
310+
"to trace time and the packet could not be deferred for later " \
311+
"resolution (the trace sorter was unable to switch to full-sort mode). " \
312+
"The packet was dropped and its data will be missing from query " \
313+
"results. This can happen when a sequence-scoped clock (64-127) is " \
314+
"used before the ClockSnapshot defining it arrives, and the sorter " \
315+
"has already started flushing."), \
307316
F(clock_sync_cache_miss, kSingle, kInfo, kAnalysis, ""), \
308317
F(process_tracker_errors, kSingle, kError, kAnalysis, ""), \
309318
F(namespaced_thread_missing_process, kSingle, kError, kAnalysis, \

src/trace_processor/util/clock_synchronizer.cc

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -202,25 +202,28 @@ std::optional<int64_t> ClockSynchronizer::ConvertSlowpath(
202202
int64_t src_timestamp,
203203
std::optional<int64_t> src_ts_ns,
204204
ClockId target_clock_id,
205-
std::optional<size_t> byte_offset) {
205+
std::optional<size_t> byte_offset,
206+
bool suppress_errors) {
206207
PERFETTO_DCHECK(!src_clock_id.IsSequenceClock() || src_clock_id.seq_id != 0);
207208
PERFETTO_DCHECK(!target_clock_id.IsSequenceClock() ||
208209
target_clock_id.seq_id != 0);
209210
clock_event_listener_->OnClockSyncCacheMiss();
210211

211212
ClockPath path = FindPath(src_clock_id, target_clock_id);
212213
if (!path.valid()) {
213-
// Determine which clock(s) are unknown and record error
214-
ClockSyncErrorType error;
215-
if (clocks_.find(src_clock_id) == clocks_.end()) {
216-
error = ClockSyncErrorType::kUnknownSourceClock;
217-
} else if (clocks_.find(target_clock_id) == clocks_.end()) {
218-
error = ClockSyncErrorType::kUnknownTargetClock;
219-
} else {
220-
error = ClockSyncErrorType::kNoPath;
214+
if (!suppress_errors) {
215+
// Determine which clock(s) are unknown and record error
216+
ClockSyncErrorType error;
217+
if (clocks_.find(src_clock_id) == clocks_.end()) {
218+
error = ClockSyncErrorType::kUnknownSourceClock;
219+
} else if (clocks_.find(target_clock_id) == clocks_.end()) {
220+
error = ClockSyncErrorType::kUnknownTargetClock;
221+
} else {
222+
error = ClockSyncErrorType::kNoPath;
223+
}
224+
clock_event_listener_->RecordConversionError(
225+
error, src_clock_id, target_clock_id, src_timestamp, byte_offset);
221226
}
222-
clock_event_listener_->RecordConversionError(
223-
error, src_clock_id, target_clock_id, src_timestamp, byte_offset);
224227
return std::nullopt;
225228
}
226229

src/trace_processor/util/clock_synchronizer.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,8 @@ class ClockSynchronizer {
260260
std::optional<int64_t> Convert(ClockId src_clock_id,
261261
int64_t src_timestamp,
262262
ClockId target_clock_id,
263-
std::optional<size_t> byte_offset) {
263+
std::optional<size_t> byte_offset,
264+
bool suppress_errors = false) {
264265
if (PERFETTO_LIKELY(src_clock_id == target_clock_id)) {
265266
return src_timestamp;
266267
}
@@ -282,7 +283,7 @@ class ClockSynchronizer {
282283
}
283284
}
284285
return ConvertSlowpath(src_clock_id, src_timestamp, ns, target_clock_id,
285-
byte_offset);
286+
byte_offset, suppress_errors);
286287
}
287288

288289
// For testing:
@@ -386,7 +387,8 @@ class ClockSynchronizer {
386387
int64_t src_timestamp,
387388
std::optional<int64_t> src_ts_ns,
388389
ClockId target_clock_id,
389-
std::optional<size_t> byte_offset);
390+
std::optional<size_t> byte_offset,
391+
bool suppress_errors);
390392

391393
// Returns whether |global_clock_id| represents a sequence-scoped clock, i.e.
392394
// a ClockId returned by SequenceToGlobalClock().

0 commit comments

Comments
 (0)