blob: 33177872ea61efd062553686c4ef5e9da4ea11ea [file] [log] [blame]
/*
* Copyright (C) 2018 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.
*/
#include <limits>
#include <stdint.h>
#include "src/trace_processor/args_tracker.h"
#include "src/trace_processor/process_tracker.h"
#include "src/trace_processor/slice_tracker.h"
#include "src/trace_processor/trace_processor_context.h"
#include "src/trace_processor/trace_storage.h"
#include "src/trace_processor/track_tracker.h"
namespace perfetto {
namespace trace_processor {
namespace {
// Slices which have been opened but haven't been closed yet will be marked
// with this duration placeholder.
constexpr int64_t kPendingDuration = -1;
} // namespace
SliceTracker::SliceTracker(TraceProcessorContext* context)
: context_(context) {}
SliceTracker::~SliceTracker() = default;
base::Optional<uint32_t> SliceTracker::BeginAndroid(int64_t timestamp,
uint32_t ftrace_tid,
uint32_t atrace_tgid,
StringId category,
StringId name) {
UniqueTid utid =
context_->process_tracker->UpdateThread(ftrace_tid, atrace_tgid);
ftrace_to_atrace_tgid_[ftrace_tid] = atrace_tgid;
TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
return Begin(timestamp, track_id, utid, RefType::kRefUtid, category, name);
}
base::Optional<uint32_t> SliceTracker::Begin(int64_t timestamp,
TrackId track_id,
int64_t ref,
RefType ref_type,
StringId category,
StringId name,
SetArgsCallback args_callback) {
// At this stage all events should be globally timestamp ordered.
if (timestamp < prev_timestamp_) {
context_->storage->IncrementStats(stats::slice_out_of_order);
return base::nullopt;
}
prev_timestamp_ = timestamp;
MaybeCloseStack(timestamp, &stacks_[track_id]);
return StartSlice(timestamp, kPendingDuration, track_id, ref, ref_type,
category, name, args_callback);
}
base::Optional<uint32_t> SliceTracker::Scoped(int64_t timestamp,
TrackId track_id,
int64_t ref,
RefType ref_type,
StringId category,
StringId name,
int64_t duration,
SetArgsCallback args_callback) {
// At this stage all events should be globally timestamp ordered.
if (timestamp < prev_timestamp_) {
context_->storage->IncrementStats(stats::slice_out_of_order);
return base::nullopt;
}
prev_timestamp_ = timestamp;
PERFETTO_DCHECK(duration >= 0);
MaybeCloseStack(timestamp, &stacks_[track_id]);
return StartSlice(timestamp, duration, track_id, ref, ref_type, category,
name, args_callback);
}
base::Optional<uint32_t> SliceTracker::StartSlice(
int64_t timestamp,
int64_t duration,
TrackId track_id,
int64_t ref,
RefType ref_type,
StringId category,
StringId name,
SetArgsCallback args_callback) {
auto* stack = &stacks_[track_id];
auto* slices = context_->storage->mutable_nestable_slices();
const uint8_t depth = static_cast<uint8_t>(stack->size());
if (depth >= std::numeric_limits<uint8_t>::max()) {
PERFETTO_DFATAL("Slices with too large depth found.");
return base::nullopt;
}
int64_t parent_stack_id =
depth == 0 ? 0 : slices->stack_ids()[stack->back().first];
uint32_t slice_idx =
slices->AddSlice(timestamp, duration, track_id, ref, ref_type, category,
name, depth, 0, parent_stack_id);
stack->emplace_back(std::make_pair(slice_idx, ArgsTracker(context_)));
if (args_callback) {
args_callback(
&stack->back().second,
TraceStorage::CreateRowId(TableId::kNestableSlices, slice_idx));
}
slices->set_stack_id(slice_idx, GetStackHash(*stack));
return slice_idx;
}
base::Optional<uint32_t> SliceTracker::EndAndroid(int64_t timestamp,
uint32_t ftrace_tid,
uint32_t atrace_tgid) {
auto actual_tgid_it = ftrace_to_atrace_tgid_.find(ftrace_tid);
if (actual_tgid_it == ftrace_to_atrace_tgid_.end()) {
// This is possible if we start tracing after a begin slice.
PERFETTO_DLOG("Unknown tgid for ftrace tid %u", ftrace_tid);
return base::nullopt;
}
uint32_t actual_tgid = actual_tgid_it->second;
// atrace_tgid can be 0 in older android versions where the end event would
// not contain the value.
if (atrace_tgid != 0 && atrace_tgid != actual_tgid) {
PERFETTO_DLOG("Mismatched atrace pid %u and looked up pid %u", atrace_tgid,
actual_tgid);
context_->storage->IncrementStats(stats::atrace_tgid_mismatch);
}
UniqueTid utid =
context_->process_tracker->UpdateThread(ftrace_tid, actual_tgid);
TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
return End(timestamp, track_id);
}
// Returns the first incomplete slice in the stack with matching name and
// category. We assume null category/name matches everything. Returns
// nullopt if no matching slice is found.
base::Optional<size_t> SliceTracker::MatchingIncompleteSliceIndex(
SlicesStack& stack,
StringId name,
StringId category) {
auto* slices = context_->storage->mutable_nestable_slices();
for (int i = static_cast<int>(stack.size()) - 1; i >= 0; i--) {
uint32_t slice_idx = stack[static_cast<size_t>(i)].first;
if (slices->durations()[slice_idx] != kPendingDuration)
continue;
const StringId& other_category = slices->categories()[slice_idx];
if (!category.is_null() && !other_category.is_null() &&
category != other_category)
continue;
const StringId& other_name = slices->names()[slice_idx];
if (!name.is_null() && !other_name.is_null() && name != other_name)
continue;
return static_cast<size_t>(i);
}
return base::nullopt;
}
base::Optional<uint32_t> SliceTracker::End(int64_t timestamp,
TrackId track_id,
StringId category,
StringId name,
SetArgsCallback args_callback) {
// At this stage all events should be globally timestamp ordered.
if (timestamp < prev_timestamp_) {
context_->storage->IncrementStats(stats::slice_out_of_order);
return base::nullopt;
}
prev_timestamp_ = timestamp;
MaybeCloseStack(timestamp, &stacks_[track_id]);
auto& stack = stacks_[track_id];
if (stack.empty())
return base::nullopt;
auto* slices = context_->storage->mutable_nestable_slices();
base::Optional<size_t> stack_idx =
MatchingIncompleteSliceIndex(stack, name, category);
// If we are trying to close slices that are not open on the stack (e.g.,
// slices that began before tracing started), bail out.
if (!stack_idx)
return base::nullopt;
if (*stack_idx != stack.size() - 1) {
// This usually happens because we have two slices that are partially
// overlapping.
// [ slice 1 ]
// [ slice 2 ]
// This is invalid in chrome and should be fixed. Duration events should
// either be nested or disjoint, never partially intersecting.
PERFETTO_DLOG(
"Incorrect ordering of End slice event around timestamp "
"%" PRId64,
timestamp);
context_->storage->IncrementStats(stats::misplaced_end_event);
}
uint32_t slice_idx = stack[stack_idx.value()].first;
PERFETTO_DCHECK(slices->durations()[slice_idx] == kPendingDuration);
slices->set_duration(slice_idx, timestamp - slices->start_ns()[slice_idx]);
if (args_callback) {
args_callback(
&stack.back().second,
TraceStorage::CreateRowId(TableId::kNestableSlices, slice_idx));
}
return CompleteSlice(track_id);
// TODO(primiano): auto-close B slices left open at the end.
}
void SliceTracker::FlushPendingSlices() {
// Clear the remaining stack entries. This ensures that any pending args are
// written to the storage. We don't close any slices with kPendingDuration so
// that the UI can still distinguish such "incomplete" slices.
//
// TODO(eseckler): Reconsider whether we want to close pending slices by
// setting their duration to |trace_end - event_start|. Might still want some
// additional way of flagging these events as "incomplete" to the UI.
stacks_.clear();
}
base::Optional<uint32_t> SliceTracker::CompleteSlice(TrackId track_id) {
auto& stack = stacks_[track_id];
uint32_t slice_idx = stack.back().first;
stack.pop_back();
return slice_idx;
}
void SliceTracker::MaybeCloseStack(int64_t ts, SlicesStack* stack) {
const auto& slices = context_->storage->nestable_slices();
bool pending_dur_descendent = false;
for (int i = static_cast<int>(stack->size()) - 1; i >= 0; i--) {
uint32_t slice_idx = (*stack)[static_cast<size_t>(i)].first;
int64_t start_ts = slices.start_ns()[slice_idx];
int64_t dur = slices.durations()[slice_idx];
int64_t end_ts = start_ts + dur;
if (dur == kPendingDuration) {
pending_dur_descendent = true;
}
if (pending_dur_descendent) {
PERFETTO_DCHECK(ts >= start_ts);
// Some trace producers emit END events in the wrong order (even after
// sorting by timestamp), e.g. BEGIN A, BEGIN B, END A, END B. We discard
// the mismatching END A in End(). Because of this, we can end up in a
// situation where we attempt to close the stack on top of A at a
// timestamp beyond A's parent. To avoid crashing in such a case, we just
// emit a warning instead.
if (dur != kPendingDuration && ts > end_ts) {
PERFETTO_DLOG(
"Incorrect ordering of begin/end slice events around timestamp "
"%" PRId64,
ts);
}
continue;
}
if (end_ts <= ts) {
stack->pop_back();
}
}
}
int64_t SliceTracker::GetStackHash(const SlicesStack& stack) {
PERFETTO_DCHECK(!stack.empty());
const auto& slices = context_->storage->nestable_slices();
std::string s;
s.reserve(stack.size() * sizeof(uint64_t) * 2);
for (size_t i = 0; i < stack.size(); i++) {
uint32_t slice_idx = stack[i].first;
s.append(reinterpret_cast<const char*>(&slices.categories()[slice_idx]),
sizeof(slices.categories()[slice_idx]));
s.append(reinterpret_cast<const char*>(&slices.names()[slice_idx]),
sizeof(slices.names()[slice_idx]));
}
constexpr uint64_t kMask = uint64_t(-1) >> 1;
return static_cast<int64_t>((std::hash<std::string>{}(s)) & kMask);
}
} // namespace trace_processor
} // namespace perfetto