xref: /aosp_15_r20/external/perfetto/src/traced/probes/sys_stats/sys_stats_data_source.cc (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
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 #include <algorithm>
22 #include <array>
23 #include <bitset>
24 #include <limits>
25 #include <utility>
26 
27 #include "perfetto/base/task_runner.h"
28 #include "perfetto/base/time.h"
29 #include "perfetto/ext/base/file_utils.h"
30 #include "perfetto/ext/base/metatrace.h"
31 #include "perfetto/ext/base/scoped_file.h"
32 #include "perfetto/ext/base/string_splitter.h"
33 #include "perfetto/ext/base/string_utils.h"
34 #include "perfetto/ext/base/utils.h"
35 #include "perfetto/ext/traced/sys_stats_counters.h"
36 
37 #include "protos/perfetto/common/sys_stats_counters.pbzero.h"
38 #include "protos/perfetto/config/sys_stats/sys_stats_config.pbzero.h"
39 #include "protos/perfetto/trace/sys_stats/sys_stats.pbzero.h"
40 #include "protos/perfetto/trace/trace_packet.pbzero.h"
41 
42 namespace perfetto {
43 
44 using protos::pbzero::SysStatsConfig;
45 
46 namespace {
47 constexpr size_t kReadBufSize = 1024 * 16;
48 
OpenReadOnly(const char * path)49 base::ScopedFile OpenReadOnly(const char* path) {
50   base::ScopedFile fd(base::OpenFile(path, O_RDONLY));
51   if (!fd)
52     PERFETTO_PLOG("Failed opening %s", path);
53   return fd;
54 }
55 
ClampTo10Ms(uint32_t period_ms,const char * counter_name)56 uint32_t ClampTo10Ms(uint32_t period_ms, const char* counter_name) {
57   if (period_ms > 0 && period_ms < 10) {
58     PERFETTO_ILOG("%s %" PRIu32
59                   " is less than minimum of 10ms. Increasing to 10ms.",
60                   counter_name, period_ms);
61     return 10;
62   }
63   return period_ms;
64 }
65 
66 }  // namespace
67 
68 // static
69 const ProbesDataSource::Descriptor SysStatsDataSource::descriptor = {
70     /*name*/ "linux.sys_stats",
71     /*flags*/ Descriptor::kFlagsNone,
72     /*fill_descriptor_func*/ nullptr,
73 };
74 
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)75 SysStatsDataSource::SysStatsDataSource(
76     base::TaskRunner* task_runner,
77     TracingSessionID session_id,
78     std::unique_ptr<TraceWriter> writer,
79     const DataSourceConfig& ds_config,
80     std::unique_ptr<CpuFreqInfo> cpu_freq_info,
81     OpenFunction open_fn)
82     : ProbesDataSource(session_id, &descriptor),
83       task_runner_(task_runner),
84       writer_(std::move(writer)),
85       cpu_freq_info_(std::move(cpu_freq_info)),
86       weak_factory_(this) {
87   ns_per_user_hz_ = 1000000000ull / static_cast<uint64_t>(sysconf(_SC_CLK_TCK));
88 
89   open_fn = open_fn ? open_fn : OpenReadOnly;
90   meminfo_fd_ = open_fn("/proc/meminfo");
91   vmstat_fd_ = open_fn("/proc/vmstat");
92   stat_fd_ = open_fn("/proc/stat");
93   buddy_fd_ = open_fn("/proc/buddyinfo");
94   diskstat_fd_ = open_fn("/proc/diskstats");
95   psi_cpu_fd_ = open_fn("/proc/pressure/cpu");
96   psi_io_fd_ = open_fn("/proc/pressure/io");
97   psi_memory_fd_ = open_fn("/proc/pressure/memory");
98   read_buf_ = base::PagedMemory::Allocate(kReadBufSize);
99 
100   // Build a lookup map that allows to quickly translate strings like "MemTotal"
101   // into the corresponding enum value, only for the counters enabled in the
102   // config.
103 
104   using protos::pbzero::SysStatsConfig;
105   SysStatsConfig::Decoder cfg(ds_config.sys_stats_config_raw());
106 
107   constexpr size_t kMaxMeminfoEnum = protos::pbzero::MeminfoCounters_MAX;
108   std::bitset<kMaxMeminfoEnum + 1> meminfo_counters_enabled{};
109   if (!cfg.has_meminfo_counters())
110     meminfo_counters_enabled.set();
111   for (auto it = cfg.meminfo_counters(); it; ++it) {
112     uint32_t counter = static_cast<uint32_t>(*it);
113     if (counter > 0 && counter <= kMaxMeminfoEnum) {
114       meminfo_counters_enabled.set(counter);
115     } else {
116       PERFETTO_DFATAL("Meminfo counter out of bounds %u", counter);
117     }
118   }
119   for (size_t i = 0; i < base::ArraySize(kMeminfoKeys); i++) {
120     const auto& k = kMeminfoKeys[i];
121     if (meminfo_counters_enabled[static_cast<size_t>(k.id)])
122       meminfo_counters_.emplace(k.str, k.id);
123   }
124 
125   constexpr size_t kMaxVmstatEnum = protos::pbzero::VmstatCounters_MAX;
126   std::bitset<kMaxVmstatEnum + 1> vmstat_counters_enabled{};
127   if (!cfg.has_vmstat_counters())
128     vmstat_counters_enabled.set();
129   for (auto it = cfg.vmstat_counters(); it; ++it) {
130     uint32_t counter = static_cast<uint32_t>(*it);
131     if (counter > 0 && counter <= kMaxVmstatEnum) {
132       vmstat_counters_enabled.set(counter);
133     } else {
134       PERFETTO_DFATAL("Vmstat counter out of bounds %u", counter);
135     }
136   }
137   for (size_t i = 0; i < base::ArraySize(kVmstatKeys); i++) {
138     const auto& k = kVmstatKeys[i];
139     if (vmstat_counters_enabled[static_cast<size_t>(k.id)])
140       vmstat_counters_.emplace(k.str, k.id);
141   }
142 
143   if (!cfg.has_stat_counters())
144     stat_enabled_fields_ = ~0u;
145   for (auto counter = cfg.stat_counters(); counter; ++counter) {
146     stat_enabled_fields_ |= 1ul << static_cast<uint32_t>(*counter);
147   }
148 
149   std::array<uint32_t, 11> periods_ms{};
150   std::array<uint32_t, 11> ticks{};
151   static_assert(periods_ms.size() == ticks.size(), "must have same size");
152 
153   periods_ms[0] = ClampTo10Ms(cfg.meminfo_period_ms(), "meminfo_period_ms");
154   periods_ms[1] = ClampTo10Ms(cfg.vmstat_period_ms(), "vmstat_period_ms");
155   periods_ms[2] = ClampTo10Ms(cfg.stat_period_ms(), "stat_period_ms");
156   periods_ms[3] = ClampTo10Ms(cfg.devfreq_period_ms(), "devfreq_period_ms");
157   periods_ms[4] = ClampTo10Ms(cfg.cpufreq_period_ms(), "cpufreq_period_ms");
158   periods_ms[5] = ClampTo10Ms(cfg.buddyinfo_period_ms(), "buddyinfo_period_ms");
159   periods_ms[6] = ClampTo10Ms(cfg.diskstat_period_ms(), "diskstat_period_ms");
160   periods_ms[7] = ClampTo10Ms(cfg.psi_period_ms(), "psi_period_ms");
161   periods_ms[8] = ClampTo10Ms(cfg.thermal_period_ms(), "thermal_period_ms");
162   periods_ms[9] = ClampTo10Ms(cfg.cpuidle_period_ms(), "cpuidle_period_ms");
163   periods_ms[10] = ClampTo10Ms(cfg.gpufreq_period_ms(), "gpufreq_period_ms");
164 
165   tick_period_ms_ = 0;
166   for (uint32_t ms : periods_ms) {
167     if (ms && (ms < tick_period_ms_ || tick_period_ms_ == 0))
168       tick_period_ms_ = ms;
169   }
170 
171   if (tick_period_ms_ == 0)
172     return;  // No polling configured.
173 
174   for (size_t i = 0; i < periods_ms.size(); i++) {
175     auto ms = periods_ms[i];
176     if (ms && ms % tick_period_ms_ != 0) {
177       PERFETTO_ELOG("SysStat periods are not integer multiples of each other");
178       return;
179     }
180     ticks[i] = ms / tick_period_ms_;
181   }
182   meminfo_ticks_ = ticks[0];
183   vmstat_ticks_ = ticks[1];
184   stat_ticks_ = ticks[2];
185   devfreq_ticks_ = ticks[3];
186   cpufreq_ticks_ = ticks[4];
187   buddyinfo_ticks_ = ticks[5];
188   diskstat_ticks_ = ticks[6];
189   psi_ticks_ = ticks[7];
190   thermal_ticks_ = ticks[8];
191   cpuidle_ticks_ = ticks[9];
192   gpufreq_ticks_ = ticks[10];
193 }
194 
Start()195 void SysStatsDataSource::Start() {
196   auto weak_this = GetWeakPtr();
197   task_runner_->PostTask(std::bind(&SysStatsDataSource::Tick, weak_this));
198 }
199 
200 // static
Tick(base::WeakPtr<SysStatsDataSource> weak_this)201 void SysStatsDataSource::Tick(base::WeakPtr<SysStatsDataSource> weak_this) {
202   if (!weak_this)
203     return;
204   SysStatsDataSource& thiz = *weak_this;
205 
206   uint32_t period_ms = thiz.tick_period_ms_;
207   uint32_t delay_ms =
208       period_ms -
209       static_cast<uint32_t>(base::GetWallTimeMs().count() % period_ms);
210   thiz.task_runner_->PostDelayedTask(
211       std::bind(&SysStatsDataSource::Tick, weak_this), delay_ms);
212   thiz.ReadSysStats();
213 }
214 
215 SysStatsDataSource::~SysStatsDataSource() = default;
216 
ReadSysStats()217 void SysStatsDataSource::ReadSysStats() {
218   PERFETTO_METATRACE_SCOPED(TAG_PROC_POLLERS, READ_SYS_STATS);
219   auto packet = writer_->NewTracePacket();
220 
221   packet->set_timestamp(static_cast<uint64_t>(base::GetBootTimeNs().count()));
222   auto* sys_stats = packet->set_sys_stats();
223 
224   if (meminfo_ticks_ && tick_ % meminfo_ticks_ == 0)
225     ReadMeminfo(sys_stats);
226 
227   if (vmstat_ticks_ && tick_ % vmstat_ticks_ == 0)
228     ReadVmstat(sys_stats);
229 
230   if (stat_ticks_ && tick_ % stat_ticks_ == 0)
231     ReadStat(sys_stats);
232 
233   if (devfreq_ticks_ && tick_ % devfreq_ticks_ == 0)
234     ReadDevfreq(sys_stats);
235 
236   if (cpufreq_ticks_ && tick_ % cpufreq_ticks_ == 0)
237     ReadCpufreq(sys_stats);
238 
239   if (buddyinfo_ticks_ && tick_ % buddyinfo_ticks_ == 0)
240     ReadBuddyInfo(sys_stats);
241 
242   if (diskstat_ticks_ && tick_ % diskstat_ticks_ == 0)
243     ReadDiskStat(sys_stats);
244 
245   if (psi_ticks_ && tick_ % psi_ticks_ == 0)
246     ReadPsi(sys_stats);
247 
248   if (thermal_ticks_ && tick_ % thermal_ticks_ == 0)
249     ReadThermalZones(sys_stats);
250 
251   if (cpuidle_ticks_ && tick_ % cpuidle_ticks_ == 0)
252     ReadCpuIdleStates(sys_stats);
253 
254   if (gpufreq_ticks_ && tick_ % gpufreq_ticks_ == 0)
255     ReadGpuFrequency(sys_stats);
256 
257   sys_stats->set_collection_end_timestamp(
258       static_cast<uint64_t>(base::GetBootTimeNs().count()));
259 
260   tick_++;
261 }
262 
OpenDirAndLogOnErrorOnce(const std::string & dir_path,bool * already_logged)263 base::ScopedDir SysStatsDataSource::OpenDirAndLogOnErrorOnce(
264     const std::string& dir_path,
265     bool* already_logged) {
266   base::ScopedDir dir(opendir(dir_path.c_str()));
267   if (!dir && !(*already_logged)) {
268     PERFETTO_PLOG("Failed to open %s", dir_path.c_str());
269     *already_logged = true;
270   }
271   return dir;
272 }
273 
ReadFileToString(const std::string & path)274 std::optional<std::string> SysStatsDataSource::ReadFileToString(
275     const std::string& path) {
276   base::ScopedFile fd = OpenReadOnly(path.c_str());
277   if (!fd) {
278     return std::nullopt;
279   }
280   size_t rsize = ReadFile(&fd, path.c_str());
281   if (!rsize)
282     return std::nullopt;
283   return base::StripSuffix(static_cast<char*>(read_buf_.Get()), "\n");
284 }
285 
ReadFileToUInt64(const std::string & path)286 std::optional<uint64_t> SysStatsDataSource::ReadFileToUInt64(
287     const std::string& path) {
288   base::ScopedFile fd = OpenReadOnly(path.c_str());
289   if (!fd) {
290     return std::nullopt;
291   }
292   size_t rsize = ReadFile(&fd, path.c_str());
293   if (!rsize)
294     return std::nullopt;
295 
296   return static_cast<uint64_t>(
297       strtoll(static_cast<char*>(read_buf_.Get()), nullptr, 10));
298 }
299 
ReadThermalZones(protos::pbzero::SysStats * sys_stats)300 void SysStatsDataSource::ReadThermalZones(protos::pbzero::SysStats* sys_stats) {
301   std::string base_dir = "/sys/class/thermal/";
302   base::ScopedDir thermal_dir =
303       OpenDirAndLogOnErrorOnce(base_dir, &thermal_error_logged_);
304   if (!thermal_dir) {
305     return;
306   }
307   while (struct dirent* dir_ent = readdir(*thermal_dir)) {
308     // Entries in /sys/class/thermal are symlinks to /devices/virtual
309     if (dir_ent->d_type != DT_LNK)
310       continue;
311     const char* name = dir_ent->d_name;
312     if (!base::StartsWith(name, "thermal_zone")) {
313       continue;
314     }
315     auto* thermal_zone = sys_stats->add_thermal_zone();
316     thermal_zone->set_name(name);
317     base::StackString<256> thermal_zone_temp_path("/sys/class/thermal/%s/temp",
318                                                   name);
319     auto temp = ReadFileToUInt64(thermal_zone_temp_path.ToStdString());
320     if (temp) {
321       thermal_zone->set_temp(*temp / 1000);
322     }
323     base::StackString<256> thermal_zone_type_path("/sys/class/thermal/%s/type",
324                                                   name);
325     auto type = ReadFileToString(thermal_zone_type_path.ToStdString());
326     if (type) {
327       thermal_zone->set_type(*type);
328     }
329   }
330 }
331 
ReadCpuIdleStates(protos::pbzero::SysStats * sys_stats)332 void SysStatsDataSource::ReadCpuIdleStates(
333     protos::pbzero::SysStats* sys_stats) {
334   std::string cpu_dir_path = "/sys/devices/system/cpu/";
335   base::ScopedDir cpu_dir =
336       OpenDirAndLogOnErrorOnce(cpu_dir_path, &cpuidle_error_logged_);
337   if (!cpu_dir) {
338     return;
339   }
340   // Iterate over all CPUs.
341   while (struct dirent* cpu_dir_ent = readdir(*cpu_dir)) {
342     const char* cpu_name = cpu_dir_ent->d_name;
343     if (!base::StartsWith(cpu_name, "cpu"))
344       continue;
345     auto maybe_cpu_index =
346         base::StringToUInt32(base::StripPrefix(cpu_name, "cpu"));
347     if (!maybe_cpu_index.has_value()) {
348       continue;
349     }
350     uint32_t cpu_id = maybe_cpu_index.value();
351 
352     auto* cpuidle_stats = sys_stats->add_cpuidle_state();
353     cpuidle_stats->set_cpu_id(cpu_id);
354     std::string cpuidle_path =
355         "/sys/devices/system/cpu/" + std::string(cpu_name) + "/cpuidle/";
356     base::ScopedDir cpu_state_dir =
357         OpenDirAndLogOnErrorOnce(cpuidle_path, &cpuidle_error_logged_);
358     if (!cpu_state_dir) {
359       return;
360     }
361     // Iterate over all CPU idle states.
362     while (struct dirent* state_dir_ent = readdir(*cpu_state_dir)) {
363       const char* state_name = state_dir_ent->d_name;
364       if (!base::StartsWith(state_name, "state"))
365         continue;
366       base::StackString<256> cpuidle_state_name_path(
367           "/sys/devices/system/cpu/%s/cpuidle/%s/name", cpu_name, state_name);
368       auto cpuidle_state_name =
369           ReadFileToString(cpuidle_state_name_path.ToStdString());
370 
371       base::StackString<256> cpuidle_state_time_path(
372           "/sys/devices/system/cpu/%s/cpuidle/%s/time", cpu_name, state_name);
373       auto time = ReadFileToUInt64(cpuidle_state_time_path.ToStdString());
374       if (!cpuidle_state_name || !time) {
375         continue;
376       }
377       auto cpuidle_state = cpuidle_stats->add_cpuidle_state_entry();
378       cpuidle_state->set_state(*cpuidle_state_name);
379       cpuidle_state->set_duration_us(*time);
380     }
381   }
382 }
383 
ReadAMDGpuFreq()384 std::optional<uint64_t> SysStatsDataSource::ReadAMDGpuFreq() {
385   std::optional<std::string> amd_gpu_freq =
386       ReadFileToString("/sys/class/drm/card0/device/pp_dpm_sclk");
387   if (!amd_gpu_freq) {
388     return std::nullopt;
389   }
390   for (base::StringSplitter lines(*amd_gpu_freq, '\n'); lines.Next();) {
391     base::StringView line(lines.cur_token(), lines.cur_token_size());
392     // Current frequency indicated with asterisk.
393     if (line.EndsWith("*")) {
394       for (base::StringSplitter words(line.ToStdString(), ' '); words.Next();) {
395         if (!base::EndsWith(words.cur_token(), "Mhz"))
396           continue;
397         // Strip suffix "Mhz".
398         std::string maybe_freq = std::string(words.cur_token())
399                                      .substr(0, words.cur_token_size() - 3);
400         auto freq = base::StringToUInt32(maybe_freq);
401         return freq;
402       }
403     }
404   }
405   return std::nullopt;
406 }
407 
ReadGpuFrequency(protos::pbzero::SysStats * sys_stats)408 void SysStatsDataSource::ReadGpuFrequency(protos::pbzero::SysStats* sys_stats) {
409   std::optional<uint64_t> freq;
410   // Intel GPU Frequency.
411   freq = ReadFileToUInt64("/sys/class/drm/card0/gt_act_freq_mhz");
412   if (freq) {
413     sys_stats->add_gpufreq_mhz((*freq));
414     return;
415   }
416   freq = ReadAMDGpuFreq();
417   if (freq) {
418     sys_stats->add_gpufreq_mhz((*freq));
419   }
420 }
421 
ReadDiskStat(protos::pbzero::SysStats * sys_stats)422 void SysStatsDataSource::ReadDiskStat(protos::pbzero::SysStats* sys_stats) {
423   size_t rsize = ReadFile(&diskstat_fd_, "/proc/diskstats");
424   if (!rsize) {
425     return;
426   }
427 
428   char* buf = static_cast<char*>(read_buf_.Get());
429   for (base::StringSplitter lines(buf, rsize, '\n'); lines.Next();) {
430     uint32_t index = 0;
431     auto* disk_stat = sys_stats->add_disk_stat();
432     for (base::StringSplitter words(&lines, ' '); words.Next(); index++) {
433       if (index == 2) {  // index for device name (string)
434         disk_stat->set_device_name(words.cur_token());
435       } else if (index >= 5) {  // integer values from index 5
436         std::optional<uint64_t> value_address =
437             base::CStringToUInt64(words.cur_token());
438         uint64_t value = value_address ? *value_address : 0;
439 
440         switch (index) {
441           case 5:
442             disk_stat->set_read_sectors(value);
443             break;
444           case 6:
445             disk_stat->set_read_time_ms(value);
446             break;
447           case 9:
448             disk_stat->set_write_sectors(value);
449             break;
450           case 10:
451             disk_stat->set_write_time_ms(value);
452             break;
453           case 16:
454             disk_stat->set_discard_sectors(value);
455             break;
456           case 17:
457             disk_stat->set_discard_time_ms(value);
458             break;
459           case 18:
460             disk_stat->set_flush_count(value);
461             break;
462           case 19:
463             disk_stat->set_flush_time_ms(value);
464             break;
465         }
466 
467         if (index == 19) {
468           break;
469         }
470       }
471     }
472   }
473 }
474 
ReadPsi(protos::pbzero::SysStats * sys_stats)475 void SysStatsDataSource::ReadPsi(protos::pbzero::SysStats* sys_stats) {
476   using PsiSample = protos::pbzero::SysStats::PsiSample;
477 
478   auto read_psi_resource = [this, sys_stats](
479                                base::ScopedFile* file, const char* path,
480                                PsiSample::PsiResource resource_some,
481                                PsiSample::PsiResource resource_full) {
482     size_t rsize = ReadFile(file, path);
483     if (!rsize) {
484       return;
485     }
486 
487     char* buf = static_cast<char*>(read_buf_.Get());
488     for (base::StringSplitter lines(buf, rsize, '\n'); lines.Next();) {
489       uint32_t index = 0;
490       auto* psi = sys_stats->add_psi();
491       for (base::StringSplitter words(&lines, ' '); words.Next(); ++index) {
492         base::StringView token(words.cur_token(), words.cur_token_size());
493         // A single line is of the form (note we skip avg parsing, indexes 1-3):
494         //     some avg10=0.00 avg60=0.00 avg300=0.00 total=0
495         if (index == 0) {
496           auto resource = token == "some" ? resource_some
497                         : token == "full" ? resource_full
498                                           : PsiSample::PSI_RESOURCE_UNSPECIFIED;
499           psi->set_resource(resource);
500         } else if (index == 4) {
501           const base::StringView prefix("total=");
502           if (token.StartsWith(prefix)) {
503             token = token.substr(prefix.size());
504           }
505           // The raw PSI total readings are in micros, so convert accordingly.
506           std::optional<uint64_t> total_us =
507               base::CStringToUInt64(token.data());
508           uint64_t total_ns = total_us ? *total_us * 1000 : 0;
509           psi->set_total_ns(total_ns);
510         } else if (index > 4) {
511           break;
512         }
513       }
514     }
515   };
516 
517   read_psi_resource(&psi_cpu_fd_, "/proc/pressure/cpu",
518                     PsiSample::PSI_RESOURCE_CPU_SOME,
519                     PsiSample::PSI_RESOURCE_CPU_FULL);
520   read_psi_resource(&psi_io_fd_, "/proc/pressure/io",
521                     PsiSample::PSI_RESOURCE_IO_SOME,
522                     PsiSample::PSI_RESOURCE_IO_FULL);
523   read_psi_resource(&psi_memory_fd_, "/proc/pressure/memory",
524                     PsiSample::PSI_RESOURCE_MEMORY_SOME,
525                     PsiSample::PSI_RESOURCE_MEMORY_FULL);
526 }
527 
ReadBuddyInfo(protos::pbzero::SysStats * sys_stats)528 void SysStatsDataSource::ReadBuddyInfo(protos::pbzero::SysStats* sys_stats) {
529   size_t rsize = ReadFile(&buddy_fd_, "/proc/buddyinfo");
530   if (!rsize) {
531     return;
532   }
533 
534   char* buf = static_cast<char*>(read_buf_.Get());
535   for (base::StringSplitter lines(buf, rsize, '\n'); lines.Next();) {
536     uint32_t index = 0;
537     auto* buddy_info = sys_stats->add_buddy_info();
538     for (base::StringSplitter words(&lines, ' '); words.Next();) {
539       if (index == 1) {
540         std::string token = words.cur_token();
541         token = token.substr(0, token.find(","));
542         buddy_info->set_node(token);
543       } else if (index == 3) {
544         buddy_info->set_zone(words.cur_token());
545       } else if (index > 3) {
546         buddy_info->add_order_pages(
547             static_cast<uint32_t>(strtoul(words.cur_token(), nullptr, 0)));
548       }
549       index++;
550     }
551   }
552 }
553 
ReadDevfreq(protos::pbzero::SysStats * sys_stats)554 void SysStatsDataSource::ReadDevfreq(protos::pbzero::SysStats* sys_stats) {
555   std::string base_dir = "/sys/class/devfreq/";
556   base::ScopedDir devfreq_dir =
557       OpenDirAndLogOnErrorOnce(base_dir, &devfreq_error_logged_);
558   if (!devfreq_dir) {
559     return;
560   }
561   while (struct dirent* dir_ent = readdir(*devfreq_dir)) {
562     // Entries in /sys/class/devfreq are symlinks to /devices/platform
563     if (dir_ent->d_type != DT_LNK)
564       continue;
565     const char* name = dir_ent->d_name;
566     const char* file_content = ReadDevfreqCurFreq(name);
567     auto value = static_cast<uint64_t>(strtoll(file_content, nullptr, 10));
568     auto* devfreq = sys_stats->add_devfreq();
569     devfreq->set_key(name);
570     devfreq->set_value(value);
571   }
572 }
573 
ReadCpufreq(protos::pbzero::SysStats * sys_stats)574 void SysStatsDataSource::ReadCpufreq(protos::pbzero::SysStats* sys_stats) {
575   const auto& cpufreq = cpu_freq_info_->ReadCpuCurrFreq();
576 
577   for (const auto& c : cpufreq)
578     sys_stats->add_cpufreq_khz(c);
579 }
580 
ReadDevfreqCurFreq(const std::string & deviceName)581 const char* SysStatsDataSource::ReadDevfreqCurFreq(
582     const std::string& deviceName) {
583   const char* devfreq_base_path = "/sys/class/devfreq";
584   const char* freq_file_name = "cur_freq";
585   base::StackString<256> cur_freq_path("%s/%s/%s", devfreq_base_path,
586                                        deviceName.c_str(), freq_file_name);
587   base::ScopedFile fd = OpenReadOnly(cur_freq_path.c_str());
588   if (!fd && !devfreq_error_logged_) {
589     devfreq_error_logged_ = true;
590     PERFETTO_PLOG("Failed to open %s", cur_freq_path.c_str());
591     return "";
592   }
593   size_t rsize = ReadFile(&fd, cur_freq_path.c_str());
594   if (!rsize)
595     return "";
596   return static_cast<char*>(read_buf_.Get());
597 }
598 
ReadMeminfo(protos::pbzero::SysStats * sys_stats)599 void SysStatsDataSource::ReadMeminfo(protos::pbzero::SysStats* sys_stats) {
600   size_t rsize = ReadFile(&meminfo_fd_, "/proc/meminfo");
601   if (!rsize)
602     return;
603   char* buf = static_cast<char*>(read_buf_.Get());
604   for (base::StringSplitter lines(buf, rsize, '\n'); lines.Next();) {
605     base::StringSplitter words(&lines, ' ');
606     if (!words.Next())
607       continue;
608     // Extract the meminfo key, dropping trailing ':' (e.g., "MemTotal: NN KB").
609     words.cur_token()[words.cur_token_size() - 1] = '\0';
610     auto it = meminfo_counters_.find(words.cur_token());
611     if (it == meminfo_counters_.end())
612       continue;
613     int counter_id = it->second;
614     if (!words.Next())
615       continue;
616     auto value = static_cast<uint64_t>(strtoll(words.cur_token(), nullptr, 10));
617     auto* meminfo = sys_stats->add_meminfo();
618     meminfo->set_key(static_cast<protos::pbzero::MeminfoCounters>(counter_id));
619     meminfo->set_value(value);
620   }
621 }
622 
ReadVmstat(protos::pbzero::SysStats * sys_stats)623 void SysStatsDataSource::ReadVmstat(protos::pbzero::SysStats* sys_stats) {
624   size_t rsize = ReadFile(&vmstat_fd_, "/proc/vmstat");
625   if (!rsize)
626     return;
627   char* buf = static_cast<char*>(read_buf_.Get());
628   for (base::StringSplitter lines(buf, rsize, '\n'); lines.Next();) {
629     base::StringSplitter words(&lines, ' ');
630     if (!words.Next())
631       continue;
632     auto it = vmstat_counters_.find(words.cur_token());
633     if (it == vmstat_counters_.end())
634       continue;
635     int counter_id = it->second;
636     if (!words.Next())
637       continue;
638     auto value = static_cast<uint64_t>(strtoll(words.cur_token(), nullptr, 10));
639     auto* vmstat = sys_stats->add_vmstat();
640     vmstat->set_key(static_cast<protos::pbzero::VmstatCounters>(counter_id));
641     vmstat->set_value(value);
642   }
643 }
644 
ReadStat(protos::pbzero::SysStats * sys_stats)645 void SysStatsDataSource::ReadStat(protos::pbzero::SysStats* sys_stats) {
646   size_t rsize = ReadFile(&stat_fd_, "/proc/stat");
647   if (!rsize)
648     return;
649   char* buf = static_cast<char*>(read_buf_.Get());
650   for (base::StringSplitter lines(buf, rsize, '\n'); lines.Next();) {
651     base::StringSplitter words(&lines, ' ');
652     if (!words.Next())
653       continue;
654 
655     // Per-CPU stats.
656     if ((stat_enabled_fields_ & (1 << SysStatsConfig::STAT_CPU_TIMES)) &&
657         words.cur_token_size() > 3 && !strncmp(words.cur_token(), "cpu", 3)) {
658       long cpu_id = strtol(words.cur_token() + 3, nullptr, 10);
659       std::array<uint64_t, 7> cpu_times{};
660       for (size_t i = 0; i < cpu_times.size() && words.Next(); i++) {
661         cpu_times[i] =
662             static_cast<uint64_t>(strtoll(words.cur_token(), nullptr, 10));
663       }
664       auto* cpu_stat = sys_stats->add_cpu_stat();
665       cpu_stat->set_cpu_id(static_cast<uint32_t>(cpu_id));
666       cpu_stat->set_user_ns(cpu_times[0] * ns_per_user_hz_);
667       cpu_stat->set_user_nice_ns(cpu_times[1] * ns_per_user_hz_);
668       cpu_stat->set_system_mode_ns(cpu_times[2] * ns_per_user_hz_);
669       cpu_stat->set_idle_ns(cpu_times[3] * ns_per_user_hz_);
670       cpu_stat->set_io_wait_ns(cpu_times[4] * ns_per_user_hz_);
671       cpu_stat->set_irq_ns(cpu_times[5] * ns_per_user_hz_);
672       cpu_stat->set_softirq_ns(cpu_times[6] * ns_per_user_hz_);
673     }
674     // IRQ counters
675     else if ((stat_enabled_fields_ & (1 << SysStatsConfig::STAT_IRQ_COUNTS)) &&
676              !strcmp(words.cur_token(), "intr")) {
677       for (size_t i = 0; words.Next(); i++) {
678         auto v = static_cast<uint64_t>(strtoll(words.cur_token(), nullptr, 10));
679         if (i == 0) {
680           sys_stats->set_num_irq_total(v);
681         } else if (v > 0) {
682           auto* irq_stat = sys_stats->add_num_irq();
683           irq_stat->set_irq(static_cast<int32_t>(i - 1));
684           irq_stat->set_count(v);
685         }
686       }
687     }
688     // Softirq counters.
689     else if ((stat_enabled_fields_ &
690               (1 << SysStatsConfig::STAT_SOFTIRQ_COUNTS)) &&
691              !strcmp(words.cur_token(), "softirq")) {
692       for (size_t i = 0; words.Next(); i++) {
693         auto v = static_cast<uint64_t>(strtoll(words.cur_token(), nullptr, 10));
694         if (i == 0) {
695           sys_stats->set_num_softirq_total(v);
696         } else {
697           auto* softirq_stat = sys_stats->add_num_softirq();
698           softirq_stat->set_irq(static_cast<int32_t>(i - 1));
699           softirq_stat->set_count(v);
700         }
701       }
702     }
703     // Number of forked processes since boot.
704     else if ((stat_enabled_fields_ & (1 << SysStatsConfig::STAT_FORK_COUNT)) &&
705              !strcmp(words.cur_token(), "processes")) {
706       if (words.Next()) {
707         sys_stats->set_num_forks(
708             static_cast<uint64_t>(strtoll(words.cur_token(), nullptr, 10)));
709       }
710     }
711 
712   }  // for (line)
713 }
714 
GetWeakPtr() const715 base::WeakPtr<SysStatsDataSource> SysStatsDataSource::GetWeakPtr() const {
716   return weak_factory_.GetWeakPtr();
717 }
718 
Flush(FlushRequestID,std::function<void ()> callback)719 void SysStatsDataSource::Flush(FlushRequestID, std::function<void()> callback) {
720   writer_->Flush(callback);
721 }
722 
ReadFile(base::ScopedFile * fd,const char * path)723 size_t SysStatsDataSource::ReadFile(base::ScopedFile* fd, const char* path) {
724   if (!*fd)
725     return 0;
726   ssize_t res = pread(**fd, read_buf_.Get(), kReadBufSize - 1, 0);
727   if (res <= 0) {
728     PERFETTO_PLOG("Failed reading %s", path);
729     fd->reset();
730     return 0;
731   }
732   size_t rsize = static_cast<size_t>(res);
733   static_cast<char*>(read_buf_.Get())[rsize] = '\0';
734   return rsize + 1;  // Include null terminator in the count.
735 }
736 
737 }  // namespace perfetto
738