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 <limits>
25 #include <utility>
26
27 #include "perfetto/base/file_utils.h"
28 #include "perfetto/base/metatrace.h"
29 #include "perfetto/base/scoped_file.h"
30 #include "perfetto/base/string_splitter.h"
31 #include "perfetto/base/task_runner.h"
32 #include "perfetto/base/time.h"
33 #include "perfetto/base/utils.h"
34 #include "perfetto/traced/sys_stats_counters.h"
35 #include "perfetto/tracing/core/sys_stats_config.h"
36
37 #include "perfetto/common/sys_stats_counters.pb.h"
38 #include "perfetto/config/sys_stats/sys_stats_config.pb.h"
39 #include "perfetto/trace/sys_stats/sys_stats.pbzero.h"
40 #include "perfetto/trace/trace_packet.pbzero.h"
41
42 namespace perfetto {
43
44 namespace {
45 constexpr size_t kReadBufSize = 1024 * 16;
46
OpenReadOnly(const char * path)47 base::ScopedFile OpenReadOnly(const char* path) {
48 base::ScopedFile fd(base::OpenFile(path, O_RDONLY));
49 if (!fd)
50 PERFETTO_PLOG("Failed opening %s", path);
51 return fd;
52 }
53
ClampTo10Ms(uint32_t period_ms,const char * counter_name)54 uint32_t ClampTo10Ms(uint32_t period_ms, const char* counter_name) {
55 if (period_ms > 0 && period_ms < 10) {
56 PERFETTO_ILOG("%s %" PRIu32
57 " is less than minimum of 10ms. Increasing to 10ms.",
58 counter_name, period_ms);
59 return 10;
60 }
61 return period_ms;
62 }
63
64 } // namespace
65
66 // static
67 constexpr int SysStatsDataSource::kTypeId;
68
SysStatsDataSource(base::TaskRunner * task_runner,TracingSessionID session_id,std::unique_ptr<TraceWriter> writer,const DataSourceConfig & ds_config,OpenFunction open_fn)69 SysStatsDataSource::SysStatsDataSource(base::TaskRunner* task_runner,
70 TracingSessionID session_id,
71 std::unique_ptr<TraceWriter> writer,
72 const DataSourceConfig& ds_config,
73 OpenFunction open_fn)
74 : ProbesDataSource(session_id, kTypeId),
75 task_runner_(task_runner),
76 writer_(std::move(writer)),
77 weak_factory_(this) {
78 const auto& config = ds_config.sys_stats_config();
79
80 ns_per_user_hz_ = 1000000000ull / static_cast<uint64_t>(sysconf(_SC_CLK_TCK));
81
82 open_fn = open_fn ? open_fn : OpenReadOnly;
83 meminfo_fd_ = open_fn("/proc/meminfo");
84 vmstat_fd_ = open_fn("/proc/vmstat");
85 stat_fd_ = open_fn("/proc/stat");
86
87 read_buf_ = base::PagedMemory::Allocate(kReadBufSize);
88
89 // Build a lookup map that allows to quickly translate strings like "MemTotal"
90 // into the corresponding enum value, only for the counters enabled in the
91 // config.
92 for (const auto& counter_id : config.meminfo_counters()) {
93 for (size_t i = 0; i < base::ArraySize(kMeminfoKeys); i++) {
94 const auto& k = kMeminfoKeys[i];
95 if (static_cast<int>(k.id) == static_cast<int>(counter_id))
96 meminfo_counters_.emplace(k.str, k.id);
97 }
98 }
99
100 for (const auto& counter_id : config.vmstat_counters()) {
101 for (size_t i = 0; i < base::ArraySize(kVmstatKeys); i++) {
102 const auto& k = kVmstatKeys[i];
103 if (static_cast<int>(k.id) == static_cast<int>(counter_id))
104 vmstat_counters_.emplace(k.str, k.id);
105 }
106 }
107
108 for (const auto& counter_id : config.stat_counters()) {
109 stat_enabled_fields_ |= 1 << counter_id;
110 }
111
112 std::array<uint32_t, 3> periods_ms{};
113 std::array<uint32_t, 3> ticks{};
114 static_assert(periods_ms.size() == ticks.size(), "must have same size");
115
116 periods_ms[0] = ClampTo10Ms(config.meminfo_period_ms(), "meminfo_period_ms");
117 periods_ms[1] = ClampTo10Ms(config.vmstat_period_ms(), "vmstat_period_ms");
118 periods_ms[2] = ClampTo10Ms(config.stat_period_ms(), "stat_period_ms");
119
120 tick_period_ms_ = 0;
121 for (uint32_t ms : periods_ms) {
122 if (ms && (ms < tick_period_ms_ || tick_period_ms_ == 0))
123 tick_period_ms_ = ms;
124 }
125 if (tick_period_ms_ == 0)
126 return; // No polling configured.
127
128 for (size_t i = 0; i < periods_ms.size(); i++) {
129 auto ms = periods_ms[i];
130 if (ms && ms % tick_period_ms_ != 0) {
131 PERFETTO_ELOG("SysStat periods are not integer multiples of each other");
132 return;
133 }
134 ticks[i] = ms / tick_period_ms_;
135 }
136 meminfo_ticks_ = ticks[0];
137 vmstat_ticks_ = ticks[1];
138 stat_ticks_ = ticks[2];
139 }
140
Start()141 void SysStatsDataSource::Start() {
142 auto weak_this = GetWeakPtr();
143 task_runner_->PostTask(std::bind(&SysStatsDataSource::Tick, weak_this));
144 }
145
146 // static
Tick(base::WeakPtr<SysStatsDataSource> weak_this)147 void SysStatsDataSource::Tick(base::WeakPtr<SysStatsDataSource> weak_this) {
148 if (!weak_this)
149 return;
150 SysStatsDataSource& thiz = *weak_this;
151
152 uint32_t period_ms = thiz.tick_period_ms_;
153 uint32_t delay_ms = period_ms - (base::GetWallTimeMs().count() % period_ms);
154 thiz.task_runner_->PostDelayedTask(
155 std::bind(&SysStatsDataSource::Tick, weak_this), delay_ms);
156 thiz.ReadSysStats();
157 }
158
159 SysStatsDataSource::~SysStatsDataSource() = default;
160
ReadSysStats()161 void SysStatsDataSource::ReadSysStats() {
162 PERFETTO_METATRACE("ReadSysStats", 0);
163 auto packet = writer_->NewTracePacket();
164
165 packet->set_timestamp(static_cast<uint64_t>(base::GetBootTimeNs().count()));
166 auto* sys_stats = packet->set_sys_stats();
167
168 if (meminfo_ticks_ && tick_ % meminfo_ticks_ == 0)
169 ReadMeminfo(sys_stats);
170
171 if (vmstat_ticks_ && tick_ % vmstat_ticks_ == 0)
172 ReadVmstat(sys_stats);
173
174 if (stat_ticks_ && tick_ % stat_ticks_ == 0)
175 ReadStat(sys_stats);
176
177 sys_stats->set_collection_end_timestamp(
178 static_cast<uint64_t>(base::GetBootTimeNs().count()));
179
180 tick_++;
181 }
182
ReadMeminfo(protos::pbzero::SysStats * sys_stats)183 void SysStatsDataSource::ReadMeminfo(protos::pbzero::SysStats* sys_stats) {
184 size_t rsize = ReadFile(&meminfo_fd_, "/proc/meminfo");
185 if (!rsize)
186 return;
187 char* buf = static_cast<char*>(read_buf_.Get());
188 for (base::StringSplitter lines(buf, rsize, '\n'); lines.Next();) {
189 base::StringSplitter words(&lines, ' ');
190 if (!words.Next())
191 continue;
192 // Extract the meminfo key, dropping trailing ':' (e.g., "MemTotal: NN KB").
193 words.cur_token()[words.cur_token_size() - 1] = '\0';
194 auto it = meminfo_counters_.find(words.cur_token());
195 if (it == meminfo_counters_.end())
196 continue;
197 int counter_id = it->second;
198 if (!words.Next())
199 continue;
200 auto value = static_cast<uint64_t>(strtoll(words.cur_token(), nullptr, 10));
201 auto* meminfo = sys_stats->add_meminfo();
202 meminfo->set_key(static_cast<protos::pbzero::MeminfoCounters>(counter_id));
203 meminfo->set_value(value);
204 }
205 }
206
ReadVmstat(protos::pbzero::SysStats * sys_stats)207 void SysStatsDataSource::ReadVmstat(protos::pbzero::SysStats* sys_stats) {
208 size_t rsize = ReadFile(&vmstat_fd_, "/proc/vmstat");
209 if (!rsize)
210 return;
211 char* buf = static_cast<char*>(read_buf_.Get());
212 for (base::StringSplitter lines(buf, rsize, '\n'); lines.Next();) {
213 base::StringSplitter words(&lines, ' ');
214 if (!words.Next())
215 continue;
216 auto it = vmstat_counters_.find(words.cur_token());
217 if (it == vmstat_counters_.end())
218 continue;
219 int counter_id = it->second;
220 if (!words.Next())
221 continue;
222 auto value = static_cast<uint64_t>(strtoll(words.cur_token(), nullptr, 10));
223 auto* vmstat = sys_stats->add_vmstat();
224 vmstat->set_key(static_cast<protos::pbzero::VmstatCounters>(counter_id));
225 vmstat->set_value(value);
226 }
227 }
228
ReadStat(protos::pbzero::SysStats * sys_stats)229 void SysStatsDataSource::ReadStat(protos::pbzero::SysStats* sys_stats) {
230 size_t rsize = ReadFile(&stat_fd_, "/proc/stat");
231 if (!rsize)
232 return;
233 char* buf = static_cast<char*>(read_buf_.Get());
234 for (base::StringSplitter lines(buf, rsize, '\n'); lines.Next();) {
235 base::StringSplitter words(&lines, ' ');
236 if (!words.Next())
237 continue;
238
239 // Per-CPU stats.
240 if ((stat_enabled_fields_ & (1 << SysStatsConfig::STAT_CPU_TIMES)) &&
241 words.cur_token_size() > 3 && !strncmp(words.cur_token(), "cpu", 3)) {
242 long cpu_id = strtol(words.cur_token() + 3, nullptr, 10);
243 std::array<uint64_t, 7> cpu_times{};
244 for (size_t i = 0; i < cpu_times.size() && words.Next(); i++) {
245 cpu_times[i] =
246 static_cast<uint64_t>(strtoll(words.cur_token(), nullptr, 10));
247 }
248 auto* cpu_stat = sys_stats->add_cpu_stat();
249 cpu_stat->set_cpu_id(static_cast<uint32_t>(cpu_id));
250 cpu_stat->set_user_ns(cpu_times[0] * ns_per_user_hz_);
251 cpu_stat->set_user_ice_ns(cpu_times[1] * ns_per_user_hz_);
252 cpu_stat->set_system_mode_ns(cpu_times[2] * ns_per_user_hz_);
253 cpu_stat->set_idle_ns(cpu_times[3] * ns_per_user_hz_);
254 cpu_stat->set_io_wait_ns(cpu_times[4] * ns_per_user_hz_);
255 cpu_stat->set_irq_ns(cpu_times[5] * ns_per_user_hz_);
256 cpu_stat->set_softirq_ns(cpu_times[6] * ns_per_user_hz_);
257 }
258 // IRQ counters
259 else if ((stat_enabled_fields_ & (1 << SysStatsConfig::STAT_IRQ_COUNTS)) &&
260 !strcmp(words.cur_token(), "intr")) {
261 for (size_t i = 0; words.Next(); i++) {
262 auto v = static_cast<uint64_t>(strtoll(words.cur_token(), nullptr, 10));
263 if (i == 0) {
264 sys_stats->set_num_irq_total(v);
265 } else {
266 auto* irq_stat = sys_stats->add_num_irq();
267 irq_stat->set_irq(static_cast<int32_t>(i - 1));
268 irq_stat->set_count(v);
269 }
270 }
271 }
272 // Softirq counters.
273 else if ((stat_enabled_fields_ &
274 (1 << SysStatsConfig::STAT_SOFTIRQ_COUNTS)) &&
275 !strcmp(words.cur_token(), "softirq")) {
276 for (size_t i = 0; words.Next(); i++) {
277 auto v = static_cast<uint64_t>(strtoll(words.cur_token(), nullptr, 10));
278 if (i == 0) {
279 sys_stats->set_num_softirq_total(v);
280 } else {
281 auto* softirq_stat = sys_stats->add_num_softirq();
282 softirq_stat->set_irq(static_cast<int32_t>(i - 1));
283 softirq_stat->set_count(v);
284 }
285 }
286 }
287 // Number of forked processes since boot.
288 else if ((stat_enabled_fields_ & (1 << SysStatsConfig::STAT_FORK_COUNT)) &&
289 !strcmp(words.cur_token(), "processes")) {
290 if (words.Next()) {
291 sys_stats->set_num_forks(
292 static_cast<uint64_t>(strtoll(words.cur_token(), nullptr, 10)));
293 }
294 }
295
296 } // for (line)
297 }
298
GetWeakPtr() const299 base::WeakPtr<SysStatsDataSource> SysStatsDataSource::GetWeakPtr() const {
300 return weak_factory_.GetWeakPtr();
301 }
302
Flush(FlushRequestID,std::function<void ()> callback)303 void SysStatsDataSource::Flush(FlushRequestID, std::function<void()> callback) {
304 writer_->Flush(callback);
305 }
306
ReadFile(base::ScopedFile * fd,const char * path)307 size_t SysStatsDataSource::ReadFile(base::ScopedFile* fd, const char* path) {
308 if (!*fd)
309 return 0;
310 ssize_t res = pread(**fd, read_buf_.Get(), kReadBufSize - 1, 0);
311 if (res <= 0) {
312 PERFETTO_PLOG("Failed reading %s", path);
313 fd->reset();
314 return 0;
315 }
316 size_t rsize = static_cast<size_t>(res);
317 static_cast<char*>(read_buf_.Get())[rsize] = '\0';
318 return rsize + 1; // Include null terminator in the count.
319 }
320
321 } // namespace perfetto
322