OLD | NEW |
1 // Copyright 2013 The Chromium Authors. All rights reserved. | 1 // Copyright 2013 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 "components/startup_metric_utils/browser/startup_metric_utils.h" | 5 #include "components/startup_metric_utils/browser/startup_metric_utils.h" |
6 | 6 |
7 #include "base/containers/hash_tables.h" | 7 #include "base/containers/hash_tables.h" |
8 #include "base/environment.h" | 8 #include "base/environment.h" |
9 #include "base/lazy_instance.h" | 9 #include "base/lazy_instance.h" |
10 #include "base/logging.h" | 10 #include "base/logging.h" |
| 11 #include "base/metrics/histogram.h" |
11 #include "base/metrics/histogram_macros.h" | 12 #include "base/metrics/histogram_macros.h" |
12 #include "base/prefs/pref_registry_simple.h" | 13 #include "base/prefs/pref_registry_simple.h" |
13 #include "base/prefs/pref_service.h" | 14 #include "base/prefs/pref_service.h" |
14 #include "base/process/process_info.h" | 15 #include "base/process/process_info.h" |
15 #include "base/strings/string_number_conversions.h" | 16 #include "base/strings/string_number_conversions.h" |
16 #include "base/sys_info.h" | 17 #include "base/sys_info.h" |
17 #include "base/threading/platform_thread.h" | 18 #include "base/threading/platform_thread.h" |
18 #include "base/trace_event/trace_event.h" | 19 #include "base/trace_event/trace_event.h" |
19 #include "build/build_config.h" | 20 #include "build/build_config.h" |
20 #include "components/startup_metric_utils/browser/pref_names.h" | 21 #include "components/startup_metric_utils/browser/pref_names.h" |
(...skipping 217 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
238 GetSystemUptimeOnProcessLaunch(); | 239 GetSystemUptimeOnProcessLaunch(); |
239 if (system_uptime_on_process_launch.is_zero()) | 240 if (system_uptime_on_process_launch.is_zero()) |
240 return; | 241 return; |
241 | 242 |
242 UMA_HISTOGRAM_WITH_STARTUP_TEMPERATURE(UMA_HISTOGRAM_LONG_TIMES_100, | 243 UMA_HISTOGRAM_WITH_STARTUP_TEMPERATURE(UMA_HISTOGRAM_LONG_TIMES_100, |
243 "Startup.SystemUptime", | 244 "Startup.SystemUptime", |
244 GetSystemUptimeOnProcessLaunch()); | 245 GetSystemUptimeOnProcessLaunch()); |
245 } | 246 } |
246 | 247 |
247 // On Windows, records the number of hard-faults that have occurred in the | 248 // On Windows, records the number of hard-faults that have occurred in the |
248 // current chrome.exe process since it was started. This is a nop on other | 249 // current chrome.exe process since it was started. A version of the histograms |
249 // platforms. | 250 // recorded in this method suffixed by |same_version_startup_count| will also be |
250 void RecordHardFaultHistogram(bool is_first_run) { | 251 // recorded (unless |same_version_startup_count| is 0 which indicates it's |
| 252 // unknown). This is a nop on other platforms. |
| 253 void RecordHardFaultHistogram(int same_version_startup_count) { |
251 #if defined(OS_WIN) | 254 #if defined(OS_WIN) |
252 uint32_t hard_fault_count = 0; | 255 uint32_t hard_fault_count = 0; |
253 bool has_os_support = false; | 256 bool has_os_support = false; |
254 bool success = GetHardFaultCountForCurrentProcess( | 257 bool success = GetHardFaultCountForCurrentProcess( |
255 &hard_fault_count, &has_os_support); | 258 &hard_fault_count, &has_os_support); |
256 | 259 |
257 // Log whether or not the system call was successful, assuming the OS was | 260 // Log whether or not the system call was successful, assuming the OS was |
258 // detected to support it. | 261 // detected to support it. |
259 if (has_os_support) { | 262 if (has_os_support) { |
260 UMA_HISTOGRAM_BOOLEAN( | 263 UMA_HISTOGRAM_BOOLEAN( |
261 "Startup.BrowserMessageLoopStartHardFaultCount.Success", | 264 "Startup.BrowserMessageLoopStartHardFaultCount.Success", |
262 success); | 265 success); |
263 } | 266 } |
264 | 267 |
265 // Don't log a histogram value if unable to get the hard fault count. | 268 // Don't record histograms if unable to get the hard fault count. |
266 if (!success) | 269 if (!success) |
267 return; | 270 return; |
268 | 271 |
| 272 std::string same_version_startup_count_suffix; |
| 273 if (same_version_startup_count != 0) { |
| 274 // Histograms below will be suffixed by |same_version_startup_count| up to |
| 275 // |kMaxSameVersionCountRecorded|, higher counts will be grouped in the |
| 276 // ".Over" suffix. Make sure to reflect changes to |
| 277 // kMaxSameVersionCountRecorded in the "SameVersionStartupCounts" histogram |
| 278 // suffix. |
| 279 const int kMaxSameVersionCountRecorded = 9; |
| 280 same_version_startup_count_suffix.push_back('.'); |
| 281 DCHECK_GE(same_version_startup_count, 1); |
| 282 if (same_version_startup_count <= kMaxSameVersionCountRecorded) { |
| 283 same_version_startup_count_suffix.append( |
| 284 base::IntToString(same_version_startup_count)); |
| 285 } else { |
| 286 same_version_startup_count_suffix.append("Over"); |
| 287 } |
| 288 } |
| 289 |
269 // Hard fault counts are expected to be in the thousands range, | 290 // Hard fault counts are expected to be in the thousands range, |
270 // corresponding to faulting in ~10s of MBs of code ~10s of KBs at a time. | 291 // corresponding to faulting in ~10s of MBs of code ~10s of KBs at a time. |
271 // (Observed to vary from 1000 to 10000 on various test machines and | 292 // (Observed to vary from 1000 to 10000 on various test machines and |
272 // platforms.) | 293 // platforms.) |
273 if (is_first_run) { | 294 const char kHardFaultCountHistogram[] = |
274 UMA_HISTOGRAM_CUSTOM_COUNTS( | 295 "Startup.BrowserMessageLoopStartHardFaultCount"; |
275 "Startup.BrowserMessageLoopStartHardFaultCount.FirstRun", | 296 UMA_HISTOGRAM_CUSTOM_COUNTS(kHardFaultCountHistogram, hard_fault_count, 1, |
276 hard_fault_count, | 297 40000, 50); |
277 0, 40000, 50); | 298 // Also record the hard fault count histogram suffixed by the number of |
278 } else { | 299 // startups this specific version has been through. |
279 UMA_HISTOGRAM_CUSTOM_COUNTS( | 300 // Factory properties copied from UMA_HISTOGRAM_CUSTOM_COUNTS macro. |
280 "Startup.BrowserMessageLoopStartHardFaultCount", | 301 if (!same_version_startup_count_suffix.empty()) { |
281 hard_fault_count, | 302 base::Histogram::FactoryGet( |
282 0, 40000, 50); | 303 kHardFaultCountHistogram + same_version_startup_count_suffix, 1, 40000, |
| 304 50, base::HistogramBase::kUmaTargetedHistogramFlag) |
| 305 ->Add(hard_fault_count); |
283 } | 306 } |
284 | 307 |
285 // Determine the startup type based on the number of observed hard faults. | 308 // Determine the startup type based on the number of observed hard faults. |
286 DCHECK_EQ(UNDETERMINED_STARTUP_TEMPERATURE, g_startup_temperature); | 309 DCHECK_EQ(UNDETERMINED_STARTUP_TEMPERATURE, g_startup_temperature); |
287 if (hard_fault_count < WARM_START_HARD_FAULT_COUNT_THRESHOLD) { | 310 if (hard_fault_count < WARM_START_HARD_FAULT_COUNT_THRESHOLD) { |
288 g_startup_temperature = WARM_STARTUP_TEMPERATURE; | 311 g_startup_temperature = WARM_STARTUP_TEMPERATURE; |
289 } else if (hard_fault_count >= COLD_START_HARD_FAULT_COUNT_THRESHOLD) { | 312 } else if (hard_fault_count >= COLD_START_HARD_FAULT_COUNT_THRESHOLD) { |
290 g_startup_temperature = COLD_STARTUP_TEMPERATURE; | 313 g_startup_temperature = COLD_STARTUP_TEMPERATURE; |
291 } else { | 314 } else { |
292 g_startup_temperature = LUKEWARM_STARTUP_TEMPERATURE; | 315 g_startup_temperature = LUKEWARM_STARTUP_TEMPERATURE; |
293 } | 316 } |
294 | 317 |
295 // Record the startup 'temperature'. | 318 // Record the startup 'temperature'. |
296 UMA_HISTOGRAM_ENUMERATION( | 319 const char kStartupTemperatureHistogram[] = "Startup.Temperature"; |
297 "Startup.Temperature", g_startup_temperature, STARTUP_TEMPERATURE_COUNT); | 320 UMA_HISTOGRAM_ENUMERATION(kStartupTemperatureHistogram, g_startup_temperature, |
| 321 STARTUP_TEMPERATURE_COUNT); |
| 322 // As well as its suffixed twin. |
| 323 // Factory properties copied from UMA_HISTOGRAM_ENUMERATION macro. |
| 324 if (!same_version_startup_count_suffix.empty()) { |
| 325 base::LinearHistogram::FactoryGet( |
| 326 kStartupTemperatureHistogram + same_version_startup_count_suffix, 1, |
| 327 STARTUP_TEMPERATURE_COUNT, STARTUP_TEMPERATURE_COUNT + 1, |
| 328 base::HistogramBase::kUmaTargetedHistogramFlag) |
| 329 ->Add(g_startup_temperature); |
| 330 } |
298 #endif // defined(OS_WIN) | 331 #endif // defined(OS_WIN) |
299 } | 332 } |
300 | 333 |
301 // Converts a base::Time value to a base::TimeTicks value. The conversion isn't | 334 // Converts a base::Time value to a base::TimeTicks value. The conversion isn't |
302 // exact, but by capturing Time::Now() as early as possible, the likelihood of a | 335 // exact, but by capturing Time::Now() as early as possible, the likelihood of a |
303 // clock change between it and process start is as low as possible. There is | 336 // clock change between it and process start is as low as possible. There is |
304 // also the time taken to synchronously resolve base::Time::Now() and | 337 // also the time taken to synchronously resolve base::Time::Now() and |
305 // base::TimeTicks::Now() at play, but in practice it is pretty much instant | 338 // base::TimeTicks::Now() at play, but in practice it is pretty much instant |
306 // compared to multi-seconds startup timings. | 339 // compared to multi-seconds startup timings. |
307 base::TimeTicks StartupTimeToTimeTicks(const base::Time& time) { | 340 base::TimeTicks StartupTimeToTimeTicks(const base::Time& time) { |
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
378 scoped_ptr<base::Environment> env(base::Environment::Create()); | 411 scoped_ptr<base::Environment> env(base::Environment::Create()); |
379 std::string ticks_string; | 412 std::string ticks_string; |
380 int64 time_int = 0; | 413 int64 time_int = 0; |
381 if (env->GetVar(kChromeMainTicksEnvVar, &ticks_string) && | 414 if (env->GetVar(kChromeMainTicksEnvVar, &ticks_string) && |
382 base::StringToInt64(ticks_string, &time_int)) { | 415 base::StringToInt64(ticks_string, &time_int)) { |
383 return base::TimeTicks::FromInternalValue(time_int); | 416 return base::TimeTicks::FromInternalValue(time_int); |
384 } | 417 } |
385 return base::TimeTicks(); | 418 return base::TimeTicks(); |
386 } | 419 } |
387 | 420 |
| 421 // Logs the Startup.TimeSinceLastStartup histogram. Obtains the timestamp of the |
| 422 // last startup from |pref_service| and overwrites it with the timestamp of the |
| 423 // current startup. If the startup temperature has been set by |
| 424 // RecordBrowserMainMessageLoopStart, the time since last startup is also logged |
| 425 // to an histogram suffixed with the startup temperature. |
| 426 void RecordTimeSinceLastStartup(PrefService* pref_service) { |
| 427 #if defined(OS_MACOSX) || defined(OS_WIN) || defined(OS_LINUX) |
| 428 DCHECK(pref_service); |
| 429 |
| 430 // Get the timestamp of the current startup. |
| 431 const base::Time process_start_time = |
| 432 base::CurrentProcessInfo::CreationTime(); |
| 433 |
| 434 // Get the timestamp of the last startup from |pref_service|. |
| 435 const int64_t last_startup_timestamp_internal = |
| 436 pref_service->GetInt64(prefs::kLastStartupTimestamp); |
| 437 if (last_startup_timestamp_internal != 0) { |
| 438 // Log the Startup.TimeSinceLastStartup histogram. |
| 439 const base::Time last_startup_timestamp = |
| 440 base::Time::FromInternalValue(last_startup_timestamp_internal); |
| 441 const base::TimeDelta time_since_last_startup = |
| 442 process_start_time - last_startup_timestamp; |
| 443 const int minutes_since_last_startup = time_since_last_startup.InMinutes(); |
| 444 |
| 445 // Ignore negative values, which can be caused by system clock changes. |
| 446 if (minutes_since_last_startup >= 0) { |
| 447 UMA_HISTOGRAM_WITH_STARTUP_TEMPERATURE( |
| 448 UMA_HISTOGRAM_TIME_IN_MINUTES_MONTH_RANGE, |
| 449 "Startup.TimeSinceLastStartup", minutes_since_last_startup); |
| 450 } |
| 451 } |
| 452 |
| 453 // Write the timestamp of the current startup in |pref_service|. |
| 454 pref_service->SetInt64(prefs::kLastStartupTimestamp, |
| 455 process_start_time.ToInternalValue()); |
| 456 #endif // defined(OS_MACOSX) || defined(OS_WIN) || defined(OS_LINUX) |
| 457 } |
| 458 |
| 459 // Logs the Startup.SameVersionStartupCount histogram. Relies on |pref_service| |
| 460 // to know information about the previous startups and store information for |
| 461 // future ones. Returns the number of startups with the same version count that |
| 462 // was logged. |
| 463 int RecordSameVersionStartupCount(PrefService* pref_service) { |
| 464 DCHECK(pref_service); |
| 465 |
| 466 const std::string current_version = version_info::GetVersionNumber(); |
| 467 |
| 468 int startups_with_current_version = 0; |
| 469 if (current_version == pref_service->GetString(prefs::kLastStartupVersion)) { |
| 470 startups_with_current_version = |
| 471 pref_service->GetInteger(prefs::kSameVersionStartupCount); |
| 472 ++startups_with_current_version; |
| 473 pref_service->SetInteger(prefs::kSameVersionStartupCount, |
| 474 startups_with_current_version); |
| 475 } else { |
| 476 startups_with_current_version = 1; |
| 477 pref_service->SetString(prefs::kLastStartupVersion, current_version); |
| 478 pref_service->SetInteger(prefs::kSameVersionStartupCount, 1); |
| 479 } |
| 480 |
| 481 UMA_HISTOGRAM_COUNTS_100("Startup.SameVersionStartupCount", |
| 482 startups_with_current_version); |
| 483 return startups_with_current_version; |
| 484 } |
| 485 |
388 } // namespace | 486 } // namespace |
389 | 487 |
390 void RegisterPrefs(PrefRegistrySimple* registry) { | 488 void RegisterPrefs(PrefRegistrySimple* registry) { |
391 DCHECK(registry); | 489 DCHECK(registry); |
392 registry->RegisterInt64Pref(prefs::kLastStartupTimestamp, 0); | 490 registry->RegisterInt64Pref(prefs::kLastStartupTimestamp, 0); |
393 registry->RegisterStringPref(prefs::kLastStartupVersion, std::string()); | 491 registry->RegisterStringPref(prefs::kLastStartupVersion, std::string()); |
394 registry->RegisterIntegerPref(prefs::kSameVersionStartupCount, 0); | 492 registry->RegisterIntegerPref(prefs::kSameVersionStartupCount, 0); |
395 } | 493 } |
396 | 494 |
397 bool WasNonBrowserUIDisplayed() { | 495 bool WasNonBrowserUIDisplayed() { |
(...skipping 23 matching lines...) Expand all Loading... |
421 } | 519 } |
422 | 520 |
423 void RecordExeMainEntryPointTime(const base::Time& time) { | 521 void RecordExeMainEntryPointTime(const base::Time& time) { |
424 const std::string exe_load_ticks = | 522 const std::string exe_load_ticks = |
425 base::Int64ToString(StartupTimeToTimeTicks(time).ToInternalValue()); | 523 base::Int64ToString(StartupTimeToTimeTicks(time).ToInternalValue()); |
426 scoped_ptr<base::Environment> env(base::Environment::Create()); | 524 scoped_ptr<base::Environment> env(base::Environment::Create()); |
427 env->SetVar(kChromeMainTicksEnvVar, exe_load_ticks); | 525 env->SetVar(kChromeMainTicksEnvVar, exe_load_ticks); |
428 } | 526 } |
429 | 527 |
430 void RecordBrowserMainMessageLoopStart(const base::TimeTicks& ticks, | 528 void RecordBrowserMainMessageLoopStart(const base::TimeTicks& ticks, |
431 bool is_first_run) { | 529 bool is_first_run, |
432 RecordHardFaultHistogram(is_first_run); | 530 PrefService* pref_service) { |
| 531 int same_version_startup_count = 0; |
| 532 if (pref_service) |
| 533 same_version_startup_count = RecordSameVersionStartupCount(pref_service); |
| 534 // Keep RecordHardFaultHistogram() first as much as possible as many other |
| 535 // histograms depend on it setting |g_startup_temperature|. |
| 536 RecordHardFaultHistogram(same_version_startup_count); |
| 537 if (pref_service) |
| 538 RecordTimeSinceLastStartup(pref_service); |
433 RecordSystemUptimeHistogram(); | 539 RecordSystemUptimeHistogram(); |
434 RecordMainEntryTimeHistogram(); | 540 RecordMainEntryTimeHistogram(); |
435 | 541 |
436 const base::TimeTicks& process_creation_ticks = | 542 const base::TimeTicks& process_creation_ticks = |
437 g_process_creation_ticks.Get(); | 543 g_process_creation_ticks.Get(); |
438 if (!is_first_run && !process_creation_ticks.is_null()) { | 544 if (!is_first_run && !process_creation_ticks.is_null()) { |
439 UMA_HISTOGRAM_AND_TRACE_WITH_STARTUP_TEMPERATURE( | 545 UMA_HISTOGRAM_AND_TRACE_WITH_STARTUP_TEMPERATURE( |
440 UMA_HISTOGRAM_LONG_TIMES_100, "Startup.BrowserMessageLoopStartTime", | 546 UMA_HISTOGRAM_LONG_TIMES_100, "Startup.BrowserMessageLoopStartTime", |
441 process_creation_ticks, ticks); | 547 process_creation_ticks, ticks); |
442 } | 548 } |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
481 | 587 |
482 // Process create to chrome.dll:main(). Reported as a histogram only as | 588 // Process create to chrome.dll:main(). Reported as a histogram only as |
483 // the other two events above are sufficient for tracing purposes. | 589 // the other two events above are sufficient for tracing purposes. |
484 UMA_HISTOGRAM_WITH_STARTUP_TEMPERATURE( | 590 UMA_HISTOGRAM_WITH_STARTUP_TEMPERATURE( |
485 UMA_HISTOGRAM_LONG_TIMES, "Startup.LoadTime.ProcessCreateToDllMain", | 591 UMA_HISTOGRAM_LONG_TIMES, "Startup.LoadTime.ProcessCreateToDllMain", |
486 g_main_entry_point_ticks.Get() - process_creation_ticks); | 592 g_main_entry_point_ticks.Get() - process_creation_ticks); |
487 } | 593 } |
488 } | 594 } |
489 } | 595 } |
490 | 596 |
491 void RecordTimeSinceLastStartup(PrefService* pref_service) { | |
492 #if defined(OS_MACOSX) || defined(OS_WIN) || defined(OS_LINUX) | |
493 DCHECK(pref_service); | |
494 | |
495 // Get the timestamp of the current startup. | |
496 const base::Time process_start_time = | |
497 base::CurrentProcessInfo::CreationTime(); | |
498 | |
499 // Get the timestamp of the last startup from |pref_service|. | |
500 const int64_t last_startup_timestamp_internal = | |
501 pref_service->GetInt64(prefs::kLastStartupTimestamp); | |
502 if (last_startup_timestamp_internal != 0) { | |
503 // Log the Startup.TimeSinceLastStartup histogram. | |
504 const base::Time last_startup_timestamp = | |
505 base::Time::FromInternalValue(last_startup_timestamp_internal); | |
506 const base::TimeDelta time_since_last_startup = | |
507 process_start_time - last_startup_timestamp; | |
508 const int minutes_since_last_startup = time_since_last_startup.InMinutes(); | |
509 | |
510 // Ignore negative values, which can be caused by system clock changes. | |
511 if (minutes_since_last_startup >= 0) { | |
512 UMA_HISTOGRAM_WITH_STARTUP_TEMPERATURE( | |
513 UMA_HISTOGRAM_TIME_IN_MINUTES_MONTH_RANGE, | |
514 "Startup.TimeSinceLastStartup", minutes_since_last_startup); | |
515 } | |
516 } | |
517 | |
518 // Write the timestamp of the current startup in |pref_service|. | |
519 pref_service->SetInt64(prefs::kLastStartupTimestamp, | |
520 process_start_time.ToInternalValue()); | |
521 #endif // defined(OS_MACOSX) || defined(OS_WIN) || defined(OS_LINUX) | |
522 } | |
523 | |
524 void RecordStartupCount(PrefService* pref_service) { | |
525 DCHECK(pref_service); | |
526 | |
527 const std::string current_version = version_info::GetVersionNumber(); | |
528 | |
529 int startups_with_current_version = 0; | |
530 if (current_version == pref_service->GetString(prefs::kLastStartupVersion)) { | |
531 startups_with_current_version = | |
532 pref_service->GetInteger(prefs::kSameVersionStartupCount); | |
533 ++startups_with_current_version; | |
534 pref_service->SetInteger(prefs::kSameVersionStartupCount, | |
535 startups_with_current_version); | |
536 } else { | |
537 startups_with_current_version = 1; | |
538 pref_service->SetString(prefs::kLastStartupVersion, current_version); | |
539 pref_service->SetInteger(prefs::kSameVersionStartupCount, 1); | |
540 } | |
541 | |
542 UMA_HISTOGRAM_COUNTS_100("Startup.SameVersionStartupCount", | |
543 startups_with_current_version); | |
544 } | |
545 | |
546 void RecordBrowserWindowDisplay(const base::TimeTicks& ticks) { | 597 void RecordBrowserWindowDisplay(const base::TimeTicks& ticks) { |
547 static bool is_first_call = true; | 598 static bool is_first_call = true; |
548 if (!is_first_call || ticks.is_null()) | 599 if (!is_first_call || ticks.is_null()) |
549 return; | 600 return; |
550 is_first_call = false; | 601 is_first_call = false; |
551 if (WasNonBrowserUIDisplayed() || g_process_creation_ticks.Get().is_null()) | 602 if (WasNonBrowserUIDisplayed() || g_process_creation_ticks.Get().is_null()) |
552 return; | 603 return; |
553 | 604 |
554 UMA_HISTOGRAM_AND_TRACE_WITH_STARTUP_TEMPERATURE( | 605 UMA_HISTOGRAM_AND_TRACE_WITH_STARTUP_TEMPERATURE( |
555 UMA_HISTOGRAM_LONG_TIMES, "Startup.BrowserWindowDisplay", | 606 UMA_HISTOGRAM_LONG_TIMES, "Startup.BrowserWindowDisplay", |
(...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
651 | 702 |
652 base::TimeTicks MainEntryPointTicks() { | 703 base::TimeTicks MainEntryPointTicks() { |
653 return g_main_entry_point_ticks.Get(); | 704 return g_main_entry_point_ticks.Get(); |
654 } | 705 } |
655 | 706 |
656 StartupTemperature GetStartupTemperature() { | 707 StartupTemperature GetStartupTemperature() { |
657 return g_startup_temperature; | 708 return g_startup_temperature; |
658 } | 709 } |
659 | 710 |
660 } // namespace startup_metric_utils | 711 } // namespace startup_metric_utils |
OLD | NEW |