/* * Copyright (C) 2019 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 "src/trace_processor/fuchsia_trace_tokenizer.h" #include #include #include "perfetto/base/logging.h" #include "perfetto/base/string_view.h" #include "src/trace_processor/ftrace_utils.h" #include "src/trace_processor/fuchsia_provider_view.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_sorter.h" namespace perfetto { namespace trace_processor { namespace { // Record types constexpr uint32_t kMetadata = 0; constexpr uint32_t kInitialization = 1; constexpr uint32_t kString = 2; constexpr uint32_t kThread = 3; constexpr uint32_t kEvent = 4; constexpr uint32_t kKernelObject = 7; constexpr uint32_t kContextSwitch = 8; // Metadata types constexpr uint32_t kProviderInfo = 1; constexpr uint32_t kProviderSection = 2; constexpr uint32_t kProviderEvent = 3; // Thread states constexpr uint32_t kThreadNew = 0; constexpr uint32_t kThreadRunning = 1; constexpr uint32_t kThreadSuspended = 2; constexpr uint32_t kThreadBlocked = 3; constexpr uint32_t kThreadDying = 4; constexpr uint32_t kThreadDead = 5; // Zircon object types constexpr uint32_t kZxObjTypeProcess = 1; constexpr uint32_t kZxObjTypeThread = 2; // Argument types constexpr uint32_t kArgKernelObject = 8; } // namespace FuchsiaTraceTokenizer::FuchsiaTraceTokenizer(TraceProcessorContext* context) : context_(context) { RegisterProvider(0, ""); } FuchsiaTraceTokenizer::~FuchsiaTraceTokenizer() = default; bool FuchsiaTraceTokenizer::Parse(std::unique_ptr data, size_t size) { // The relevant internal state is |leftover_bytes_|. Each call to Parse should // maintain the following properties, unless a fatal error occurs in which // case it should return false and no assumptions should be made about the // resulting internal state: // // 1) Every byte passed to |Parse| has either been passed to |ParseRecord| or // is present in |leftover_bytes_|, but not both. // 2) |leftover_bytes_| does not contain a complete record. // // Parse is responsible for creating the "full" |TraceBlobView|s, which own // the underlying data. Generally, there will be one such view. However, if // there is a record that started in an earlier call, then a new buffer is // created here to make the bytes in that record contiguous. // // Because some of the bytes in |data| might belong to the record starting in // |leftover_bytes_|, we track the offset at which the following record will // start. size_t byte_offset = 0; // Look for a record starting with the leftover bytes. if (leftover_bytes_.size() + size < 8) { // Even with the new bytes, we can't even read the header of the next // record, so just add the new bytes to |leftover_bytes_| and return. leftover_bytes_.insert(leftover_bytes_.end(), data.get() + byte_offset, data.get() + size); return true; } if (leftover_bytes_.size() > 0) { // There is a record starting from leftover bytes. if (leftover_bytes_.size() < 8) { // Header was previously incomplete, but we have enough now. // Copy bytes into |leftover_bytes_| so that the whole header is present, // and update |byte_offset| and |size| accordingly. size_t needed_bytes = 8 - leftover_bytes_.size(); leftover_bytes_.insert(leftover_bytes_.end(), data.get() + byte_offset, data.get() + needed_bytes); byte_offset += needed_bytes; size -= needed_bytes; } // Read the record length from the header. uint64_t header = *reinterpret_cast(leftover_bytes_.data()); uint32_t record_len_words = fuchsia_trace_utils::ReadField(header, 4, 15); uint32_t record_len_bytes = record_len_words * sizeof(uint64_t); // From property (2) above, leftover_bytes_ must have had less than a full // record to start with. We padded leftover_bytes_ out to read the header, // so it may now be a full record (in the case that the record consists of // only the header word), but it still cannot have any extra bytes. PERFETTO_DCHECK(leftover_bytes_.size() <= record_len_bytes); size_t missing_bytes = record_len_bytes - leftover_bytes_.size(); if (missing_bytes <= size) { // We have enough bytes to complete the partial record. Create a new // buffer for that record. std::unique_ptr buf(new uint8_t[record_len_bytes]); memcpy(&buf[0], leftover_bytes_.data(), leftover_bytes_.size()); memcpy(&buf[leftover_bytes_.size()], &data[byte_offset], missing_bytes); byte_offset += missing_bytes; size -= missing_bytes; leftover_bytes_.clear(); TraceBlobView leftover_record(std::move(buf), 0, record_len_bytes); ParseRecord(std::move(leftover_record)); } else { // There are not enough bytes for the full record. Add all the bytes we // have to leftover_bytes_ and wait for more. leftover_bytes_.insert(leftover_bytes_.end(), data.get() + byte_offset, data.get() + byte_offset + size); return true; } } TraceBlobView full_view(std::move(data), byte_offset, size); // |record_offset| is a number of bytes past |byte_offset| where the record // under consideration starts. As a result, it must always be in the range [0, // size-8]. Any larger offset means we don't have enough bytes for the header. size_t record_offset = 0; while (record_offset + 8 <= size) { uint64_t header = *reinterpret_cast(full_view.data() + record_offset); uint32_t record_len_bytes = fuchsia_trace_utils::ReadField(header, 4, 15) * sizeof(uint64_t); if (record_len_bytes == 0) { PERFETTO_DLOG("Unexpected record of size 0"); return false; } if (record_offset + record_len_bytes > size) break; TraceBlobView record = full_view.slice(byte_offset + record_offset, record_len_bytes); ParseRecord(std::move(record)); record_offset += record_len_bytes; } leftover_bytes_.insert(leftover_bytes_.end(), full_view.data() + record_offset, full_view.data() + size); return true; } // Most record types are read and recorded in |TraceStorage| here directly. // Event records are sorted by timestamp before processing, so instead of // recording them in |TraceStorage| they are given to |TraceSorter|. In order to // facilitate the parsing after sorting, a small view of the provider's string // and thread tables is passed alongside the record. See |FuchsiaProviderView|. void FuchsiaTraceTokenizer::ParseRecord(TraceBlobView tbv) { TraceStorage* storage = context_->storage.get(); ProcessTracker* procs = context_->process_tracker.get(); TraceSorter* sorter = context_->sorter.get(); const uint64_t* record = reinterpret_cast(tbv.data()); uint64_t header = *record; uint32_t record_type = fuchsia_trace_utils::ReadField(header, 0, 3); switch (record_type) { case kMetadata: { uint32_t metadata_type = fuchsia_trace_utils::ReadField(header, 16, 19); switch (metadata_type) { case kProviderInfo: { uint32_t provider_id = fuchsia_trace_utils::ReadField(header, 20, 51); uint32_t name_len = fuchsia_trace_utils::ReadField(header, 52, 59); std::string name(reinterpret_cast(&record[1]), name_len); RegisterProvider(provider_id, name); break; } case kProviderSection: { uint32_t provider_id = fuchsia_trace_utils::ReadField(header, 20, 51); current_provider_ = providers_[provider_id].get(); break; } case kProviderEvent: { // TODO(bhamrick): Handle buffer fill events PERFETTO_DLOG( "Ignoring provider event. Events may have been dropped"); break; } } break; } case kInitialization: { current_provider_->ticks_per_second = record[1]; break; } case kString: { uint32_t index = fuchsia_trace_utils::ReadField(header, 16, 30); if (index != 0) { uint32_t len = fuchsia_trace_utils::ReadField(header, 32, 46); base::StringView s(reinterpret_cast(&record[1]), len); StringId id = storage->InternString(s); current_provider_->string_table[index] = id; } break; } case kThread: { uint32_t index = fuchsia_trace_utils::ReadField(header, 16, 23); if (index != 0) { fuchsia_trace_utils::ThreadInfo tinfo; tinfo.pid = record[1]; tinfo.tid = record[2]; current_provider_->thread_table[index] = tinfo; } break; } case kEvent: { uint32_t thread_ref = fuchsia_trace_utils::ReadField(header, 24, 31); uint32_t cat_ref = fuchsia_trace_utils::ReadField(header, 32, 47); uint32_t name_ref = fuchsia_trace_utils::ReadField(header, 48, 63); // Build the minimal FuchsiaProviderView needed by // the record. This means the thread information if not inline, and any // non-inline strings (name, category for now, arg names and string values // in the future. const uint64_t* current = &record[1]; auto provider_view = std::unique_ptr(new FuchsiaProviderView()); provider_view->set_ticks_per_second(current_provider_->ticks_per_second); uint64_t ticks = *current++; int64_t ts = fuchsia_trace_utils::TicksToNs( ticks, current_provider_->ticks_per_second); if (fuchsia_trace_utils::IsInlineThread(thread_ref)) { // Skip over inline thread fuchsia_trace_utils::ReadInlineThread(¤t); } else { provider_view->InsertThread( thread_ref, current_provider_->thread_table[thread_ref]); } if (fuchsia_trace_utils::IsInlineString(cat_ref)) { // Skip over inline string fuchsia_trace_utils::ReadInlineString(¤t, cat_ref); } else { provider_view->InsertString(cat_ref, current_provider_->string_table[cat_ref]); } if (fuchsia_trace_utils::IsInlineString(name_ref)) { // Skip over inline string fuchsia_trace_utils::ReadInlineString(¤t, name_ref); } else { provider_view->InsertString(name_ref, current_provider_->string_table[name_ref]); } sorter->PushFuchsiaRecord(ts, std::move(tbv), std::move(provider_view)); break; } case kKernelObject: { uint32_t obj_type = fuchsia_trace_utils::ReadField(header, 16, 23); uint32_t name_ref = fuchsia_trace_utils::ReadField(header, 24, 39); const uint64_t* current = &record[1]; uint64_t obj_id = *current++; StringId name = StringId(); if (fuchsia_trace_utils::IsInlineString(name_ref)) { name = storage->InternString( fuchsia_trace_utils::ReadInlineString(¤t, name_ref)); } else { name = current_provider_->string_table[name_ref]; } switch (obj_type) { case kZxObjTypeProcess: { // Note: Fuchsia pid/tids are 64 bits but Perfetto's tables only // support 32 bits. This is usually not an issue except for // artificial koids which have the 2^63 bit set. This is used for // things such as virtual threads. procs->UpdateProcess(static_cast(obj_id), base::Optional(), base::StringView(storage->GetString(name))); break; } case kZxObjTypeThread: { uint32_t n_args = fuchsia_trace_utils::ReadField(header, 40, 43); uint64_t pid = 0; // Scan for a Kernel Object argument named "process" for (uint32_t i = 0; i < n_args; i++) { const uint64_t* arg_base = current; uint64_t arg_header = *current++; uint32_t arg_type = fuchsia_trace_utils::ReadField(arg_header, 0, 3); uint32_t arg_size = fuchsia_trace_utils::ReadField(arg_header, 4, 15); if (arg_type == kArgKernelObject) { uint32_t arg_name_ref = fuchsia_trace_utils::ReadField(arg_header, 16, 31); base::StringView arg_name; if (fuchsia_trace_utils::IsInlineString(arg_name_ref)) { arg_name = fuchsia_trace_utils::ReadInlineString(¤t, arg_name_ref); } else { arg_name = storage->GetString( current_provider_->string_table[arg_name_ref]); } if (arg_name == "process") { pid = *current++; } } current = arg_base + arg_size; } pid_table_[obj_id] = pid; UniqueTid utid = procs->UpdateThread(static_cast(obj_id), static_cast(pid)); storage->GetMutableThread(utid)->name_id = name; break; } default: { PERFETTO_DLOG("Skipping Kernel Object record with type %d", obj_type); break; } } break; } case kContextSwitch: { // Context switch records come in order, so they do not need to go through // TraceSorter. uint32_t cpu = fuchsia_trace_utils::ReadField(header, 16, 23); uint32_t outgoing_state = fuchsia_trace_utils::ReadField(header, 24, 27); uint32_t outgoing_thread_ref = fuchsia_trace_utils::ReadField(header, 28, 35); uint32_t incoming_thread_ref = fuchsia_trace_utils::ReadField(header, 36, 43); int32_t outgoing_priority = fuchsia_trace_utils::ReadField(header, 44, 51); uint64_t ticks = record[1]; int64_t ts = fuchsia_trace_utils::TicksToNs( ticks, current_provider_->ticks_per_second); const uint64_t* current = &record[2]; fuchsia_trace_utils::ThreadInfo outgoing_thread; if (fuchsia_trace_utils::IsInlineThread(outgoing_thread_ref)) { outgoing_thread = fuchsia_trace_utils::ReadInlineThread(¤t); } else { outgoing_thread = current_provider_->thread_table[outgoing_thread_ref]; } fuchsia_trace_utils::ThreadInfo incoming_thread; if (fuchsia_trace_utils::IsInlineThread(incoming_thread_ref)) { incoming_thread = fuchsia_trace_utils::ReadInlineThread(¤t); } else { incoming_thread = current_provider_->thread_table[incoming_thread_ref]; } // A thread with priority 0 represents an idle CPU if (cpu_threads_.count(cpu) != 0 && outgoing_priority != 0) { // TODO(bhamrick): Some early events will fail to associate with their // pid because the kernel object info event hasn't been processed yet. if (pid_table_.count(outgoing_thread.tid) > 0) { outgoing_thread.pid = pid_table_[outgoing_thread.tid]; } UniqueTid utid = procs->UpdateThread(static_cast(outgoing_thread.tid), static_cast(outgoing_thread.pid)); RunningThread previous_thread = cpu_threads_[cpu]; ftrace_utils::TaskState end_state; switch (outgoing_state) { case kThreadNew: case kThreadRunning: { end_state = ftrace_utils::TaskState(ftrace_utils::TaskState::kRunnable); break; } case kThreadBlocked: { end_state = ftrace_utils::TaskState( ftrace_utils::TaskState::kInterruptibleSleep); break; } case kThreadSuspended: { end_state = ftrace_utils::TaskState(ftrace_utils::TaskState::kStopped); break; } case kThreadDying: { end_state = ftrace_utils::TaskState(ftrace_utils::TaskState::kExitZombie); break; } case kThreadDead: { end_state = ftrace_utils::TaskState(ftrace_utils::TaskState::kExitDead); break; } default: { break; } } storage->mutable_slices()->AddSlice(cpu, previous_thread.start_ts, ts - previous_thread.start_ts, utid, end_state, outgoing_priority); } RunningThread new_running; new_running.info = incoming_thread; new_running.start_ts = ts; cpu_threads_[cpu] = new_running; break; } default: { PERFETTO_DLOG("Skipping record of unknown type %d", record_type); break; } } } void FuchsiaTraceTokenizer::RegisterProvider(uint32_t provider_id, std::string name) { std::unique_ptr provider(new ProviderInfo()); provider->name = name; current_provider_ = provider.get(); providers_[provider_id] = std::move(provider); } } // namespace trace_processor } // namespace perfetto