diff --git a/scripts/agent.mjs b/scripts/agent.mjs
index ca4e7bda731c..10cf71d866f9 100755
--- a/scripts/agent.mjs
+++ b/scripts/agent.mjs
@@ -13,6 +13,7 @@ import {
getAwsSecret,
getAzureSecret,
getCloud,
+ getCloudLaunchedInstanceType,
getCloudMetadataTag,
getDistro,
getDistroVersion,
@@ -383,6 +384,12 @@ async function doBuildkiteAgent(action, cliOptions = {}) {
tags[tag] = value;
}
}
+ // Deliberately not `instance-type`: that key is the type the job asked
+ // for (.buildkite/ci.mjs), and under capacity pressure this machine may
+ // be a different one. The runner reads this back from
+ // BUILDKITE_AGENT_META_DATA_LAUNCHED_INSTANCE_TYPE for the job log header
+ // and failure annotations.
+ tags["launched-instance-type"] = await getCloudLaunchedInstanceType(cloud);
}
options["tags"] = Object.entries(tags)
diff --git a/scripts/runner.node.mjs b/scripts/runner.node.mjs
index 9ef779b2ad96..2fc852e14f1e 100755
--- a/scripts/runner.node.mjs
+++ b/scripts/runner.node.mjs
@@ -43,6 +43,7 @@ import {
getBuildLabel,
getBuildMetadata,
getBuildUrl,
+ getCloudInstanceType,
getCommit,
getDistro,
getDistroVersion,
@@ -1131,7 +1132,7 @@ async function runTests() {
context: "flaky",
label: title,
style: "warning",
- content: `${title} - ${reason} (in the parallel batch on ${getBuildLabel()}; passed alone)
${detail} `,
+ content: `${title} - ${reason} (in the parallel batch on ${getTestLabel()}; passed alone)
${detail} `,
});
}
}
@@ -2789,10 +2790,19 @@ function addPath(...paths) {
}
/**
+ * The lane a failure is reported against, plus the machine type the job
+ * actually ran on when that is known, e.g. ":debian: 13 x64-asan (r7i.2xlarge)".
+ * The type is what tells a timeout on fallback hardware apart from one on the
+ * hardware the lane asks for, so it goes in the annotation next to the lane.
* @returns {string | undefined}
*/
function getTestLabel() {
- return getBuildLabel()?.replace(" - test-bun", "");
+ const label = getBuildLabel()?.replace(" - test-bun", "");
+ if (!label) {
+ return;
+ }
+ const instanceType = getCloudInstanceType();
+ return instanceType ? `${label} (${instanceType})` : label;
}
/**
diff --git a/scripts/utils.mjs b/scripts/utils.mjs
index a1a204c09e7b..1134170f2480 100755
--- a/scripts/utils.mjs
+++ b/scripts/utils.mjs
@@ -15,7 +15,15 @@ import {
writeFileSync,
} from "node:fs";
import { connect } from "node:net";
-import { hostname, homedir as nodeHomedir, tmpdir as nodeTmpdir, release, userInfo } from "node:os";
+import {
+ availableParallelism,
+ cpus,
+ hostname,
+ homedir as nodeHomedir,
+ tmpdir as nodeTmpdir,
+ release,
+ userInfo,
+} from "node:os";
import { basename, dirname, join, relative, resolve } from "node:path";
import { normalize as normalizeWindows } from "node:path/win32";
@@ -1870,6 +1878,79 @@ export function getPublicIp() {
}
}
+/**
+ * The CPU model and the number of CPUs this process may run on, e.g.
+ * "Intel(R) Xeon(R) Platinum 8488C x8" or "Apple M2 Ultra x24".
+ * @returns {string | undefined}
+ */
+export function getCpuDescription() {
+ const [cpu] = cpus();
+ const model = cpu?.model?.replace(/\s+/g, " ").trim();
+ if (!model) {
+ return;
+ }
+ return `${model} x${availableParallelism()}`;
+}
+
+/** @type {string | undefined} "" once looked up and not available */
+let cloudInstanceType;
+
+/**
+ * The instance type the cloud provider actually launched for this machine,
+ * e.g. "r7i.2xlarge" on EC2 or "Standard_D8s_v5" on Azure. CI asks for one
+ * type per lane (.buildkite/ci.mjs), but under capacity pressure the
+ * machine may be a different, slower type. scripts/agent.mjs records the
+ * launched type as the agent's `launched-instance-type` tag, which Buildkite
+ * exposes to the job as an environment variable. Looked up once per process.
+ * @returns {string | undefined}
+ */
+export function getCloudInstanceType() {
+ if (typeof cloudInstanceType !== "string") {
+ cloudInstanceType =
+ getEnv("BUILDKITE_AGENT_META_DATA_LAUNCHED_INSTANCE_TYPE", false) || fetchCloudInstanceType() || "";
+ }
+ return cloudInstanceType || undefined;
+}
+
+/**
+ * Images baked before agent.mjs set the tag only have the `cloud` tag, so
+ * ask the metadata service directly. Synchronous and bounded, since this
+ * runs while printing a log header.
+ * @returns {string | undefined}
+ */
+function fetchCloudInstanceType() {
+ // Without a `cloud` tag (the darwin fleet, GitHub Actions) this is not a
+ // machine whose type we chose, so there is nothing to compare against.
+ const cloud = getEnv("BUILDKITE_AGENT_META_DATA_CLOUD", false);
+ let request;
+ if (cloud === "aws") {
+ request = ["http://169.254.169.254/latest/meta-data/instance-type"];
+ } else if (cloud === "azure") {
+ request = [
+ "-H",
+ "Metadata: true",
+ "http://169.254.169.254/metadata/instance/compute/vmSize?api-version=2021-02-01&format=text",
+ ];
+ } else {
+ return;
+ }
+
+ const { error, stdout } = spawnSync([
+ "curl",
+ "-sf",
+ "--noproxy",
+ "*",
+ "--connect-timeout",
+ "1",
+ "--max-time",
+ "3",
+ ...request,
+ ]);
+ if (!error) {
+ return stdout.trim() || undefined;
+ }
+}
+
/**
* @returns {string}
*/
@@ -2266,6 +2347,34 @@ export async function getCloudMetadataTag(tag, cloud) {
return getCloudMetadata(metadata, cloud);
}
+/**
+ * The instance type this machine was launched as, e.g. "r7i.2xlarge" or
+ * "Standard_D8s_v5". agent.mjs tags the agent with it at start; jobs read it
+ * back through getCloudInstanceType().
+ * @param {Cloud} [cloud]
+ * @returns {Promise}
+ */
+export async function getCloudLaunchedInstanceType(cloud) {
+ cloud ??= await getCloud();
+
+ if (cloud === "azure") {
+ const body = await getCloudMetadata("", cloud);
+ if (!body) return;
+ try {
+ return JSON.parse(body)?.compute?.vmSize || undefined;
+ } catch {}
+ return;
+ }
+
+ const metadata = {
+ "aws": "instance-type",
+ // "projects//machineTypes/"
+ "google": "machine-type",
+ };
+
+ return (await getCloudMetadata(metadata, cloud))?.split("/").pop() || undefined;
+}
+
/**
* @typedef {Object} AwsCredentials
* @property {string} AccessKeyId
@@ -3095,8 +3204,10 @@ export function printEnvironment() {
}
console.log("Distro:", getDistro());
console.log("Distro Version:", getDistroVersion());
+ console.log("CPU:", getCpuDescription());
console.log("Hostname:", getHostname());
if (isCI) {
+ console.log("Instance Type:", getCloudInstanceType());
console.log("Tailscale IP:", getTailscaleIp());
console.log("Public IP:", getPublicIp());
}