/* * 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 "src/traced/probes/sys_stats/sys_stats_data_source.h" #include #include #include #include #include #include #include #include "perfetto/base/task_runner.h" #include "perfetto/base/time.h" #include "perfetto/ext/base/file_utils.h" #include "perfetto/ext/base/metatrace.h" #include "perfetto/ext/base/scoped_file.h" #include "perfetto/ext/base/string_splitter.h" #include "perfetto/ext/base/utils.h" #include "perfetto/ext/traced/sys_stats_counters.h" #include "protos/perfetto/common/sys_stats_counters.pbzero.h" #include "protos/perfetto/config/sys_stats/sys_stats_config.pbzero.h" #include "protos/perfetto/trace/sys_stats/sys_stats.pbzero.h" #include "protos/perfetto/trace/trace_packet.pbzero.h" namespace perfetto { using protos::pbzero::SysStatsConfig; namespace { constexpr size_t kReadBufSize = 1024 * 16; base::ScopedFile OpenReadOnly(const char* path) { base::ScopedFile fd(base::OpenFile(path, O_RDONLY)); if (!fd) PERFETTO_PLOG("Failed opening %s", path); return fd; } uint32_t ClampTo10Ms(uint32_t period_ms, const char* counter_name) { if (period_ms > 0 && period_ms < 10) { PERFETTO_ILOG("%s %" PRIu32 " is less than minimum of 10ms. Increasing to 10ms.", counter_name, period_ms); return 10; } return period_ms; } } // namespace // static const ProbesDataSource::Descriptor SysStatsDataSource::descriptor = { /*name*/ "linux.sys_stats", /*flags*/ Descriptor::kFlagsNone, }; SysStatsDataSource::SysStatsDataSource(base::TaskRunner* task_runner, TracingSessionID session_id, std::unique_ptr writer, const DataSourceConfig& ds_config, OpenFunction open_fn) : ProbesDataSource(session_id, &descriptor), task_runner_(task_runner), writer_(std::move(writer)), weak_factory_(this) { ns_per_user_hz_ = 1000000000ull / static_cast(sysconf(_SC_CLK_TCK)); open_fn = open_fn ? open_fn : OpenReadOnly; meminfo_fd_ = open_fn("/proc/meminfo"); vmstat_fd_ = open_fn("/proc/vmstat"); stat_fd_ = open_fn("/proc/stat"); read_buf_ = base::PagedMemory::Allocate(kReadBufSize); // Build a lookup map that allows to quickly translate strings like "MemTotal" // into the corresponding enum value, only for the counters enabled in the // config. using protos::pbzero::SysStatsConfig; SysStatsConfig::Decoder cfg(ds_config.sys_stats_config_raw()); constexpr size_t kMaxMeminfoEnum = protos::pbzero::MeminfoCounters_MAX; std::bitset meminfo_counters_enabled{}; if (!cfg.has_meminfo_counters()) meminfo_counters_enabled.set(); for (auto it = cfg.meminfo_counters(); it; ++it) { uint32_t counter = static_cast(*it); if (counter > 0 && counter <= kMaxMeminfoEnum) { meminfo_counters_enabled.set(counter); } else { PERFETTO_DFATAL("Meminfo counter out of bounds %u", counter); } } for (size_t i = 0; i < base::ArraySize(kMeminfoKeys); i++) { const auto& k = kMeminfoKeys[i]; if (meminfo_counters_enabled[static_cast(k.id)]) meminfo_counters_.emplace(k.str, k.id); } constexpr size_t kMaxVmstatEnum = protos::pbzero::VmstatCounters_MAX; std::bitset vmstat_counters_enabled{}; if (!cfg.has_vmstat_counters()) vmstat_counters_enabled.set(); for (auto it = cfg.vmstat_counters(); it; ++it) { uint32_t counter = static_cast(*it); if (counter > 0 && counter <= kMaxVmstatEnum) { vmstat_counters_enabled.set(counter); } else { PERFETTO_DFATAL("Vmstat counter out of bounds %u", counter); } } for (size_t i = 0; i < base::ArraySize(kVmstatKeys); i++) { const auto& k = kVmstatKeys[i]; if (vmstat_counters_enabled[static_cast(k.id)]) vmstat_counters_.emplace(k.str, k.id); } if (!cfg.has_stat_counters()) stat_enabled_fields_ = ~0u; for (auto counter = cfg.stat_counters(); counter; ++counter) { stat_enabled_fields_ |= 1ul << static_cast(*counter); } std::array periods_ms{}; std::array ticks{}; static_assert(periods_ms.size() == ticks.size(), "must have same size"); periods_ms[0] = ClampTo10Ms(cfg.meminfo_period_ms(), "meminfo_period_ms"); periods_ms[1] = ClampTo10Ms(cfg.vmstat_period_ms(), "vmstat_period_ms"); periods_ms[2] = ClampTo10Ms(cfg.stat_period_ms(), "stat_period_ms"); periods_ms[3] = ClampTo10Ms(cfg.devfreq_period_ms(), "devfreq_period_ms"); tick_period_ms_ = 0; for (uint32_t ms : periods_ms) { if (ms && (ms < tick_period_ms_ || tick_period_ms_ == 0)) tick_period_ms_ = ms; } if (tick_period_ms_ == 0) return; // No polling configured. for (size_t i = 0; i < periods_ms.size(); i++) { auto ms = periods_ms[i]; if (ms && ms % tick_period_ms_ != 0) { PERFETTO_ELOG("SysStat periods are not integer multiples of each other"); return; } ticks[i] = ms / tick_period_ms_; } meminfo_ticks_ = ticks[0]; vmstat_ticks_ = ticks[1]; stat_ticks_ = ticks[2]; devfreq_ticks_ = ticks[3]; } void SysStatsDataSource::Start() { auto weak_this = GetWeakPtr(); task_runner_->PostTask(std::bind(&SysStatsDataSource::Tick, weak_this)); } // static void SysStatsDataSource::Tick(base::WeakPtr weak_this) { if (!weak_this) return; SysStatsDataSource& thiz = *weak_this; uint32_t period_ms = thiz.tick_period_ms_; uint32_t delay_ms = period_ms - static_cast(base::GetWallTimeMs().count() % period_ms); thiz.task_runner_->PostDelayedTask( std::bind(&SysStatsDataSource::Tick, weak_this), delay_ms); thiz.ReadSysStats(); } SysStatsDataSource::~SysStatsDataSource() = default; void SysStatsDataSource::ReadSysStats() { PERFETTO_METATRACE_SCOPED(TAG_PROC_POLLERS, READ_SYS_STATS); auto packet = writer_->NewTracePacket(); packet->set_timestamp(static_cast(base::GetBootTimeNs().count())); auto* sys_stats = packet->set_sys_stats(); if (meminfo_ticks_ && tick_ % meminfo_ticks_ == 0) ReadMeminfo(sys_stats); if (vmstat_ticks_ && tick_ % vmstat_ticks_ == 0) ReadVmstat(sys_stats); if (stat_ticks_ && tick_ % stat_ticks_ == 0) ReadStat(sys_stats); if (devfreq_ticks_ && tick_ % devfreq_ticks_ == 0) ReadDevfreq(sys_stats); sys_stats->set_collection_end_timestamp( static_cast(base::GetBootTimeNs().count())); tick_++; } void SysStatsDataSource::ReadDevfreq(protos::pbzero::SysStats* sys_stats) { base::ScopedDir devfreq_dir = OpenDevfreqDir(); if (devfreq_dir) { while (struct dirent* dir_ent = readdir(*devfreq_dir)) { // Entries in /sys/class/devfreq are symlinks to /devices/platform if (dir_ent->d_type != DT_LNK) continue; const char* name = dir_ent->d_name; const char* file_content = ReadDevfreqCurFreq(name); auto value = static_cast(strtoll(file_content, nullptr, 10)); auto* devfreq = sys_stats->add_devfreq(); devfreq->set_key(name); devfreq->set_value(value); } } } base::ScopedDir SysStatsDataSource::OpenDevfreqDir() { const char* base_dir = "/sys/class/devfreq/"; base::ScopedDir devfreq_dir(opendir(base_dir)); if (!devfreq_dir && !devfreq_error_logged_) { devfreq_error_logged_ = true; PERFETTO_PLOG("failed to opendir(/sys/class/devfreq)"); } return devfreq_dir; } const char* SysStatsDataSource::ReadDevfreqCurFreq( const std::string& deviceName) { const char* devfreq_base_path = "/sys/class/devfreq"; const char* freq_file_name = "cur_freq"; char cur_freq_path[256]; snprintf(cur_freq_path, sizeof(cur_freq_path), "%s/%s/%s", devfreq_base_path, deviceName.c_str(), freq_file_name); base::ScopedFile fd = OpenReadOnly(cur_freq_path); if (!fd && !devfreq_error_logged_) { devfreq_error_logged_ = true; PERFETTO_PLOG("Failed to open %s", cur_freq_path); return ""; } size_t rsize = ReadFile(&fd, cur_freq_path); if (!rsize) return ""; return static_cast(read_buf_.Get()); } void SysStatsDataSource::ReadMeminfo(protos::pbzero::SysStats* sys_stats) { size_t rsize = ReadFile(&meminfo_fd_, "/proc/meminfo"); if (!rsize) return; char* buf = static_cast(read_buf_.Get()); for (base::StringSplitter lines(buf, rsize, '\n'); lines.Next();) { base::StringSplitter words(&lines, ' '); if (!words.Next()) continue; // Extract the meminfo key, dropping trailing ':' (e.g., "MemTotal: NN KB"). words.cur_token()[words.cur_token_size() - 1] = '\0'; auto it = meminfo_counters_.find(words.cur_token()); if (it == meminfo_counters_.end()) continue; int counter_id = it->second; if (!words.Next()) continue; auto value = static_cast(strtoll(words.cur_token(), nullptr, 10)); auto* meminfo = sys_stats->add_meminfo(); meminfo->set_key(static_cast(counter_id)); meminfo->set_value(value); } } void SysStatsDataSource::ReadVmstat(protos::pbzero::SysStats* sys_stats) { size_t rsize = ReadFile(&vmstat_fd_, "/proc/vmstat"); if (!rsize) return; char* buf = static_cast(read_buf_.Get()); for (base::StringSplitter lines(buf, rsize, '\n'); lines.Next();) { base::StringSplitter words(&lines, ' '); if (!words.Next()) continue; auto it = vmstat_counters_.find(words.cur_token()); if (it == vmstat_counters_.end()) continue; int counter_id = it->second; if (!words.Next()) continue; auto value = static_cast(strtoll(words.cur_token(), nullptr, 10)); auto* vmstat = sys_stats->add_vmstat(); vmstat->set_key(static_cast(counter_id)); vmstat->set_value(value); } } void SysStatsDataSource::ReadStat(protos::pbzero::SysStats* sys_stats) { size_t rsize = ReadFile(&stat_fd_, "/proc/stat"); if (!rsize) return; char* buf = static_cast(read_buf_.Get()); for (base::StringSplitter lines(buf, rsize, '\n'); lines.Next();) { base::StringSplitter words(&lines, ' '); if (!words.Next()) continue; // Per-CPU stats. if ((stat_enabled_fields_ & (1 << SysStatsConfig::STAT_CPU_TIMES)) && words.cur_token_size() > 3 && !strncmp(words.cur_token(), "cpu", 3)) { long cpu_id = strtol(words.cur_token() + 3, nullptr, 10); std::array cpu_times{}; for (size_t i = 0; i < cpu_times.size() && words.Next(); i++) { cpu_times[i] = static_cast(strtoll(words.cur_token(), nullptr, 10)); } auto* cpu_stat = sys_stats->add_cpu_stat(); cpu_stat->set_cpu_id(static_cast(cpu_id)); cpu_stat->set_user_ns(cpu_times[0] * ns_per_user_hz_); cpu_stat->set_user_ice_ns(cpu_times[1] * ns_per_user_hz_); cpu_stat->set_system_mode_ns(cpu_times[2] * ns_per_user_hz_); cpu_stat->set_idle_ns(cpu_times[3] * ns_per_user_hz_); cpu_stat->set_io_wait_ns(cpu_times[4] * ns_per_user_hz_); cpu_stat->set_irq_ns(cpu_times[5] * ns_per_user_hz_); cpu_stat->set_softirq_ns(cpu_times[6] * ns_per_user_hz_); } // IRQ counters else if ((stat_enabled_fields_ & (1 << SysStatsConfig::STAT_IRQ_COUNTS)) && !strcmp(words.cur_token(), "intr")) { for (size_t i = 0; words.Next(); i++) { auto v = static_cast(strtoll(words.cur_token(), nullptr, 10)); if (i == 0) { sys_stats->set_num_irq_total(v); } else if (v > 0) { auto* irq_stat = sys_stats->add_num_irq(); irq_stat->set_irq(static_cast(i - 1)); irq_stat->set_count(v); } } } // Softirq counters. else if ((stat_enabled_fields_ & (1 << SysStatsConfig::STAT_SOFTIRQ_COUNTS)) && !strcmp(words.cur_token(), "softirq")) { for (size_t i = 0; words.Next(); i++) { auto v = static_cast(strtoll(words.cur_token(), nullptr, 10)); if (i == 0) { sys_stats->set_num_softirq_total(v); } else { auto* softirq_stat = sys_stats->add_num_softirq(); softirq_stat->set_irq(static_cast(i - 1)); softirq_stat->set_count(v); } } } // Number of forked processes since boot. else if ((stat_enabled_fields_ & (1 << SysStatsConfig::STAT_FORK_COUNT)) && !strcmp(words.cur_token(), "processes")) { if (words.Next()) { sys_stats->set_num_forks( static_cast(strtoll(words.cur_token(), nullptr, 10))); } } } // for (line) } base::WeakPtr SysStatsDataSource::GetWeakPtr() const { return weak_factory_.GetWeakPtr(); } void SysStatsDataSource::Flush(FlushRequestID, std::function callback) { writer_->Flush(callback); } size_t SysStatsDataSource::ReadFile(base::ScopedFile* fd, const char* path) { if (!*fd) return 0; ssize_t res = pread(**fd, read_buf_.Get(), kReadBufSize - 1, 0); if (res <= 0) { PERFETTO_PLOG("Failed reading %s", path); fd->reset(); return 0; } size_t rsize = static_cast(res); static_cast(read_buf_.Get())[rsize] = '\0'; return rsize + 1; // Include null terminator in the count. } } // namespace perfetto