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