// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/metrics/perf/process_type_collector.h"
#include "base/metrics/histogram_macros.h"
#include "base/process/launch.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "content/public/common/content_switches.h"
#include "third_party/re2/src/re2/re2.h"
namespace metrics {
namespace {
// Name the histogram that represents the success and various failure modes for
// reporting collection of types.
const char kUmaHistogramName[] = "ChromeOS.CWP.CollectProcessTypes";
void SkipLine(std::string_view* contents) {
static const LazyRE2 kSkipLine = {R"(.+\n?)"};
RE2::Consume(contents, *kSkipLine);
}
// Matches both Ash-Chrome and Lacros binaries.
const LazyRE2 kChromeExePathMatcher = {
R"((/opt/google/chrome/chrome|\S*lacros\S*/chrome))"};
// Matches Lacros binaries.
const LazyRE2 kLacrosExePathMatcher = {R"((\S*lacros\S*/chrome))"};
} // namespace
std::map<uint32_t, Process> ProcessTypeCollector::ChromeProcessTypes(
std::vector<uint32_t>& lacros_pids,
std::string& lacros_path) {
std::string output;
if (!base::GetAppOutput(std::vector<std::string>({"ps", "-ewwo", "pid,cmd"}),
&output)) {
UMA_HISTOGRAM_ENUMERATION(kUmaHistogramName,
CollectionAttemptStatus::kProcessTypeCmdError);
return std::map<uint32_t, Process>();
}
return ParseProcessTypes(output, lacros_pids, lacros_path);
}
std::map<uint32_t, Thread> ProcessTypeCollector::ChromeThreadTypes() {
std::string output;
if (!base::GetAppOutput(
std::vector<std::string>({"ps", "-ewwLo", "pid,lwp,comm,cmd"}),
&output)) {
UMA_HISTOGRAM_ENUMERATION(kUmaHistogramName,
CollectionAttemptStatus::kThreadTypeCmdError);
return std::map<uint32_t, Thread>();
}
return ParseThreadTypes(output);
}
std::map<uint32_t, Process> ProcessTypeCollector::ParseProcessTypes(
std::string_view contents,
std::vector<uint32_t>& lacros_pids,
std::string& lacros_path) {
static const LazyRE2 kLineMatcher = {
R"(\s*(\d+))" // PID
R"(\s+(.+)\n?)" // COMMAND LINE
};
// Type flag with or without any value.
const std::string kTypeFlagRegex =
base::StrCat({R"(.*--)", switches::kProcessType, R"(=(\S*))"});
static const LazyRE2 kTypeFlagMatcher = {kTypeFlagRegex.c_str()};
// Skip header.
SkipLine(&contents);
std::map<uint32_t, Process> process_types;
bool is_truncated = false;
while (!contents.empty()) {
uint32_t pid = 0;
std::string_view cmd_line;
if (!RE2::Consume(&contents, *kLineMatcher, &pid, &cmd_line)) {
SkipLine(&contents);
is_truncated = true;
continue;
}
if (process_types.find(pid) != process_types.end()) {
is_truncated = true;
continue;
}
std::string_view cmd;
if (!RE2::Consume(&cmd_line, *kChromeExePathMatcher, &cmd)) {
continue;
}
// Use a second match to record any Lacros PID.
std::string_view lacros_cmd;
if (RE2::Consume(&cmd, *kLacrosExePathMatcher, &lacros_cmd)) {
lacros_pids.emplace_back(pid);
if (lacros_path.empty()) {
lacros_path = lacros_cmd;
}
}
std::string type;
RE2::Consume(&cmd_line, *kTypeFlagMatcher, &type);
Process process = Process::OTHER_PROCESS;
if (type.empty()) {
process = Process::BROWSER_PROCESS;
} else if (type == switches::kRendererProcess) {
process = Process::RENDERER_PROCESS;
} else if (type == switches::kGpuProcess) {
process = Process::GPU_PROCESS;
} else if (type == switches::kUtilityProcess) {
process = Process::UTILITY_PROCESS;
} else if (type == switches::kZygoteProcess) {
process = Process::ZYGOTE_PROCESS;
} else if (type == switches::kPpapiPluginProcess) {
process = Process::PPAPI_PLUGIN_PROCESS;
}
process_types.emplace(pid, process);
}
if (process_types.empty()) {
UMA_HISTOGRAM_ENUMERATION(kUmaHistogramName,
CollectionAttemptStatus::kEmptyProcessType);
} else if (is_truncated) {
UMA_HISTOGRAM_ENUMERATION(kUmaHistogramName,
CollectionAttemptStatus::kProcessTypeTruncated);
} else {
UMA_HISTOGRAM_ENUMERATION(kUmaHistogramName,
CollectionAttemptStatus::kProcessTypeSuccess);
}
return process_types;
}
std::map<uint32_t, Thread> ProcessTypeCollector::ParseThreadTypes(
std::string_view contents) {
static const LazyRE2 kLineMatcher = {
R"(\s*(\d+))" // PID
R"(\s+(\d+))" // TID
R"(\s+(.+)\n?)" // COMM and CMD, either of which may contain spaces
};
// Skip header.
SkipLine(&contents);
std::map<uint32_t, Thread> thread_types;
bool is_truncated = false;
while (!contents.empty()) {
uint32_t pid = 0, tid = 0;
std::string_view comm_cmd;
if (!RE2::Consume(&contents, *kLineMatcher, &pid, &tid, &comm_cmd)) {
SkipLine(&contents);
is_truncated = true;
continue;
}
if (!RE2::PartialMatch(comm_cmd, *kChromeExePathMatcher)) {
continue;
}
if (thread_types.find(tid) != thread_types.end()) {
is_truncated = true;
continue;
}
Thread thread = Thread::OTHER_THREAD;
if (pid == tid) {
thread = Thread::MAIN_THREAD;
} else if (comm_cmd.starts_with("Chrome_IOThread") ||
comm_cmd.starts_with("Chrome_ChildIOT")) {
thread = Thread::IO_THREAD;
} else if (comm_cmd.starts_with("CompositorTileW")) {
thread = Thread::COMPOSITOR_TILE_WORKER_THREAD;
} else if (comm_cmd.starts_with("Compositor") ||
comm_cmd.starts_with("VizCompositorTh")) {
thread = Thread::COMPOSITOR_THREAD;
} else if (comm_cmd.starts_with("ThreadPool")) {
thread = Thread::THREAD_POOL_THREAD;
} else if (comm_cmd.starts_with("DrmThread")) {
thread = Thread::DRM_THREAD;
} else if (comm_cmd.starts_with("GpuMemory")) {
thread = Thread::GPU_MEMORY_THREAD;
} else if (comm_cmd.starts_with("MemoryInfra")) {
thread = Thread::MEMORY_INFRA_THREAD;
} else if (comm_cmd.starts_with("Media")) {
thread = Thread::MEDIA_THREAD;
} else if (comm_cmd.starts_with("DedicatedWorker")) {
thread = Thread::DEDICATED_WORKER_THREAD;
} else if (comm_cmd.starts_with("ServiceWorker")) {
thread = Thread::SERVICE_WORKER_THREAD;
} else if (comm_cmd.starts_with("WebRTC")) {
thread = Thread::WEBRTC_THREAD;
} else if (comm_cmd.starts_with("dav1d-worker")) {
thread = Thread::DAV1D_WORKER_THREAD;
} else if (comm_cmd.starts_with("AudioThread")) {
thread = Thread::AUDIO_THREAD;
} else if (comm_cmd.starts_with("AudioOutputDevi")) {
thread = Thread::AUDIO_DEVICE_THREAD;
} else if (comm_cmd.starts_with("StackSamplingPr")) {
thread = Thread::STACK_SAMPLING_THREAD;
} else if (comm_cmd.starts_with("VideoFrameCompo")) {
thread = Thread::VIDEO_FRAME_COMPOSITOR_THREAD;
} else if (comm_cmd.starts_with("CodecWorker")) {
thread = Thread::CODEC_WORKER_THREAD;
}
thread_types.emplace(tid, thread);
}
if (thread_types.empty()) {
UMA_HISTOGRAM_ENUMERATION(kUmaHistogramName,
CollectionAttemptStatus::kEmptyThreadType);
} else if (is_truncated) {
UMA_HISTOGRAM_ENUMERATION(kUmaHistogramName,
CollectionAttemptStatus::kThreadTypeTruncated);
} else {
UMA_HISTOGRAM_ENUMERATION(kUmaHistogramName,
CollectionAttemptStatus::kThreadTypeSuccess);
}
return thread_types;
}
} // namespace metrics