Files
openclaw-backups/skills/openclaw-self-healing/scripts/level2-weekly-analysis.js

298 lines
10 KiB
JavaScript

#!/usr/bin/env node
/**
* Level 2: Weekly Analysis (Semi-Automatic)
*
* 주간 로그 분석 + 파라미터 조정 제안 생성
* - 자동 실행 (매주 일요일 23:00)
* - 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'),
discordWebhook: process.env.DISCORD_WEBHOOK_URL || process.env.OPENCLAW_DISCORD_WEBHOOK,
timeWindow: 7 * 24 * 3600 * 1000 // 7 days
};
// ============================================================================
// Discord Notification
// ============================================================================
async function sendDiscordReport(analysis, recommendations) {
if (!CONFIG.discordWebhook) {
console.log('⚠️ No Discord webhook configured, skipping notification');
return;
}
const https = require('https');
const url = new URL(CONFIG.discordWebhook);
// Build Discord embed
const embed = {
title: '📊 Level 2: Weekly Auto-Retry Analysis',
description: `Analysis period: ${Math.round(CONFIG.timeWindow / (24 * 3600 * 1000))} days`,
color: recommendations.length > 0 ? 0xFFA500 : 0x00FF00, // Orange if recs, green if none
fields: [],
footer: { text: 'Level 2 Parameter Tuning (Semi-Automatic)' },
timestamp: new Date().toISOString()
};
// Overall summary
embed.fields.push({
name: '📈 Overall Summary',
value: [
`Total Executions: **${analysis.summary.overall.totalExecutions}**`,
`Success Rate: **${analysis.summary.overall.successRate}**`,
`Retry Rate: **${analysis.summary.overall.retryRate}**`,
`Failure Rate: **${analysis.summary.overall.failureRate}**`,
`Avg Duration: **${analysis.summary.overall.avgDuration}**`
].join('\n'),
inline: false
});
// Recommendations
if (recommendations.length > 0) {
const recText = recommendations.slice(0, 3).map((rec, i) => {
const icon = rec.severity === 'high' ? '🔴' : rec.severity === 'medium' ? '🟡' : '🟢';
const safeIcon = rec.safe ? '✅' : '⚠️';
return [
`${i + 1}. ${icon} **${rec.cron}**`,
` ${safeIcon} \`${rec.param}\`: ${rec.current} → **${rec.proposed}**`,
` 📝 ${rec.reason}`,
` 💡 ${rec.expectedImprovement}`,
` 🎯 Confidence: ${rec.confidence}`
].join('\n');
}).join('\n\n');
embed.fields.push({
name: `💡 Recommendations (${recommendations.length} total)`,
value: recText + (recommendations.length > 3 ? `\n\n_...and ${recommendations.length - 3} more_` : ''),
inline: false
});
// How to apply
embed.fields.push({
name: '🔧 How to Apply',
value: [
'```bash',
'# Review recommendations',
'cat ~/openclaw/logs/level2/recommendations-latest.json',
'',
'# Apply a specific recommendation',
'node ~/openclaw/scripts/apply-recommendation.js --id=0',
'```'
].join('\n'),
inline: false
});
} else {
embed.fields.push({
name: '✅ Status',
value: 'No optimization needed - all metrics within normal range',
inline: false
});
}
// Top errors (if any)
if (analysis.summary.topErrors.length > 0) {
const errorsText = analysis.summary.topErrors.slice(0, 3).map(e =>
`- **${e.category}** (${e.count}x): ${e.topType}`
).join('\n');
embed.fields.push({
name: '🚨 Top Errors',
value: errorsText,
inline: false
});
}
// Send to Discord
const message = { embeds: [embed] };
const data = JSON.stringify(message);
return new Promise((resolve, reject) => {
const options = {
hostname: url.hostname,
path: url.pathname,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': data.length
}
};
const req = https.request(options, (res) => {
if (res.statusCode === 204) {
console.log('✅ Discord notification sent');
resolve();
} else {
reject(new Error(`Discord returned ${res.statusCode}`));
}
});
req.on('error', reject);
req.write(data);
req.end();
});
}
// ============================================================================
// Main Analysis
// ============================================================================
async function main() {
console.log('╔═══════════════════════════════════════════════════════════╗');
console.log('║ 📊 Level 2: Weekly Auto-Retry Analysis ║');
console.log('╚═══════════════════════════════════════════════════════════╝\n');
// 1. Ensure output directory exists
if (!fs.existsSync(CONFIG.outputDir)) {
fs.mkdirSync(CONFIG.outputDir, { recursive: true });
}
// 2. Analyze logs
console.log('📖 Reading logs...');
const analyzer = new LogAnalyzer({
timeWindow: CONFIG.timeWindow,
minSampleSize: 5 // Low threshold for pattern detection, actual tuning has higher threshold
});
const analysis = await analyzer.analyze(CONFIG.logPath);
console.log(` ✅ Analyzed ${analysis.metadata.analyzedEntries} entries\n`);
// 3. Print summary
analyzer.printResults(analysis);
// 4. Generate recommendations
console.log('\n💡 Generating recommendations...');
const optimizer = new ParameterOptimizer({
aggressiveness: 'conservative',
requireStatisticalSignificance: true
});
const recommendations = optimizer.generateRecommendations(
analysis.patterns,
analysis.stats,
analysis.trends
);
console.log(` ✅ Generated ${recommendations.length} recommendation(s)\n`);
// 5. Print recommendations
if (recommendations.length > 0) {
console.log('═'.repeat(60));
console.log('💡 RECOMMENDATIONS');
console.log('═'.repeat(60) + '\n');
recommendations.forEach((rec, i) => {
const icon = rec.severity === 'high' ? '🔴' : rec.severity === 'medium' ? '🟡' : '🟢';
const safeIcon = rec.safe ? '✅ SAFE' : '⚠️ REVIEW REQUIRED';
console.log(`${i}. ${icon} ${rec.cron}`);
console.log(` Parameter: ${rec.param}`);
console.log(` Current: ${rec.current}`);
console.log(` Proposed: ${rec.proposed}`);
console.log(` Reason: ${rec.reason}`);
console.log(` Expected: ${rec.expectedImprovement}`);
console.log(` Severity: ${rec.severity}`);
console.log(` Confidence: ${rec.confidence}`);
console.log(` Safety: ${safeIcon}`);
if (rec.warning) {
console.log(` ⚠️ Warning: ${rec.warning}`);
}
if (rec.recommendation) {
console.log(` 📝 Note: ${rec.recommendation}`);
}
console.log('');
});
console.log('═'.repeat(60));
console.log('🔧 TO APPLY:');
console.log('═'.repeat(60));
console.log('');
console.log('# Review the recommendations');
console.log('cat ~/openclaw/logs/level2/recommendations-latest.json');
console.log('');
console.log('# Apply a specific recommendation (by index)');
console.log('node ~/openclaw/scripts/apply-recommendation.js --id=0');
console.log('');
console.log('# Apply all safe recommendations');
console.log('node ~/openclaw/scripts/apply-recommendation.js --all-safe');
console.log('');
} else {
console.log('✅ No recommendations - all metrics within normal range\n');
}
// 6. Save results
const timestamp = new Date().toISOString().replace(/:/g, '-').split('.')[0];
const outputPath = path.join(CONFIG.outputDir, `recommendations-${timestamp}.json`);
const latestPath = path.join(CONFIG.outputDir, 'recommendations-latest.json');
const output = {
timestamp: new Date().toISOString(),
analysis: {
summary: analysis.summary,
patterns: analysis.patterns,
trends: analysis.trends,
metadata: analysis.metadata
},
recommendations,
config: {
timeWindow: CONFIG.timeWindow,
aggressiveness: 'conservative'
}
};
fs.writeFileSync(outputPath, JSON.stringify(output, null, 2));
fs.writeFileSync(latestPath, JSON.stringify(output, null, 2));
console.log(`📁 Saved to: ${outputPath}`);
console.log(`📁 Latest: ${latestPath}\n`);
// 7. Send Discord notification
try {
await sendDiscordReport(analysis, recommendations);
} catch (error) {
console.error('❌ Discord notification failed:', error.message);
}
// 8. Summary
console.log('╔═══════════════════════════════════════════════════════════╗');
console.log('║ ✅ Weekly Analysis Complete ║');
console.log('╚═══════════════════════════════════════════════════════════╝');
console.log('');
console.log('Next steps:');
if (recommendations.length > 0) {
console.log(' 1. Review recommendations in Discord or log file');
console.log(' 2. Apply selected recommendations manually');
console.log(' 3. Monitor results for 24-48 hours');
console.log(' 4. Rollback if needed');
} else {
console.log(' - Continue monitoring (no action needed)');
}
console.log('');
}
// ============================================================================
// Run
// ============================================================================
if (require.main === module) {
main().catch(err => {
console.error('❌ Error:', err.message);
console.error(err.stack);
process.exit(1);
});
}
module.exports = { main };