| 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 |