| // Copyright 2018 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifdef UNSAFE_BUFFERS_BUILD |
| // TODO(crbug.com/40285824): Remove this and convert code to safer constructs. |
| #pragma allow_unsafe_buffers |
| #endif |
| |
| #include "chrome/browser/ash/system/procfs_util.h" |
| |
| #include <string_view> |
| |
| #include "base/files/file_util.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" |
| |
| namespace ash { |
| namespace system { |
| |
| std::optional<SingleProcStat> GetSingleProcStat( |
| const base::FilePath& stat_file) { |
| SingleProcStat stat; |
| std::string stat_contents; |
| if (!base::ReadFileToString(stat_file, &stat_contents)) |
| return std::nullopt; |
| |
| // This file looks like: |
| // <num1> (<str>) <char> <num2> <num3> ... |
| // The entries at 0-based index 0 is the PID. |
| // The entry at index 1 represents a filename, which can have an arbitrary |
| // number of spaces, so skip it by finding the last parenthesis. |
| // The entry at index 3, represents the PPID of the process. |
| // The entries at indices 13 and 14 represent the amount of time the |
| // process was in user mode and kernel mode in jiffies. |
| // The entry at index 23 represents process resident memory in pages. |
| const auto first_space = stat_contents.find(' '); |
| if (first_space == std::string::npos) |
| return std::nullopt; |
| if (!base::StringToInt(stat_contents.substr(0, first_space), &stat.pid)) |
| return std::nullopt; |
| |
| const auto left_parenthesis = stat_contents.find('('); |
| if (left_parenthesis == std::string::npos) |
| return std::nullopt; |
| const auto right_parenthesis = stat_contents.find(')'); |
| if (right_parenthesis == std::string::npos) |
| return std::nullopt; |
| if ((right_parenthesis - left_parenthesis - 1) <= 0) |
| return std::nullopt; |
| stat.name = stat_contents.substr(left_parenthesis + 1, |
| right_parenthesis - left_parenthesis - 1); |
| |
| // Skip the comm field. |
| const auto last_parenthesis = stat_contents.find_last_of(')'); |
| if (last_parenthesis == std::string::npos || |
| last_parenthesis + 1 > stat_contents.length()) |
| return std::nullopt; |
| |
| // Skip the parenthesis itself. |
| const std::string truncated_proc_stat_contents = |
| stat_contents.substr(last_parenthesis + 1); |
| |
| std::vector<std::string_view> proc_stat_split = |
| base::SplitStringPiece(truncated_proc_stat_contents, " \t\n", |
| base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); |
| |
| // The first 2 entries of the file were removed earlier, so all the indices |
| // for the entries will be shifted by 2. |
| if (proc_stat_split.size() < 21) |
| return std::nullopt; |
| if (!base::StringToInt(proc_stat_split[1], &stat.ppid)) |
| return std::nullopt; |
| |
| // These two entries contain the total time this process spent in user mode |
| // and kernel mode. This is roughly the total CPU time that the process has |
| // used. |
| if (!base::StringToInt64(proc_stat_split[11], &stat.utime)) |
| return std::nullopt; |
| |
| if (!base::StringToInt64(proc_stat_split[12], &stat.stime)) |
| return std::nullopt; |
| |
| if (!base::StringToInt64(proc_stat_split[21], &stat.rss)) |
| return std::nullopt; |
| return stat; |
| } |
| |
| std::optional<int64_t> GetCpuTimeJiffies(const base::FilePath& stat_file) { |
| std::string stat_contents; |
| if (!base::ReadFileToString(stat_file, &stat_contents)) |
| return std::nullopt; |
| |
| // This file looks like: |
| // cpu <num1> <num2> ... |
| // cpu0 <num1> <num2> ... |
| // cpu1 <num1> <num2> ... |
| // ... |
| // Where each number represents the amount of time in jiffies a certain CPU is |
| // in some state. The first line presents the total amount of time in jiffies |
| // the system is in some state across all CPUs. The first line beginning with |
| // "cpu " needs to be singled out. The first 8 of the 10 numbers on that line |
| // need to be summed to obtain the total amount of time in jiffies the system |
| // has been running across all states. The last 2 numbers are guest and |
| // guest_nice, which are already accounted for in the first 2 numbers of user |
| // and nice respectively. |
| std::vector<std::string_view> stat_lines = base::SplitStringPiece( |
| stat_contents, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); |
| for (const auto& line : stat_lines) { |
| // Find the line that starts with "cpu " and sum the first 8 numbers to |
| // get the total amount of jiffies used. |
| if (base::StartsWith(line, "cpu ", base::CompareCase::SENSITIVE)) { |
| std::vector<std::string_view> cpu_info_parts = base::SplitStringPiece( |
| line, " \t", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); |
| if (cpu_info_parts.size() != 11) |
| return std::nullopt; |
| |
| int64_t total_time = 0; |
| // Sum the first 8 numbers. Element 0 is "cpu". |
| for (int i = 1; i <= 8; i++) { |
| int64_t curr; |
| if (!base::StringToInt64(cpu_info_parts.at(i), &curr)) |
| return std::nullopt; |
| total_time += curr; |
| } |
| return total_time; |
| } |
| } |
| return std::nullopt; |
| } |
| |
| ProcStatFile::ProcStatFile(base::ProcessId process_id) { |
| base::FilePath procfs_stat_path = |
| base::FilePath("/proc") |
| .Append(base::NumberToString(process_id)) |
| .Append("stat"); |
| // Opening procfs file is not blocking. |
| base::ScopedAllowBlocking allow_blocking; |
| file_ = base::File(procfs_stat_path, |
| base::File::FLAG_OPEN | base::File::FLAG_READ); |
| } |
| |
| ProcStatFile::~ProcStatFile() { |
| // Closing procfs file is not blocking. |
| base::ScopedAllowBlocking allow_blocking; |
| file_.Close(); |
| } |
| |
| bool ProcStatFile::IsValid() const { |
| return file_.IsValid(); |
| } |
| |
| bool ProcStatFile::IsPidAlive() { |
| char buf; |
| // Reading procfs is not blocking. |
| base::ScopedAllowBlocking allow_blocking; |
| // If the process/thread dies, read(2)ing stat file fails as ESRCH. |
| return file_.IsValid() && file_.Read(0, &buf, 1) == 1; |
| } |
| |
| } // namespace system |
| } // namespace ash |