Skip to content

Commit bab40c4

Browse files
committed
fix: usage accounting across windows
1 parent 9adf00f commit bab40c4

File tree

4 files changed

+363
-31
lines changed

4 files changed

+363
-31
lines changed

dist/post/index.js

Lines changed: 139 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27862,7 +27862,79 @@ function makeSummaryTable(resources) {
2786227862
return summaryTable;
2786327863
}
2786427864

27865-
module.exports = { formatMs, makeSummaryTable };
27865+
/**
27866+
* Computes usage stats for a single bucket using pre/post snapshots.
27867+
*
27868+
* @param {object} startingBucket - bucket from the pre snapshot.
27869+
* @param {object} endingBucket - bucket from the post snapshot.
27870+
* @param {number} endTimeSeconds - post snapshot time in seconds.
27871+
* @returns {object} usage details and validation status.
27872+
*/
27873+
function computeBucketUsage(startingBucket, endingBucket, endTimeSeconds) {
27874+
const result = {
27875+
valid: false,
27876+
used: 0,
27877+
remaining: undefined,
27878+
crossed_reset: false,
27879+
warnings: []
27880+
};
27881+
27882+
if (!startingBucket || !endingBucket) {
27883+
result.reason = 'missing_bucket';
27884+
return result;
27885+
}
27886+
27887+
const startingRemaining = Number(startingBucket.remaining);
27888+
const endingRemaining = Number(endingBucket.remaining);
27889+
if (!Number.isFinite(startingRemaining) || !Number.isFinite(endingRemaining)) {
27890+
result.reason = 'invalid_remaining';
27891+
return result;
27892+
}
27893+
27894+
const startingLimit = Number(startingBucket.limit);
27895+
const endingLimit = Number(endingBucket.limit);
27896+
const resetPre = Number(startingBucket.reset);
27897+
const crossedReset = Number.isFinite(resetPre) && endTimeSeconds >= resetPre;
27898+
result.crossed_reset = crossedReset;
27899+
27900+
let used;
27901+
if (crossedReset) {
27902+
if (!Number.isFinite(startingLimit) || !Number.isFinite(endingLimit)) {
27903+
result.reason = 'invalid_limit';
27904+
return result;
27905+
}
27906+
if (startingLimit !== endingLimit) {
27907+
result.warnings.push('limit_changed_across_reset');
27908+
}
27909+
used = startingLimit - startingRemaining + (endingLimit - endingRemaining);
27910+
} else {
27911+
if (
27912+
Number.isFinite(startingLimit) &&
27913+
Number.isFinite(endingLimit) &&
27914+
startingLimit !== endingLimit
27915+
) {
27916+
result.reason = 'limit_changed_without_reset';
27917+
return result;
27918+
}
27919+
used = startingRemaining - endingRemaining;
27920+
if (used < 0) {
27921+
result.reason = 'remaining_increased_without_reset';
27922+
return result;
27923+
}
27924+
}
27925+
27926+
if (used < 0) {
27927+
result.reason = 'negative_usage';
27928+
return result;
27929+
}
27930+
27931+
result.valid = true;
27932+
result.used = used;
27933+
result.remaining = endingRemaining;
27934+
return result;
27935+
}
27936+
27937+
module.exports = { formatMs, makeSummaryTable, computeBucketUsage };
2786627938

2786727939

2786827940
/***/ }),
@@ -27980,7 +28052,7 @@ const fs = __nccwpck_require__(9896);
2798028052
const path = __nccwpck_require__(6928);
2798128053
const { fetchRateLimit } = __nccwpck_require__(5042);
2798228054
const { log, parseBuckets } = __nccwpck_require__(9630);
27983-
const { formatMs, makeSummaryTable } = __nccwpck_require__(5828);
28055+
const { formatMs, makeSummaryTable, computeBucketUsage } = __nccwpck_require__(5828);
2798428056

2798528057
/**
2798628058
* Writes JSON-stringified data to a file if a valid pathname is provided.
@@ -28032,6 +28104,7 @@ async function run() {
2803228104
);
2803328105
}
2803428106
const endTime = Date.now();
28107+
const endTimeSeconds = Math.floor(endTime / 1000);
2803528108
const duration = hasStartTime ? endTime - startTime : null;
2803628109

2803728110
log('[github-api-usage-tracker] Fetching final rate limits...');
@@ -28044,33 +28117,77 @@ async function run() {
2804428117
log(`[github-api-usage-tracker] ${JSON.stringify(endingResources, null, 2)}`);
2804528118

2804628119
const data = {};
28120+
const crossedBuckets = [];
2804728121
let totalUsed = 0;
2804828122

2804928123
for (const bucket of buckets) {
28050-
const startingUsed = startingResources[bucket]?.used;
28051-
const endingUsed = endingResources[bucket]?.used;
28052-
if (startingUsed === undefined) {
28124+
const startingBucket = startingResources[bucket];
28125+
const endingBucket = endingResources[bucket];
28126+
if (!startingBucket) {
2805328127
core.warning(
2805428128
`[github-api-usage-tracker] Starting rate limit bucket "${bucket}" not found; skipping`
2805528129
);
2805628130
continue;
2805728131
}
28058-
if (endingUsed === undefined) {
28132+
if (!endingBucket) {
2805928133
core.warning(
2806028134
`[github-api-usage-tracker] Ending rate limit bucket "${bucket}" not found; skipping`
2806128135
);
2806228136
continue;
2806328137
}
28064-
let used = endingUsed - startingUsed;
28065-
if (used < 0) {
28138+
28139+
const usage = computeBucketUsage(startingBucket, endingBucket, endTimeSeconds);
28140+
if (!usage.valid) {
28141+
switch (usage.reason) {
28142+
case 'invalid_remaining':
28143+
core.warning(
28144+
`[github-api-usage-tracker] Invalid remaining count for bucket "${bucket}"; skipping`
28145+
);
28146+
break;
28147+
case 'invalid_limit':
28148+
core.warning(
28149+
`[github-api-usage-tracker] Invalid limit for bucket "${bucket}" during reset crossing; skipping`
28150+
);
28151+
break;
28152+
case 'limit_changed_without_reset':
28153+
core.warning(
28154+
`[github-api-usage-tracker] Limit changed without reset for bucket "${bucket}"; skipping`
28155+
);
28156+
break;
28157+
case 'remaining_increased_without_reset':
28158+
core.warning(
28159+
`[github-api-usage-tracker] Remaining increased without reset for bucket "${bucket}"; skipping`
28160+
);
28161+
break;
28162+
case 'negative_usage':
28163+
core.warning(
28164+
`[github-api-usage-tracker] Negative usage for bucket "${bucket}" detected; skipping`
28165+
);
28166+
break;
28167+
default:
28168+
core.warning(
28169+
`[github-api-usage-tracker] Invalid usage data for bucket "${bucket}"; skipping`
28170+
);
28171+
break;
28172+
}
28173+
continue;
28174+
}
28175+
28176+
if (usage.warnings.includes('limit_changed_across_reset')) {
2806628177
core.warning(
28067-
`[github-api-usage-tracker] Negative usage for bucket "${bucket}" detected; clamping to 0`
28178+
`[github-api-usage-tracker] Limit changed across reset for bucket "${bucket}"; results may reflect a token change`
2806828179
);
28069-
used = 0;
2807028180
}
28071-
const remaining = endingResources[bucket].remaining;
28072-
data[bucket] = { used, remaining };
28073-
totalUsed += used;
28181+
28182+
data[bucket] = {
28183+
used: usage.used,
28184+
remaining: usage.remaining,
28185+
crossed_reset: usage.crossed_reset
28186+
};
28187+
if (usage.crossed_reset) {
28188+
crossedBuckets.push(bucket);
28189+
}
28190+
totalUsed += usage.used;
2807428191
}
2807528192

2807628193
// Set output
@@ -28088,9 +28205,16 @@ async function run() {
2808828205
log(
2808928206
`[github-api-usage-tracker] Preparing summary table for ${Object.keys(data).length} bucket(s)`
2809028207
);
28091-
core.summary
28208+
const summary = core.summary
2809228209
.addHeading('GitHub API Usage Tracker Summary')
28093-
.addTable(makeSummaryTable(data))
28210+
.addTable(makeSummaryTable(data));
28211+
if (crossedBuckets.length > 0) {
28212+
summary.addRaw(
28213+
`<p><strong>Reset Window Crossed:</strong> Yes (${crossedBuckets.join(', ')})</p>`,
28214+
true
28215+
);
28216+
}
28217+
summary
2809428218
.addRaw(
2809528219
`<p><strong>Action Duration:</strong> ${
2809628220
hasStartTime ? formatMs(duration) : 'Unknown (data missing)'

src/post-utils.js

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,76 @@ function makeSummaryTable(resources) {
3535
return summaryTable;
3636
}
3737

38-
module.exports = { formatMs, makeSummaryTable };
38+
/**
39+
* Computes usage stats for a single bucket using pre/post snapshots.
40+
*
41+
* @param {object} startingBucket - bucket from the pre snapshot.
42+
* @param {object} endingBucket - bucket from the post snapshot.
43+
* @param {number} endTimeSeconds - post snapshot time in seconds.
44+
* @returns {object} usage details and validation status.
45+
*/
46+
function computeBucketUsage(startingBucket, endingBucket, endTimeSeconds) {
47+
const result = {
48+
valid: false,
49+
used: 0,
50+
remaining: undefined,
51+
crossed_reset: false,
52+
warnings: []
53+
};
54+
55+
if (!startingBucket || !endingBucket) {
56+
result.reason = 'missing_bucket';
57+
return result;
58+
}
59+
60+
const startingRemaining = Number(startingBucket.remaining);
61+
const endingRemaining = Number(endingBucket.remaining);
62+
if (!Number.isFinite(startingRemaining) || !Number.isFinite(endingRemaining)) {
63+
result.reason = 'invalid_remaining';
64+
return result;
65+
}
66+
67+
const startingLimit = Number(startingBucket.limit);
68+
const endingLimit = Number(endingBucket.limit);
69+
const resetPre = Number(startingBucket.reset);
70+
const crossedReset = Number.isFinite(resetPre) && endTimeSeconds >= resetPre;
71+
result.crossed_reset = crossedReset;
72+
73+
let used;
74+
if (crossedReset) {
75+
if (!Number.isFinite(startingLimit) || !Number.isFinite(endingLimit)) {
76+
result.reason = 'invalid_limit';
77+
return result;
78+
}
79+
if (startingLimit !== endingLimit) {
80+
result.warnings.push('limit_changed_across_reset');
81+
}
82+
used = startingLimit - startingRemaining + (endingLimit - endingRemaining);
83+
} else {
84+
if (
85+
Number.isFinite(startingLimit) &&
86+
Number.isFinite(endingLimit) &&
87+
startingLimit !== endingLimit
88+
) {
89+
result.reason = 'limit_changed_without_reset';
90+
return result;
91+
}
92+
used = startingRemaining - endingRemaining;
93+
if (used < 0) {
94+
result.reason = 'remaining_increased_without_reset';
95+
return result;
96+
}
97+
}
98+
99+
if (used < 0) {
100+
result.reason = 'negative_usage';
101+
return result;
102+
}
103+
104+
result.valid = true;
105+
result.used = used;
106+
result.remaining = endingRemaining;
107+
return result;
108+
}
109+
110+
module.exports = { formatMs, makeSummaryTable, computeBucketUsage };

0 commit comments

Comments
 (0)