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