@layr-labs/hourglass-performer
Version:
TypeScript SDK for building Hourglass AVS performers
407 lines • 14.6 kB
JavaScript
"use strict";
// Core PerformerServer implementation
// Based on the Go PonosPerformer in ponos/pkg/performer/server/server.go
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PerformerServer = void 0;
const grpc = __importStar(require("@grpc/grpc-js"));
const protoLoader = __importStar(require("@grpc/proto-loader"));
const path_1 = __importDefault(require("path"));
const performer_1 = require("../types/performer");
const logger_1 = require("../utils/logger");
const taskProcessor_1 = require("./taskProcessor");
const healthManager_1 = require("./healthManager");
const metrics_1 = require("../utils/metrics");
const diagnostics_1 = require("../utils/diagnostics");
/**
* PerformerServer class that implements the gRPC PerformerService
* This mirrors the Go PonosPerformer structure
*/
class PerformerServer {
constructor(worker, config = {}, taskProcessorConfig, healthConfig, metricsConfig) {
this.isRunning = false;
this.worker = worker;
this.config = {
port: config.port ?? 8080,
timeout: config.timeout ?? 5000,
debug: config.debug ?? false,
};
this.logger = (0, logger_1.createLogger)({ debug: this.config.debug });
// Initialize health manager
this.healthManager = new healthManager_1.HealthManager(this.logger, {
checkInterval: 30000,
maxFailures: 3,
...healthConfig,
});
// Initialize metrics collector
this.metricsCollector = new metrics_1.MetricsCollector(this.logger, {
enabled: true,
defaultLabels: { service: 'hourglass-performer' },
...metricsConfig,
});
// Initialize diagnostic tool
this.diagnosticTool = new diagnostics_1.DiagnosticTool(this.logger, {
enabled: this.config.debug,
enablePerformanceMonitoring: true,
enableMemoryLeakDetection: true,
});
this.diagnosticTool.setMetricsCollector(this.metricsCollector);
// Initialize task processor with enhanced capabilities
this.taskProcessor = new taskProcessor_1.TaskProcessor(worker, this.logger, {
timeout: this.config.timeout,
enableMetrics: true,
...taskProcessorConfig,
});
this.server = new grpc.Server();
this.setupGrpcServer();
}
/**
* Set up the gRPC server with the PerformerService
*/
setupGrpcServer() {
// Load protobuf definition
const protoPath = path_1.default.join(__dirname, '../../proto/performer.proto');
const packageDefinition = protoLoader.loadSync(protoPath, {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
});
const protoDescriptor = grpc.loadPackageDefinition(packageDefinition);
const performerService = protoDescriptor.eigenlayer.hourglass.v1.performer.PerformerService;
// Register service implementation
this.server.addService(performerService.service, {
ExecuteTask: this.executeTask.bind(this),
HealthCheck: this.healthCheck.bind(this),
StartSync: this.startSync.bind(this),
});
}
/**
* Start the gRPC server
*/
async start() {
if (this.isRunning) {
this.logger.warn('Server is already running');
return;
}
// Start monitoring services
this.healthManager.start();
this.metricsCollector.start();
this.diagnosticTool.start();
return new Promise((resolve, reject) => {
const bindAddress = `0.0.0.0:${this.config.port}`;
this.server.bindAsync(bindAddress, grpc.ServerCredentials.createInsecure(), (error, port) => {
if (error) {
this.logger.error('Failed to bind server', { error: error.message });
this.healthManager.setStatus(performer_1.PerformerStatus.ERROR);
reject(error);
return;
}
this.server.start();
this.isRunning = true;
this.healthManager.setStatus(performer_1.PerformerStatus.READY_FOR_TASK);
this.logger.info(`Performer server started on port ${port}`);
this.metricsCollector.counter('server_starts_total');
resolve();
});
});
}
/**
* Stop the gRPC server gracefully
*/
async stop() {
if (!this.isRunning) {
this.logger.warn('Server is not running');
return;
}
this.healthManager.setStatus(performer_1.PerformerStatus.STOPPING);
return new Promise((resolve) => {
this.logger.info('Shutting down server...');
this.server.tryShutdown((error) => {
if (error) {
this.logger.error('Error during shutdown', { error: error.message });
// Force shutdown
this.server.forceShutdown();
}
else {
this.logger.info('Server shutdown complete');
}
// Stop monitoring services
this.healthManager.stop();
this.metricsCollector.stop();
this.diagnosticTool.stop();
this.isRunning = false;
resolve();
});
});
}
/**
* Execute a task (gRPC handler)
*/
async executeTask(call, callback) {
const request = call.request;
const timer = this.metricsCollector.timer('task_execution');
this.logger.info('Received task', { taskId: request.taskId });
this.metricsCollector.counter('tasks_received_total');
this.healthManager.recordTask();
// Start diagnostic trace if enabled
this.diagnosticTool.startTrace(request.taskId, 'execute_task', {
payloadSize: request.payload.length,
});
try {
this.healthManager.setStatus(performer_1.PerformerStatus.BUSY);
// Use the enhanced task processor
const response = await this.taskProcessor.processTask(request);
timer(); // Record execution time
this.metricsCollector.counter('tasks_completed_total');
this.metricsCollector.histogram('task_payload_size_bytes', request.payload.length);
this.metricsCollector.histogram('task_result_size_bytes', response.result.length);
this.diagnosticTool.endTrace(request.taskId, { success: true });
this.healthManager.setStatus(performer_1.PerformerStatus.READY_FOR_TASK);
callback(null, response);
}
catch (error) {
timer(); // Record execution time even on failure
this.metricsCollector.counter('tasks_failed_total');
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
this.logger.error('Task processing failed', {
taskId: request.taskId,
error: errorMessage
});
this.healthManager.recordError(`Task ${request.taskId}: ${errorMessage}`);
this.diagnosticTool.recordError(errorMessage);
this.diagnosticTool.endTrace(request.taskId, { success: false, error: errorMessage });
// Convert to gRPC error
const grpcError = this.createGrpcError(error, request.taskId);
callback(grpcError);
}
}
/**
* Get task execution metrics
*/
getTaskMetrics() {
return this.taskProcessor.getMetrics();
}
/**
* Clear accumulated task metrics
*/
clearTaskMetrics() {
this.taskProcessor.clearMetrics();
}
/**
* Add custom serialization strategy
*/
addSerializationStrategy(strategy) {
this.taskProcessor.addSerializationStrategy(strategy);
}
/**
* Health check (gRPC handler)
*/
async healthCheck(call, callback) {
try {
// Perform comprehensive health check
const healthResult = await this.healthManager.performHealthCheck();
const currentStatus = this.healthManager.getStatus();
this.metricsCollector.counter('health_checks_total');
this.metricsCollector.gauge('health_status', currentStatus === performer_1.PerformerStatus.READY_FOR_TASK ? 1 : 0);
const response = {
status: currentStatus,
};
callback(null, response);
}
catch (error) {
this.logger.error('Health check failed', {
error: error instanceof Error ? error.message : 'Unknown error'
});
const response = {
status: performer_1.PerformerStatus.ERROR,
};
callback(null, response);
}
}
/**
* Start sync (gRPC handler)
*/
async startSync(call, callback) {
const response = {};
callback(null, response);
}
/**
* Convert application errors to gRPC errors
*/
createGrpcError(error, taskId) {
let code = grpc.status.INTERNAL;
let message = 'Internal server error';
if (error instanceof performer_1.TaskValidationError) {
code = grpc.status.INVALID_ARGUMENT;
message = `Task validation failed: ${error.message}`;
}
else if (error instanceof performer_1.TaskExecutionError) {
code = grpc.status.INTERNAL;
message = `Task execution failed: ${error.message}`;
}
else if (error instanceof Error) {
message = error.message;
}
const grpcError = {
name: 'ServiceError',
message,
code,
details: taskId ? `Task ID: ${taskId}` : '',
metadata: new grpc.Metadata(),
};
return grpcError;
}
/**
* PerformerService interface implementation
*/
async ExecuteTask(request) {
return new Promise((resolve, reject) => {
const call = {};
call.request = request;
this.executeTask(call, (error, response) => {
if (error) {
reject(error);
}
else {
resolve(response);
}
});
});
}
async HealthCheck(request) {
return new Promise((resolve, reject) => {
const call = {};
call.request = request;
this.healthCheck(call, (error, response) => {
if (error) {
reject(error);
}
else {
resolve(response);
}
});
});
}
async StartSync(request) {
return new Promise((resolve, reject) => {
const call = {};
call.request = request;
this.startSync(call, (error, response) => {
if (error) {
reject(error);
}
else {
resolve(response);
}
});
});
}
/**
* Set up graceful shutdown handlers
*/
setupGracefulShutdown() {
const shutdown = async (signal) => {
this.logger.info(`Received ${signal}, shutting down gracefully...`);
await this.stop();
process.exit(0);
};
process.on('SIGTERM', () => shutdown('SIGTERM'));
process.on('SIGINT', () => shutdown('SIGINT'));
}
/**
* Get server status
*/
getStatus() {
return {
isRunning: this.isRunning,
config: this.config,
};
}
/**
* Get comprehensive health information
*/
async getHealthInfo() {
return await this.healthManager.performHealthCheck();
}
/**
* Get health summary
*/
getHealthSummary() {
return this.healthManager.getHealthSummary();
}
/**
* Get metrics summary
*/
getMetricsSummary() {
return this.metricsCollector.getSummary();
}
/**
* Get diagnostic report
*/
getDiagnosticReport() {
return this.diagnosticTool.generateReport(this.healthManager.getStatus(), this.config);
}
/**
* Add custom health check provider
*/
addHealthProvider(provider) {
this.healthManager.addProvider(provider);
}
/**
* Add custom metrics exporter
*/
addMetricsExporter(exporter) {
this.metricsCollector.addExporter(exporter);
}
/**
* Record custom metric
*/
recordMetric(name, value, labels) {
this.metricsCollector.gauge(name, value, labels);
}
/**
* Get active diagnostic traces
*/
getActiveTraces() {
return this.diagnosticTool.getActiveTraces();
}
}
exports.PerformerServer = PerformerServer;
//# sourceMappingURL=performerServer.js.map