Chromium Code Reviews| 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 <limits> |
| 8 #include "base/single_thread_task_runner.h" | 8 |
| 9 #include "base/threading/thread_task_runner_handle.h" | 9 #include "base/bind.h" |
| 10 #include "base/trace_event/memory_dump_manager.h" | 10 #include "base/logging.h" |
| 11 #include "build/build_config.h" | 11 #include "base/threading/sequenced_task_runner_handle.h" |
| 12 | 12 |
| 13 namespace base { | 13 namespace base { |
| 14 namespace trace_event { | 14 namespace trace_event { |
| 15 | 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 | 16 // static |
| 25 MemoryDumpScheduler* MemoryDumpScheduler::GetInstance() { | 17 MemoryDumpScheduler* MemoryDumpScheduler::GetInstance() { |
| 26 static MemoryDumpScheduler* instance = new MemoryDumpScheduler(); | 18 static MemoryDumpScheduler* instance = new MemoryDumpScheduler(); |
| 27 return instance; | 19 return instance; |
| 28 } | 20 } |
| 29 | 21 |
| 30 MemoryDumpScheduler::MemoryDumpScheduler() : mdm_(nullptr), is_setup_(false) {} | 22 MemoryDumpScheduler::MemoryDumpScheduler() : period_ms_(0), generation_(0) {} |
| 31 MemoryDumpScheduler::~MemoryDumpScheduler() {} | 23 MemoryDumpScheduler::~MemoryDumpScheduler() { |
| 32 | 24 // Hit only in tests. Check that tests don't leave without stopping. |
| 33 void MemoryDumpScheduler::Setup( | 25 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 } | 26 } |
| 42 | 27 |
| 43 void MemoryDumpScheduler::AddTrigger(MemoryDumpType trigger_type, | 28 void MemoryDumpScheduler::Start( |
| 44 MemoryDumpLevelOfDetail level_of_detail, | 29 MemoryDumpScheduler::Config config, |
| 45 uint32_t min_time_between_dumps_ms) { | 30 scoped_refptr<SequencedTaskRunner> task_runner) { |
| 46 DCHECK(is_setup_); | 31 DCHECK(!task_runner_); |
| 47 if (trigger_type == MemoryDumpType::PEAK_MEMORY_USAGE) { | 32 task_runner_ = task_runner; |
| 48 DCHECK(!periodic_state_->is_configured); | 33 task_runner->PostTask(FROM_HERE, Bind(&MemoryDumpScheduler::StartInternal, |
| 49 DCHECK_EQ(PollingTriggerState::DISABLED, polling_state_->current_state); | 34 Unretained(this), config)); |
| 50 DCHECK_NE(0u, min_time_between_dumps_ms); | 35 } |
| 51 | 36 |
| 52 polling_state_->level_of_detail = level_of_detail; | 37 void MemoryDumpScheduler::Stop() { |
| 53 polling_state_->min_polls_between_dumps = | 38 if (!task_runner_) |
| 54 (min_time_between_dumps_ms + polling_state_->polling_interval_ms - 1) / | 39 return; |
| 55 polling_state_->polling_interval_ms; | 40 task_runner_->PostTask( |
| 56 polling_state_->current_state = PollingTriggerState::CONFIGURED; | 41 FROM_HERE, Bind(&MemoryDumpScheduler::StopInternal, Unretained(this))); |
| 57 } else if (trigger_type == MemoryDumpType::PERIODIC_INTERVAL) { | 42 task_runner_ = nullptr; |
| 58 DCHECK_EQ(PollingTriggerState::DISABLED, polling_state_->current_state); | 43 } |
| 59 periodic_state_->is_configured = true; | 44 |
| 60 DCHECK_NE(0u, min_time_between_dumps_ms); | 45 void MemoryDumpScheduler::StartInternal(MemoryDumpScheduler::Config config) { |
| 61 switch (level_of_detail) { | 46 uint32_t light_dump_period_ms = 0; |
| 47 uint32_t heavy_dump_period_ms = 0; | |
| 48 uint32_t min_period_ms = std::numeric_limits<uint32_t>::max(); | |
| 49 for (const Config::Trigger& trigger : config.triggers) { | |
| 50 DCHECK_GT(trigger.period_ms, 0u); | |
| 51 switch (trigger.level_of_detail) { | |
| 62 case MemoryDumpLevelOfDetail::BACKGROUND: | 52 case MemoryDumpLevelOfDetail::BACKGROUND: |
| 63 break; | 53 break; |
| 64 case MemoryDumpLevelOfDetail::LIGHT: | 54 case MemoryDumpLevelOfDetail::LIGHT: |
| 65 DCHECK_EQ(0u, periodic_state_->light_dump_period_ms); | 55 DCHECK_EQ(0u, light_dump_period_ms); |
| 66 periodic_state_->light_dump_period_ms = min_time_between_dumps_ms; | 56 light_dump_period_ms = trigger.period_ms; |
| 67 break; | 57 break; |
| 68 case MemoryDumpLevelOfDetail::DETAILED: | 58 case MemoryDumpLevelOfDetail::DETAILED: |
| 69 DCHECK_EQ(0u, periodic_state_->heavy_dump_period_ms); | 59 DCHECK_EQ(0u, heavy_dump_period_ms); |
| 70 periodic_state_->heavy_dump_period_ms = min_time_between_dumps_ms; | 60 heavy_dump_period_ms = trigger.period_ms; |
| 71 break; | 61 break; |
| 72 } | 62 } |
| 63 min_period_ms = std::min(min_period_ms, trigger.period_ms); | |
|
hjd
2017/04/11 09:42:55
#include <algorithm>?
Primiano Tucci (use gerrit)
2017/04/11 11:43:08
Good point done. (I always find funny that in orde
| |
| 64 } | |
| 73 | 65 |
| 74 periodic_state_->min_timer_period_ms = std::min( | 66 DCHECK_EQ(0u, light_dump_period_ms % min_period_ms); |
| 75 periodic_state_->min_timer_period_ms, min_time_between_dumps_ms); | 67 DCHECK_EQ(0u, heavy_dump_period_ms % min_period_ms); |
| 76 DCHECK_EQ(0u, periodic_state_->light_dump_period_ms % | 68 DCHECK(!config.callback.is_null()); |
| 77 periodic_state_->min_timer_period_ms); | 69 callback_ = config.callback; |
| 78 DCHECK_EQ(0u, periodic_state_->heavy_dump_period_ms % | 70 period_ms_ = min_period_ms; |
| 79 periodic_state_->min_timer_period_ms); | 71 tick_count_ = 0; |
| 80 } | 72 light_dump_rate_ = light_dump_period_ms / min_period_ms; |
| 73 heavy_dump_rate_ = heavy_dump_period_ms / min_period_ms; | |
| 74 SequencedTaskRunnerHandle::Get()->PostTask( | |
| 75 FROM_HERE, | |
| 76 Bind(&MemoryDumpScheduler::Tick, Unretained(this), ++generation_)); | |
| 81 } | 77 } |
| 82 | 78 |
| 83 void MemoryDumpScheduler::EnablePeriodicTriggerIfNeeded() { | 79 void MemoryDumpScheduler::StopInternal() { |
| 84 DCHECK(is_setup_); | 80 period_ms_ = 0; |
| 85 if (!periodic_state_->is_configured || periodic_state_->timer.IsRunning()) | 81 generation_++; |
| 86 return; | 82 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 } | 83 } |
| 98 | 84 |
| 99 void MemoryDumpScheduler::EnablePollingIfNeeded() { | 85 void MemoryDumpScheduler::Tick(uint32_t expected_generation) { |
| 100 DCHECK(is_setup_); | 86 if (period_ms_ == 0 || generation_ != expected_generation) |
| 101 if (polling_state_->current_state != PollingTriggerState::CONFIGURED) | |
| 102 return; | 87 return; |
| 103 | 88 |
| 104 polling_state_->current_state = PollingTriggerState::ENABLED; | 89 SequencedTaskRunnerHandle::Get()->PostDelayedTask( |
| 105 polling_state_->ResetTotals(); | 90 FROM_HERE, |
| 91 Bind(&MemoryDumpScheduler::Tick, Unretained(this), expected_generation), | |
| 92 TimeDelta::FromMilliseconds(period_ms_)); | |
| 106 | 93 |
| 107 polling_task_runner_->PostTask( | 94 MemoryDumpLevelOfDetail level_of_detail = MemoryDumpLevelOfDetail::BACKGROUND; |
| 108 FROM_HERE, | 95 if (light_dump_rate_ > 0 && tick_count_ % light_dump_rate_ == 0) |
| 109 Bind(&MemoryDumpScheduler::PollMemoryOnPollingThread, Unretained(this))); | 96 level_of_detail = MemoryDumpLevelOfDetail::LIGHT; |
| 97 if (heavy_dump_rate_ > 0 && tick_count_ % heavy_dump_rate_ == 0) | |
| 98 level_of_detail = MemoryDumpLevelOfDetail::DETAILED; | |
| 99 tick_count_++; | |
| 100 | |
| 101 callback_.Run(level_of_detail); | |
| 110 } | 102 } |
| 111 | 103 |
| 112 void MemoryDumpScheduler::NotifyDumpTriggered() { | 104 MemoryDumpScheduler::Config::Config() {} |
| 113 if (polling_task_runner_ && | 105 MemoryDumpScheduler::Config::~Config() {} |
| 114 !polling_task_runner_->RunsTasksOnCurrentThread()) { | 106 MemoryDumpScheduler::Config::Config(const MemoryDumpScheduler::Config&) = |
| 115 polling_task_runner_->PostTask( | 107 default; |
| 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 | 108 |
| 327 } // namespace trace_event | 109 } // namespace trace_event |
| 328 } // namespace base | 110 } // namespace base |
| OLD | NEW |