1 /*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "src/traced/probes/sys_stats/sys_stats_data_source.h"
18
19 #include <stdlib.h>
20 #include <unistd.h>
21
22 #include <algorithm>
23 #include <array>
24 #include <bitset>
25 #include <limits>
26 #include <utility>
27
28 #include "perfetto/base/task_runner.h"
29 #include "perfetto/base/time.h"
30 #include "perfetto/ext/base/file_utils.h"
31 #include "perfetto/ext/base/metatrace.h"
32 #include "perfetto/ext/base/scoped_file.h"
33 #include "perfetto/ext/base/string_splitter.h"
34 #include "perfetto/ext/base/string_utils.h"
35 #include "perfetto/ext/base/utils.h"
36 #include "perfetto/ext/traced/sys_stats_counters.h"
37
38 #include "protos/perfetto/common/sys_stats_counters.pbzero.h"
39 #include "protos/perfetto/config/sys_stats/sys_stats_config.pbzero.h"
40 #include "protos/perfetto/trace/sys_stats/sys_stats.pbzero.h"
41 #include "protos/perfetto/trace/trace_packet.pbzero.h"
42
43 namespace perfetto {
44
45 using protos::pbzero::SysStatsConfig;
46
47 namespace {
48 constexpr size_t kReadBufSize = 1024 * 16;
49
OpenReadOnly(const char * path)50 base::ScopedFile OpenReadOnly(const char* path) {
51 base::ScopedFile fd(base::OpenFile(path, O_RDONLY));
52 if (!fd)
53 PERFETTO_PLOG("Failed opening %s", path);
54 return fd;
55 }
56
ClampTo10Ms(uint32_t period_ms,const char * counter_name)57 uint32_t ClampTo10Ms(uint32_t period_ms, const char* counter_name) {
58 if (period_ms > 0 && period_ms < 10) {
59 PERFETTO_ILOG("%s %" PRIu32
60 " is less than minimum of 10ms. Increasing to 10ms.",
61 counter_name, period_ms);
62 return 10;
63 }
64 return period_ms;
65 }
66
67 } // namespace
68
69 // static
70 const ProbesDataSource::Descriptor SysStatsDataSource::descriptor = {
71 /*name*/ "linux.sys_stats",
72 /*flags*/ Descriptor::kFlagsNone,
73 /*fill_descriptor_func*/ nullptr,
74 };
75
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)76 SysStatsDataSource::SysStatsDataSource(
77 base::TaskRunner* task_runner,
78 TracingSessionID session_id,
79 std::unique_ptr<TraceWriter> writer,
80 const DataSourceConfig& ds_config,
81 std::unique_ptr<CpuFreqInfo> cpu_freq_info,
82 OpenFunction open_fn)
83 : ProbesDataSource(session_id, &descriptor),
84 task_runner_(task_runner),
85 writer_(std::move(writer)),
86 cpu_freq_info_(std::move(cpu_freq_info)),
87 weak_factory_(this) {
88 ns_per_user_hz_ = 1000000000ull / static_cast<uint64_t>(sysconf(_SC_CLK_TCK));
89
90 open_fn = open_fn ? open_fn : OpenReadOnly;
91 meminfo_fd_ = open_fn("/proc/meminfo");
92 vmstat_fd_ = open_fn("/proc/vmstat");
93 stat_fd_ = open_fn("/proc/stat");
94
95 read_buf_ = base::PagedMemory::Allocate(kReadBufSize);
96
97 // Build a lookup map that allows to quickly translate strings like "MemTotal"
98 // into the corresponding enum value, only for the counters enabled in the
99 // config.
100
101 using protos::pbzero::SysStatsConfig;
102 SysStatsConfig::Decoder cfg(ds_config.sys_stats_config_raw());
103
104 constexpr size_t kMaxMeminfoEnum = protos::pbzero::MeminfoCounters_MAX;
105 std::bitset<kMaxMeminfoEnum + 1> meminfo_counters_enabled{};
106 if (!cfg.has_meminfo_counters())
107 meminfo_counters_enabled.set();
108 for (auto it = cfg.meminfo_counters(); it; ++it) {
109 uint32_t counter = static_cast<uint32_t>(*it);
110 if (counter > 0 && counter <= kMaxMeminfoEnum) {
111 meminfo_counters_enabled.set(counter);
112 } else {
113 PERFETTO_DFATAL("Meminfo counter out of bounds %u", counter);
114 }
115 }
116 for (size_t i = 0; i < base::ArraySize(kMeminfoKeys); i++) {
117 const auto& k = kMeminfoKeys[i];
118 if (meminfo_counters_enabled[static_cast<size_t>(k.id)])
119 meminfo_counters_.emplace(k.str, k.id);
120 }
121
122 constexpr size_t kMaxVmstatEnum = protos::pbzero::VmstatCounters_MAX;
123 std::bitset<kMaxVmstatEnum + 1> vmstat_counters_enabled{};
124 if (!cfg.has_vmstat_counters())
125 vmstat_counters_enabled.set();
126 for (auto it = cfg.vmstat_counters(); it; ++it) {
127 uint32_t counter = static_cast<uint32_t>(*it);
128 if (counter > 0 && counter <= kMaxVmstatEnum) {
129 vmstat_counters_enabled.set(counter);
130 } else {
131 PERFETTO_DFATAL("Vmstat counter out of bounds %u", counter);
132 }
133 }
134 for (size_t i = 0; i < base::ArraySize(kVmstatKeys); i++) {
135 const auto& k = kVmstatKeys[i];
136 if (vmstat_counters_enabled[static_cast<size_t>(k.id)])
137 vmstat_counters_.emplace(k.str, k.id);
138 }
139
140 if (!cfg.has_stat_counters())
141 stat_enabled_fields_ = ~0u;
142 for (auto counter = cfg.stat_counters(); counter; ++counter) {
143 stat_enabled_fields_ |= 1ul << static_cast<uint32_t>(*counter);
144 }
145
146 std::array<uint32_t, 5> periods_ms{};
147 std::array<uint32_t, 5> ticks{};
148 static_assert(periods_ms.size() == ticks.size(), "must have same size");
149
150 periods_ms[0] = ClampTo10Ms(cfg.meminfo_period_ms(), "meminfo_period_ms");
151 periods_ms[1] = ClampTo10Ms(cfg.vmstat_period_ms(), "vmstat_period_ms");
152 periods_ms[2] = ClampTo10Ms(cfg.stat_period_ms(), "stat_period_ms");
153 periods_ms[3] = ClampTo10Ms(cfg.devfreq_period_ms(), "devfreq_period_ms");
154 periods_ms[4] = ClampTo10Ms(cfg.cpufreq_period_ms(), "cpufreq_period_ms");
155
156 tick_period_ms_ = 0;
157 for (uint32_t ms : periods_ms) {
158 if (ms && (ms < tick_period_ms_ || tick_period_ms_ == 0))
159 tick_period_ms_ = ms;
160 }
161
162 if (tick_period_ms_ == 0)
163 return; // No polling configured.
164
165 for (size_t i = 0; i < periods_ms.size(); i++) {
166 auto ms = periods_ms[i];
167 if (ms && ms % tick_period_ms_ != 0) {
168 PERFETTO_ELOG("SysStat periods are not integer multiples of each other");
169 return;
170 }
171 ticks[i] = ms / tick_period_ms_;
172 }
173 meminfo_ticks_ = ticks[0];
174 vmstat_ticks_ = ticks[1];
175 stat_ticks_ = ticks[2];
176 devfreq_ticks_ = ticks[3];
177 cpufreq_ticks_ = ticks[4];
178 }
179
Start()180 void SysStatsDataSource::Start() {
181 auto weak_this = GetWeakPtr();
182 task_runner_->PostTask(std::bind(&SysStatsDataSource::Tick, weak_this));
183 }
184
185 // static
Tick(base::WeakPtr<SysStatsDataSource> weak_this)186 void SysStatsDataSource::Tick(base::WeakPtr<SysStatsDataSource> weak_this) {
187 if (!weak_this)
188 return;
189 SysStatsDataSource& thiz = *weak_this;
190
191 uint32_t period_ms = thiz.tick_period_ms_;
192 uint32_t delay_ms =
193 period_ms -
194 static_cast<uint32_t>(base::GetWallTimeMs().count() % period_ms);
195 thiz.task_runner_->PostDelayedTask(
196 std::bind(&SysStatsDataSource::Tick, weak_this), delay_ms);
197 thiz.ReadSysStats();
198 }
199
200 SysStatsDataSource::~SysStatsDataSource() = default;
201
ReadSysStats()202 void SysStatsDataSource::ReadSysStats() {
203 PERFETTO_METATRACE_SCOPED(TAG_PROC_POLLERS, READ_SYS_STATS);
204 auto packet = writer_->NewTracePacket();
205
206 packet->set_timestamp(static_cast<uint64_t>(base::GetBootTimeNs().count()));
207 auto* sys_stats = packet->set_sys_stats();
208
209 if (meminfo_ticks_ && tick_ % meminfo_ticks_ == 0)
210 ReadMeminfo(sys_stats);
211
212 if (vmstat_ticks_ && tick_ % vmstat_ticks_ == 0)
213 ReadVmstat(sys_stats);
214
215 if (stat_ticks_ && tick_ % stat_ticks_ == 0)
216 ReadStat(sys_stats);
217
218 if (devfreq_ticks_ && tick_ % devfreq_ticks_ == 0)
219 ReadDevfreq(sys_stats);
220
221 if (cpufreq_ticks_ && tick_ % cpufreq_ticks_ == 0)
222 ReadCpufreq(sys_stats);
223
224 sys_stats->set_collection_end_timestamp(
225 static_cast<uint64_t>(base::GetBootTimeNs().count()));
226
227 tick_++;
228 }
229
ReadDevfreq(protos::pbzero::SysStats * sys_stats)230 void SysStatsDataSource::ReadDevfreq(protos::pbzero::SysStats* sys_stats) {
231 base::ScopedDir devfreq_dir = OpenDevfreqDir();
232 if (devfreq_dir) {
233 while (struct dirent* dir_ent = readdir(*devfreq_dir)) {
234 // Entries in /sys/class/devfreq are symlinks to /devices/platform
235 if (dir_ent->d_type != DT_LNK)
236 continue;
237 const char* name = dir_ent->d_name;
238 const char* file_content = ReadDevfreqCurFreq(name);
239 auto value = static_cast<uint64_t>(strtoll(file_content, nullptr, 10));
240 auto* devfreq = sys_stats->add_devfreq();
241 devfreq->set_key(name);
242 devfreq->set_value(value);
243 }
244 }
245 }
246
ReadCpufreq(protos::pbzero::SysStats * sys_stats)247 void SysStatsDataSource::ReadCpufreq(protos::pbzero::SysStats* sys_stats) {
248 const auto& cpufreq = cpu_freq_info_->ReadCpuCurrFreq();
249
250 for (const auto& c : cpufreq)
251 sys_stats->add_cpufreq_khz(c);
252 }
253
OpenDevfreqDir()254 base::ScopedDir SysStatsDataSource::OpenDevfreqDir() {
255 const char* base_dir = "/sys/class/devfreq/";
256 base::ScopedDir devfreq_dir(opendir(base_dir));
257 if (!devfreq_dir && !devfreq_error_logged_) {
258 devfreq_error_logged_ = true;
259 PERFETTO_PLOG("failed to opendir(/sys/class/devfreq)");
260 }
261 return devfreq_dir;
262 }
263
ReadDevfreqCurFreq(const std::string & deviceName)264 const char* SysStatsDataSource::ReadDevfreqCurFreq(
265 const std::string& deviceName) {
266 const char* devfreq_base_path = "/sys/class/devfreq";
267 const char* freq_file_name = "cur_freq";
268 base::StackString<256> cur_freq_path("%s/%s/%s", devfreq_base_path,
269 deviceName.c_str(), freq_file_name);
270 base::ScopedFile fd = OpenReadOnly(cur_freq_path.c_str());
271 if (!fd && !devfreq_error_logged_) {
272 devfreq_error_logged_ = true;
273 PERFETTO_PLOG("Failed to open %s", cur_freq_path.c_str());
274 return "";
275 }
276 size_t rsize = ReadFile(&fd, cur_freq_path.c_str());
277 if (!rsize)
278 return "";
279 return static_cast<char*>(read_buf_.Get());
280 }
281
ReadMeminfo(protos::pbzero::SysStats * sys_stats)282 void SysStatsDataSource::ReadMeminfo(protos::pbzero::SysStats* sys_stats) {
283 size_t rsize = ReadFile(&meminfo_fd_, "/proc/meminfo");
284 if (!rsize)
285 return;
286 char* buf = static_cast<char*>(read_buf_.Get());
287 for (base::StringSplitter lines(buf, rsize, '\n'); lines.Next();) {
288 base::StringSplitter words(&lines, ' ');
289 if (!words.Next())
290 continue;
291 // Extract the meminfo key, dropping trailing ':' (e.g., "MemTotal: NN KB").
292 words.cur_token()[words.cur_token_size() - 1] = '\0';
293 auto it = meminfo_counters_.find(words.cur_token());
294 if (it == meminfo_counters_.end())
295 continue;
296 int counter_id = it->second;
297 if (!words.Next())
298 continue;
299 auto value = static_cast<uint64_t>(strtoll(words.cur_token(), nullptr, 10));
300 auto* meminfo = sys_stats->add_meminfo();
301 meminfo->set_key(static_cast<protos::pbzero::MeminfoCounters>(counter_id));
302 meminfo->set_value(value);
303 }
304 }
305
ReadVmstat(protos::pbzero::SysStats * sys_stats)306 void SysStatsDataSource::ReadVmstat(protos::pbzero::SysStats* sys_stats) {
307 size_t rsize = ReadFile(&vmstat_fd_, "/proc/vmstat");
308 if (!rsize)
309 return;
310 char* buf = static_cast<char*>(read_buf_.Get());
311 for (base::StringSplitter lines(buf, rsize, '\n'); lines.Next();) {
312 base::StringSplitter words(&lines, ' ');
313 if (!words.Next())
314 continue;
315 auto it = vmstat_counters_.find(words.cur_token());
316 if (it == vmstat_counters_.end())
317 continue;
318 int counter_id = it->second;
319 if (!words.Next())
320 continue;
321 auto value = static_cast<uint64_t>(strtoll(words.cur_token(), nullptr, 10));
322 auto* vmstat = sys_stats->add_vmstat();
323 vmstat->set_key(static_cast<protos::pbzero::VmstatCounters>(counter_id));
324 vmstat->set_value(value);
325 }
326 }
327
ReadStat(protos::pbzero::SysStats * sys_stats)328 void SysStatsDataSource::ReadStat(protos::pbzero::SysStats* sys_stats) {
329 size_t rsize = ReadFile(&stat_fd_, "/proc/stat");
330 if (!rsize)
331 return;
332 char* buf = static_cast<char*>(read_buf_.Get());
333 for (base::StringSplitter lines(buf, rsize, '\n'); lines.Next();) {
334 base::StringSplitter words(&lines, ' ');
335 if (!words.Next())
336 continue;
337
338 // Per-CPU stats.
339 if ((stat_enabled_fields_ & (1 << SysStatsConfig::STAT_CPU_TIMES)) &&
340 words.cur_token_size() > 3 && !strncmp(words.cur_token(), "cpu", 3)) {
341 long cpu_id = strtol(words.cur_token() + 3, nullptr, 10);
342 std::array<uint64_t, 7> cpu_times{};
343 for (size_t i = 0; i < cpu_times.size() && words.Next(); i++) {
344 cpu_times[i] =
345 static_cast<uint64_t>(strtoll(words.cur_token(), nullptr, 10));
346 }
347 auto* cpu_stat = sys_stats->add_cpu_stat();
348 cpu_stat->set_cpu_id(static_cast<uint32_t>(cpu_id));
349 cpu_stat->set_user_ns(cpu_times[0] * ns_per_user_hz_);
350 cpu_stat->set_user_ice_ns(cpu_times[1] * ns_per_user_hz_);
351 cpu_stat->set_system_mode_ns(cpu_times[2] * ns_per_user_hz_);
352 cpu_stat->set_idle_ns(cpu_times[3] * ns_per_user_hz_);
353 cpu_stat->set_io_wait_ns(cpu_times[4] * ns_per_user_hz_);
354 cpu_stat->set_irq_ns(cpu_times[5] * ns_per_user_hz_);
355 cpu_stat->set_softirq_ns(cpu_times[6] * ns_per_user_hz_);
356 }
357 // IRQ counters
358 else if ((stat_enabled_fields_ & (1 << SysStatsConfig::STAT_IRQ_COUNTS)) &&
359 !strcmp(words.cur_token(), "intr")) {
360 for (size_t i = 0; words.Next(); i++) {
361 auto v = static_cast<uint64_t>(strtoll(words.cur_token(), nullptr, 10));
362 if (i == 0) {
363 sys_stats->set_num_irq_total(v);
364 } else if (v > 0) {
365 auto* irq_stat = sys_stats->add_num_irq();
366 irq_stat->set_irq(static_cast<int32_t>(i - 1));
367 irq_stat->set_count(v);
368 }
369 }
370 }
371 // Softirq counters.
372 else if ((stat_enabled_fields_ &
373 (1 << SysStatsConfig::STAT_SOFTIRQ_COUNTS)) &&
374 !strcmp(words.cur_token(), "softirq")) {
375 for (size_t i = 0; words.Next(); i++) {
376 auto v = static_cast<uint64_t>(strtoll(words.cur_token(), nullptr, 10));
377 if (i == 0) {
378 sys_stats->set_num_softirq_total(v);
379 } else {
380 auto* softirq_stat = sys_stats->add_num_softirq();
381 softirq_stat->set_irq(static_cast<int32_t>(i - 1));
382 softirq_stat->set_count(v);
383 }
384 }
385 }
386 // Number of forked processes since boot.
387 else if ((stat_enabled_fields_ & (1 << SysStatsConfig::STAT_FORK_COUNT)) &&
388 !strcmp(words.cur_token(), "processes")) {
389 if (words.Next()) {
390 sys_stats->set_num_forks(
391 static_cast<uint64_t>(strtoll(words.cur_token(), nullptr, 10)));
392 }
393 }
394
395 } // for (line)
396 }
397
GetWeakPtr() const398 base::WeakPtr<SysStatsDataSource> SysStatsDataSource::GetWeakPtr() const {
399 return weak_factory_.GetWeakPtr();
400 }
401
Flush(FlushRequestID,std::function<void ()> callback)402 void SysStatsDataSource::Flush(FlushRequestID, std::function<void()> callback) {
403 writer_->Flush(callback);
404 }
405
ReadFile(base::ScopedFile * fd,const char * path)406 size_t SysStatsDataSource::ReadFile(base::ScopedFile* fd, const char* path) {
407 if (!*fd)
408 return 0;
409 ssize_t res = pread(**fd, read_buf_.Get(), kReadBufSize - 1, 0);
410 if (res <= 0) {
411 PERFETTO_PLOG("Failed reading %s", path);
412 fd->reset();
413 return 0;
414 }
415 size_t rsize = static_cast<size_t>(res);
416 static_cast<char*>(read_buf_.Get())[rsize] = '\0';
417 return rsize + 1; // Include null terminator in the count.
418 }
419
420 } // namespace perfetto
421