blob: 1edd86a4ebc904f6bbf404521a6dcee1f7edcb81 [file] [log] [blame] [edit]
// Copyright 2023 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "vm_tools/concierge/mm/mglru.h"
#include <algorithm>
#include <utility>
#include <vector>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_split.h>
#include <base/strings/string_util.h>
#include <vm_memory_management/vm_memory_management.pb.h>
#include "vm_tools/concierge/byte_unit.h"
using vm_tools::vm_memory_management::MglruGeneration;
using vm_tools::vm_memory_management::MglruMemcg;
using vm_tools::vm_memory_management::MglruNode;
namespace vm_tools::concierge::mm::mglru {
namespace {
// Parses a single generation from the input line and returns it if successful.
// On failure a std::nullopt is returned.
std::optional<MglruGeneration> ParseGeneration(const std::string_view line) {
std::vector<std::string_view> tokens =
base::SplitStringPiece(line, base::kWhitespaceASCII,
base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
// sequence_num, timestamp_msec, anon_pages, file_pages.
constexpr size_t kNumGenVals = 4;
int32_t vals[kNumGenVals];
// A generation should have at least 4 distinct numbers.
if (tokens.size() < kNumGenVals) {
return std::nullopt;
}
for (size_t i = 0; i < kNumGenVals; i++) {
if (!base::StringToInt(tokens[i], &vals[i])) {
return std::nullopt;
}
// If the nr_pages are of negative values, convert it to 0.
vals[i] = std::max(0, vals[i]);
}
MglruGeneration gen;
gen.set_sequence_num(vals[0]);
gen.set_timestamp_msec(vals[1]);
// Note: The values are set as pages here even though the final units will be
// KiB. The conversion is handled at the end of parsing in
// ConvertStatsToKbUnits.
gen.set_anon_kb(vals[2]);
gen.set_file_kb(vals[3]);
return gen;
}
// Parses a single node from the input lines and returns it if successful. The
// line index is expected to be at the starting position for the node, otherwise
// this function will fail. On failure a std::nullopt is returned and the index
// is reset to the original position.
std::optional<MglruNode> ParseNode(const std::vector<std::string_view>& lines,
size_t& index) {
size_t starting_position = index;
// A node always starts with the node identifier token.
if (index >= lines.size()) {
return std::nullopt;
}
std::vector<std::string_view> tokens =
base::SplitStringPiece(lines.at(index++), base::kWhitespaceASCII,
base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
// The first line of the node is 'node' followed by the node's id.
uint32_t node_id;
if (tokens.size() < 2 || tokens[0] != "node" ||
!base::StringToUint(tokens[1], &node_id)) {
index = starting_position;
return std::nullopt;
}
MglruNode new_node;
new_node.set_id(node_id);
// Next is one or more generations, each of which consists of a single line of
// text. Parse generations until we encounter a failure. The first failure
// indicates the end of the list of generations.
std::optional<MglruGeneration> parsed_gen;
while (index < lines.size() &&
(parsed_gen = ParseGeneration(lines.at(index++)))) {
new_node.mutable_generations()->Add(std::move(*parsed_gen));
}
// If the last generation failed, decrement the line index since the last line
// was not a valid generation.
if (!parsed_gen) {
index--;
}
// If no generations were parsed, there is an error.
if (new_node.generations_size() == 0) {
index = starting_position;
return std::nullopt;
}
return new_node;
}
// Parses a single memcg from the input lines and returns it if successful. The
// line index is expected to be at the starting position for the memcg,
// otherwise this function will fail. On failure the line index is reset to its
// original position and a std::nullopt is returned.
std::optional<MglruMemcg> ParseMemcg(const std::vector<std::string_view>& lines,
size_t& index) {
size_t starting_index = index;
if (index >= lines.size()) {
return std::nullopt;
}
std::vector<std::string_view> tokens =
base::SplitStringPiece(lines.at(index++), base::kWhitespaceASCII,
base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
// The first line of a memcg is always 'memcg' followed by the id.
uint32_t id;
if (tokens.size() < 2 || tokens[0] != "memcg" ||
!base::StringToUint(tokens[1], &id)) {
index = starting_index;
return std::nullopt;
}
MglruMemcg new_memcg;
new_memcg.set_id(id);
// After the id is a list of one or more nodes. Parse nodes until failure. The
// first failure indicates the end of the list of nodes.
std::optional<MglruNode> parsed_node;
while ((parsed_node = ParseNode(lines, index))) {
new_memcg.mutable_nodes()->Add(std::move(*parsed_node));
}
// If no nodes were parsed, then there was an error.
if (new_memcg.nodes_size() == 0) {
index = starting_index;
return std::nullopt;
}
return new_memcg;
}
void ConvertStatsToKbUnits(MglruStats& stats, const size_t page_size) {
// Convert the page sizes from the file to KiB sizes.
const size_t page_k = (page_size / KiB(1));
for (size_t i = 0; i < stats.cgs_size(); i++) {
MglruMemcg* cg = stats.mutable_cgs(i);
for (size_t j = 0; j < cg->nodes_size(); j++) {
MglruNode* node = cg->mutable_nodes(j);
for (size_t k = 0; k < node->generations_size(); k++) {
MglruGeneration* gen = node->mutable_generations(k);
gen->set_anon_kb(gen->anon_kb() * page_k);
gen->set_file_kb(gen->file_kb() * page_k);
}
}
}
}
} // namespace
std::optional<MglruStats> ParseStatsFromString(
const std::string_view stats_string, const size_t page_size) {
std::vector<std::string_view> lines = base::SplitStringPiece(
stats_string, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
MglruStats parsed_stats;
size_t index = 0;
// The MGLRU stats file is a list of one or more memcgs. Parse until failure.
// The first failure indicates the end of the list of memcgs.
std::optional<MglruMemcg> parsed_memcg;
while ((parsed_memcg = ParseMemcg(lines, index))) {
parsed_stats.mutable_cgs()->Add(std::move(*parsed_memcg));
}
// If the parsing did not consume the entire input file, or if no memcgs were
// parsed, then something went wrong.
if (parsed_stats.cgs_size() == 0 || index != lines.size()) {
return std::nullopt;
}
ConvertStatsToKbUnits(parsed_stats, page_size);
return parsed_stats;
}
std::string StatsToString(const MglruStats& stats, const size_t page_size) {
std::stringstream output;
size_t page_k = (page_size / KiB(1));
for (size_t i = 0; i < stats.cgs_size(); i++) {
const MglruMemcg& cg = stats.cgs(i);
output << "memcg " << cg.id() << '\n';
for (size_t j = 0; j < cg.nodes_size(); j++) {
const MglruNode& node = cg.nodes(j);
output << " node " << node.id() << '\n';
for (size_t k = 0; k < node.generations_size(); k++) {
const MglruGeneration& gen = node.generations(k);
output << " " << gen.sequence_num() << " " << gen.timestamp_msec()
<< " " << gen.anon_kb() / page_k << " "
<< gen.file_kb() / page_k << '\n';
}
}
}
return output.str();
}
} // namespace vm_tools::concierge::mm::mglru