| // Copyright 2012 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/process/internal_linux.h" |
| |
| #include <limits.h> |
| #include <unistd.h> |
| |
| #include <map> |
| #include <string> |
| #include <vector> |
| |
| #include "base/files/file_util.h" |
| #include "base/logging.h" |
| #include "base/notreached.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" |
| #include "base/time/time.h" |
| #include "build/build_config.h" |
| |
| // Not defined on AIX by default. |
| #if BUILDFLAG(IS_AIX) |
| #define NAME_MAX 255 |
| #endif |
| |
| namespace base { |
| namespace internal { |
| |
| const char kProcDir[] = "/proc"; |
| |
| const char kStatFile[] = "stat"; |
| |
| #if !defined(STARBOARD) |
| FilePath GetProcPidDir(pid_t pid) { |
| return FilePath(kProcDir).Append(NumberToString(pid)); |
| } |
| |
| pid_t ProcDirSlotToPid(const char* d_name) { |
| int i; |
| for (i = 0; i < NAME_MAX && d_name[i]; ++i) { |
| if (!IsAsciiDigit(d_name[i])) { |
| return 0; |
| } |
| } |
| if (i == NAME_MAX) |
| return 0; |
| |
| // Read the process's command line. |
| pid_t pid; |
| std::string pid_string(d_name); |
| if (!StringToInt(pid_string, &pid)) { |
| NOTREACHED(); |
| return 0; |
| } |
| return pid; |
| } |
| #endif // !defined(STARBOARD) |
| |
| bool ReadProcFile(const FilePath& file, std::string* buffer) { |
| DCHECK(FilePath(kProcDir).IsParent(file)); |
| buffer->clear(); |
| // Synchronously reading files in /proc is safe. |
| ScopedAllowBlocking scoped_allow_blocking; |
| |
| if (!ReadFileToString(file, buffer)) { |
| DLOG(WARNING) << "Failed to read " << file.MaybeAsASCII(); |
| return false; |
| } |
| 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) { |
| // |stats_data| may be empty if the process disappeared somehow. |
| // e.g. http://crbug.com/145811 |
| if (stats_data.empty()) |
| return false; |
| |
| // The stat file is formatted as: |
| // pid (process name) data1 data2 .... dataN |
| // Look for the closing paren by scanning backwards, to avoid being fooled by |
| // processes with ')' in the name. |
| size_t pid_end = stats_data.find(" ("); |
| size_t comm_start = pid_end + 2; |
| size_t comm_end = stats_data.rfind(") "); |
| size_t state_start = comm_end + 2; |
| if (pid_end == std::string::npos || |
| comm_end == std::string::npos || |
| pid_end > comm_end) { |
| DLOG(WARNING) << "Failed to find matched parens in '" << stats_data << "'"; |
| NOTREACHED(); |
| return false; |
| } |
| |
| proc_stats->clear(); |
| // PID. |
| proc_stats->push_back(stats_data.substr(0, pid_end)); |
| // Process name without parentheses. |
| proc_stats->push_back( |
| stats_data.substr(comm_start, |
| comm_end - comm_start)); |
| |
| // Split the rest. |
| std::vector<std::string> other_stats = SplitString( |
| stats_data.substr(state_start), " ", |
| base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); |
| for (const auto& i : other_stats) |
| proc_stats->push_back(i); |
| return true; |
| } |
| |
| typedef std::map<std::string, std::string> ProcStatMap; |
| void ParseProcStat(const std::string& contents, ProcStatMap* output) { |
| StringPairs key_value_pairs; |
| SplitStringIntoKeyValuePairs(contents, ' ', '\n', &key_value_pairs); |
| for (auto& i : key_value_pairs) { |
| output->insert(std::move(i)); |
| } |
| } |
| |
| int64_t GetProcStatsFieldAsInt64(const std::vector<std::string>& proc_stats, |
| ProcStatsFields field_num) { |
| DCHECK_GE(field_num, VM_PPID); |
| CHECK_LT(static_cast<size_t>(field_num), proc_stats.size()); |
| |
| int64_t value; |
| return StringToInt64(proc_stats[field_num], &value) ? value : 0; |
| } |
| |
| size_t GetProcStatsFieldAsSizeT(const std::vector<std::string>& proc_stats, |
| ProcStatsFields field_num) { |
| DCHECK_GE(field_num, VM_PPID); |
| CHECK_LT(static_cast<size_t>(field_num), proc_stats.size()); |
| |
| size_t value; |
| return StringToSizeT(proc_stats[field_num], &value) ? value : 0; |
| } |
| |
| int64_t ReadStatFileAndGetFieldAsInt64(const FilePath& stat_file, |
| ProcStatsFields field_num) { |
| std::string stats_data; |
| if (!ReadProcFile(stat_file, &stats_data)) |
| return 0; |
| std::vector<std::string> proc_stats; |
| if (!ParseProcStats(stats_data, &proc_stats)) |
| return 0; |
| 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; |
| if (!ReadProcStats(pid, &stats_data)) |
| return 0; |
| std::vector<std::string> proc_stats; |
| if (!ParseProcStats(stats_data, &proc_stats)) |
| return 0; |
| return GetProcStatsFieldAsSizeT(proc_stats, field_num); |
| } |
| #endif // !defined(STARBOARD) |
| |
| Time GetBootTime() { |
| FilePath path("/proc/stat"); |
| std::string contents; |
| if (!ReadProcFile(path, &contents)) |
| return Time(); |
| ProcStatMap proc_stat; |
| ParseProcStat(contents, &proc_stat); |
| ProcStatMap::const_iterator btime_it = proc_stat.find("btime"); |
| if (btime_it == proc_stat.end()) |
| return Time(); |
| int btime; |
| if (!StringToInt(btime_it->second, &btime)) |
| return Time(); |
| return Time::FromTimeT(btime); |
| } |
| |
| #if !defined(STARBOARD) |
| TimeDelta GetUserCpuTimeSinceBoot() { |
| FilePath path("/proc/stat"); |
| std::string contents; |
| if (!ReadProcFile(path, &contents)) |
| return TimeDelta(); |
| |
| ProcStatMap proc_stat; |
| ParseProcStat(contents, &proc_stat); |
| ProcStatMap::const_iterator cpu_it = proc_stat.find("cpu"); |
| if (cpu_it == proc_stat.end()) |
| return TimeDelta(); |
| |
| std::vector<std::string> cpu = SplitString( |
| cpu_it->second, kWhitespaceASCII, TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY); |
| |
| if (cpu.size() < 2 || cpu[0] != "cpu") |
| return TimeDelta(); |
| |
| uint64_t user; |
| uint64_t nice; |
| if (!StringToUint64(cpu[0], &user) || !StringToUint64(cpu[1], &nice)) |
| return TimeDelta(); |
| |
| return ClockTicksToTimeDelta(checked_cast<int64_t>(user + nice)); |
| } |
| |
| TimeDelta ClockTicksToTimeDelta(int64_t clock_ticks) { |
| // This queries the /proc-specific scaling factor which is |
| // conceptually the system hertz. To dump this value on another |
| // system, try |
| // od -t dL /proc/self/auxv |
| // and look for the number after 17 in the output; mine is |
| // 0000040 17 100 3 134512692 |
| // which means the answer is 100. |
| // It may be the case that this value is always 100. |
| static const long kHertz = sysconf(_SC_CLK_TCK); |
| |
| return Microseconds(Time::kMicrosecondsPerSecond * clock_ticks / kHertz); |
| } |
| #endif // !defined(STARBOARD) |
| |
| } // namespace internal |
| } // namespace base |