From d790a9f9de18a145df3d8b4df82eae75f0747954 Mon Sep 17 00:00:00 2001 From: andrew clark Date: Wed, 26 Nov 2025 16:27:27 -0700 Subject: [PATCH] Automated Perfetto UI Notifications (#3255) * Testing visualization generation * Update Jenkinsfile * Update Jenkinsfile * Update Jenkinsfile * Update Jenkinsfile * Update Jenkinsfile * Update Jenkinsfile * Update Jenkinsfile * Update Jenkinsfile * Update Jenkinsfile * Update Jenkinsfile * Update Jenkinsfile * Update Jenkinsfile * Adding dummy test data * Update Jenkinsfile * Update Jenkinsfile * Adding notifications * Testing * Update Jenkinsfile * Update Jenkinsfile * Update Jenkinsfile * Update Jenkinsfile * Update Jenkinsfile * Image compression * Update Jenkinsfile * Moving capture logic to main Jenkins file * Testing generation * Update Jenkinsfile * Update Jenkinsfile * Update Jenkinsfile * Update Jenkinsfile * Update Jenkinsfile * Update Jenkinsfile * Update Jenkinsfile * Update Jenkinsfile * Update Jenkinsfile * Fixing curl request * Update Jenkinsfile * Clean up * Fix * Fixing notification * Testing message creation * Adjusting message payload * Testing notification generation * Updating main jenkinsfile * Fixing cleanup call * Removing test pipeline code * Comment clean up * Testing pipeline * Update Jenkinsfile * Update Jenkinsfile * Update Jenkinsfile * Moving archive Moving trace archive to safe location before source checkout * Removing test pipeline * Testing pipeline with unique file names * Update Jenkinsfile * Removing test files Updated main pipeline [ROCm/composable_kernel commit: 40d7217ac7144dcc65436d29333d3daf61963467] --- Jenkinsfile | 132 +++++++++++++++++++++ script/infra_helper/capture_build_trace.js | 53 +++++++++ 2 files changed, 185 insertions(+) create mode 100644 script/infra_helper/capture_build_trace.js diff --git a/Jenkinsfile b/Jenkinsfile index f3e690edd7..c79b8f18e1 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -72,6 +72,129 @@ def sendFailureNotifications() { } } +def generateAndArchiveBuildTraceVisualization() { + try { + def buildTraceFileName = "ck_build_trace.json"; + + // Attempt to download the build trace file to check if it exists + def traceFileExists = false + try { + copyArtifacts( + projectName: env.JOB_NAME, + selector: specific(env.BUILD_NUMBER), + filter: buildTraceFileName + ) + traceFileExists = fileExists(buildTraceFileName) + } catch (Exception e) { + echo "Could not copy artifacts: ${e.getMessage()}" + traceFileExists = false + } + + sh """ + echo "post download:" + ls -la + """ + + if (traceFileExists) { + // Move the build trace file to a temporary location to preserve it during checkout + sh """ + mkdir -p /tmp/jenkins_artifacts + cp ${buildTraceFileName} /tmp/jenkins_artifacts/${buildTraceFileName} + ls -la /tmp/jenkins_artifacts/ + """ + } else { + echo "Build trace archive not found" + return + } + + // Checkout source code to get required files + checkout scm + + // Restore the build trace file after checkout + sh """ + ls -la + cp /tmp/jenkins_artifacts/${buildTraceFileName} ${buildTraceFileName} + ls -la ${buildTraceFileName} + """ + + // Pull image + def image = "ghcr.io/puppeteer/puppeteer:24.30.0" + echo "Pulling image: ${image}" + def retimage = docker.image("${image}") + retimage.pull() + + // Create a temporary workspace + sh """#!/bin/bash + ls -la + mkdir -p workspace + cp ./script/infra_helper/capture_build_trace.js ./workspace + cp ${buildTraceFileName} ./workspace/${buildTraceFileName} + chmod 777 ./workspace + ls -la ./workspace + """ + + // Run container to get snapshot + def dockerOpts = "--cap-add=SYS_ADMIN -v \"\$(pwd)/workspace:/workspace\" -e NODE_PATH=/home/pptruser/node_modules" + // Create unique image name by sanitizing job name + def sanitizedJobName = env.JOB_NAME.replaceAll(/[\/\\:*?"<>| ]/, '_') + def imageName = "perfetto_snapshot_${sanitizedJobName}_build_${env.BUILD_NUMBER}.png" + sh """ + docker run --rm ${dockerOpts} ${image} node /workspace/capture_build_trace.js + mv ./workspace/perfetto_snapshot_build.png ./workspace/${imageName} + """ + + // Archive the snapshot + sh """ + mv ./workspace/${imageName} ${imageName} + """ + archiveArtifacts "${imageName}" + + // Notify the channel + withCredentials([string(credentialsId: 'ck_ci_build_perf_webhook_url', variable: 'WEBHOOK_URL')]) { + sh ''' + # Create build trace filename with build number based on the original filename + BUILD_TRACE_WITH_NUMBER=$(echo "''' + buildTraceFileName + '''" | sed 's/.json/_''' + sanitizedJobName + '''_''' + env.BUILD_NUMBER + '''.json/') + + # Convert image to base64 + echo "Converting image to base64..." + IMAGE_BASE64=$(base64 -w 0 ''' + imageName + ''') + echo "Image base64 length: ${#IMAGE_BASE64}" + + # Convert build trace to base64 + echo "Converting build trace to base64..." + BUILD_TRACE_BASE64=$(base64 -w 0 ''' + buildTraceFileName + ''') + echo "Build trace base64 length: ${#BUILD_TRACE_BASE64}" + + # Create JSON payload with base64 data + echo "Creating JSON payload..." + { + printf '{\n' + printf ' "jobName": "%s",\n' "''' + env.JOB_NAME + '''" + printf ' "buildNumber": "%s",\n' "''' + env.BUILD_NUMBER + '''" + printf ' "jobUrl": "%s",\n' "''' + env.RUN_DISPLAY_URL + '''" + printf ' "imageName": "%s",\n' "''' + imageName + '''" + printf ' "imageData": "%s",\n' "$IMAGE_BASE64" + printf ' "buildTraceName": "%s",\n' "$BUILD_TRACE_WITH_NUMBER" + printf ' "buildTraceData": "%s"\n' "$BUILD_TRACE_BASE64" + printf '}\n' + } > webhook_payload.json + + echo "JSON payload created, size: $(wc -c < webhook_payload.json) bytes" + + curl -X POST "${WEBHOOK_URL}" \ + -H "Content-Type: application/json" \ + -d @webhook_payload.json + + # Clean up temporary file + rm -f webhook_payload.json + ''' + } + } catch (Exception e) { + echo "Throwing error exception while generating build trace visualization" + echo 'Exception occurred: ' + e.toString() + } +} + class Version { int major, minor, patch @Override @@ -1750,6 +1873,15 @@ pipeline { } } post { + always { + node(rocmnode("nogpu")) { + script { + // Simulate capture + generateAndArchiveBuildTraceVisualization() + } + cleanWs() + } + } success { script { // Report the parent stage build ck and run tests status diff --git a/script/infra_helper/capture_build_trace.js b/script/infra_helper/capture_build_trace.js new file mode 100644 index 0000000000..e484a815cc --- /dev/null +++ b/script/infra_helper/capture_build_trace.js @@ -0,0 +1,53 @@ +const puppeteer = require('puppeteer'); + +(async () => { + try { + // Launch the browser + const browser = await puppeteer.launch({ + args: [ + '--no-sandbox', + '--headless', + '--disable-gpu', + '--window-size=1920x1080' + ]}); + const page = await browser.newPage(); + await page.setViewport({ width: 1920, height: 1080 }); + await page.goto('https://ui.perfetto.dev'); + // Wait for the home page to be visible + console.log('Waiting for page to load...'); + await page.waitForSelector('.pf-home-page', { visible: true, timeout: 30000 }); + // Locate and click the Open trace button + const elements = await page.$$('li'); + let element = null; + for (const el of elements) { + const text = await el.evaluate(node => node.textContent); + if (text && text.includes('Open trace file')) { + element = el; + break; + } + } + if (element) { + const [fileChooser] = await Promise.all([ + page.waitForFileChooser(), + element.click() + ]); + await fileChooser.accept(['/workspace/ck_build_trace.json']); + } else { + throw new Error('Element not found'); + } + console.log('Waiting for data to load...'); + // Wait for the timeline element to be visible + await page.waitForSelector('.pf-track', { timeout: 30000 }); + // Wait for the data to finish loading + await page.waitForFunction(() => { + return !document.body.textContent.includes('Loading...'); + }, { timeout: 30000 }); + console.log('Capturing screenshot...'); + await page.screenshot({path: '/workspace/perfetto_snapshot_build.png'}); + console.log('Done capturing screenshot...'); + await browser.close(); + } catch (err) { + console.error(err); + process.exit(1); + } +})(); \ No newline at end of file