| // Copyright 2024 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifdef UNSAFE_BUFFERS_BUILD |
| // TODO(crbug.com/40285824): Remove this and convert code to safer constructs. |
| #pragma allow_unsafe_buffers |
| #endif |
| |
| #include "components/tracing/common/etw_system_data_source_win.h" |
| |
| #include <windows.h> |
| |
| #include <evntcons.h> |
| #include <evntrace.h> |
| |
| #include <utility> |
| |
| #include "base/check.h" |
| #include "base/check_op.h" |
| #include "base/containers/buffer_iterator.h" |
| #include "base/containers/span.h" |
| #include "base/logging.h" |
| #include "base/task/thread_pool.h" |
| #include "base/win/event_trace_consumer.h" |
| #include "third_party/perfetto/include/perfetto/tracing/core/data_source_descriptor.h" |
| #include "third_party/perfetto/protos/perfetto/config/data_source_config.gen.h" |
| #include "third_party/perfetto/protos/perfetto/config/etw/etw_config.gen.h" |
| #include "third_party/perfetto/protos/perfetto/trace/etw/etw.pbzero.h" |
| #include "third_party/perfetto/protos/perfetto/trace/etw/etw_event.pbzero.h" |
| #include "third_party/perfetto/protos/perfetto/trace/etw/etw_event_bundle.pbzero.h" |
| |
| namespace tracing { |
| |
| namespace { |
| |
| constexpr wchar_t kEtwSystemSessionName[] = L"org.chromium.etw_system"; |
| constexpr uint8_t kCSwitchEventOpcode = 36; |
| |
| ULONG EtwSystemFlagsFromEnum( |
| perfetto::protos::gen::EtwConfig::KernelFlag flag) { |
| switch (flag) { |
| case perfetto::protos::gen::EtwConfig::CSWITCH: |
| return EVENT_TRACE_FLAG_CSWITCH; |
| case perfetto::protos::gen::EtwConfig::DISPATCHER: |
| return EVENT_TRACE_FLAG_DISPATCHER; |
| } |
| } |
| |
| } // namespace |
| |
| class EtwSystemDataSource::Consumer |
| : public base::win::EtwTraceConsumerBase<Consumer> { |
| public: |
| static constexpr bool kEnableRecordMode = true; |
| static constexpr bool kRawTimestamp = true; |
| |
| explicit Consumer(std::unique_ptr<perfetto::TraceWriterBase>); |
| ~Consumer(); |
| |
| static void ProcessEventRecord(EVENT_RECORD* event_record); |
| static bool ProcessBuffer(EVENT_TRACE_LOGFILE* buffer); |
| |
| void ConsumeEvents(); |
| |
| private: |
| void ProcessEventRecordImpl(const EVENT_HEADER* header, |
| const ETW_BUFFER_CONTEXT* buffer_context, |
| base::span<uint8_t> packet_data) |
| VALID_CONTEXT_REQUIRED(sequence_checker_); |
| |
| std::unique_ptr<perfetto::TraceWriterBase> trace_writer_ |
| GUARDED_BY_CONTEXT(sequence_checker_); |
| perfetto::TraceWriter::TracePacketHandle packet_handle_ |
| GUARDED_BY_CONTEXT(sequence_checker_); |
| raw_ptr<perfetto::protos::pbzero::EtwTraceEventBundle> etw_events_ |
| GUARDED_BY_CONTEXT(sequence_checker_); |
| |
| SEQUENCE_CHECKER(sequence_checker_); |
| }; |
| |
| void EtwSystemDataSource::Register() { |
| perfetto::DataSourceDescriptor desc; |
| desc.set_name("org.chromium.etw_system"); |
| perfetto::DataSource<EtwSystemDataSource>::Register(desc); |
| } |
| |
| EtwSystemDataSource::EtwSystemDataSource() |
| : consume_task_runner_(base::ThreadPool::CreateSequencedTaskRunner( |
| {base::MayBlock(), |
| base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN})), |
| consumer_{nullptr, base::OnTaskRunnerDeleter(nullptr)} { |
| DETACH_FROM_SEQUENCE(sequence_checker_); |
| } |
| |
| EtwSystemDataSource::~EtwSystemDataSource() = default; |
| |
| void EtwSystemDataSource::OnSetup(const SetupArgs& args) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| data_source_config_ = *args.config; |
| } |
| |
| void EtwSystemDataSource::OnStart(const StartArgs&) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (data_source_config_.etw_config_raw().empty()) { |
| DLOG(ERROR) << "Skipping empty etw_config"; |
| return; |
| } |
| perfetto::protos::gen::EtwConfig etw_config; |
| if (!etw_config.ParseFromString(data_source_config_.etw_config_raw())) { |
| DLOG(ERROR) << "Failed to parse etw_config"; |
| return; |
| } |
| |
| base::win::EtwTraceProperties ignore; |
| HRESULT hr = |
| base::win::EtwTraceController::Stop(kEtwSystemSessionName, &ignore); |
| if (FAILED(hr) && hr != HRESULT_FROM_WIN32(ERROR_WMI_INSTANCE_NOT_FOUND)) { |
| DLOG(ERROR) << "Failed to stop previous trace session: 0x" << std::hex |
| << hr; |
| return; |
| } |
| |
| // MD5 hash of "org.chromium.etw_system". |
| // 696e5ace-ff06-bfc6-4e63-0198f5b3bc99 |
| static constexpr GUID kChromeEtwSystemGuid = { |
| 0x696e5ace, |
| 0xff06, |
| 0xbfc6, |
| {0x4e, 0x63, 0x01, 0x98, 0xf5, 0xb3, 0xbc, 0x99}}; |
| |
| base::win::EtwTraceProperties prop; |
| EVENT_TRACE_PROPERTIES& p = *prop.get(); |
| |
| // QPC timer accuracy. |
| // https://learn.microsoft.com/en-us/windows/win32/etw/wnode-header |
| p.Wnode.ClientContext = 1; |
| // Windows 8 and later supports SystemTraceProvider in multiple |
| // private session that's not NT Kernel Logger. |
| // https://learn.microsoft.com/en-us/windows/win32/etw/configuring-and-starting-a-systemtraceprovider-session#enable-a-systemtraceprovider-session |
| prop.SetLoggerName(kEtwSystemSessionName); |
| p.Wnode.Guid = kChromeEtwSystemGuid; |
| p.LogFileMode = EVENT_TRACE_REAL_TIME_MODE | EVENT_TRACE_SYSTEM_LOGGER_MODE; |
| p.MinimumBuffers = 16; |
| p.BufferSize = 16; |
| p.FlushTimer = 1; // flush every second. |
| |
| for (auto flag : etw_config.kernel_flags()) { |
| p.EnableFlags |= EtwSystemFlagsFromEnum(flag); |
| } |
| |
| hr = etw_controller_.Start(kEtwSystemSessionName, &prop); |
| if (FAILED(hr)) { |
| DLOG(ERROR) << "Failed to start system trace session: 0x" << std::hex << hr; |
| return; |
| } |
| |
| consumer_ = {new Consumer(CreateTraceWriter()), |
| base::OnTaskRunnerDeleter(consume_task_runner_)}; |
| hr = consumer_->OpenRealtimeSession(kEtwSystemSessionName); |
| if (FAILED(hr)) { |
| etw_controller_.Stop(nullptr); |
| DLOG(ERROR) << "Failed to open system trace session: 0x" << std::hex << hr; |
| return; |
| } |
| consume_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&EtwSystemDataSource::Consumer::ConsumeEvents, |
| base::Unretained(consumer_.get()))); |
| } |
| |
| void EtwSystemDataSource::OnStop(const StopArgs&) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| etw_controller_.Stop(nullptr); |
| consumer_.reset(); |
| } |
| |
| EtwSystemDataSource::Consumer::Consumer( |
| std::unique_ptr<perfetto::TraceWriterBase> trace_writer) |
| : trace_writer_(std::move(trace_writer)) { |
| DETACH_FROM_SEQUENCE(sequence_checker_); |
| } |
| |
| // Decodes a CSwitch Event. |
| // https://learn.microsoft.com/en-us/windows/win32/etw/cswitch |
| // Returns true on success, or false if `packet_data` is invalid. |
| bool EtwSystemDataSource::DecodeCSwitchEvent( |
| base::span<uint8_t> packet_data, |
| perfetto::protos::pbzero::EtwTraceEvent& event) { |
| // Size of CSwitch v2 in bytes. |
| static constexpr size_t kMinimumCSwitchLength = 24; |
| if (packet_data.size() < kMinimumCSwitchLength) { |
| return false; |
| } |
| base::BufferIterator<uint8_t> iterator{packet_data}; |
| auto* c_switch = event.set_c_switch(); |
| c_switch->set_new_thread_id(*iterator.Object<uint32_t>()); |
| c_switch->set_old_thread_id(*iterator.Object<uint32_t>()); |
| c_switch->set_new_thread_priority(*iterator.Object<int8_t>()); |
| c_switch->set_old_thread_priority(*iterator.Object<int8_t>()); |
| c_switch->set_previous_c_state(*iterator.Object<uint8_t>()); |
| |
| // SpareByte |
| std::ignore = iterator.Object<uint8_t>(); |
| |
| const int8_t* old_thread_wait_reason = iterator.Object<int8_t>(); |
| if (!(*old_thread_wait_reason >= 0 && |
| *old_thread_wait_reason < |
| perfetto::protos::pbzero::CSwitchEtwEvent::MAXIMUM_WAIT_REASON)) { |
| return false; |
| } |
| c_switch->set_old_thread_wait_reason( |
| static_cast< |
| perfetto::protos::pbzero::CSwitchEtwEvent::OldThreadWaitReason>( |
| *old_thread_wait_reason)); |
| |
| const int8_t* old_thread_wait_mode = iterator.Object<int8_t>(); |
| if (!(*old_thread_wait_mode >= 0 && |
| *old_thread_wait_mode <= |
| perfetto::protos::pbzero::CSwitchEtwEvent::USER_MODE)) { |
| return false; |
| } |
| c_switch->set_old_thread_wait_mode( |
| static_cast<perfetto::protos::pbzero::CSwitchEtwEvent::OldThreadWaitMode>( |
| *old_thread_wait_mode)); |
| |
| const int8_t* old_thread_state = iterator.Object<int8_t>(); |
| if (!(*old_thread_state >= 0 && |
| *old_thread_state <= |
| perfetto::protos::pbzero::CSwitchEtwEvent::DEFERRED_READY)) { |
| return false; |
| } |
| c_switch->set_old_thread_state( |
| static_cast<perfetto::protos::pbzero::CSwitchEtwEvent::OldThreadState>( |
| *old_thread_state)); |
| |
| c_switch->set_old_thread_wait_ideal_processor(*iterator.Object<int8_t>()); |
| c_switch->set_new_thread_wait_time(*iterator.Object<uint32_t>()); |
| return true; |
| } |
| |
| EtwSystemDataSource::Consumer::~Consumer() = default; |
| |
| void EtwSystemDataSource::Consumer::ConsumeEvents() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| base::ScopedBlockingCall scoped_blocking(FROM_HERE, |
| base::BlockingType::MAY_BLOCK); |
| Consume(); |
| } |
| |
| void EtwSystemDataSource::Consumer::ProcessEventRecord( |
| EVENT_RECORD* event_record) { |
| // ThreadGuid, 3d6fa8d1-fe05-11d0-9dda-00c04fd7ba7c |
| // https://learn.microsoft.com/en-us/windows/win32/etw/nt-kernel-logger-constants |
| static constexpr GUID kThreadGuid = { |
| 0x3d6fa8d1, |
| 0xfe05, |
| 0x11d0, |
| {0x9d, 0xda, 0x00, 0xc0, 0x4f, 0xd7, 0xba, 0x7c}}; |
| |
| Consumer* self = reinterpret_cast<Consumer*>(event_record->UserContext); |
| DCHECK_CALLED_ON_VALID_SEQUENCE(self->sequence_checker_); |
| |
| if (IsEqualGUID(event_record->EventHeader.ProviderId, kThreadGuid)) { |
| self->ProcessEventRecordImpl(&event_record->EventHeader, |
| &event_record->BufferContext, |
| {static_cast<uint8_t*>(event_record->UserData), |
| event_record->UserDataLength}); |
| } |
| } |
| |
| void EtwSystemDataSource::Consumer::ProcessEventRecordImpl( |
| const EVENT_HEADER* header, |
| const ETW_BUFFER_CONTEXT* buffer_context, |
| base::span<uint8_t> packet_data) { |
| static const int64_t qpc_ticks_per_second = []() { |
| LARGE_INTEGER perf_counter_frequency = {}; |
| ::QueryPerformanceFrequency(&perf_counter_frequency); |
| CHECK_GT(perf_counter_frequency.QuadPart, 0); |
| return perf_counter_frequency.QuadPart; |
| }(); |
| |
| uint64_t now = |
| static_cast<uint64_t>(base::Time::kNanosecondsPerSecond * |
| static_cast<double>(header->TimeStamp.QuadPart) / |
| static_cast<double>(qpc_ticks_per_second)); |
| if (!etw_events_) { |
| // Resetting the `packet_handle_` finalizes previous data. |
| packet_handle_ = trace_writer_->NewTracePacket(); |
| packet_handle_->set_timestamp(now); |
| etw_events_ = packet_handle_->set_etw_events(); |
| } |
| |
| auto* event = etw_events_->add_event(); |
| event->set_timestamp(now); |
| event->set_cpu(buffer_context->ProcessorIndex); |
| if (header->EventDescriptor.Opcode == kCSwitchEventOpcode) { |
| bool result = DecodeCSwitchEvent(packet_data, *event); |
| if (!result) { |
| DLOG(ERROR) << "Error decoding CSwitch Event"; |
| } |
| } |
| } |
| |
| bool EtwSystemDataSource::Consumer::ProcessBuffer(EVENT_TRACE_LOGFILE* buffer) { |
| Consumer* self = reinterpret_cast<Consumer*>(buffer->Context); |
| DCHECK_CALLED_ON_VALID_SEQUENCE(self->sequence_checker_); |
| self->etw_events_ = nullptr; |
| self->packet_handle_ = {}; |
| return true; |
| } |
| |
| } // namespace tracing |
| |
| PERFETTO_DEFINE_DATA_SOURCE_STATIC_MEMBERS_WITH_ATTRS( |
| BASE_EXPORT, |
| tracing::EtwSystemDataSource); |
| |
| // This should go after PERFETTO_DEFINE_DATA_SOURCE_STATIC_MEMBERS_WITH_ATTRS |
| // to avoid instantiation of type() template method before specialization. |
| std::unique_ptr<perfetto::TraceWriterBase> |
| tracing::EtwSystemDataSource::CreateTraceWriter() { |
| perfetto::internal::DataSourceStaticState* static_state = |
| perfetto::DataSourceHelper<EtwSystemDataSource>::type().static_state(); |
| // EtwSystemDataSource disallows multiple instances, so our instance will |
| // always have index 0. |
| perfetto::internal::DataSourceState* instance_state = static_state->TryGet(0); |
| CHECK(instance_state); |
| return perfetto::internal::TracingMuxer::Get()->CreateTraceWriter( |
| static_state, data_source_config_.target_buffer(), instance_state, |
| perfetto::BufferExhaustedPolicy::kDrop); |
| } |