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 "perfetto/ext/base/watchdog.h"
18
19 #if PERFETTO_BUILDFLAG(PERFETTO_WATCHDOG)
20
21 #include <fcntl.h>
22 #include <inttypes.h>
23 #include <signal.h>
24 #include <stdint.h>
25
26 #include <fstream>
27 #include <thread>
28
29 #include "perfetto/base/build_config.h"
30 #include "perfetto/base/logging.h"
31 #include "perfetto/base/thread_utils.h"
32 #include "perfetto/ext/base/scoped_file.h"
33 #include "perfetto/ext/base/utils.h"
34
35 namespace perfetto {
36 namespace base {
37
38 namespace {
39
40 constexpr uint32_t kDefaultPollingInterval = 30 * 1000;
41
IsMultipleOf(uint32_t number,uint32_t divisor)42 bool IsMultipleOf(uint32_t number, uint32_t divisor) {
43 return number >= divisor && number % divisor == 0;
44 }
45
MeanForArray(const uint64_t array[],size_t size)46 double MeanForArray(const uint64_t array[], size_t size) {
47 uint64_t total = 0;
48 for (size_t i = 0; i < size; i++) {
49 total += array[i];
50 }
51 return static_cast<double>(total / size);
52
53 }
54
55 } // namespace
56
ReadProcStat(int fd,ProcStat * out)57 bool ReadProcStat(int fd, ProcStat* out) {
58 char c[512];
59 size_t c_pos = 0;
60 while (c_pos < sizeof(c) - 1) {
61 ssize_t rd = PERFETTO_EINTR(read(fd, c + c_pos, sizeof(c) - c_pos));
62 if (rd < 0) {
63 PERFETTO_ELOG("Failed to read stat file to enforce resource limits.");
64 return false;
65 }
66 if (rd == 0)
67 break;
68 c_pos += static_cast<size_t>(rd);
69 }
70 PERFETTO_CHECK(c_pos < sizeof(c));
71 c[c_pos] = '\0';
72
73 if (sscanf(c,
74 "%*d %*s %*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u %lu"
75 "%lu %*d %*d %*d %*d %*d %*d %*u %*u %ld",
76 &out->utime, &out->stime, &out->rss_pages) != 3) {
77 PERFETTO_ELOG("Invalid stat format: %s", c);
78 return false;
79 }
80 return true;
81 }
82
Watchdog(uint32_t polling_interval_ms)83 Watchdog::Watchdog(uint32_t polling_interval_ms)
84 : polling_interval_ms_(polling_interval_ms) {}
85
~Watchdog()86 Watchdog::~Watchdog() {
87 if (!thread_.joinable()) {
88 PERFETTO_DCHECK(!enabled_);
89 return;
90 }
91 PERFETTO_DCHECK(enabled_);
92 enabled_ = false;
93 exit_signal_.notify_one();
94 thread_.join();
95 }
96
GetInstance()97 Watchdog* Watchdog::GetInstance() {
98 static Watchdog* watchdog = new Watchdog(kDefaultPollingInterval);
99 return watchdog;
100 }
101
CreateFatalTimer(uint32_t ms)102 Watchdog::Timer Watchdog::CreateFatalTimer(uint32_t ms) {
103 if (!enabled_.load(std::memory_order_relaxed))
104 return Watchdog::Timer(0);
105
106 return Watchdog::Timer(ms);
107 }
108
Start()109 void Watchdog::Start() {
110 std::lock_guard<std::mutex> guard(mutex_);
111 if (thread_.joinable()) {
112 PERFETTO_DCHECK(enabled_);
113 } else {
114 PERFETTO_DCHECK(!enabled_);
115
116 #if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
117 PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
118 // Kick the thread to start running but only on Android or Linux.
119 enabled_ = true;
120 thread_ = std::thread(&Watchdog::ThreadMain, this);
121 #endif
122 }
123 }
124
SetMemoryLimit(uint64_t bytes,uint32_t window_ms)125 void Watchdog::SetMemoryLimit(uint64_t bytes, uint32_t window_ms) {
126 // Update the fields under the lock.
127 std::lock_guard<std::mutex> guard(mutex_);
128
129 PERFETTO_CHECK(IsMultipleOf(window_ms, polling_interval_ms_) || bytes == 0);
130
131 size_t size = bytes == 0 ? 0 : window_ms / polling_interval_ms_ + 1;
132 memory_window_bytes_.Reset(size);
133 memory_limit_bytes_ = bytes;
134 }
135
SetCpuLimit(uint32_t percentage,uint32_t window_ms)136 void Watchdog::SetCpuLimit(uint32_t percentage, uint32_t window_ms) {
137 std::lock_guard<std::mutex> guard(mutex_);
138
139 PERFETTO_CHECK(percentage <= 100);
140 PERFETTO_CHECK(IsMultipleOf(window_ms, polling_interval_ms_) ||
141 percentage == 0);
142
143 size_t size = percentage == 0 ? 0 : window_ms / polling_interval_ms_ + 1;
144 cpu_window_time_ticks_.Reset(size);
145 cpu_limit_percentage_ = percentage;
146 }
147
ThreadMain()148 void Watchdog::ThreadMain() {
149 base::ScopedFile stat_fd(base::OpenFile("/proc/self/stat", O_RDONLY));
150 if (!stat_fd) {
151 PERFETTO_ELOG("Failed to open stat file to enforce resource limits.");
152 return;
153 }
154
155 std::unique_lock<std::mutex> guard(mutex_);
156 for (;;) {
157 exit_signal_.wait_for(guard,
158 std::chrono::milliseconds(polling_interval_ms_));
159 if (!enabled_)
160 return;
161
162 lseek(stat_fd.get(), 0, SEEK_SET);
163
164 ProcStat stat;
165 if (!ReadProcStat(stat_fd.get(), &stat)) {
166 return;
167 }
168
169 uint64_t cpu_time = stat.utime + stat.stime;
170 uint64_t rss_bytes =
171 static_cast<uint64_t>(stat.rss_pages) * base::GetSysPageSize();
172
173 CheckMemory(rss_bytes);
174 CheckCpu(cpu_time);
175 }
176 }
177
CheckMemory(uint64_t rss_bytes)178 void Watchdog::CheckMemory(uint64_t rss_bytes) {
179 if (memory_limit_bytes_ == 0)
180 return;
181
182 // Add the current stat value to the ring buffer and check that the mean
183 // remains under our threshold.
184 if (memory_window_bytes_.Push(rss_bytes)) {
185 if (memory_window_bytes_.Mean() > static_cast<double>(memory_limit_bytes_)) {
186 PERFETTO_ELOG(
187 "Memory watchdog trigger. Memory window of %f bytes is above the "
188 "%" PRIu64 " bytes limit.",
189 memory_window_bytes_.Mean(), memory_limit_bytes_);
190 kill(getpid(), SIGABRT);
191 }
192 }
193 }
194
CheckCpu(uint64_t cpu_time)195 void Watchdog::CheckCpu(uint64_t cpu_time) {
196 if (cpu_limit_percentage_ == 0)
197 return;
198
199 // Add the cpu time to the ring buffer.
200 if (cpu_window_time_ticks_.Push(cpu_time)) {
201 // Compute the percentage over the whole window and check that it remains
202 // under the threshold.
203 uint64_t difference_ticks = cpu_window_time_ticks_.NewestWhenFull() -
204 cpu_window_time_ticks_.OldestWhenFull();
205 double window_interval_ticks =
206 (static_cast<double>(WindowTimeForRingBuffer(cpu_window_time_ticks_)) /
207 1000.0) *
208 static_cast<double>(sysconf(_SC_CLK_TCK));
209 double percentage = static_cast<double>(difference_ticks) /
210 static_cast<double>(window_interval_ticks) * 100;
211 if (percentage > cpu_limit_percentage_) {
212 PERFETTO_ELOG("CPU watchdog trigger. %f%% CPU use is above the %" PRIu32
213 "%% CPU limit.",
214 percentage, cpu_limit_percentage_);
215 kill(getpid(), SIGABRT);
216 }
217 }
218 }
219
WindowTimeForRingBuffer(const WindowedInterval & window)220 uint32_t Watchdog::WindowTimeForRingBuffer(const WindowedInterval& window) {
221 return static_cast<uint32_t>(window.size() - 1) * polling_interval_ms_;
222 }
223
Push(uint64_t sample)224 bool Watchdog::WindowedInterval::Push(uint64_t sample) {
225 // Add the sample to the current position in the ring buffer.
226 buffer_[position_] = sample;
227
228 // Update the position with next one circularily.
229 position_ = (position_ + 1) % size_;
230
231 // Set the filled flag the first time we wrap.
232 filled_ = filled_ || position_ == 0;
233 return filled_;
234 }
235
Mean() const236 double Watchdog::WindowedInterval::Mean() const {
237 return MeanForArray(buffer_.get(), size_);
238 }
239
Clear()240 void Watchdog::WindowedInterval::Clear() {
241 position_ = 0;
242 buffer_.reset(new uint64_t[size_]());
243 }
244
Reset(size_t new_size)245 void Watchdog::WindowedInterval::Reset(size_t new_size) {
246 position_ = 0;
247 size_ = new_size;
248 buffer_.reset(new_size == 0 ? nullptr : new uint64_t[new_size]());
249 }
250
Timer(uint32_t ms)251 Watchdog::Timer::Timer(uint32_t ms) {
252 if (!ms)
253 return; // No-op timer created when the watchdog is disabled.
254
255 struct sigevent sev = {};
256 sev.sigev_notify = SIGEV_THREAD_ID;
257 sev._sigev_un._tid = base::GetThreadId();
258 sev.sigev_signo = SIGABRT;
259 PERFETTO_CHECK(timer_create(CLOCK_MONOTONIC, &sev, &timerid_) != -1);
260 struct itimerspec its = {};
261 its.it_value.tv_sec = ms / 1000;
262 its.it_value.tv_nsec = 1000000L * (ms % 1000);
263 PERFETTO_CHECK(timer_settime(timerid_, 0, &its, nullptr) != -1);
264 }
265
~Timer()266 Watchdog::Timer::~Timer() {
267 if (timerid_ != nullptr) {
268 timer_delete(timerid_);
269 }
270 }
271
Timer(Timer && other)272 Watchdog::Timer::Timer(Timer&& other) noexcept {
273 timerid_ = other.timerid_;
274 other.timerid_ = nullptr;
275 }
276
277 } // namespace base
278 } // namespace perfetto
279
280 #endif // PERFETTO_BUILDFLAG(PERFETTO_WATCHDOG)
281