| // Copyright 2021 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/metrics/psi_memory_parser.h" |
| |
| #include <stddef.h> |
| |
| #include <cinttypes> |
| #include <map> |
| #include <memory> |
| #include <string> |
| |
| #include "base/metrics/histogram.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/stringprintf.h" |
| #include "components/metrics/metrics_log_store.h" |
| |
| namespace metrics { |
| |
| namespace { |
| |
| // Periods supported by standard Linux PSI metricvs. |
| constexpr uint32_t kMinCollectionInterval = 10; |
| constexpr uint32_t kMidCollectionInterval = 60; |
| constexpr uint32_t kMaxCollectionInterval = 300; |
| |
| constexpr uint32_t kDefaultCollectionInterval = kMinCollectionInterval; |
| |
| // Name of the histogram that represents the success and various failure modes |
| // for parsing PSI memory data. |
| const char kParsePSIMemoryHistogramName[] = "ChromeOS.CWP.ParsePSIMemory"; |
| |
| constexpr base::StringPiece kContentPrefixSome = "some"; |
| constexpr base::StringPiece kContentPrefixFull = "full"; |
| constexpr base::StringPiece kContentTerminator = " total="; |
| constexpr base::StringPiece kMetricTerminator = " "; |
| |
| const char kMetricPrefixFormat[] = "avg%d="; |
| |
| } // namespace |
| |
| PSIMemoryParser::PSIMemoryParser(uint32_t period) |
| : period_(kDefaultCollectionInterval) { |
| if (period == kMinCollectionInterval || period == kMidCollectionInterval || |
| period == kMaxCollectionInterval) { |
| period_ = period; |
| } else { |
| LOG(WARNING) << "Ignoring invalid interval [" << period << "]"; |
| } |
| |
| metric_prefix_ = base::StringPrintf(kMetricPrefixFormat, period_); |
| } |
| |
| PSIMemoryParser::~PSIMemoryParser() = default; |
| |
| uint32_t PSIMemoryParser::GetPeriod() const { |
| return period_; |
| } |
| |
| int PSIMemoryParser::GetMetricValue(const base::StringPiece& content, |
| size_t start, |
| size_t end) { |
| size_t value_start; |
| size_t value_end; |
| if (!internal::FindMiddleString(content, start, metric_prefix_, |
| kMetricTerminator, &value_start, |
| &value_end)) { |
| return -1; |
| } |
| if (value_end > end) { |
| return -1; // Out of bounds of the search area. |
| } |
| |
| double n; |
| const base::StringPiece metric_value_text = |
| content.substr(value_start, value_end - value_start); |
| if (!base::StringToDouble(metric_value_text, &n)) { |
| return -1; // Unable to convert string to number |
| } |
| |
| // Want to multiply by 100, but to avoid integer truncation, |
| // do best-effort rounding. |
| const int preround = static_cast<int>(n * 1000); |
| return (preround + 5) / 10; |
| } |
| |
| void PSIMemoryParser::LogParseStatus(ParsePSIMemStatus stat) { |
| constexpr int statCeiling = |
| static_cast<int>(ParsePSIMemStatus::kMaxValue) + 1; |
| base::UmaHistogramExactLinear(kParsePSIMemoryHistogramName, |
| static_cast<int>(stat), statCeiling); |
| } |
| |
| ParsePSIMemStatus PSIMemoryParser::ParseMetrics( |
| const base::StringPiece& content, |
| int* metric_some, |
| int* metric_full) { |
| size_t str_some_start; |
| size_t str_some_end; |
| size_t str_full_start; |
| size_t str_full_end; |
| |
| // Example of content: |
| // some avg10=0.00 avg60=0.00 avg300=0.00 total=417963 |
| // full avg10=0.00 avg60=0.00 avg300=0.00 total=205933 |
| // we will pick one of the columns depending on the colleciton period set |
| |
| DCHECK_NE(metric_some, nullptr); |
| DCHECK_NE(metric_full, nullptr); |
| |
| if (!internal::FindMiddleString(content, 0, kContentPrefixSome, |
| kContentTerminator, &str_some_start, |
| &str_some_end)) { |
| return ParsePSIMemStatus::kUnexpectedDataFormat; |
| } |
| |
| if (!internal::FindMiddleString(content, |
| str_some_end + kContentTerminator.length(), |
| kContentPrefixFull, kContentTerminator, |
| &str_full_start, &str_full_end)) { |
| return ParsePSIMemStatus::kUnexpectedDataFormat; |
| } |
| |
| int compute_some = GetMetricValue(content, str_some_start, str_some_end); |
| if (compute_some < 0) { |
| return ParsePSIMemStatus::kInvalidMetricFormat; |
| } |
| |
| int compute_full = GetMetricValue(content, str_full_start, str_full_end); |
| if (compute_full < 0) { |
| return ParsePSIMemStatus::kInvalidMetricFormat; |
| } |
| |
| *metric_some = compute_some; |
| *metric_full = compute_full; |
| |
| return ParsePSIMemStatus::kSuccess; |
| } |
| |
| ParsePSIMemStatus PSIMemoryParser::ParseMetrics(const uint8_t* content, |
| uint32_t len, |
| int* metric_some, |
| int* metric_full) { |
| // The cast below is admittedly sneaky, but inherently safe because |
| // we are translating a const pointer into another const pointer, |
| // and the data sizes of the pointed object are the same. |
| const char* string_content = reinterpret_cast<const char*>(content); |
| |
| return ParseMetrics(base::StringPiece(string_content, len), metric_some, |
| metric_full); |
| } |
| |
| namespace internal { |
| |
| bool FindMiddleString(const base::StringPiece& content, |
| size_t search_start, |
| const base::StringPiece& prefix, |
| const base::StringPiece& suffix, |
| size_t* start, |
| size_t* end) { |
| DCHECK_NE(start, nullptr); |
| DCHECK_NE(end, nullptr); |
| |
| size_t compute_start = content.find(prefix, search_start); |
| if (compute_start == std::string::npos) { |
| return false; |
| } |
| compute_start += prefix.length(); |
| |
| size_t compute_end = content.find(suffix, compute_start); |
| if (compute_end == std::string::npos) { |
| return false; |
| } |
| |
| *start = compute_start; |
| *end = compute_end; |
| |
| return true; |
| } |
| |
| } // namespace internal |
| |
| } // namespace metrics |