blob: 039690c2f4e78df1c4c837c43d2e3883c00b47d2 [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// 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 <string_view>
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/ranges/algorithm.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") {
base::ranges::copy(kGfxEvents, std::back_inserter(*events));
return;
}
if (category == "input") {
base::ranges::copy(kInputEvents, std::back_inserter(*events));
return;
}
if (category == TRACE_DISABLED_BY_DEFAULT("irq")) {
base::ranges::copy(kIrqEvents, std::back_inserter(*events));
return;
}
if (category == "power") {
base::ranges::copy(kPowerEvents, std::back_inserter(*events));
return;
}
if (category == "sched") {
base::ranges::copy(kSchedEvents, std::back_inserter(*events));
return;
}
if (category == "workq") {
base::ranges::copy(kWorkqEvents, std::back_inserter(*events));
return;
}
LOG(WARNING) << "Unrecognized category: " << category;
}
bool WriteTracingFile(const char* tracing_dir,
const char* trace_file,
std::string_view contents) {
base::FilePath path = base::FilePath(tracing_dir).Append(trace_file);
if (!base::WriteFile(path, contents)) {
PLOG(ERROR) << "write: " << path;
return false;
}
return true;
}
bool EnableTraceEvent(const char* tracing_dir, std::string_view 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) && 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(std::string_view str) {
for (std::string_view category : kCategories) {
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