Class McpMetrics

java.lang.Object
com.mcpdbwizard.pub.McpMetrics

public final class McpMetrics extends Object
Per-tool call metrics for a generated MCP server, in Prometheus exposition format.

Why the generator has to tell it which database object a tool touches

At run time the server knows only the tool NAME — a lower-cased, punctuation-stripped form of an Oracle name, with an overload number appended where one was needed. Going back from that to APPSCHEMA.FIXTURE_PKG.GREET is not possible, and one Oracle object routinely yields several tools (a table yields at least four). The generator is the only place that holds the mapping, so it bakes it in through describe(java.lang.String, java.lang.String, java.lang.String) at start-up and every series carries the object as a label. That is what makes sum by (db_object) work in a query.

What is measured, and over what window

Counts and byte totals are cumulative since start-up, which is what a Prometheus counter must be. The quantiles are over the last 2048 calls to that tool, computed exactly from a ring of retained samples rather than estimated — the same windowed spirit as a Prometheus summary, and the reason they are useful at all: a p90 over all of history stops moving after a day and stops answering "is it slow now?".

The maximum is deliberately NOT windowed. A windowed max silently discards the worst call the server ever served, which is the one an operator is looking for.

Cost

record(java.lang.String, java.lang.String, long, long, long) takes one uncontended lock per tool and writes a handful of longs. It sits behind a database round trip, so it is noise. Nothing here is emitted unless the config sets PROMETHEUS_SERVER=YES, so a server that does not want metrics does not pay for the argument serialisation the byte counters need either. Copyright 2003-2026 ATB Consultancy Services Ltd (formerly Orinda Software Ltd, Dublin, Ireland)

  • Field Details

  • Method Details

    • forServer

      public static McpMetrics forServer(String theServerNameValue)
      Create the registry for one generated server.
      Parameters:
      theServerNameValue - the server class name, reported as the server label so a scrape of several servers on one host can tell them apart
    • forServer

      public static McpMetrics forServer(String theServerNameValue, String theConfigNameValue)
      As forServer(String), naming the config this server was generated from.
      Parameters:
      theConfigNameValue - the generation-time identity, typically the emitted package name. MCP_METRICS_CONFIG_LABEL overrides it at run time, which is how the web runtime substitutes the config's own name.
    • describe

      public void describe(String theToolNameValue, String theDbObjectValue, String theObjectTypeValue)
      Record which database object a tool reaches. Called once per tool at start-up, from a static initialiser the generator writes.

      Describing a tool does not make it appear in a scrape: a tool that has never been called has no series, which is correct — Prometheus counters should not be born at zero for thousands of tools that may never be used.

      Parameters:
      theToolNameValue - the MCP tool name, as it appears in tools/list
      theDbObjectValue - the fully-qualified Oracle object, e.g. APPSCHEMA.FIXTURE_PKG.GREET
      theObjectTypeValue - table, view, procedure, sequence, statement — what kind of thing the object is
    • bindPool

      public void bindPool(DaoFactoryPool<?> thePoolValue)
      Report the connection pool, so a scrape carries its counters too.

      These are the numbers the POOL-STATS log line has always held. They were only ever recoverable by tailing a log file and parsing it, which is a poor channel for something a monitoring system wants every fifteen seconds.

      Parameters:
      thePoolValue - the pool, or null for an unpooled server
    • bindAuditSink

      public void bindAuditSink(McpAuditSink theSinkValue)
      Report the audit sink, so a scrape carries the one number that says whether the trail can be relied on: how many records have been LOST.

      This is the metric worth alerting on. Everything else here describes how the server is performing; a non-zero drop count says the record of what it did is incomplete, and nothing else in the system will mention it — the sink counts and logs, and a log line is not something anyone watches for.

      Parameters:
      theSinkValue - the sink in use, or null when the server is unaudited
    • record

      public void record(String theToolNameValue, String theOutcomeValue, long theDurationMicros, long theRequestBytes, long theResponseBytes)
      Record one completed tool call. Called from the generated call(...) funnel's finally, so failures are measured as well as successes.
      Parameters:
      theToolNameValue - the tool that ran; null or blank is counted as UNKNOWN_TOOL
      theOutcomeValue - one of the McpCallRecord OUTCOME_ constants
      theDurationMicros - elapsed duration of the call in MICROSECONDS, and it must come from System.nanoTime() rather than a wall clock — at a few milliseconds a call, currentTimeMillis cannot resolve the measurement and an NTP step can make it run backwards
      theRequestBytes - size of the call's arguments as JSON, or 0 when there were none
      theResponseBytes - size of the response payload, or 0 when the call produced none
    • scrape

      public String scrape()
      The whole registry in Prometheus text exposition format (version 0.0.4).

      Deterministically ordered — tools by name, outcomes by name — so a diff of two scrapes is readable and the tests can assert on whole blocks rather than fishing for lines.