Files
openclaw-backups/skills/openclaw-self-healing/scripts/level2-auto-tune.js

178 lines
6.1 KiB
JavaScript

#!/usr/bin/env node
/**
* Level 2: Auto-Tune Cron Script
*
* 매일 새벽 3시에 실행:
* 1. 로그 분석 (log-analyzer)
* 2. 패턴 감지 → 추천 생성 (parameter-optimizer)
* 3. 결과 저장 + Discord 알림
*
* 자동 적용은 하지 않음 - 추천만 생성하고 알림
*/
const path = require('path');
const fs = require('fs');
const { LogAnalyzer } = require('../lib/log-analyzer');
const { ParameterOptimizer } = require('../lib/parameter-optimizer');
// ============================================================================
// Configuration
// ============================================================================
const CONFIG = {
logPath: path.join(process.env.HOME, 'openclaw/logs/auto-retry.jsonl'),
outputDir: path.join(process.env.HOME, 'openclaw/logs/level2'),
timeWindow: 7, // days
discordWebhook: process.env.OPENCLAW_DISCORD_WEBHOOK || null
};
// ============================================================================
// Main
// ============================================================================
async function main() {
const startTime = Date.now();
console.log(`[${new Date().toISOString()}] Level 2 Auto-Tune 시작\n`);
// Ensure output directory exists
if (!fs.existsSync(CONFIG.outputDir)) {
fs.mkdirSync(CONFIG.outputDir, { recursive: true });
}
// Step 1: Analyze logs
console.log('Step 1: 로그 분석...');
const analyzer = new LogAnalyzer({
timeWindow: CONFIG.timeWindow * 24 * 3600 * 1000,
minSampleSize: 5
});
let analysis;
try {
analysis = await analyzer.analyze(CONFIG.logPath);
console.log(`${analysis.metadata.analyzedEntries}건 분석 완료`);
} catch (error) {
console.error(` → 분석 실패: ${error.message}`);
process.exit(1);
}
// Save analysis
const analysisPath = path.join(CONFIG.outputDir, 'analysis-latest.json');
fs.writeFileSync(analysisPath, JSON.stringify(analysis, null, 2));
// Step 2: Generate recommendations
console.log('\nStep 2: 추천 생성...');
const optimizer = new ParameterOptimizer({
aggressiveness: 'conservative'
});
const recommendations = optimizer.generateRecommendations(
analysis.patterns,
analysis.stats,
analysis.trends
);
console.log(`${recommendations.length}건 추천 생성`);
// Save recommendations
const recResult = {
timestamp: new Date().toISOString(),
analysisId: analysis.metadata.analyzedAt,
recommendations,
summary: {
total: recommendations.length,
safe: recommendations.filter(r => r.safe).length,
highSeverity: recommendations.filter(r => r.severity === 'high').length,
highConfidence: recommendations.filter(r => r.confidence === 'high').length
},
stats: {
totalExecutions: analysis.summary.overall.totalExecutions,
successRate: analysis.summary.overall.successRate,
retryRate: analysis.summary.overall.retryRate,
avgDuration: analysis.summary.overall.avgDuration
}
};
const recPath = path.join(CONFIG.outputDir, 'recommendations-latest.json');
fs.writeFileSync(recPath, JSON.stringify(recResult, null, 2));
// Also save timestamped copy for history
const historyPath = path.join(
CONFIG.outputDir,
`recommendations-${new Date().toISOString().split('T')[0]}.json`
);
fs.writeFileSync(historyPath, JSON.stringify(recResult, null, 2));
// Step 3: Print summary
const duration = Date.now() - startTime;
console.log('\n' + '='.repeat(50));
console.log('Level 2 Auto-Tune 결과');
console.log('='.repeat(50));
console.log(`실행 시간: ${duration}ms`);
console.log(`분석 기간: ${CONFIG.timeWindow}`);
console.log(`총 실행: ${analysis.summary.overall.totalExecutions}`);
console.log(`성공률: ${analysis.summary.overall.successRate}`);
console.log(`재시도율: ${analysis.summary.overall.retryRate}`);
console.log(`평균 응답: ${analysis.summary.overall.avgDuration}`);
console.log(`감지 패턴: ${analysis.patterns.length}`);
console.log(`추천: ${recommendations.length}건 (안전: ${recResult.summary.safe}건)`);
console.log('='.repeat(50));
if (recommendations.length > 0) {
console.log('\n추천 목록:');
recommendations.forEach((rec, i) => {
const icon = rec.severity === 'high' ? '[HIGH]' : rec.severity === 'medium' ? '[MED]' : '[LOW]';
const safe = rec.safe ? '[SAFE]' : '[REVIEW]';
console.log(` ${i}. ${icon} ${safe} ${rec.cron}: ${rec.param} ${rec.current}${rec.proposed}`);
});
console.log(`\n적용: node ~/openclaw/scripts/apply-recommendation.js --list`);
} else {
console.log('\n모든 파라미터 정상 범위 - 조정 불필요');
}
// Step 4: Discord notification (if webhook configured)
if (CONFIG.discordWebhook && recommendations.length > 0) {
await sendDiscordNotification(recResult);
}
console.log(`\n[${new Date().toISOString()}] 완료 (${duration}ms)`);
}
async function sendDiscordNotification(result) {
try {
const message = {
embeds: [{
title: 'Level 2 Auto-Tune Report',
color: result.summary.highSeverity > 0 ? 0xFF0000 : 0x00FF00,
fields: [
{ name: '실행', value: result.stats.totalExecutions, inline: true },
{ name: '성공률', value: result.stats.successRate, inline: true },
{ name: '추천', value: `${result.summary.total}`, inline: true },
{ name: '안전', value: `${result.summary.safe}`, inline: true }
],
timestamp: result.timestamp
}]
};
const response = await fetch(CONFIG.discordWebhook, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(message)
});
if (response.ok) {
console.log(' → Discord 알림 전송 완료');
}
} catch (error) {
console.warn(` → Discord 알림 실패: ${error.message}`);
}
}
// ============================================================================
// Run
// ============================================================================
main().catch(error => {
console.error('Fatal error:', error.message);
process.exit(1);
});