| /* | |
| * Copyright (C) 2026 The Android Open Source Project | |
| * | |
| * 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 <optional> | |
| #include <string> | |
| #include <utility> | |
| #include <vector> | |
| #include "perfetto/ext/base/cpu_info.h" | |
| #include "perfetto/ext/base/cpu_info_features_allowlist.h" | |
| #include "perfetto/ext/base/file_utils.h" | |
| #include "perfetto/ext/base/string_splitter.h" | |
| #include "perfetto/ext/base/string_utils.h" | |
| #include "perfetto/ext/base/utils.h" | |
| namespace perfetto { | |
| namespace base { | |
| namespace { | |
| // Key for default processor string in /proc/cpuinfo as seen on arm. Note the | |
| // uppercase P. | |
| const char kDefaultProcessor[] = "Processor"; | |
| // Key for processor entry in /proc/cpuinfo. Used to determine whether a group | |
| // of lines describes a CPU. | |
| const char kProcessor[] = "processor"; | |
| // Key for CPU implementer in /proc/cpuinfo. Arm only. | |
| const char kImplementer[] = "CPU implementer"; | |
| // Key for CPU architecture in /proc/cpuinfo. Arm only. | |
| const char kArchitecture[] = "CPU architecture"; | |
| // Key for CPU variant in /proc/cpuinfo. Arm only. | |
| const char kVariant[] = "CPU variant"; | |
| // Key for CPU part in /proc/cpuinfo. Arm only. | |
| const char kPart[] = "CPU part"; | |
| // Key for CPU revision in /proc/cpuinfo. Arm only. | |
| const char kRevision[] = "CPU revision"; | |
| // Key for feature flags in /proc/cpuinfo. Arm calls them Features, | |
| // Intel calls them Flags. | |
| const char kFeatures[] = "Features"; | |
| const char kFlags[] = "Flags"; | |
| std::string ReadFile(const std::string& path) { | |
| std::string contents; | |
| if (!base::ReadFile(path, &contents)) | |
| return ""; | |
| return contents; | |
| } | |
| } // namespace | |
| std::vector<CpuInfo> ParseCpuInfo(std::string proc_cpu_info) { | |
| std::vector<CpuInfo> cpus; | |
| std::string processor = "unknown"; | |
| std::optional<uint32_t> cpu_index; | |
| std::optional<uint32_t> implementer; | |
| std::optional<uint32_t> architecture; | |
| std::optional<uint32_t> variant; | |
| std::optional<uint32_t> part; | |
| std::optional<uint32_t> revision; | |
| uint64_t features = 0; | |
| uint32_t next_cpu_index = 0; | |
| auto flush_cpu = [&] { | |
| if (cpu_index.has_value()) { | |
| CpuInfo cpu{}; | |
| cpu.processor = processor; | |
| cpu.cpu_index = *cpu_index; | |
| cpu.implementer = implementer; | |
| cpu.architecture = architecture; | |
| cpu.variant = variant; | |
| cpu.part = part; | |
| cpu.revision = revision; | |
| cpu.features = features; | |
| #if PERFETTO_BUILDFLAG(PERFETTO_ARCH_CPU_ARM64) | |
| if (cpu.implementer && cpu.part) { | |
| std::string cpuid = | |
| base::Uint64ToHexStringNoPrefix(cpu.implementer.value()) + | |
| base::Uint64ToHexStringNoPrefix(cpu.part.value()); | |
| if (cpu.variant) { | |
| cpuid += base::Uint64ToHexStringNoPrefix(cpu.variant.value()); | |
| if (cpu.revision) { | |
| cpuid += base::Uint64ToHexStringNoPrefix(cpu.revision.value()); | |
| } | |
| } | |
| base::StringCopy(cpu.arm_cpuid, cpuid.c_str(), sizeof(cpu.arm_cpuid)); | |
| } | |
| #endif // PERFETTO_BUILDFLAG(PERFETTO_ARCH_CPU_ARM64) | |
| cpus.emplace_back(std::move(cpu)); | |
| next_cpu_index++; | |
| } | |
| cpu_index = std::nullopt; | |
| implementer = std::nullopt; | |
| architecture = std::nullopt; | |
| variant = std::nullopt; | |
| part = std::nullopt; | |
| revision = std::nullopt; | |
| features = 0; | |
| }; | |
| for (base::StringSplitter lines( | |
| std::move(proc_cpu_info), '\n', | |
| base::StringSplitter::EmptyTokenMode::ALLOW_EMPTY_TOKENS); | |
| lines.Next();) { | |
| std::string line(lines.cur_token(), lines.cur_token_size()); | |
| if (line.empty() && cpu_index.has_value()) { | |
| flush_cpu(); | |
| continue; | |
| } | |
| auto splits = base::SplitString(line, ":"); | |
| if (splits.size() != 2) | |
| continue; | |
| std::string key = | |
| base::StripSuffix(base::StripChars(splits[0], "\t", ' '), " "); | |
| std::string value = base::StripPrefix(splits[1], " "); | |
| if (key == kDefaultProcessor) { | |
| processor = value; | |
| } else if (key == kProcessor) { | |
| cpu_index = base::StringToUInt32(value); | |
| } else if (key == kImplementer) { | |
| implementer = base::CStringToUInt32(value.data(), 16); | |
| } else if (key == kArchitecture) { | |
| architecture = base::CStringToUInt32(value.data(), 10); | |
| } else if (key == kVariant) { | |
| variant = base::CStringToUInt32(value.data(), 16); | |
| } else if (key == kPart) { | |
| part = base::CStringToUInt32(value.data(), 16); | |
| } else if (key == kRevision) { | |
| revision = base::CStringToUInt32(value.data(), 10); | |
| } else if (key == kFeatures || key == kFlags) { | |
| for (base::StringSplitter ss(value.data(), ' '); ss.Next();) { | |
| for (size_t i = 0; i < base::ArraySize(kCpuInfoFeatures); ++i) { | |
| if (strcmp(ss.cur_token(), kCpuInfoFeatures[i]) == 0) { | |
| static_assert(base::ArraySize(kCpuInfoFeatures) < 64); | |
| features |= 1ull << i; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| flush_cpu(); | |
| return cpus; | |
| } | |
| std::vector<CpuInfo> ReadCpuInfo() { | |
| return ParseCpuInfo(ReadFile("/proc/cpuinfo")); | |
| } | |
| } // namespace base | |
| } // namespace perfetto |