OLD | NEW |
1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 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/metrics/file_metrics_provider.h" | 5 #include "components/metrics/file_metrics_provider.h" |
6 | 6 |
7 #include "base/command_line.h" | 7 #include "base/command_line.h" |
8 #include "base/files/file.h" | 8 #include "base/files/file.h" |
9 #include "base/files/file_enumerator.h" | 9 #include "base/files/file_enumerator.h" |
10 #include "base/files/file_util.h" | 10 #include "base/files/file_util.h" |
11 #include "base/files/memory_mapped_file.h" | 11 #include "base/files/memory_mapped_file.h" |
12 #include "base/logging.h" | 12 #include "base/logging.h" |
13 #include "base/memory/ptr_util.h" | 13 #include "base/memory/ptr_util.h" |
14 #include "base/metrics/histogram_base.h" | 14 #include "base/metrics/histogram_base.h" |
15 #include "base/metrics/histogram_macros.h" | 15 #include "base/metrics/histogram_macros.h" |
16 #include "base/metrics/persistent_histogram_allocator.h" | 16 #include "base/metrics/persistent_histogram_allocator.h" |
17 #include "base/metrics/persistent_memory_allocator.h" | 17 #include "base/metrics/persistent_memory_allocator.h" |
18 #include "base/strings/string_piece.h" | 18 #include "base/strings/string_piece.h" |
19 #include "base/task_runner.h" | 19 #include "base/task_runner.h" |
20 #include "base/time/time.h" | 20 #include "base/time/time.h" |
21 #include "components/metrics/metrics_pref_names.h" | 21 #include "components/metrics/metrics_pref_names.h" |
22 #include "components/metrics/metrics_service.h" | 22 #include "components/metrics/metrics_service.h" |
| 23 #include "components/metrics/persistent_system_profile.h" |
23 #include "components/prefs/pref_registry_simple.h" | 24 #include "components/prefs/pref_registry_simple.h" |
24 #include "components/prefs/pref_service.h" | 25 #include "components/prefs/pref_service.h" |
25 | 26 |
26 namespace metrics { | 27 namespace metrics { |
27 | 28 |
28 namespace { | 29 namespace { |
29 | 30 |
30 // These structures provide values used to define how files are opened and | 31 // These structures provide values used to define how files are opened and |
31 // accessed. It obviates the need for multiple code-paths within several of | 32 // accessed. It obviates the need for multiple code-paths within several of |
32 // the methods. | 33 // the methods. |
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
77 // of the delete task. | 78 // of the delete task. |
78 base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ | | 79 base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ | |
79 base::File::FLAG_DELETE_ON_CLOSE); | 80 base::File::FLAG_DELETE_ON_CLOSE); |
80 } | 81 } |
81 | 82 |
82 } // namespace | 83 } // namespace |
83 | 84 |
84 // This structure stores all the information about the sources being monitored | 85 // This structure stores all the information about the sources being monitored |
85 // and their current reporting state. | 86 // and their current reporting state. |
86 struct FileMetricsProvider::SourceInfo { | 87 struct FileMetricsProvider::SourceInfo { |
87 SourceInfo(SourceType source_type) : type(source_type) {} | 88 SourceInfo(SourceType source_type, SourceAssociation source_association) |
| 89 : type(source_type), association(source_association) {} |
88 ~SourceInfo() {} | 90 ~SourceInfo() {} |
89 | 91 |
90 // How to access this source (file/dir, atomic/active). | 92 // How to access this source (file/dir, atomic/active). |
91 const SourceType type; | 93 const SourceType type; |
92 | 94 |
| 95 // With what run this source is associated. |
| 96 const SourceAssociation association; |
| 97 |
93 // Where on disk the directory is located. This will only be populated when | 98 // Where on disk the directory is located. This will only be populated when |
94 // a directory is being monitored. | 99 // a directory is being monitored. |
95 base::FilePath directory; | 100 base::FilePath directory; |
96 | 101 |
97 // Where on disk the file is located. If a directory is being monitored, | 102 // Where on disk the file is located. If a directory is being monitored, |
98 // this will be updated for whatever file is being read. | 103 // this will be updated for whatever file is being read. |
99 base::FilePath path; | 104 base::FilePath path; |
100 | 105 |
101 // Name used inside prefs to persistent metadata. | 106 // Name used inside prefs to persistent metadata. |
102 std::string prefs_key; | 107 std::string prefs_key; |
(...skipping 26 matching lines...) Expand all Loading... |
129 | 134 |
130 void FileMetricsProvider::RegisterSource(const base::FilePath& path, | 135 void FileMetricsProvider::RegisterSource(const base::FilePath& path, |
131 SourceType type, | 136 SourceType type, |
132 SourceAssociation source_association, | 137 SourceAssociation source_association, |
133 const base::StringPiece prefs_key) { | 138 const base::StringPiece prefs_key) { |
134 DCHECK(thread_checker_.CalledOnValidThread()); | 139 DCHECK(thread_checker_.CalledOnValidThread()); |
135 | 140 |
136 // Ensure that kSourceOptions has been filled for this type. | 141 // Ensure that kSourceOptions has been filled for this type. |
137 DCHECK_GT(arraysize(kSourceOptions), static_cast<size_t>(type)); | 142 DCHECK_GT(arraysize(kSourceOptions), static_cast<size_t>(type)); |
138 | 143 |
139 std::unique_ptr<SourceInfo> source(new SourceInfo(type)); | 144 std::unique_ptr<SourceInfo> source(new SourceInfo(type, source_association)); |
140 source->prefs_key = prefs_key.as_string(); | 145 source->prefs_key = prefs_key.as_string(); |
141 | 146 |
142 switch (source->type) { | 147 switch (source->type) { |
143 case SOURCE_HISTOGRAMS_ACTIVE_FILE: | 148 case SOURCE_HISTOGRAMS_ACTIVE_FILE: |
144 DCHECK(prefs_key.empty()); | 149 DCHECK(prefs_key.empty()); |
145 // fall through | 150 // fall through |
146 case SOURCE_HISTOGRAMS_ATOMIC_FILE: | 151 case SOURCE_HISTOGRAMS_ATOMIC_FILE: |
147 source->path = path; | 152 source->path = path; |
148 break; | 153 break; |
149 case SOURCE_HISTOGRAMS_ATOMIC_DIR: | 154 case SOURCE_HISTOGRAMS_ATOMIC_DIR: |
150 source->directory = path; | 155 source->directory = path; |
151 break; | 156 break; |
152 } | 157 } |
153 | 158 |
154 // |prefs_key| may be empty if the caller does not wish to persist the | 159 // |prefs_key| may be empty if the caller does not wish to persist the |
155 // state across instances of the program. | 160 // state across instances of the program. |
156 if (pref_service_ && !prefs_key.empty()) { | 161 if (pref_service_ && !prefs_key.empty()) { |
157 source->last_seen = base::Time::FromInternalValue( | 162 source->last_seen = base::Time::FromInternalValue( |
158 pref_service_->GetInt64(metrics::prefs::kMetricsLastSeenPrefix + | 163 pref_service_->GetInt64(metrics::prefs::kMetricsLastSeenPrefix + |
159 source->prefs_key)); | 164 source->prefs_key)); |
160 } | 165 } |
161 | 166 |
162 switch (source_association) { | 167 switch (source_association) { |
163 case ASSOCIATE_CURRENT_RUN: | 168 case ASSOCIATE_CURRENT_RUN: |
| 169 case ASSOCIATE_INTERNAL_PROFILE: |
164 sources_to_check_.push_back(std::move(source)); | 170 sources_to_check_.push_back(std::move(source)); |
165 break; | 171 break; |
166 case ASSOCIATE_PREVIOUS_RUN: | 172 case ASSOCIATE_PREVIOUS_RUN: |
| 173 case ASSOCIATE_INTERNAL_PROFILE_OR_PREVIOUS_RUN: |
167 DCHECK_EQ(SOURCE_HISTOGRAMS_ATOMIC_FILE, source->type); | 174 DCHECK_EQ(SOURCE_HISTOGRAMS_ATOMIC_FILE, source->type); |
168 sources_for_previous_run_.push_back(std::move(source)); | 175 sources_for_previous_run_.push_back(std::move(source)); |
169 break; | 176 break; |
170 } | 177 } |
171 } | 178 } |
172 | 179 |
173 // static | 180 // static |
174 void FileMetricsProvider::RegisterPrefs(PrefRegistrySimple* prefs, | 181 void FileMetricsProvider::RegisterPrefs(PrefRegistrySimple* prefs, |
175 const base::StringPiece prefs_key) { | 182 const base::StringPiece prefs_key) { |
176 prefs->RegisterInt64Pref(metrics::prefs::kMetricsLastSeenPrefix + | 183 prefs->RegisterInt64Pref(metrics::prefs::kMetricsLastSeenPrefix + |
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
243 if (oldest_file_path.empty()) | 250 if (oldest_file_path.empty()) |
244 return false; | 251 return false; |
245 | 252 |
246 // Set the active file to be the oldest modified file that has not yet | 253 // Set the active file to be the oldest modified file that has not yet |
247 // been read. | 254 // been read. |
248 source->path = std::move(oldest_file_path); | 255 source->path = std::move(oldest_file_path); |
249 return true; | 256 return true; |
250 } | 257 } |
251 | 258 |
252 // static | 259 // static |
| 260 void FileMetricsProvider::FinishedWithSource(SourceInfo* source, |
| 261 AccessResult result) { |
| 262 // Different source types require different post-processing. |
| 263 switch (source->type) { |
| 264 case SOURCE_HISTOGRAMS_ATOMIC_FILE: |
| 265 case SOURCE_HISTOGRAMS_ATOMIC_DIR: |
| 266 // Done with this file so delete the allocator and its owned file. |
| 267 source->allocator.reset(); |
| 268 // Remove the file if has been recorded. This prevents them from |
| 269 // accumulating or also being recorded by different instances of |
| 270 // the browser. |
| 271 if (result == ACCESS_RESULT_SUCCESS || |
| 272 result == ACCESS_RESULT_NOT_MODIFIED) { |
| 273 DeleteFileWhenPossible(source->path); |
| 274 } |
| 275 break; |
| 276 case SOURCE_HISTOGRAMS_ACTIVE_FILE: |
| 277 // Keep the allocator open so it doesn't have to be re-mapped each |
| 278 // time. This also allows the contents to be merged on-demand. |
| 279 break; |
| 280 } |
| 281 } |
| 282 |
| 283 // static |
253 void FileMetricsProvider::CheckAndMergeMetricSourcesOnTaskRunner( | 284 void FileMetricsProvider::CheckAndMergeMetricSourcesOnTaskRunner( |
254 SourceInfoList* sources) { | 285 SourceInfoList* sources) { |
255 // This method has all state information passed in |sources| and is intended | 286 // This method has all state information passed in |sources| and is intended |
256 // to run on a worker thread rather than the UI thread. | 287 // to run on a worker thread rather than the UI thread. |
257 for (std::unique_ptr<SourceInfo>& source : *sources) { | 288 for (std::unique_ptr<SourceInfo>& source : *sources) { |
258 AccessResult result = CheckAndMapMetricSource(source.get()); | 289 AccessResult result = CheckAndMapMetricSource(source.get()); |
259 | 290 |
260 // Some results are not reported in order to keep the dashboard clean. | 291 // Some results are not reported in order to keep the dashboard clean. |
261 if (result != ACCESS_RESULT_DOESNT_EXIST && | 292 if (result != ACCESS_RESULT_DOESNT_EXIST && |
262 result != ACCESS_RESULT_NOT_MODIFIED) { | 293 result != ACCESS_RESULT_NOT_MODIFIED) { |
263 UMA_HISTOGRAM_ENUMERATION( | 294 UMA_HISTOGRAM_ENUMERATION( |
264 "UMA.FileMetricsProvider.AccessResult", result, ACCESS_RESULT_MAX); | 295 "UMA.FileMetricsProvider.AccessResult", result, ACCESS_RESULT_MAX); |
265 } | 296 } |
266 | 297 |
| 298 // Metrics associted with internal profiles have to be fetched directly |
| 299 // so just keep the mapping for use by the main thread. |
| 300 if (source->association == ASSOCIATE_INTERNAL_PROFILE) |
| 301 continue; |
| 302 |
267 // Mapping was successful. Merge it. | 303 // Mapping was successful. Merge it. |
268 if (result == ACCESS_RESULT_SUCCESS) { | 304 if (result == ACCESS_RESULT_SUCCESS) { |
269 MergeHistogramDeltasFromSource(source.get()); | 305 MergeHistogramDeltasFromSource(source.get()); |
270 DCHECK(source->read_complete); | 306 DCHECK(source->read_complete); |
271 } | 307 } |
272 | 308 |
273 // Different source types require different post-processing. | 309 // All done with this source. |
274 switch (source->type) { | 310 FinishedWithSource(source.get(), result); |
275 case SOURCE_HISTOGRAMS_ATOMIC_FILE: | |
276 case SOURCE_HISTOGRAMS_ATOMIC_DIR: | |
277 // Done with this file so delete the allocator and its owned file. | |
278 source->allocator.reset(); | |
279 // Remove the file if has been recorded. This prevents them from | |
280 // accumulating or also being recorded by different instances of | |
281 // the browser. | |
282 if (result == ACCESS_RESULT_SUCCESS || | |
283 result == ACCESS_RESULT_NOT_MODIFIED) { | |
284 base::DeleteFile(source->path, /*recursive=*/false); | |
285 } | |
286 break; | |
287 case SOURCE_HISTOGRAMS_ACTIVE_FILE: | |
288 // Keep the allocator open so it doesn't have to be re-mapped each | |
289 // time. This also allows the contents to be merged on-demand. | |
290 break; | |
291 } | |
292 } | 311 } |
293 } | 312 } |
294 | 313 |
295 // This method has all state information passed in |source| and is intended | 314 // This method has all state information passed in |source| and is intended |
296 // to run on a worker thread rather than the UI thread. | 315 // to run on a worker thread rather than the UI thread. |
297 // static | 316 // static |
298 FileMetricsProvider::AccessResult FileMetricsProvider::CheckAndMapMetricSource( | 317 FileMetricsProvider::AccessResult FileMetricsProvider::CheckAndMapMetricSource( |
299 SourceInfo* source) { | 318 SourceInfo* source) { |
| 319 // If source was read, clean up after it. |
| 320 if (source->read_complete) |
| 321 FinishedWithSource(source, ACCESS_RESULT_SUCCESS); |
| 322 source->read_complete = false; |
300 DCHECK(!source->allocator); | 323 DCHECK(!source->allocator); |
301 source->read_complete = false; | |
302 | 324 |
303 // If the source is a directory, look for files within it. | 325 // If the source is a directory, look for files within it. |
304 if (!source->directory.empty() && !LocateNextFileInDirectory(source)) | 326 if (!source->directory.empty() && !LocateNextFileInDirectory(source)) |
305 return ACCESS_RESULT_DOESNT_EXIST; | 327 return ACCESS_RESULT_DOESNT_EXIST; |
306 | 328 |
307 // Do basic validation on the file metadata. | 329 // Do basic validation on the file metadata. |
308 base::File::Info info; | 330 base::File::Info info; |
309 if (!base::GetFileInfo(source->path, &info)) | 331 if (!base::GetFileInfo(source->path, &info)) |
310 return ACCESS_RESULT_DOESNT_EXIST; | 332 return ACCESS_RESULT_DOESNT_EXIST; |
311 | 333 |
(...skipping 118 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
430 // try again immediately to see if more is available (in a directory of | 452 // try again immediately to see if more is available (in a directory of |
431 // files). Otherwise, remember the source for checking again at a later time. | 453 // files). Otherwise, remember the source for checking again at a later time. |
432 bool did_read = false; | 454 bool did_read = false; |
433 for (auto iter = checked->begin(); iter != checked->end();) { | 455 for (auto iter = checked->begin(); iter != checked->end();) { |
434 auto temp = iter++; | 456 auto temp = iter++; |
435 SourceInfo* source = temp->get(); | 457 SourceInfo* source = temp->get(); |
436 if (source->read_complete) { | 458 if (source->read_complete) { |
437 RecordSourceAsRead(source); | 459 RecordSourceAsRead(source); |
438 did_read = true; | 460 did_read = true; |
439 } | 461 } |
440 if (source->allocator) | 462 if (source->allocator) { |
441 sources_mapped_.splice(sources_mapped_.end(), *checked, temp); | 463 if (source->association == ASSOCIATE_INTERNAL_PROFILE) { |
442 else | 464 sources_with_profile_.splice(sources_with_profile_.end(), *checked, |
| 465 temp); |
| 466 } else { |
| 467 sources_mapped_.splice(sources_mapped_.end(), *checked, temp); |
| 468 } |
| 469 } else { |
443 sources_to_check_.splice(sources_to_check_.end(), *checked, temp); | 470 sources_to_check_.splice(sources_to_check_.end(), *checked, temp); |
| 471 } |
444 } | 472 } |
445 | 473 |
446 // If a read was done, schedule another one immediately. In the case of a | 474 // If a read was done, schedule another one immediately. In the case of a |
447 // directory of files, this ensures that all entries get processed. It's | 475 // directory of files, this ensures that all entries get processed. It's |
448 // done here instead of as a loop in CheckAndMergeMetricSourcesOnTaskRunner | 476 // done here instead of as a loop in CheckAndMergeMetricSourcesOnTaskRunner |
449 // so that (a) it gives the disk a rest and (b) testing of individual reads | 477 // so that (a) it gives the disk a rest and (b) testing of individual reads |
450 // is possible. | 478 // is possible. |
451 if (did_read) | 479 if (did_read) |
452 ScheduleSourcesCheck(); | 480 ScheduleSourcesCheck(); |
453 } | 481 } |
(...skipping 26 matching lines...) Expand all Loading... |
480 // Clear any data for initial metrics since they're always reported | 508 // Clear any data for initial metrics since they're always reported |
481 // before the first call to this method. It couldn't be released after | 509 // before the first call to this method. It couldn't be released after |
482 // being reported in RecordInitialHistogramSnapshots because the data | 510 // being reported in RecordInitialHistogramSnapshots because the data |
483 // will continue to be used by the caller after that method returns. Once | 511 // will continue to be used by the caller after that method returns. Once |
484 // here, though, all actions to be done on the data have been completed. | 512 // here, though, all actions to be done on the data have been completed. |
485 for (const std::unique_ptr<SourceInfo>& source : sources_for_previous_run_) | 513 for (const std::unique_ptr<SourceInfo>& source : sources_for_previous_run_) |
486 DeleteFileAsync(source->path); | 514 DeleteFileAsync(source->path); |
487 sources_for_previous_run_.clear(); | 515 sources_for_previous_run_.clear(); |
488 } | 516 } |
489 | 517 |
| 518 bool FileMetricsProvider::ProvideIndependentMetrics( |
| 519 SystemProfileProto* system_profile_proto, |
| 520 base::HistogramSnapshotManager* snapshot_manager) { |
| 521 DCHECK(thread_checker_.CalledOnValidThread()); |
| 522 |
| 523 while (true) { |
| 524 if (sources_with_profile_.empty()) |
| 525 return false; |
| 526 |
| 527 SourceInfo* source = sources_with_profile_.begin()->get(); |
| 528 DCHECK(source->allocator); |
| 529 |
| 530 bool success = false; |
| 531 if (PersistentSystemProfile::GetSystemProfile( |
| 532 *source->allocator->memory_allocator(), system_profile_proto)) { |
| 533 RecordHistogramSnapshotsFromSource(snapshot_manager, source); |
| 534 success = true; |
| 535 } |
| 536 |
| 537 // Regardless of whether this source was successfully recorded, it is never |
| 538 // read again. |
| 539 source->read_complete = true; |
| 540 RecordSourceAsRead(source); |
| 541 sources_to_check_.splice(sources_to_check_.end(), sources_with_profile_, |
| 542 sources_with_profile_.begin()); |
| 543 if (success) |
| 544 return true; |
| 545 } |
| 546 } |
| 547 |
490 bool FileMetricsProvider::HasInitialStabilityMetrics() { | 548 bool FileMetricsProvider::HasInitialStabilityMetrics() { |
491 DCHECK(thread_checker_.CalledOnValidThread()); | 549 DCHECK(thread_checker_.CalledOnValidThread()); |
492 | 550 |
493 // Measure the total time spent checking all sources as well as the time | 551 // Measure the total time spent checking all sources as well as the time |
494 // per individual file. This method is called during startup and thus blocks | 552 // per individual file. This method is called during startup and thus blocks |
495 // the initial showing of the browser window so it's important to know the | 553 // the initial showing of the browser window so it's important to know the |
496 // total delay. | 554 // total delay. |
497 SCOPED_UMA_HISTOGRAM_TIMER("UMA.FileMetricsProvider.InitialCheckTime.Total"); | 555 SCOPED_UMA_HISTOGRAM_TIMER("UMA.FileMetricsProvider.InitialCheckTime.Total"); |
498 | 556 |
499 // Check all sources for previous run to see if they need to be read. | 557 // Check all sources for previous run to see if they need to be read. |
(...skipping 14 matching lines...) Expand all Loading... |
514 // If it couldn't be accessed, remove it from the list. There is only ever | 572 // If it couldn't be accessed, remove it from the list. There is only ever |
515 // one chance to record it so no point keeping it around for later. Also | 573 // one chance to record it so no point keeping it around for later. Also |
516 // mark it as having been read since uploading it with a future browser | 574 // mark it as having been read since uploading it with a future browser |
517 // run would associate it with the then-previous run which would no longer | 575 // run would associate it with the then-previous run which would no longer |
518 // be the run from which it came. | 576 // be the run from which it came. |
519 if (result != ACCESS_RESULT_SUCCESS) { | 577 if (result != ACCESS_RESULT_SUCCESS) { |
520 DCHECK(!source->allocator); | 578 DCHECK(!source->allocator); |
521 RecordSourceAsRead(source); | 579 RecordSourceAsRead(source); |
522 DeleteFileAsync(source->path); | 580 DeleteFileAsync(source->path); |
523 sources_for_previous_run_.erase(temp); | 581 sources_for_previous_run_.erase(temp); |
| 582 continue; |
| 583 } |
| 584 |
| 585 DCHECK(source->allocator); |
| 586 |
| 587 // If the source should be associated with an existing internal profile, |
| 588 // move it to |sources_with_profile_| for later upload. |
| 589 if (source->association == ASSOCIATE_INTERNAL_PROFILE_OR_PREVIOUS_RUN) { |
| 590 if (PersistentSystemProfile::HasSystemProfile( |
| 591 *source->allocator->memory_allocator())) { |
| 592 sources_with_profile_.splice(sources_with_profile_.end(), |
| 593 sources_for_previous_run_, temp); |
| 594 } |
524 } | 595 } |
525 } | 596 } |
526 | 597 |
527 return !sources_for_previous_run_.empty(); | 598 return !sources_for_previous_run_.empty(); |
528 } | 599 } |
529 | 600 |
530 void FileMetricsProvider::RecordInitialHistogramSnapshots( | 601 void FileMetricsProvider::RecordInitialHistogramSnapshots( |
531 base::HistogramSnapshotManager* snapshot_manager) { | 602 base::HistogramSnapshotManager* snapshot_manager) { |
532 DCHECK(thread_checker_.CalledOnValidThread()); | 603 DCHECK(thread_checker_.CalledOnValidThread()); |
533 | 604 |
(...skipping 29 matching lines...) Expand all Loading... |
563 // important to know how much total "jank" may be introduced. | 634 // important to know how much total "jank" may be introduced. |
564 SCOPED_UMA_HISTOGRAM_TIMER("UMA.FileMetricsProvider.SnapshotTime.Total"); | 635 SCOPED_UMA_HISTOGRAM_TIMER("UMA.FileMetricsProvider.SnapshotTime.Total"); |
565 | 636 |
566 for (std::unique_ptr<SourceInfo>& source : sources_mapped_) { | 637 for (std::unique_ptr<SourceInfo>& source : sources_mapped_) { |
567 SCOPED_UMA_HISTOGRAM_TIMER("UMA.FileMetricsProvider.SnapshotTime.File"); | 638 SCOPED_UMA_HISTOGRAM_TIMER("UMA.FileMetricsProvider.SnapshotTime.File"); |
568 MergeHistogramDeltasFromSource(source.get()); | 639 MergeHistogramDeltasFromSource(source.get()); |
569 } | 640 } |
570 } | 641 } |
571 | 642 |
572 } // namespace metrics | 643 } // namespace metrics |
OLD | NEW |