Skip to content

Commit df9bd84

Browse files
authored
Merge pull request #2 from SillyLittleTech/copilot/update-heartbeat-reporting
Reduce KV writes: skip saves when state hasn't changed, batch daily
2 parents 3696ec8 + 2f0d993 commit df9bd84

3 files changed

Lines changed: 72 additions & 19 deletions

File tree

.github/workflows/deploy.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
name: Deploy
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
8+
jobs:
9+
deploy:
10+
runs-on: ubuntu-latest
11+
name: Deploy Worker
12+
steps:
13+
- uses: actions/checkout@v4
14+
- name: Deploy
15+
uses: cloudflare/wrangler-action@v3
16+
with:
17+
apiToken: ${{ secrets.CF_API_TOKEN }}
18+
accountId: ${{ secrets.CF_ACCOUNT_ID }}
19+
command: deploy

worker/index.js

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,14 @@ async function runHeartbeat(env) {
123123
if (!state.historyByService) state.historyByService = {};
124124

125125
let statusChanged = false;
126+
let needsSave = false;
126127
let notifications = [];
127128
const heartbeatAt = Date.now();
129+
const todayUTC = new Date(heartbeatAt).toISOString().slice(0, 10); // "YYYY-MM-DD"
130+
131+
// Save at most every 15 minutes for latency/history freshness (~96 writes/day on free tier)
132+
const PERIODIC_SAVE_INTERVAL_MS = 15 * 60 * 1000;
133+
const isPeriodicSave = heartbeatAt - (state._lastSaveTime || 0) >= PERIODIC_SAVE_INTERVAL_MS;
128134

129135
for (const target of targets) {
130136
if (state.services[target.id]?.isManual) {
@@ -168,25 +174,56 @@ async function runHeartbeat(env) {
168174
status: newStatus,
169175
latency: latency
170176
});
177+
178+
// Internal log every heartbeat (no KV write)
179+
console.log(`[heartbeat] ${target.name}: ${newStatus}${latency !== null ? ` (${latency}ms)` : ''}`);
171180
}
172181

173-
// Fetch maintained issues from ProjectHub
174-
try {
175-
const issuesRes = await fetch('https://api.github.com/repos/SillyLittleTech/projecthub/issues?labels=maintain&state=open', {
176-
headers: {
177-
'User-Agent': 'SLT-Status-Worker',
178-
'Authorization': env.ISSUES_PAT ? `Bearer ${env.ISSUES_PAT}` : ''
182+
// Fetch maintained issues from ProjectHub at most once per day.
183+
// Track attempt day separately from success day so a non-2xx response
184+
// doesn't cause unbounded retries on every heartbeat.
185+
const lastIssuesFetchAttempt = state._lastIssuesFetchAttemptDay || '';
186+
if (lastIssuesFetchAttempt !== todayUTC) {
187+
state._lastIssuesFetchAttemptDay = todayUTC; // record attempt regardless of outcome
188+
needsSave = true; // persist the attempt date so retries are bounded
189+
try {
190+
const issuesRes = await fetch('https://api.github.com/repos/SillyLittleTech/projecthub/issues?labels=maintain&state=open', {
191+
headers: {
192+
'User-Agent': 'SLT-Status-Worker',
193+
'Authorization': env.ISSUES_PAT ? `Bearer ${env.ISSUES_PAT}` : ''
194+
}
195+
});
196+
if (issuesRes.ok) {
197+
const issues = await issuesRes.json();
198+
state.maintenance = issues.map(i => ({ title: i.title, url: i.html_url, created_at: i.created_at }));
199+
state._lastIssuesFetchDay = todayUTC;
200+
console.log(`[daily] Fetched ${issues.length} maintenance issue(s) for ${todayUTC}`);
201+
} else {
202+
console.error(`[daily] Issues fetch failed: HTTP ${issuesRes.status} for ${todayUTC}`);
179203
}
180-
});
181-
if (issuesRes.ok) {
182-
const issues = await issuesRes.json();
183-
state.maintenance = issues.map(i => ({ title: i.title, url: i.html_url, created_at: i.created_at }));
204+
} catch (e) {
205+
console.error('Failed to fetch maintenance issues', e);
184206
}
185-
} catch (e) {
186-
console.error('Failed to fetch maintenance issues', e);
187207
}
188208

189-
await saveState(env, state);
209+
// Only write to KV when something meaningful changed:
210+
// - status change: immediate (for accurate frontend display)
211+
// - periodic (every 15 min): keeps latency/history reasonably fresh (~96 writes/day)
212+
// - daily: ensures a flush even during quiet periods + logs a summary
213+
// - needsSave: issues fetch attempt date or maintenance list must be persisted
214+
const lastDailySaveDate = state._lastDailySaveDay || '';
215+
const isDailySave = lastDailySaveDate !== todayUTC;
216+
if (statusChanged || needsSave || isDailySave || isPeriodicSave) {
217+
state._lastSaveTime = heartbeatAt; // reset the 15-min clock on any save
218+
if (isDailySave) {
219+
state._lastDailySaveDay = todayUTC;
220+
const summary = Object.entries(state.services)
221+
.map(([, svc]) => `${svc.name}: ${svc.status}`)
222+
.join(', ');
223+
console.log(`[daily summary] ${todayUTC}${summary}`);
224+
}
225+
await saveState(env, state);
226+
}
190227
}
191228

192229
async function saveState(env, state) {

wrangler.toml

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
name = "upmagic"
22
compatibility_date = "2024-05-01"
33

4-
# Use a Worker + site configuration so Wrangler deploys both the Worker and static files
54
main = "worker/index.js"
65

7-
[site]
8-
bucket = "./frontend"
9-
include = ["**/*"]
10-
exclude = ["**/.git/*"]
6+
[assets]
7+
directory = "./frontend"
118

129
[vars]
1310
# GITHUB_GIST_ID = "" (Set via secrets or vars later)
@@ -20,4 +17,4 @@ id = "d6179b2f901645de81b9805c4e7ac591"
2017
preview_id = "59e7164e44154d7fa79b2a093b338530"
2118

2219
[triggers]
23-
crons = ["* * * * *"] # Run every minute for heartbeat
20+
crons = ["*/5 * * * *"] # Every 5 minutes (intentionally relaxed from 1 min to cut KV writes ~5x)

0 commit comments

Comments
 (0)