| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chromecast/tracing/ftrace.h" |
| |
| #include <fcntl.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include "base/files/file_util.h" |
| #include "base/strings/string_piece.h" |
| #include "base/strings/string_util.h" |
| #include "base/trace_event/common/trace_event_common.h" |
| #include "chromecast/tracing/system_tracing_common.h" |
| |
| namespace chromecast { |
| namespace tracing { |
| namespace { |
| |
| const char kPathTracefs[] = "/sys/kernel/tracing"; |
| const char kPathDebugfsTracing[] = "/sys/kernel/debug/tracing"; |
| const char kTraceFileTracingOn[] = "tracing_on"; |
| const char kTraceFileTraceMarker[] = "trace_marker"; |
| const char kTraceFileSetEvent[] = "set_event"; |
| const char kTraceFileTraceClock[] = "trace_clock"; |
| const char kTraceFileTrace[] = "trace"; |
| const char kTraceFileBufferSizeKb[] = "buffer_size_kb"; |
| |
| const char kBufferSizeKbRunning[] = "7040"; |
| const char kBufferSizeKbIdle[] = "1408"; |
| |
| // TODO(spang): Load these lists from a configuration file. |
| const char* const kGfxEvents[] = { |
| "i915:i915_flip_request", "i915:i915_flip_complete", |
| "i915:i915_gem_object_pwrite", "i915:intel_gpu_freq_change", |
| "exynos:exynos_flip_request", "exynos:exynos_flip_complete", |
| "exynos:exynos_page_flip_state", "drm:drm_vblank_event", |
| }; |
| |
| const char* const kInputEvents[] = { |
| "irq:irq_threaded_handler_entry", "irq:irq_threaded_handler_exit", |
| }; |
| |
| const char* const kIrqEvents[] = { |
| "irq:irq_handler_exit", "irq:irq_handler_entry", |
| }; |
| |
| const char* const kPowerEvents[] = { |
| "power:cpu_idle", |
| "power:cpu_frequency", |
| "mali:mali_dvfs_set_clock", |
| "mali:mali_dvfs_set_voltage", |
| "cpufreq_interactive:cpufreq_interactive_boost", |
| "cpufreq_interactive:cpufreq_interactive_unboost", |
| "exynos_busfreq:exynos_busfreq_target_int", |
| "exynos_busfreq:exynos_busfreq_target_mif", |
| }; |
| |
| const char* const kSchedEvents[] = { |
| "sched:sched_switch", "sched:sched_wakeup", |
| }; |
| |
| const char* const kWorkqEvents[] = { |
| "workqueue:workqueue_execute_start", "workqueue:workqueue_execute_end", |
| }; |
| |
| void AddCategoryEvents(const std::string& category, |
| std::vector<std::string>* events) { |
| if (category == "gfx") { |
| std::copy(kGfxEvents, kGfxEvents + arraysize(kGfxEvents), |
| std::back_inserter(*events)); |
| return; |
| } |
| if (category == "input") { |
| std::copy(kInputEvents, kInputEvents + arraysize(kInputEvents), |
| std::back_inserter(*events)); |
| return; |
| } |
| if (category == TRACE_DISABLED_BY_DEFAULT("irq")) { |
| std::copy(kIrqEvents, kIrqEvents + arraysize(kIrqEvents), |
| std::back_inserter(*events)); |
| return; |
| } |
| if (category == "power") { |
| std::copy(kPowerEvents, kPowerEvents + arraysize(kPowerEvents), |
| std::back_inserter(*events)); |
| return; |
| } |
| if (category == "sched") { |
| std::copy(kSchedEvents, kSchedEvents + arraysize(kSchedEvents), |
| std::back_inserter(*events)); |
| return; |
| } |
| if (category == "workq") { |
| std::copy(kWorkqEvents, kWorkqEvents + arraysize(kWorkqEvents), |
| std::back_inserter(*events)); |
| return; |
| } |
| |
| LOG(WARNING) << "Unrecognized category: " << category; |
| } |
| |
| bool WriteTracingFile(const char* tracing_dir, |
| const char* trace_file, |
| base::StringPiece contents) { |
| base::FilePath path = base::FilePath(tracing_dir).Append(trace_file); |
| |
| if (!base::WriteFile(path, contents.data(), contents.size())) { |
| PLOG(ERROR) << "write: " << path; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool EnableTraceEvent(const char* tracing_dir, base::StringPiece event) { |
| base::FilePath path = base::FilePath(tracing_dir).Append(kTraceFileSetEvent); |
| |
| // Enabling events returns EINVAL if the event does not exist. It is normal |
| // for driver specific events to be missing when the driver is not built in. |
| if (!base::AppendToFile(path, event.data(), event.size()) && |
| errno != EINVAL) { |
| PLOG(ERROR) << "write: " << path; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| const char* FindTracingDir() { |
| base::FilePath path = base::FilePath(kPathTracefs).Append(kTraceFileSetEvent); |
| if (!access(path.value().c_str(), W_OK)) |
| return kPathTracefs; |
| return kPathDebugfsTracing; |
| } |
| |
| } // namespace |
| |
| bool IsValidCategory(base::StringPiece str) { |
| for (size_t i = 0; i < kCategoryCount; ++i) { |
| base::StringPiece category(kCategories[i]); |
| if (category == str) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool StartFtrace(const std::vector<std::string>& categories) { |
| if (categories.size() == 0) { |
| LOG(ERROR) << "No categories to enable"; |
| return false; |
| } |
| |
| std::vector<std::string> events; |
| for (const auto& category : categories) |
| AddCategoryEvents(category, &events); |
| |
| if (events.size() == 0) { |
| LOG(ERROR) << "No events to enable"; |
| return false; |
| } |
| |
| const char* tracing_dir = FindTracingDir(); |
| |
| // Disable tracing and clear events. |
| if (!WriteTracingFile(tracing_dir, kTraceFileTracingOn, "0")) |
| return false; |
| if (!WriteTracingFile(tracing_dir, kTraceFileSetEvent, "\n")) |
| return false; |
| |
| // Use CLOCK_MONOTONIC so that kernel timestamps align with std::steady_clock |
| // and base::TimeTicks. |
| if (!WriteTracingFile(tracing_dir, kTraceFileTraceClock, "mono")) |
| return false; |
| |
| for (const auto& event : events) |
| EnableTraceEvent(tracing_dir, event); |
| |
| if (!WriteTracingFile(tracing_dir, kTraceFileBufferSizeKb, |
| kBufferSizeKbRunning)) |
| return false; |
| if (!WriteTracingFile(tracing_dir, kTraceFileTracingOn, "1")) |
| return false; |
| |
| return true; |
| } |
| |
| bool WriteFtraceTimeSyncMarker() { |
| return WriteTracingFile(FindTracingDir(), kTraceFileTraceMarker, |
| "trace_event_clock_sync: parent_ts=0"); |
| } |
| |
| bool StopFtrace() { |
| if (!WriteTracingFile(FindTracingDir(), kTraceFileTracingOn, "0")) |
| return false; |
| return true; |
| } |
| |
| base::ScopedFD GetFtraceData() { |
| base::FilePath path = |
| base::FilePath(FindTracingDir()).Append(kTraceFileTrace); |
| base::ScopedFD trace_data(HANDLE_EINTR( |
| open(path.value().c_str(), O_RDONLY | O_CLOEXEC | O_NONBLOCK))); |
| if (!trace_data.is_valid()) |
| PLOG(ERROR) << "open: " << path.value(); |
| return trace_data; |
| } |
| |
| bool ClearFtrace() { |
| const char* tracing_dir = FindTracingDir(); |
| if (!WriteTracingFile(tracing_dir, kTraceFileBufferSizeKb, kBufferSizeKbIdle)) |
| return false; |
| if (!WriteTracingFile(tracing_dir, kTraceFileTrace, "0")) |
| return false; |
| return true; |
| } |
| |
| } // namespace tracing |
| } // namespace chromecast |