blob: 78e66e48430b1ee4cf9007fd8f3780a39b796fae [file]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/cpu_performance/cpu_performance.h"
#include <optional>
#include "base/functional/bind.h"
#include "base/no_destructor.h"
#include "base/strings/string_util.h"
#include "base/synchronization/lock.h"
#include "base/system/sys_info.h"
#include "base/task/thread_pool.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/re2/src/re2/re2.h"
// The implementation of the CPU Performance API is experimental and subject to
// change.
namespace content::cpu_performance {
namespace {
bool Search(std::string_view text, const re2::RE2& re) {
return re2::RE2::PartialMatch(text, re);
}
void Replace(std::string* text,
const re2::RE2& re,
std::string_view replacement) {
re2::RE2::GlobalReplace(text, re, replacement);
}
void ReplaceFirst(std::string* text,
const re2::RE2& re,
std::string_view replacement) {
re2::RE2::Replace(text, re, replacement);
}
void TrimAndCollapseWhitespace(std::string* text) {
// \p{Z} matches unicode separators (including NBSP).
// \x1C\x1F match the file and unit separator characters.
Replace(text, "^[\\s\\p{Z}\\x1C\\x1F]+", "");
Replace(text, "[\\s\\p{Z}\\x1C\\x1F]+$", "");
Replace(text, "[\\s\\p{Z}\\x1C\\x1F]+", " ");
}
// Returns the manufacturer.
Manufacturer GetManufacturer(std::string_view cpu_model) {
if (Search(cpu_model, "(?i)\\bAMD\\b")) {
return Manufacturer::kAMD;
} else if (Search(cpu_model, "(?i)\\bApple\\b")) {
return Manufacturer::kApple;
} else if (Search(cpu_model, "(?i)\\b(Intel|Celeron|Pentium)\\b")) {
return Manufacturer::kIntel;
} else if (Search(cpu_model, "(?i)\\bMediaTek\\b")) {
return Manufacturer::kMediaTek;
} else if (Search(cpu_model, "(?i)\\bMicrosoft\\b")) {
return Manufacturer::kMicrosoft;
} else if (Search(cpu_model, "(?i)\\b(Qualcomm|Snapdragon)\\b")) {
return Manufacturer::kQualcomm;
} else if (Search(cpu_model, "(?i)\\bSamsung\\b")) {
return Manufacturer::kSamsung;
}
return Manufacturer::kUnknown;
}
} // anonymous namespace
Tier TierFromInt(int value) {
CHECK_LE(static_cast<int>(Tier::kUnknown), value);
CHECK_GE(static_cast<int>(Tier::kUltra), value);
return static_cast<Tier>(value);
}
std::pair<Manufacturer, std::string> SplitCpuModel(std::string_view cpu_model) {
std::string text(cpu_model);
TrimAndCollapseWhitespace(&text);
Manufacturer manufacturer = GetManufacturer(text);
// Remove everything between parentheses.
Replace(&text, "\\([^)]*\\)", " ");
// Remove special characters.
Replace(&text, "\\$|®|™", " ");
// Remove "GHz" patterns.
Replace(&text, "(?i)@( )?\\d[.,]\\d+([~\\-]\\d[.,]\\d+)?( )?GHz\\b", "");
Replace(&text, "(?i)\\b\\d[.,]\\d+([~\\-]\\d[.,]\\d+)?( )?GHz\\b", "");
TrimAndCollapseWhitespace(&text);
// Remove trailing special characters.
Replace(&text, "(^| )?[@~\\-,.]$", "");
// Remove filler words.
Replace(&text, "(?i)\\bCPU\\b", "");
Replace(&text, "(?i)\\bMobile\\b", "");
Replace(&text, "(?i)\\bProcessor\\b", "");
Replace(&text, "(?i)\\bSilicon\\b", "");
Replace(&text, "(?i)\\bSOC\\b", "");
Replace(&text, "(?i)\\bTechnology\\b", "");
TrimAndCollapseWhitespace(&text);
// Processing specific to each manufacturer.
switch (manufacturer) {
case Manufacturer::kAMD:
ReplaceFirst(&text, "(?i).*?\\bAMD\\b", "");
TrimAndCollapseWhitespace(&text);
Replace(&text, "(?i)\\bFX -", "FX-");
Replace(&text, "(?i)\\+( )?(AMD )?Radeon.*", "");
Replace(&text,
"(?i)\\b(RADEON )?R\\d+, \\d+ COMPUTE CORES \\d+C\\+\\d+G\\b",
"");
Replace(&text, "(?i)\\bwith (AMD )?Radeon.*", "");
Replace(&text, "(?i)\\bw\\/( )?(AMD )?Radeon.*", "");
Replace(&text, "(?i)\\bRadeon.*", "");
Replace(&text, "(?i)\\b\\w+( |-)Core\\b", "");
Replace(&text, "(?i)\\b\\d+-Core(s)?\\b", "");
Replace(&text, "(?i)\\bAPU\\b", "");
Replace(&text, "(?i)\\bCreator Edition\\b", "");
Replace(&text, "(?i)\\bDesktop Kit\\b", "");
Replace(&text, "(?i)\\b(3250C) 15W\\b", "\\1");
break;
case Manufacturer::kApple:
ReplaceFirst(&text, "(?i).*?\\bApple\\b", "");
break;
case Manufacturer::kIntel:
ReplaceFirst(&text, "(?i).*?\\bIntel\\b", "");
TrimAndCollapseWhitespace(&text);
Replace(&text, "(?i)\\b(Core)(2)\\b", "\\1 \\2");
Replace(&text, "(?i)\\b(Core i\\d+)( )?-( )?", "\\1-");
Replace(&text, "(?i)\\b(Core i\\d+) (M) (\\d+)\\b", "\\1-\\3\\2");
Replace(&text, "(?i)\\b(Core i\\d+) ([LQU]) (\\d+)\\b", "\\1-\\3\\2M");
Replace(&text, "(?i)\\b(Core i\\d+) (\\d+)\\b", "\\1-\\2");
Replace(&text, "(?i)\\b(Celeron|Pentium) Dual(-Core)?\\b", "\\1");
Replace(&text, "\\b0+$", "");
break;
default:
// For all other manufacturers including kUnknown, we just return an
// empty model string.
return {manufacturer, ""};
}
TrimAndCollapseWhitespace(&text);
return {manufacturer, text};
}
namespace {
struct CpuInfoState {
base::Lock lock;
Tier tier = Tier::kUnknown;
std::string model;
int cores = 0;
};
CpuInfoState& GetCpuInfoState() {
static base::NoDestructor<CpuInfoState> state;
return *state;
}
void InitializeFromCores() {
int cores = base::SysInfo::NumberOfProcessors();
Tier tier = GetTierFromCores(cores);
base::AutoLock auto_lock(GetCpuInfoState().lock);
GetCpuInfoState().cores = cores;
GetCpuInfoState().tier = tier;
}
void InitializeFromCpuInfo() {
std::string model = base::SysInfo::CPUModelName();
int cores = base::SysInfo::NumberOfProcessors();
Tier tier = GetTierFromCpuInfo(model, cores);
base::AutoLock auto_lock(GetCpuInfoState().lock);
GetCpuInfoState().model = std::move(model);
GetCpuInfoState().cores = cores;
GetCpuInfoState().tier = tier;
}
} // anonymous namespace
Tier GetTier() {
base::AutoLock auto_lock(GetCpuInfoState().lock);
return GetCpuInfoState().tier;
}
std::string GetModel() {
base::AutoLock auto_lock(GetCpuInfoState().lock);
return GetCpuInfoState().model;
}
int GetCores() {
base::AutoLock auto_lock(GetCpuInfoState().lock);
return GetCpuInfoState().cores;
}
void Initialize() {
if (base::FeatureList::IsEnabled(blink::features::kCpuPerformance)) {
// Use a simple, default implementation which is non-blocking, so that we
// have a reasonable computed value in the unlikely case that the tier is
// fetched by renderer initialization before the task executes.
InitializeFromCores();
// Post the task that will update the tier asynchronously, using the more
// accurate (but potentially blocking) implementation. Note that this task
// is a "MayBlock" because of base::SysInfo::CPUModelName(), which on Linux
// reads /proc/cpuinfo.
base::ThreadPool::PostTask(
FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
base::BindOnce(&InitializeFromCpuInfo));
}
}
Tier GetTierFromCores(int cores) {
if (cores >= 1 && cores <= 2) {
return Tier::kLow;
} else if (cores >= 3 && cores <= 4) {
return Tier::kMid;
} else if (cores >= 5 && cores <= 12) {
return Tier::kHigh;
} else if (cores >= 13) {
return Tier::kUltra;
}
return Tier::kUnknown;
}
Tier GetTierFromCpuInfo(std::string_view cpu_model, int cores) {
// Number of cores is unknown.
if (cores <= 0) {
return Tier::kUnknown;
}
if (cores <= 1) {
return Tier::kLow;
}
auto [manufacturer, model] = SplitCpuModel(cpu_model);
if (cores <= 4) {
switch (manufacturer) {
case Manufacturer::kAMD:
// 2 cores, K8 + K10 + Mobile Trinity
if (cores == 2 &&
(Search(model, "^Athlon 64\\b") || Search(model, "^Athlon II\\b") ||
Search(model, "^Athlon X2\\b") || Search(model, "^Phenom II\\b") ||
Search(model, "^Sempron X2\\b") ||
Search(model, "^Turion II\\b") || Search(model, "^Turion X2\\b") ||
// Llano (~2011)
Search(model, "^(A4|E2)-[3]\\d\\d\\d[A-Z]*\\b") ||
// Trinity (~2012)
Search(model, "^(A4|A6)-[4]\\d\\d\\dM[A-Z]*\\b"))) {
return Tier::kLow;
}
// 2 cores, Bobcat + Jaguar
if (cores == 2 && (Search(model, "^(C|E|E1|E2|T|Z)-\\w*\\b") ||
Search(model, "^(A4)-[1]\\d\\d\\d[A-Z]*\\b") ||
Search(model, "^(GX)-[2]\\d\\d[A-Z]*\\b") ||
Search(model, "^Sempron 2650\\b"))) {
return Tier::kLow;
}
// 2 cores, Entry-Level Stoney Ridge 2019 6W Re-Release
if (cores == 2 && Search(model, "^A4-9120[Ce]\\b")) {
return Tier::kLow;
}
// 4 cores, >= Zen
if (cores == 4 && Search(model, "^Ryzen\\b") &&
// not 2 cores, Zen (~2017)
!Search(model, "^Ryzen 3 Pro 2100GE\\b") &&
!Search(model, "^Ryzen 3 Pro 3050GE\\b") &&
!Search(model, "^Ryzen 3 2200U\\b") &&
!Search(model, "^Ryzen 3 3200U\\b") &&
!Search(model, "^Ryzen 3 3250[UC]\\b") &&
!Search(model, "^Ryzen Embedded R[1]\\d\\d\\d[A-Z]*\\b") &&
// not 2 cores, Zen+ (~2018)
!Search(model, "^Ryzen Embedded R2312\\b")) {
return Tier::kHigh;
}
break;
case Manufacturer::kIntel:
// 2-4 cores, E-Core, Bonnell + Saltwell
if (cores >= 2 && cores <= 4 &&
// Silverthorne (~2008)
(Search(model, "^Atom (Z5)\\d\\d[A-Z]*\\b") ||
// Diamondville (~2008)
Search(model, "^Atom (2|3|N2)\\d\\d[A-Z]*\\b") ||
// Pineview (~2010)
Search(model, "^Atom (D4|N4|D5|N5)\\d\\d[A-Z]*\\b") ||
// Tunnel Creek (~2010) / Stellarton (~2010)
Search(model, "^Atom (E6)\\d\\d[A-Z]*\\b") ||
// Lincroft (~2010)
Search(model, "^Atom (Z6)\\d\\d[A-Z]*\\b") ||
// Cedarview (~2011)
Search(model, "^Atom (D2|N2)\\d\\d\\d[A-Z]*\\b") ||
// Penwell (~2012) + Cloverview (~2012)
Search(model, "^Atom (Z2)\\d\\d\\d[A-Z]*\\b"))) {
return Tier::kLow;
}
// 2-4 cores, E-Core, Silvermont + Airmont
if (cores >= 2 && cores <= 4 &&
// Bay Trail-D (~2013)
(Search(model, "^Celeron (J1)\\d\\d\\d[A-Z]*\\b") ||
Search(model, "^Pentium (J2)\\d\\d\\d[A-Z]*\\b") ||
// Avoton (~2013) / Rangeley (~2013)
Search(model, "^Atom (C2)[35]\\d\\d[A-Z]*\\b") ||
// Bay Trail-I (~2013)
Search(model, "^Atom (E3)[8]\\d\\d[A-Z]*\\b") ||
// Bay Trail-M (~2013)
Search(model, "^Celeron (N2)\\d\\d\\d[A-Z]*\\b") ||
Search(model, "^Pentium (N3)[5]\\d\\d[A-Z]*\\b") ||
// Bay Trail-T (~2013) + Merrifield (~2014) + Moorefield (~2014)
Search(model, "^Atom (Z3)\\d\\d\\d[A-Z]*\\b") ||
// Braswell (~2016)
Search(model, "^Atom x[5]-(E8)\\d\\d\\d[A-Z]*\\b") ||
Search(model, "^Celeron (J3|N3)[01]\\d\\d[A-Z]*\\b") ||
Search(model, "^Pentium (J3|N3)[7]\\d\\d[A-Z]*\\b") ||
// Cherry Trail-T (~2015)
Search(model, "^Atom x[57]-(Z8)\\d\\d\\d[A-Z]*\\b"))) {
return Tier::kLow;
}
// 2 cores, E-Core, Goldmont
if (cores == 2 &&
// Apollo Lake (~2016)
(Search(model, "^Atom x[5]-(A3|E3)\\d\\d\\d[A-Z]*\\b") ||
Search(model, "^Celeron (J3|N3)[34]\\d\\d[A-Z]*\\b") ||
// Denverton (~2017)
Search(model, "^Atom (C3)\\d\\d\\d[A-Z]*\\b"))) {
return Tier::kLow;
}
// 2 cores, P-Core, Merom + Penryn
if (cores == 2 &&
// Merom (~2006) + Conroe (~2006) + Penryn (~2007)
(Search(model, "^Celeron (E1|SU2|T1|T3)\\d\\d\\d[A-Z]*\\b") ||
Search(model, "^Core 2 Duo\\b") ||
Search(model, "^Core 2 Extreme\\b") ||
Search(model,
"^Pentium (E2|SU2|SU4|T2|T3|T4)\\d\\d\\d[A-Z]*\\b") ||
Search(model, "^Xeon (3|E3|L3)\\d\\d\\d[A-Z]*\\b"))) {
return Tier::kLow;
}
// 2 cores, P-Core, Nehalem + Westmere
if (cores == 2 &&
// Arrandale (~2010)
(Search(model, "^Celeron (P4|U3)\\d\\d\\d[A-Z]*\\b") ||
Search(model, "^Pentium (P6|U5)\\d\\d\\d[A-Z]*\\b"))) {
return Tier::kLow;
}
// 2 cores, P-Core, Mobile Sandy Bridge + Mobile Ivy Bridge
if (cores == 2 &&
// Sandy Bridge-M (~2011)
(Search(model, "^Celeron (7|8|B7|B8)\\d\\d[A-Z]*\\b") ||
Search(model, "^Pentium (9|B9)\\d\\d[A-Z]*\\b") ||
// Ivy Bridge-M (~2012)
Search(model, "^Celeron (1)\\d\\d\\d[A-Z]*\\b") ||
Search(model, "^Pentium (2|A1)\\d\\d\\d[A-Z]*\\b"))) {
return Tier::kLow;
}
// 4 cores, E-Core, >= Gracemont
if (cores == 4 && (Search(model, "^N\\d\\d+\\b") ||
// Alder Lake-N (~2023)
Search(model, "^Atom x7425E\\b"))) {
return Tier::kHigh;
}
break;
default:
if (cores <= 2) {
// Any other manufacturer
return Tier::kLow;
}
break;
}
return Tier::kMid;
}
if (cores <= 10) {
switch (manufacturer) {
case Manufacturer::kApple:
// 8+ cores, M-series
if (cores >= 8 && Search(model, "^M\\d+\\b")) {
return Tier::kUltra;
}
break;
case Manufacturer::kIntel:
// 8+ cores, >= Meteor Lake
if (cores >= 8 && Search(model, "^Core Ultra\\b")) {
return Tier::kUltra;
}
break;
default:
break;
}
return Tier::kHigh;
}
return Tier::kUltra;
}
} // namespace content::cpu_performance