blob: 60df9d232926066ee4febc41dce5c351d38854ed [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/process/internal_linux.h"
#include <limits.h>
#include <unistd.h>
#include <algorithm>
#include <map>
#include <string>
#include <string_view>
#include <vector>
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/notreached.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/time.h"
#include "build/build_config.h"
// Not defined on AIX by default.
#if BUILDFLAG(IS_AIX)
#define NAME_MAX 255
#endif
namespace base::internal {
const char kProcDir[] = "/proc";
const char kStatFile[] = "stat";
FilePath GetProcPidDir(pid_t pid) {
return FilePath(kProcDir).Append(NumberToString(pid));
}
pid_t ProcDirSlotToPid(std::string_view d_name) {
if (d_name.size() >= NAME_MAX ||
!std::ranges::all_of(d_name, &IsAsciiDigit<char>)) {
return 0;
}
// Read the process's command line.
pid_t pid;
if (!StringToInt(d_name, &pid)) {
NOTREACHED();
}
return pid;
}
bool ReadProcFile(const FilePath& file, std::string* buffer) {
DCHECK(FilePath(kProcDir).IsParent(file));
buffer->clear();
// Synchronously reading files in /proc is safe.
ScopedAllowBlocking scoped_allow_blocking;
if (!ReadFileToString(file, buffer)) {
return false;
}
return !buffer->empty();
}
std::optional<StringViewPairs> ReadProcFileToTrimmedStringPairs(
pid_t pid,
std::string_view filename,
std::string* buffer) {
FilePath status_file = GetProcPidDir(pid).Append(filename);
if (!ReadProcFile(status_file, buffer)) {
return std::nullopt;
}
StringViewPairs key_value_pairs;
SplitStringIntoKeyValueViewPairs(*buffer, ':', '\n', &key_value_pairs);
for (auto& [key, value] : key_value_pairs) {
key = TrimWhitespaceASCII(key, TRIM_ALL);
value = TrimWhitespaceASCII(value, TRIM_ALL);
}
return key_value_pairs;
}
size_t ReadProcStatusAndGetKbFieldAsSizeT(pid_t pid, std::string_view field) {
std::string buffer;
std::optional<StringViewPairs> pairs =
ReadProcFileToTrimmedStringPairs(pid, "status", &buffer);
if (!pairs) {
return 0;
}
for (const auto& [key, value_str] : *pairs) {
if (key != field) {
continue;
}
std::vector<std::string_view> split_value_str =
SplitStringPiece(value_str, " ", TRIM_WHITESPACE, SPLIT_WANT_ALL);
if (split_value_str.size() != 2 || split_value_str[1] != "kB") {
NOTREACHED();
}
size_t value;
if (!StringToSizeT(split_value_str[0], &value)) {
NOTREACHED();
}
return value;
}
// This can be reached if the process dies when proc is read -- in that case,
// the kernel can return missing fields.
return 0;
}
bool ReadProcStatusAndGetFieldAsUint64(pid_t pid,
std::string_view field,
uint64_t* result) {
std::string buffer;
std::optional<StringViewPairs> pairs =
ReadProcFileToTrimmedStringPairs(pid, "status", &buffer);
if (!pairs) {
return false;
}
for (const auto& [key, value_str] : *pairs) {
if (key != field) {
continue;
}
uint64_t value;
if (!StringToUint64(value_str, &value)) {
return false;
}
*result = value;
return true;
}
return false;
}
bool ReadProcStats(pid_t pid, std::string* buffer) {
FilePath stat_file = internal::GetProcPidDir(pid).Append(kStatFile);
return ReadProcFile(stat_file, buffer);
}
bool ParseProcStats(std::string_view stats_data,
std::vector<std::string_view>* proc_stats) {
// |stats_data| may be empty if the process disappeared somehow.
// e.g. http://crbug.com/145811
if (stats_data.empty()) {
return false;
}
// The stat file is formatted as:
// pid (process name) data1 data2 .... dataN
// Look for the closing paren by scanning backwards, to avoid being fooled by
// processes with ')' in the name.
size_t open_parens_idx = stats_data.find(" (");
size_t close_parens_idx = stats_data.rfind(") ");
if (open_parens_idx == std::string::npos ||
close_parens_idx == std::string::npos ||
open_parens_idx > close_parens_idx) {
DLOG(WARNING) << "Failed to find matched parens in '" << stats_data << "'";
NOTREACHED();
}
open_parens_idx++;
proc_stats->clear();
// PID.
proc_stats->push_back(stats_data.substr(0, open_parens_idx));
// Process name without parentheses.
proc_stats->push_back(stats_data.substr(
open_parens_idx + 1, close_parens_idx - (open_parens_idx + 1)));
// Split the rest.
std::vector<std::string_view> other_stats =
SplitStringPiece(stats_data.substr(close_parens_idx + 2), " ",
base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
proc_stats->insert(proc_stats->end(), other_stats.begin(), other_stats.end());
return true;
}
int64_t GetProcStatsFieldAsInt64(base::span<std::string_view> proc_stats,
ProcStatsFields field_num) {
DCHECK_GE(field_num, VM_PPID);
return GetProcStatsFieldAsOptionalInt64(proc_stats, field_num).value_or(0);
}
std::optional<int64_t> GetProcStatsFieldAsOptionalInt64(
base::span<std::string_view> proc_stats,
ProcStatsFields field_num) {
int64_t value;
if (StringToInt64(proc_stats.at(field_num), &value)) {
return value;
}
return std::nullopt;
}
size_t GetProcStatsFieldAsSizeT(base::span<std::string_view> proc_stats,
ProcStatsFields field_num) {
DCHECK_GE(field_num, VM_PPID);
size_t value;
return StringToSizeT(proc_stats.at(field_num), &value) ? value : 0;
}
int64_t ReadStatFileAndGetFieldAsInt64(const FilePath& stat_file,
ProcStatsFields field_num) {
std::string stats_data;
if (!ReadProcFile(stat_file, &stats_data)) {
return 0;
}
std::vector<std::string_view> proc_stats;
if (!ParseProcStats(stats_data, &proc_stats)) {
return 0;
}
return GetProcStatsFieldAsInt64(proc_stats, field_num);
}
int64_t ReadProcStatsAndGetFieldAsInt64(pid_t pid, ProcStatsFields field_num) {
FilePath stat_file = internal::GetProcPidDir(pid).Append(kStatFile);
return ReadStatFileAndGetFieldAsInt64(stat_file, field_num);
}
int64_t ReadProcSelfStatsAndGetFieldAsInt64(ProcStatsFields field_num) {
FilePath stat_file = FilePath(kProcDir).Append("self").Append(kStatFile);
return ReadStatFileAndGetFieldAsInt64(stat_file, field_num);
}
size_t ReadProcStatsAndGetFieldAsSizeT(pid_t pid, ProcStatsFields field_num) {
std::string stats_data;
if (!ReadProcStats(pid, &stats_data)) {
return 0;
}
std::vector<std::string_view> proc_stats;
if (!ParseProcStats(stats_data, &proc_stats)) {
return 0;
}
return GetProcStatsFieldAsSizeT(proc_stats, field_num);
}
Time GetBootTime() {
FilePath path("/proc/stat");
std::string contents;
if (!ReadProcFile(path, &contents)) {
return Time();
}
StringViewPairs key_value_pairs;
SplitStringIntoKeyValueViewPairs(contents, ' ', '\n', &key_value_pairs);
for (const auto& [key, value] : key_value_pairs) {
if (key == "btime") {
int btime;
if (!StringToInt(value, &btime)) {
return Time();
}
return Time::FromTimeT(btime);
}
}
return Time();
}
TimeDelta GetUserCpuTimeSinceBoot() {
FilePath path("/proc/stat");
std::string contents;
if (!ReadProcFile(path, &contents)) {
return TimeDelta();
}
StringViewPairs key_value_pairs;
SplitStringIntoKeyValueViewPairs(contents, ' ', '\n', &key_value_pairs);
for (const auto& [key, value] : key_value_pairs) {
if (key == "cpu") {
std::vector<std::string_view> cpu = SplitStringPiece(
value, kWhitespaceASCII, TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
if (cpu.size() < 2 || cpu[0] != "cpu") {
return TimeDelta();
}
uint64_t user;
uint64_t nice;
if (!StringToUint64(cpu[0], &user) || !StringToUint64(cpu[1], &nice)) {
return TimeDelta();
}
return ClockTicksToTimeDelta(checked_cast<int64_t>(user + nice));
}
}
return TimeDelta();
}
TimeDelta ClockTicksToTimeDelta(int64_t clock_ticks) {
// This queries the /proc-specific scaling factor which is
// conceptually the system hertz. To dump this value on another
// system, try
// od -t dL /proc/self/auxv
// and look for the number after 17 in the output; mine is
// 0000040 17 100 3 134512692
// which means the answer is 100.
// It may be the case that this value is always 100.
static const long kHertz = sysconf(_SC_CLK_TCK);
return Microseconds(Time::kMicrosecondsPerSecond * clock_ticks / kHertz);
}
} // namespace base::internal