From 984c501176a328a72a5aa790b6a4bacafcf42792 Mon Sep 17 00:00:00 2001 From: "Mateusz P. Nowak" Date: Tue, 2 Sep 2025 17:22:48 +0000 Subject: [PATCH 1/8] add links for download of traces --- devops/actions/run-tests/benchmark/action.yml | 1 + devops/scripts/benchmarks/html/scripts.js | 115 +++++++++++++++++- devops/scripts/benchmarks/utils/unitrace.py | 15 +++ 3 files changed, 130 insertions(+), 1 deletion(-) diff --git a/devops/actions/run-tests/benchmark/action.yml b/devops/actions/run-tests/benchmark/action.yml index e09582be0fde6..b2aebd983fe34 100644 --- a/devops/actions/run-tests/benchmark/action.yml +++ b/devops/actions/run-tests/benchmark/action.yml @@ -198,6 +198,7 @@ runs: --ur "$(realpath ./ur/install)" \ --adapter "$FORCELOAD_ADAPTER" \ --save "$SAVE_NAME" \ + --unitrace inclusive \ --output-html remote \ --results-dir "./llvm-ci-perf-results/" \ --output-dir "./llvm-ci-perf-results/" \ diff --git a/devops/scripts/benchmarks/html/scripts.js b/devops/scripts/benchmarks/html/scripts.js index 28e3a708c2956..3ac1428300e31 100644 --- a/devops/scripts/benchmarks/html/scripts.js +++ b/devops/scripts/benchmarks/html/scripts.js @@ -557,10 +557,123 @@ function createChartContainer(data, canvasId, type) { const downloadButton = document.createElement('button'); downloadButton.className = 'download-button'; downloadButton.textContent = 'Download'; - downloadButton.onclick = (event) => { + // Create a select dropdown to pick Unitrace archive per run (if available) + const downloadSelect = document.createElement('select'); + downloadSelect.className = 'download-select'; + downloadSelect.style.marginRight = '8px'; + // Hidden by default; shown only after user clicks "Download Unitrace" + downloadSelect.style.display = 'none'; + + // Helper: format Date to YYYYMMDD_HHMMSS (UTC) + function formatTimestampFromDate(d) { + if (!d) return null; + const date = (d instanceof Date) ? d : new Date(d); + if (isNaN(date)) return null; + const pad = (n) => n.toString().padStart(2, '0'); + const Y = date.getUTCFullYear(); + const M = pad(date.getUTCMonth() + 1); + const D = pad(date.getUTCDate()); + const h = pad(date.getUTCHours()); + const m = pad(date.getUTCMinutes()); + const s = pad(date.getUTCSeconds()); + return `${Y}${M}${D}_${h}${m}${s}`; + } + + // Base raw URL for archives (branch-based) + const RAW_BASE = 'https://raw.githubusercontent.com/intel/llvm-ci-perf-results/unify-ci'; + + // Populate download options from chart data using authoritative run entries + function populateDownloadOptions() { + downloadSelect.innerHTML = ''; + let added = 0; + + if (Array.isArray(loadedBenchmarkRuns)) { + loadedBenchmarkRuns.forEach(run => { + // run.results / run.benchmarks / run.data may hold per-benchmark entries + const results = run.results || run.benchmarks || run.data || []; + const found = results.find(r => r.label === data.label); + if (!found) return; + + const runDate = run.date || run.timestamp || run.time; + if (!runDate) return; + + const formattedTs = formatTimestampFromDate(new Date(runDate)); + const saveName = run.save_name || run.saveName || run.save || run.name; + if (!formattedTs || !saveName) return; + + const filename = `${formattedTs}_${saveName}.tgz`; + const filePath = `traces/${encodeURIComponent(data.label)}/${encodeURIComponent(filename)}`; + const url = `${RAW_BASE}/${filePath}`; + + const opt = document.createElement('option'); + opt.value = url; + opt.textContent = `${run.name || saveName} — ${formattedTs}`; + downloadSelect.appendChild(opt); + added += 1; + }); + } + + if (added === 0) { + const opt = document.createElement('option'); + opt.value = ''; + opt.textContent = 'No Unitrace archives available'; + downloadSelect.appendChild(opt); + downloadSelect.disabled = true; + downloadButton.disabled = true; + } else { + downloadSelect.disabled = false; + downloadButton.disabled = false; + } + + return added; + } + + // Create Download Chart button (original download behavior) + const chartButton = document.createElement('button'); + chartButton.className = 'download-button chart-download-button'; + chartButton.textContent = 'Download Chart'; + chartButton.style.marginRight = '8px'; + chartButton.onclick = (event) => { event.stopPropagation(); // Prevent details toggle downloadChart(canvasId, data.label); }; + + // Unitrace download button + downloadButton.textContent = 'Download Unitrace'; + downloadButton.onclick = (event) => { + event.stopPropagation(); // Prevent details toggle + + // Lazy populate options on demand + const count = populateDownloadOptions(); + + if (count === 0) { + alert('No Unitrace archive available for this chart.'); + return; + } + + // If exactly one archive available, download immediately + if (count === 1) { + const url = downloadSelect.options[0].value; + if (url) window.open(url, '_blank'); + return; + } + + // Otherwise show the dropdown so the user picks an archive + downloadSelect.style.display = 'inline-block'; + // focus the select so keyboard users can choose quickly + downloadSelect.focus(); + }; + + // When user selects an option, start download and hide the select + downloadSelect.onchange = function() { + const url = this.value; + if (url) window.open(url, '_blank'); + this.style.display = 'none'; + }; + + // Append the select and buttons to the summary (chart download first) + summary.appendChild(chartButton); + summary.appendChild(downloadSelect); summary.appendChild(downloadButton); details.appendChild(summary); diff --git a/devops/scripts/benchmarks/utils/unitrace.py b/devops/scripts/benchmarks/utils/unitrace.py index 721ff270fdc8f..af8398f4b8206 100644 --- a/devops/scripts/benchmarks/utils/unitrace.py +++ b/devops/scripts/benchmarks/utils/unitrace.py @@ -4,6 +4,7 @@ # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception import os import shutil +import tarfile from options import options from utils.utils import run, git_clone @@ -172,6 +173,20 @@ def handle_output(self, unitrace_output: str): shutil.move(os.path.join(options.benchmark_cwd, pid_json_files[-1]), json_name) log.debug(f"Moved {pid_json_files[-1]} to {json_name}") + # Create a per-benchmark archive (.tgz) containing both the .out and .json + try: + archive_path = unitrace_output[: -len(".out")] + ".tgz" + with tarfile.open(archive_path, "w:gz") as tar: + # add the .out file + if os.path.exists(unitrace_output): + tar.add(unitrace_output, arcname=os.path.basename(unitrace_output)) + # add the moved .json file + if os.path.exists(json_name): + tar.add(json_name, arcname=os.path.basename(json_name)) + log.info(f"Created Unitrace archive: {archive_path}") + except Exception as e: + log.warning(f"Failed to create Unitrace archive for {unitrace_output}: {e}") + # Prune old unitrace directories self._prune_unitrace_dirs(os.path.dirname(unitrace_output)) From 3ec8a7b3fdad1d50eb2ad09d885c243311a67088 Mon Sep 17 00:00:00 2001 From: "Mateusz P. Nowak" Date: Wed, 3 Sep 2025 15:53:27 +0000 Subject: [PATCH 2/8] fix links --- devops/actions/run-tests/benchmark/action.yml | 63 +++- devops/scripts/benchmarks/html/scripts.js | 305 ++++++++++++------ devops/scripts/benchmarks/html/styles.css | 90 +++++- 3 files changed, 333 insertions(+), 125 deletions(-) diff --git a/devops/actions/run-tests/benchmark/action.yml b/devops/actions/run-tests/benchmark/action.yml index b2aebd983fe34..f7bcbc233adb7 100644 --- a/devops/actions/run-tests/benchmark/action.yml +++ b/devops/actions/run-tests/benchmark/action.yml @@ -199,6 +199,7 @@ runs: --adapter "$FORCELOAD_ADAPTER" \ --save "$SAVE_NAME" \ --unitrace inclusive \ + --flamegraph inclusive \ --output-html remote \ --results-dir "./llvm-ci-perf-results/" \ --output-dir "./llvm-ci-perf-results/" \ @@ -206,6 +207,18 @@ runs: --timestamp-override "$SAVE_TIMESTAMP" \ --detect-version sycl,compute_runtime + echo "-----" + # Configure remote URL for GitHub Pages deployment + echo "Configuring config.js for remote deployment..." + cd "./llvm-ci-perf-results" + if [ -f "config.js" ]; then + # Uncomment and set the remoteDataUrl for GitHub raw access + sed -i 's|//remoteDataUrl = .*|remoteDataUrl = "https://raw.githubusercontent.com/intel/llvm-ci-perf-results/unify-ci";|' config.js + echo "Remote URL configured in config.js" + else + echo "Warning: config.js not found" + fi + cd - echo "-----" python3 ./devops/scripts/benchmarks/compare.py to_hist \ --avg-type EWMA \ @@ -229,9 +242,12 @@ runs: cd "./llvm-ci-perf-results" git add . - for diff in $(git diff HEAD --name-only); do - mkdir -p "../cached_changes/$(dirname $diff)" - cp "$diff" "../cached_changes/$diff" + # Cache all new and modified files, including untracked files in subdirectories + for diff in $(git diff HEAD --name-only; git ls-files --others --exclude-standard); do + if [ -f "$diff" ]; then + mkdir -p "../cached_changes/$(dirname $diff)" + cp "$diff" "../cached_changes/$diff" + fi done - name: Push benchmarks results if: inputs.upload_results == 'true' && always() @@ -251,23 +267,44 @@ runs: echo "Attempt $attempt to push new results" git add . git commit -m "[GHA] Upload compute-benchmarks results from https://github.com/intel/llvm/actions/runs/${{ github.run_id }}" - results_file="$(git diff HEAD~1 --name-only -- results/ | head -n 1)" + + # Cache all new files before attempting push (in case we need to retry) + cached_files=() + for new_file in $(git diff HEAD~1 --name-only); do + if [ -f "$new_file" ]; then + cached_dir="$(mktemp -d)" + mkdir -p "$cached_dir/$(dirname $new_file)" + cp "$new_file" "$cached_dir/$new_file" + cached_files+=("$cached_dir:$new_file") + fi + done if git push "https://$GITHUB_TOKEN@github.com/intel/llvm-ci-perf-results.git" "$results_branch"; then echo "Push succeeded" + # Clean up cached files on success + for cached_entry in "${cached_files[@]}"; do + cached_dir="${cached_entry%:*}" + [ -d "$cached_dir" ] && rm -rf "$cached_dir" + done break fi echo "Push failed, retrying..." - if [ -n "$results_file" ]; then - cached_result="$(mktemp -d)/$(basename $results_file)" - mv "$results_file" "$cached_result" - - git reset --hard "origin/$results_branch" - git pull origin "$results_branch" - - mv "$cached_result" "$results_file" - fi + + # Reset and pull latest changes + git reset --hard "origin/$results_branch" + git pull origin "$results_branch" + + # Restore all cached files + for cached_entry in "${cached_files[@]}"; do + cached_dir="${cached_entry%:*}" + file_path="${cached_entry#*:}" + if [ -f "$cached_dir/$file_path" ]; then + mkdir -p "$(dirname $file_path)" + mv "$cached_dir/$file_path" "$file_path" + fi + [ -d "$cached_dir" ] && rm -rf "$cached_dir" + done echo "Regenerating data.json..." cd ../ diff --git a/devops/scripts/benchmarks/html/scripts.js b/devops/scripts/benchmarks/html/scripts.js index eb17ed08a21e8..0b7b76a8e6701 100644 --- a/devops/scripts/benchmarks/html/scripts.js +++ b/devops/scripts/benchmarks/html/scripts.js @@ -23,6 +23,13 @@ let loadedBenchmarkRuns = []; // Loaded results from the js/json files // - defaultCompareNames: default run names for comparison // - flamegraphData: available flamegraphs data with runs information (if available) +// Helper function to get base URL for remote or local resources +function getResourceBaseUrl() { + return typeof remoteDataUrl !== 'undefined' && remoteDataUrl !== '' + ? 'https://raw.githubusercontent.com/intel/llvm-ci-perf-results/unify-ci' + : '.'; +} + // Toggle configuration and abstraction // // HOW TO ADD A NEW TOGGLE: @@ -102,6 +109,8 @@ const toggleConfigs = { // Toggle between flamegraph-only display and normal charts updateCharts(); updateFlameGraphTooltip(); + // Refresh download buttons to adapt to new mode + refreshDownloadButtons(); updateURL(); } } @@ -448,6 +457,19 @@ function updateCharts() { drawCharts(filteredTimeseriesData, filteredBarChartsData, filteredLayerComparisonsData); } +// Function to refresh download buttons when mode changes +function refreshDownloadButtons() { + // Wait a bit for charts to be redrawn + setTimeout(() => { + document.querySelectorAll('.chart-download-button').forEach(button => { + const container = button.closest('.chart-container'); + if (container && button.updateChartButton) { + button.updateChartButton(); + } + }); + }, 100); +} + function drawCharts(filteredTimeseriesData, filteredBarChartsData, filteredLayerComparisonsData) { // Clear existing charts document.querySelectorAll('.charts').forEach(container => container.innerHTML = ''); @@ -578,20 +600,17 @@ function createChartContainer(data, canvasId, type) { const flamegraphsToShow = getFlameGraphsForBenchmark(data.label, activeRuns); if (flamegraphsToShow.length > 0) { - // Create multiple iframes for each run that has flamegraph data + // Add a class to reduce padding for flamegraph containers + container.classList.add('flamegraph-chart'); + // Create individual containers for each flamegraph to give them proper space flamegraphsToShow.forEach((flamegraphInfo, index) => { + // Create a dedicated container for this flamegraph + const flamegraphContainer = document.createElement('div'); + flamegraphContainer.className = 'flamegraph-container'; + const iframe = document.createElement('iframe'); iframe.src = flamegraphInfo.path; iframe.className = 'flamegraph-iframe'; - - // Calculate dimensions that fit within the existing container constraints - // The container has max-width: 1100px with 24px padding on each side - const containerMaxWidth = 1100; - const containerPadding = 48; // 24px on each side - const availableWidth = containerMaxWidth - containerPadding; - - // Only set max-width dynamically, other styles handled by CSS - iframe.style.maxWidth = `${availableWidth}px`; iframe.title = `${flamegraphInfo.runName} - ${data.label}`; // Add error handling for missing flamegraph files @@ -599,25 +618,15 @@ function createChartContainer(data, canvasId, type) { const errorDiv = document.createElement('div'); errorDiv.className = 'flamegraph-error'; errorDiv.textContent = `No flamegraph available for ${flamegraphInfo.runName} - ${data.label}`; - contentSection.replaceChild(errorDiv, iframe); + flamegraphContainer.replaceChild(errorDiv, iframe); }; - contentSection.appendChild(iframe); + flamegraphContainer.appendChild(iframe); + contentSection.appendChild(flamegraphContainer); }); - // Add resize handling to maintain proper sizing for all iframes - const updateIframeSizes = () => { - const containerMaxWidth = 1100; - const containerPadding = 48; - const availableWidth = containerMaxWidth - containerPadding; - - contentSection.querySelectorAll('iframe[src*="flamegraphs"]').forEach(iframe => { - iframe.style.maxWidth = `${availableWidth}px`; - }); - }; - - // Update size on window resize - window.addEventListener('resize', updateIframeSizes); + // No need for resize handling since CSS handles all sizing + // The flamegraphs will automatically use the full container width } else { // Show message when no flamegraph is available const noFlameGraphDiv = document.createElement('div'); @@ -649,12 +658,6 @@ function createChartContainer(data, canvasId, type) { const downloadButton = document.createElement('button'); downloadButton.className = 'download-button'; downloadButton.textContent = 'Download'; - // Create a select dropdown to pick Unitrace archive per run (if available) - const downloadSelect = document.createElement('select'); - downloadSelect.className = 'download-select'; - downloadSelect.style.marginRight = '8px'; - // Hidden by default; shown only after user clicks "Download Unitrace" - downloadSelect.style.display = 'none'; // Helper: format Date to YYYYMMDD_HHMMSS (UTC) function formatTimestampFromDate(d) { @@ -672,104 +675,204 @@ function createChartContainer(data, canvasId, type) { } // Base raw URL for archives (branch-based) - const RAW_BASE = 'https://raw.githubusercontent.com/intel/llvm-ci-perf-results/unify-ci'; + const RAW_BASE = getResourceBaseUrl(); - // Populate download options from chart data using authoritative run entries - function populateDownloadOptions() { - downloadSelect.innerHTML = ''; - let added = 0; + // Helper function to show flamegraph list + function showFlameGraphList(flamegraphs, buttonElement) { + const existingList = document.querySelector('.download-list'); + if (existingList) existingList.remove(); - if (Array.isArray(loadedBenchmarkRuns)) { - loadedBenchmarkRuns.forEach(run => { - // run.results / run.benchmarks / run.data may hold per-benchmark entries - const results = run.results || run.benchmarks || run.data || []; - const found = results.find(r => r.label === data.label); - if (!found) return; - - const runDate = run.date || run.timestamp || run.time; - if (!runDate) return; - - const formattedTs = formatTimestampFromDate(new Date(runDate)); - const saveName = run.save_name || run.saveName || run.save || run.name; - if (!formattedTs || !saveName) return; - - const filename = `${formattedTs}_${saveName}.tgz`; - const filePath = `traces/${encodeURIComponent(data.label)}/${encodeURIComponent(filename)}`; - const url = `${RAW_BASE}/${filePath}`; + const listContainer = document.createElement('div'); + listContainer.className = 'download-list'; + + const rect = buttonElement.getBoundingClientRect(); + listContainer.style.position = 'absolute'; + listContainer.style.top = `${window.scrollY + rect.bottom + 5}px`; + listContainer.style.left = `${window.scrollX + rect.left}px`; + listContainer.style.zIndex = '1000'; + listContainer.style.backgroundColor = 'white'; + listContainer.style.border = '1px solid #ccc'; + listContainer.style.borderRadius = '4px'; + listContainer.style.padding = '4px'; + listContainer.style.minWidth = '200px'; + listContainer.style.boxShadow = '0 2px 5px rgba(0,0,0,0.15)'; + + flamegraphs.forEach(fg => { + const link = document.createElement('a'); + link.href = fg.path; + link.textContent = fg.runName; + link.style.display = 'block'; + link.style.padding = '8px 12px'; + link.style.textDecoration = 'none'; + link.style.color = '#333'; + link.onclick = (e) => { + e.preventDefault(); + window.open(fg.path, '_blank'); + listContainer.remove(); + }; + listContainer.appendChild(link); + }); - const opt = document.createElement('option'); - opt.value = url; - opt.textContent = `${run.name || saveName} — ${formattedTs}`; - downloadSelect.appendChild(opt); - added += 1; + document.body.appendChild(listContainer); + + setTimeout(() => { + document.addEventListener('click', function closeHandler(event) { + if (!listContainer.contains(event.target) && !buttonElement.contains(event.target)) { + listContainer.remove(); + document.removeEventListener('click', closeHandler); + } }); - } - - if (added === 0) { - const opt = document.createElement('option'); - opt.value = ''; - opt.textContent = 'No Unitrace archives available'; - downloadSelect.appendChild(opt); - downloadSelect.disabled = true; - downloadButton.disabled = true; - } else { - downloadSelect.disabled = false; - downloadButton.disabled = false; - } - - return added; + }, 0); } - // Create Download Chart button (original download behavior) + // Create Download Chart button (adapts to mode) const chartButton = document.createElement('button'); chartButton.className = 'download-button chart-download-button'; - chartButton.textContent = 'Download Chart'; chartButton.style.marginRight = '8px'; - chartButton.onclick = (event) => { - event.stopPropagation(); // Prevent details toggle + + // Function to update button based on current mode + function updateChartButton() { if (isFlameGraphEnabled()) { - downloadFlameGraph(data.label, activeRuns, downloadButton); + const flamegraphs = getFlameGraphsForBenchmark(data.label, activeRuns); + if (flamegraphs.length === 0) { + chartButton.textContent = 'No Flamegraph Available'; + chartButton.disabled = true; + } else if (flamegraphs.length === 1) { + chartButton.textContent = 'Download Flamegraph'; + chartButton.disabled = false; + chartButton.onclick = (event) => { + event.stopPropagation(); + window.open(flamegraphs[0].path, '_blank'); + }; + } else { + chartButton.textContent = 'Download Flamegraphs'; + chartButton.disabled = false; + chartButton.onclick = (event) => { + event.stopPropagation(); + showFlameGraphList(flamegraphs, chartButton); + }; + } } else { - downloadChart(canvasId, data.label); + chartButton.textContent = 'Download Chart'; + chartButton.disabled = false; + chartButton.onclick = (event) => { + event.stopPropagation(); + downloadChart(canvasId, data.label); + }; } - }; + } + + updateChartButton(); + + // Store the update function on the button so it can be called when mode changes + chartButton.updateChartButton = updateChartButton; // Unitrace download button downloadButton.textContent = 'Download Unitrace'; downloadButton.onclick = (event) => { event.stopPropagation(); // Prevent details toggle - // Lazy populate options on demand - const count = populateDownloadOptions(); + // Get available Unitrace files + const unitraceFiles = getUnitraceFiles(data); - if (count === 0) { + if (unitraceFiles.length === 0) { alert('No Unitrace archive available for this chart.'); return; } // If exactly one archive available, download immediately - if (count === 1) { - const url = downloadSelect.options[0].value; - if (url) window.open(url, '_blank'); + if (unitraceFiles.length === 1) { + window.open(unitraceFiles[0].url, '_blank'); return; } - // Otherwise show the dropdown so the user picks an archive - downloadSelect.style.display = 'inline-block'; - // focus the select so keyboard users can choose quickly - downloadSelect.focus(); + // Otherwise show the list window so the user picks an archive + showUnitraceList(unitraceFiles, downloadButton); }; - // When user selects an option, start download and hide the select - downloadSelect.onchange = function() { - const url = this.value; - if (url) window.open(url, '_blank'); - this.style.display = 'none'; - }; + // Helper function to get Unitrace files (moved from populateDownloadOptions) + function getUnitraceFiles(data) { + const files = []; + + if (Array.isArray(loadedBenchmarkRuns)) { + loadedBenchmarkRuns.forEach(run => { + // run.results / run.benchmarks / run.data may hold per-benchmark entries + const results = run.results || run.benchmarks || run.data || []; + const found = results.find(r => r.label === data.label); + if (!found) return; + + const runDate = run.date || run.timestamp || run.time; + if (!runDate) return; + + const formattedTs = formatTimestampFromDate(new Date(runDate)); + const saveName = run.save_name || run.saveName || run.save || run.name; + if (!formattedTs || !saveName) return; + + const filename = `${formattedTs}_${saveName}.tgz`; + const filePath = `traces/${encodeURIComponent(data.label)}/${encodeURIComponent(filename)}`; + const url = `${RAW_BASE}/${filePath}`; + + files.push({ + name: `${run.name || saveName} — ${formattedTs}`, + url: url, + filename: filename + }); + }); + } + + return files; + } + + // Helper function to show Unitrace list (similar to showFlameGraphList) + function showUnitraceList(unitraceFiles, buttonElement) { + const existingList = document.querySelector('.download-list'); + if (existingList) existingList.remove(); + + const listContainer = document.createElement('div'); + listContainer.className = 'download-list'; + + const rect = buttonElement.getBoundingClientRect(); + listContainer.style.position = 'absolute'; + listContainer.style.top = `${window.scrollY + rect.bottom + 5}px`; + listContainer.style.left = `${window.scrollX + rect.left}px`; + listContainer.style.zIndex = '1000'; + listContainer.style.backgroundColor = 'white'; + listContainer.style.border = '1px solid #ccc'; + listContainer.style.borderRadius = '4px'; + listContainer.style.padding = '4px'; + listContainer.style.minWidth = '200px'; + listContainer.style.boxShadow = '0 2px 5px rgba(0,0,0,0.15)'; + + unitraceFiles.forEach(file => { + const link = document.createElement('a'); + link.href = file.url; + link.textContent = file.name; + link.style.display = 'block'; + link.style.padding = '8px 12px'; + link.style.textDecoration = 'none'; + link.style.color = '#333'; + link.onclick = (e) => { + e.preventDefault(); + window.open(file.url, '_blank'); + listContainer.remove(); + }; + listContainer.appendChild(link); + }); + + document.body.appendChild(listContainer); + + setTimeout(() => { + document.addEventListener('click', function closeHandler(event) { + if (!listContainer.contains(event.target) && !buttonElement.contains(event.target)) { + listContainer.remove(); + document.removeEventListener('click', closeHandler); + } + }); + }, 0); + } - // Append the select and buttons to the summary (chart download first) + // Append the buttons to the summary (chart download first) summary.appendChild(chartButton); - summary.appendChild(downloadSelect); summary.appendChild(downloadButton); details.appendChild(summary); @@ -1366,12 +1469,14 @@ function createFlameGraphPath(benchmarkLabel, runName, timestamp) { // Fallback to old path for safety, though it's likely to fail. const benchmarkDirName = benchmarkLabel; const timestampPrefix = timestamp + '_'; - return `results/flamegraphs/${benchmarkDirName}/${timestampPrefix}${runName}.svg`; + const relativePath = `results/flamegraphs/${benchmarkDirName}/${timestampPrefix}${runName}.svg`; + return `${getResourceBaseUrl()}/${relativePath}`; } const benchmarkDirName = `${suiteName}__${benchmarkLabel}`; const timestampPrefix = timestamp + '_'; - return `results/flamegraphs/${benchmarkDirName}/${timestampPrefix}${runName}.svg`; + const relativePath = `results/flamegraphs/${benchmarkDirName}/${timestampPrefix}${runName}.svg`; + return `${getResourceBaseUrl()}/${relativePath}`; } function getRunsWithFlameGraph(benchmarkLabel, activeRuns) { diff --git a/devops/scripts/benchmarks/html/styles.css b/devops/scripts/benchmarks/html/styles.css index 96c81223ecb40..5ddb7a1c5d65a 100644 --- a/devops/scripts/benchmarks/html/styles.css +++ b/devops/scripts/benchmarks/html/styles.css @@ -206,6 +206,39 @@ details[open] summary::after { .download-button:hover { color: var(--color-cyan); } + +.download-button:disabled { + color: var(--text-muted); + cursor: not-allowed; +} + +.download-list { + position: absolute !important; + z-index: 1000 !important; + background: var(--bg-white) !important; + border: 1px solid var(--border-medium) !important; + border-radius: 4px !important; + box-shadow: var(--shadow-dropdown) !important; + padding: 4px !important; + min-width: 200px !important; + max-width: 300px !important; +} + +.download-list a { + display: block !important; + padding: 8px 12px !important; + text-decoration: none !important; + color: var(--text-dark) !important; + border-radius: 2px !important; + font-size: 14px !important; + white-space: nowrap !important; + overflow: hidden !important; + text-overflow: ellipsis !important; +} + +.download-list a:hover { + background-color: var(--bg-light) !important; +} .loading-indicator { text-align: center; font-size: 18px; @@ -530,30 +563,63 @@ details[open] summary::after { border: 1px solid var(--border-medium); border-radius: 4px; display: block; - margin: 10px auto; + margin: 0 0 10px 0; transition: all 0.3s ease; box-sizing: border-box; overflow: hidden; + /* Ensure maximum width utilization */ + max-width: none; + min-width: 0; +} + +/* Flamegraph container styles - gives each flamegraph its own space */ +.flamegraph-container { + margin-bottom: 20px; + padding: 0; + border: none; + border-radius: 0; + background-color: transparent; + width: 100%; + /* Ensure no width constraints */ + max-width: none; + min-width: 0; + box-sizing: border-box; } -.flamegraph-iframe:first-child { - margin: 0 auto 10px auto; +.flamegraph-container:last-child { + margin-bottom: 0; +} + +.flamegraph-title { + font-size: 16px; + font-weight: 600; + color: var(--text-dark); + margin: 0 0 10px 0; + padding: 8px 12px; + background-color: var(--bg-light); + border-radius: 4px; + border-left: 4px solid var(--color-blue); } /* Ensure flamegraph containers have proper spacing and fit within container */ -.chart-container iframe { - margin-bottom: 10px; +.chart-container .flamegraph-container { + margin-bottom: 20px; + width: 100%; } -/* Handle multiple flamegraphs displayed vertically */ -.chart-content iframe[src*="flamegraphs"]:not(:last-child) { - margin-bottom: 15px; - border-bottom: 2px solid var(--bg-light); +/* Reduce padding for chart containers that contain flamegraphs to maximize space */ +.chart-container.flamegraph-chart { + padding: 8px; +} + +/* Ensure chart content doesn't constrain flamegraphs */ +.chart-container.flamegraph-chart .chart-content { + padding: 0; } -/* Add subtle visual separation between multiple flamegraphs */ -.chart-content iframe[src*="flamegraphs"]:not(:first-child) { - margin-top: 15px; +/* Handle multiple flamegraphs displayed vertically - now handled by container */ +.flamegraph-container:not(:last-child) { + margin-bottom: 20px; } /* Floating flamegraph download list */ From 5435384492f4950b3893ade622485fa086bd2eee Mon Sep 17 00:00:00 2001 From: "Mateusz P. Nowak" Date: Thu, 4 Sep 2025 13:46:07 +0000 Subject: [PATCH 3/8] fix files placement Signed-off-by: Mateusz P. Nowak --- devops/actions/run-tests/benchmark/action.yml | 72 +++------ devops/scripts/benchmarks/html/scripts.js | 152 +++++------------- devops/scripts/benchmarks/output_html.py | 8 +- devops/scripts/benchmarks/utils/flamegraph.py | 22 ++- devops/scripts/benchmarks/utils/unitrace.py | 20 +-- devops/scripts/benchmarks/utils/utils.py | 13 ++ 6 files changed, 99 insertions(+), 188 deletions(-) diff --git a/devops/actions/run-tests/benchmark/action.yml b/devops/actions/run-tests/benchmark/action.yml index f7bcbc233adb7..622c849e8b5de 100644 --- a/devops/actions/run-tests/benchmark/action.yml +++ b/devops/actions/run-tests/benchmark/action.yml @@ -133,6 +133,11 @@ runs: cmake --install build cd - + - name: Install linux-tools package + shell: bash + run: | + sudo apt-get update + sudo apt-get install -y linux-tools-generic linux-tools-$(uname -r) - name: Checkout results repo shell: bash run: | @@ -198,27 +203,14 @@ runs: --ur "$(realpath ./ur/install)" \ --adapter "$FORCELOAD_ADAPTER" \ --save "$SAVE_NAME" \ - --unitrace inclusive \ - --flamegraph inclusive \ --output-html remote \ --results-dir "./llvm-ci-perf-results/" \ --output-dir "./llvm-ci-perf-results/" \ --preset "$PRESET" \ --timestamp-override "$SAVE_TIMESTAMP" \ - --detect-version sycl,compute_runtime + --detect-version sycl,compute_runtime \ + --flamegraph inclusive - echo "-----" - # Configure remote URL for GitHub Pages deployment - echo "Configuring config.js for remote deployment..." - cd "./llvm-ci-perf-results" - if [ -f "config.js" ]; then - # Uncomment and set the remoteDataUrl for GitHub raw access - sed -i 's|//remoteDataUrl = .*|remoteDataUrl = "https://raw.githubusercontent.com/intel/llvm-ci-perf-results/unify-ci";|' config.js - echo "Remote URL configured in config.js" - else - echo "Warning: config.js not found" - fi - cd - echo "-----" python3 ./devops/scripts/benchmarks/compare.py to_hist \ --avg-type EWMA \ @@ -242,12 +234,9 @@ runs: cd "./llvm-ci-perf-results" git add . - # Cache all new and modified files, including untracked files in subdirectories - for diff in $(git diff HEAD --name-only; git ls-files --others --exclude-standard); do - if [ -f "$diff" ]; then - mkdir -p "../cached_changes/$(dirname $diff)" - cp "$diff" "../cached_changes/$diff" - fi + for diff in $(git diff HEAD --name-only); do + mkdir -p "../cached_changes/$(dirname $diff)" + cp "$diff" "../cached_changes/$diff" done - name: Push benchmarks results if: inputs.upload_results == 'true' && always() @@ -267,44 +256,23 @@ runs: echo "Attempt $attempt to push new results" git add . git commit -m "[GHA] Upload compute-benchmarks results from https://github.com/intel/llvm/actions/runs/${{ github.run_id }}" - - # Cache all new files before attempting push (in case we need to retry) - cached_files=() - for new_file in $(git diff HEAD~1 --name-only); do - if [ -f "$new_file" ]; then - cached_dir="$(mktemp -d)" - mkdir -p "$cached_dir/$(dirname $new_file)" - cp "$new_file" "$cached_dir/$new_file" - cached_files+=("$cached_dir:$new_file") - fi - done + results_file="$(git diff HEAD~1 --name-only -- results/ | head -n 1)" if git push "https://$GITHUB_TOKEN@github.com/intel/llvm-ci-perf-results.git" "$results_branch"; then echo "Push succeeded" - # Clean up cached files on success - for cached_entry in "${cached_files[@]}"; do - cached_dir="${cached_entry%:*}" - [ -d "$cached_dir" ] && rm -rf "$cached_dir" - done break fi echo "Push failed, retrying..." - - # Reset and pull latest changes - git reset --hard "origin/$results_branch" - git pull origin "$results_branch" - - # Restore all cached files - for cached_entry in "${cached_files[@]}"; do - cached_dir="${cached_entry%:*}" - file_path="${cached_entry#*:}" - if [ -f "$cached_dir/$file_path" ]; then - mkdir -p "$(dirname $file_path)" - mv "$cached_dir/$file_path" "$file_path" - fi - [ -d "$cached_dir" ] && rm -rf "$cached_dir" - done + if [ -n "$results_file" ]; then + cached_result="$(mktemp -d)/$(basename $results_file)" + mv "$results_file" "$cached_result" + + git reset --hard "origin/$results_branch" + git pull origin "$results_branch" + + mv "$cached_result" "$results_file" + fi echo "Regenerating data.json..." cd ../ diff --git a/devops/scripts/benchmarks/html/scripts.js b/devops/scripts/benchmarks/html/scripts.js index 0b7b76a8e6701..56ee9afe8180e 100644 --- a/devops/scripts/benchmarks/html/scripts.js +++ b/devops/scripts/benchmarks/html/scripts.js @@ -654,11 +654,6 @@ function createChartContainer(data, canvasId, type) { summary.className = 'download-summary'; summary.textContent = "Details"; - // Add subtle download button to the summary - const downloadButton = document.createElement('button'); - downloadButton.className = 'download-button'; - downloadButton.textContent = 'Download'; - // Helper: format Date to YYYYMMDD_HHMMSS (UTC) function formatTimestampFromDate(d) { if (!d) return null; @@ -767,113 +762,8 @@ function createChartContainer(data, canvasId, type) { // Store the update function on the button so it can be called when mode changes chartButton.updateChartButton = updateChartButton; - // Unitrace download button - downloadButton.textContent = 'Download Unitrace'; - downloadButton.onclick = (event) => { - event.stopPropagation(); // Prevent details toggle - - // Get available Unitrace files - const unitraceFiles = getUnitraceFiles(data); - - if (unitraceFiles.length === 0) { - alert('No Unitrace archive available for this chart.'); - return; - } - - // If exactly one archive available, download immediately - if (unitraceFiles.length === 1) { - window.open(unitraceFiles[0].url, '_blank'); - return; - } - - // Otherwise show the list window so the user picks an archive - showUnitraceList(unitraceFiles, downloadButton); - }; - - // Helper function to get Unitrace files (moved from populateDownloadOptions) - function getUnitraceFiles(data) { - const files = []; - - if (Array.isArray(loadedBenchmarkRuns)) { - loadedBenchmarkRuns.forEach(run => { - // run.results / run.benchmarks / run.data may hold per-benchmark entries - const results = run.results || run.benchmarks || run.data || []; - const found = results.find(r => r.label === data.label); - if (!found) return; - - const runDate = run.date || run.timestamp || run.time; - if (!runDate) return; - - const formattedTs = formatTimestampFromDate(new Date(runDate)); - const saveName = run.save_name || run.saveName || run.save || run.name; - if (!formattedTs || !saveName) return; - - const filename = `${formattedTs}_${saveName}.tgz`; - const filePath = `traces/${encodeURIComponent(data.label)}/${encodeURIComponent(filename)}`; - const url = `${RAW_BASE}/${filePath}`; - - files.push({ - name: `${run.name || saveName} — ${formattedTs}`, - url: url, - filename: filename - }); - }); - } - - return files; - } - - // Helper function to show Unitrace list (similar to showFlameGraphList) - function showUnitraceList(unitraceFiles, buttonElement) { - const existingList = document.querySelector('.download-list'); - if (existingList) existingList.remove(); - - const listContainer = document.createElement('div'); - listContainer.className = 'download-list'; - - const rect = buttonElement.getBoundingClientRect(); - listContainer.style.position = 'absolute'; - listContainer.style.top = `${window.scrollY + rect.bottom + 5}px`; - listContainer.style.left = `${window.scrollX + rect.left}px`; - listContainer.style.zIndex = '1000'; - listContainer.style.backgroundColor = 'white'; - listContainer.style.border = '1px solid #ccc'; - listContainer.style.borderRadius = '4px'; - listContainer.style.padding = '4px'; - listContainer.style.minWidth = '200px'; - listContainer.style.boxShadow = '0 2px 5px rgba(0,0,0,0.15)'; - - unitraceFiles.forEach(file => { - const link = document.createElement('a'); - link.href = file.url; - link.textContent = file.name; - link.style.display = 'block'; - link.style.padding = '8px 12px'; - link.style.textDecoration = 'none'; - link.style.color = '#333'; - link.onclick = (e) => { - e.preventDefault(); - window.open(file.url, '_blank'); - listContainer.remove(); - }; - listContainer.appendChild(link); - }); - - document.body.appendChild(listContainer); - - setTimeout(() => { - document.addEventListener('click', function closeHandler(event) { - if (!listContainer.contains(event.target) && !buttonElement.contains(event.target)) { - listContainer.remove(); - document.removeEventListener('click', closeHandler); - } - }); - }, 0); - } - - // Append the buttons to the summary (chart download first) + // Append the chart download button to the summary summary.appendChild(chartButton); - summary.appendChild(downloadButton); details.appendChild(summary); // Create and append extra info @@ -1461,22 +1351,43 @@ function validateFlameGraphData() { return window.flamegraphData?.runs !== undefined; } +function sanitizeFilename(name) { + /** + * Sanitize a string to be safe for use as a filename or directory name. + * Replace invalid characters with underscores. + * + * Invalid characters: " : < > | * ? \r \n + */ + const invalidChars = /[":;<>|*?\r\n]/g; + return name.replace(invalidChars, '_'); +} + function createFlameGraphPath(benchmarkLabel, runName, timestamp) { const suiteName = window.flamegraphData?.runs?.[runName]?.suites?.[benchmarkLabel]; if (!suiteName) { console.error(`Could not find suite for benchmark '${benchmarkLabel}' in run '${runName}'`); - // Fallback to old path for safety, though it's likely to fail. - const benchmarkDirName = benchmarkLabel; + // Fallback: sanitize benchmark name for directory structure + const sanitizedBenchmarkName = sanitizeFilename(benchmarkLabel); + const benchmarkDirName = sanitizedBenchmarkName; const timestampPrefix = timestamp + '_'; const relativePath = `results/flamegraphs/${benchmarkDirName}/${timestampPrefix}${runName}.svg`; - return `${getResourceBaseUrl()}/${relativePath}`; + + // For local mode, use relative path; for remote mode, use full URL + const baseUrl = getResourceBaseUrl(); + return baseUrl === '.' ? relativePath : `${baseUrl}/${relativePath}`; } - const benchmarkDirName = `${suiteName}__${benchmarkLabel}`; + // Apply sanitization to both suite and benchmark names to match Python implementation + const sanitizedSuiteName = sanitizeFilename(suiteName); + const sanitizedBenchmarkName = sanitizeFilename(benchmarkLabel); + const benchmarkDirName = `${sanitizedSuiteName}__${sanitizedBenchmarkName}`; const timestampPrefix = timestamp + '_'; const relativePath = `results/flamegraphs/${benchmarkDirName}/${timestampPrefix}${runName}.svg`; - return `${getResourceBaseUrl()}/${relativePath}`; + + // For local mode, use relative path; for remote mode, use full URL + const baseUrl = getResourceBaseUrl(); + return baseUrl === '.' ? relativePath : `${baseUrl}/${relativePath}`; } function getRunsWithFlameGraph(benchmarkLabel, activeRuns) { @@ -1837,33 +1748,42 @@ function fetchAndProcessData(url, isArchived = false) { // Load data based on configuration function loadData() { + console.log('=== loadData() called ==='); const loadingIndicator = document.getElementById('loading-indicator'); loadingIndicator.classList.remove('hidden'); // Show loading indicator if (typeof remoteDataUrl !== 'undefined' && remoteDataUrl !== '') { + console.log('Using remote data URL:', remoteDataUrl); // Fetch data from remote URL const url = remoteDataUrl.endsWith('/') ? remoteDataUrl + 'data.json' : remoteDataUrl + '/data.json'; fetchAndProcessData(url); } else { + console.log('Using local data'); // Use local data + console.log('benchmarkRuns available:', typeof benchmarkRuns, Array.isArray(benchmarkRuns) ? benchmarkRuns.length : 'not array'); loadedBenchmarkRuns = benchmarkRuns; // Assign global metadata from data.js if window.benchmarkMetadata is not set if (!window.benchmarkMetadata) { window.benchmarkMetadata = (typeof benchmarkMetadata !== 'undefined') ? benchmarkMetadata : {}; } + console.log('benchmarkMetadata loaded:', Object.keys(window.benchmarkMetadata).length, 'items'); // Assign global tags from data.js if window.benchmarkTags is not set if (!window.benchmarkTags) { window.benchmarkTags = (typeof benchmarkTags !== 'undefined') ? benchmarkTags : {}; } + console.log('benchmarkTags loaded:', Object.keys(window.benchmarkTags).length, 'items'); // Assign flamegraph data from data.js if available if (typeof flamegraphData !== 'undefined') { window.flamegraphData = flamegraphData; console.log('Loaded flamegraph data from data.js with', Object.keys(flamegraphData.runs || {}).length, 'runs'); } else { window.flamegraphData = { runs: {} }; + console.log('No flamegraph data available'); } + console.log('defaultCompareNames available:', typeof defaultCompareNames, Array.isArray(defaultCompareNames) ? defaultCompareNames : 'not defined'); initializeCharts(); loadingIndicator.classList.add('hidden'); // Hide loading indicator + console.log('=== loadData() completed ==='); } } diff --git a/devops/scripts/benchmarks/output_html.py b/devops/scripts/benchmarks/output_html.py index 621ed884aed0f..433ef7050a80b 100644 --- a/devops/scripts/benchmarks/output_html.py +++ b/devops/scripts/benchmarks/output_html.py @@ -108,13 +108,19 @@ def _write_output_to_file( # For remote format, we write a single JSON file data_path = os.path.join(html_path, f"{filename}.json") output_data = json.loads(output.to_json()) # type: ignore + + # Add defaultCompareNames to match the expected structure + output_data["defaultCompareNames"] = output.default_compare_names + if options.flamegraph: flamegraph_data = _get_flamegraph_data(html_path) if flamegraph_data and flamegraph_data.get("runs"): - output_data["flamegraphs"] = flamegraph_data + # Use "flamegraphData" to match the expected variable name + output_data["flamegraphData"] = flamegraph_data log.debug( f"Added flamegraph data for {len(flamegraph_data['runs'])} runs to {filename}.json" ) + with open(data_path, "w") as f: json.dump(output_data, f, indent=2) log.info( diff --git a/devops/scripts/benchmarks/utils/flamegraph.py b/devops/scripts/benchmarks/utils/flamegraph.py index aeeb9330fcc1e..65eb7e6d5a08e 100644 --- a/devops/scripts/benchmarks/utils/flamegraph.py +++ b/devops/scripts/benchmarks/utils/flamegraph.py @@ -8,7 +8,13 @@ from pathlib import Path from options import options -from utils.utils import run, git_clone, prune_old_files, remove_by_prefix +from utils.utils import ( + run, + git_clone, + prune_old_files, + remove_by_prefix, + sanitize_filename, +) from utils.logger import log from datetime import datetime, timezone @@ -40,7 +46,7 @@ def __init__(self): if options.results_directory_override: self.flamegraphs_dir = ( - Path(options.results_directory_override) / "flamegraphs" + Path(options.results_directory_override) / "results" / "flamegraphs" ) else: self.flamegraphs_dir = Path(options.workdir) / "results" / "flamegraphs" @@ -67,7 +73,9 @@ def setup( "perf command not found. Please install linux-tools or perf package." ) - dir_name = f"{suite_name}__{bench_name}" + sanitized_suite_name = sanitize_filename(suite_name) + sanitized_bench_name = sanitize_filename(bench_name) + dir_name = f"{sanitized_suite_name}__{sanitized_bench_name}" bench_dir = self.flamegraphs_dir / dir_name bench_dir.mkdir(parents=True, exist_ok=True) @@ -111,8 +119,14 @@ def handle_output(self, bench_name: str, perf_data_file: str, suite_name: str = self._convert_perf_to_folded(perf_data_path, folded_file) self._generate_svg(folded_file, svg_file, bench_name) - log.debug(f"Generated flamegraph: {svg_file}") + log.info(f"FlameGraph SVG created: {svg_file.resolve()}") self._create_immediate_symlink(svg_file) + + # Clean up the original perf data file after successful SVG generation + if perf_data_path.exists(): + perf_data_path.unlink() + log.debug(f"Removed original perf data file: {perf_data_path}") + prune_old_files(str(perf_data_path.parent)) return str(svg_file) except Exception as e: diff --git a/devops/scripts/benchmarks/utils/unitrace.py b/devops/scripts/benchmarks/utils/unitrace.py index d660c0c7a4f0f..6405a1b36336d 100644 --- a/devops/scripts/benchmarks/utils/unitrace.py +++ b/devops/scripts/benchmarks/utils/unitrace.py @@ -13,6 +13,7 @@ prune_old_files, remove_by_prefix, remove_by_extension, + sanitize_filename, ) from utils.logger import log @@ -66,7 +67,7 @@ def __init__(self): if options.results_directory_override == None: self.traces_dir = os.path.join(options.workdir, "results", "traces") else: - self.traces_dir = os.path.join(options.results_directory_override, "traces") + self.traces_dir = os.path.join(options.results_directory_override, "results", "traces") def _prune_unitrace_dirs(self, res_dir: str, FILECNT: int = 10): """Keep only the last FILECNT files in the traces directory.""" @@ -92,7 +93,8 @@ def setup( if not os.path.exists(unitrace_bin): raise FileNotFoundError(f"Unitrace binary not found: {unitrace_bin}. ") os.makedirs(self.traces_dir, exist_ok=True) - bench_dir = os.path.join(f"{self.traces_dir}", f"{bench_name}") + sanitized_bench_name = sanitize_filename(bench_name) + bench_dir = os.path.join(f"{self.traces_dir}", f"{sanitized_bench_name}") os.makedirs(bench_dir, exist_ok=True) @@ -163,19 +165,7 @@ def handle_output(self, unitrace_output: str): shutil.move(os.path.join(options.benchmark_cwd, pid_json_files[-1]), json_name) log.debug(f"Moved {pid_json_files[-1]} to {json_name}") - # Create a per-benchmark archive (.tgz) containing both the .out and .json - try: - archive_path = unitrace_output[: -len(".out")] + ".tgz" - with tarfile.open(archive_path, "w:gz") as tar: - # add the .out file - if os.path.exists(unitrace_output): - tar.add(unitrace_output, arcname=os.path.basename(unitrace_output)) - # add the moved .json file - if os.path.exists(json_name): - tar.add(json_name, arcname=os.path.basename(json_name)) - log.info(f"Created Unitrace archive: {archive_path}") - except Exception as e: - log.warning(f"Failed to create Unitrace archive for {unitrace_output}: {e}") + log.info(f"Unitrace output files: {unitrace_output}, {json_name}") # Prune old unitrace directories self._prune_unitrace_dirs(os.path.dirname(unitrace_output)) diff --git a/devops/scripts/benchmarks/utils/utils.py b/devops/scripts/benchmarks/utils/utils.py index be37cb7bfb363..ae0f423f653e7 100644 --- a/devops/scripts/benchmarks/utils/utils.py +++ b/devops/scripts/benchmarks/utils/utils.py @@ -19,6 +19,19 @@ from utils.logger import log +def sanitize_filename(name: str) -> str: + """ + Sanitize a string to be safe for use as a filename or directory name. + Replace invalid characters with underscores. + + Invalid characters: " : < > | * ? \r \n + """ + # Replace invalid characters with underscores + invalid_chars = r'[":;<>|*?\r\n]' + sanitized = re.sub(invalid_chars, '_', name) + return sanitized + + def run( command, env_vars={}, From bf1819c67bcbbfe65fdc2174cae363c2a4b197c5 Mon Sep 17 00:00:00 2001 From: "Mateusz P. Nowak" Date: Mon, 8 Sep 2025 16:07:11 +0000 Subject: [PATCH 4/8] Simplify the flamegraphs interface Signed-off-by: Mateusz P. Nowak --- devops/scripts/benchmarks/html/scripts.js | 132 ++++++++++++------ devops/scripts/benchmarks/html/styles.css | 42 ++++++ devops/scripts/benchmarks/output_html.py | 66 +++------ devops/scripts/benchmarks/utils/flamegraph.py | 1 - 4 files changed, 155 insertions(+), 86 deletions(-) diff --git a/devops/scripts/benchmarks/html/scripts.js b/devops/scripts/benchmarks/html/scripts.js index 56ee9afe8180e..3b92d44a718fd 100644 --- a/devops/scripts/benchmarks/html/scripts.js +++ b/devops/scripts/benchmarks/html/scripts.js @@ -644,6 +644,50 @@ function createChartContainer(data, canvasId, type) { container.appendChild(contentSection); + // Add simple flamegraph links below the chart: left label, inline orange links + (function addFlamegraphLinks() { + try { + const flamegraphs = getFlameGraphsForBenchmark(data.label, activeRuns || new Set()); + if (!flamegraphs || flamegraphs.length === 0) return; + + const outer = document.createElement('div'); + outer.className = 'chart-flamegraph-links'; + + const label = document.createElement('div'); + label.className = 'flamegraph-label'; + label.textContent = 'Flamegraph(s):'; + + const links = document.createElement('div'); + links.className = 'flamegraph-links-inline'; + + flamegraphs.forEach(fg => { + const a = document.createElement('a'); + a.className = 'flamegraph-link'; + a.href = fg.path; + a.target = '_blank'; + + // flame emoticon before run name + const icon = document.createElement('span'); + icon.className = 'flame-icon'; + icon.textContent = '🔥'; + + const text = document.createElement('span'); + text.className = 'flame-text'; + text.textContent = fg.runName ? `${fg.runName}${fg.timestamp ? ' — ' + fg.timestamp : ''}` : (fg.timestamp || 'Flamegraph'); + + a.appendChild(icon); + a.appendChild(text); + links.appendChild(a); + }); + + outer.appendChild(label); + outer.appendChild(links); + container.appendChild(outer); + } catch (e) { + console.error('Error while adding flamegraph links for', data.label, e); + } + })(); + // Create footer section for details const footerSection = document.createElement('div'); footerSection.className = 'chart-footer'; @@ -1550,6 +1594,9 @@ function toggleAllTags(select) { function initializeCharts() { console.log('initializeCharts() started'); + console.log('loadedBenchmarkRuns:', loadedBenchmarkRuns.length, 'runs'); + console.log('First run name:', loadedBenchmarkRuns.length > 0 ? loadedBenchmarkRuns[0].name : 'no runs'); + console.log('defaultCompareNames:', defaultCompareNames); // Process raw data console.log('Processing timeseries data...'); @@ -1565,11 +1612,13 @@ function initializeCharts() { console.log('Layer comparisons data processed:', layerComparisonsData.length, 'items'); allRunNames = [...new Set(loadedBenchmarkRuns.map(run => run.name))]; + console.log('All run names:', allRunNames); // In flamegraph-only mode, ensure we include runs from flamegraph data if (validateFlameGraphData()) { const flamegraphRunNames = Object.keys(window.flamegraphData.runs); allRunNames = [...new Set([...allRunNames, ...flamegraphRunNames])]; + console.log('Added flamegraph runs, total run names:', allRunNames); } latestRunsLookup = createLatestRunsLookup(); @@ -1718,32 +1767,37 @@ window.toggleAllTags = toggleAllTags; // Helper function to fetch and process benchmark data function fetchAndProcessData(url, isArchived = false) { const loadingIndicator = document.getElementById('loading-indicator'); - return fetch(url) - .then(response => { - if (!response.ok) { throw new Error(`Got response status ${response.status}.`) } - return response.json(); - }) + .then(resp => { if (!resp.ok) throw new Error(`Got response status ${resp.status}.`); return resp.json(); }) .then(data => { - const newRuns = data.runs || data; - + if (!data || !Array.isArray(data.runs)) { + throw new Error('Invalid data format: expected { runs: [] , ... }'); + } if (isArchived) { - // Merge with existing data for archived data - loadedBenchmarkRuns = loadedBenchmarkRuns.concat(newRuns); + loadedBenchmarkRuns = loadedBenchmarkRuns.concat(data.runs); archivedDataLoaded = true; } else { - // Replace existing data for current data - loadedBenchmarkRuns = newRuns; + loadedBenchmarkRuns = data.runs; + window.benchmarkMetadata = data.metadata || {}; + window.benchmarkTags = data.tags || {}; + window.flamegraphData = (data.flamegraphData && data.flamegraphData.runs) ? data.flamegraphData : { runs: {} }; + if (Array.isArray(data.defaultCompareNames)) { + console.log('defaultCompareNames (remote):', data.defaultCompareNames); + } + console.log('Remote data loaded (canonical):', { + runs: data.runs.length, + metadata: Object.keys(window.benchmarkMetadata).length, + tags: Object.keys(window.benchmarkTags).length, + flamegraphs: Object.keys(window.flamegraphData.runs).length + }); } initializeCharts(); }) - .catch(error => { - console.error(`Error fetching ${isArchived ? 'archived' : 'remote'} data:`, error); + .catch(err => { + console.error(`Error fetching ${isArchived ? 'archived' : 'remote'} data:`, err); loadingIndicator.textContent = 'Fetching remote data failed.'; }) - .finally(() => { - loadingIndicator.classList.add('hidden'); - }); + .finally(() => loadingIndicator.classList.add('hidden')); } // Load data based on configuration @@ -1755,34 +1809,32 @@ function loadData() { if (typeof remoteDataUrl !== 'undefined' && remoteDataUrl !== '') { console.log('Using remote data URL:', remoteDataUrl); // Fetch data from remote URL - const url = remoteDataUrl.endsWith('/') ? remoteDataUrl + 'data.json' : remoteDataUrl + '/data.json'; - fetchAndProcessData(url); + fetchAndProcessData(remoteDataUrl); } else { - console.log('Using local data'); - // Use local data - console.log('benchmarkRuns available:', typeof benchmarkRuns, Array.isArray(benchmarkRuns) ? benchmarkRuns.length : 'not array'); - loadedBenchmarkRuns = benchmarkRuns; - // Assign global metadata from data.js if window.benchmarkMetadata is not set - if (!window.benchmarkMetadata) { - window.benchmarkMetadata = (typeof benchmarkMetadata !== 'undefined') ? benchmarkMetadata : {}; - } - console.log('benchmarkMetadata loaded:', Object.keys(window.benchmarkMetadata).length, 'items'); - // Assign global tags from data.js if window.benchmarkTags is not set - if (!window.benchmarkTags) { - window.benchmarkTags = (typeof benchmarkTags !== 'undefined') ? benchmarkTags : {}; - } - console.log('benchmarkTags loaded:', Object.keys(window.benchmarkTags).length, 'items'); - // Assign flamegraph data from data.js if available - if (typeof flamegraphData !== 'undefined') { - window.flamegraphData = flamegraphData; - console.log('Loaded flamegraph data from data.js with', Object.keys(flamegraphData.runs || {}).length, 'runs'); - } else { + console.log('Using local canonical data'); + if (typeof benchmarkDataCanonical !== 'object' || !Array.isArray(benchmarkDataCanonical.runs)) { + console.error('benchmarkDataCanonical missing or invalid'); + loadedBenchmarkRuns = []; + window.benchmarkMetadata = {}; + window.benchmarkTags = {}; window.flamegraphData = { runs: {} }; - console.log('No flamegraph data available'); + } else { + loadedBenchmarkRuns = benchmarkDataCanonical.runs; + window.benchmarkMetadata = benchmarkDataCanonical.metadata || {}; + window.benchmarkTags = benchmarkDataCanonical.tags || {}; + window.flamegraphData = (benchmarkDataCanonical.flamegraphData && benchmarkDataCanonical.flamegraphData.runs) ? benchmarkDataCanonical.flamegraphData : { runs: {} }; + if (Array.isArray(benchmarkDataCanonical.defaultCompareNames)) { + defaultCompareNames = benchmarkDataCanonical.defaultCompareNames; // assume global defined elsewhere + } + console.log('Local canonical data loaded:', { + runs: loadedBenchmarkRuns.length, + metadata: Object.keys(window.benchmarkMetadata).length, + tags: Object.keys(window.benchmarkTags).length, + flamegraphs: Object.keys(window.flamegraphData.runs).length + }); } - console.log('defaultCompareNames available:', typeof defaultCompareNames, Array.isArray(defaultCompareNames) ? defaultCompareNames : 'not defined'); initializeCharts(); - loadingIndicator.classList.add('hidden'); // Hide loading indicator + loadingIndicator.classList.add('hidden'); console.log('=== loadData() completed ==='); } } diff --git a/devops/scripts/benchmarks/html/styles.css b/devops/scripts/benchmarks/html/styles.css index 5ddb7a1c5d65a..58538ff681231 100644 --- a/devops/scripts/benchmarks/html/styles.css +++ b/devops/scripts/benchmarks/html/styles.css @@ -402,6 +402,48 @@ details[open] summary::after { cursor: help; font-size: 12px; } + +/* Flamegraph link area styles (used by scripts.js) */ +.chart-flamegraph-links { + display: flex; + align-items: center; + gap: 12px; + margin-top: 8px; + /* small margin below links to separate from any gray bar or footer */ + margin-bottom: 6px; +} +.flamegraph-label { + color: var(--text-dark); + font-weight: 600; +} +.flamegraph-links-inline { + display: flex; + gap: 8px; + flex-wrap: wrap; +} +.flamegraph-link { + color: var(--text-warning); + text-decoration: none; + font-weight: 500; + display: inline-flex; + align-items: center; + gap: 6px; +} +.flamegraph-link:hover { + text-decoration: underline; +} +.flame-icon { + font-size: 16px; + line-height: 1; + display: inline-block; +} +.flame-text { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 220px; + display: inline-block; +} #tag-filters { display: flex; flex-wrap: wrap; diff --git a/devops/scripts/benchmarks/output_html.py b/devops/scripts/benchmarks/output_html.py index 433ef7050a80b..7149c689231ea 100644 --- a/devops/scripts/benchmarks/output_html.py +++ b/devops/scripts/benchmarks/output_html.py @@ -71,56 +71,32 @@ def _write_output_to_file( # Define variable configuration based on whether we're archiving or not filename = "data_archive" if archive else "data" + # Emit unified canonical format for both local (JS wrapper) and remote (pure JSON) + output_data = json.loads(output.to_json()) # type: ignore + output_data["defaultCompareNames"] = output.default_compare_names + + if options.flamegraph: + flamegraph_data = _get_flamegraph_data(html_path) + if flamegraph_data and flamegraph_data.get("runs"): + output_data["flamegraphData"] = flamegraph_data + log.debug( + f"Added flamegraph data for {len(flamegraph_data['runs'])} runs to {filename}.*" + ) + if options.output_html == "local": + # Single JS assignment for canonical object data_path = os.path.join(html_path, f"{filename}.js") with open(data_path, "w") as f: - # For local format, we need to write JavaScript variable assignments - f.write("benchmarkRuns = ") - json.dump(json.loads(output.to_json())["runs"], f, indent=2) # type: ignore - f.write(";\n\n") - - f.write(f"benchmarkMetadata = ") - json.dump(json.loads(output.to_json())["metadata"], f, indent=2) # type: ignore - f.write(";\n\n") - - f.write(f"benchmarkTags = ") - json.dump(json.loads(output.to_json())["tags"], f, indent=2) # type: ignore - f.write(";\n\n") - - f.write(f"defaultCompareNames = ") - json.dump(output.default_compare_names, f, indent=2) - f.write(";\n\n") - - # Add flamegraph data if it exists - if options.flamegraph: - flamegraph_data = _get_flamegraph_data(html_path) - if flamegraph_data and flamegraph_data.get("runs"): - f.write("flamegraphData = ") - json.dump(flamegraph_data, f, indent=2) - f.write(";\n\n") - log.debug( - f"Added flamegraph data for {len(flamegraph_data['runs'])} runs to data.js" - ) - - if not archive: - log.info(f"See {html_path}/index.html for the results.") + f.write("benchmarkRuns = ") # kept for backward references, but embed full object under runs + # Write one canonical object as benchmarkDataCanonical for clarity, but keep benchmarkRuns for compatibility + f.write("undefined; /* placeholder to preserve legacy global; use benchmarkDataCanonical instead */\n") + f.write("benchmarkDataCanonical = ") + json.dump(output_data, f, indent=2) + f.write(";\n") + if not archive: + log.info(f"See {html_path}/index.html for the results.") else: - # For remote format, we write a single JSON file data_path = os.path.join(html_path, f"{filename}.json") - output_data = json.loads(output.to_json()) # type: ignore - - # Add defaultCompareNames to match the expected structure - output_data["defaultCompareNames"] = output.default_compare_names - - if options.flamegraph: - flamegraph_data = _get_flamegraph_data(html_path) - if flamegraph_data and flamegraph_data.get("runs"): - # Use "flamegraphData" to match the expected variable name - output_data["flamegraphData"] = flamegraph_data - log.debug( - f"Added flamegraph data for {len(flamegraph_data['runs'])} runs to {filename}.json" - ) - with open(data_path, "w") as f: json.dump(output_data, f, indent=2) log.info( diff --git a/devops/scripts/benchmarks/utils/flamegraph.py b/devops/scripts/benchmarks/utils/flamegraph.py index 65eb7e6d5a08e..7c5374e2b20c8 100644 --- a/devops/scripts/benchmarks/utils/flamegraph.py +++ b/devops/scripts/benchmarks/utils/flamegraph.py @@ -114,7 +114,6 @@ def handle_output(self, bench_name: str, perf_data_file: str, suite_name: str = perf_data_path.stem.replace(".perf", "") + ".svg" ) folded_file = perf_data_path.with_suffix(".folded") - try: self._convert_perf_to_folded(perf_data_path, folded_file) self._generate_svg(folded_file, svg_file, bench_name) From c4d50c95157fc094a0fbcc3a3db559cd25ace5f2 Mon Sep 17 00:00:00 2001 From: "Mateusz P. Nowak" Date: Tue, 16 Sep 2025 12:07:01 +0000 Subject: [PATCH 5/8] apply comments and fix CI error Signed-off-by: Mateusz P. Nowak --- devops/actions/run-tests/benchmark/action.yml | 3 +- devops/scripts/benchmarks/html/scripts.js | 67 ++++++------- devops/scripts/benchmarks/html/styles.css | 94 +++++++++++-------- devops/scripts/benchmarks/output_html.py | 39 +++++--- devops/scripts/benchmarks/utils/utils.py | 6 +- 5 files changed, 120 insertions(+), 89 deletions(-) diff --git a/devops/actions/run-tests/benchmark/action.yml b/devops/actions/run-tests/benchmark/action.yml index 622c849e8b5de..315f942b75c8e 100644 --- a/devops/actions/run-tests/benchmark/action.yml +++ b/devops/actions/run-tests/benchmark/action.yml @@ -209,7 +209,8 @@ runs: --preset "$PRESET" \ --timestamp-override "$SAVE_TIMESTAMP" \ --detect-version sycl,compute_runtime \ - --flamegraph inclusive + --flamegraph inclusive \ + --unitrace echo "-----" python3 ./devops/scripts/benchmarks/compare.py to_hist \ diff --git a/devops/scripts/benchmarks/html/scripts.js b/devops/scripts/benchmarks/html/scripts.js index 3b92d44a718fd..f82434994cd17 100644 --- a/devops/scripts/benchmarks/html/scripts.js +++ b/devops/scripts/benchmarks/html/scripts.js @@ -723,27 +723,17 @@ function createChartContainer(data, canvasId, type) { const listContainer = document.createElement('div'); listContainer.className = 'download-list'; - + + // Dynamic positioning (kept in JS) const rect = buttonElement.getBoundingClientRect(); - listContainer.style.position = 'absolute'; listContainer.style.top = `${window.scrollY + rect.bottom + 5}px`; listContainer.style.left = `${window.scrollX + rect.left}px`; - listContainer.style.zIndex = '1000'; - listContainer.style.backgroundColor = 'white'; - listContainer.style.border = '1px solid #ccc'; - listContainer.style.borderRadius = '4px'; - listContainer.style.padding = '4px'; - listContainer.style.minWidth = '200px'; - listContainer.style.boxShadow = '0 2px 5px rgba(0,0,0,0.15)'; flamegraphs.forEach(fg => { const link = document.createElement('a'); link.href = fg.path; link.textContent = fg.runName; - link.style.display = 'block'; - link.style.padding = '8px 12px'; - link.style.textDecoration = 'none'; - link.style.color = '#333'; + link.className = 'download-list-link'; link.onclick = (e) => { e.preventDefault(); window.open(fg.path, '_blank'); @@ -753,7 +743,7 @@ function createChartContainer(data, canvasId, type) { }); document.body.appendChild(listContainer); - + setTimeout(() => { document.addEventListener('click', function closeHandler(event) { if (!listContainer.contains(event.target) && !buttonElement.contains(event.target)) { @@ -1398,11 +1388,11 @@ function validateFlameGraphData() { function sanitizeFilename(name) { /** * Sanitize a string to be safe for use as a filename or directory name. - * Replace invalid characters with underscores. + * Replace invalid characters (including space) with underscores so paths are shell-safe. * - * Invalid characters: " : < > | * ? \r \n + * Invalid characters: " : < > | * ? \r \n */ - const invalidChars = /[":;<>|*?\r\n]/g; + const invalidChars = /[":;<>|*?\r\n ]/g; // Added space to align with Python implementation return name.replace(invalidChars, '_'); } @@ -1770,22 +1760,23 @@ function fetchAndProcessData(url, isArchived = false) { return fetch(url) .then(resp => { if (!resp.ok) throw new Error(`Got response status ${resp.status}.`); return resp.json(); }) .then(data => { - if (!data || !Array.isArray(data.runs)) { - throw new Error('Invalid data format: expected { runs: [] , ... }'); + const runsArray = Array.isArray(data.benchmarkRuns) ? data.benchmarkRuns : data.runs; + if (!runsArray || !Array.isArray(runsArray)) { + throw new Error('Invalid data format: expected benchmarkRuns or runs array'); } if (isArchived) { - loadedBenchmarkRuns = loadedBenchmarkRuns.concat(data.runs); + loadedBenchmarkRuns = loadedBenchmarkRuns.concat(runsArray); archivedDataLoaded = true; } else { - loadedBenchmarkRuns = data.runs; - window.benchmarkMetadata = data.metadata || {}; - window.benchmarkTags = data.tags || {}; + loadedBenchmarkRuns = runsArray; + window.benchmarkMetadata = data.benchmarkMetadata || data.metadata || {}; + window.benchmarkTags = data.benchmarkTags || data.tags || {}; window.flamegraphData = (data.flamegraphData && data.flamegraphData.runs) ? data.flamegraphData : { runs: {} }; if (Array.isArray(data.defaultCompareNames)) { - console.log('defaultCompareNames (remote):', data.defaultCompareNames); + defaultCompareNames = data.defaultCompareNames; } - console.log('Remote data loaded (canonical):', { - runs: data.runs.length, + console.log('Remote data loaded (normalized):', { + runs: runsArray.length, metadata: Object.keys(window.benchmarkMetadata).length, tags: Object.keys(window.benchmarkTags).length, flamegraphs: Object.keys(window.flamegraphData.runs).length @@ -1812,21 +1803,18 @@ function loadData() { fetchAndProcessData(remoteDataUrl); } else { console.log('Using local canonical data'); - if (typeof benchmarkDataCanonical !== 'object' || !Array.isArray(benchmarkDataCanonical.runs)) { - console.error('benchmarkDataCanonical missing or invalid'); + if (!Array.isArray(window.benchmarkRuns)) { + console.error('benchmarkRuns missing or invalid'); loadedBenchmarkRuns = []; window.benchmarkMetadata = {}; window.benchmarkTags = {}; window.flamegraphData = { runs: {} }; } else { - loadedBenchmarkRuns = benchmarkDataCanonical.runs; - window.benchmarkMetadata = benchmarkDataCanonical.metadata || {}; - window.benchmarkTags = benchmarkDataCanonical.tags || {}; - window.flamegraphData = (benchmarkDataCanonical.flamegraphData && benchmarkDataCanonical.flamegraphData.runs) ? benchmarkDataCanonical.flamegraphData : { runs: {} }; - if (Array.isArray(benchmarkDataCanonical.defaultCompareNames)) { - defaultCompareNames = benchmarkDataCanonical.defaultCompareNames; // assume global defined elsewhere - } - console.log('Local canonical data loaded:', { + loadedBenchmarkRuns = window.benchmarkRuns; + window.benchmarkMetadata = window.benchmarkMetadata || {}; + window.benchmarkTags = window.benchmarkTags || {}; + window.flamegraphData = (window.flamegraphData && window.flamegraphData.runs) ? window.flamegraphData : { runs: {} }; + console.log('Local data loaded (standalone globals):', { runs: loadedBenchmarkRuns.length, metadata: Object.keys(window.benchmarkMetadata).length, tags: Object.keys(window.benchmarkTags).length, @@ -1834,7 +1822,12 @@ function loadData() { }); } initializeCharts(); - loadingIndicator.classList.add('hidden'); + if (loadedBenchmarkRuns.length === 0) { + loadingIndicator.textContent = 'No benchmark data found.'; + loadingIndicator.setAttribute('role', 'alert'); // optional accessibility + } else { + loadingIndicator.classList.add('hidden'); // hide when data present + } console.log('=== loadData() completed ==='); } } diff --git a/devops/scripts/benchmarks/html/styles.css b/devops/scripts/benchmarks/html/styles.css index 58538ff681231..7b68942b85bc5 100644 --- a/devops/scripts/benchmarks/html/styles.css +++ b/devops/scripts/benchmarks/html/styles.css @@ -3,8 +3,11 @@ ======================================== */ :root { /* Core Color Palette - only used colors from scripts.js */ - --color-red: rgb(255, 50, 80); - --color-orange: rgb(255, 145, 15); + /* Provide RGB component vars to allow reuse in rgba() backgrounds */ + --color-red-rgb: 255, 50, 80; + --color-orange-rgb: 255, 145, 15; + --color-red: rgb(var(--color-red-rgb)); + --color-orange: rgb(var(--color-orange-rgb)); --color-yellow: rgb(255, 220, 0); --color-green: rgb(20, 200, 50); --color-blue: rgb(0, 130, 255); @@ -24,12 +27,14 @@ /* Backgrounds - consolidated similar grays */ --bg-body: #f8f9fa; /* Replaces bg-lighter, bg-light-gray */ --bg-white: white; + --bg-hover: #f5f5f5; /* used for hover states (was fallback) */ --bg-light: #e9ecef; /* Replaces bg-disabled */ --bg-summary: #dee2e6; --bg-summary-hover: #ced4da; --bg-info: #cfe2ff; - --bg-warning: rgba(255, 145, 15, 0.1); /* Light orange background for warnings */ - --bg-danger: rgba(255, 50, 80, 0.1); /* Light red background for errors */ + --bg-warning: rgba(var(--color-orange-rgb), 0.1); /* Light orange background for warnings */ + --bg-danger: rgba(var(--color-red-rgb), 0.1); /* Light red background for errors */ + background: var(--bg-white); /* Borders - simplified */ --border-light: #ccc; @@ -57,10 +62,10 @@ h1, h2 { font-weight: 500; } .chart-container { - background: white; - border-radius: 8px; + /* Removed direct literal 'white'; using themed hover background */ + background: var(--bg-hover); padding: 24px; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + box-shadow: var(--shadow-subtle); position: relative; display: flex; flex-direction: column; @@ -168,7 +173,7 @@ details[open] summary::after { .run-selector button { padding: 8px 16px; background: var(--color-blue); - color: white; + color: var(--bg-white); border: none; border-radius: 4px; cursor: pointer; @@ -213,31 +218,43 @@ details[open] summary::after { } .download-list { - position: absolute !important; - z-index: 1000 !important; - background: var(--bg-white) !important; - border: 1px solid var(--border-medium) !important; - border-radius: 4px !important; - box-shadow: var(--shadow-dropdown) !important; - padding: 4px !important; - min-width: 200px !important; - max-width: 300px !important; -} - -.download-list a { - display: block !important; - padding: 8px 12px !important; - text-decoration: none !important; - color: var(--text-dark) !important; - border-radius: 2px !important; - font-size: 14px !important; - white-space: nowrap !important; - overflow: hidden !important; - text-overflow: ellipsis !important; + position: absolute; + z-index: 1000; + background: var(--bg-white); + border: 1px solid var(--border-medium); + border-radius: 4px; + padding: 4px; + min-width: 200px; + max-width: 300px; + box-shadow: var(--shadow-dropdown); + font-size: 0.9rem; +} + +/* Support both legacy .download-list-link and generic anchors inside list */ +.download-list a, +.download-list-link { + display: block; + padding: 8px 12px; + text-decoration: none; + color: var(--text-dark); + border-radius: 2px; + font-size: 14px; + line-height: 1.2; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.download-list a:hover, +.download-list a:focus, +.download-list-link:hover, +.download-list-link:focus { + background: var(--bg-light); + outline: none; } -.download-list a:hover { - background-color: var(--bg-light) !important; +.chart-download-button { + margin-right: 8px; } .loading-indicator { text-align: center; @@ -470,7 +487,7 @@ details[open] summary::after { .remove-tag { background: none; border: none; - color: white; + color: var(--bg-white); margin-left: 4px; cursor: pointer; font-size: 16px; @@ -481,7 +498,7 @@ details[open] summary::after { } .platform { padding: 16px; - background: white; + background: var(--bg-white); border-radius: 8px; margin-top: 8px; } @@ -571,9 +588,10 @@ details[open] summary::after { } .flamegraph-error { - background-color: var(--bg-warning); - border-color: var(--color-orange); - color: var(--text-warning); + /* Repurposed to use dedicated danger background */ + background-color: var(--bg-danger); + border-color: var(--text-danger); + color: var(--text-danger); } /* ======================================== @@ -670,8 +688,8 @@ details[open] summary::after { z-index: 1000; border: 1px solid var(--border-light); border-radius: 4px; - background-color: white; - box-shadow: 0 2px 5px rgba(0,0,0,0.15); + background-color: var(--bg-white); + box-shadow: var(--shadow-dropdown); padding: 5px; margin-top: 5px; } diff --git a/devops/scripts/benchmarks/output_html.py b/devops/scripts/benchmarks/output_html.py index 57eb000ebdf46..cad51c94d30c8 100644 --- a/devops/scripts/benchmarks/output_html.py +++ b/devops/scripts/benchmarks/output_html.py @@ -70,10 +70,7 @@ def _write_output_to_file( """ # Define variable configuration based on whether we're archiving or not filename = "data_archive" if archive else "data" - - # Emit unified canonical format for both local (JS wrapper) and remote (pure JSON) output_data = json.loads(output.to_json()) # type: ignore - output_data["defaultCompareNames"] = output.default_compare_names if options.flamegraph: flamegraph_data = _get_flamegraph_data(html_path) @@ -83,24 +80,44 @@ def _write_output_to_file( f"Added flamegraph data for {len(flamegraph_data['runs'])} runs to {filename}.*" ) + runs_list = output_data.get("runs", []) + if options.output_html == "local": - # Single JS assignment for canonical object + # Local JS: emit standalone globals (legacy-style) without wrapper object data_path = os.path.join(html_path, f"{filename}.js") with open(data_path, "w") as f: - # kept for backward references, but embed full object under runs f.write("benchmarkRuns = ") - f.write( - "undefined; /* placeholder to preserve legacy global; use benchmarkDataCanonical instead */\n" - ) - f.write("benchmarkDataCanonical = ") - json.dump(output_data, f, indent=2) + json.dump(runs_list, f, indent=2) + f.write(";\n") + if "flamegraphData" in output_data: + f.write("flamegraphData = ") + json.dump(output_data["flamegraphData"], f, indent=2) + f.write(";\n") + else: + f.write("flamegraphData = { runs: {} };\n") + f.write("benchmarkMetadata = ") + json.dump(output_data.get("metadata", {}), f, indent=2) + f.write(";\n") + f.write("benchmarkTags = ") + json.dump(output_data.get("tags", {}), f, indent=2) + f.write(";\n") + f.write("defaultCompareNames = ") + json.dump(output.default_compare_names, f) f.write(";\n") if not archive: log.info(f"See {html_path}/index.html for the results.") else: + # Remote JSON: emit flat schema aligning with local globals + remote_obj = { + "benchmarkRuns": runs_list, + "benchmarkMetadata": output_data.get("metadata", {}), + "benchmarkTags": output_data.get("tags", {}), + "flamegraphData": output_data.get("flamegraphData", {"runs": {}}), + "defaultCompareNames": output.default_compare_names, + } data_path = os.path.join(html_path, f"{filename}.json") with open(data_path, "w") as f: - json.dump(output_data, f, indent=2) + json.dump(remote_obj, f, indent=2) log.info( f"Upload {data_path} to a location set in config.js remoteDataUrl argument." ) diff --git a/devops/scripts/benchmarks/utils/utils.py b/devops/scripts/benchmarks/utils/utils.py index 49bc9474ae4ce..89276f79d7584 100644 --- a/devops/scripts/benchmarks/utils/utils.py +++ b/devops/scripts/benchmarks/utils/utils.py @@ -26,7 +26,8 @@ def sanitize_filename(name: str) -> str: Invalid characters: " : < > | * ? \r \n """ # Replace invalid characters with underscores - invalid_chars = r'[":;<>|*?\r\n]' + # Added space to list to avoid directories with spaces which cause issues in shell commands + invalid_chars = r'[":;<>|*?\r\n ]' sanitized = re.sub(invalid_chars, "_", name) return sanitized @@ -198,7 +199,8 @@ def download(dir, url, file, untar=False, unzip=False, checksum=""): if unzip: [stripped_gz, _] = os.path.splitext(data_file) with gzip.open(data_file, "rb") as f_in, open(stripped_gz, "wb") as f_out: - shutil.copyfileobj(f_in, f_out) + # copyfileobj expects binary file-like objects; type checker may complain about union types + shutil.copyfileobj(f_in, f_out) # type: ignore[arg-type] else: log.debug(f"{data_file} exists, skipping...") return data_file From ff4f3bbcf347678edc2264c44c143374de5d37c0 Mon Sep 17 00:00:00 2001 From: "Mateusz P. Nowak" Date: Mon, 22 Sep 2025 10:06:51 +0000 Subject: [PATCH 6/8] disable unitrace --- devops/actions/run-tests/benchmark/action.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/devops/actions/run-tests/benchmark/action.yml b/devops/actions/run-tests/benchmark/action.yml index 315f942b75c8e..622c849e8b5de 100644 --- a/devops/actions/run-tests/benchmark/action.yml +++ b/devops/actions/run-tests/benchmark/action.yml @@ -209,8 +209,7 @@ runs: --preset "$PRESET" \ --timestamp-override "$SAVE_TIMESTAMP" \ --detect-version sycl,compute_runtime \ - --flamegraph inclusive \ - --unitrace + --flamegraph inclusive echo "-----" python3 ./devops/scripts/benchmarks/compare.py to_hist \ From a3307296ba1465925c895fff505d013f28c59a9c Mon Sep 17 00:00:00 2001 From: "Mateusz P. Nowak" Date: Fri, 26 Sep 2025 14:14:37 +0000 Subject: [PATCH 7/8] apply comment and fix format --- devops/scripts/benchmarks/html/scripts.js | 149 +++++++++++----------- 1 file changed, 74 insertions(+), 75 deletions(-) diff --git a/devops/scripts/benchmarks/html/scripts.js b/devops/scripts/benchmarks/html/scripts.js index f82434994cd17..8c7460db1da49 100644 --- a/devops/scripts/benchmarks/html/scripts.js +++ b/devops/scripts/benchmarks/html/scripts.js @@ -25,7 +25,7 @@ let loadedBenchmarkRuns = []; // Loaded results from the js/json files // Helper function to get base URL for remote or local resources function getResourceBaseUrl() { - return typeof remoteDataUrl !== 'undefined' && remoteDataUrl !== '' + return typeof remoteDataUrl !== 'undefined' && remoteDataUrl !== '' ? 'https://raw.githubusercontent.com/intel/llvm-ci-perf-results/unify-ci' : '.'; } @@ -59,7 +59,7 @@ const toggleConfigs = { defaultValue: true, urlParam: 'notes', invertUrlParam: true, // Store false in URL when enabled (legacy behavior) - onChange: function(isEnabled) { + onChange: function (isEnabled) { document.querySelectorAll('.benchmark-note').forEach(note => { note.style.display = isEnabled ? 'block' : 'none'; }); @@ -70,7 +70,7 @@ const toggleConfigs = { defaultValue: false, urlParam: 'unstable', invertUrlParam: false, - onChange: function(isEnabled) { + onChange: function (isEnabled) { document.querySelectorAll('.benchmark-unstable').forEach(warning => { warning.style.display = isEnabled ? 'block' : 'none'; }); @@ -81,7 +81,7 @@ const toggleConfigs = { defaultValue: false, urlParam: 'customRange', invertUrlParam: false, - onChange: function(isEnabled) { + onChange: function (isEnabled) { updateCharts(); } }, @@ -89,7 +89,7 @@ const toggleConfigs = { defaultValue: false, urlParam: 'archived', invertUrlParam: false, - onChange: function(isEnabled) { + onChange: function (isEnabled) { if (isEnabled) { loadArchivedData(); } else { @@ -105,7 +105,7 @@ const toggleConfigs = { defaultValue: false, urlParam: 'flamegraph', invertUrlParam: false, - onChange: function(isEnabled) { + onChange: function (isEnabled) { // Toggle between flamegraph-only display and normal charts updateCharts(); updateFlameGraphTooltip(); @@ -127,7 +127,7 @@ function setupToggle(toggleId, config) { if (!toggle) return; // Set up event listener - toggle.addEventListener('change', function() { + toggle.addEventListener('change', function () { config.onChange(toggle.checked); }); @@ -484,7 +484,7 @@ function drawCharts(filteredTimeseriesData, filteredBarChartsData, filteredLayer const containerId = `timeseries-${index}`; const container = createChartContainer(data, containerId, 'benchmark'); document.querySelector('.timeseries .charts').appendChild(container); - + // Only set up chart observers if not in flamegraph mode if (!isFlameGraphEnabled()) { pendingCharts.set(containerId, { data, type: 'time' }); @@ -497,7 +497,7 @@ function drawCharts(filteredTimeseriesData, filteredBarChartsData, filteredLayer const containerId = `layer-comparison-${index}`; const container = createChartContainer(data, containerId, 'group'); document.querySelector('.layer-comparisons .charts').appendChild(container); - + // Only set up chart observers if not in flamegraph mode if (!isFlameGraphEnabled()) { pendingCharts.set(containerId, { data, type: 'time' }); @@ -510,7 +510,7 @@ function drawCharts(filteredTimeseriesData, filteredBarChartsData, filteredLayer const containerId = `barchart-${index}`; const container = createChartContainer(data, containerId, 'group'); document.querySelector('.bar-charts .charts').appendChild(container); - + // Only set up chart observers if not in flamegraph mode if (!isFlameGraphEnabled()) { pendingCharts.set(containerId, { data, type: 'bar' }); @@ -598,7 +598,7 @@ function createChartContainer(data, canvasId, type) { if (isFlameGraphEnabled()) { // Get all flamegraph data for this benchmark from selected runs const flamegraphsToShow = getFlameGraphsForBenchmark(data.label, activeRuns); - + if (flamegraphsToShow.length > 0) { // Add a class to reduce padding for flamegraph containers container.classList.add('flamegraph-chart'); @@ -607,24 +607,24 @@ function createChartContainer(data, canvasId, type) { // Create a dedicated container for this flamegraph const flamegraphContainer = document.createElement('div'); flamegraphContainer.className = 'flamegraph-container'; - + const iframe = document.createElement('iframe'); iframe.src = flamegraphInfo.path; iframe.className = 'flamegraph-iframe'; iframe.title = `${flamegraphInfo.runName} - ${data.label}`; - + // Add error handling for missing flamegraph files - iframe.onerror = function() { + iframe.onerror = function () { const errorDiv = document.createElement('div'); errorDiv.className = 'flamegraph-error'; errorDiv.textContent = `No flamegraph available for ${flamegraphInfo.runName} - ${data.label}`; flamegraphContainer.replaceChild(errorDiv, iframe); }; - + flamegraphContainer.appendChild(iframe); contentSection.appendChild(flamegraphContainer); }); - + // No need for resize handling since CSS handles all sizing // The flamegraphs will automatically use the full container width } else { @@ -758,7 +758,7 @@ function createChartContainer(data, canvasId, type) { const chartButton = document.createElement('button'); chartButton.className = 'download-button chart-download-button'; chartButton.style.marginRight = '8px'; - + // Function to update button based on current mode function updateChartButton() { if (isFlameGraphEnabled()) { @@ -790,9 +790,9 @@ function createChartContainer(data, canvasId, type) { }; } } - + updateChartButton(); - + // Store the update function on the button so it can be called when mode changes chartButton.updateChartButton = updateChartButton; @@ -1403,9 +1403,8 @@ function createFlameGraphPath(benchmarkLabel, runName, timestamp) { console.error(`Could not find suite for benchmark '${benchmarkLabel}' in run '${runName}'`); // Fallback: sanitize benchmark name for directory structure const sanitizedBenchmarkName = sanitizeFilename(benchmarkLabel); - const benchmarkDirName = sanitizedBenchmarkName; const timestampPrefix = timestamp + '_'; - const relativePath = `results/flamegraphs/${benchmarkDirName}/${timestampPrefix}${runName}.svg`; + const relativePath = `results/flamegraphs/${sanitizedBenchmarkName}/${timestampPrefix}${runName}.svg`; // For local mode, use relative path; for remote mode, use full URL const baseUrl = getResourceBaseUrl(); @@ -1429,11 +1428,11 @@ function getRunsWithFlameGraph(benchmarkLabel, activeRuns) { if (!window.flamegraphData?.runs) { return []; } - + const runsWithFlameGraph = []; activeRuns.forEach(runName => { - if (window.flamegraphData.runs[runName] && - window.flamegraphData.runs[runName].suites && + if (window.flamegraphData.runs[runName] && + window.flamegraphData.runs[runName].suites && Object.keys(window.flamegraphData.runs[runName].suites).includes(benchmarkLabel)) { runsWithFlameGraph.push({ name: runName, @@ -1441,25 +1440,25 @@ function getRunsWithFlameGraph(benchmarkLabel, activeRuns) { }); } }); - + return runsWithFlameGraph; } function getFlameGraphsForBenchmark(benchmarkLabel, activeRuns) { const runsWithFlameGraph = getRunsWithFlameGraph(benchmarkLabel, activeRuns); const flamegraphsToShow = []; - + // For each run that has flamegraph data, create the path runsWithFlameGraph.forEach(runInfo => { const flamegraphPath = createFlameGraphPath(benchmarkLabel, runInfo.name, runInfo.timestamp); - + flamegraphsToShow.push({ path: flamegraphPath, runName: runInfo.name, timestamp: runInfo.timestamp }); }); - + // Sort by the order of activeRuns to maintain consistent display order const runOrder = Array.from(activeRuns); flamegraphsToShow.sort((a, b) => { @@ -1467,23 +1466,23 @@ function getFlameGraphsForBenchmark(benchmarkLabel, activeRuns) { const indexB = runOrder.indexOf(b.runName); return indexA - indexB; }); - + return flamegraphsToShow; } function updateFlameGraphTooltip() { const flameGraphToggle = document.getElementById('show-flamegraph'); const label = document.querySelector('label[for="show-flamegraph"]'); - + if (!flameGraphToggle || !label) return; - + // Check if we have flamegraph data if (validateFlameGraphData()) { const runsWithFlameGraphs = Object.keys(window.flamegraphData.runs).filter( - runName => window.flamegraphData.runs[runName].suites && - Object.keys(window.flamegraphData.runs[runName].suites).length > 0 + runName => window.flamegraphData.runs[runName].suites && + Object.keys(window.flamegraphData.runs[runName].suites).length > 0 ); - + if (runsWithFlameGraphs.length > 0) { label.title = `Show flamegraph SVG files instead of benchmark charts. Available for runs: ${runsWithFlameGraphs.join(', ')}`; flameGraphToggle.disabled = false; @@ -1587,39 +1586,39 @@ function initializeCharts() { console.log('loadedBenchmarkRuns:', loadedBenchmarkRuns.length, 'runs'); console.log('First run name:', loadedBenchmarkRuns.length > 0 ? loadedBenchmarkRuns[0].name : 'no runs'); console.log('defaultCompareNames:', defaultCompareNames); - + // Process raw data console.log('Processing timeseries data...'); timeseriesData = processTimeseriesData(); console.log('Timeseries data processed:', timeseriesData.length, 'items'); - + console.log('Processing bar charts data...'); barChartsData = processBarChartsData(); console.log('Bar charts data processed:', barChartsData.length, 'items'); - + console.log('Processing layer comparisons data...'); layerComparisonsData = processLayerComparisonsData(); console.log('Layer comparisons data processed:', layerComparisonsData.length, 'items'); - + allRunNames = [...new Set(loadedBenchmarkRuns.map(run => run.name))]; console.log('All run names:', allRunNames); - + // In flamegraph-only mode, ensure we include runs from flamegraph data if (validateFlameGraphData()) { const flamegraphRunNames = Object.keys(window.flamegraphData.runs); allRunNames = [...new Set([...allRunNames, ...flamegraphRunNames])]; console.log('Added flamegraph runs, total run names:', allRunNames); } - + latestRunsLookup = createLatestRunsLookup(); console.log('Run names and lookup created. Runs:', allRunNames); // Check if we have actual benchmark results vs flamegraph-only results - const hasActualBenchmarks = loadedBenchmarkRuns.some(run => + const hasActualBenchmarks = loadedBenchmarkRuns.some(run => run.results && run.results.length > 0 && run.results.some(result => result.suite !== 'flamegraph') ); - - const hasFlameGraphResults = loadedBenchmarkRuns.some(run => + + const hasFlameGraphResults = loadedBenchmarkRuns.some(run => run.results && run.results.some(result => result.suite === 'flamegraph') ) || (validateFlameGraphData() && Object.keys(window.flamegraphData.runs).length > 0); @@ -1639,16 +1638,16 @@ function initializeCharts() { // If we only have flamegraph results (no actual benchmark data), create synthetic data if (!hasActualBenchmarks && hasFlameGraphResults) { console.log('Detected flamegraph-only mode - creating synthetic data for flamegraphs'); - + // Check if we have flamegraph data available - const hasFlamegraphData = validateFlameGraphData() && - Object.keys(window.flamegraphData.runs).length > 0 && - Object.values(window.flamegraphData.runs).some(run => run.suites && Object.keys(run.suites).length > 0); - + const hasFlamegraphData = validateFlameGraphData() && + Object.keys(window.flamegraphData.runs).length > 0 && + Object.values(window.flamegraphData.runs).some(run => run.suites && Object.keys(run.suites).length > 0); + if (hasFlamegraphData) { console.log('Creating synthetic benchmark data for flamegraph display'); createFlameGraphOnlyData(); - + // Auto-enable flamegraph mode for user convenience const flameGraphToggle = document.getElementById('show-flamegraph'); if (flameGraphToggle && !flameGraphToggle.checked) { @@ -1695,7 +1694,7 @@ function initializeCharts() { } else { // No runs parameter, use defaults activeRuns = new Set(defaultCompareNames || []); - + // If no default runs and we're in flamegraph-only mode, use all available runs if (activeRuns.size === 0 && !hasActualBenchmarks && hasFlameGraphResults) { activeRuns = new Set(allRunNames); @@ -1925,17 +1924,17 @@ function displaySelectedRunsPlatformInfo() { selectedRunsWithPlatform.forEach(runData => { const runSection = document.createElement('div'); runSection.className = 'platform-run-section'; - const runTitle = document.createElement('h3'); + const runTitle = document.createElement('h3'); runTitle.textContent = `Run: ${runData.name}`; runTitle.className = 'platform-run-title'; runSection.appendChild(runTitle); - // Create just the platform details without the grid wrapper + // Create just the platform details without the grid wrapper const platform = runData.platform; const detailsContainer = document.createElement('div'); detailsContainer.className = 'platform-details-compact'; detailsContainer.innerHTML = createPlatformDetailsHTML(platform); runSection.appendChild(detailsContainer); - container.appendChild(runSection); + container.appendChild(runSection); }); } @@ -1969,8 +1968,8 @@ function createPlatformDetailsHTML(platform) { GPU:
${platform.gpu_info && platform.gpu_info.length > 0 - ? platform.gpu_info.map(gpu => `
• ${gpu}
`).join('') - : '
• No GPU detected
'} + ? platform.gpu_info.map(gpu => `
• ${gpu}
`).join('') + : '
• No GPU detected
'}
@@ -2000,7 +1999,7 @@ function createFlameGraphOnlyData() { // Collect all unique benchmarks from all runs that have flamegraphs const allBenchmarks = new Set(); const availableRuns = Object.keys(window.flamegraphData.runs); - + availableRuns.forEach(runName => { if (window.flamegraphData.runs[runName].suites) { Object.keys(window.flamegraphData.runs[runName].suites).forEach(benchmark => { @@ -2008,7 +2007,7 @@ function createFlameGraphOnlyData() { }); } }); - + if (allBenchmarks.size > 0) { console.log(`Using flamegraphData from data.js for runs: ${availableRuns.join(', ')}`); console.log(`Available benchmarks with flamegraphs: ${Array.from(allBenchmarks).join(', ')}`); @@ -2016,26 +2015,26 @@ function createFlameGraphOnlyData() { return; // Success - we have flamegraph data } } - + // No flamegraph data available - benchmarks were run without --flamegraph option console.log('No flamegraph data found - benchmarks were likely run without --flamegraph option'); - + // Disable the flamegraph checkbox since no flamegraphs are available const flameGraphToggle = document.getElementById('show-flamegraph'); if (flameGraphToggle) { flameGraphToggle.disabled = true; flameGraphToggle.checked = false; - + // Add a visual indicator that flamegraphs are not available const label = document.querySelector('label[for="show-flamegraph"]'); if (label) { label.classList.add('disabled-text'); label.title = 'No flamegraph data available - run benchmarks with --flamegraph option to enable'; } - + console.log('Disabled flamegraph toggle - no flamegraph data available'); } - + // Clear any flamegraph-only mode detection and proceed with normal benchmark display // This handles the case where we're in flamegraph-only mode but have no actual flamegraph data } @@ -2045,10 +2044,10 @@ function displayNoFlameGraphsMessage() { timeseriesData = []; barChartsData = []; layerComparisonsData = []; - + // Add a special suite for the message suiteNames.add('Information'); - + // Create a special entry to show a helpful message const messageData = { label: 'No FlameGraphs Available', @@ -2060,7 +2059,7 @@ function displayNoFlameGraphsMessage() { range_max: null, runs: {} }; - + timeseriesData.push(messageData); console.log('Added informational message about missing flamegraphs'); } @@ -2070,10 +2069,10 @@ function displayNoDataMessage() { timeseriesData = []; barChartsData = []; layerComparisonsData = []; - + // Add a special suite for the message suiteNames.add('Information'); - + // Create a special entry to show a helpful message const messageData = { label: 'No Data Available', @@ -2085,7 +2084,7 @@ function displayNoDataMessage() { range_max: null, runs: {} }; - + timeseriesData.push(messageData); console.log('Added informational message about missing benchmark data'); } @@ -2095,12 +2094,12 @@ function createSyntheticFlameGraphData(flamegraphLabels) { timeseriesData = []; barChartsData = []; layerComparisonsData = []; - + // Create synthetic benchmark results for each flamegraph flamegraphLabels.forEach(label => { // Get suite from flamegraphData - this should always be available let suite = null; - + if (window.flamegraphData?.runs) { // Check all runs for suite information for this benchmark for (const runName in window.flamegraphData.runs) { @@ -2111,16 +2110,16 @@ function createSyntheticFlameGraphData(flamegraphLabels) { } } } - + // If no suite found, this indicates a problem with the flamegraph data generation if (!suite) { console.error(`No suite information found for flamegraph: ${label}. This indicates missing suite data in flamegraphs.js`); suite = `ERROR: Missing suite for ${label}`; } - + // Add to suite names suiteNames.add(suite); - + // Create a synthetic timeseries entry for this flamegraph const syntheticData = { label: label, @@ -2132,11 +2131,11 @@ function createSyntheticFlameGraphData(flamegraphLabels) { range_max: null, runs: {} }; - + // Add this to timeseriesData so it shows up in the charts timeseriesData.push(syntheticData); }); - + console.log(`Created synthetic data for ${flamegraphLabels.length} flamegraphs with suites:`, Array.from(suiteNames)); } From 003633a76949b72f5216e8d0a125f190de7b4fec Mon Sep 17 00:00:00 2001 From: "Mateusz P. Nowak" Date: Fri, 3 Oct 2025 12:06:26 +0000 Subject: [PATCH 8/8] fix CI scripts --- devops/actions/run-tests/benchmark/action.yml | 5 ----- devops/scripts/install_build_tools.sh | 2 ++ 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/devops/actions/run-tests/benchmark/action.yml b/devops/actions/run-tests/benchmark/action.yml index 622c849e8b5de..add36688f94b5 100644 --- a/devops/actions/run-tests/benchmark/action.yml +++ b/devops/actions/run-tests/benchmark/action.yml @@ -133,11 +133,6 @@ runs: cmake --install build cd - - - name: Install linux-tools package - shell: bash - run: | - sudo apt-get update - sudo apt-get install -y linux-tools-generic linux-tools-$(uname -r) - name: Checkout results repo shell: bash run: | diff --git a/devops/scripts/install_build_tools.sh b/devops/scripts/install_build_tools.sh index 39ad259550d6f..1a1aa6dccda63 100755 --- a/devops/scripts/install_build_tools.sh +++ b/devops/scripts/install_build_tools.sh @@ -26,6 +26,8 @@ apt update && apt install -yqq \ curl \ libhwloc-dev \ libzstd-dev \ + linux-tools-generic \ + linux-tools-common \ time # To obtain latest release of spriv-tool.