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 buddy_fd_ = open_fn("/proc/buddyinfo");
95 diskstat_fd_ = open_fn("/proc/diskstats");
96
97 read_buf_ = base::PagedMemory::Allocate(kReadBufSize);
98
99 // Build a lookup map that allows to quickly translate strings like "MemTotal"
100 // into the corresponding enum value, only for the counters enabled in the
101 // config.
102
103 using protos::pbzero::SysStatsConfig;
104 SysStatsConfig::Decoder cfg(ds_config.sys_stats_config_raw());
105
106 constexpr size_t kMaxMeminfoEnum = protos::pbzero::MeminfoCounters_MAX;
107 std::bitset<kMaxMeminfoEnum + 1> meminfo_counters_enabled{};
108 if (!cfg.has_meminfo_counters())
109 meminfo_counters_enabled.set();
110 for (auto it = cfg.meminfo_counters(); it; ++it) {
111 uint32_t counter = static_cast<uint32_t>(*it);
112 if (counter > 0 && counter <= kMaxMeminfoEnum) {
113 meminfo_counters_enabled.set(counter);
114 } else {
115 PERFETTO_DFATAL("Meminfo counter out of bounds %u", counter);
116 }
117 }
118 for (size_t i = 0; i < base::ArraySize(kMeminfoKeys); i++) {
119 const auto& k = kMeminfoKeys[i];
120 if (meminfo_counters_enabled[static_cast<size_t>(k.id)])
121 meminfo_counters_.emplace(k.str, k.id);
122 }
123
124 constexpr size_t kMaxVmstatEnum = protos::pbzero::VmstatCounters_MAX;
125 std::bitset<kMaxVmstatEnum + 1> vmstat_counters_enabled{};
126 if (!cfg.has_vmstat_counters())
127 vmstat_counters_enabled.set();
128 for (auto it = cfg.vmstat_counters(); it; ++it) {
129 uint32_t counter = static_cast<uint32_t>(*it);
130 if (counter > 0 && counter <= kMaxVmstatEnum) {
131 vmstat_counters_enabled.set(counter);
132 } else {
133 PERFETTO_DFATAL("Vmstat counter out of bounds %u", counter);
134 }
135 }
136 for (size_t i = 0; i < base::ArraySize(kVmstatKeys); i++) {
137 const auto& k = kVmstatKeys[i];
138 if (vmstat_counters_enabled[static_cast<size_t>(k.id)])
139 vmstat_counters_.emplace(k.str, k.id);
140 }
141
142 if (!cfg.has_stat_counters())
143 stat_enabled_fields_ = ~0u;
144 for (auto counter = cfg.stat_counters(); counter; ++counter) {
145 stat_enabled_fields_ |= 1ul << static_cast<uint32_t>(*counter);
146 }
147
148 std::array<uint32_t, 7> periods_ms{};
149 std::array<uint32_t, 7> ticks{};
150 static_assert(periods_ms.size() == ticks.size(), "must have same size");
151
152 periods_ms[0] = ClampTo10Ms(cfg.meminfo_period_ms(), "meminfo_period_ms");
153 periods_ms[1] = ClampTo10Ms(cfg.vmstat_period_ms(), "vmstat_period_ms");
154 periods_ms[2] = ClampTo10Ms(cfg.stat_period_ms(), "stat_period_ms");
155 periods_ms[3] = ClampTo10Ms(cfg.devfreq_period_ms(), "devfreq_period_ms");
156 periods_ms[4] = ClampTo10Ms(cfg.cpufreq_period_ms(), "cpufreq_period_ms");
157 periods_ms[5] = ClampTo10Ms(cfg.buddyinfo_period_ms(), "buddyinfo_period_ms");
158 periods_ms[6] = ClampTo10Ms(cfg.diskstat_period_ms(), "diskstat_period_ms");
159
160 tick_period_ms_ = 0;
161 for (uint32_t ms : periods_ms) {
162 if (ms && (ms < tick_period_ms_ || tick_period_ms_ == 0))
163 tick_period_ms_ = ms;
164 }
165
166 if (tick_period_ms_ == 0)
167 return; // No polling configured.
168
169 for (size_t i = 0; i < periods_ms.size(); i++) {
170 auto ms = periods_ms[i];
171 if (ms && ms % tick_period_ms_ != 0) {
172 PERFETTO_ELOG("SysStat periods are not integer multiples of each other");
173 return;
174 }
175 ticks[i] = ms / tick_period_ms_;
176 }
177 meminfo_ticks_ = ticks[0];
178 vmstat_ticks_ = ticks[1];
179 stat_ticks_ = ticks[2];
180 devfreq_ticks_ = ticks[3];
181 cpufreq_ticks_ = ticks[4];
182 buddyinfo_ticks_ = ticks[5];
183 diskstat_ticks_ = ticks[6];
184 }
185
Start()186 void SysStatsDataSource::Start() {
187 auto weak_this = GetWeakPtr();
188 task_runner_->PostTask(std::bind(&SysStatsDataSource::Tick, weak_this));
189 }
190
191 // static
Tick(base::WeakPtr<SysStatsDataSource> weak_this)192 void SysStatsDataSource::Tick(base::WeakPtr<SysStatsDataSource> weak_this) {
193 if (!weak_this)
194 return;
195 SysStatsDataSource& thiz = *weak_this;
196
197 uint32_t period_ms = thiz.tick_period_ms_;
198 uint32_t delay_ms =
199 period_ms -
200 static_cast<uint32_t>(base::GetWallTimeMs().count() % period_ms);
201 thiz.task_runner_->PostDelayedTask(
202 std::bind(&SysStatsDataSource::Tick, weak_this), delay_ms);
203 thiz.ReadSysStats();
204 }
205
206 SysStatsDataSource::~SysStatsDataSource() = default;
207
ReadSysStats()208 void SysStatsDataSource::ReadSysStats() {
209 PERFETTO_METATRACE_SCOPED(TAG_PROC_POLLERS, READ_SYS_STATS);
210 auto packet = writer_->NewTracePacket();
211
212 packet->set_timestamp(static_cast<uint64_t>(base::GetBootTimeNs().count()));
213 auto* sys_stats = packet->set_sys_stats();
214
215 if (meminfo_ticks_ && tick_ % meminfo_ticks_ == 0)
216 ReadMeminfo(sys_stats);
217
218 if (vmstat_ticks_ && tick_ % vmstat_ticks_ == 0)
219 ReadVmstat(sys_stats);
220
221 if (stat_ticks_ && tick_ % stat_ticks_ == 0)
222 ReadStat(sys_stats);
223
224 if (devfreq_ticks_ && tick_ % devfreq_ticks_ == 0)
225 ReadDevfreq(sys_stats);
226
227 if (cpufreq_ticks_ && tick_ % cpufreq_ticks_ == 0)
228 ReadCpufreq(sys_stats);
229
230 if (buddyinfo_ticks_ && tick_ % buddyinfo_ticks_ == 0)
231 ReadBuddyInfo(sys_stats);
232
233 if (diskstat_ticks_ && tick_ % diskstat_ticks_ == 0)
234 ReadDiskStat(sys_stats);
235
236 sys_stats->set_collection_end_timestamp(
237 static_cast<uint64_t>(base::GetBootTimeNs().count()));
238
239 tick_++;
240 }
241
ReadDiskStat(protos::pbzero::SysStats * sys_stats)242 void SysStatsDataSource::ReadDiskStat(protos::pbzero::SysStats* sys_stats) {
243 size_t rsize = ReadFile(&diskstat_fd_, "/proc/diskstats");
244 if (!rsize) {
245 return;
246 }
247
248 char* buf = static_cast<char*>(read_buf_.Get());
249 for (base::StringSplitter lines(buf, rsize, '\n'); lines.Next();) {
250 uint32_t index = 0;
251 auto* disk_stat = sys_stats->add_disk_stat();
252 for (base::StringSplitter words(&lines, ' '); words.Next(); index++) {
253 if (index == 2) { // index for device name (string)
254 disk_stat->set_device_name(words.cur_token());
255 } else if (index >= 5) { // integer values from index 5
256 std::optional<uint64_t> value_address =
257 base::CStringToUInt64(words.cur_token());
258 uint64_t value = value_address ? *value_address : 0;
259
260 switch (index) {
261 case 5:
262 disk_stat->set_read_sectors(value);
263 break;
264 case 6:
265 disk_stat->set_read_time_ms(value);
266 break;
267 case 9:
268 disk_stat->set_write_sectors(value);
269 break;
270 case 10:
271 disk_stat->set_write_time_ms(value);
272 break;
273 case 16:
274 disk_stat->set_discard_sectors(value);
275 break;
276 case 17:
277 disk_stat->set_discard_time_ms(value);
278 break;
279 case 18:
280 disk_stat->set_flush_count(value);
281 break;
282 case 19:
283 disk_stat->set_flush_time_ms(value);
284 break;
285 }
286
287 if (index == 19) {
288 break;
289 }
290 }
291 }
292 }
293 }
294
ReadBuddyInfo(protos::pbzero::SysStats * sys_stats)295 void SysStatsDataSource::ReadBuddyInfo(protos::pbzero::SysStats* sys_stats) {
296 size_t rsize = ReadFile(&buddy_fd_, "/proc/buddyinfo");
297 if (!rsize) {
298 return;
299 }
300
301 char* buf = static_cast<char*>(read_buf_.Get());
302 for (base::StringSplitter lines(buf, rsize, '\n'); lines.Next();) {
303 uint32_t index = 0;
304 auto* buddy_info = sys_stats->add_buddy_info();
305 for (base::StringSplitter words(&lines, ' '); words.Next();) {
306 if (index == 1) {
307 std::string token = words.cur_token();
308 token = token.substr(0, token.find(","));
309 buddy_info->set_node(token);
310 } else if (index == 3) {
311 buddy_info->set_zone(words.cur_token());
312 } else if (index > 3) {
313 buddy_info->add_order_pages(
314 static_cast<uint32_t>(strtoul(words.cur_token(), nullptr, 0)));
315 }
316 index++;
317 }
318 }
319 }
320
ReadDevfreq(protos::pbzero::SysStats * sys_stats)321 void SysStatsDataSource::ReadDevfreq(protos::pbzero::SysStats* sys_stats) {
322 base::ScopedDir devfreq_dir = OpenDevfreqDir();
323 if (devfreq_dir) {
324 while (struct dirent* dir_ent = readdir(*devfreq_dir)) {
325 // Entries in /sys/class/devfreq are symlinks to /devices/platform
326 if (dir_ent->d_type != DT_LNK)
327 continue;
328 const char* name = dir_ent->d_name;
329 const char* file_content = ReadDevfreqCurFreq(name);
330 auto value = static_cast<uint64_t>(strtoll(file_content, nullptr, 10));
331 auto* devfreq = sys_stats->add_devfreq();
332 devfreq->set_key(name);
333 devfreq->set_value(value);
334 }
335 }
336 }
337
ReadCpufreq(protos::pbzero::SysStats * sys_stats)338 void SysStatsDataSource::ReadCpufreq(protos::pbzero::SysStats* sys_stats) {
339 const auto& cpufreq = cpu_freq_info_->ReadCpuCurrFreq();
340
341 for (const auto& c : cpufreq)
342 sys_stats->add_cpufreq_khz(c);
343 }
344
OpenDevfreqDir()345 base::ScopedDir SysStatsDataSource::OpenDevfreqDir() {
346 const char* base_dir = "/sys/class/devfreq/";
347 base::ScopedDir devfreq_dir(opendir(base_dir));
348 if (!devfreq_dir && !devfreq_error_logged_) {
349 devfreq_error_logged_ = true;
350 PERFETTO_PLOG("failed to opendir(/sys/class/devfreq)");
351 }
352 return devfreq_dir;
353 }
354
ReadDevfreqCurFreq(const std::string & deviceName)355 const char* SysStatsDataSource::ReadDevfreqCurFreq(
356 const std::string& deviceName) {
357 const char* devfreq_base_path = "/sys/class/devfreq";
358 const char* freq_file_name = "cur_freq";
359 base::StackString<256> cur_freq_path("%s/%s/%s", devfreq_base_path,
360 deviceName.c_str(), freq_file_name);
361 base::ScopedFile fd = OpenReadOnly(cur_freq_path.c_str());
362 if (!fd && !devfreq_error_logged_) {
363 devfreq_error_logged_ = true;
364 PERFETTO_PLOG("Failed to open %s", cur_freq_path.c_str());
365 return "";
366 }
367 size_t rsize = ReadFile(&fd, cur_freq_path.c_str());
368 if (!rsize)
369 return "";
370 return static_cast<char*>(read_buf_.Get());
371 }
372
ReadMeminfo(protos::pbzero::SysStats * sys_stats)373 void SysStatsDataSource::ReadMeminfo(protos::pbzero::SysStats* sys_stats) {
374 size_t rsize = ReadFile(&meminfo_fd_, "/proc/meminfo");
375 if (!rsize)
376 return;
377 char* buf = static_cast<char*>(read_buf_.Get());
378 for (base::StringSplitter lines(buf, rsize, '\n'); lines.Next();) {
379 base::StringSplitter words(&lines, ' ');
380 if (!words.Next())
381 continue;
382 // Extract the meminfo key, dropping trailing ':' (e.g., "MemTotal: NN KB").
383 words.cur_token()[words.cur_token_size() - 1] = '\0';
384 auto it = meminfo_counters_.find(words.cur_token());
385 if (it == meminfo_counters_.end())
386 continue;
387 int counter_id = it->second;
388 if (!words.Next())
389 continue;
390 auto value = static_cast<uint64_t>(strtoll(words.cur_token(), nullptr, 10));
391 auto* meminfo = sys_stats->add_meminfo();
392 meminfo->set_key(static_cast<protos::pbzero::MeminfoCounters>(counter_id));
393 meminfo->set_value(value);
394 }
395 }
396
ReadVmstat(protos::pbzero::SysStats * sys_stats)397 void SysStatsDataSource::ReadVmstat(protos::pbzero::SysStats* sys_stats) {
398 size_t rsize = ReadFile(&vmstat_fd_, "/proc/vmstat");
399 if (!rsize)
400 return;
401 char* buf = static_cast<char*>(read_buf_.Get());
402 for (base::StringSplitter lines(buf, rsize, '\n'); lines.Next();) {
403 base::StringSplitter words(&lines, ' ');
404 if (!words.Next())
405 continue;
406 auto it = vmstat_counters_.find(words.cur_token());
407 if (it == vmstat_counters_.end())
408 continue;
409 int counter_id = it->second;
410 if (!words.Next())
411 continue;
412 auto value = static_cast<uint64_t>(strtoll(words.cur_token(), nullptr, 10));
413 auto* vmstat = sys_stats->add_vmstat();
414 vmstat->set_key(static_cast<protos::pbzero::VmstatCounters>(counter_id));
415 vmstat->set_value(value);
416 }
417 }
418
ReadStat(protos::pbzero::SysStats * sys_stats)419 void SysStatsDataSource::ReadStat(protos::pbzero::SysStats* sys_stats) {
420 size_t rsize = ReadFile(&stat_fd_, "/proc/stat");
421 if (!rsize)
422 return;
423 char* buf = static_cast<char*>(read_buf_.Get());
424 for (base::StringSplitter lines(buf, rsize, '\n'); lines.Next();) {
425 base::StringSplitter words(&lines, ' ');
426 if (!words.Next())
427 continue;
428
429 // Per-CPU stats.
430 if ((stat_enabled_fields_ & (1 << SysStatsConfig::STAT_CPU_TIMES)) &&
431 words.cur_token_size() > 3 && !strncmp(words.cur_token(), "cpu", 3)) {
432 long cpu_id = strtol(words.cur_token() + 3, nullptr, 10);
433 std::array<uint64_t, 7> cpu_times{};
434 for (size_t i = 0; i < cpu_times.size() && words.Next(); i++) {
435 cpu_times[i] =
436 static_cast<uint64_t>(strtoll(words.cur_token(), nullptr, 10));
437 }
438 auto* cpu_stat = sys_stats->add_cpu_stat();
439 cpu_stat->set_cpu_id(static_cast<uint32_t>(cpu_id));
440 cpu_stat->set_user_ns(cpu_times[0] * ns_per_user_hz_);
441 cpu_stat->set_user_ice_ns(cpu_times[1] * ns_per_user_hz_);
442 cpu_stat->set_system_mode_ns(cpu_times[2] * ns_per_user_hz_);
443 cpu_stat->set_idle_ns(cpu_times[3] * ns_per_user_hz_);
444 cpu_stat->set_io_wait_ns(cpu_times[4] * ns_per_user_hz_);
445 cpu_stat->set_irq_ns(cpu_times[5] * ns_per_user_hz_);
446 cpu_stat->set_softirq_ns(cpu_times[6] * ns_per_user_hz_);
447 }
448 // IRQ counters
449 else if ((stat_enabled_fields_ & (1 << SysStatsConfig::STAT_IRQ_COUNTS)) &&
450 !strcmp(words.cur_token(), "intr")) {
451 for (size_t i = 0; words.Next(); i++) {
452 auto v = static_cast<uint64_t>(strtoll(words.cur_token(), nullptr, 10));
453 if (i == 0) {
454 sys_stats->set_num_irq_total(v);
455 } else if (v > 0) {
456 auto* irq_stat = sys_stats->add_num_irq();
457 irq_stat->set_irq(static_cast<int32_t>(i - 1));
458 irq_stat->set_count(v);
459 }
460 }
461 }
462 // Softirq counters.
463 else if ((stat_enabled_fields_ &
464 (1 << SysStatsConfig::STAT_SOFTIRQ_COUNTS)) &&
465 !strcmp(words.cur_token(), "softirq")) {
466 for (size_t i = 0; words.Next(); i++) {
467 auto v = static_cast<uint64_t>(strtoll(words.cur_token(), nullptr, 10));
468 if (i == 0) {
469 sys_stats->set_num_softirq_total(v);
470 } else {
471 auto* softirq_stat = sys_stats->add_num_softirq();
472 softirq_stat->set_irq(static_cast<int32_t>(i - 1));
473 softirq_stat->set_count(v);
474 }
475 }
476 }
477 // Number of forked processes since boot.
478 else if ((stat_enabled_fields_ & (1 << SysStatsConfig::STAT_FORK_COUNT)) &&
479 !strcmp(words.cur_token(), "processes")) {
480 if (words.Next()) {
481 sys_stats->set_num_forks(
482 static_cast<uint64_t>(strtoll(words.cur_token(), nullptr, 10)));
483 }
484 }
485
486 } // for (line)
487 }
488
GetWeakPtr() const489 base::WeakPtr<SysStatsDataSource> SysStatsDataSource::GetWeakPtr() const {
490 return weak_factory_.GetWeakPtr();
491 }
492
Flush(FlushRequestID,std::function<void ()> callback)493 void SysStatsDataSource::Flush(FlushRequestID, std::function<void()> callback) {
494 writer_->Flush(callback);
495 }
496
ReadFile(base::ScopedFile * fd,const char * path)497 size_t SysStatsDataSource::ReadFile(base::ScopedFile* fd, const char* path) {
498 if (!*fd)
499 return 0;
500 ssize_t res = pread(**fd, read_buf_.Get(), kReadBufSize - 1, 0);
501 if (res <= 0) {
502 PERFETTO_PLOG("Failed reading %s", path);
503 fd->reset();
504 return 0;
505 }
506 size_t rsize = static_cast<size_t>(res);
507 static_cast<char*>(read_buf_.Get())[rsize] = '\0';
508 return rsize + 1; // Include null terminator in the count.
509 }
510
511 } // namespace perfetto
512