goodgrief/scripts/report-admin-performance.mjs

82 lines
3.4 KiB
JavaScript
Raw Normal View History

import { createHash } from "node:crypto";
import { readdirSync, readFileSync, statSync } from "node:fs";
import path from "node:path";
const rootDir = path.resolve(path.dirname(new URL(import.meta.url).pathname), "..");
const statePath = path.join(rootDir, "data", "runtime", "state.json");
const distDir = path.join(rootDir, "apps", "admin", "dist", "assets");
const state = JSON.parse(readFileSync(statePath, "utf8"));
const revision = (value) => createHash("sha1").update(JSON.stringify(value)).digest("hex");
const pendingCount = state.photoAssets.filter((asset) => asset.moderationStatus === "pending" && state.submissions.find((submission) => submission.id === asset.submissionId)?.source !== "admin_upload").length;
const approvedCount = state.photoAssets.filter((asset) => asset.moderationStatus === "approved").length;
const libraryRevision = revision({
photoAssets: state.photoAssets.map((asset) => ({
id: asset.id,
submissionId: asset.submissionId,
moderationStatus: asset.moderationStatus,
processingStatus: asset.processingStatus,
thumbKey: asset.thumbKey,
previewKey: asset.previewKey,
renderKey: asset.renderKey,
approvedAt: asset.approvedAt
})),
submissions: state.submissions.map((submission) => ({
id: submission.id,
status: submission.status,
contributorName: submission.contributorName,
lovedOneName: submission.lovedOneName,
displayName: submission.displayName,
caption: submission.caption,
promptAnswer: submission.promptAnswer,
notes: submission.notes,
source: submission.source
})),
collections: state.collections.map((collection) => ({
id: collection.id,
assetIds: collection.assetIds,
coverAssetId: collection.coverAssetId
}))
});
const programRevision = revision({
cues: state.cues.map((cue) => ({
id: cue.id,
orderIndex: cue.orderIndex,
sceneDefinitionId: cue.sceneDefinitionId,
effectPresetId: cue.effectPresetId,
updated: [cue.transitionIn, cue.transitionOut, cue.assetIds, cue.notes, cue.triggerMode, cue.durationMs, cue.nextCueId, cue.collectionId]
})),
safeSceneCueId: state.showConfig.safeSceneCueId
});
const payloads = {
bootstrap: JSON.stringify({ ...state, libraryRevision, programRevision }).length,
library: JSON.stringify({ photoAssets: state.photoAssets, submissions: state.submissions, collections: state.collections, revision: libraryRevision }).length,
live: JSON.stringify({ cues: state.cues, pendingCount, approvedCount, libraryRevision, programRevision }).length
};
const chunks = readdirSync(distDir)
.filter((file) => file.endsWith('.js') || file.endsWith('.css'))
.map((file) => ({ file, size: statSync(path.join(distDir, file)).size }))
.sort((left, right) => right.size - left.size);
const totalJs = chunks.filter((chunk) => chunk.file.endsWith('.js')).reduce((sum, chunk) => sum + chunk.size, 0);
console.log(JSON.stringify({
assets: chunks,
totalJsBytes: totalJs,
payloadBytes: payloads,
stablePollingBytesPerMinute: payloads.live * 15,
legacyPollingBytesPerMinute: (payloads.live + payloads.library) * 15,
stateCounts: {
photoAssets: state.photoAssets.length,
submissions: state.submissions.length,
cues: state.cues.length
},
viewportProfiles: {
preview: { targetFps: '18-24', dprCap: 0.85 },
programMonitor: { targetFps: '20-30', dprCap: 1 },
programOutput: { targetFps: '30-45', dprCap: 1.35 }
}
}, null, 2));