@redpanda-data/docs-extensions-and-macros
Version:
Antora extensions and macros developed for Redpanda documentation.
212 lines (186 loc) • 5.41 kB
JavaScript
/**
* Usage Telemetry and Statistics
*
* Tracks usage of prompts, resources, and tools to provide insights
* into adoption and identify unused features.
*/
const fs = require('fs');
const path = require('path');
/**
* Usage statistics tracker
*/
class UsageStats {
constructor() {
this.prompts = new Map(); // name count
this.resources = new Map(); // uri count
this.tools = new Map(); // name count
this.startTime = new Date();
this.lastReportTime = new Date();
}
/**
* Record prompt usage
* @param {string} name - Prompt name
*/
recordPrompt(name) {
const count = this.prompts.get(name) || 0;
this.prompts.set(name, count + 1);
}
/**
* Record resource usage
* @param {string} uri - Resource URI
*/
recordResource(uri) {
const count = this.resources.get(uri) || 0;
this.resources.set(uri, count + 1);
}
/**
* Record tool usage
* @param {string} name - Tool name
*/
recordTool(name) {
const count = this.tools.get(name) || 0;
this.tools.set(name, count + 1);
}
/**
* Get stats summary
* @returns {Object} Statistics summary
*/
getSummary() {
const now = new Date();
const uptime = Math.floor((now - this.startTime) / 1000); // seconds
const timeSinceLastReport = Math.floor((now - this.lastReportTime) / 1000);
return {
uptime,
timeSinceLastReport,
promptCount: this.prompts.size,
resourceCount: this.resources.size,
toolCount: this.tools.size,
totalPromptCalls: Array.from(this.prompts.values()).reduce((a, b) => a + b, 0),
totalResourceCalls: Array.from(this.resources.values()).reduce((a, b) => a + b, 0),
totalToolCalls: Array.from(this.tools.values()).reduce((a, b) => a + b, 0),
prompts: Object.fromEntries(this.prompts),
resources: Object.fromEntries(this.resources),
tools: Object.fromEntries(this.tools)
};
}
/**
* Get top N most used items
* @param {Map} map - Map to get top from
* @param {number} n - Number of items
* @returns {Array} Top N items as [name, count] pairs
*/
getTopN(map, n = 5) {
return Array.from(map.entries())
.sort((a, b) => b[1] - a[1])
.slice(0, n);
}
/**
* Format stats for display
* @returns {string} Formatted stats
*/
format() {
const summary = this.getSummary();
const lines = [];
lines.push('MCP Server Usage Statistics');
lines.push('='.repeat(60));
lines.push('');
// Uptime
const hours = Math.floor(summary.uptime / 3600);
const minutes = Math.floor((summary.uptime % 3600) / 60);
lines.push(`Uptime: ${hours}h ${minutes}m`);
lines.push('');
// Totals
lines.push(`Total calls:`);
lines.push(` Prompts: ${summary.totalPromptCalls}`);
lines.push(` Resources: ${summary.totalResourceCalls}`);
lines.push(` Tools: ${summary.totalToolCalls}`);
lines.push('');
// Top prompts
if (this.prompts.size > 0) {
lines.push('Most used prompts:');
const topPrompts = this.getTopN(this.prompts, 5);
topPrompts.forEach(([name, count]) => {
lines.push(` ${name}: ${count} calls`);
});
lines.push('');
}
// Top resources
if (this.resources.size > 0) {
lines.push('Most used resources:');
const topResources = this.getTopN(this.resources, 5);
topResources.forEach(([uri, count]) => {
lines.push(` ${uri}: ${count} calls`);
});
lines.push('');
}
// Top tools
if (this.tools.size > 0) {
lines.push('Most used tools:');
const topTools = this.getTopN(this.tools, 5);
topTools.forEach(([name, count]) => {
lines.push(` ${name}: ${count} calls`);
});
lines.push('');
}
return lines.join('\n');
}
/**
* Export stats to JSON file
* @param {string} filepath - Path to export to
*/
exportToFile(filepath) {
const summary = this.getSummary();
summary.exportedAt = new Date().toISOString();
fs.writeFileSync(filepath, JSON.stringify(summary, null, 2), 'utf8');
}
/**
* Reset stats (for periodic reporting)
*/
reset() {
this.lastReportTime = new Date();
// Don't reset the maps - keep cumulative stats
}
}
/**
* Create a periodic reporter
* @param {UsageStats} stats - Stats instance
* @param {number} intervalMs - Interval in milliseconds
* @returns {NodeJS.Timeout} Interval handle
*/
function createPeriodicReporter(stats, intervalMs = 3600000) {
return setInterval(() => {
const output = stats.format();
console.error('\n' + output);
stats.reset();
}, intervalMs);
}
/**
* Create a shutdown handler to export stats
* @param {UsageStats} stats - Stats instance
* @param {string} baseDir - Base directory for export
*/
function createShutdownHandler(stats, baseDir) {
const handler = () => {
const exportPath = path.join(baseDir, 'mcp-usage-stats.json');
try {
stats.exportToFile(exportPath);
console.error(`\nUsage stats exported to: ${exportPath}`);
} catch (err) {
console.error(`Failed to export usage stats: ${err.message}`);
}
};
process.on('SIGINT', () => {
handler();
process.exit(0);
});
process.on('SIGTERM', () => {
handler();
process.exit(0);
});
return handler;
}
module.exports = {
UsageStats,
createPeriodicReporter,
createShutdownHandler
};