From 3bfce72715ccddaabbd785032411fd0bc75a358f Mon Sep 17 00:00:00 2001 From: Rick Byers Date: Sat, 20 Jun 2026 09:12:46 -0400 Subject: [PATCH 1/9] Add reference impl, tests and demo Derived from chromium implementation at https://source.chromium.org/chromium/chromium/src/+/main:content/browser/cpu_performance/cpu_performance.cc?q=cpu_performance.cc&ss=chromium --- cpu_performance.js | 352 ++++++++++++++++++++++++++++++++++++++++ cpu_performance.test.js | 238 +++++++++++++++++++++++++++ demo.html | 287 ++++++++++++++++++++++++++++++++ 3 files changed, 877 insertions(+) create mode 100644 cpu_performance.js create mode 100644 cpu_performance.test.js create mode 100644 demo.html diff --git a/cpu_performance.js b/cpu_performance.js new file mode 100644 index 0000000..c0a99b4 --- /dev/null +++ b/cpu_performance.js @@ -0,0 +1,352 @@ +// JavaScript reference implementation of the CPU Performance API. +// +// This module is a direct translation of the Chromium C++ implementation in +// cpu_performance.cc. Its purpose is to make the classification rules +// described informally by the specification (index.bs) easy to inspect, +// reproduce, and test in isolation. The high-level shape of the API matches +// the specification: a tier value in the range 0..4, where 0 means +// "unknown" and 1..4 map to the four documented performance tiers. + +'use strict'; + +// Tier enum: numeric values match the specification's `navigator.cpuPerformance` +// return values exactly (0 = unknown, 1..4 = low..ultra). +const Tier = Object.freeze({ + UNKNOWN: 0, + LOW: 1, + MID: 2, + HIGH: 3, + ULTRA: 4, +}); + +const Manufacturer = Object.freeze({ + UNKNOWN: 'unknown', + AMD: 'amd', + APPLE: 'apple', + INTEL: 'intel', + MEDIATEK: 'mediatek', + MICROSOFT: 'microsoft', + QUALCOMM: 'qualcomm', + SAMSUNG: 'samsung', +}); + +function trimAndCollapseWhitespace(text) { + // \p{Z} matches unicode separators (including NBSP). + // \x1C\x1F match the file and unit separator characters. + return text + .replace(/^[\s\p{Z}\x1C\x1F]+/u, '') + .replace(/[\s\p{Z}\x1C\x1F]+$/u, '') + .replace(/[\s\p{Z}\x1C\x1F]+/gu, ' '); +} + +function getManufacturer(cpuModel) { + if (/\bAMD\b/i.test(cpuModel)) return Manufacturer.AMD; + if (/\bApple\b/i.test(cpuModel)) return Manufacturer.APPLE; + if (/\b(Intel|Celeron|Pentium)\b/i.test(cpuModel)) return Manufacturer.INTEL; + if (/\bMediaTek\b/i.test(cpuModel)) return Manufacturer.MEDIATEK; + if (/\bMicrosoft\b/i.test(cpuModel)) return Manufacturer.MICROSOFT; + if (/\b(Qualcomm|Snapdragon)\b/i.test(cpuModel)) return Manufacturer.QUALCOMM; + if (/\bSamsung\b/i.test(cpuModel)) return Manufacturer.SAMSUNG; + return Manufacturer.UNKNOWN; +} + +function splitCpuModel(cpuModel) { + let text = String(cpuModel == null ? '' : cpuModel); + + text = trimAndCollapseWhitespace(text); + + const manufacturer = getManufacturer(text); + + // Remove everything between parentheses. + text = text.replace(/\([^)]*\)/g, ' '); + + // Remove special characters. + text = text.replace(/\$|®|™/g, ' '); + + // Remove "GHz" patterns. + text = text.replace(/@( )?\d[.,]\d+([~\-]\d[.,]\d+)?( )?GHz\b/gi, ''); + text = text.replace(/\b\d[.,]\d+([~\-]\d[.,]\d+)?( )?GHz\b/gi, ''); + + text = trimAndCollapseWhitespace(text); + + // Remove trailing special characters. + text = text.replace(/(^| )?[@~\-,.]$/g, ''); + + // Remove filler words. + text = text.replace(/\bCPU\b/gi, ''); + text = text.replace(/\bMobile\b/gi, ''); + text = text.replace(/\bProcessor\b/gi, ''); + text = text.replace(/\bSilicon\b/gi, ''); + text = text.replace(/\bSOC\b/gi, ''); + text = text.replace(/\bTechnology\b/gi, ''); + + text = trimAndCollapseWhitespace(text); + + switch (manufacturer) { + case Manufacturer.AMD: + // ReplaceFirst: strip everything up to and including the first "AMD" token. + text = text.replace(/.*?\bAMD\b/i, ''); + text = trimAndCollapseWhitespace(text); + text = text.replace(/\bFX -/gi, 'FX-'); + text = text.replace(/\+( )?(AMD )?Radeon.*/gi, ''); + text = text.replace( + /\b(RADEON )?R\d+, \d+ COMPUTE CORES \d+C\+\d+G\b/gi, + '', + ); + text = text.replace(/\bwith (AMD )?Radeon.*/gi, ''); + text = text.replace(/\bw\/( )?(AMD )?Radeon.*/gi, ''); + text = text.replace(/\bRadeon.*/gi, ''); + + text = text.replace(/\b\w+( |-)Core\b/gi, ''); + text = text.replace(/\b\d+-Core(s)?\b/gi, ''); + + text = text.replace(/\bAPU\b/gi, ''); + text = text.replace(/\bCreator Edition\b/gi, ''); + text = text.replace(/\bDesktop Kit\b/gi, ''); + + text = text.replace(/\b(3250C) 15W\b/gi, '$1'); + break; + case Manufacturer.APPLE: + text = text.replace(/.*?\bApple\b/i, ''); + break; + case Manufacturer.INTEL: + text = text.replace(/.*?\bIntel\b/i, ''); + text = trimAndCollapseWhitespace(text); + text = text.replace(/\b(Core)(2)\b/gi, '$1 $2'); + text = text.replace(/\b(Core i\d+)( )?-( )?/gi, '$1-'); + text = text.replace(/\b(Core i\d+) (M) (\d+)\b/gi, '$1-$3$2'); + text = text.replace(/\b(Core i\d+) ([LQU]) (\d+)\b/gi, '$1-$3$2M'); + text = text.replace(/\b(Core i\d+) (\d+)\b/gi, '$1-$2'); + text = text.replace(/\b(Celeron|Pentium) Dual(-Core)?\b/gi, '$1'); + text = text.replace(/\b0+$/g, ''); + break; + default: + // For all other manufacturers including UNKNOWN, return an empty model. + return { manufacturer, model: '' }; + } + + text = trimAndCollapseWhitespace(text); + return { manufacturer, model: text }; +} + +function getTierFromCores(cores) { + if (cores >= 1 && cores <= 2) return Tier.LOW; + if (cores >= 3 && cores <= 4) return Tier.MID; + if (cores >= 5 && cores <= 12) return Tier.HIGH; + if (cores >= 13) return Tier.ULTRA; + return Tier.UNKNOWN; +} + +function getTierFromCpuInfo(cpuModel, cores) { + if (cores <= 0) return Tier.UNKNOWN; + if (cores <= 1) return Tier.LOW; + + const { manufacturer, model } = splitCpuModel(cpuModel); + const m = (re) => re.test(model); + + if (cores <= 4) { + switch (manufacturer) { + case Manufacturer.AMD: + // 2 cores, K8 + K10 + Mobile Trinity + if ( + cores === 2 && + (m(/^Athlon 64\b/) || + m(/^Athlon II\b/) || + m(/^Athlon X2\b/) || + m(/^Phenom II\b/) || + m(/^Sempron X2\b/) || + m(/^Turion II\b/) || + m(/^Turion X2\b/) || + // Llano (~2011) + m(/^(A4|E2)-[3]\d\d\d[A-Z]*\b/) || + // Trinity (~2012) + m(/^(A4|A6)-[4]\d\d\dM[A-Z]*\b/)) + ) { + return Tier.LOW; + } + // 2 cores, Bobcat + Jaguar + if ( + cores === 2 && + (m(/^(C|E|E1|E2|T|Z)-\w*\b/) || + m(/^(A4)-[1]\d\d\d[A-Z]*\b/) || + m(/^(GX)-[2]\d\d[A-Z]*\b/) || + m(/^Sempron 2650\b/)) + ) { + return Tier.LOW; + } + // 2 cores, Entry-Level Stoney Ridge 2019 6W Re-Release + if (cores === 2 && m(/^A4-9120[Ce]\b/)) { + return Tier.LOW; + } + // 4 cores, >= Zen + if ( + cores === 4 && + m(/^Ryzen\b/) && + // not 2 cores, Zen (~2017) + !m(/^Ryzen 3 Pro 2100GE\b/) && + !m(/^Ryzen 3 Pro 3050GE\b/) && + !m(/^Ryzen 3 2200U\b/) && + !m(/^Ryzen 3 3200U\b/) && + !m(/^Ryzen 3 3250[UC]\b/) && + !m(/^Ryzen Embedded R[1]\d\d\d[A-Z]*\b/) && + // not 2 cores, Zen+ (~2018) + !m(/^Ryzen Embedded R2312\b/) + ) { + return Tier.HIGH; + } + break; + case Manufacturer.INTEL: + // 2-4 cores, E-Core, Bonnell + Saltwell + if ( + cores >= 2 && cores <= 4 && + (// Silverthorne (~2008) + m(/^Atom (Z5)\d\d[A-Z]*\b/) || + // Diamondville (~2008) + m(/^Atom (2|3|N2)\d\d[A-Z]*\b/) || + // Pineview (~2010) + m(/^Atom (D4|N4|D5|N5)\d\d[A-Z]*\b/) || + // Tunnel Creek (~2010) / Stellarton (~2010) + m(/^Atom (E6)\d\d[A-Z]*\b/) || + // Lincroft (~2010) + m(/^Atom (Z6)\d\d[A-Z]*\b/) || + // Cedarview (~2011) + m(/^Atom (D2|N2)\d\d\d[A-Z]*\b/) || + // Penwell (~2012) + Cloverview (~2012) + m(/^Atom (Z2)\d\d\d[A-Z]*\b/)) + ) { + return Tier.LOW; + } + // 2-4 cores, E-Core, Silvermont + Airmont + if ( + cores >= 2 && cores <= 4 && + (// Bay Trail-D (~2013) + m(/^Celeron (J1)\d\d\d[A-Z]*\b/) || + m(/^Pentium (J2)\d\d\d[A-Z]*\b/) || + // Avoton (~2013) / Rangeley (~2013) + m(/^Atom (C2)[35]\d\d[A-Z]*\b/) || + // Bay Trail-I (~2013) + m(/^Atom (E3)[8]\d\d[A-Z]*\b/) || + // Bay Trail-M (~2013) + m(/^Celeron (N2)\d\d\d[A-Z]*\b/) || + m(/^Pentium (N3)[5]\d\d[A-Z]*\b/) || + // Bay Trail-T (~2013) + Merrifield (~2014) + Moorefield (~2014) + m(/^Atom (Z3)\d\d\d[A-Z]*\b/) || + // Braswell (~2016) + m(/^Atom x[5]-(E8)\d\d\d[A-Z]*\b/) || + m(/^Celeron (J3|N3)[01]\d\d[A-Z]*\b/) || + m(/^Pentium (J3|N3)[7]\d\d[A-Z]*\b/) || + // Cherry Trail-T (~2015) + m(/^Atom x[57]-(Z8)\d\d\d[A-Z]*\b/)) + ) { + return Tier.LOW; + } + // 2 cores, E-Core, Goldmont + if ( + cores === 2 && + (// Apollo Lake (~2016) + m(/^Atom x[5]-(A3|E3)\d\d\d[A-Z]*\b/) || + m(/^Celeron (J3|N3)[34]\d\d[A-Z]*\b/) || + // Denverton (~2017) + m(/^Atom (C3)\d\d\d[A-Z]*\b/)) + ) { + return Tier.LOW; + } + // 2 cores, P-Core, Merom + Penryn + if ( + cores === 2 && + (// Merom (~2006) + Conroe (~2006) + Penryn (~2007) + m(/^Celeron (E1|SU2|T1|T3)\d\d\d[A-Z]*\b/) || + m(/^Core 2 Duo\b/) || + m(/^Core 2 Extreme\b/) || + m(/^Pentium (E2|SU2|SU4|T2|T3|T4)\d\d\d[A-Z]*\b/) || + m(/^Xeon (3|E3|L3)\d\d\d[A-Z]*\b/)) + ) { + return Tier.LOW; + } + // 2 cores, P-Core, Nehalem + Westmere + if ( + cores === 2 && + (// Arrandale (~2010) + m(/^Celeron (P4|U3)\d\d\d[A-Z]*\b/) || + m(/^Pentium (P6|U5)\d\d\d[A-Z]*\b/)) + ) { + return Tier.LOW; + } + // 2 cores, P-Core, Mobile Sandy Bridge + Mobile Ivy Bridge + if ( + cores === 2 && + (// Sandy Bridge-M (~2011) + m(/^Celeron (7|8|B7|B8)\d\d[A-Z]*\b/) || + m(/^Pentium (9|B9)\d\d[A-Z]*\b/) || + // Ivy Bridge-M (~2012) + m(/^Celeron (1)\d\d\d[A-Z]*\b/) || + m(/^Pentium (2|A1)\d\d\d[A-Z]*\b/)) + ) { + return Tier.LOW; + } + // 4 cores, E-Core, >= Gracemont + if ( + cores === 4 && + (m(/^N\d\d+\b/) || + // Alder Lake-N (~2023) + m(/^Atom x7425E\b/)) + ) { + return Tier.HIGH; + } + break; + default: + // Any other manufacturer with at most 2 cores is treated as Low. + if (cores <= 2) return Tier.LOW; + break; + } + return Tier.MID; + } + + if (cores <= 10) { + switch (manufacturer) { + case Manufacturer.APPLE: + // 8+ cores, M-series + if (cores >= 8 && m(/^M\d+\b/)) return Tier.ULTRA; + break; + case Manufacturer.INTEL: + // 8+ cores, >= Meteor Lake + if (cores >= 8 && m(/^Core Ultra\b/)) return Tier.ULTRA; + break; + default: + break; + } + return Tier.HIGH; + } + + return Tier.ULTRA; +} + +// Spec-shaped accessor. Returns an object that exposes a `cpuPerformance` +// getter behaving like `navigator.cpuPerformance` as defined in index.bs: +// a non-negative integer in the range 0..4 (or higher in future revisions). +function createNavigatorCpuPerformance({ cpuModel = '', cores = 0 } = {}) { + const tier = getTierFromCpuInfo(cpuModel, cores); + return Object.freeze({ + get cpuPerformance() { + return tier; + }, + }); +} + +const exports_ = { + Tier, + Manufacturer, + trimAndCollapseWhitespace, + getManufacturer, + splitCpuModel, + getTierFromCores, + getTierFromCpuInfo, + createNavigatorCpuPerformance, +}; + +// Support both Node.js (require) and a plain browser + + + From adf324b9e9c88a99cef0e5441703d55904ddd7dc Mon Sep 17 00:00:00 2001 From: Rick Byers Date: Sat, 20 Jun 2026 09:37:11 -0400 Subject: [PATCH 2/9] Update demo with more examples Add the version of the chromium source we're using --- cpu_performance_chromium.cc | 406 ++++++++++++++++++++++++++++++++++++ demo.html | 294 ++++++++++++++++++++++---- 2 files changed, 655 insertions(+), 45 deletions(-) create mode 100644 cpu_performance_chromium.cc diff --git a/cpu_performance_chromium.cc b/cpu_performance_chromium.cc new file mode 100644 index 0000000..78e66e4 --- /dev/null +++ b/cpu_performance_chromium.cc @@ -0,0 +1,406 @@ +// 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 + +#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(Tier::kUnknown), value); + CHECK_GE(static_cast(Tier::kUltra), value); + return static_cast(value); +} + +std::pair 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 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 diff --git a/demo.html b/demo.html index 4fb46a1..1ec2212 100644 --- a/demo.html +++ b/demo.html @@ -75,23 +75,22 @@ border-radius: 6px; padding: 0.5rem 0.65rem; } - input:focus { outline: 2px solid var(--accent); outline-offset: 1px; } + input:focus, select:focus { + outline: 2px solid var(--accent); + outline-offset: 1px; + } .presets { - display: flex; - flex-wrap: wrap; - gap: 0.4rem; margin-top: 1rem; } - .presets button { + select { + width: 100%; font: inherit; color: inherit; background: var(--bg); border: 1px solid var(--border); - border-radius: 999px; - padding: 0.3rem 0.75rem; - cursor: pointer; + border-radius: 6px; + padding: 0.5rem 0.65rem; } - .presets button:hover { border-color: var(--accent); } .result-grid { display: grid; grid-template-columns: 160px 1fr; @@ -167,8 +166,11 @@

CPU Performance API Playground

-
- +
+ +
@@ -189,12 +191,6 @@

CPU Performance API Playground

0 — Unknown
- - @@ -211,21 +207,198 @@

CPU Performance API Playground

const TIER_NAMES = ['Unknown', 'Low', 'Mid', 'High', 'Ultra']; - const PRESETS = [ - { model: 'Apple M2', cores: 8 }, - { model: 'Apple M3 Max', cores: 14 }, - { model: 'Intel(R) Core(TM) Ultra 7 155H', cores: 8 }, - { model: 'Intel(R) Core(TM) i7-1185G7 CPU @ 3.00GHz', cores: 8 }, - { model: 'Intel(R) Core(TM) i5-10400 CPU @ 2.90GHz', cores: 6 }, - { model: 'Intel(R) Pentium(R) CPU N3540 @ 2.16GHz', cores: 4 }, - { model: 'Intel(R) Atom(TM) CPU Z520 @ 1.33GHz', cores: 2 }, - { model: 'Intel N100', cores: 4 }, - { model: 'AMD Ryzen 9 5950X 16-Core Processor', cores: 16 }, - { model: 'AMD Ryzen 5 5600X 6-Core Processor', cores: 6 }, - { model: 'AMD Ryzen 3 2200U', cores: 4 }, - { model: 'AMD A4-9120e', cores: 2 }, - { model: 'Qualcomm Snapdragon 8cx Gen 3', cores: 8 }, - { model: 'Microsoft SQ3', cores: 8 }, + // Grouped presets — used to populate elements. + // Device groups (real-world phones, tablets, laptops) come first; the + // CPU-family groups below are useful for poking at individual edge + // cases in the classifier. + const PRESET_GROUPS = [ + { + group: 'Phones — flagship', + items: [ + { device: 'iPhone 16 Pro (2024)', model: 'Apple A18 Pro', cores: 6 }, + { device: 'iPhone 15 Pro (2023)', model: 'Apple A17 Pro', cores: 6 }, + { device: 'iPhone 14 Pro (2022)', model: 'Apple A16 Bionic', cores: 6 }, + { device: 'iPhone 13 (2021)', model: 'Apple A15 Bionic', cores: 6 }, + { device: 'Samsung Galaxy S24 Ultra (US, 2024)', model: 'Qualcomm Snapdragon 8 Gen 3 for Galaxy', cores: 8 }, + { device: 'Samsung Galaxy S24 (International, 2024)', model: 'Samsung Exynos 2400', cores: 10 }, + { device: 'Samsung Galaxy S23 Ultra (2023)', model: 'Qualcomm Snapdragon 8 Gen 2 for Galaxy', cores: 8 }, + { device: 'Samsung Galaxy S22 (US, 2022)', model: 'Qualcomm Snapdragon 8 Gen 1', cores: 8 }, + { device: 'Google Pixel 8 Pro (2023)', model: 'Google Tensor G3', cores: 9 }, + { device: 'OnePlus 12 (2024)', model: 'Qualcomm Snapdragon 8 Gen 3', cores: 8 }, + { device: 'Xiaomi 14 (2024)', model: 'Qualcomm Snapdragon 8 Gen 3', cores: 8 }, + { device: 'Vivo X100 Pro (India, 2023)', model: 'MediaTek Dimensity 9300', cores: 8 }, + { device: 'iQOO 12 (India, 2023)', model: 'Qualcomm Snapdragon 8 Gen 3', cores: 8 }, + { device: 'Realme GT Neo 5 (2023)', model: 'Qualcomm Snapdragon 8+ Gen 1', cores: 8 }, + ], + }, + { + group: 'Phones — mid-range / budget', + items: [ + { device: 'iPhone SE 3rd gen (2022)', model: 'Apple A15 Bionic', cores: 6 }, + { device: 'Samsung Galaxy A54 5G (2023)', model: 'Samsung Exynos 1380', cores: 8 }, + { device: 'Samsung Galaxy A14 (2023)', model: 'MediaTek Helio G80', cores: 8 }, + { device: 'Google Pixel 6a (2022)', model: 'Google Tensor', cores: 8 }, + { device: 'Redmi Note 13 Pro (India, 2024)', model: 'Qualcomm Snapdragon 7s Gen 2', cores: 8 }, + { device: 'Redmi 9 (India, 2020)', model: 'MediaTek Helio G80', cores: 8 }, + { device: 'Redmi 9A (India, 2020)', model: 'MediaTek Helio G25', cores: 8 }, + { device: 'Oppo Reno 11 (India, 2024)', model: 'MediaTek Dimensity 7050', cores: 8 }, + { device: 'Motorola Edge 50 (2024)', model: 'MediaTek Dimensity 7300', cores: 8 }, + { device: 'Moto G Power (2022)', model: 'MediaTek Helio G37', cores: 8 }, + { device: 'Nokia G42 5G (2023)', model: 'Qualcomm Snapdragon 480+', cores: 8 }, + ], + }, + { + group: 'Phones — older / legacy', + items: [ + { device: 'iPhone 6s (2015)', model: 'Apple A9', cores: 2 }, + { device: 'iPhone 7 (2016)', model: 'Apple A10 Fusion', cores: 4 }, + { device: 'iPhone X (2017)', model: 'Apple A11 Bionic', cores: 6 }, + { device: 'iPhone XS (2018)', model: 'Apple A12 Bionic', cores: 6 }, + { device: 'iPhone 11 (2019)', model: 'Apple A13 Bionic', cores: 6 }, + { device: 'Samsung Galaxy S8 (US, 2017)', model: 'Qualcomm Snapdragon 835', cores: 8 }, + { device: 'Samsung Galaxy S8 (International, 2017)', model: 'Samsung Exynos 8895', cores: 8 }, + { device: 'Samsung Galaxy S10 (2019)', model: 'Qualcomm Snapdragon 855', cores: 8 }, + { device: 'Samsung Galaxy Note 8 (2017)', model: 'Qualcomm Snapdragon 835', cores: 8 }, + { device: 'Samsung Galaxy J7 (2016)', model: 'Samsung Exynos 7580', cores: 8 }, + { device: 'OnePlus 6 (2018)', model: 'Qualcomm Snapdragon 845', cores: 8 }, + { device: 'Google Nexus 5 (2013)', model: 'Qualcomm Snapdragon 800', cores: 4 }, + ], + }, + { + group: 'Tablets', + items: [ + { device: 'iPad Pro M4 (2024, 1TB+)', model: 'Apple M4', cores: 10 }, + { device: 'iPad Air M2 (2024)', model: 'Apple M2', cores: 8 }, + { device: 'iPad (10th gen, 2022)', model: 'Apple A14 Bionic', cores: 6 }, + { device: 'iPad mini 6 (2021)', model: 'Apple A15 Bionic', cores: 6 }, + { device: 'iPad (9th gen, 2021)', model: 'Apple A13 Bionic', cores: 6 }, + { device: 'Samsung Galaxy Tab S9 (2023)', model: 'Qualcomm Snapdragon 8 Gen 2 for Galaxy', cores: 8 }, + { device: 'Samsung Galaxy Tab A9+ (2023)', model: 'Qualcomm Snapdragon 695', cores: 8 }, + { device: 'Amazon Fire HD 10 (2023)', model: 'MediaTek Helio P60T', cores: 8 }, + ], + }, + { + group: 'Laptops — Apple', + items: [ + { device: 'MacBook Pro 16" M3 Max (2023)', model: 'Apple M3 Max', cores: 16 }, + { device: 'MacBook Pro 14" M3 Pro (2023)', model: 'Apple M3 Pro', cores: 12 }, + { device: 'MacBook Air M2 (2022)', model: 'Apple M2', cores: 8 }, + { device: 'MacBook Air M1 (2020)', model: 'Apple M1', cores: 8 }, + { device: 'MacBook Pro 13" Intel (2020)', model: 'Intel(R) Core(TM) i5-1038NG7 CPU @ 2.00GHz', cores: 8 }, + ], + }, + { + group: 'Laptops — Windows / Chromebook', + items: [ + { device: 'Dell XPS 15 (2023)', model: 'Intel(R) Core(TM) i7-13700H', cores: 20 }, + { device: 'Dell XPS 13 (2023)', model: 'Intel(R) Core(TM) i7-1360P', cores: 16 }, + { device: 'HP Spectre x360 14 (2024)', model: 'Intel(R) Core(TM) Ultra 7 155H', cores: 16 }, + { device: 'Lenovo ThinkPad X1 Carbon Gen 11 (2023)', model: 'Intel(R) Core(TM) i7-1365U', cores: 12 }, + { device: 'Lenovo Legion Pro 7i (2023)', model: 'Intel(R) Core(TM) i9-13900HX', cores: 24 }, + { device: 'ASUS ROG Zephyrus G14 (2023)', model: 'AMD Ryzen 9 7940HS with Radeon Graphics', cores: 16 }, + { device: 'Framework Laptop 13 (AMD, 2023)', model: 'AMD Ryzen 7 7840U with Radeon Graphics', cores: 16 }, + { device: 'Microsoft Surface Pro 9 (2022)', model: 'Intel(R) Core(TM) i7-1255U', cores: 10 }, + { device: 'Microsoft Surface Laptop 7 (2024, Copilot+)', model: 'Qualcomm Snapdragon X Elite', cores: 12 }, + { device: 'Microsoft Surface Pro X (2019)', model: 'Microsoft SQ1', cores: 8 }, + { device: 'Acer Chromebook 314 (2023)', model: 'Intel N100', cores: 4 }, + { device: 'HP Chromebook 11 G4 (2015)', model: 'Intel(R) Celeron(R) CPU N2840 @ 2.16GHz', cores: 2 }, + { device: 'Lenovo IdeaPad 1 (2022)', model: 'AMD Athlon Silver 3050U with Radeon Graphics', cores: 2 }, + ], + }, + { + group: 'Apple Silicon (Macs)', + items: [ + { model: 'Apple M1', cores: 8 }, + { model: 'Apple M1 Pro', cores: 10 }, + { model: 'Apple M2', cores: 8 }, + { model: 'Apple M2 Pro', cores: 10 }, + { model: 'Apple M3', cores: 8 }, + { model: 'Apple M3 Max', cores: 14 }, + { model: 'Apple M4 Max', cores: 16 }, + ], + }, + { + group: 'Intel — Core Ultra', + items: [ + { model: 'Intel(R) Core(TM) Ultra 5 125H', cores: 8 }, + { model: 'Intel(R) Core(TM) Ultra 7 155H', cores: 8 }, + { model: 'Intel(R) Core(TM) Ultra 9 285K', cores: 24 }, + ], + }, + { + group: 'Intel — Core i', + items: [ + { model: 'Intel(R) Core(TM) i3-1115G4 CPU @ 3.00GHz', cores: 2 }, + { model: 'Intel(R) Core(TM) i5-10400 CPU @ 2.90GHz', cores: 6 }, + { model: 'Intel(R) Core(TM) i7-1185G7 CPU @ 3.00GHz', cores: 8 }, + { model: 'Intel(R) Core(TM) i7-10750H CPU @ 2.60GHz', cores: 12 }, + { model: 'Intel(R) Core(TM) i9-13900K CPU @ 3.00GHz', cores: 24 }, + { model: 'Intel(R) Core(TM)2 Duo CPU T7700 @ 2.40GHz', cores: 2 }, + { model: 'Intel(R) Core(TM)2 Quad CPU Q9400 @ 2.66GHz', cores: 4 }, + ], + }, + { + group: 'Intel — Atom / Pentium / Celeron', + items: [ + { model: 'Intel(R) Atom(TM) CPU Z520 @ 1.33GHz', cores: 2 }, + { model: 'Intel(R) Atom(TM) CPU N270 @ 1.60GHz', cores: 2 }, + { model: 'Intel(R) Atom(TM) CPU D525 @ 1.80GHz', cores: 4 }, + { model: 'Intel(R) Atom(TM) x5-Z8350 CPU @ 1.44GHz', cores: 4 }, + { model: 'Intel(R) Atom(TM) x7425E', cores: 4 }, + { model: 'Intel(R) Celeron(R) CPU N2840 @ 2.16GHz', cores: 2 }, + { model: 'Intel(R) Celeron(R) CPU J3455 @ 1.50GHz', cores: 4 }, + { model: 'Intel(R) Pentium(R) CPU N3540 @ 2.16GHz', cores: 4 }, + { model: 'Intel(R) Pentium(R) Silver N6005', cores: 4 }, + { model: 'Intel N100', cores: 4 }, + { model: 'Intel N305', cores: 8 }, + ], + }, + { + group: 'AMD — Ryzen', + items: [ + { model: 'AMD Ryzen 3 2200U', cores: 4 }, + { model: 'AMD Ryzen 3 3250U', cores: 4 }, + { model: 'AMD Ryzen 5 2400G', cores: 4 }, + { model: 'AMD Ryzen 5 5600X 6-Core Processor', cores: 6 }, + { model: 'AMD Ryzen 7 5800H with Radeon Graphics', cores: 8 }, + { model: 'AMD Ryzen 9 5950X 16-Core Processor', cores: 16 }, + { model: 'AMD Ryzen 9 7950X3D 16-Core Processor', cores: 16 }, + { model: 'AMD Ryzen Threadripper PRO 5995WX', cores: 64 }, + ], + }, + { + group: 'AMD — legacy / low-power', + items: [ + { model: 'AMD A4-9120e', cores: 2 }, + { model: 'AMD A6-4400M APU with Radeon HD Graphics', cores: 2 }, + { model: 'AMD E1-2500 APU with Radeon HD Graphics', cores: 2 }, + { model: 'AMD Athlon 64 X2', cores: 2 }, + { model: 'AMD FX-8350 Eight-Core Processor', cores: 8 }, + ], + }, + { + group: 'Qualcomm / Microsoft / MediaTek / Samsung', + items: [ + { model: 'Qualcomm Snapdragon 7c Gen 2', cores: 8 }, + { model: 'Qualcomm Snapdragon 8cx Gen 3', cores: 8 }, + { model: 'Qualcomm Snapdragon X Elite', cores: 12 }, + { model: 'Microsoft SQ1', cores: 8 }, + { model: 'Microsoft SQ3', cores: 8 }, + { model: 'MediaTek Kompanio 1200', cores: 8 }, + { model: 'Samsung Exynos 2200', cores: 8 }, + ], + }, + { + group: 'Cores-only fallback (unknown manufacturer)', + items: [ + { model: 'Generic CPU', cores: 1 }, + { model: 'Generic CPU', cores: 2 }, + { model: 'Generic CPU', cores: 4 }, + { model: 'Generic CPU', cores: 8 }, + { model: 'Generic CPU', cores: 16 }, + { model: '', cores: 0 }, + ], + }, ]; const modelInput = document.getElementById('model'); @@ -235,7 +408,7 @@

CPU Performance API Playground

const manufacturerEl = document.getElementById('manufacturer'); const normalizedEl = document.getElementById('normalized'); const coresTierEl = document.getElementById('cores-tier'); - const presetsEl = document.getElementById('presets'); + const presetSelect = document.getElementById('preset'); function tierLabel(tier) { return tier >= 0 && tier < TIER_NAMES.length @@ -262,20 +435,51 @@

CPU Performance API Playground

String(coresTier) + ' — ' + tierLabel(coresTier); } - for (const preset of PRESETS) { - const btn = document.createElement('button'); - btn.type = 'button'; - btn.textContent = preset.model + ' · ' + preset.cores + 'c'; - btn.addEventListener('click', () => { - modelInput.value = preset.model; - coresInput.value = String(preset.cores); - update(); - }); - presetsEl.appendChild(btn); + const placeholder = document.createElement('option'); + placeholder.value = ''; + placeholder.textContent = 'Select an example…'; + presetSelect.appendChild(placeholder); + + const presetIndex = []; + for (const { group, items } of PRESET_GROUPS) { + const og = document.createElement('optgroup'); + og.label = group; + for (const preset of items) { + const opt = document.createElement('option'); + opt.value = String(presetIndex.length); + const cpuLabel = + (preset.model || '(empty)') + ' · ' + preset.cores + 'c'; + opt.textContent = preset.device + ? preset.device + ' — ' + cpuLabel + : cpuLabel; + og.appendChild(opt); + presetIndex.push(preset); + } + presetSelect.appendChild(og); } - modelInput.addEventListener('input', update); - coresInput.addEventListener('input', update); + presetSelect.addEventListener('change', () => { + const idx = Number(presetSelect.value); + if (!Number.isInteger(idx) || idx < 0 || idx >= presetIndex.length) { + return; + } + const preset = presetIndex[idx]; + modelInput.value = preset.model; + coresInput.value = String(preset.cores); + update(); + }); + + function clearPresetSelection() { + presetSelect.value = ''; + } + modelInput.addEventListener('input', () => { + clearPresetSelection(); + update(); + }); + coresInput.addEventListener('input', () => { + clearPresetSelection(); + update(); + }); // Initialise with a reasonable default so the page isn't empty. modelInput.value = 'Intel(R) Core(TM) Ultra 7 155H'; From a81e889b393fb1f6895e207863b8058b66da6859 Mon Sep 17 00:00:00 2001 From: Rick Byers Date: Sat, 20 Jun 2026 16:03:40 -0400 Subject: [PATCH 3/9] Add real call to cpuPerformance --- demo.html | 78 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 47 insertions(+), 31 deletions(-) diff --git a/demo.html b/demo.html index 1ec2212..b0188d5 100644 --- a/demo.html +++ b/demo.html @@ -3,6 +3,11 @@ + + + + + CPU Performance API — Reference Implementation Playground