Skip to main content
Glama
performance.ts•3.83 kB
/** * @fileoverview Provides a utility for performance monitoring of tool execution. * This module introduces a higher-order function to wrap tool logic, measure its * execution time, and log a structured metrics event. * @module src/utils/internal/performance */ import { SpanStatusCode, trace } from "@opentelemetry/api"; import { ATTR_CODE_FUNCTION, ATTR_CODE_NAMESPACE, } from "../telemetry/semconv.js"; import { config } from "../../config/index.js"; import { McpError } from "../../types-global/errors.js"; import { logger } from "./logger.js"; import { RequestContext } from "./requestContext.js"; /** * Calculates the size of a payload in bytes. * @param payload - The payload to measure. * @returns The size in bytes. * @private */ function getPayloadSize(payload: unknown): number { if (!payload) return 0; try { const stringified = JSON.stringify(payload); return Buffer.byteLength(stringified, "utf8"); } catch { return 0; // Could not stringify } } /** * A higher-order function that wraps a tool's core logic to measure its performance * and log a structured metrics event upon completion. * * @template T The expected return type of the tool's logic function. * @param toolLogicFn - The asynchronous tool logic function to be executed and measured. * @param context - The request context for the operation, used for logging and tracing. * @param inputPayload - The input payload to the tool for size calculation. * @returns A promise that resolves with the result of the tool logic function. * @throws Re-throws any error caught from the tool logic function after logging the failure. */ export async function measureToolExecution<T>( toolLogicFn: () => Promise<T>, context: RequestContext & { toolName: string }, inputPayload: unknown, ): Promise<T> { const tracer = trace.getTracer( config.openTelemetry.serviceName, config.openTelemetry.serviceVersion, ); const { toolName } = context; return tracer.startActiveSpan(`tool_execution:${toolName}`, async (span) => { span.setAttributes({ [ATTR_CODE_FUNCTION]: toolName, [ATTR_CODE_NAMESPACE]: "mcp-tools", "mcp.tool.input_bytes": getPayloadSize(inputPayload), }); const startTime = process.hrtime.bigint(); let isSuccess = false; let errorCode: string | undefined; let outputPayload: T | undefined; try { const result = await toolLogicFn(); isSuccess = true; outputPayload = result; span.setStatus({ code: SpanStatusCode.OK }); span.setAttribute("mcp.tool.output_bytes", getPayloadSize(outputPayload)); return result; } catch (error) { if (error instanceof McpError) { errorCode = error.code; } else if (error instanceof Error) { errorCode = "UNHANDLED_ERROR"; } else { errorCode = "UNKNOWN_ERROR"; } if (error instanceof Error) { span.recordException(error); } span.setStatus({ code: SpanStatusCode.ERROR, message: error instanceof Error ? error.message : String(error), }); throw error; } finally { const endTime = process.hrtime.bigint(); const durationMs = Number(endTime - startTime) / 1_000_000; span.setAttributes({ "mcp.tool.duration_ms": parseFloat(durationMs.toFixed(2)), "mcp.tool.success": isSuccess, }); if (errorCode) { span.setAttribute("mcp.tool.error_code", errorCode); } span.end(); logger.info("Tool execution finished.", { ...context, metrics: { durationMs: parseFloat(durationMs.toFixed(2)), isSuccess, errorCode, inputBytes: getPayloadSize(inputPayload), outputBytes: getPayloadSize(outputPayload), }, }); } }); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/cyanheads/pubmed-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server