Skip to content

Commit

Permalink
Add CPU usage tracking specifically for 24.
Browse files Browse the repository at this point in the history
b/341774149
  • Loading branch information
aee-google committed Aug 20, 2024
1 parent f514f84 commit a3e85f8
Show file tree
Hide file tree
Showing 15 changed files with 1,249 additions and 2 deletions.
2 changes: 0 additions & 2 deletions base/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -1244,8 +1244,6 @@ component("base") {
"native_library_win.cc",
"os_compat_android.cc",
"os_compat_android.h",
"process/internal_linux.cc",
"process/internal_linux.h",
"process/kill.cc",
"process/kill.h",
"process/kill_mac.cc",
Expand Down
14 changes: 14 additions & 0 deletions base/process/internal_linux.cc
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const char kProcDir[] = "/proc";

const char kStatFile[] = "stat";

#if !defined(STARBOARD)
FilePath GetProcPidDir(pid_t pid) {
return FilePath(kProcDir).Append(IntToString(pid));
}
Expand All @@ -55,6 +56,7 @@ pid_t ProcDirSlotToPid(const char* d_name) {
}
return pid;
}
#endif // !defined(STARBOARD)

bool ReadProcFile(const FilePath& file, std::string* buffer) {
buffer->clear();
Expand All @@ -68,10 +70,12 @@ bool ReadProcFile(const FilePath& file, std::string* buffer) {
return !buffer->empty();
}

#if !defined(STARBOARD)
bool ReadProcStats(pid_t pid, std::string* buffer) {
FilePath stat_file = internal::GetProcPidDir(pid).Append(kStatFile);
return ReadProcFile(stat_file, buffer);
}
#endif // !defined(STARBOARD)

bool ParseProcStats(const std::string& stats_data,
std::vector<std::string>* proc_stats) {
Expand All @@ -97,7 +101,11 @@ bool ParseProcStats(const std::string& stats_data,

proc_stats->clear();
// PID.
#if defined(STARBOARD)
proc_stats->push_back(stats_data.substr(0, open_parens_idx - 1));
#else
proc_stats->push_back(stats_data.substr(0, open_parens_idx));
#endif
// Process name without parentheses.
proc_stats->push_back(
stats_data.substr(open_parens_idx + 1,
Expand Down Expand Up @@ -150,16 +158,19 @@ int64_t ReadStatFileAndGetFieldAsInt64(const FilePath& stat_file,
return GetProcStatsFieldAsInt64(proc_stats, field_num);
}

#if !defined(STARBOARD)
int64_t ReadProcStatsAndGetFieldAsInt64(pid_t pid, ProcStatsFields field_num) {
FilePath stat_file = internal::GetProcPidDir(pid).Append(kStatFile);
return ReadStatFileAndGetFieldAsInt64(stat_file, field_num);
}
#endif // !defined(STARBOARD)

int64_t ReadProcSelfStatsAndGetFieldAsInt64(ProcStatsFields field_num) {
FilePath stat_file = FilePath(kProcDir).Append("self").Append(kStatFile);
return ReadStatFileAndGetFieldAsInt64(stat_file, field_num);
}

#if !defined(STARBOARD)
size_t ReadProcStatsAndGetFieldAsSizeT(pid_t pid,
ProcStatsFields field_num) {
std::string stats_data;
Expand All @@ -170,6 +181,7 @@ size_t ReadProcStatsAndGetFieldAsSizeT(pid_t pid,
return 0;
return GetProcStatsFieldAsSizeT(proc_stats, field_num);
}
#endif // !defined(STARBOARD)

Time GetBootTime() {
FilePath path("/proc/stat");
Expand All @@ -187,6 +199,7 @@ Time GetBootTime() {
return Time::FromTimeT(btime);
}

#if !defined(STARBOARD)
TimeDelta GetUserCpuTimeSinceBoot() {
FilePath path("/proc/stat");
std::string contents;
Expand Down Expand Up @@ -227,6 +240,7 @@ TimeDelta ClockTicksToTimeDelta(int clock_ticks) {
return TimeDelta::FromMicroseconds(
Time::kMicrosecondsPerSecond * clock_ticks / kHertz);
}
#endif // !defined(STARBOARD)

} // namespace internal
} // namespace base
8 changes: 8 additions & 0 deletions base/process/internal_linux.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ extern const char kProcDir[];
// "stat"
extern const char kStatFile[];

#if !defined(STARBOARD)
// Returns a FilePath to "/proc/pid".
base::FilePath GetProcPidDir(pid_t pid);

Expand All @@ -38,6 +39,7 @@ pid_t ProcDirSlotToPid(const char* d_name);
// Reads /proc/<pid>/stat into |buffer|. Returns true if the file can be read
// and is non-empty.
bool ReadProcStats(pid_t pid, std::string* buffer);
#endif // !defined(STARBOARD)

// Takes |stats_data| and populates |proc_stats| with the values split by
// spaces. Taking into account the 2nd field may, in itself, contain spaces.
Expand Down Expand Up @@ -77,21 +79,27 @@ size_t GetProcStatsFieldAsSizeT(const std::vector<std::string>& proc_stats,
// ReadProcStats(). See GetProcStatsFieldAsInt64() for details.
int64_t ReadStatsFilendGetFieldAsInt64(const FilePath& stat_file,
ProcStatsFields field_num);
#if !defined(STARBOARD)
int64_t ReadProcStatsAndGetFieldAsInt64(pid_t pid, ProcStatsFields field_num);
#endif // !defined(STARBOARD)
int64_t ReadProcSelfStatsAndGetFieldAsInt64(ProcStatsFields field_num);

#if !defined(STARBOARD)
// Same as ReadProcStatsAndGetFieldAsInt64() but for size_t values.
size_t ReadProcStatsAndGetFieldAsSizeT(pid_t pid,
ProcStatsFields field_num);
#endif // !defined(STARBOARD)

// Returns the time that the OS started. Clock ticks are relative to this.
Time GetBootTime();

#if !defined(STARBOARD)
// Returns the amount of time spent in user space since boot across all CPUs.
TimeDelta GetUserCpuTimeSinceBoot();

// Converts Linux clock ticks to a wall time delta.
TimeDelta ClockTicksToTimeDelta(int clock_ticks);
#endif // !defined(STARBOARD)

} // namespace internal
} // namespace base
Expand Down
3 changes: 3 additions & 0 deletions cobalt/base/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ static_library("base") {
"poller.h",
"polymorphic_downcast.h",
"polymorphic_equatable.h",
"process/process_metrics_helper.cc",
"process/process_metrics_helper.h",
"ref_counted_lock.h",
"source_location.cc",
"source_location.h",
Expand Down Expand Up @@ -116,6 +118,7 @@ target(gtest_target_type, "base_test") {
"c_val_time_interval_timer_stats_test.cc",
"circular_buffer_shell_unittest.cc",
"fixed_size_lru_cache_test.cc",
"process/process_metrics_helper_test.cc",
"statistics_test.cc",
"token_test.cc",
]
Expand Down
182 changes: 182 additions & 0 deletions cobalt/base/process/process_metrics_helper.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
// Copyright 2024 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "cobalt/base/process/process_metrics_helper.h"

#include <atomic>
#include <cmath>
#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "base/bind.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/memory/ptr_util.h"
#include "base/optional.h"
#include "base/process/internal_linux.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"

namespace base {

namespace {

static std::atomic<int> clock_ticks_per_s{0};

ProcessMetricsHelper::ReadCallback GetReadCallback(const FilePath& path) {
return BindOnce(
[](const FilePath& path) -> Optional<std::string> {
std::string contents;
if (!ReadFileToString(path, &contents)) return nullopt;
return contents;
},
path);
}

double CalculateCPUUsageSeconds(const std::string& utime_string,
const std::string& stime_string,
int ticks_per_s) {
DCHECK_NE(ticks_per_s, 0);
double utime;
if (!StringToDouble(utime_string, &utime)) return 0.0;
double stime;
if (!StringToDouble(stime_string, &stime)) return 0.0;
return (utime + stime) / static_cast<double>(ticks_per_s);
}

} // namespace

// static
int ProcessMetricsHelper::GetClockTicksPerS() {
return clock_ticks_per_s.load();
}

// static
int ProcessMetricsHelper::GetClockTicksPerS(ReadCallback uptime_callback,
ReadCallback stat_callback) {
double current_uptime = 0.0;
{
auto uptime_contents = std::move(uptime_callback).Run();
if (!uptime_contents) return 0;
auto parts = SplitString(*uptime_contents, " ", TRIM_WHITESPACE,
SPLIT_WANT_NONEMPTY);
if (parts.size() == 0 || !StringToDouble(parts[0], &current_uptime) ||
current_uptime == 0.0) {
return 0;
}
}

auto fields = GetProcStatFields(std::move(stat_callback),
{internal::ProcStatsFields::VM_STARTTIME});
if (fields.size() != 1) return 0;
double process_starttime;
if (!StringToDouble(fields[0], &process_starttime) ||
process_starttime == 0.0)
return 0;
double ticks_per_s = process_starttime / current_uptime;
int rounded_up = 10 * static_cast<int>(std::ceil(ticks_per_s / 10.0));
return rounded_up;
}

// static
void ProcessMetricsHelper::PopulateClockTicksPerS() {
DCHECK_EQ(clock_ticks_per_s.load(), 0);
clock_ticks_per_s.store(
GetClockTicksPerS(GetReadCallback(FilePath("/proc/uptime")),
GetReadCallback(FilePath("/proc/self/stat"))));
}

// static
TimeDelta ProcessMetricsHelper::GetCumulativeCPUUsage() {
int ticks_per_s = clock_ticks_per_s.load();
if (ticks_per_s == 0) return TimeDelta();
return GetCPUUsage(FilePath("/proc/self"), ticks_per_s);
}

// static
Value ProcessMetricsHelper::GetCumulativeCPUUsagePerThread() {
int ticks_per_s = clock_ticks_per_s.load();
if (ticks_per_s == 0) return Value();
ListValue cpu_per_thread;
FileEnumerator file_enum(FilePath("/proc/self/task"), /*recursive=*/false,
FileEnumerator::DIRECTORIES);
for (FilePath path = file_enum.Next(); !path.empty();
path = file_enum.Next()) {
Fields fields =
GetProcStatFields(path, {0, internal::ProcStatsFields::VM_COMM,
internal::ProcStatsFields::VM_UTIME,
internal::ProcStatsFields::VM_STIME});
if (fields.size() != 4) continue;
int id;
if (!StringToInt(fields[0], &id)) continue;
DictionaryValue entry;
entry.SetKey("id", Value(id));
entry.SetKey("name", Value(fields[1]));
entry.SetKey("utime", Value(fields[2]));
entry.SetKey("stime", Value(fields[3]));
entry.SetKey("usage_seconds", Value(CalculateCPUUsageSeconds(
fields[2], fields[3], ticks_per_s)));
cpu_per_thread.GetList().push_back(std::move(entry));
}
return std::move(cpu_per_thread);
}

// static
ProcessMetricsHelper::Fields ProcessMetricsHelper::GetProcStatFields(
ReadCallback read_callback, std::initializer_list<int> indices) {
auto contents = std::move(read_callback).Run();
if (!contents) return Fields();

std::vector<std::string> proc_stats;
if (!internal::ParseProcStats(*contents, &proc_stats)) return Fields();

Fields fields;
for (int index : indices) {
if (index < 0 || index >= proc_stats.size()) return Fields();
fields.push_back(std::move(proc_stats[index]));
}
return std::move(fields);
}

// static
ProcessMetricsHelper::Fields ProcessMetricsHelper::GetProcStatFields(
const FilePath& path, std::initializer_list<int> indices) {
return ProcessMetricsHelper::GetProcStatFields(
GetReadCallback(path.Append("stat")), indices);
}

// static
TimeDelta ProcessMetricsHelper::GetCPUUsage(ReadCallback read_callback,
int ticks_per_s) {
auto fields = ProcessMetricsHelper::GetProcStatFields(
std::move(read_callback), {internal::ProcStatsFields::VM_UTIME,
internal::ProcStatsFields::VM_STIME});
if (fields.size() != 2) return TimeDelta();
return TimeDelta::FromSecondsD(
CalculateCPUUsageSeconds(fields[0], fields[1], ticks_per_s));
}

// static
TimeDelta ProcessMetricsHelper::GetCPUUsage(const FilePath& path,
int ticks_per_s) {
return ProcessMetricsHelper::GetCPUUsage(GetReadCallback(path.Append("stat")),
ticks_per_s);
}

} // namespace base
56 changes: 56 additions & 0 deletions cobalt/base/process/process_metrics_helper.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright 2024 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef COBALT_BASE_PROCESS_PROCESS_METRICS_HELPER_H_
#define COBALT_BASE_PROCESS_PROCESS_METRICS_HELPER_H_

#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "base/bind.h"
#include "base/callback.h"
#include "base/files/file_path.h"
#include "base/optional.h"
#include "base/threading/platform_thread.h"
#include "base/time/time.h"
#include "base/values.h"

namespace base {

class ProcessMetricsHelper {
public:
using CPUUsagePerThread = std::vector<std::pair<std::string, TimeDelta>>;
using ReadCallback = OnceCallback<Optional<std::string>()>;
using Fields = std::vector<std::string>;

static int GetClockTicksPerS();
static void PopulateClockTicksPerS();
static TimeDelta GetCumulativeCPUUsage();
static Value GetCumulativeCPUUsagePerThread();

private:
friend class ProcessMetricsHelperTest;

static int GetClockTicksPerS(ReadCallback, ReadCallback);
static Fields GetProcStatFields(ReadCallback, std::initializer_list<int>);
static Fields GetProcStatFields(const FilePath&, std::initializer_list<int>);
static TimeDelta GetCPUUsage(ReadCallback, int);
static TimeDelta GetCPUUsage(const FilePath&, int);
};

} // namespace base

#endif // COBALT_BASE_PROCESS_PROCESS_METRICS_HELPER_H_
Loading

0 comments on commit a3e85f8

Please sign in to comment.