151 lines
4.4 KiB
JavaScript
151 lines
4.4 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* skyeye/scripts/quota-audit.js
|
|
* 天眼配额审计脚本 — 配额精算与预警
|
|
* Performs quota audit across all services defined in quota-ledger.json
|
|
*/
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const SKYEYE_DIR = path.resolve(__dirname, '..');
|
|
const LEDGER_PATH = path.join(SKYEYE_DIR, 'quota-ledger.json');
|
|
const MANIFEST_PATH = path.join(SKYEYE_DIR, 'infra-manifest.json');
|
|
const LOGS_DIR = path.join(SKYEYE_DIR, 'logs');
|
|
|
|
function getTimestamp() {
|
|
return new Date().toISOString();
|
|
}
|
|
|
|
function getDateStr() {
|
|
return new Date().toISOString().slice(0, 10).replace(/-/g, '');
|
|
}
|
|
|
|
function loadJSON(filePath) {
|
|
try {
|
|
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function saveJSON(filePath, data) {
|
|
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf8');
|
|
}
|
|
|
|
function getDaysRemainingInMonth() {
|
|
const now = new Date();
|
|
const lastDay = new Date(now.getFullYear(), now.getMonth() + 1, 0);
|
|
return lastDay.getDate() - now.getDate();
|
|
}
|
|
|
|
function auditService(name, data) {
|
|
const result = {
|
|
service: name,
|
|
status: 'healthy',
|
|
usage_percent: 0,
|
|
alerts: [],
|
|
actions: []
|
|
};
|
|
|
|
if (data.limit && data.used !== undefined && data.used !== null) {
|
|
result.usage_percent = data.limit > 0
|
|
? Math.round((data.used / data.limit) * 100 * 10) / 10
|
|
: 0;
|
|
}
|
|
|
|
// Threshold checks
|
|
if (result.usage_percent > 95) {
|
|
result.status = 'critical';
|
|
result.alerts.push(`${name}: 配额消耗 ${result.usage_percent}% · 立即降频/暂停非核心触发`);
|
|
result.actions.push('emergency_throttle');
|
|
} else if (result.usage_percent > 80) {
|
|
result.status = 'warning';
|
|
result.alerts.push(`${name}: 配额消耗 ${result.usage_percent}% · 配额告警`);
|
|
result.actions.push('reduce_frequency');
|
|
}
|
|
|
|
// Budget projection for monthly services
|
|
if (data.remaining !== undefined) {
|
|
const daysLeft = getDaysRemainingInMonth();
|
|
if (daysLeft > 0) {
|
|
result.daily_budget = Math.round(data.remaining / daysLeft);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function run() {
|
|
const args = process.argv.slice(2);
|
|
const verbose = args.includes('--verbose');
|
|
|
|
console.log(`[SkyEye Quota Audit] Starting quota audit`);
|
|
console.log(`[SkyEye Quota Audit] Timestamp: ${getTimestamp()}`);
|
|
|
|
const ledger = loadJSON(LEDGER_PATH);
|
|
if (!ledger) {
|
|
console.error('[SkyEye Quota Audit] ERROR: Cannot load quota-ledger.json');
|
|
process.exit(1);
|
|
}
|
|
|
|
const manifest = loadJSON(MANIFEST_PATH);
|
|
|
|
const auditResult = {
|
|
audit_id: `QUOTA-AUDIT-${getDateStr()}`,
|
|
timestamp: getTimestamp(),
|
|
services: {},
|
|
overall_status: 'healthy',
|
|
total_alerts: 0,
|
|
actions_required: []
|
|
};
|
|
|
|
// Audit each service
|
|
if (ledger.services) {
|
|
for (const [name, data] of Object.entries(ledger.services)) {
|
|
const serviceAudit = auditService(name, data);
|
|
auditResult.services[name] = serviceAudit;
|
|
|
|
if (serviceAudit.status === 'critical') {
|
|
auditResult.overall_status = 'critical';
|
|
} else if (serviceAudit.status === 'warning' && auditResult.overall_status !== 'critical') {
|
|
auditResult.overall_status = 'warning';
|
|
}
|
|
|
|
auditResult.total_alerts += serviceAudit.alerts.length;
|
|
auditResult.actions_required.push(...serviceAudit.actions);
|
|
}
|
|
}
|
|
|
|
// Update ledger health summary
|
|
if (ledger.health_summary) {
|
|
ledger.health_summary.overall = auditResult.overall_status;
|
|
ledger.health_summary.alerts = [];
|
|
for (const [, svc] of Object.entries(auditResult.services)) {
|
|
ledger.health_summary.alerts.push(...svc.alerts);
|
|
}
|
|
ledger.health_summary.last_updated = getTimestamp();
|
|
saveJSON(LEDGER_PATH, ledger);
|
|
}
|
|
|
|
// Write audit log
|
|
const logDir = path.join(LOGS_DIR, 'daily');
|
|
if (!fs.existsSync(logDir)) fs.mkdirSync(logDir, { recursive: true });
|
|
const logPath = path.join(logDir, `quota-audit-${getDateStr()}.json`);
|
|
saveJSON(logPath, auditResult);
|
|
|
|
console.log(`[SkyEye Quota Audit] Overall: ${auditResult.overall_status}`);
|
|
console.log(`[SkyEye Quota Audit] Alerts: ${auditResult.total_alerts}`);
|
|
for (const [name, svc] of Object.entries(auditResult.services)) {
|
|
console.log(` ${name}: ${svc.status} (${svc.usage_percent}%)`);
|
|
}
|
|
console.log(`[SkyEye Quota Audit] Log saved: ${logPath}`);
|
|
|
|
console.log('---QUOTA_AUDIT_JSON---');
|
|
console.log(JSON.stringify(auditResult, null, 2));
|
|
|
|
return auditResult;
|
|
}
|
|
|
|
run();
|