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
2 changes: 1 addition & 1 deletion dist/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/index.js.map

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
getNumCPUs,
pruneBuildkitCache,
logDatabaseHashes,
logBuildxDiskUsage,
} from "./setup_builder";
import {
installBuildKit,
Expand Down Expand Up @@ -444,6 +445,11 @@ void actionsToolkit.run(
core.info(JSON.stringify(builder, null, 2));
core.info("Blacksmith builder is ready for use by Docker");
});

// Log buildx disk usage to identify cached layers
await core.group(`Buildx disk usage`, async () => {
await logBuildxDiskUsage();
});
} else {
// Fallback to local builder
core.warning("Failed to setup Blacksmith builder, using local builder");
Expand Down
113 changes: 113 additions & 0 deletions src/setup_builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,119 @@ export async function logDatabaseHashes(label: string): Promise<void> {
}
}

/**
* Logs buildx disk usage to help identify cached layers.
* Uses a 5-second timeout to avoid blocking.
*/
export async function logBuildxDiskUsage(): Promise<void> {
try {
core.info("Checking buildx disk usage for cached layers:");

const { stdout } = await execAsync(
`timeout 5s docker buildx du --verbose 2>&1`,
);

const output = stdout.trim();
if (!output) {
core.info("No disk usage data available.");
return;
}

// Parse and format the output for better readability.
const lines = output.split("\n");
let totalSize = 0;
const cacheEntries: Array<{
id: string;
size: number;
shared: boolean;
description: string;
}> = [];

for (const line of lines) {
// Parse lines that contain cache IDs and sizes.
// Expected format: ID SIZE [SHARED] DESCRIPTION
const match = line.match(/^(\S+)\s+(\d+(?:\.\d+)?[KMGT]?B?)\s+(.*?)$/);
if (match) {
const [, id, sizeStr, description] = match;
const isShared = description.includes("shared");
Comment on lines +384 to +390
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parsing logic doesn't work as is.

On the version of buildx we install by default (v0.23.0), docker buildx du --verbose outputs

ID:		x4eh4xn1wx4s5ypq2qs0fmkva
Parent:		vvkxylkn6pgzuochbi3jp6d75
Created at:	2025-09-23 18:41:43.506943453 +0000 UTC
Mutable:	false
Reclaimable:	true
Shared:		false
Size:		359.2MB
Description:	pulled from docker.io/library/golang:1.21@sha256:4746d26432a9117a5f58e95cb9f954ddf0de128e9d5816886514199316e4a2fb
Usage count:	1
Last used:	2 weeks ago
Type:		regular

Starting from v0.29.0, it uses this template:
https://github.com/docker/buildx/blob/beaebcbf39b9f61cda762669083450c65470ec00/commands/diskusage.go#L38-L60


// Convert size to bytes for aggregation.
const sizeBytes = parseSizeToBytes(sizeStr);
totalSize += sizeBytes;

cacheEntries.push({
id: id,
size: sizeBytes,
shared: isShared,
description: description.trim(),
});
}
}

// Log summary.
if (cacheEntries.length > 0) {
core.info(`Found ${cacheEntries.length} cache entries`);
core.info(`Total cache size: ${formatBytes(totalSize)}`);

// Log all cache entries in build order (as returned by buildx du).
core.info("Cache entries in build order:");
for (const entry of cacheEntries) {
const sharedLabel = entry.shared ? " [SHARED]" : "";
core.info(
` ${entry.id}: ${formatBytes(entry.size)}${sharedLabel} - ${entry.description}`,
);
}
} else {
// If parsing failed, just log the raw output.
core.info("Raw disk usage output:");
core.info(output);
}
} catch (error) {
const execError = error as { code?: number; message?: string };
if (execError.code === 124) {
core.warning("buildx disk usage check timed out after 5 seconds.");
} else {
core.warning(
`Failed to get buildx disk usage: ${execError.message || "unknown error"}`,
);
}
}
}

/**
* Parses a size string (e.g., "1.5GB", "500MB") to bytes.
*/
function parseSizeToBytes(sizeStr: string): number {
const match = sizeStr.match(/^(\d+(?:\.\d+)?)\s*([KMGT]?B?)$/i);
if (!match) return 0;

const value = parseFloat(match[1]);
const unit = match[2].toUpperCase();

const multipliers: Record<string, number> = {
B: 1,
KB: 1024,
MB: 1024 * 1024,
GB: 1024 * 1024 * 1024,
TB: 1024 * 1024 * 1024 * 1024,
};

return value * (multipliers[unit] || 1);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Disk Size Parsing Fails Without 'B' Suffix

The parseSizeToBytes function's regex allows size units like 'K', 'M', 'G', or 'T' without a 'B' suffix, but the multipliers object only includes units with 'B' (e.g., 'KB'). This mismatch means sizes from docker buildx du output are incorrectly treated as bytes, leading to miscalculated disk usage.

Fix in Cursor Fix in Web


/**
* Formats bytes into a human-readable string.
*/
function formatBytes(bytes: number): string {
if (bytes === 0) return "0 B";

const units = ["B", "KB", "MB", "GB", "TB"];
const k = 1024;
const i = Math.floor(Math.log(bytes) / Math.log(k));

return `${(bytes / Math.pow(k, i)).toFixed(2)} ${units[i]}`;
}

// stickyDiskTimeoutMs states the max amount of time this action will wait for the VM agent to
// expose the sticky disk from the storage agent, map it onto the host and then patch the drive
// into the VM.
Expand Down