Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CPU usage tracking specifically for 24. #4026

Merged
merged 1 commit into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
16 changes: 16 additions & 0 deletions base/process/internal_linux.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
#include "base/process/internal_linux.h"

#include <limits.h>
#if !defined(STARBOARD)
#include <unistd.h>
#endif // !defined(STARBOARD)

#include <map>
#include <string>
Expand All @@ -32,6 +34,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 +58,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 +72,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 +103,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 +160,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 +183,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 +201,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 +242,7 @@ TimeDelta ClockTicksToTimeDelta(int clock_ticks) {
return TimeDelta::FromMicroseconds(
Time::kMicrosecondsPerSecond * clock_ticks / kHertz);
}
#endif // !defined(STARBOARD)

} // namespace internal
} // namespace base
10 changes: 10 additions & 0 deletions base/process/internal_linux.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
#ifndef BASE_PROCESS_INTERNAL_LINUX_H_
#define BASE_PROCESS_INTERNAL_LINUX_H_

#if !defined(STARBOARD)
#include <unistd.h>
#endif // !defined(STARBOARD)

#include "base/files/file_path.h"
#include "starboard/types.h"
Expand All @@ -26,6 +28,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 +41,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 +81,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
Loading
Loading