1 /*
2 * Copyright (C) 2018 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 <limits>
18
19 #include <stdint.h>
20
21 #include "src/trace_processor/importers/common/process_tracker.h"
22 #include "src/trace_processor/importers/common/slice_tracker.h"
23 #include "src/trace_processor/importers/common/track_tracker.h"
24 #include "src/trace_processor/storage/trace_storage.h"
25 #include "src/trace_processor/types/trace_processor_context.h"
26
27 namespace perfetto {
28 namespace trace_processor {
29 namespace {
30 // Slices which have been opened but haven't been closed yet will be marked
31 // with this duration placeholder.
32 constexpr int64_t kPendingDuration = -1;
33 } // namespace
34
SliceTracker(TraceProcessorContext * context)35 SliceTracker::SliceTracker(TraceProcessorContext* context)
36 : context_(context) {}
37
38 SliceTracker::~SliceTracker() = default;
39
Begin(int64_t timestamp,TrackId track_id,StringId category,StringId name,SetArgsCallback args_callback)40 base::Optional<uint32_t> SliceTracker::Begin(int64_t timestamp,
41 TrackId track_id,
42 StringId category,
43 StringId name,
44 SetArgsCallback args_callback) {
45 tables::SliceTable::Row row(timestamp, kPendingDuration, track_id, category,
46 name);
47 return StartSlice(timestamp, track_id, args_callback, [this, &row]() {
48 return context_->storage->mutable_slice_table()->Insert(row).id;
49 });
50 }
51
BeginGpu(tables::GpuSliceTable::Row row,SetArgsCallback args_callback)52 void SliceTracker::BeginGpu(tables::GpuSliceTable::Row row,
53 SetArgsCallback args_callback) {
54 // Ensure that the duration is pending for this row.
55 // TODO(lalitm): change this to eventually use null instead of -1.
56 row.dur = kPendingDuration;
57
58 StartSlice(row.ts, row.track_id, args_callback, [this, &row]() {
59 return context_->storage->mutable_gpu_slice_table()->Insert(row).id;
60 });
61 }
62
BeginFrameEvent(tables::GraphicsFrameSliceTable::Row row,SetArgsCallback args_callback)63 void SliceTracker::BeginFrameEvent(tables::GraphicsFrameSliceTable::Row row,
64 SetArgsCallback args_callback) {
65 // Ensure that the duration is pending for this row.
66 // TODO(lalitm): change this to eventually use null instead of -1.
67 row.dur = kPendingDuration;
68
69 StartSlice(row.ts, row.track_id, args_callback, [this, &row]() {
70 return context_->storage->mutable_graphics_frame_slice_table()
71 ->Insert(row)
72 .id;
73 });
74 }
75
Scoped(int64_t timestamp,TrackId track_id,StringId category,StringId name,int64_t duration,SetArgsCallback args_callback)76 base::Optional<uint32_t> SliceTracker::Scoped(int64_t timestamp,
77 TrackId track_id,
78 StringId category,
79 StringId name,
80 int64_t duration,
81 SetArgsCallback args_callback) {
82 PERFETTO_DCHECK(duration >= 0);
83
84 tables::SliceTable::Row row(timestamp, duration, track_id, category, name);
85 return StartSlice(timestamp, track_id, args_callback, [this, &row]() {
86 return context_->storage->mutable_slice_table()->Insert(row).id;
87 });
88 }
89
ScopedGpu(const tables::GpuSliceTable::Row & row,SetArgsCallback args_callback)90 void SliceTracker::ScopedGpu(const tables::GpuSliceTable::Row& row,
91 SetArgsCallback args_callback) {
92 PERFETTO_DCHECK(row.dur >= 0);
93
94 StartSlice(row.ts, TrackId(row.track_id), args_callback, [this, &row]() {
95 return context_->storage->mutable_gpu_slice_table()->Insert(row).id;
96 });
97 }
98
ScopedFrameEvent(const tables::GraphicsFrameSliceTable::Row & row,SetArgsCallback args_callback)99 SliceId SliceTracker::ScopedFrameEvent(
100 const tables::GraphicsFrameSliceTable::Row& row,
101 SetArgsCallback args_callback) {
102 PERFETTO_DCHECK(row.dur >= 0);
103
104 SliceId id;
105 StartSlice(row.ts, TrackId(row.track_id), args_callback, [this, &row, &id]() {
106 id =
107 context_->storage->mutable_graphics_frame_slice_table()->Insert(row).id;
108 return id;
109 });
110 return id;
111 }
112
End(int64_t timestamp,TrackId track_id,StringId category,StringId name,SetArgsCallback args_callback)113 base::Optional<uint32_t> SliceTracker::End(int64_t timestamp,
114 TrackId track_id,
115 StringId category,
116 StringId name,
117 SetArgsCallback args_callback) {
118 auto finder = [this, category, name](const SlicesStack& stack) {
119 return MatchingIncompleteSliceIndex(stack, name, category);
120 };
121 auto slice_id = CompleteSlice(timestamp, track_id, args_callback, finder);
122 if (!slice_id)
123 return base::nullopt;
124 return context_->storage->slice_table().id().IndexOf(*slice_id);
125 }
126
AddArgs(TrackId track_id,StringId category,StringId name,SetArgsCallback args_callback)127 base::Optional<uint32_t> SliceTracker::AddArgs(TrackId track_id,
128 StringId category,
129 StringId name,
130 SetArgsCallback args_callback) {
131 auto& stack = stacks_[track_id];
132 if (stack.empty())
133 return base::nullopt;
134
135 auto* slices = context_->storage->mutable_slice_table();
136 base::Optional<uint32_t> stack_idx =
137 MatchingIncompleteSliceIndex(stack, name, category);
138 if (!stack_idx.has_value())
139 return base::nullopt;
140 uint32_t slice_idx = stack[*stack_idx].first;
141 PERFETTO_DCHECK(slices->dur()[slice_idx] == kPendingDuration);
142 // Add args to current pending slice.
143 ArgsTracker* tracker = &stack[*stack_idx].second;
144 auto bound_inserter = tracker->AddArgsTo(slices->id()[slice_idx]);
145 args_callback(&bound_inserter);
146 return slice_idx;
147 }
148
EndGpu(int64_t ts,TrackId t_id,SetArgsCallback args_callback)149 base::Optional<SliceId> SliceTracker::EndGpu(int64_t ts,
150 TrackId t_id,
151 SetArgsCallback args_callback) {
152 return CompleteSlice(ts, t_id, args_callback, [](const SlicesStack& stack) {
153 return static_cast<uint32_t>(stack.size() - 1);
154 });
155 }
156
EndFrameEvent(int64_t ts,TrackId t_id,SetArgsCallback args_callback)157 base::Optional<SliceId> SliceTracker::EndFrameEvent(
158 int64_t ts,
159 TrackId t_id,
160 SetArgsCallback args_callback) {
161 return CompleteSlice(ts, t_id, args_callback, [](const SlicesStack& stack) {
162 return static_cast<uint32_t>(stack.size() - 1);
163 });
164 }
165
StartSlice(int64_t timestamp,TrackId track_id,SetArgsCallback args_callback,std::function<SliceId ()> inserter)166 base::Optional<uint32_t> SliceTracker::StartSlice(
167 int64_t timestamp,
168 TrackId track_id,
169 SetArgsCallback args_callback,
170 std::function<SliceId()> inserter) {
171 // At this stage all events should be globally timestamp ordered.
172 if (timestamp < prev_timestamp_) {
173 context_->storage->IncrementStats(stats::slice_out_of_order);
174 return base::nullopt;
175 }
176 prev_timestamp_ = timestamp;
177
178 auto* stack = &stacks_[track_id];
179 auto* slices = context_->storage->mutable_slice_table();
180 MaybeCloseStack(timestamp, stack);
181
182 const uint8_t depth = static_cast<uint8_t>(stack->size());
183 if (depth >= std::numeric_limits<uint8_t>::max()) {
184 PERFETTO_DFATAL("Slices with too large depth found.");
185 return base::nullopt;
186 }
187 int64_t parent_stack_id =
188 depth == 0 ? 0 : slices->stack_id()[stack->back().first];
189 base::Optional<tables::SliceTable::Id> parent_id =
190 depth == 0 ? base::nullopt
191 : base::make_optional(slices->id()[stack->back().first]);
192
193 SliceId id = inserter();
194 uint32_t slice_idx = *slices->id().IndexOf(id);
195 stack->emplace_back(std::make_pair(slice_idx, ArgsTracker(context_)));
196
197 // Post fill all the relevant columns. All the other columns should have
198 // been filled by the inserter.
199 slices->mutable_depth()->Set(slice_idx, depth);
200 slices->mutable_parent_stack_id()->Set(slice_idx, parent_stack_id);
201 slices->mutable_stack_id()->Set(slice_idx, GetStackHash(*stack));
202 if (parent_id)
203 slices->mutable_parent_id()->Set(slice_idx, *parent_id);
204
205 if (args_callback) {
206 ArgsTracker* tracker = &stack->back().second;
207 auto bound_inserter = tracker->AddArgsTo(id);
208 args_callback(&bound_inserter);
209 }
210 return slice_idx;
211 }
212
CompleteSlice(int64_t timestamp,TrackId track_id,SetArgsCallback args_callback,std::function<base::Optional<uint32_t> (const SlicesStack &)> finder)213 base::Optional<SliceId> SliceTracker::CompleteSlice(
214 int64_t timestamp,
215 TrackId track_id,
216 SetArgsCallback args_callback,
217 std::function<base::Optional<uint32_t>(const SlicesStack&)> finder) {
218 // At this stage all events should be globally timestamp ordered.
219 if (timestamp < prev_timestamp_) {
220 context_->storage->IncrementStats(stats::slice_out_of_order);
221 return base::nullopt;
222 }
223 prev_timestamp_ = timestamp;
224
225 auto& stack = stacks_[track_id];
226 MaybeCloseStack(timestamp, &stack);
227 if (stack.empty())
228 return base::nullopt;
229
230 auto* slices = context_->storage->mutable_slice_table();
231 base::Optional<uint32_t> stack_idx = finder(stack);
232
233 // If we are trying to close slices that are not open on the stack (e.g.,
234 // slices that began before tracing started), bail out.
235 if (!stack_idx)
236 return base::nullopt;
237
238 uint32_t slice_idx = stack[stack_idx.value()].first;
239 PERFETTO_DCHECK(slices->dur()[slice_idx] == kPendingDuration);
240 slices->mutable_dur()->Set(slice_idx, timestamp - slices->ts()[slice_idx]);
241
242 if (args_callback) {
243 ArgsTracker* tracker = &stack[stack_idx.value()].second;
244 auto bound_inserter = tracker->AddArgsTo(slices->id()[slice_idx]);
245 args_callback(&bound_inserter);
246 }
247
248 // If this slice is the top slice on the stack, pop it off.
249 if (*stack_idx == stack.size() - 1)
250 stack.pop_back();
251 return slices->id()[slice_idx];
252 }
253
254 // Returns the first incomplete slice in the stack with matching name and
255 // category. We assume null category/name matches everything. Returns
256 // nullopt if no matching slice is found.
MatchingIncompleteSliceIndex(const SlicesStack & stack,StringId name,StringId category)257 base::Optional<uint32_t> SliceTracker::MatchingIncompleteSliceIndex(
258 const SlicesStack& stack,
259 StringId name,
260 StringId category) {
261 auto* slices = context_->storage->mutable_slice_table();
262 for (int i = static_cast<int>(stack.size()) - 1; i >= 0; i--) {
263 uint32_t slice_idx = stack[static_cast<size_t>(i)].first;
264 if (slices->dur()[slice_idx] != kPendingDuration)
265 continue;
266 const StringId& other_category = slices->category()[slice_idx];
267 if (!category.is_null() &&
268 (other_category.is_null() || category != other_category))
269 continue;
270 const StringId& other_name = slices->name()[slice_idx];
271 if (!name.is_null() && !other_name.is_null() && name != other_name)
272 continue;
273 return static_cast<uint32_t>(i);
274 }
275 return base::nullopt;
276 }
277
FlushPendingSlices()278 void SliceTracker::FlushPendingSlices() {
279 // Clear the remaining stack entries. This ensures that any pending args are
280 // written to the storage. We don't close any slices with kPendingDuration so
281 // that the UI can still distinguish such "incomplete" slices.
282 //
283 // TODO(eseckler): Reconsider whether we want to close pending slices by
284 // setting their duration to |trace_end - event_start|. Might still want some
285 // additional way of flagging these events as "incomplete" to the UI.
286 stacks_.clear();
287 }
288
MaybeCloseStack(int64_t ts,SlicesStack * stack)289 void SliceTracker::MaybeCloseStack(int64_t ts, SlicesStack* stack) {
290 auto* slices = context_->storage->mutable_slice_table();
291 bool incomplete_descendent = false;
292 for (int i = static_cast<int>(stack->size()) - 1; i >= 0; i--) {
293 uint32_t slice_idx = (*stack)[static_cast<size_t>(i)].first;
294
295 int64_t start_ts = slices->ts()[slice_idx];
296 int64_t dur = slices->dur()[slice_idx];
297 int64_t end_ts = start_ts + dur;
298 if (dur == kPendingDuration) {
299 incomplete_descendent = true;
300 continue;
301 }
302
303 if (incomplete_descendent) {
304 PERFETTO_DCHECK(ts >= start_ts);
305
306 // Only process slices if the ts is past the end of the slice.
307 if (ts <= end_ts)
308 continue;
309
310 // This usually happens because we have two slices that are partially
311 // overlapping.
312 // [ slice 1 ]
313 // [ slice 2 ]
314 // This is invalid in chrome and should be fixed. Duration events should
315 // either be nested or disjoint, never partially intersecting.
316 PERFETTO_DLOG(
317 "Incorrect ordering of begin/end slice events around timestamp "
318 "%" PRId64,
319 ts);
320 context_->storage->IncrementStats(stats::misplaced_end_event);
321
322 // Every slice below this one should have a pending duration. Update
323 // of them to have the end ts of the current slice and pop them
324 // all off.
325 for (int j = static_cast<int>(stack->size()) - 1; j > i; --j) {
326 uint32_t child_idx = (*stack)[static_cast<size_t>(j)].first;
327 PERFETTO_DCHECK(slices->dur()[child_idx] == kPendingDuration);
328 slices->mutable_dur()->Set(child_idx, end_ts - slices->ts()[child_idx]);
329 stack->pop_back();
330 }
331
332 // Also pop the current row itself and reset the incomplete flag.
333 stack->pop_back();
334 incomplete_descendent = false;
335
336 continue;
337 }
338
339 if (end_ts <= ts) {
340 stack->pop_back();
341 }
342 }
343 }
344
GetStackHash(const SlicesStack & stack)345 int64_t SliceTracker::GetStackHash(const SlicesStack& stack) {
346 PERFETTO_DCHECK(!stack.empty());
347
348 const auto& slices = context_->storage->slice_table();
349
350 base::Hash hash;
351 for (size_t i = 0; i < stack.size(); i++) {
352 uint32_t slice_idx = stack[i].first;
353 hash.Update(slices.category()[slice_idx]);
354 hash.Update(slices.name()[slice_idx]);
355 }
356
357 // For clients which don't have an integer type (i.e. Javascript), returning
358 // hashes which have the top 11 bits set leads to numbers which are
359 // unrepresenatble. This means that clients cannot filter using this number as
360 // it will be meaningless when passed back to us. For this reason, make sure
361 // that the hash is always less than 2^53 - 1.
362 constexpr uint64_t kSafeBitmask = (1ull << 53) - 1;
363 return static_cast<int64_t>(hash.digest() & kSafeBitmask);
364 }
365
366 } // namespace trace_processor
367 } // namespace perfetto
368