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/ftrace/ftrace_tokenizer.h"
18
19 #include "perfetto/base/logging.h"
20 #include "perfetto/protozero/proto_decoder.h"
21 #include "perfetto/protozero/proto_utils.h"
22 #include "src/trace_processor/storage/stats.h"
23 #include "src/trace_processor/storage/trace_storage.h"
24 #include "src/trace_processor/trace_sorter.h"
25
26 #include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
27 #include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
28
29 namespace perfetto {
30 namespace trace_processor {
31
32 using protozero::ProtoDecoder;
33 using protozero::proto_utils::MakeTagLengthDelimited;
34 using protozero::proto_utils::MakeTagVarInt;
35 using protozero::proto_utils::ParseVarInt;
36
37 PERFETTO_ALWAYS_INLINE
TokenizeFtraceBundle(TraceBlobView bundle)38 void FtraceTokenizer::TokenizeFtraceBundle(TraceBlobView bundle) {
39 protos::pbzero::FtraceEventBundle::Decoder decoder(bundle.data(),
40 bundle.length());
41
42 if (PERFETTO_UNLIKELY(!decoder.has_cpu())) {
43 PERFETTO_ELOG("CPU field not found in FtraceEventBundle");
44 context_->storage->IncrementStats(stats::ftrace_bundle_tokenizer_errors);
45 return;
46 }
47
48 uint32_t cpu = decoder.cpu();
49 if (PERFETTO_UNLIKELY(cpu > kMaxCpus)) {
50 PERFETTO_ELOG("CPU larger than kMaxCpus (%u > %zu)", cpu, kMaxCpus);
51 return;
52 }
53
54 if (decoder.has_compact_sched()) {
55 TokenizeFtraceCompactSched(cpu, decoder.compact_sched().data,
56 decoder.compact_sched().size);
57 }
58
59 for (auto it = decoder.event(); it; ++it) {
60 protozero::ConstBytes event = *it;
61 size_t off = bundle.offset_of(event.data);
62 TokenizeFtraceEvent(cpu, bundle.slice(off, event.size));
63 }
64 context_->sorter->FinalizeFtraceEventBatch(cpu);
65 }
66
67 PERFETTO_ALWAYS_INLINE
TokenizeFtraceEvent(uint32_t cpu,TraceBlobView event)68 void FtraceTokenizer::TokenizeFtraceEvent(uint32_t cpu, TraceBlobView event) {
69 constexpr auto kTimestampFieldNumber =
70 protos::pbzero::FtraceEvent::kTimestampFieldNumber;
71 const uint8_t* data = event.data();
72 const size_t length = event.length();
73 ProtoDecoder decoder(data, length);
74 uint64_t raw_timestamp = 0;
75 bool timestamp_found = false;
76
77 // Speculate on the fact that the timestamp is often the 1st field of the
78 // event.
79 constexpr auto timestampFieldTag = MakeTagVarInt(kTimestampFieldNumber);
80 if (PERFETTO_LIKELY(length > 10 && data[0] == timestampFieldTag)) {
81 // Fastpath.
82 const uint8_t* next = ParseVarInt(data + 1, data + 11, &raw_timestamp);
83 timestamp_found = next != data + 1;
84 decoder.Reset(next);
85 } else {
86 // Slowpath.
87 if (auto ts_field = decoder.FindField(kTimestampFieldNumber)) {
88 timestamp_found = true;
89 raw_timestamp = ts_field.as_uint64();
90 }
91 }
92
93 if (PERFETTO_UNLIKELY(!timestamp_found)) {
94 PERFETTO_ELOG("Timestamp field not found in FtraceEvent");
95 context_->storage->IncrementStats(stats::ftrace_bundle_tokenizer_errors);
96 return;
97 }
98
99 int64_t timestamp = static_cast<int64_t>(raw_timestamp);
100
101 // We don't need to parse this packet, just push it to be sorted with
102 // the timestamp.
103 context_->sorter->PushFtraceEvent(cpu, timestamp, std::move(event));
104 }
105
106 PERFETTO_ALWAYS_INLINE
TokenizeFtraceCompactSched(uint32_t cpu,const uint8_t * data,size_t size)107 void FtraceTokenizer::TokenizeFtraceCompactSched(uint32_t cpu,
108 const uint8_t* data,
109 size_t size) {
110 protos::pbzero::FtraceEventBundle::CompactSched::Decoder compact_sched(data,
111 size);
112 // Build the interning table for comm fields.
113 std::vector<StringId> string_table;
114 string_table.reserve(512);
115 for (auto it = compact_sched.intern_table(); it; it++) {
116 StringId value = context_->storage->InternString(*it);
117 string_table.push_back(value);
118 }
119
120 TokenizeFtraceCompactSchedSwitch(cpu, compact_sched, string_table);
121 TokenizeFtraceCompactSchedWaking(cpu, compact_sched, string_table);
122 }
123
TokenizeFtraceCompactSchedSwitch(uint32_t cpu,const protos::pbzero::FtraceEventBundle::CompactSched::Decoder & compact,const std::vector<StringId> & string_table)124 void FtraceTokenizer::TokenizeFtraceCompactSchedSwitch(
125 uint32_t cpu,
126 const protos::pbzero::FtraceEventBundle::CompactSched::Decoder& compact,
127 const std::vector<StringId>& string_table) {
128 // Accumulator for timestamp deltas.
129 int64_t timestamp_acc = 0;
130
131 // The events' fields are stored in a structure-of-arrays style, using packed
132 // repeated fields. Walk each repeated field in step to recover individual
133 // events.
134 bool parse_error = false;
135 auto timestamp_it = compact.switch_timestamp(&parse_error);
136 auto pstate_it = compact.switch_prev_state(&parse_error);
137 auto npid_it = compact.switch_next_pid(&parse_error);
138 auto nprio_it = compact.switch_next_prio(&parse_error);
139 auto comm_it = compact.switch_next_comm_index(&parse_error);
140 for (; timestamp_it && pstate_it && npid_it && nprio_it && comm_it;
141 ++timestamp_it, ++pstate_it, ++npid_it, ++nprio_it, ++comm_it) {
142 InlineSchedSwitch event{};
143
144 // delta-encoded timestamp
145 timestamp_acc += static_cast<int64_t>(*timestamp_it);
146 int64_t event_timestamp = timestamp_acc;
147
148 // index into the interned string table
149 PERFETTO_DCHECK(*comm_it < string_table.size());
150 event.next_comm = string_table[*comm_it];
151
152 event.prev_state = *pstate_it;
153 event.next_pid = *npid_it;
154 event.next_prio = *nprio_it;
155
156 context_->sorter->PushInlineFtraceEvent(cpu, event_timestamp, event);
157 }
158
159 // Check that all packed buffers were decoded correctly, and fully.
160 bool sizes_match =
161 !timestamp_it && !pstate_it && !npid_it && !nprio_it && !comm_it;
162 if (parse_error || !sizes_match)
163 context_->storage->IncrementStats(stats::compact_sched_has_parse_errors);
164 }
165
TokenizeFtraceCompactSchedWaking(uint32_t cpu,const protos::pbzero::FtraceEventBundle::CompactSched::Decoder & compact,const std::vector<StringId> & string_table)166 void FtraceTokenizer::TokenizeFtraceCompactSchedWaking(
167 uint32_t cpu,
168 const protos::pbzero::FtraceEventBundle::CompactSched::Decoder& compact,
169 const std::vector<StringId>& string_table) {
170 // Accumulator for timestamp deltas.
171 int64_t timestamp_acc = 0;
172
173 // The events' fields are stored in a structure-of-arrays style, using packed
174 // repeated fields. Walk each repeated field in step to recover individual
175 // events.
176 bool parse_error = false;
177 auto timestamp_it = compact.waking_timestamp(&parse_error);
178 auto pid_it = compact.waking_pid(&parse_error);
179 auto tcpu_it = compact.waking_target_cpu(&parse_error);
180 auto prio_it = compact.waking_prio(&parse_error);
181 auto comm_it = compact.waking_comm_index(&parse_error);
182
183 for (; timestamp_it && pid_it && tcpu_it && prio_it && comm_it;
184 ++timestamp_it, ++pid_it, ++tcpu_it, ++prio_it, ++comm_it) {
185 InlineSchedWaking event{};
186
187 // delta-encoded timestamp
188 timestamp_acc += static_cast<int64_t>(*timestamp_it);
189 int64_t event_timestamp = timestamp_acc;
190
191 // index into the interned string table
192 PERFETTO_DCHECK(*comm_it < string_table.size());
193 event.comm = string_table[*comm_it];
194
195 event.pid = *pid_it;
196 event.target_cpu = *tcpu_it;
197 event.prio = *prio_it;
198
199 context_->sorter->PushInlineFtraceEvent(cpu, event_timestamp, event);
200 }
201
202 // Check that all packed buffers were decoded correctly, and fully.
203 bool sizes_match =
204 !timestamp_it && !pid_it && !tcpu_it && !prio_it && !comm_it;
205 if (parse_error || !sizes_match)
206 context_->storage->IncrementStats(stats::compact_sched_has_parse_errors);
207 }
208
209 } // namespace trace_processor
210 } // namespace perfetto
211