Skip to content

Commit

Permalink
Optimistically read from /proc for process stats.
Browse files Browse the repository at this point in the history
b/341774149
  • Loading branch information
aee-google committed Jun 3, 2024
1 parent 27addc6 commit 9f95721
Show file tree
Hide file tree
Showing 7 changed files with 517 additions and 7 deletions.
4 changes: 4 additions & 0 deletions base/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -1104,6 +1104,10 @@ component("base") {
all_dependent_configs += [ ":starboard_config" ]
}

if (use_cobalt_customizations) {
sources += [ "process/process_metrics.h" ]
}

if (is_starboard && current_toolchain != host_toolchain) {
deps += [ "//starboard:starboard_group" ]
}
Expand Down
11 changes: 4 additions & 7 deletions base/process/process_metrics.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,14 @@
#endif

#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID) || \
BUILDFLAG(IS_AIX)
BUILDFLAG(IS_AIX) || defined(STARBOARD)
#include <string>
#include <utility>
#include <vector>

#include "base/threading/platform_thread.h"
#endif

#if !defined(STARBOARD)

namespace base {

// Full declaration is in process_metrics_iocounters.h.
Expand Down Expand Up @@ -165,7 +163,7 @@ class BASE_EXPORT ProcessMetrics {
#endif // BUILDFLAG(IS_WIN)

#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID) || \
BUILDFLAG(IS_AIX)
BUILDFLAG(IS_AIX) || defined(STARBOARD)
// Emits the cumulative CPU usage for all currently active threads since they
// were started into the output parameter (replacing its current contents).
// Threads that have already terminated will not be reported. Thus, the sum of
Expand All @@ -177,7 +175,7 @@ class BASE_EXPORT ProcessMetrics {
using CPUUsagePerThread = std::vector<std::pair<PlatformThreadId, TimeDelta>>;
bool GetCumulativeCPUUsagePerThread(CPUUsagePerThread&);
#endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) ||
// BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_AIX)
// BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_AIX) || defined(STARBOARD)

// Returns the number of average idle cpu wakeups per second since the last
// call.
Expand Down Expand Up @@ -481,7 +479,7 @@ BASE_EXPORT bool GetSystemDiskInfo(SystemDiskInfo* diskinfo);
BASE_EXPORT TimeDelta GetUserCpuTimeSinceBoot();

#endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) ||
// BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_AIX)
// BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_AIX) || defined(STARBOARD)

#if BUILDFLAG(IS_CHROMEOS)
// Data from files in directory /sys/block/zram0 about ZRAM usage.
Expand Down Expand Up @@ -647,5 +645,4 @@ BASE_EXPORT MachVMRegionResult GetTopInfo(mach_port_t task,

} // namespace base

#endif // !defined(STARBOARD)
#endif // BASE_PROCESS_PROCESS_METRICS_H_
3 changes: 3 additions & 0 deletions cobalt/base/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ static_library("base") {
"path_provider.h",
"polymorphic_downcast.h",
"polymorphic_equatable.h",
"process/process_metrics_starboard.cc",
"process/process_metrics_starboard.h",
"ref_counted_lock.h",
"source_location.cc",
"source_location.h",
Expand Down Expand Up @@ -117,6 +119,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_starboard_test.cc",
"statistics_test.cc",
"token_test.cc",
]
Expand Down
202 changes: 202 additions & 0 deletions cobalt/base/process/process_metrics_starboard.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
// 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_starboard.h"

#include <atomic>
#include <memory>
#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 "base/memory/ptr_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/time/time.h"

namespace base {

namespace {

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

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

} // namespace

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

// static
int ProcessMetricsHelper::GetClockTicksPerS(ReadCallback uptime_callback,
ReadCallback stat_callback) {
double 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], &uptime) ||
uptime == 0.0) {
return 0;
}
}

// Get starttime.
auto fields = GetProcStatFields(std::move(stat_callback), {21});
if (fields.size() != 1) return 0;
double starttime;
if (!StringToDouble(fields[0], &starttime) || starttime == 0.0) return 0;
int ticks_per_s = static_cast<int>(starttime / uptime);
int nearest_tens = 10 * ((ticks_per_s + 5) / 10);
return nearest_tens;
}

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

// static
PlatformThreadId ProcessMetricsHelper::GetPid() {
PlatformThreadId pid;
auto fields = GetProcStatFields(FilePath("/proc/self"), {0});
if (fields.size() != 1 || !StringToInt(fields[0], &pid)) {
return -1;
}
return pid;
}

// static
TimeDelta ProcessMetricsHelper::GetCumulativeCPUUsage() {
return GetCPUUsage(FilePath("/proc/self"));
}

// static
bool ProcessMetricsHelper::GetCumulativeCPUUsagePerThread(
CPUUsagePerThread& cpu_per_thread) {
cpu_per_thread.clear();
FileEnumerator file_enum(FilePath("/proc/self/task"), /*recursive=*/false,
FileEnumerator::DIRECTORIES);
for (FilePath path = file_enum.Next(); !path.empty();
path = file_enum.Next()) {
PlatformThreadId tid;
if (!StringToInt(path.GetComponents().back(), &tid)) {
return false;
}
cpu_per_thread.emplace_back(tid, GetCPUUsage(path));
}
return !cpu_per_thread.empty();
}

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

// Between the first '(' and last ')' is the second field called comm. It
// contains the process name and can include spaces.
int comm_start = contents->find('(') + 1;
int comm_end = contents->rfind(')');
// End before " (".
std::string pid = contents->substr(0, comm_start - 2);
std::string comm = contents->substr(comm_start, comm_end - comm_start);
// Field after comm is state. Start after ") ".
int state_start = comm_end + 2;
// Split the string starting with the state field.
auto parts = SplitString(contents->substr(state_start), " ", TRIM_WHITESPACE,
SPLIT_WANT_NONEMPTY);
Fields fields;
for (int index : indices) {
if (index < 0 || index >= parts.size() + 2) {
return Fields();
}
if (index == 0) {
fields.push_back(pid);
} else if (index == 1) {
fields.push_back(comm);
} else {
// Shift index to account for pid and comm.
int offset_index = index - 2;
fields.push_back(parts[offset_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) {
double ticks_per_s = clock_ticks_per_s.load();
if (ticks_per_s == 0) return TimeDelta();
// Get utime and stime.
auto fields = ProcessMetricsHelper::GetProcStatFields(
std::move(read_callback), {13, 14});
if (fields.size() != 2) return TimeDelta();
double utime;
if (!StringToDouble(fields[0], &utime)) return TimeDelta();
double stime;
if (!StringToDouble(fields[1], &stime)) return TimeDelta();
return TimeDelta::FromSecondsD((utime + stime) / ticks_per_s);
}

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

// static
std::unique_ptr<ProcessMetrics> ProcessMetrics::CreateProcessMetrics(
ProcessHandle process) {
return WrapUnique(new ProcessMetrics(process));
}

ProcessMetrics::ProcessMetrics(ProcessHandle process) {}

ProcessMetrics::~ProcessMetrics() {}

TimeDelta ProcessMetrics::GetCumulativeCPUUsage() {
return ProcessMetricsHelper::GetCumulativeCPUUsage();
}

bool ProcessMetrics::GetCumulativeCPUUsagePerThread(
CPUUsagePerThread& cpu_per_thread) {
return ProcessMetricsHelper::GetCumulativeCPUUsagePerThread(cpu_per_thread);
}

} // namespace base
54 changes: 54 additions & 0 deletions cobalt/base/process/process_metrics_starboard.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// 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_STARBOARD_H_
#define COBALT_BASE_PROCESS_PROCESS_METRICS_STARBOARD_H_

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

#include "base/bind.h"
#include "base/process/process_metrics.h"
#include "base/threading/platform_thread.h"
#include "base/time/time.h"
#include "third_party/abseil-cpp/absl/types/optional.h"

namespace base {

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

static int GetClockTicksPerS();
static void PopulateClockTicksPerS();
static PlatformThreadId GetPid();
static TimeDelta GetCumulativeCPUUsage();
static bool GetCumulativeCPUUsagePerThread(CPUUsagePerThread&);

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);
static TimeDelta GetCPUUsage(const FilePath&);
};

} // namespace base

#endif // COBALT_BASE_PROCESS_PROCESS_METRICS_STARBOARD_H_
Loading

0 comments on commit 9f95721

Please sign in to comment.