Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 4 additions & 7 deletions .github/actions/log-api-budget/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,10 @@ runs:
printf '%s\n' "$lines"
sed 's/^::notice title=api-budget::/api-budget: /' <<<"$lines" >>"$GITHUB_STEP_SUMMARY"
# This job's own calls, one reading each: unlike cost, no other job's.
# A notice, as the hub reads annotations, not the log; its message
# escaped as workflow commands require.
calls=$(awk '$1 != 304 { n++ } $1 == 304 { m++ } END { printf "%s=%d not-modified=%d", token, n, m }' token="$name" "$RUNNER_TEMP/api-budget/$name")
notice=${calls//%/%25}
notice=${notice//$'\r'/%0D}
echo "::notice title=api-calls::${notice//$'\n'/%0A}"
echo "api-calls $calls" >>"$GITHUB_STEP_SUMMARY"
# A notice, as the hub reads annotations, not the log.
calls=$(api_budget_emit_calls "$name")
printf '%s\n' "$calls"
echo "api-calls ${calls#*title=api-calls::}" >>"$GITHUB_STEP_SUMMARY"
cost=${lines##*cost=}
costs+=("$name-cost=${cost/na/unknown}")
done
Expand Down
15 changes: 15 additions & 0 deletions .github/actions/log-api-budget/api-budget.bash
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@
# with the same reset and resource, else na
# api_budget_emit_content the notice for the job's content-creating
# writes: count and most in any 60 s
# api_budget_emit_calls <token>...
# the hub's api-calls notice for each token with
# a reading, `<token>=N not-modified=M`: N is
# the job's own calls, M its 304 answers, which
# GitHub does not count
#
# pr-require-checks.yml's ci-status job carries this file verbatim in its
# API_BUDGET_SH env (it has no checkout of this directory);
Expand Down Expand Up @@ -367,6 +372,16 @@ api_budget_emit_content() {
return 0
}

api_budget_emit_calls() {
local token file
for token; do
file=${RUNNER_TEMP:-}/api-budget/$token
[[ -n ${RUNNER_TEMP:-} && $token =~ ^[a-z][a-z0-9-]{0,39}$ && -s $file ]] || continue
awk '$1 != 304 { n++ } $1 == 304 { m++ } END { printf "::notice title=api-calls::%s=%d not-modified=%d\n", token, n, m }' token="$token" "$file"
done
return 0
}

api_budget_emit() {
local token file where workflow=${GITHUB_WORKFLOW_REF:-}
workflow=${workflow%%@*}
Expand Down
6 changes: 6 additions & 0 deletions .github/actions/log-api-budget/log-api-budget.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,8 @@ test("each heavy lane job names its API_BUDGET_TOKEN and ends with the log-api-b
// label-unblocked's ACTIONS_TOKEN client records under github-token, so
// that job logs both: [API_BUDGET_TOKEN, the tokens it logs].
"work-unblock.yml": { "wip-state": "github-token", "label-unblocked": ["lanes-app", "lanes-app github-token"] },
"pr-merge.yml": { "post-merge-dispatch": "github-token" },
"intake-reenter.yml": { reenter: "github-token" },
};
for (const [file, tokens] of Object.entries(jobs)) {
const text = readFileSync(new URL(`../../workflows/${file}`, import.meta.url), "utf8");
Expand Down Expand Up @@ -672,6 +674,10 @@ test("ci-status carries api-budget.bash verbatim in its API_BUDGET_SH env", () =
assert.equal(carried, readFileSync(LIB, "utf8"));
});

test("ci-status emits the api-calls notice beside its api-budget notice", () => {
assert.match(CI, /api_budget_emit github-token; api_budget_emit_calls github-token; /);
});

// pr-feedback-dispatch.yml's post-verify-notes and rerun-ci-status jobs post
// no other notice, so without these lines their calls go unattributed.
// post-verify-notes' notice covers only its artifact listing, the one call
Expand Down
7 changes: 6 additions & 1 deletion .github/scripts/pr-merge/merge-job.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ test("the re-dispatch for earlier lane merges waits on neither the listing nor t
"Record the re-run",
"Send a red full run to the demotion alert",
"Record the alert sent",
"Log the API budget at the end",
],
);
assert.doesNotMatch(early, /steps\.candidates|steps\.gate|needs\.merge/);
Expand Down Expand Up @@ -1896,7 +1897,11 @@ test("the alert send runs after the re-run while the lane is live, and stops aft
/\n {8}if: >-\n {10}!cancelled\(\)\n {10}&& steps\.kill\.outputs\.proceed == 'true'\n {10}&& steps\.mode\.outputs\.live == 'true'\n {10}&& steps\.post-merge\.outputs\.code != '75'\n {10}&& steps\.cover\.outputs\.rate-limited != 'true'\n {10}&& steps\.dispatch\.outputs\.rate-limited != 'true'\n {10}&& steps\.rerun\.outputs\.rate-limited != 'true'\n/,
);
const names = list.map((s) => s.name);
assert.deepEqual(names.slice(-2), ["Send a red full run to the demotion alert", "Record the alert sent"]);
assert.deepEqual(names.slice(-3), [
"Send a red full run to the demotion alert",
"Record the alert sent",
"Log the API budget at the end",
]);
});

// The scheduled job's cover read, run with .base at this checkout (the rate
Expand Down
12 changes: 12 additions & 0 deletions .github/workflows/intake-reenter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ jobs:
concurrency:
group: intake-reenter-${{ github.event.issue.number }}
queue: max
# Each guarded API call's rate-limit reading, for the job-end api-budget
# notice (.github/actions/log-api-budget).
env:
API_BUDGET_TOKEN: github-token
steps:
- name: Check out the default branch tip
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand Down Expand Up @@ -114,3 +118,11 @@ jobs:
TRUSTED_ACTORS_PATH: .base/.github/standards/trusted-actors/trusted-actors.json
KILL_SWITCH: proceed
run: node .base/.github/scripts/intake-reenter/reenter.mjs

- name: Log the API budget at the end
if: always()
continue-on-error: true
timeout-minutes: 1
uses: ./.base/.github/actions/log-api-budget # zizmor: ignore[self-repository]
with:
tokens: github-token
13 changes: 13 additions & 0 deletions .github/workflows/pr-merge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,11 @@ jobs:
# The pool and reset a rate limit stopped this job's GITHUB_TOKEN calls on,
# if one did: the merge and enable-auto-merge jobs then send nothing.
rate-limited: ${{ steps.post-merge.outputs.rate-limited || steps.cover.outputs.stop || steps.dispatch.outputs.stop || steps.rerun.outputs.stop || steps.alert.outputs.stop }}
# Each guarded API call's rate-limit reading, for the job-end api-budget
# notice (.github/actions/log-api-budget). The full-run dispatch's own
# `gh api -i` POST leaves none.
env:
API_BUDGET_TOKEN: github-token
steps:
- name: Check out the default branch tip
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand Down Expand Up @@ -695,6 +700,14 @@ jobs:
retention-days: 7
if-no-files-found: error

- name: Log the API budget at the end
if: always()
continue-on-error: true
timeout-minutes: 1
uses: ./.base/.github/actions/log-api-budget # zizmor: ignore[self-repository]
with:
tokens: github-token

merge:
# On the schedule and a hand-started run it waits for post-merge-dispatch
# and reuses its report (the post-merge check below). Where that job is
Expand Down
17 changes: 16 additions & 1 deletion .github/workflows/pr-require-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3359,6 +3359,11 @@ jobs:
# with the same reset and resource, else na
# api_budget_emit_content the notice for the job's content-creating
# writes: count and most in any 60 s
# api_budget_emit_calls <token>...
# the hub's api-calls notice for each token with
# a reading, `<token>=N not-modified=M`: N is
# the job's own calls, M its 304 answers, which
# GitHub does not count
#
# pr-require-checks.yml's ci-status job carries this file verbatim in its
# API_BUDGET_SH env (it has no checkout of this directory);
Expand Down Expand Up @@ -3656,6 +3661,16 @@ jobs:
return 0
}

api_budget_emit_calls() {
local token file
for token; do
file=${RUNNER_TEMP:-}/api-budget/$token
[[ -n ${RUNNER_TEMP:-} && $token =~ ^[a-z][a-z0-9-]{0,39}$ && -s $file ]] || continue
awk '$1 != 304 { n++ } $1 == 304 { m++ } END { printf "::notice title=api-calls::%s=%d not-modified=%d\n", token, n, m }' token="$token" "$file"
done
return 0
}

api_budget_emit() {
local token file where workflow=${GITHUB_WORKFLOW_REF:-}
workflow=${workflow%%@*}
Expand Down Expand Up @@ -5249,7 +5264,7 @@ jobs:
env:
LANES: ${{ toJSON(needs) }}
run: |
[ -z "${API_BUDGET_SH:-}" ] || { eval "$API_BUDGET_SH"; api_budget_emit github-token; api_budget_emit_content; }
[ -z "${API_BUDGET_SH:-}" ] || { eval "$API_BUDGET_SH"; api_budget_emit github-token; api_budget_emit_calls github-token; api_budget_emit_content; }
results=$(jq -r '
.["select-tests"] as $s
| to_entries
Expand Down
Loading