blob: 4eb14430f5a914358fc75a642f23b39f48e363ba [file] [log] [blame]
// Copyright 2021 The Chromium OS 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 "croslog/metrics_collector_util.h"
#include <algorithm>
#include <deque>
#include <functional>
#include <string>
#include <utility>
#include <vector>
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "croslog/log_entry_reader.h"
#include "croslog/log_parser_syslog.h"
namespace croslog {
void CalculateLogMetrics(const base::FilePath& path,
const base::Time& count_after,
std::unique_ptr<LogParser> parser_in,
int64_t* byte_count_out,
int64_t* entry_count_out,
int64_t* max_throughput_out) {
// Checks if the file exists.
if (!base::PathExists(path)) {
if (byte_count_out)
*byte_count_out = -1;
if (entry_count_out)
*entry_count_out = -1;
if (max_throughput_out)
*max_throughput_out = -1;
return;
}
if (byte_count_out)
*byte_count_out = 0;
if (entry_count_out)
*entry_count_out = 0;
if (max_throughput_out)
*max_throughput_out = 0;
LogEntryReader reader(path, std::move(parser_in), false);
// Traverses reversely from the last.
reader.SetPositionLast();
std::deque<base::Time> recent_timestamps;
while (true) {
MaybeLogEntry entry = reader.GetPreviousEntry();
if (!entry.has_value())
return;
if (!count_after.is_null() && entry->time() < count_after)
return;
if (byte_count_out && *byte_count_out >= 0) {
*byte_count_out += entry->entire_line().size();
// Adding 1 for a terminating LF.
*byte_count_out += 1;
}
if (max_throughput_out) {
// Resets the state, if the timestamps are in a wrong order.
if (!recent_timestamps.empty() &&
recent_timestamps.back() < entry->time()) {
recent_timestamps.clear();
}
// Keeps the timestamps only within 1 minute.
recent_timestamps.push_back(entry->time());
while ((recent_timestamps.front() - entry->time()) >
base::TimeDelta::FromMinutes(1)) {
recent_timestamps.pop_front();
}
// Get the current throughput (number of entries within the recent 1
// minute).
int64_t current_throughput =
static_cast<int64_t>(recent_timestamps.size());
*max_throughput_out = std::max(*max_throughput_out, current_throughput);
}
if (entry_count_out)
(*entry_count_out)++;
}
}
void CalculateMultipleLogMetrics(Multiplexer* multiplexer,
const base::Time& count_after,
int64_t* entry_count_out,
int64_t* max_throughput_out) {
multiplexer->SetLinesFromLast(0);
if (entry_count_out)
*entry_count_out = 0;
if (max_throughput_out)
*max_throughput_out = 0;
std::deque<base::Time> recent_timestamps;
while (true) {
const MaybeLogEntry& entry = multiplexer->Backward();
if (!entry.has_value())
return;
if (!count_after.is_null() && entry->time() < count_after)
return;
if (max_throughput_out) {
// Resets the state, if the timestamp order is strange.
if (!recent_timestamps.empty() &&
recent_timestamps.back() < entry->time()) {
recent_timestamps.clear();
}
// Keeps the timestamps only within 1 minute.
recent_timestamps.push_back(entry->time());
while ((recent_timestamps.front() - entry->time()) >
base::TimeDelta::FromMinutes(1)) {
recent_timestamps.pop_front();
}
// Get the current throughput (number of entries with the recent 1
// minute).
int64_t current_throughput =
static_cast<int64_t>(recent_timestamps.size());
*max_throughput_out = std::max(*max_throughput_out, current_throughput);
}
if (entry_count_out)
(*entry_count_out)++;
}
}
void CalculateChromeLogMetrics(const base::FilePath& directory,
const char* filename_pattern,
const base::Time& count_after,
int64_t* byte_count_out,
int64_t* entry_count_out,
int64_t* max_throughput_out) {
// This logic traverses the chrome logs, since the chrome logs are splitted
// on every session, instead of daily rotation like other log files.
if (entry_count_out)
*entry_count_out = 0;
if (byte_count_out)
*byte_count_out = 0;
if (max_throughput_out)
*max_throughput_out = 0;
std::vector<base::FilePath> file_path;
base::FileEnumerator e(directory, false, base::FileEnumerator::FILES,
filename_pattern);
// FileEnumerator doesn't guarantee order of results, so we put the result
// into the vector and sort it.
for (base::FilePath name = e.Next(); !name.empty(); name = e.Next()) {
file_path.push_back(name);
}
// Sorting is in reverse lexicographic order, which will also sort the logs by
// time (more recent file first), since the chrome logs contain date and time
// in their filenames. For example, "chrome_20210115_123456.txt" comes before
// "chrome_20210115_123456.txt".
std::sort(
file_path.begin(), file_path.end(),
[](const auto& l, const auto& r) { return r.BaseName() < l.BaseName(); });
for (const base::FilePath& name : file_path) {
int64_t file_size;
if (!GetFileSize(name, &file_size) || file_size == 0)
continue;
int64_t byte_count_temporary;
int64_t entry_count_temporary;
int64_t max_throughput_temporary;
CalculateLogMetrics(name, count_after, std::make_unique<LogParserSyslog>(),
&byte_count_temporary, &entry_count_temporary,
&max_throughput_temporary);
// Skips this file since the file doesn't exist (this rarely happens by
// file remove/rename race).
if (entry_count_temporary < 0)
continue;
// Stops the traversal. We found no entries newer than |count_after| in
// this log files. So, we assume the later files contain older entries,
// since the file list has been sorted (more recent file first).
if (entry_count_temporary == 0)
return;
if (entry_count_out)
*entry_count_out += entry_count_temporary;
if (byte_count_out)
*byte_count_out += byte_count_temporary;
if (max_throughput_out) {
*max_throughput_out =
std::max(*max_throughput_out, max_throughput_temporary);
}
}
}
} // namespace croslog