diff --git a/.github/actions/build-electron/action.yml b/.github/actions/build-electron/action.yml index 7b278470f5c8..7224cb23e760 100644 --- a/.github/actions/build-electron/action.yml +++ b/.github/actions/build-electron/action.yml @@ -63,6 +63,13 @@ runs: NINJA_SUMMARIZE_BUILD=1 e build cp out/Default/.ninja_log out/electron_ninja_log node electron/script/check-symlinks.js + + # Upload build stats to Datadog + if ! [ -z $DD_API_KEY ]; then + npx node electron/script/build-stats.mjs out/Default/siso.INFO --upload-stats || true + else + echo "Skipping build-stats.mjs upload because DD_API_KEY is not set" + fi - name: Build Electron dist.zip ${{ inputs.step-suffix }} shell: bash run: | diff --git a/.github/workflows/pipeline-segment-electron-build.yml b/.github/workflows/pipeline-segment-electron-build.yml index b634e2b67570..23ee025b6c51 100644 --- a/.github/workflows/pipeline-segment-electron-build.yml +++ b/.github/workflows/pipeline-segment-electron-build.yml @@ -66,6 +66,7 @@ concurrency: env: CHROMIUM_GIT_COOKIE: ${{ secrets.CHROMIUM_GIT_COOKIE }} CHROMIUM_GIT_COOKIE_WINDOWS_STRING: ${{ secrets.CHROMIUM_GIT_COOKIE_WINDOWS_STRING }} + DD_API_KEY: ${{ secrets.DD_API_KEY }} ELECTRON_ARTIFACTS_BLOB_STORAGE: ${{ secrets.ELECTRON_ARTIFACTS_BLOB_STORAGE }} ELECTRON_RBE_JWT: ${{ secrets.ELECTRON_RBE_JWT }} SUDOWOODO_EXCHANGE_URL: ${{ secrets.SUDOWOODO_EXCHANGE_URL }} @@ -84,6 +85,7 @@ jobs: environment: ${{ inputs.environment }} env: TARGET_ARCH: ${{ inputs.target-arch }} + TARGET_PLATFORM: ${{ inputs.target-platform }} steps: - name: Create src dir run: | diff --git a/script/build-stats.mjs b/script/build-stats.mjs new file mode 100644 index 000000000000..8079f3456bf9 --- /dev/null +++ b/script/build-stats.mjs @@ -0,0 +1,96 @@ +import * as fs from 'node:fs/promises'; +import { fileURLToPath } from 'node:url'; +import { parseArgs } from 'node:util'; + +async function main () { + const { positionals: [filename], values: { 'upload-stats': uploadStats } } = parseArgs({ + allowPositionals: true, + options: { + 'upload-stats': { + type: 'boolean', + default: false + } + } + }); + + if (!filename) { + throw new Error('filename is required (should be a siso.INFO file)'); + } + + const log = await fs.readFile(filename, 'utf-8'); + + // We expect to find a line which looks like stats=build.Stats{..., CacheHit:39008, Local:4778, Remote:0, LocalFallback:0, ...} + const match = log.match(/stats=build\.Stats{(.*)}/); + + if (!match) { + throw new Error('could not find stats=build.Stats in log'); + } + + const stats = Object.fromEntries(match[1].split(',').map(part => { + const [key, value] = part.trim().split(':'); + return [key, parseInt(value)]; + })); + const hitRate = stats.CacheHit / (stats.Remote + stats.CacheHit + stats.LocalFallback); + + console.log(`Effective cache hit rate: ${(hitRate * 100).toFixed(2)}%`); + + if (uploadStats) { + if (!process.env.DD_API_KEY) { + throw new Error('DD_API_KEY is not set'); + } + + const timestamp = Math.round(new Date().getTime() / 1000); + + const tags = []; + + if (process.env.TARGET_ARCH) tags.push(`target-arch:${process.env.TARGET_ARCH}`); + if (process.env.TARGET_PLATFORM) tags.push(`target-platform:${process.env.TARGET_PLATFORM}`); + if (process.env.GITHUB_HEAD_REF) { + // Will be set in pull requests + tags.push(`branch:${process.env.GITHUB_HEAD_REF}`); + } else if (process.env.GITHUB_REF_NAME) { + // Will be set for release branches + tags.push(`branch:${process.env.GITHUB_REF_NAME}`); + } + + const series = [ + { + metric: 'electron.build.effective-cache-hit-rate', + points: [{ timestamp, value: (hitRate * 100).toFixed(2) }], + type: 3, // GAUGE + unit: 'percent', + tags + } + ]; + + // Add all raw stats as individual metrics + for (const [key, value] of Object.entries(stats)) { + series.push({ + metric: `electron.build.stats.${key.toLowerCase()}`, + points: [{ timestamp, value }], + type: 1, // COUNT + tags + }); + } + + await fetch('https://api.datadoghq.com/api/v2/series', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'DD-API-KEY': process.env.DD_API_KEY + }, + body: JSON.stringify({ series }) + }); + } +} + +if ((await fs.realpath(process.argv[1])) === fileURLToPath(import.meta.url)) { + main() + .then(() => { + process.exit(0); + }) + .catch((err) => { + console.error(`ERROR: ${err.message}`); + process.exit(1); + }); +}