blob: f5fddb04e7f569367183a6460f25c9aa7ed181de [file] [log] [blame]
// 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