From f3dbe51be661611be19a3fe4c81d2aae70b6e1b8 Mon Sep 17 00:00:00 2001 From: Adam Kinniburgh Date: Thu, 6 Aug 2026 13:44:41 +0100 Subject: [PATCH 1/2] feat: jaeger plugin --- plugins/Jaeger/v1/configValidation.json | 13 ++ plugins/Jaeger/v1/cspell.json | 6 + plugins/Jaeger/v1/custom_types.json | 16 ++ .../Jaeger/v1/dataStreams/dependencies.json | 89 +++++++++++ plugins/Jaeger/v1/dataStreams/operations.json | 41 +++++ .../Jaeger/v1/dataStreams/scripts/traces.js | 83 ++++++++++ plugins/Jaeger/v1/dataStreams/services.json | 25 +++ plugins/Jaeger/v1/dataStreams/traces.json | 113 ++++++++++++++ .../Jaeger/v1/defaultContent/manifest.json | 12 ++ .../v1/defaultContent/overview.dash.json | 146 ++++++++++++++++++ plugins/Jaeger/v1/defaultContent/scopes.json | 19 +++ .../v1/defaultContent/service.dash.json | 121 +++++++++++++++ plugins/Jaeger/v1/docs/README.md | 53 +++++++ plugins/Jaeger/v1/icon.png | Bin 0 -> 11743 bytes .../Jaeger/v1/indexDefinitions/default.json | 37 +++++ plugins/Jaeger/v1/metadata.json | 51 ++++++ plugins/Jaeger/v1/ui.json | 18 +++ 17 files changed, 843 insertions(+) create mode 100644 plugins/Jaeger/v1/configValidation.json create mode 100644 plugins/Jaeger/v1/cspell.json create mode 100644 plugins/Jaeger/v1/custom_types.json create mode 100644 plugins/Jaeger/v1/dataStreams/dependencies.json create mode 100644 plugins/Jaeger/v1/dataStreams/operations.json create mode 100644 plugins/Jaeger/v1/dataStreams/scripts/traces.js create mode 100644 plugins/Jaeger/v1/dataStreams/services.json create mode 100644 plugins/Jaeger/v1/dataStreams/traces.json create mode 100644 plugins/Jaeger/v1/defaultContent/manifest.json create mode 100644 plugins/Jaeger/v1/defaultContent/overview.dash.json create mode 100644 plugins/Jaeger/v1/defaultContent/scopes.json create mode 100644 plugins/Jaeger/v1/defaultContent/service.dash.json create mode 100644 plugins/Jaeger/v1/docs/README.md create mode 100644 plugins/Jaeger/v1/icon.png create mode 100644 plugins/Jaeger/v1/indexDefinitions/default.json create mode 100644 plugins/Jaeger/v1/metadata.json create mode 100644 plugins/Jaeger/v1/ui.json diff --git a/plugins/Jaeger/v1/configValidation.json b/plugins/Jaeger/v1/configValidation.json new file mode 100644 index 00000000..02ee322f --- /dev/null +++ b/plugins/Jaeger/v1/configValidation.json @@ -0,0 +1,13 @@ +{ + "steps": [ + { + "displayName": "Check connection", + "dataStream": { + "name": "services" + }, + "required": true, + "error": "Could not reach the Jaeger Query API. Check the URL is correct, that the instance is reachable (directly or via an on-prem relay agent), and enable 'Ignore certificate errors' if it uses a self-signed certificate.", + "success": "Connected successfully." + } + ] +} diff --git a/plugins/Jaeger/v1/cspell.json b/plugins/Jaeger/v1/cspell.json new file mode 100644 index 00000000..7f2ae8e3 --- /dev/null +++ b/plugins/Jaeger/v1/cspell.json @@ -0,0 +1,6 @@ +{ + "words": [ + "jaeger", + "opentelemetry" + ] +} diff --git a/plugins/Jaeger/v1/custom_types.json b/plugins/Jaeger/v1/custom_types.json new file mode 100644 index 00000000..dc4501f3 --- /dev/null +++ b/plugins/Jaeger/v1/custom_types.json @@ -0,0 +1,16 @@ +[ + { + "name": "Service", + "sourceType": "Service", + "icon": "server", + "singular": "Service", + "plural": "Services" + }, + { + "name": "Dependency", + "sourceType": "Dependency", + "icon": "share-nodes", + "singular": "Dependency", + "plural": "Dependencies" + } +] diff --git a/plugins/Jaeger/v1/dataStreams/dependencies.json b/plugins/Jaeger/v1/dataStreams/dependencies.json new file mode 100644 index 00000000..7c52ca26 --- /dev/null +++ b/plugins/Jaeger/v1/dataStreams/dependencies.json @@ -0,0 +1,89 @@ +{ + "name": "dependencies", + "displayName": "Dependencies", + "description": "Call dependencies between services, one row per parent-child pair", + "tags": [ + "Services" + ], + "baseDataSourceName": "httpRequestUnscoped", + "matches": "none", + "timeframes": false, + "config": { + "httpMethod": "get", + "endpointPath": "/api/dependencies", + "pathToData": "data", + "getArgs": [ + { + "key": "endTs", + "value": "{{Date.now()}}" + }, + { + "key": "lookback", + "value": "{{lookback}}" + } + ], + "headers": [] + }, + "ui": [ + { + "type": "choiceChips", + "name": "lookback", + "label": "Lookback window", + "defaultValue": "3600000", + "options": [ + { + "value": "3600000", + "label": "Last hour" + }, + { + "value": "21600000", + "label": "Last 6 hours" + }, + { + "value": "86400000", + "label": "Last 24 hours" + }, + { + "value": "604800000", + "label": "Last 7 days" + } + ] + } + ], + "metadata": [ + { + "name": "key", + "displayName": "Dependency", + "computed": true, + "valueExpression": "{{ $['parent'] + ' -> ' + $['child'] }}", + "shape": "string", + "role": "label" + }, + { + "name": "parent", + "displayName": "Parent Service", + "shape": "string" + }, + { + "name": "child", + "displayName": "Child Service", + "shape": "string" + }, + { + "name": "callCount", + "displayName": "Call Count", + "shape": [ + "number", + { + "thousandsSeparator": true + } + ] + }, + { + "name": "source", + "displayName": "Source", + "shape": "string", + "visible": false + } + ] +} diff --git a/plugins/Jaeger/v1/dataStreams/operations.json b/plugins/Jaeger/v1/dataStreams/operations.json new file mode 100644 index 00000000..8dde26d3 --- /dev/null +++ b/plugins/Jaeger/v1/dataStreams/operations.json @@ -0,0 +1,41 @@ +{ + "name": "operations", + "displayName": "Operations", + "description": "Operation names reported by a service", + "tags": [ + "Operations" + ], + "baseDataSourceName": "httpRequestScopedSingle", + "matches": { + "sourceType": { + "type": "equals", + "value": "Service" + } + }, + "timeframes": false, + "config": { + "httpMethod": "get", + "endpointPath": "/api/v3/operations", + "getArgs": [ + { + "key": "service", + "value": "{{object.rawId}}" + } + ], + "headers": [], + "pathToData": "operations" + }, + "metadata": [ + { + "name": "name", + "displayName": "Operation", + "shape": "string", + "role": "label" + }, + { + "name": "spanKind", + "displayName": "Span Kind", + "shape": "string" + } + ] +} diff --git a/plugins/Jaeger/v1/dataStreams/scripts/traces.js b/plugins/Jaeger/v1/dataStreams/scripts/traces.js new file mode 100644 index 00000000..157afed2 --- /dev/null +++ b/plugins/Jaeger/v1/dataStreams/scripts/traces.js @@ -0,0 +1,83 @@ +// dataStreams/scripts/traces.js +// +// The /api/v3/traces response is a single JSON object shaped +// { "result": { "resourceSpans": [...] } } (OTLP JSON) - not NDJSON - so the +// platform's automatic JSON parse populates `data` and we read it directly. +// +// OTLP JSON encodes int64 (startTimeUnixNano/endTimeUnixNano) as STRING. +// Epoch nanoseconds (~1.7e18) exceed JS Number safe-integer precision, so we +// do the nanosecond arithmetic in BigInt and only convert to Number once +// we've reduced to milliseconds (~1.7e12, well within safe range). + +const SPAN_KIND = { + 0: "UNSPECIFIED", + 1: "INTERNAL", + 2: "SERVER", + 3: "CLIENT", + 4: "PRODUCER", + 5: "CONSUMER", +}; + +const STATUS_CODE = { + 0: "UNSET", + 1: "OK", + 2: "ERROR", +}; + +function attrsToObject(attributes) { + const obj = {}; + (attributes || []).forEach((a) => { + const value = a.value || {}; + obj[a.key] = + value.stringValue ?? + value.intValue ?? + value.doubleValue ?? + value.boolValue; + }); + return obj; +} + +function nanoStrToBig(nanoStr) { + return BigInt(nanoStr || "0"); +} + +const resourceSpans = (data && data.result && data.result.resourceSpans) || []; + +result = _.flatMap(resourceSpans, (rs) => { + const resourceAttrs = attrsToObject((rs.resource || {}).attributes); + const serviceName = resourceAttrs["service.name"] || ""; + + return _.flatMap(rs.scopeSpans || [], (ss) => { + return (ss.spans || []).map((s) => { + const startNsBig = nanoStrToBig(s.startTimeUnixNano); + const endNsBig = nanoStrToBig(s.endTimeUnixNano); + // Compute duration from the full-precision nanosecond values before + // rounding to ms — rounding each endpoint first and then subtracting + // can be off by up to 1ms (e.g. start=1.999999ms, end=2.000001ms + // truncate to 1ms/2ms, giving a 1ms duration for a ~2ns span). + const durationMsBig = (endNsBig - startNsBig) / 1000000n; + const status = s.status || {}; + // proto3 JSON omits fields at their default value, so an + // INTERNAL/unspecified-kind span (kind 0) typically has no `kind` + // field at all — default the missing case to 0 rather than + // stringifying `undefined`. + const kind = s.kind ?? 0; + + return { + traceId: s.traceId, + spanId: s.spanId, + parentSpanId: s.parentSpanId || "", + operationName: s.name, + serviceName, + kind: SPAN_KIND[kind] ?? String(kind), + startTime: new Date( + Number(startNsBig / 1000000n), + ).toISOString(), + durationMs: Number(durationMsBig), + statusCode: STATUS_CODE[status.code] ?? "UNSET", + statusMessage: status.message || "", + attributes: attrsToObject(s.attributes), + }; + }); + }); +}); diff --git a/plugins/Jaeger/v1/dataStreams/services.json b/plugins/Jaeger/v1/dataStreams/services.json new file mode 100644 index 00000000..f88c3abd --- /dev/null +++ b/plugins/Jaeger/v1/dataStreams/services.json @@ -0,0 +1,25 @@ +{ + "name": "services", + "displayName": "Services", + "description": "Services known to the Jaeger backend", + "tags": [ + "Services" + ], + "baseDataSourceName": "httpRequestUnscoped", + "matches": "none", + "timeframes": false, + "config": { + "httpMethod": "get", + "endpointPath": "/api/v3/services", + "postRequestScript": "result = (data.services || []).map(service => ({ service }));", + "headers": [] + }, + "metadata": [ + { + "name": "service", + "displayName": "Service", + "shape": "string", + "role": "label" + } + ] +} diff --git a/plugins/Jaeger/v1/dataStreams/traces.json b/plugins/Jaeger/v1/dataStreams/traces.json new file mode 100644 index 00000000..efe7b348 --- /dev/null +++ b/plugins/Jaeger/v1/dataStreams/traces.json @@ -0,0 +1,113 @@ +{ + "name": "traces", + "displayName": "Traces", + "description": "Spans reported by a service within the selected timeframe", + "tags": [ + "Traces" + ], + "baseDataSourceName": "httpRequestScopedSingle", + "matches": { + "sourceType": { + "type": "equals", + "value": "Service" + } + }, + "timeframes": true, + "config": { + "httpMethod": "get", + "endpointPath": "/api/v3/traces", + "getArgs": [ + { + "key": "query.service_name", + "value": "{{object.rawId}}" + }, + { + "key": "query.start_time_min", + "value": "{{timeframe.start}}" + }, + { + "key": "query.start_time_max", + "value": "{{timeframe.end}}" + }, + { + "key": "query.search_depth", + "value": "{{limit}}" + } + ], + "headers": [], + "postRequestScript": "traces.js" + }, + "ui": [ + { + "name": "limit", + "label": "Number of Traces", + "type": "number", + "defaultValue": 20, + "min": 1 + } + ], + "metadata": [ + { + "name": "traceId", + "displayName": "Trace ID", + "shape": "string" + }, + { + "name": "spanId", + "displayName": "Span ID", + "shape": "string", + "visible": false + }, + { + "name": "parentSpanId", + "displayName": "Parent Span ID", + "shape": "string", + "visible": false + }, + { + "name": "operationName", + "displayName": "Operation", + "shape": "string", + "role": "label" + }, + { + "name": "serviceName", + "displayName": "Service", + "shape": "string" + }, + { + "name": "kind", + "displayName": "Kind", + "shape": "string" + }, + { + "name": "startTime", + "displayName": "Start Time", + "shape": "date", + "role": "timestamp" + }, + { + "name": "durationMs", + "displayName": "Duration (ms)", + "shape": "milliseconds", + "role": "value" + }, + { + "name": "statusCode", + "displayName": "Status Code", + "shape": "string" + }, + { + "name": "statusMessage", + "displayName": "Status Message", + "shape": "string", + "visible": false + }, + { + "name": "attributes", + "displayName": "Attributes", + "shape": "json", + "visible": false + } + ] +} diff --git a/plugins/Jaeger/v1/defaultContent/manifest.json b/plugins/Jaeger/v1/defaultContent/manifest.json new file mode 100644 index 00000000..31242423 --- /dev/null +++ b/plugins/Jaeger/v1/defaultContent/manifest.json @@ -0,0 +1,12 @@ +{ + "items": [ + { + "name": "overview", + "type": "dashboard" + }, + { + "name": "service", + "type": "dashboard" + } + ] +} diff --git a/plugins/Jaeger/v1/defaultContent/overview.dash.json b/plugins/Jaeger/v1/defaultContent/overview.dash.json new file mode 100644 index 00000000..137d5e9b --- /dev/null +++ b/plugins/Jaeger/v1/defaultContent/overview.dash.json @@ -0,0 +1,146 @@ +{ + "name": "Overview", + "schemaVersion": "1.5", + "timeframe": "last24hours", + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "ca8ac41e-7b75-444a-a116-fd28fc6d3bba", + "x": 0, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Services", + "description": "Total number of services known to Jaeger", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[services]}}", + "name": "services", + "pluginConfigId": "{{configId}}", + "group": { + "by": [], + "aggregate": [ + { + "type": "count" + } + ] + } + }, + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "count", + "comparisonColumn": "none", + "label": "Services" + } + } + } + } + }, + { + "i": "09b6f5ed-2565-42d0-8295-e4b1fedaa2e3", + "x": 1, + "y": 0, + "w": 3, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Services", + "description": "Services known to the Jaeger backend", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[services]}}", + "name": "services", + "pluginConfigId": "{{configId}}" + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false + } + } + } + } + }, + { + "i": "3479eea2-6a91-44e3-9b3b-c22f90edfd98", + "x": 0, + "y": 2, + "w": 4, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Top Dependencies by Call Count", + "description": "Busiest service-to-service call paths in the last hour", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[dependencies]}}", + "name": "dependencies", + "pluginConfigId": "{{configId}}", + "sort": { + "by": [ + [ + "callCount", + "desc" + ] + ], + "top": 10 + } + }, + "visualisation": { + "type": "data-stream-bar-chart", + "config": { + "data-stream-bar-chart": { + "xAxisData": "key", + "yAxisData": [ + "callCount" + ], + "xAxisGroup": "none", + "xAxisLabel": "", + "yAxisLabel": "Calls", + "showXAxisLabel": false, + "showYAxisLabel": true, + "showLegend": false, + "legendPosition": "bottom", + "showGrid": true, + "horizontalLayout": "vertical", + "displayMode": "actual", + "showTotals": false, + "showValue": false, + "grouping": false, + "range": { + "type": "auto" + } + } + } + } + } + } + ] + } +} diff --git a/plugins/Jaeger/v1/defaultContent/scopes.json b/plugins/Jaeger/v1/defaultContent/scopes.json new file mode 100644 index 00000000..8d5adf22 --- /dev/null +++ b/plugins/Jaeger/v1/defaultContent/scopes.json @@ -0,0 +1,19 @@ +[ + { + "name": "Services", + "matches": { + "sourceType": { + "type": "oneOf", + "values": [ + "Service" + ] + } + }, + "variable": { + "name": "Service", + "allowMultipleSelection": false, + "default": "none", + "type": "object" + } + } +] diff --git a/plugins/Jaeger/v1/defaultContent/service.dash.json b/plugins/Jaeger/v1/defaultContent/service.dash.json new file mode 100644 index 00000000..ef515d97 --- /dev/null +++ b/plugins/Jaeger/v1/defaultContent/service.dash.json @@ -0,0 +1,121 @@ +{ + "name": "Service", + "schemaVersion": "1.5", + "timeframe": "last24hours", + "variables": [ + "{{variables.[Service]}}" + ], + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "62d75b50-0b77-4358-947f-cb76744a6044", + "x": 0, + "y": 0, + "w": 1, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Operations", + "description": "Operation names reported by this service", + "activePluginConfigIds": [ + "{{configId}}" + ], + "timeframe": "none", + "variables": [ + "{{variables.[Service]}}" + ], + "dataStream": { + "id": "{{dataStreams.[operations]}}", + "name": "operations", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "scope": "{{scopes.[Services]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Service]}}" + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "name", + "spanKind" + ] + } + } + } + } + }, + { + "i": "5b847262-6bde-4b0c-9ae9-c2aba620599b", + "x": 1, + "y": 0, + "w": 3, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Traces", + "description": "Spans reported by this service within the selected timeframe", + "activePluginConfigIds": [ + "{{configId}}" + ], + "variables": [ + "{{variables.[Service]}}" + ], + "dataStream": { + "id": "{{dataStreams.[traces]}}", + "name": "traces", + "pluginConfigId": "{{configId}}", + "sort": { + "by": [ + [ + "startTime", + "desc" + ] + ] + } + }, + "scope": { + "scope": "{{scopes.[Services]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Service]}}" + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "traceId", + "operationName", + "serviceName", + "kind", + "startTime", + "durationMs", + "statusCode" + ], + "hiddenColumns": [ + "spanId", + "parentSpanId", + "statusMessage", + "attributes" + ] + } + } + } + } + } + ] + } +} diff --git a/plugins/Jaeger/v1/docs/README.md b/plugins/Jaeger/v1/docs/README.md new file mode 100644 index 00000000..35fc2c5c --- /dev/null +++ b/plugins/Jaeger/v1/docs/README.md @@ -0,0 +1,53 @@ +Monitor services, dependencies, operations, and traces from a [Jaeger](https://www.jaegertracing.io/) distributed tracing backend, via its [Query API](https://www.jaegertracing.io/docs/latest/apis/). + +> ⚠️ Jaeger has no built-in authentication. If your deployment sits behind an authenticating reverse proxy, this plugin does not support that — the Query API must be reachable without credentials from the URL you provide. + +## Setup + +You will need the base URL of your Jaeger **Query** service (the same one that serves the Jaeger UI) — for example `http://jaeger-query:16686`. + +1. If your Jaeger instance is only reachable on an internal network, install a SquaredUp [on-prem relay agent](https://docs.squaredup.com/) that can reach it, and select that agent group when adding this plugin. +2. Add this plugin and paste the base URL into the **URL** field — no scheme-relative path (like `/api`) and no trailing slash; the plugin appends those automatically. +3. If your Jaeger instance uses a self-signed certificate, enable **Ignore certificate errors**. + +## Configuration fields + +| Field | What it is | Where to find it | Required | +| ----------------------------- | --------------------------------------------------------------- | ------------------------------------------------- | -------- | +| **URL** | The base URL of your Jaeger Query service. | The same host/port your Jaeger UI is served from. | Yes | +| **Ignore certificate errors** | Skips TLS certificate validation, for self-signed certificates. | — | No | + +On save, the plugin validates the URL by fetching the list of known services; an unreachable URL or connection failure fails setup with a connection error. + +## What this plugin monitors + +- **Services** — every service Jaeger has seen spans for. +- **Dependencies** — call relationships between services, with call counts over a configurable lookback window. +- **Operations** — the operation (endpoint/method) names reported by a service. +- **Traces** — individual spans reported by a service within a selected timeframe, including duration, kind, and status. + +The out-of-the-box dashboards include an estate-wide **Overview** plus a **Service** perspective. + +## Data streams + +- **Services** — services known to the Jaeger backend, account-wide. +- **Dependencies** — call dependencies between services, one row per parent-child pair, account-wide. +- **Operations** — operation names reported by a service, per service. +- **Traces** — spans reported by a service within the selected timeframe, per service. + +## What gets indexed + +| Object type | API source | Represents | +| -------------- | ------------------------- | ------------------------------------------ | +| **Service** | `GET /api/v3/services` | A service Jaeger has seen spans for. | +| **Dependency** | `GET /api/dependencies` | A call relationship between two services. | + +Each Dependency row carries `parent` and `child` properties naming the two Services involved in that call path. + +## Known limitations + +- **Dependencies reflect a fixed lookback window** — the Dependencies data stream queries Jaeger's dependency graph over a selectable window (last hour up to last 7 days), not the dashboard timeframe; it has no time-range parameter of its own. +- **Traces are per-service only** — there's no cross-service trace search or single-trace detail view in this version. +- **No Service Performance Monitoring (SPM) metrics** — SPM requires a separate metrics storage backend that most Jaeger deployments don't enable, so it isn't covered here. +- **No authentication** — the Jaeger Query API has none; this plugin can't authenticate through a reverse proxy that requires it. +- **Read-only** — the plugin never creates, modifies, or deletes anything in Jaeger. diff --git a/plugins/Jaeger/v1/icon.png b/plugins/Jaeger/v1/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..2e2c65945b050a15172946b83ea78bb8f4e48e83 GIT binary patch literal 11743 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 z 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 Date: Thu, 6 Aug 2026 14:56:03 +0100 Subject: [PATCH 2/2] fix: use real timeframes --- .../Jaeger/v1/dataStreams/dependencies.json | 37 ++++--------------- .../v1/defaultContent/overview.dash.json | 5 +-- plugins/Jaeger/v1/docs/README.md | 5 ++- 3 files changed, 13 insertions(+), 34 deletions(-) diff --git a/plugins/Jaeger/v1/dataStreams/dependencies.json b/plugins/Jaeger/v1/dataStreams/dependencies.json index 7c52ca26..287e95c2 100644 --- a/plugins/Jaeger/v1/dataStreams/dependencies.json +++ b/plugins/Jaeger/v1/dataStreams/dependencies.json @@ -7,7 +7,12 @@ ], "baseDataSourceName": "httpRequestUnscoped", "matches": "none", - "timeframes": false, + "timeframes": [ + "last1hour", + "last12hours", + "last24hours", + "last7days" + ], "config": { "httpMethod": "get", "endpointPath": "/api/dependencies", @@ -15,41 +20,15 @@ "getArgs": [ { "key": "endTs", - "value": "{{Date.now()}}" + "value": "{{timeframe?.unixEnd ? timeframe.unixEnd * 1000 : Date.now()}}" }, { "key": "lookback", - "value": "{{lookback}}" + "value": "{{(timeframe?.unixStart && timeframe?.unixEnd) ? (timeframe.unixEnd - timeframe.unixStart) * 1000 : 1800000}}" } ], "headers": [] }, - "ui": [ - { - "type": "choiceChips", - "name": "lookback", - "label": "Lookback window", - "defaultValue": "3600000", - "options": [ - { - "value": "3600000", - "label": "Last hour" - }, - { - "value": "21600000", - "label": "Last 6 hours" - }, - { - "value": "86400000", - "label": "Last 24 hours" - }, - { - "value": "604800000", - "label": "Last 7 days" - } - ] - } - ], "metadata": [ { "name": "key", diff --git a/plugins/Jaeger/v1/defaultContent/overview.dash.json b/plugins/Jaeger/v1/defaultContent/overview.dash.json index 137d5e9b..ef72d0a0 100644 --- a/plugins/Jaeger/v1/defaultContent/overview.dash.json +++ b/plugins/Jaeger/v1/defaultContent/overview.dash.json @@ -1,7 +1,7 @@ { "name": "Overview", "schemaVersion": "1.5", - "timeframe": "last24hours", + "timeframe": "last1hour", "dashboard": { "_type": "layout/grid", "columns": 4, @@ -93,11 +93,10 @@ "config": { "_type": "tile/data-stream", "title": "Top Dependencies by Call Count", - "description": "Busiest service-to-service call paths in the last hour", + "description": "Busiest service-to-service call paths within the selected timeframe", "activePluginConfigIds": [ "{{configId}}" ], - "timeframe": "none", "dataStream": { "id": "{{dataStreams.[dependencies]}}", "name": "dependencies", diff --git a/plugins/Jaeger/v1/docs/README.md b/plugins/Jaeger/v1/docs/README.md index 35fc2c5c..9fc91192 100644 --- a/plugins/Jaeger/v1/docs/README.md +++ b/plugins/Jaeger/v1/docs/README.md @@ -22,7 +22,7 @@ On save, the plugin validates the URL by fetching the list of known services; an ## What this plugin monitors - **Services** — every service Jaeger has seen spans for. -- **Dependencies** — call relationships between services, with call counts over a configurable lookback window. +- **Dependencies** — call relationships between services, with call counts over the selected timeframe. - **Operations** — the operation (endpoint/method) names reported by a service. - **Traces** — individual spans reported by a service within a selected timeframe, including duration, kind, and status. @@ -46,7 +46,8 @@ Each Dependency row carries `parent` and `child` properties naming the two Servi ## Known limitations -- **Dependencies reflect a fixed lookback window** — the Dependencies data stream queries Jaeger's dependency graph over a selectable window (last hour up to last 7 days), not the dashboard timeframe; it has no time-range parameter of its own. +- **Dependencies' timeframe is restricted to last 1 hour–last 7 days** — Jaeger's dependency-graph query is comparatively expensive, so the range of selectable timeframes is deliberately narrower than Traces' full range. +- **Scheduled indexing of Dependencies always uses a 1-hour trailing window** — indexing runs with no dashboard timeframe selected, so it falls back to querying just the hour before each run rather than spanning the full interval between imports (imports run every 12 hours by default). A dependency between two services that only occurred outside that trailing hour won't be indexed until it recurs within one. - **Traces are per-service only** — there's no cross-service trace search or single-trace detail view in this version. - **No Service Performance Monitoring (SPM) metrics** — SPM requires a separate metrics storage backend that most Jaeger deployments don't enable, so it isn't covered here. - **No authentication** — the Jaeger Query API has none; this plugin can't authenticate through a reverse proxy that requires it.