blob: e5c02a9a650c572199b04b05a912c0c104f39862 [file] [log] [blame]
/*
* 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 <stdlib.h>
#include <unistd.h>
#include <algorithm>
#include <array>
#include <bitset>
#include <limits>
#include <utility>
#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/string_utils.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,
/*fill_descriptor_func*/ nullptr,
};
SysStatsDataSource::SysStatsDataSource(
base::TaskRunner* task_runner,
TracingSessionID session_id,
std::unique_ptr<TraceWriter> writer,
const DataSourceConfig& ds_config,
std::unique_ptr<CpuFreqInfo> cpu_freq_info,
OpenFunction open_fn)
: ProbesDataSource(session_id, &descriptor),
task_runner_(task_runner),
writer_(std::move(writer)),
cpu_freq_info_(std::move(cpu_freq_info)),
weak_factory_(this) {
ns_per_user_hz_ = 1000000000ull / static_cast<uint64_t>(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");
buddy_fd_ = open_fn("/proc/buddyinfo");
diskstat_fd_ = open_fn("/proc/diskstats");
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<kMaxMeminfoEnum + 1> 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<uint32_t>(*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<size_t>(k.id)])
meminfo_counters_.emplace(k.str, k.id);
}
constexpr size_t kMaxVmstatEnum = protos::pbzero::VmstatCounters_MAX;
std::bitset<kMaxVmstatEnum + 1> 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<uint32_t>(*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<size_t>(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<uint32_t>(*counter);
}
std::array<uint32_t, 7> periods_ms{};
std::array<uint32_t, 7> 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");
periods_ms[4] = ClampTo10Ms(cfg.cpufreq_period_ms(), "cpufreq_period_ms");
periods_ms[5] = ClampTo10Ms(cfg.buddyinfo_period_ms(), "buddyinfo_period_ms");
periods_ms[6] = ClampTo10Ms(cfg.diskstat_period_ms(), "diskstat_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];
cpufreq_ticks_ = ticks[4];
buddyinfo_ticks_ = ticks[5];
diskstat_ticks_ = ticks[6];
}
void SysStatsDataSource::Start() {
auto weak_this = GetWeakPtr();
task_runner_->PostTask(std::bind(&SysStatsDataSource::Tick, weak_this));
}
// static
void SysStatsDataSource::Tick(base::WeakPtr<SysStatsDataSource> 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<uint32_t>(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<uint64_t>(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);
if (cpufreq_ticks_ && tick_ % cpufreq_ticks_ == 0)
ReadCpufreq(sys_stats);
if (buddyinfo_ticks_ && tick_ % buddyinfo_ticks_ == 0)
ReadBuddyInfo(sys_stats);
if (diskstat_ticks_ && tick_ % diskstat_ticks_ == 0)
ReadDiskStat(sys_stats);
sys_stats->set_collection_end_timestamp(
static_cast<uint64_t>(base::GetBootTimeNs().count()));
tick_++;
}
void SysStatsDataSource::ReadDiskStat(protos::pbzero::SysStats* sys_stats) {
size_t rsize = ReadFile(&diskstat_fd_, "/proc/diskstats");
if (!rsize) {
return;
}
char* buf = static_cast<char*>(read_buf_.Get());
for (base::StringSplitter lines(buf, rsize, '\n'); lines.Next();) {
uint32_t index = 0;
auto* disk_stat = sys_stats->add_disk_stat();
for (base::StringSplitter words(&lines, ' '); words.Next(); index++) {
if (index == 2) { // index for device name (string)
disk_stat->set_device_name(words.cur_token());
} else if (index >= 5) { // integer values from index 5
std::optional<uint64_t> value_address =
base::CStringToUInt64(words.cur_token());
uint64_t value = value_address ? *value_address : 0;
switch (index) {
case 5:
disk_stat->set_read_sectors(value);
break;
case 6:
disk_stat->set_read_time_ms(value);
break;
case 9:
disk_stat->set_write_sectors(value);
break;
case 10:
disk_stat->set_write_time_ms(value);
break;
case 16:
disk_stat->set_discard_sectors(value);
break;
case 17:
disk_stat->set_discard_time_ms(value);
break;
case 18:
disk_stat->set_flush_count(value);
break;
case 19:
disk_stat->set_flush_time_ms(value);
break;
}
if (index == 19) {
break;
}
}
}
}
}
void SysStatsDataSource::ReadBuddyInfo(protos::pbzero::SysStats* sys_stats) {
size_t rsize = ReadFile(&buddy_fd_, "/proc/buddyinfo");
if (!rsize) {
return;
}
char* buf = static_cast<char*>(read_buf_.Get());
for (base::StringSplitter lines(buf, rsize, '\n'); lines.Next();) {
uint32_t index = 0;
auto* buddy_info = sys_stats->add_buddy_info();
for (base::StringSplitter words(&lines, ' '); words.Next();) {
if (index == 1) {
std::string token = words.cur_token();
token = token.substr(0, token.find(","));
buddy_info->set_node(token);
} else if (index == 3) {
buddy_info->set_zone(words.cur_token());
} else if (index > 3) {
buddy_info->add_order_pages(
static_cast<uint32_t>(strtoul(words.cur_token(), nullptr, 0)));
}
index++;
}
}
}
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<uint64_t>(strtoll(file_content, nullptr, 10));
auto* devfreq = sys_stats->add_devfreq();
devfreq->set_key(name);
devfreq->set_value(value);
}
}
}
void SysStatsDataSource::ReadCpufreq(protos::pbzero::SysStats* sys_stats) {
const auto& cpufreq = cpu_freq_info_->ReadCpuCurrFreq();
for (const auto& c : cpufreq)
sys_stats->add_cpufreq_khz(c);
}
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";
base::StackString<256> cur_freq_path("%s/%s/%s", devfreq_base_path,
deviceName.c_str(), freq_file_name);
base::ScopedFile fd = OpenReadOnly(cur_freq_path.c_str());
if (!fd && !devfreq_error_logged_) {
devfreq_error_logged_ = true;
PERFETTO_PLOG("Failed to open %s", cur_freq_path.c_str());
return "";
}
size_t rsize = ReadFile(&fd, cur_freq_path.c_str());
if (!rsize)
return "";
return static_cast<char*>(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<char*>(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<uint64_t>(strtoll(words.cur_token(), nullptr, 10));
auto* meminfo = sys_stats->add_meminfo();
meminfo->set_key(static_cast<protos::pbzero::MeminfoCounters>(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<char*>(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<uint64_t>(strtoll(words.cur_token(), nullptr, 10));
auto* vmstat = sys_stats->add_vmstat();
vmstat->set_key(static_cast<protos::pbzero::VmstatCounters>(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<char*>(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<uint64_t, 7> cpu_times{};
for (size_t i = 0; i < cpu_times.size() && words.Next(); i++) {
cpu_times[i] =
static_cast<uint64_t>(strtoll(words.cur_token(), nullptr, 10));
}
auto* cpu_stat = sys_stats->add_cpu_stat();
cpu_stat->set_cpu_id(static_cast<uint32_t>(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<uint64_t>(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<int32_t>(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<uint64_t>(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<int32_t>(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<uint64_t>(strtoll(words.cur_token(), nullptr, 10)));
}
}
} // for (line)
}
base::WeakPtr<SysStatsDataSource> SysStatsDataSource::GetWeakPtr() const {
return weak_factory_.GetWeakPtr();
}
void SysStatsDataSource::Flush(FlushRequestID, std::function<void()> 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<size_t>(res);
static_cast<char*>(read_buf_.Get())[rsize] = '\0';
return rsize + 1; // Include null terminator in the count.
}
} // namespace perfetto