File: cpu_proportion_tracker.cc

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (174 lines) | stat: -rw-r--r-- 5,982 bytes parent folder | download | duplicates (8)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/performance_manager/public/resource_attribution/cpu_proportion_tracker.h"

#include <algorithm>
#include <utility>

#include "base/check.h"
#include "base/functional/callback.h"

namespace resource_attribution {

CPUProportionTracker::CPUProportionTracker(
    ContextFilterCallback context_filter,
    CPUProportionType cpu_proportion_type)
    : cpu_proportion_type_(cpu_proportion_type),
      context_filter_(std::move(context_filter)) {}

CPUProportionTracker::~CPUProportionTracker() = default;

void CPUProportionTracker::StartFirstInterval(base::TimeTicks time,
                                              const QueryResultMap& results) {
  CHECK(!last_measurement_time_.has_value());
  CHECK(cached_cpu_measurements_.empty());
  last_measurement_time_ = time;
  cached_cpu_measurements_ = results;
}

std::map<ResourceContext, double> CPUProportionTracker::StartNextInterval(
    base::TimeTicks time,
    const QueryResultMap& results) {
  CHECK(last_measurement_time_.has_value());
  const base::TimeTicks interval_start = last_measurement_time_.value();
  const base::TimeDelta measurement_interval = time - interval_start;
  last_measurement_time_ = time;
  if (measurement_interval.is_zero()) {
    // No time passed to measure. Ignore the results to avoid division by zero.
    return {};
  }
  CHECK(measurement_interval.is_positive());

  // Swap a new measurement into `cached_cpu_measurements_`, storing the
  // previous contents in `previous_measurements`.
  QueryResultMap previous_measurements =
      std::exchange(cached_cpu_measurements_, results);

  std::map<ResourceContext, double> cpu_usage_map;
  for (const auto& [context, result] : cached_cpu_measurements_) {
    if (!context_filter_.is_null() && !context_filter_.Run(context)) {
      continue;
    }
    if (!result.cpu_time_result.has_value()) {
      continue;
    }

    // Let time A be the last time StartNextInterval() was called, or the time
    // when StartFirstInterval() was called if this is the first one. The
    // results seen at that time are saved in `previous_measurements`.
    //
    // Let time B be current time. (`measurement_interval` is A..B.)
    //
    // There are 5 cases:
    //
    // 1. The context was created at time C, between A and B. (It will not be
    // found in `previous_measurements`).
    //
    // This snapshot should include 0% CPU for time A..C, and the measured % of
    // CPU for time C..B.
    //
    // A    C         B
    // |----+---------|
    // | 0% |   X%    |
    //
    // CPU(C..B) is `result.cumulative_cpu`.
    // `result.start_time` is C.
    // `result.metadata.measurement_time` is B.
    //
    // 2. The context existed for the entire duration A..B.
    //
    // This snapshot should include the measured % of CPU for the whole time
    // A..B.
    //
    // A              B
    // |--------------|
    // |      X%      |
    //
    // CPU(A..B) is `result.cumulative_cpu -
    // previous_measurements[context].cumulative_cpu`.
    // `result.start_time` <= A.
    // `result.metadata.measurement_time` is B.
    //
    // 3. Context created before time A, exited at time D, between A and B.
    //
    // The snapshot should include the measured % of CPU for time A..D, and 0%
    // CPU for time D..B.
    //
    // A         D    B
    // |---------+----|
    // |    X%   | 0% |
    //
    // CPU(A..D) is `result.cumulative_cpu -
    // previous_measurements[context].cumulative_cpu`.
    // `result.start_time` <= A.
    // `result.metadata.measurement_time` is D.
    //
    // 4. Context created at time C and exited at time D, both between A and B.
    // (context is not found in `previous_measurements`.
    // `result.cumulative_cpu` ends at time D, which is
    // `result.metadata.measurement_time`.)
    //
    // The snapshot should include the measured % of CPU for time C..D, and 0%
    // CPU for the rest.
    //
    // A    C    D    B
    // |----+----+----|
    // | 0% | X% | 0% |
    //
    // CPU(C..D) is `result.cumulative_cpu`.
    // `result.start_time` is C.
    // `result.metadata.measurement_time` is D.
    //
    // 5. Context exited before time A. (This is an old cached result.)
    //
    // The snapshot should not include this context at all.
    //
    // C    D A              B
    // |----| |--------------|
    // | X% | |      0%      |
    // `result.start_time` <= `result.metadata.measurement_time` <= A
    if (result.cpu_time_result->metadata.measurement_time < interval_start) {
      // Case 5.
      continue;
    }
    base::TimeDelta current_cpu = GetCumulativeCPU(*result.cpu_time_result);
    if (result.cpu_time_result->start_time < interval_start) {
      // Case 2 or 3.
      const auto it = previous_measurements.find(context);
      if (it == previous_measurements.end() ||
          !it->second.cpu_time_result.has_value()) {
        // No baseline to know how much of the context's CPU came before the
        // interval. Skip it.
        continue;
      }
      current_cpu -= GetCumulativeCPU(*it->second.cpu_time_result);
    }
    CHECK(!current_cpu.is_negative());
    cpu_usage_map.emplace(context, current_cpu / measurement_interval);
  }
  return cpu_usage_map;
}

void CPUProportionTracker::Stop() {
  CHECK(last_measurement_time_.has_value());
  last_measurement_time_.reset();
  cached_cpu_measurements_.clear();
}

bool CPUProportionTracker::IsTracking() const {
  return last_measurement_time_.has_value();
}

base::TimeDelta CPUProportionTracker::GetCumulativeCPU(
    const CPUTimeResult& cpu_time_result) const {
  switch (cpu_proportion_type_) {
    case CPUProportionType::kAll:
      return cpu_time_result.cumulative_cpu;
    case CPUProportionType::kBackground:
      return cpu_time_result.cumulative_background_cpu;
  }
}

}  // namespace resource_attribution