Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions scripts/agent.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
getAwsSecret,
getAzureSecret,
getCloud,
getCloudLaunchedInstanceType,
getCloudMetadataTag,
getDistro,
getDistroVersion,
Expand Down Expand Up @@ -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)
Expand Down
14 changes: 12 additions & 2 deletions scripts/runner.node.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import {
getBuildLabel,
getBuildMetadata,
getBuildUrl,
getCloudInstanceType,
getCommit,
getDistro,
getDistroVersion,
Expand Down Expand Up @@ -1131,7 +1132,7 @@ async function runTests() {
context: "flaky",
label: title,
style: "warning",
content: `<details><summary><a href="${getFileUrl(title)}"><code>${title}</code></a> - ${reason} <i>(in the parallel batch on ${getBuildLabel()}; passed alone)</i></summary>${detail}</details>`,
content: `<details><summary><a href="${getFileUrl(title)}"><code>${title}</code></a> - ${reason} <i>(in the parallel batch on ${getTestLabel()}; passed alone)</i></summary>${detail}</details>`,
});
}
}
Expand Down Expand Up @@ -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;
}

/**
Expand Down
113 changes: 112 additions & 1 deletion scripts/utils.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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}
*/
Expand Down Expand Up @@ -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<string | undefined>}
*/
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/<number>/machineTypes/<type>"
"google": "machine-type",
};

return (await getCloudMetadata(metadata, cloud))?.split("/").pop() || undefined;
}

/**
* @typedef {Object} AwsCredentials
* @property {string} AccessKeyId
Expand Down Expand Up @@ -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());
}
Expand Down