Skip to main content

Performance Metrics

UI Bridge collects performance metrics to help you understand and optimize automation performance.

Metrics are produced by a MetricsCollector — an in-process ring buffer of action history that aggregates into a PerformanceMetrics snapshot on demand. There is no background sampling loop and no push channel: you record actions into the collector, and you read an aggregate out of it when you want one.

Enabling Metrics​

Inside a React app the provider builds the collector for you when the debug feature is on:

import { UIBridgeProvider } from '@qontinui/ui-bridge/react';

function App() {
return (
<UIBridgeProvider features={{ control: true, debug: true }}>
<YourApp />
</UIBridgeProvider>
);
}

With debug: false (the default) no collector is constructed and the metrics reads below return undefined.

Outside React — a Node driver, a test harness, a standalone server — construct one directly:

import { createMetricsCollector } from '@qontinui/ui-bridge';
import type { MetricsCollectorOptions } from '@qontinui/ui-bridge';

const options: MetricsCollectorOptions = {
// Ring-buffer size; oldest entries are dropped past this. Default 1000.
maxHistoryEntries: 1000,
// Window used for the actionsPerSecond rate, in ms. Default 60000.
rateWindow: 60_000,
};

const metrics = createMetricsCollector(options);

createMetricsCollector is a thin factory over the MetricsCollector class, which is exported too if you prefer new:

import { MetricsCollector } from '@qontinui/ui-bridge';

const metrics = new MetricsCollector({ maxHistoryEntries: 5000 });

Recording Actions​

A collector is fed, not scraped. There are two ways in.

From bridge events​

recordEvent accepts a BridgeEvent and records the action:completed and action:failed ones, ignoring everything else. This is exactly how the React provider wires itself up, and it is the path to use if you already have a registry:

import { getGlobalRegistry } from '@qontinui/ui-bridge';
import type { BridgeEvent } from '@qontinui/ui-bridge';

const registry = getGlobalRegistry();

const unsubCompleted = registry.on('action:completed', (event: BridgeEvent) => {
metrics.recordEvent(event);
});
const unsubFailed = registry.on('action:failed', (event: BridgeEvent) => {
metrics.recordEvent(event);
});

registry.on takes one concrete event type and returns its own unsubscribe function — there is no wildcard subscription, so subscribe to both action events if you want failures counted.

Directly, per action​

If you are driving actions yourself, record the response you got back:

import { createActionExecutor } from '@qontinui/ui-bridge';
import type { ControlActionResponse } from '@qontinui/ui-bridge';

const executor = createActionExecutor(registry);

const response: ControlActionResponse = await executor.executeAction('login-btn', {
action: 'click',
});

metrics.recordElementAction('login-btn', 'click', response);

The sibling recorders cover the other two action kinds:

metrics.recordComponentAction('login-form', 'login', componentResponse);
metrics.recordWorkflowStep('checkout-flow', stepResult);

Each recorder returns the ActionHistoryEntry it appended — id, timestamp, type (element / component / workflow-step), target, action, success, durationMs, and error when it failed.

Reading Metrics​

getMetrics() aggregates the whole buffer into a PerformanceMetrics. Pass a timestamp to aggregate only entries at or after it:

import type { PerformanceMetrics } from '@qontinui/ui-bridge';

const all: PerformanceMetrics = metrics.getMetrics();
const lastFiveMinutes = metrics.getMetrics(Date.now() - 5 * 60_000);

// {
// totalActions: 150,
// successfulActions: 147,
// failedActions: 3,
// successRate: 0.98,
// avgDurationMs: 245,
// minDurationMs: 12,
// maxDurationMs: 1890,
// p95DurationMs: 450,
// actionsPerSecond: 2.5,
// errorsByType: { TimeoutError: 2, NotFound: 1 },
// actionsByType: { 'element:click': 120, 'element:type': 30 },
// }

Note the exact field names — durations are avgDurationMs / minDurationMs / maxDurationMs / p95DurationMs. There is no p50 or p99 in the snapshot; the only percentile computed is p95. An empty buffer returns the same shape with every number at 0 rather than null or a throw.

Two formatters ship alongside for display:

import { formatDuration, formatPercentage } from '@qontinui/ui-bridge';

formatDuration(245); // '245ms'
formatDuration(2450); // '2.5s'
formatPercentage(0.98); // '98.0%'

Querying History​

The aggregate is derived; the history is the primary record, and it is filterable:

import type { ActionHistoryEntry } from '@qontinui/ui-bridge';

const failedClicks: ActionHistoryEntry[] = metrics.getHistory({
type: 'element',
action: 'click',
success: false,
since: Date.now() - 60_000,
limit: 50,
});

Every filter is optional and they compose; limit takes the most recent N after filtering.

Two shortcuts cover the common triage questions:

const errors = metrics.getRecentErrors(10); // last 10 failed entries
const slowest = metrics.getSlowestActions(10); // 10 highest durationMs

Export and Import​

History serializes to JSON — useful for attaching a run's action log to a CI artifact, or for replaying one collector's buffer into another:

const json = metrics.exportHistory();

const restored = createMetricsCollector();
restored.importHistory(json);

importHistory keeps only the most recent maxHistoryEntries from the JSON, so importing a larger buffer into a smaller collector truncates rather than grows it. To start clean:

metrics.clearHistory();

Reading Metrics Over HTTP​

When the bridge server is running, the collector's aggregate is served at:

GET /debug/metrics

The response is the PerformanceMetrics object above, wrapped in the standard API envelope. When no collector is attached to the registry, the route degrades to a registry census (elementCount, componentCount) rather than failing — so check for the metric fields you need instead of assuming they are present.

The companion route serves the raw entries:

GET /debug/action-history?limit=100

From the Python client:

from ui_bridge import UIBridgeClient

client = UIBridgeClient("http://localhost:9876")
metrics = client.get_metrics() # -> PerformanceMetrics
print(metrics.success_rate, metrics.p95_duration_ms)

history = client.get_action_history(limit=100)

Reading Metrics From React​

useUIBridge exposes the provider's collector through two reads. Both return undefined when the debug feature is off:

import { useUIBridge } from '@qontinui/ui-bridge/react';
import type { PerformanceMetrics } from '@qontinui/ui-bridge';

function MetricsPanel() {
const bridge = useUIBridge();

const snapshot = bridge.getMetrics() as PerformanceMetrics | undefined;
if (!snapshot) return <p>Metrics disabled — enable the debug feature.</p>;

return (
<dl>
<dt>Actions</dt>
<dd>{snapshot.totalActions}</dd>
<dt>Success rate</dt>
<dd>{(snapshot.successRate * 100).toFixed(1)}%</dd>
<dt>p95</dt>
<dd>{snapshot.p95DurationMs}ms</dd>
</dl>
);
}

bridge.getActionHistory() returns the entries behind that snapshot.

Both are plain reads, not subscriptions — nothing re-renders on its own. If you want a live panel, poll on an interval you choose, or re-read inside your own event handler.