1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "base/trace_event/memory_dump_scheduler.h"
6 
7 #include "base/process/process_metrics.h"
8 #include "base/single_thread_task_runner.h"
9 #include "base/threading/thread_task_runner_handle.h"
10 #include "base/trace_event/memory_dump_manager.h"
11 #include "build/build_config.h"
12 
13 namespace base {
14 namespace trace_event {
15 
16 namespace {
17 // Threshold on increase in memory from last dump beyond which a new dump must
18 // be triggered.
19 int64_t kDefaultMemoryIncreaseThreshold = 50 * 1024 * 1024;  // 50MiB
20 const uint32_t kMemoryTotalsPollingInterval = 25;
21 uint32_t g_polling_interval_ms_for_testing = 0;
22 }  // namespace
23 
24 // static
25 MemoryDumpScheduler* MemoryDumpScheduler::GetInstance() {
26   static MemoryDumpScheduler* instance = new MemoryDumpScheduler();
27   return instance;
28 }
29 
30 MemoryDumpScheduler::MemoryDumpScheduler() : mdm_(nullptr), is_setup_(false) {}
31 MemoryDumpScheduler::~MemoryDumpScheduler() {}
32 
33 void MemoryDumpScheduler::Setup(
34     MemoryDumpManager* mdm,
35     scoped_refptr<SingleThreadTaskRunner> polling_task_runner) {
36   mdm_ = mdm;
37   polling_task_runner_ = polling_task_runner;
38   periodic_state_.reset(new PeriodicTriggerState);
39   polling_state_.reset(new PollingTriggerState);
40   is_setup_ = true;
41 }
42 
43 void MemoryDumpScheduler::AddTrigger(MemoryDumpType trigger_type,
44                                      MemoryDumpLevelOfDetail level_of_detail,
45                                      uint32_t min_time_between_dumps_ms) {
46   DCHECK(is_setup_);
47   if (trigger_type == MemoryDumpType::PEAK_MEMORY_USAGE) {
48     DCHECK(!periodic_state_->is_configured);
49     DCHECK_EQ(PollingTriggerState::DISABLED, polling_state_->current_state);
50     DCHECK_NE(0u, min_time_between_dumps_ms);
51 
52     polling_state_->level_of_detail = level_of_detail;
53     polling_state_->min_polls_between_dumps =
54         (min_time_between_dumps_ms + polling_state_->polling_interval_ms - 1) /
55         polling_state_->polling_interval_ms;
56     polling_state_->current_state = PollingTriggerState::CONFIGURED;
57   } else if (trigger_type == MemoryDumpType::PERIODIC_INTERVAL) {
58     DCHECK_EQ(PollingTriggerState::DISABLED, polling_state_->current_state);
59     periodic_state_->is_configured = true;
60     DCHECK_NE(0u, min_time_between_dumps_ms);
61     switch (level_of_detail) {
62       case MemoryDumpLevelOfDetail::BACKGROUND:
63         break;
64       case MemoryDumpLevelOfDetail::LIGHT:
65         DCHECK_EQ(0u, periodic_state_->light_dump_period_ms);
66         periodic_state_->light_dump_period_ms = min_time_between_dumps_ms;
67         break;
68       case MemoryDumpLevelOfDetail::DETAILED:
69         DCHECK_EQ(0u, periodic_state_->heavy_dump_period_ms);
70         periodic_state_->heavy_dump_period_ms = min_time_between_dumps_ms;
71         break;
72     }
73 
74     periodic_state_->min_timer_period_ms = std::min(
75         periodic_state_->min_timer_period_ms, min_time_between_dumps_ms);
76     DCHECK_EQ(0u, periodic_state_->light_dump_period_ms %
77                       periodic_state_->min_timer_period_ms);
78     DCHECK_EQ(0u, periodic_state_->heavy_dump_period_ms %
79                       periodic_state_->min_timer_period_ms);
80   }
81 }
82 
83 void MemoryDumpScheduler::EnablePeriodicTriggerIfNeeded() {
84   DCHECK(is_setup_);
85   if (!periodic_state_->is_configured || periodic_state_->timer.IsRunning())
86     return;
87   periodic_state_->light_dumps_rate = periodic_state_->light_dump_period_ms /
88                                       periodic_state_->min_timer_period_ms;
89   periodic_state_->heavy_dumps_rate = periodic_state_->heavy_dump_period_ms /
90                                       periodic_state_->min_timer_period_ms;
91 
92   periodic_state_->dump_count = 0;
93   periodic_state_->timer.Start(
94       FROM_HERE,
95       TimeDelta::FromMilliseconds(periodic_state_->min_timer_period_ms),
96       Bind(&MemoryDumpScheduler::RequestPeriodicGlobalDump, Unretained(this)));
97 }
98 
99 void MemoryDumpScheduler::EnablePollingIfNeeded() {
100   DCHECK(is_setup_);
101   if (polling_state_->current_state != PollingTriggerState::CONFIGURED)
102     return;
103 
104   polling_state_->current_state = PollingTriggerState::ENABLED;
105   polling_state_->ResetTotals();
106 
107   polling_task_runner_->PostTask(
108       FROM_HERE,
109       Bind(&MemoryDumpScheduler::PollMemoryOnPollingThread, Unretained(this)));
110 }
111 
112 void MemoryDumpScheduler::NotifyDumpTriggered() {
113   if (polling_task_runner_ &&
114       !polling_task_runner_->RunsTasksOnCurrentThread()) {
115     polling_task_runner_->PostTask(
116         FROM_HERE,
117         Bind(&MemoryDumpScheduler::NotifyDumpTriggered, Unretained(this)));
118     return;
119   }
120 
121   if (!polling_state_ ||
122       polling_state_->current_state != PollingTriggerState::ENABLED) {
123     return;
124   }
125 
126   polling_state_->ResetTotals();
127 }
128 
129 void MemoryDumpScheduler::DisableAllTriggers() {
130   if (periodic_state_) {
131     if (periodic_state_->timer.IsRunning())
132       periodic_state_->timer.Stop();
133     periodic_state_.reset();
134   }
135 
136   if (polling_task_runner_) {
137     DCHECK(polling_state_);
138     polling_task_runner_->PostTask(
139         FROM_HERE, Bind(&MemoryDumpScheduler::DisablePollingOnPollingThread,
140                         Unretained(this)));
141     polling_task_runner_ = nullptr;
142   }
143   is_setup_ = false;
144 }
145 
146 void MemoryDumpScheduler::DisablePollingOnPollingThread() {
147   polling_state_->current_state = PollingTriggerState::DISABLED;
148   polling_state_.reset();
149 }
150 
151 // static
152 void MemoryDumpScheduler::SetPollingIntervalForTesting(uint32_t interval) {
153   g_polling_interval_ms_for_testing = interval;
154 }
155 
156 bool MemoryDumpScheduler::IsPeriodicTimerRunningForTesting() {
157   return periodic_state_->timer.IsRunning();
158 }
159 
160 void MemoryDumpScheduler::RequestPeriodicGlobalDump() {
161   MemoryDumpLevelOfDetail level_of_detail = MemoryDumpLevelOfDetail::BACKGROUND;
162   if (periodic_state_->light_dumps_rate > 0 &&
163       periodic_state_->dump_count % periodic_state_->light_dumps_rate == 0)
164     level_of_detail = MemoryDumpLevelOfDetail::LIGHT;
165   if (periodic_state_->heavy_dumps_rate > 0 &&
166       periodic_state_->dump_count % periodic_state_->heavy_dumps_rate == 0)
167     level_of_detail = MemoryDumpLevelOfDetail::DETAILED;
168   ++periodic_state_->dump_count;
169 
170   mdm_->RequestGlobalDump(MemoryDumpType::PERIODIC_INTERVAL, level_of_detail);
171 }
172 
173 void MemoryDumpScheduler::PollMemoryOnPollingThread() {
174   if (!polling_state_)
175     return;
176 
177   DCHECK_EQ(PollingTriggerState::ENABLED, polling_state_->current_state);
178 
179   uint64_t polled_memory = 0;
180   bool res = mdm_->PollFastMemoryTotal(&polled_memory);
181   DCHECK(res);
182   if (polling_state_->level_of_detail == MemoryDumpLevelOfDetail::DETAILED) {
183     TRACE_COUNTER1(MemoryDumpManager::kTraceCategory, "PolledMemoryMB",
184                    polled_memory / 1024 / 1024);
185   }
186 
187   if (ShouldTriggerDump(polled_memory)) {
188     TRACE_EVENT_INSTANT1(MemoryDumpManager::kTraceCategory,
189                          "Peak memory dump Triggered",
190                          TRACE_EVENT_SCOPE_PROCESS, "total_usage_MB",
191                          polled_memory / 1024 / 1024);
192 
193     mdm_->RequestGlobalDump(MemoryDumpType::PEAK_MEMORY_USAGE,
194                             polling_state_->level_of_detail);
195   }
196 
197   // TODO(ssid): Use RequestSchedulerCallback, crbug.com/607533.
198   ThreadTaskRunnerHandle::Get()->PostDelayedTask(
199       FROM_HERE,
200       Bind(&MemoryDumpScheduler::PollMemoryOnPollingThread, Unretained(this)),
201       TimeDelta::FromMilliseconds(polling_state_->polling_interval_ms));
202 }
203 
204 bool MemoryDumpScheduler::ShouldTriggerDump(uint64_t current_memory_total) {
205   // This function tries to detect peak memory usage as discussed in
206   // https://goo.gl/0kOU4A.
207 
208   if (current_memory_total == 0)
209     return false;
210 
211   bool should_dump = false;
212   ++polling_state_->num_polls_from_last_dump;
213   if (polling_state_->last_dump_memory_total == 0) {
214     // If it's first sample then trigger memory dump.
215     should_dump = true;
216   } else if (polling_state_->min_polls_between_dumps >
217              polling_state_->num_polls_from_last_dump) {
218     return false;
219   }
220 
221   int64_t increase_from_last_dump =
222       current_memory_total - polling_state_->last_dump_memory_total;
223   should_dump |=
224       increase_from_last_dump > polling_state_->memory_increase_threshold;
225   should_dump |= IsCurrentSamplePeak(current_memory_total);
226   if (should_dump)
227     polling_state_->ResetTotals();
228   return should_dump;
229 }
230 
231 bool MemoryDumpScheduler::IsCurrentSamplePeak(
232     uint64_t current_memory_total_bytes) {
233   uint64_t current_memory_total_kb = current_memory_total_bytes / 1024;
234   polling_state_->last_memory_totals_kb_index =
235       (polling_state_->last_memory_totals_kb_index + 1) %
236       PollingTriggerState::kMaxNumMemorySamples;
237   uint64_t mean = 0;
238   for (uint32_t i = 0; i < PollingTriggerState::kMaxNumMemorySamples; ++i) {
239     if (polling_state_->last_memory_totals_kb[i] == 0) {
240       // Not enough samples to detect peaks.
241       polling_state_
242           ->last_memory_totals_kb[polling_state_->last_memory_totals_kb_index] =
243           current_memory_total_kb;
244       return false;
245     }
246     mean += polling_state_->last_memory_totals_kb[i];
247   }
248   mean = mean / PollingTriggerState::kMaxNumMemorySamples;
249   uint64_t variance = 0;
250   for (uint32_t i = 0; i < PollingTriggerState::kMaxNumMemorySamples; ++i) {
251     variance += (polling_state_->last_memory_totals_kb[i] - mean) *
252                 (polling_state_->last_memory_totals_kb[i] - mean);
253   }
254   variance = variance / PollingTriggerState::kMaxNumMemorySamples;
255 
256   polling_state_
257       ->last_memory_totals_kb[polling_state_->last_memory_totals_kb_index] =
258       current_memory_total_kb;
259 
260   // If stddev is less than 0.2% then we consider that the process is inactive.
261   bool is_stddev_low = variance < mean / 500 * mean / 500;
262   if (is_stddev_low)
263     return false;
264 
265   // (mean + 3.69 * stddev) corresponds to a value that is higher than current
266   // sample with 99.99% probability.
267   return (current_memory_total_kb - mean) * (current_memory_total_kb - mean) >
268          (3.69 * 3.69 * variance);
269 }
270 
271 MemoryDumpScheduler::PeriodicTriggerState::PeriodicTriggerState()
272     : is_configured(false),
273       dump_count(0),
274       min_timer_period_ms(std::numeric_limits<uint32_t>::max()),
275       light_dumps_rate(0),
276       heavy_dumps_rate(0),
277       light_dump_period_ms(0),
278       heavy_dump_period_ms(0) {}
279 
280 MemoryDumpScheduler::PeriodicTriggerState::~PeriodicTriggerState() {
281   DCHECK(!timer.IsRunning());
282 }
283 
284 MemoryDumpScheduler::PollingTriggerState::PollingTriggerState()
285     : current_state(DISABLED),
286       level_of_detail(MemoryDumpLevelOfDetail::FIRST),
287       polling_interval_ms(g_polling_interval_ms_for_testing
288                               ? g_polling_interval_ms_for_testing
289                               : kMemoryTotalsPollingInterval),
290       min_polls_between_dumps(0),
291       num_polls_from_last_dump(-1),
292       last_dump_memory_total(0),
293       memory_increase_threshold(0),
294       last_memory_totals_kb_index(0) {}
295 
296 MemoryDumpScheduler::PollingTriggerState::~PollingTriggerState() {}
297 
298 void MemoryDumpScheduler::PollingTriggerState::ResetTotals() {
299   if (!memory_increase_threshold) {
300     memory_increase_threshold = kDefaultMemoryIncreaseThreshold;
301 #if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_LINUX) || \
302     defined(OS_ANDROID)
303     // Set threshold to 1% of total system memory.
304     SystemMemoryInfoKB meminfo;
305     bool res = GetSystemMemoryInfo(&meminfo);
306     if (res) {
307       memory_increase_threshold =
308           (static_cast<int64_t>(meminfo.total) / 100) * 1024;
309     }
310     DCHECK_GT(memory_increase_threshold, 0u);
311 #endif
312   }
313 
314   // Update the |last_dump_memory_total|'s value from the totals if it's not
315   // first poll.
316   if (num_polls_from_last_dump >= 0 &&
317       last_memory_totals_kb[last_memory_totals_kb_index]) {
318     last_dump_memory_total =
319         last_memory_totals_kb[last_memory_totals_kb_index] * 1024;
320   }
321   num_polls_from_last_dump = 0;
322   for (uint32_t i = 0; i < kMaxNumMemorySamples; ++i)
323     last_memory_totals_kb[i] = 0;
324   last_memory_totals_kb_index = 0;
325 }
326 
327 }  // namespace trace_event
328 }  // namespace base
329