Skip to content

Commit f86a84f

Browse files
committed
ci: docs: include the power statistics in the documentation deployment
Similar to how `ci-stats` works, add `power-stats` job in the workflow, and add links to the power stats pages for supported boards Signed-off-by: Sherry Li <xiaoruli@tenstorrent.com>
1 parent dd29b93 commit f86a84f

File tree

2 files changed

+182
-1
lines changed

2 files changed

+182
-1
lines changed

.github/workflows/docs.yml

Lines changed: 178 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,9 +162,177 @@ jobs:
162162
ci_stats.html
163163
*results.csv
164164
165+
power-stats:
166+
needs: [gen-config]
167+
strategy:
168+
matrix:
169+
board: ${{ fromJson(needs.gen-config.outputs.ci-boards) }}
170+
runs-on: ubuntu-22.04
171+
permissions:
172+
actions: read
173+
steps:
174+
- name: Setup Node.js
175+
uses: actions/setup-node@v4
176+
with:
177+
node-version: '20'
178+
179+
- name: Install dependencies
180+
run: |
181+
npm install adm-zip csv-parse csv-stringify
182+
pip install plotly pandas
183+
184+
- name: Checkout
185+
uses: actions/checkout@v4
186+
with:
187+
path: tt-zephyr-platforms
188+
189+
- name: Download Past Power Recordings
190+
uses: actions/github-script@v8
191+
with:
192+
github-token: ${{ secrets.GITHUB_TOKEN }}
193+
script: |
194+
const AdmZip = require('adm-zip');
195+
const fs = require('fs');
196+
const { parse } = require('csv-parse/sync');
197+
const { stringify } = require('csv-stringify/sync');
198+
199+
// Aggregate power data by workflow run
200+
const powerResults = {};
201+
const artifacts = await github.rest.actions.listArtifactsForRepo({
202+
owner: context.repo.owner,
203+
repo: context.repo.repo,
204+
name: 'Power recording (${{ matrix.board }})',
205+
});
206+
console.log(`Total artifacts found: ${artifacts.data.artifacts.length}`);
207+
208+
for (const artifact of artifacts.data.artifacts) {
209+
const download = await github.rest.actions.downloadArtifact({
210+
owner: context.repo.owner,
211+
repo: context.repo.repo,
212+
artifact_id: artifact.id,
213+
archive_format: 'zip'
214+
});
215+
216+
const fname = `${artifact.name}-${artifact.id}-${artifact.workflow_run?.head_sha || 'unknown'}.zip`;
217+
fs.writeFileSync(fname, Buffer.from(download.data));
218+
219+
const zip = new AdmZip(fname);
220+
const zipEntries = zip.getEntries();
221+
222+
zipEntries.forEach(function (zipEntry) {
223+
if (zipEntry.name === "power_recording.csv") {
224+
console.log(`Processing ${zipEntry.entryName} from artifact ${artifact.name} (${artifact.id})`);
225+
const content = zipEntry.getData().toString('utf8');
226+
227+
// Parse CSV content
228+
const records = parse(content, {
229+
columns: true,
230+
trim: true
231+
});
232+
233+
// Filter out N/A values and calculate statistics
234+
const powerValues = records
235+
.map(record => {
236+
const power = record.power_watts;
237+
if (power && power !== 'N/A') {
238+
const numPower = parseFloat(power);
239+
if (!isNaN(numPower)) {
240+
return numPower;
241+
}
242+
}
243+
return null;
244+
})
245+
.filter(val => val !== null);
246+
247+
if (powerValues.length === 0) {
248+
console.log(`No valid power values found in ${zipEntry.entryName}`);
249+
return;
250+
}
251+
252+
const workflowRunId = artifact.workflow_run?.id;
253+
if (!workflowRunId) {
254+
console.log(`Skipping artifact ${artifact.id}, no workflow_run`);
255+
return;
256+
}
257+
258+
if (artifact.workflow_run.head_branch != 'main') {
259+
console.log(`Skipping artifact ${artifact.id}, branch ${artifact.workflow_run.head_branch}`);
260+
return;
261+
}
262+
263+
// Calculate aggregate statistics
264+
const avgPower = powerValues.reduce((a, b) => a + b, 0) / powerValues.length;
265+
const maxPower = Math.max(...powerValues);
266+
const minPower = Math.min(...powerValues);
267+
268+
if (!powerResults[workflowRunId]) {
269+
powerResults[workflowRunId] = {
270+
commit: artifact.workflow_run.head_sha,
271+
branch: artifact.workflow_run.head_branch,
272+
timestamp: artifact.created_at,
273+
workflow_run_url: `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${workflowRunId}`,
274+
avg_power: avgPower,
275+
max_power: maxPower,
276+
min_power: minPower,
277+
sample_count: powerValues.length
278+
};
279+
} else {
280+
// If multiple recordings for same run, aggregate them
281+
const existing = powerResults[workflowRunId];
282+
const totalSamples = existing.sample_count + powerValues.length;
283+
existing.avg_power = (existing.avg_power * existing.sample_count + avgPower * powerValues.length) / totalSamples;
284+
existing.max_power = Math.max(existing.max_power, maxPower);
285+
existing.min_power = Math.min(existing.min_power, minPower);
286+
existing.sample_count = totalSamples;
287+
}
288+
}
289+
});
290+
291+
// Clean up zip file
292+
fs.unlinkSync(fname);
293+
}
294+
295+
// Create CSV file with aggregated power data
296+
const csvData = [
297+
['Average Power (W)', 'Max Power (W)', 'Min Power (W)', 'Sample Count', 'Commit', 'Branch', 'Workflow Run URL', 'Timestamp']
298+
];
299+
300+
// Flatten the results into an array, sorted by timestamp
301+
Object.values(powerResults)
302+
.sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp))
303+
.forEach(result => {
304+
csvData.push([
305+
result.avg_power.toFixed(2),
306+
result.max_power.toFixed(2),
307+
result.min_power.toFixed(2),
308+
result.sample_count,
309+
result.commit,
310+
result.branch,
311+
result.workflow_run_url,
312+
result.timestamp
313+
]);
314+
});
315+
316+
// Write CSV file
317+
const csvContent = stringify(csvData);
318+
fs.writeFileSync('power results.csv', csvContent);
319+
console.log(`Created power results.csv with ${csvData.length - 1} workflow runs`);
320+
321+
- name: Generate Power Graph
322+
run: |
323+
python3 tt-zephyr-platforms/scripts/ci/render_power_graph.py . power_stats.html ${{ matrix.board }}
324+
325+
- name: Upload Power Graph
326+
uses: actions/upload-artifact@v4
327+
with:
328+
name: power-graph-summaries ${{ matrix.board }}
329+
path: |
330+
power_stats.html
331+
power results.csv
332+
165333
build:
166334
runs-on: ubuntu-22.04
167-
needs: [ci-stats, gen-config]
335+
needs: [ci-stats, power-stats, gen-config]
168336
steps:
169337
- name: Checkout
170338
uses: actions/checkout@v4
@@ -224,6 +392,12 @@ jobs:
224392
path: ci-results
225393
pattern: test-result-summaries*
226394

395+
- name: Download Power Results
396+
uses: actions/download-artifact@v4
397+
with:
398+
path: power-results
399+
pattern: power-graph-summaries*
400+
227401
- name: Copy CI Results
228402
run: |
229403
CI_BOARDS="$(jq -r -c ".[]" <<< '${{ needs.gen-config.outputs.ci-boards }}')"
@@ -234,6 +408,9 @@ jobs:
234408
for board in ${CI_BOARD_REVS[@]}; do
235409
cp "ci-results/test-result-summaries ${board}/ci_stats.html" \
236410
tt-zephyr-platforms/doc/deploy/${board}_ci_stats.html
411+
412+
cp "power-results/power-graph-summaries ${board}/power_stats.html" \
413+
tt-zephyr-platforms/doc/deploy/${board}_power_stats.html
237414
done
238415
239416
- name: Setup pages

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010
[P150A CI History](https://docs.tenstorrent.com/tt-zephyr-platforms/p150a_ci_stats.html)
1111
[P300A CI History](https://docs.tenstorrent.com/tt-zephyr-platforms/p300a_ci_stats.html)
1212

13+
[P100A Power History](https://docs.tenstorrent.com/tt-zephyr-platforms/p100a_power_stats.html)
14+
[P150A Power History](https://docs.tenstorrent.com/tt-zephyr-platforms/p150a_power_stats.html)
15+
[P300A Power History](https://docs.tenstorrent.com/tt-zephyr-platforms/p300a_power_stats.html)
16+
1317
Welcome to TT-Zephyr-Platforms!
1418

1519
This is the Zephyr firmware repository for [Tenstorrent](https://tenstorrent.com) AI ULC.

0 commit comments

Comments
 (0)