• 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/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 };
73 
SysStatsDataSource(base::TaskRunner * task_runner,TracingSessionID session_id,std::unique_ptr<TraceWriter> writer,const DataSourceConfig & ds_config,OpenFunction open_fn)74 SysStatsDataSource::SysStatsDataSource(base::TaskRunner* task_runner,
75                                        TracingSessionID session_id,
76                                        std::unique_ptr<TraceWriter> writer,
77                                        const DataSourceConfig& ds_config,
78                                        OpenFunction open_fn)
79     : ProbesDataSource(session_id, &descriptor),
80       task_runner_(task_runner),
81       writer_(std::move(writer)),
82       weak_factory_(this) {
83   ns_per_user_hz_ = 1000000000ull / static_cast<uint64_t>(sysconf(_SC_CLK_TCK));
84 
85   open_fn = open_fn ? open_fn : OpenReadOnly;
86   meminfo_fd_ = open_fn("/proc/meminfo");
87   vmstat_fd_ = open_fn("/proc/vmstat");
88   stat_fd_ = open_fn("/proc/stat");
89 
90   read_buf_ = base::PagedMemory::Allocate(kReadBufSize);
91 
92   // Build a lookup map that allows to quickly translate strings like "MemTotal"
93   // into the corresponding enum value, only for the counters enabled in the
94   // config.
95 
96   using protos::pbzero::SysStatsConfig;
97   SysStatsConfig::Decoder cfg(ds_config.sys_stats_config_raw());
98 
99   constexpr size_t kMaxMeminfoEnum = protos::pbzero::MeminfoCounters_MAX;
100   std::bitset<kMaxMeminfoEnum + 1> meminfo_counters_enabled{};
101   if (!cfg.has_meminfo_counters())
102     meminfo_counters_enabled.set();
103   for (auto it = cfg.meminfo_counters(); it; ++it) {
104     uint32_t counter = static_cast<uint32_t>(*it);
105     if (counter > 0 && counter <= kMaxMeminfoEnum) {
106       meminfo_counters_enabled.set(counter);
107     } else {
108       PERFETTO_DFATAL("Meminfo counter out of bounds %u", counter);
109     }
110   }
111   for (size_t i = 0; i < base::ArraySize(kMeminfoKeys); i++) {
112     const auto& k = kMeminfoKeys[i];
113     if (meminfo_counters_enabled[static_cast<size_t>(k.id)])
114       meminfo_counters_.emplace(k.str, k.id);
115   }
116 
117   constexpr size_t kMaxVmstatEnum = protos::pbzero::VmstatCounters_MAX;
118   std::bitset<kMaxVmstatEnum + 1> vmstat_counters_enabled{};
119   if (!cfg.has_vmstat_counters())
120     vmstat_counters_enabled.set();
121   for (auto it = cfg.vmstat_counters(); it; ++it) {
122     uint32_t counter = static_cast<uint32_t>(*it);
123     if (counter > 0 && counter <= kMaxVmstatEnum) {
124       vmstat_counters_enabled.set(counter);
125     } else {
126       PERFETTO_DFATAL("Vmstat counter out of bounds %u", counter);
127     }
128   }
129   for (size_t i = 0; i < base::ArraySize(kVmstatKeys); i++) {
130     const auto& k = kVmstatKeys[i];
131     if (vmstat_counters_enabled[static_cast<size_t>(k.id)])
132       vmstat_counters_.emplace(k.str, k.id);
133   }
134 
135   if (!cfg.has_stat_counters())
136     stat_enabled_fields_ = ~0u;
137   for (auto counter = cfg.stat_counters(); counter; ++counter) {
138     stat_enabled_fields_ |= 1ul << static_cast<uint32_t>(*counter);
139   }
140 
141   std::array<uint32_t, 3> periods_ms{};
142   std::array<uint32_t, 3> ticks{};
143   static_assert(periods_ms.size() == ticks.size(), "must have same size");
144 
145   periods_ms[0] = ClampTo10Ms(cfg.meminfo_period_ms(), "meminfo_period_ms");
146   periods_ms[1] = ClampTo10Ms(cfg.vmstat_period_ms(), "vmstat_period_ms");
147   periods_ms[2] = ClampTo10Ms(cfg.stat_period_ms(), "stat_period_ms");
148 
149   tick_period_ms_ = 0;
150   for (uint32_t ms : periods_ms) {
151     if (ms && (ms < tick_period_ms_ || tick_period_ms_ == 0))
152       tick_period_ms_ = ms;
153   }
154   if (tick_period_ms_ == 0)
155     return;  // No polling configured.
156 
157   for (size_t i = 0; i < periods_ms.size(); i++) {
158     auto ms = periods_ms[i];
159     if (ms && ms % tick_period_ms_ != 0) {
160       PERFETTO_ELOG("SysStat periods are not integer multiples of each other");
161       return;
162     }
163     ticks[i] = ms / tick_period_ms_;
164   }
165   meminfo_ticks_ = ticks[0];
166   vmstat_ticks_ = ticks[1];
167   stat_ticks_ = ticks[2];
168 }
169 
Start()170 void SysStatsDataSource::Start() {
171   auto weak_this = GetWeakPtr();
172   task_runner_->PostTask(std::bind(&SysStatsDataSource::Tick, weak_this));
173 }
174 
175 // static
Tick(base::WeakPtr<SysStatsDataSource> weak_this)176 void SysStatsDataSource::Tick(base::WeakPtr<SysStatsDataSource> weak_this) {
177   if (!weak_this)
178     return;
179   SysStatsDataSource& thiz = *weak_this;
180 
181   uint32_t period_ms = thiz.tick_period_ms_;
182   uint32_t delay_ms = period_ms - (base::GetWallTimeMs().count() % period_ms);
183   thiz.task_runner_->PostDelayedTask(
184       std::bind(&SysStatsDataSource::Tick, weak_this), delay_ms);
185   thiz.ReadSysStats();
186 }
187 
188 SysStatsDataSource::~SysStatsDataSource() = default;
189 
ReadSysStats()190 void SysStatsDataSource::ReadSysStats() {
191   PERFETTO_METATRACE_SCOPED(TAG_PROC_POLLERS, READ_SYS_STATS);
192   auto packet = writer_->NewTracePacket();
193 
194   packet->set_timestamp(static_cast<uint64_t>(base::GetBootTimeNs().count()));
195   auto* sys_stats = packet->set_sys_stats();
196 
197   if (meminfo_ticks_ && tick_ % meminfo_ticks_ == 0)
198     ReadMeminfo(sys_stats);
199 
200   if (vmstat_ticks_ && tick_ % vmstat_ticks_ == 0)
201     ReadVmstat(sys_stats);
202 
203   if (stat_ticks_ && tick_ % stat_ticks_ == 0)
204     ReadStat(sys_stats);
205 
206   sys_stats->set_collection_end_timestamp(
207       static_cast<uint64_t>(base::GetBootTimeNs().count()));
208 
209   tick_++;
210 }
211 
ReadMeminfo(protos::pbzero::SysStats * sys_stats)212 void SysStatsDataSource::ReadMeminfo(protos::pbzero::SysStats* sys_stats) {
213   size_t rsize = ReadFile(&meminfo_fd_, "/proc/meminfo");
214   if (!rsize)
215     return;
216   char* buf = static_cast<char*>(read_buf_.Get());
217   for (base::StringSplitter lines(buf, rsize, '\n'); lines.Next();) {
218     base::StringSplitter words(&lines, ' ');
219     if (!words.Next())
220       continue;
221     // Extract the meminfo key, dropping trailing ':' (e.g., "MemTotal: NN KB").
222     words.cur_token()[words.cur_token_size() - 1] = '\0';
223     auto it = meminfo_counters_.find(words.cur_token());
224     if (it == meminfo_counters_.end())
225       continue;
226     int counter_id = it->second;
227     if (!words.Next())
228       continue;
229     auto value = static_cast<uint64_t>(strtoll(words.cur_token(), nullptr, 10));
230     auto* meminfo = sys_stats->add_meminfo();
231     meminfo->set_key(static_cast<protos::pbzero::MeminfoCounters>(counter_id));
232     meminfo->set_value(value);
233   }
234 }
235 
ReadVmstat(protos::pbzero::SysStats * sys_stats)236 void SysStatsDataSource::ReadVmstat(protos::pbzero::SysStats* sys_stats) {
237   size_t rsize = ReadFile(&vmstat_fd_, "/proc/vmstat");
238   if (!rsize)
239     return;
240   char* buf = static_cast<char*>(read_buf_.Get());
241   for (base::StringSplitter lines(buf, rsize, '\n'); lines.Next();) {
242     base::StringSplitter words(&lines, ' ');
243     if (!words.Next())
244       continue;
245     auto it = vmstat_counters_.find(words.cur_token());
246     if (it == vmstat_counters_.end())
247       continue;
248     int counter_id = it->second;
249     if (!words.Next())
250       continue;
251     auto value = static_cast<uint64_t>(strtoll(words.cur_token(), nullptr, 10));
252     auto* vmstat = sys_stats->add_vmstat();
253     vmstat->set_key(static_cast<protos::pbzero::VmstatCounters>(counter_id));
254     vmstat->set_value(value);
255   }
256 }
257 
ReadStat(protos::pbzero::SysStats * sys_stats)258 void SysStatsDataSource::ReadStat(protos::pbzero::SysStats* sys_stats) {
259   size_t rsize = ReadFile(&stat_fd_, "/proc/stat");
260   if (!rsize)
261     return;
262   char* buf = static_cast<char*>(read_buf_.Get());
263   for (base::StringSplitter lines(buf, rsize, '\n'); lines.Next();) {
264     base::StringSplitter words(&lines, ' ');
265     if (!words.Next())
266       continue;
267 
268     // Per-CPU stats.
269     if ((stat_enabled_fields_ & (1 << SysStatsConfig::STAT_CPU_TIMES)) &&
270         words.cur_token_size() > 3 && !strncmp(words.cur_token(), "cpu", 3)) {
271       long cpu_id = strtol(words.cur_token() + 3, nullptr, 10);
272       std::array<uint64_t, 7> cpu_times{};
273       for (size_t i = 0; i < cpu_times.size() && words.Next(); i++) {
274         cpu_times[i] =
275             static_cast<uint64_t>(strtoll(words.cur_token(), nullptr, 10));
276       }
277       auto* cpu_stat = sys_stats->add_cpu_stat();
278       cpu_stat->set_cpu_id(static_cast<uint32_t>(cpu_id));
279       cpu_stat->set_user_ns(cpu_times[0] * ns_per_user_hz_);
280       cpu_stat->set_user_ice_ns(cpu_times[1] * ns_per_user_hz_);
281       cpu_stat->set_system_mode_ns(cpu_times[2] * ns_per_user_hz_);
282       cpu_stat->set_idle_ns(cpu_times[3] * ns_per_user_hz_);
283       cpu_stat->set_io_wait_ns(cpu_times[4] * ns_per_user_hz_);
284       cpu_stat->set_irq_ns(cpu_times[5] * ns_per_user_hz_);
285       cpu_stat->set_softirq_ns(cpu_times[6] * ns_per_user_hz_);
286     }
287     // IRQ counters
288     else if ((stat_enabled_fields_ & (1 << SysStatsConfig::STAT_IRQ_COUNTS)) &&
289              !strcmp(words.cur_token(), "intr")) {
290       for (size_t i = 0; words.Next(); i++) {
291         auto v = static_cast<uint64_t>(strtoll(words.cur_token(), nullptr, 10));
292         if (i == 0) {
293           sys_stats->set_num_irq_total(v);
294         } else if (v > 0) {
295           auto* irq_stat = sys_stats->add_num_irq();
296           irq_stat->set_irq(static_cast<int32_t>(i - 1));
297           irq_stat->set_count(v);
298         }
299       }
300     }
301     // Softirq counters.
302     else if ((stat_enabled_fields_ &
303               (1 << SysStatsConfig::STAT_SOFTIRQ_COUNTS)) &&
304              !strcmp(words.cur_token(), "softirq")) {
305       for (size_t i = 0; words.Next(); i++) {
306         auto v = static_cast<uint64_t>(strtoll(words.cur_token(), nullptr, 10));
307         if (i == 0) {
308           sys_stats->set_num_softirq_total(v);
309         } else {
310           auto* softirq_stat = sys_stats->add_num_softirq();
311           softirq_stat->set_irq(static_cast<int32_t>(i - 1));
312           softirq_stat->set_count(v);
313         }
314       }
315     }
316     // Number of forked processes since boot.
317     else if ((stat_enabled_fields_ & (1 << SysStatsConfig::STAT_FORK_COUNT)) &&
318              !strcmp(words.cur_token(), "processes")) {
319       if (words.Next()) {
320         sys_stats->set_num_forks(
321             static_cast<uint64_t>(strtoll(words.cur_token(), nullptr, 10)));
322       }
323     }
324 
325   }  // for (line)
326 }
327 
GetWeakPtr() const328 base::WeakPtr<SysStatsDataSource> SysStatsDataSource::GetWeakPtr() const {
329   return weak_factory_.GetWeakPtr();
330 }
331 
Flush(FlushRequestID,std::function<void ()> callback)332 void SysStatsDataSource::Flush(FlushRequestID, std::function<void()> callback) {
333   writer_->Flush(callback);
334 }
335 
ReadFile(base::ScopedFile * fd,const char * path)336 size_t SysStatsDataSource::ReadFile(base::ScopedFile* fd, const char* path) {
337   if (!*fd)
338     return 0;
339   ssize_t res = pread(**fd, read_buf_.Get(), kReadBufSize - 1, 0);
340   if (res <= 0) {
341     PERFETTO_PLOG("Failed reading %s", path);
342     fd->reset();
343     return 0;
344   }
345   size_t rsize = static_cast<size_t>(res);
346   static_cast<char*>(read_buf_.Get())[rsize] = '\0';
347   return rsize + 1;  // Include null terminator in the count.
348 }
349 
350 }  // namespace perfetto
351