OLD | NEW |
1 // Copyright 2017 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "base/trace_event/memory_dump_scheduler.h" | 5 #include "base/trace_event/memory_dump_scheduler.h" |
6 | 6 |
7 #include "base/process/process_metrics.h" | 7 #include <algorithm> |
8 #include "base/single_thread_task_runner.h" | 8 #include <limits> |
9 #include "base/threading/thread_task_runner_handle.h" | 9 |
10 #include "base/trace_event/memory_dump_manager.h" | 10 #include "base/bind.h" |
11 #include "build/build_config.h" | 11 #include "base/logging.h" |
| 12 #include "base/threading/sequenced_task_runner_handle.h" |
12 | 13 |
13 namespace base { | 14 namespace base { |
14 namespace trace_event { | 15 namespace trace_event { |
15 | 16 |
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 | 17 // static |
25 MemoryDumpScheduler* MemoryDumpScheduler::GetInstance() { | 18 MemoryDumpScheduler* MemoryDumpScheduler::GetInstance() { |
26 static MemoryDumpScheduler* instance = new MemoryDumpScheduler(); | 19 static MemoryDumpScheduler* instance = new MemoryDumpScheduler(); |
27 return instance; | 20 return instance; |
28 } | 21 } |
29 | 22 |
30 MemoryDumpScheduler::MemoryDumpScheduler() : mdm_(nullptr), is_setup_(false) {} | 23 MemoryDumpScheduler::MemoryDumpScheduler() : period_ms_(0), generation_(0) {} |
31 MemoryDumpScheduler::~MemoryDumpScheduler() {} | 24 MemoryDumpScheduler::~MemoryDumpScheduler() { |
32 | 25 // Hit only in tests. Check that tests don't leave without stopping. |
33 void MemoryDumpScheduler::Setup( | 26 DCHECK(!is_enabled_for_testing()); |
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 } | 27 } |
42 | 28 |
43 void MemoryDumpScheduler::AddTrigger(MemoryDumpType trigger_type, | 29 void MemoryDumpScheduler::Start( |
44 MemoryDumpLevelOfDetail level_of_detail, | 30 MemoryDumpScheduler::Config config, |
45 uint32_t min_time_between_dumps_ms) { | 31 scoped_refptr<SequencedTaskRunner> task_runner) { |
46 DCHECK(is_setup_); | 32 DCHECK(!task_runner_); |
47 if (trigger_type == MemoryDumpType::PEAK_MEMORY_USAGE) { | 33 task_runner_ = task_runner; |
48 DCHECK(!periodic_state_->is_configured); | 34 task_runner->PostTask(FROM_HERE, BindOnce(&MemoryDumpScheduler::StartInternal, |
49 DCHECK_EQ(PollingTriggerState::DISABLED, polling_state_->current_state); | 35 Unretained(this), config)); |
50 DCHECK_NE(0u, min_time_between_dumps_ms); | 36 } |
51 | 37 |
52 polling_state_->level_of_detail = level_of_detail; | 38 void MemoryDumpScheduler::Stop() { |
53 polling_state_->min_polls_between_dumps = | 39 if (!task_runner_) |
54 (min_time_between_dumps_ms + polling_state_->polling_interval_ms - 1) / | 40 return; |
55 polling_state_->polling_interval_ms; | 41 task_runner_->PostTask(FROM_HERE, BindOnce(&MemoryDumpScheduler::StopInternal, |
56 polling_state_->current_state = PollingTriggerState::CONFIGURED; | 42 Unretained(this))); |
57 } else if (trigger_type == MemoryDumpType::PERIODIC_INTERVAL) { | 43 task_runner_ = nullptr; |
58 DCHECK_EQ(PollingTriggerState::DISABLED, polling_state_->current_state); | 44 } |
59 periodic_state_->is_configured = true; | 45 |
60 DCHECK_NE(0u, min_time_between_dumps_ms); | 46 void MemoryDumpScheduler::StartInternal(MemoryDumpScheduler::Config config) { |
61 switch (level_of_detail) { | 47 uint32_t light_dump_period_ms = 0; |
| 48 uint32_t heavy_dump_period_ms = 0; |
| 49 uint32_t min_period_ms = std::numeric_limits<uint32_t>::max(); |
| 50 for (const Config::Trigger& trigger : config.triggers) { |
| 51 DCHECK_GT(trigger.period_ms, 0u); |
| 52 switch (trigger.level_of_detail) { |
62 case MemoryDumpLevelOfDetail::BACKGROUND: | 53 case MemoryDumpLevelOfDetail::BACKGROUND: |
63 break; | 54 break; |
64 case MemoryDumpLevelOfDetail::LIGHT: | 55 case MemoryDumpLevelOfDetail::LIGHT: |
65 DCHECK_EQ(0u, periodic_state_->light_dump_period_ms); | 56 DCHECK_EQ(0u, light_dump_period_ms); |
66 periodic_state_->light_dump_period_ms = min_time_between_dumps_ms; | 57 light_dump_period_ms = trigger.period_ms; |
67 break; | 58 break; |
68 case MemoryDumpLevelOfDetail::DETAILED: | 59 case MemoryDumpLevelOfDetail::DETAILED: |
69 DCHECK_EQ(0u, periodic_state_->heavy_dump_period_ms); | 60 DCHECK_EQ(0u, heavy_dump_period_ms); |
70 periodic_state_->heavy_dump_period_ms = min_time_between_dumps_ms; | 61 heavy_dump_period_ms = trigger.period_ms; |
71 break; | 62 break; |
72 } | 63 } |
| 64 min_period_ms = std::min(min_period_ms, trigger.period_ms); |
| 65 } |
73 | 66 |
74 periodic_state_->min_timer_period_ms = std::min( | 67 DCHECK_EQ(0u, light_dump_period_ms % min_period_ms); |
75 periodic_state_->min_timer_period_ms, min_time_between_dumps_ms); | 68 DCHECK_EQ(0u, heavy_dump_period_ms % min_period_ms); |
76 DCHECK_EQ(0u, periodic_state_->light_dump_period_ms % | 69 DCHECK(!config.callback.is_null()); |
77 periodic_state_->min_timer_period_ms); | 70 callback_ = config.callback; |
78 DCHECK_EQ(0u, periodic_state_->heavy_dump_period_ms % | 71 period_ms_ = min_period_ms; |
79 periodic_state_->min_timer_period_ms); | 72 tick_count_ = 0; |
80 } | 73 light_dump_rate_ = light_dump_period_ms / min_period_ms; |
| 74 heavy_dump_rate_ = heavy_dump_period_ms / min_period_ms; |
| 75 SequencedTaskRunnerHandle::Get()->PostTask( |
| 76 FROM_HERE, |
| 77 BindOnce(&MemoryDumpScheduler::Tick, Unretained(this), ++generation_)); |
81 } | 78 } |
82 | 79 |
83 void MemoryDumpScheduler::EnablePeriodicTriggerIfNeeded() { | 80 void MemoryDumpScheduler::StopInternal() { |
84 DCHECK(is_setup_); | 81 period_ms_ = 0; |
85 if (!periodic_state_->is_configured || periodic_state_->timer.IsRunning()) | 82 generation_++; |
86 return; | 83 callback_.Reset(); |
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 } | 84 } |
98 | 85 |
99 void MemoryDumpScheduler::EnablePollingIfNeeded() { | 86 void MemoryDumpScheduler::Tick(uint32_t expected_generation) { |
100 DCHECK(is_setup_); | 87 if (period_ms_ == 0 || generation_ != expected_generation) |
101 if (polling_state_->current_state != PollingTriggerState::CONFIGURED) | |
102 return; | 88 return; |
103 | 89 |
104 polling_state_->current_state = PollingTriggerState::ENABLED; | 90 SequencedTaskRunnerHandle::Get()->PostDelayedTask( |
105 polling_state_->ResetTotals(); | 91 FROM_HERE, |
| 92 BindOnce(&MemoryDumpScheduler::Tick, Unretained(this), |
| 93 expected_generation), |
| 94 TimeDelta::FromMilliseconds(period_ms_)); |
106 | 95 |
107 polling_task_runner_->PostTask( | 96 MemoryDumpLevelOfDetail level_of_detail = MemoryDumpLevelOfDetail::BACKGROUND; |
108 FROM_HERE, BindOnce(&MemoryDumpScheduler::PollMemoryOnPollingThread, | 97 if (light_dump_rate_ > 0 && tick_count_ % light_dump_rate_ == 0) |
109 Unretained(this))); | 98 level_of_detail = MemoryDumpLevelOfDetail::LIGHT; |
| 99 if (heavy_dump_rate_ > 0 && tick_count_ % heavy_dump_rate_ == 0) |
| 100 level_of_detail = MemoryDumpLevelOfDetail::DETAILED; |
| 101 tick_count_++; |
| 102 |
| 103 callback_.Run(level_of_detail); |
110 } | 104 } |
111 | 105 |
112 void MemoryDumpScheduler::NotifyDumpTriggered() { | 106 MemoryDumpScheduler::Config::Config() {} |
113 if (polling_task_runner_ && | 107 MemoryDumpScheduler::Config::~Config() {} |
114 !polling_task_runner_->RunsTasksOnCurrentThread()) { | 108 MemoryDumpScheduler::Config::Config(const MemoryDumpScheduler::Config&) = |
115 polling_task_runner_->PostTask( | 109 default; |
116 FROM_HERE, | |
117 BindOnce(&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, BindOnce(&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 BindOnce(&MemoryDumpScheduler::PollMemoryOnPollingThread, | |
201 Unretained(this)), | |
202 TimeDelta::FromMilliseconds(polling_state_->polling_interval_ms)); | |
203 } | |
204 | |
205 bool MemoryDumpScheduler::ShouldTriggerDump(uint64_t current_memory_total) { | |
206 // This function tries to detect peak memory usage as discussed in | |
207 // https://goo.gl/0kOU4A. | |
208 | |
209 if (current_memory_total == 0) | |
210 return false; | |
211 | |
212 bool should_dump = false; | |
213 ++polling_state_->num_polls_from_last_dump; | |
214 if (polling_state_->last_dump_memory_total == 0) { | |
215 // If it's first sample then trigger memory dump. | |
216 should_dump = true; | |
217 } else if (polling_state_->min_polls_between_dumps > | |
218 polling_state_->num_polls_from_last_dump) { | |
219 return false; | |
220 } | |
221 | |
222 int64_t increase_from_last_dump = | |
223 current_memory_total - polling_state_->last_dump_memory_total; | |
224 should_dump |= | |
225 increase_from_last_dump > polling_state_->memory_increase_threshold; | |
226 should_dump |= IsCurrentSamplePeak(current_memory_total); | |
227 if (should_dump) | |
228 polling_state_->ResetTotals(); | |
229 return should_dump; | |
230 } | |
231 | |
232 bool MemoryDumpScheduler::IsCurrentSamplePeak( | |
233 uint64_t current_memory_total_bytes) { | |
234 uint64_t current_memory_total_kb = current_memory_total_bytes / 1024; | |
235 polling_state_->last_memory_totals_kb_index = | |
236 (polling_state_->last_memory_totals_kb_index + 1) % | |
237 PollingTriggerState::kMaxNumMemorySamples; | |
238 uint64_t mean = 0; | |
239 for (uint32_t i = 0; i < PollingTriggerState::kMaxNumMemorySamples; ++i) { | |
240 if (polling_state_->last_memory_totals_kb[i] == 0) { | |
241 // Not enough samples to detect peaks. | |
242 polling_state_ | |
243 ->last_memory_totals_kb[polling_state_->last_memory_totals_kb_index] = | |
244 current_memory_total_kb; | |
245 return false; | |
246 } | |
247 mean += polling_state_->last_memory_totals_kb[i]; | |
248 } | |
249 mean = mean / PollingTriggerState::kMaxNumMemorySamples; | |
250 uint64_t variance = 0; | |
251 for (uint32_t i = 0; i < PollingTriggerState::kMaxNumMemorySamples; ++i) { | |
252 variance += (polling_state_->last_memory_totals_kb[i] - mean) * | |
253 (polling_state_->last_memory_totals_kb[i] - mean); | |
254 } | |
255 variance = variance / PollingTriggerState::kMaxNumMemorySamples; | |
256 | |
257 polling_state_ | |
258 ->last_memory_totals_kb[polling_state_->last_memory_totals_kb_index] = | |
259 current_memory_total_kb; | |
260 | |
261 // If stddev is less than 0.2% then we consider that the process is inactive. | |
262 bool is_stddev_low = variance < mean / 500 * mean / 500; | |
263 if (is_stddev_low) | |
264 return false; | |
265 | |
266 // (mean + 3.69 * stddev) corresponds to a value that is higher than current | |
267 // sample with 99.99% probability. | |
268 return (current_memory_total_kb - mean) * (current_memory_total_kb - mean) > | |
269 (3.69 * 3.69 * variance); | |
270 } | |
271 | |
272 MemoryDumpScheduler::PeriodicTriggerState::PeriodicTriggerState() | |
273 : is_configured(false), | |
274 dump_count(0), | |
275 min_timer_period_ms(std::numeric_limits<uint32_t>::max()), | |
276 light_dumps_rate(0), | |
277 heavy_dumps_rate(0), | |
278 light_dump_period_ms(0), | |
279 heavy_dump_period_ms(0) {} | |
280 | |
281 MemoryDumpScheduler::PeriodicTriggerState::~PeriodicTriggerState() { | |
282 DCHECK(!timer.IsRunning()); | |
283 } | |
284 | |
285 MemoryDumpScheduler::PollingTriggerState::PollingTriggerState() | |
286 : current_state(DISABLED), | |
287 level_of_detail(MemoryDumpLevelOfDetail::FIRST), | |
288 polling_interval_ms(g_polling_interval_ms_for_testing | |
289 ? g_polling_interval_ms_for_testing | |
290 : kMemoryTotalsPollingInterval), | |
291 min_polls_between_dumps(0), | |
292 num_polls_from_last_dump(-1), | |
293 last_dump_memory_total(0), | |
294 memory_increase_threshold(0), | |
295 last_memory_totals_kb_index(0) {} | |
296 | |
297 MemoryDumpScheduler::PollingTriggerState::~PollingTriggerState() {} | |
298 | |
299 void MemoryDumpScheduler::PollingTriggerState::ResetTotals() { | |
300 if (!memory_increase_threshold) { | |
301 memory_increase_threshold = kDefaultMemoryIncreaseThreshold; | |
302 #if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_LINUX) || \ | |
303 defined(OS_ANDROID) | |
304 // Set threshold to 1% of total system memory. | |
305 SystemMemoryInfoKB meminfo; | |
306 bool res = GetSystemMemoryInfo(&meminfo); | |
307 if (res) { | |
308 memory_increase_threshold = | |
309 (static_cast<int64_t>(meminfo.total) / 100) * 1024; | |
310 } | |
311 DCHECK_GT(memory_increase_threshold, 0u); | |
312 #endif | |
313 } | |
314 | |
315 // Update the |last_dump_memory_total|'s value from the totals if it's not | |
316 // first poll. | |
317 if (num_polls_from_last_dump >= 0 && | |
318 last_memory_totals_kb[last_memory_totals_kb_index]) { | |
319 last_dump_memory_total = | |
320 last_memory_totals_kb[last_memory_totals_kb_index] * 1024; | |
321 } | |
322 num_polls_from_last_dump = 0; | |
323 for (uint32_t i = 0; i < kMaxNumMemorySamples; ++i) | |
324 last_memory_totals_kb[i] = 0; | |
325 last_memory_totals_kb_index = 0; | |
326 } | |
327 | 110 |
328 } // namespace trace_event | 111 } // namespace trace_event |
329 } // namespace base | 112 } // namespace base |
OLD | NEW |