Nightly Report #120
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Nightly Report | |
| on: | |
| schedule: | |
| - cron: "0 22 * * *" # 22:00 UTC daily, ~4h after TileOPs nightly (18:00 UTC) | |
| workflow_dispatch: | |
| inputs: | |
| run_id: | |
| description: "TileOPs nightly run ID (leave empty for latest)" | |
| required: false | |
| force: | |
| description: "Skip dedup check (force re-run even if run_id was already processed)" | |
| type: boolean | |
| default: false | |
| permissions: | |
| contents: write | |
| concurrency: | |
| group: nightly-report | |
| cancel-in-progress: false | |
| jobs: | |
| build: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout report repo | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.11" | |
| - name: Install Python dependencies | |
| run: pip install anthropic matplotlib pyyaml numpy | |
| # ── Find target nightly run ───────────────────────────────────────── | |
| - name: Find target nightly run | |
| id: run | |
| run: | | |
| if [ -n "${{ github.event.inputs.run_id }}" ]; then | |
| RUN_ID="${{ github.event.inputs.run_id }}" | |
| else | |
| RUN_ID=$(gh run list \ | |
| --repo tile-ai/TileOPs \ | |
| --workflow nightly.yml \ | |
| --status completed \ | |
| --limit 1 \ | |
| --json databaseId \ | |
| -q '.[0].databaseId') | |
| fi | |
| if [ -z "${RUN_ID}" ]; then | |
| echo "::error::No completed nightly run found." | |
| exit 1 | |
| fi | |
| echo "run_id=${RUN_ID}" >> "$GITHUB_OUTPUT" | |
| echo "Using nightly run: ${RUN_ID}" | |
| gh run view "${RUN_ID}" \ | |
| --repo tile-ai/TileOPs \ | |
| --json headSha,createdAt,conclusion \ | |
| -q '"sha=\(.headSha)\nconclusion=\(.conclusion)"' \ | |
| >> "$GITHUB_OUTPUT" | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # ── Checkout gh-pages + dedup ─────────────────────────────────────── | |
| - name: Checkout existing gh-pages | |
| run: | | |
| git clone --depth 1 --branch gh-pages \ | |
| "https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git" \ | |
| _gh_pages 2>/dev/null \ | |
| || { mkdir -p _gh_pages/nightly && echo "(gh-pages branch not found, starting fresh)"; } | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Check for duplicate run | |
| id: dedup | |
| run: | | |
| if [ "${{ github.event.inputs.force }}" = "true" ]; then | |
| echo "skip=false" >> "$GITHUB_OUTPUT" | |
| echo "Force mode — skipping dedup check." | |
| else | |
| RUN_ID="${{ steps.run.outputs.run_id }}" | |
| if grep -rl "^${RUN_ID}$" _gh_pages/nightly/ 2>/dev/null | grep -q "run_id.txt"; then | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| echo "Run ${RUN_ID} already processed — skipping." | |
| else | |
| echo "skip=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| fi | |
| # ── Download artifacts ────────────────────────────────────────────── | |
| - name: Download op_test artifact | |
| if: steps.dedup.outputs.skip != 'true' | |
| uses: dawidd6/action-download-artifact@v3 | |
| with: | |
| github_token: ${{ secrets.GITHUB_TOKEN }} | |
| repo: tile-ai/TileOPs | |
| run_id: ${{ steps.run.outputs.run_id }} | |
| name_is_regexp: true | |
| name: tileops_op_test_.* | |
| path: dl/op_test/ | |
| if_no_artifact_found: warn | |
| - name: Download benchmark artifact | |
| if: steps.dedup.outputs.skip != 'true' | |
| uses: dawidd6/action-download-artifact@v3 | |
| with: | |
| github_token: ${{ secrets.GITHUB_TOKEN }} | |
| repo: tile-ai/TileOPs | |
| run_id: ${{ steps.run.outputs.run_id }} | |
| name_is_regexp: true | |
| name: tileops_benchmark_.* | |
| path: dl/benchmark/ | |
| if_no_artifact_found: warn | |
| - name: Download perf history artifact | |
| if: steps.dedup.outputs.skip != 'true' | |
| uses: dawidd6/action-download-artifact@v3 | |
| with: | |
| github_token: ${{ secrets.GITHUB_TOKEN }} | |
| repo: tile-ai/TileOPs | |
| run_id: ${{ steps.run.outputs.run_id }} | |
| name: tileops_perf_history | |
| path: dl/perf_history/ | |
| if_no_artifact_found: warn | |
| # ── Prepare report directory ──────────────────────────────────────── | |
| - name: Prepare report directory | |
| if: steps.dedup.outputs.skip != 'true' | |
| id: prep | |
| run: | | |
| DATE=$(date +%Y%m%d_%H%M%S) | |
| echo "date=${DATE}" >> "$GITHUB_OUTPUT" | |
| SHA="${{ steps.run.outputs.sha }}" | |
| echo "sha=${SHA}" >> "$GITHUB_OUTPUT" | |
| REPORT_DIR="_gh_pages/nightly/${DATE}" | |
| mkdir -p "${REPORT_DIR}" | |
| echo "report_dir=${REPORT_DIR}" >> "$GITHUB_OUTPUT" | |
| echo "${{ steps.run.outputs.run_id }}" > "${REPORT_DIR}/run_id.txt" | |
| # Flatten artifact subdirectories (dawidd6 nests under artifact name) | |
| find dl/op_test/ -mindepth 2 -type f -exec mv -t dl/op_test/ {} + 2>/dev/null || true | |
| find dl/benchmark/ -mindepth 2 -type f -exec mv -t dl/benchmark/ {} + 2>/dev/null || true | |
| cp dl/op_test/test_results.xml "${REPORT_DIR}/" 2>/dev/null || true | |
| cp dl/op_test/tileops_op_test.log "${REPORT_DIR}/" 2>/dev/null || true | |
| cp dl/op_test/nightly_report.md "${REPORT_DIR}/" 2>/dev/null || true | |
| cp dl/benchmark/bench_results.xml "${REPORT_DIR}/" 2>/dev/null || true | |
| cp dl/benchmark/tileops_benchmarks.log "${REPORT_DIR}/" 2>/dev/null || true | |
| # ── Pipeline: parse → analyze → generate → email → readme → index ── | |
| - name: Parse results | |
| if: steps.dedup.outputs.skip != 'true' | |
| run: | | |
| python scripts/parse_results.py \ | |
| --test-xml "${{ steps.prep.outputs.report_dir }}/test_results.xml" \ | |
| --bench-xml "${{ steps.prep.outputs.report_dir }}/bench_results.xml" \ | |
| --perf-history dl/perf_history/perf_history.json \ | |
| --date "${{ steps.prep.outputs.date }}" \ | |
| --commit "${{ steps.prep.outputs.sha }}" \ | |
| --output "${{ steps.prep.outputs.report_dir }}/nightly_data.json" | |
| # ── Performance history & charts (gh-data branch) ────────────── | |
| - name: Checkout gh-data branch | |
| if: steps.dedup.outputs.skip != 'true' | |
| run: | | |
| git clone --depth 1 --branch gh-data \ | |
| "https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git" \ | |
| _gh_data 2>/dev/null \ | |
| || { mkdir -p _gh_data && echo '{"runs":[]}' > _gh_data/perf_history.json && echo "(gh-data branch not found, starting fresh)"; } | |
| - name: Update perf history | |
| if: steps.dedup.outputs.skip != 'true' | |
| run: | | |
| python scripts/update_perf_history.py \ | |
| --nightly-data "${{ steps.prep.outputs.report_dir }}/nightly_data.json" \ | |
| --perf-history _gh_data/perf_history.json | |
| - name: Generate charts | |
| if: steps.dedup.outputs.skip != 'true' | |
| run: | | |
| python scripts/generate_charts.py \ | |
| --config scripts/chart_config.yaml \ | |
| --perf-history _gh_data/perf_history.json \ | |
| --nightly-data "${{ steps.prep.outputs.report_dir }}/nightly_data.json" \ | |
| --output-dir "${{ steps.prep.outputs.report_dir }}/charts" | |
| - name: Push perf history to gh-data | |
| if: steps.dedup.outputs.skip != 'true' | |
| run: | | |
| cd _gh_data | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git add perf_history.json | |
| if git diff --staged --quiet; then | |
| echo "perf_history.json unchanged." | |
| else | |
| git commit -m "data: update perf_history (${{ steps.prep.outputs.date }})" | |
| git push origin gh-data | |
| fi | |
| # ── Analysis & report generation ─────────────────────────────── | |
| - name: Analyze with Claude | |
| if: steps.dedup.outputs.skip != 'true' | |
| continue-on-error: true | |
| run: | | |
| python scripts/analyze_report.py \ | |
| --nightly-data "${{ steps.prep.outputs.report_dir }}/nightly_data.json" \ | |
| --output "${{ steps.prep.outputs.report_dir }}/analysis.json" | |
| env: | |
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | |
| ANTHROPIC_BASE_URL: ${{ secrets.ANTHROPIC_BASE_URL }} | |
| - name: Generate HTML report | |
| if: steps.dedup.outputs.skip != 'true' | |
| run: | | |
| python scripts/generate_report.py \ | |
| --date "${{ steps.prep.outputs.date }}" \ | |
| --commit "${{ steps.prep.outputs.sha }}" \ | |
| --report-dir "${{ steps.prep.outputs.report_dir }}" \ | |
| --html-output "${{ steps.prep.outputs.report_dir }}/report.html" \ | |
| --nightly-data "${{ steps.prep.outputs.report_dir }}/nightly_data.json" \ | |
| --analysis-json "${{ steps.prep.outputs.report_dir }}/analysis.json" | |
| - name: Send report email | |
| if: steps.dedup.outputs.skip != 'true' | |
| continue-on-error: true | |
| run: | | |
| python scripts/send_report_email.py \ | |
| --html-file "${{ steps.prep.outputs.report_dir }}/report.html" \ | |
| --subject "TileOPs Nightly Report ${{ steps.prep.outputs.date }}" \ | |
| --recipients-file scripts/email_recipients.txt | |
| env: | |
| MAIL_SMTP_SERVER: ${{ secrets.MAIL_SMTP_SERVER }} | |
| MAIL_SMTP_PORT: ${{ secrets.MAIL_SMTP_PORT }} | |
| MAIL_USERNAME: ${{ secrets.MAIL_USERNAME }} | |
| MAIL_PASSWORD: ${{ secrets.MAIL_PASSWORD }} | |
| # ── Deploy to gh-pages ────────────────────────────────────────────── | |
| - name: Copy charts to latest/ | |
| if: steps.dedup.outputs.skip != 'true' | |
| run: | | |
| rm -rf _gh_pages/latest/charts | |
| if [ -d "${{ steps.prep.outputs.report_dir }}/charts" ]; then | |
| mkdir -p _gh_pages/latest | |
| cp -r "${{ steps.prep.outputs.report_dir }}/charts" _gh_pages/latest/charts | |
| echo "Copied charts to _gh_pages/latest/charts/" | |
| else | |
| echo "No charts directory found — skipping." | |
| fi | |
| - name: Prune reports older than 14 days | |
| if: steps.dedup.outputs.skip != 'true' | |
| run: | | |
| CUTOFF=$(date -d "14 days ago" +%Y%m%d) | |
| for dir in _gh_pages/nightly/*/; do | |
| dirname=$(basename "${dir}") | |
| dir_date="${dirname:0:8}" | |
| if [[ "${dir_date}" =~ ^[0-9]{8}$ ]] && [ "${dir_date}" -lt "${CUTOFF}" ]; then | |
| echo "Removing old report: ${dirname}" | |
| rm -rf "${dir}" | |
| fi | |
| done | |
| - name: Generate index.html | |
| if: steps.dedup.outputs.skip != 'true' | |
| run: | | |
| python scripts/gen_index.py \ | |
| --nightly-dir _gh_pages/nightly \ | |
| --output _gh_pages/nightly/index.html \ | |
| --repo-url "https://github.com/tile-ai/TileOPs" | |
| - name: Push to gh-pages | |
| if: steps.dedup.outputs.skip != 'true' | |
| run: | | |
| cd _gh_pages | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git checkout --orphan new_gh_pages | |
| git add -A | |
| git commit -m "report: ${{ steps.prep.outputs.date }} (run ${{ steps.run.outputs.run_id }})" | |
| git push --force \ | |
| "https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git" \ | |
| HEAD:gh-pages |