• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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