210 lines
6.2 KiB
JavaScript
210 lines
6.2 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* skyeye/scripts/optimizer.js
|
|
* 天眼动态调优引擎 — 基于配额使用情况自动调整 Guard 触发频率
|
|
* Phase 3 of weekly scan: Optimize Guards
|
|
*/
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const ROOT = path.resolve(__dirname, '../..');
|
|
const SKYEYE_DIR = path.resolve(__dirname, '..');
|
|
const GUARDS_DIR = path.join(SKYEYE_DIR, 'guards');
|
|
const LEDGER_PATH = path.join(SKYEYE_DIR, 'quota-ledger.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 calculateUsagePercent(guard) {
|
|
const qp = guard.quota_policy;
|
|
if (!qp || !qp.monthly_limit || qp.monthly_limit === 0) return 0;
|
|
return Math.round(((qp.current_month_used || 0) / qp.monthly_limit) * 100 * 10) / 10;
|
|
}
|
|
|
|
const OPTIMIZATION_RULES = [
|
|
{
|
|
name: 'quota_warning_reduce_frequency',
|
|
condition: (guard) => {
|
|
const pct = calculateUsagePercent(guard);
|
|
return pct > 70 && pct <= 90;
|
|
},
|
|
action: (guard) => {
|
|
const prev = guard.trigger_policy.default_frequency;
|
|
guard.trigger_policy.default_frequency = guard.trigger_policy.min_frequency;
|
|
return {
|
|
rule: 'quota_warning_reduce_frequency',
|
|
guard_id: guard.guard_id,
|
|
action: 'reduce_frequency',
|
|
detail: `配额超 70%,频率从 ${prev} 降至 ${guard.trigger_policy.min_frequency}`,
|
|
previous: prev,
|
|
current: guard.trigger_policy.min_frequency
|
|
};
|
|
}
|
|
},
|
|
{
|
|
name: 'quota_critical_emergency_only',
|
|
condition: (guard) => {
|
|
const pct = calculateUsagePercent(guard);
|
|
return pct > 90;
|
|
},
|
|
action: (guard) => {
|
|
const prevMode = guard.mode;
|
|
guard.mode = 'emergency_only';
|
|
return {
|
|
rule: 'quota_critical_emergency_only',
|
|
guard_id: guard.guard_id,
|
|
action: 'switch_to_emergency',
|
|
detail: `配额超 90%,切换至仅紧急模式`,
|
|
previous_mode: prevMode,
|
|
current_mode: 'emergency_only'
|
|
};
|
|
}
|
|
},
|
|
{
|
|
name: 'health_check_failures_suspend',
|
|
condition: (guard) => {
|
|
return guard.health_check && guard.health_check.consecutive_failures >= 3;
|
|
},
|
|
action: (guard) => {
|
|
const prevMode = guard.mode;
|
|
guard.mode = 'suspended';
|
|
return {
|
|
rule: 'health_check_failures_suspend',
|
|
guard_id: guard.guard_id,
|
|
action: 'suspend',
|
|
detail: `连续 ${guard.health_check.consecutive_failures} 次健康检查失败,已暂停`,
|
|
previous_mode: prevMode,
|
|
current_mode: 'suspended'
|
|
};
|
|
}
|
|
},
|
|
{
|
|
name: 'backlog_increase_frequency',
|
|
condition: (guard) => {
|
|
const pct = calculateUsagePercent(guard);
|
|
const pending = guard.buffer_policy && guard.buffer_policy.pending_count;
|
|
return pending > 100 && pct < 50;
|
|
},
|
|
action: (guard) => {
|
|
const prev = guard.trigger_policy.default_frequency;
|
|
guard.trigger_policy.default_frequency = guard.trigger_policy.max_frequency;
|
|
return {
|
|
rule: 'backlog_increase_frequency',
|
|
guard_id: guard.guard_id,
|
|
action: 'increase_frequency',
|
|
detail: `消息积压 ${guard.buffer_policy.pending_count} 条,配额充裕,已加频`,
|
|
previous: prev,
|
|
current: guard.trigger_policy.max_frequency
|
|
};
|
|
}
|
|
}
|
|
];
|
|
|
|
function optimizeGuard(guard) {
|
|
const adjustments = [];
|
|
|
|
for (const rule of OPTIMIZATION_RULES) {
|
|
if (rule.condition(guard)) {
|
|
const adjustment = rule.action(guard);
|
|
adjustments.push(adjustment);
|
|
// Critical/emergency rules take priority - stop processing
|
|
if (rule.name === 'quota_critical_emergency_only' || rule.name === 'health_check_failures_suspend') {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (adjustments.length > 0) {
|
|
guard.last_updated_by = 'skyeye-optimizer';
|
|
guard.last_updated_at = getTimestamp();
|
|
}
|
|
|
|
return adjustments;
|
|
}
|
|
|
|
function run() {
|
|
const args = process.argv.slice(2);
|
|
const applyChanges = args.includes('--apply');
|
|
|
|
console.log(`[SkyEye Optimizer] Mode: ${applyChanges ? 'apply' : 'dry-run'}`);
|
|
console.log(`[SkyEye Optimizer] Timestamp: ${getTimestamp()}`);
|
|
|
|
const optimizeResult = {
|
|
optimize_id: `OPT-${getDateStr()}`,
|
|
timestamp: getTimestamp(),
|
|
mode: applyChanges ? 'apply' : 'dry-run',
|
|
guards_checked: 0,
|
|
adjustments: [],
|
|
summary: {}
|
|
};
|
|
|
|
const guardFiles = fs.readdirSync(GUARDS_DIR)
|
|
.filter(f => f.endsWith('.json') && f !== 'guard-template.json');
|
|
|
|
for (const file of guardFiles) {
|
|
const filePath = path.join(GUARDS_DIR, file);
|
|
const guard = loadJSON(filePath);
|
|
if (!guard) {
|
|
console.warn(`[SkyEye Optimizer] WARNING: Cannot load ${file}`);
|
|
continue;
|
|
}
|
|
|
|
optimizeResult.guards_checked++;
|
|
const adjustments = optimizeGuard(guard);
|
|
|
|
if (adjustments.length > 0) {
|
|
optimizeResult.adjustments.push(...adjustments);
|
|
if (applyChanges) {
|
|
saveJSON(filePath, guard);
|
|
console.log(` [APPLIED] ${file}: ${adjustments.length} adjustment(s)`);
|
|
} else {
|
|
console.log(` [DRY-RUN] ${file}: ${adjustments.length} adjustment(s) would be applied`);
|
|
}
|
|
} else {
|
|
console.log(` [OK] ${file}: no adjustments needed`);
|
|
}
|
|
}
|
|
|
|
optimizeResult.summary = {
|
|
guards_checked: optimizeResult.guards_checked,
|
|
total_adjustments: optimizeResult.adjustments.length,
|
|
applied: applyChanges
|
|
};
|
|
|
|
// Write log
|
|
const logDir = path.join(LOGS_DIR, 'weekly');
|
|
if (!fs.existsSync(logDir)) fs.mkdirSync(logDir, { recursive: true });
|
|
const logPath = path.join(logDir, `optimize-${getDateStr()}.json`);
|
|
saveJSON(logPath, optimizeResult);
|
|
|
|
console.log(`[SkyEye Optimizer] Guards checked: ${optimizeResult.guards_checked}`);
|
|
console.log(`[SkyEye Optimizer] Adjustments: ${optimizeResult.adjustments.length}`);
|
|
console.log(`[SkyEye Optimizer] Log saved: ${logPath}`);
|
|
|
|
console.log('---OPTIMIZE_RESULT_JSON---');
|
|
console.log(JSON.stringify(optimizeResult, null, 2));
|
|
|
|
return optimizeResult;
|
|
}
|
|
|
|
run();
|