4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
163 lines
5.2 KiB
Python
163 lines
5.2 KiB
Python
# ======== from tools/tempo_tool/ ========
|
|
|
|
"""Grafana Tempo trace query tool (single action-based entrypoint)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from core.tool_framework.tool_decorator import tool
|
|
from integrations.tempo import TempoConfig, tempo_extract_params
|
|
from integrations.tempo.availability import tempo_available_or_backend
|
|
from integrations.tempo.client import TempoClient
|
|
|
|
_VALID_ACTIONS = ("search", "get_trace", "list_services", "list_span_names")
|
|
|
|
|
|
def _tempo_is_available(sources: dict[str, dict]) -> bool:
|
|
return tempo_available_or_backend(sources)
|
|
|
|
|
|
def _tempo_extract_params(sources: dict[str, dict]) -> dict[str, Any]:
|
|
tempo = sources.get("tempo", {})
|
|
return {
|
|
**tempo_extract_params(sources),
|
|
"service": tempo.get("service_name", ""),
|
|
"time_range_minutes": tempo.get("time_range_minutes", 60),
|
|
"limit": 20,
|
|
"tempo_backend": tempo.get("_backend"),
|
|
}
|
|
|
|
|
|
def _dispatch(
|
|
client: Any,
|
|
*,
|
|
action: str,
|
|
trace_id: str | None,
|
|
service: str | None,
|
|
span_name: str | None,
|
|
min_duration_ms: float | None,
|
|
max_duration_ms: float | None,
|
|
tags: dict[str, str] | None,
|
|
time_range_minutes: int,
|
|
limit: int,
|
|
) -> dict[str, Any]:
|
|
result: dict[str, Any]
|
|
if action == "get_trace":
|
|
result = client.get_trace_by_id(trace_id or "")
|
|
elif action == "list_services":
|
|
result = client.list_services(time_range_minutes=time_range_minutes)
|
|
elif action == "list_span_names":
|
|
result = client.list_span_names(time_range_minutes=time_range_minutes)
|
|
else:
|
|
result = client.search_traces(
|
|
service=service,
|
|
span_name=span_name,
|
|
min_duration_ms=min_duration_ms,
|
|
max_duration_ms=max_duration_ms,
|
|
tags=tags,
|
|
time_range_minutes=time_range_minutes,
|
|
limit=limit,
|
|
)
|
|
return result
|
|
|
|
|
|
@tool(
|
|
name="query_tempo",
|
|
display_name="Grafana Tempo",
|
|
source="tempo",
|
|
tags=("traces", "observability"),
|
|
description=(
|
|
"Query a standalone Grafana Tempo backend for distributed traces. "
|
|
"Use 'action' to pick: search traces, fetch a trace by ID, or list "
|
|
"registered services / span names."
|
|
),
|
|
use_cases=[
|
|
"Fetching a full trace by trace ID to inspect its spans",
|
|
"Searching traces by service, span name, duration, or tags",
|
|
"Listing services and span names registered in Tempo",
|
|
"Correlating slow or error spans with logs and metrics",
|
|
],
|
|
requires=[],
|
|
input_schema={
|
|
"type": "object",
|
|
"properties": {
|
|
"action": {
|
|
"type": "string",
|
|
"enum": list(_VALID_ACTIONS),
|
|
"default": "search",
|
|
"description": "Which Tempo query to run.",
|
|
},
|
|
"trace_id": {
|
|
"type": "string",
|
|
"description": "Trace ID to fetch (required when action='get_trace').",
|
|
},
|
|
"service": {"type": "string", "description": "Service name filter for search."},
|
|
"span_name": {"type": "string", "description": "Span name filter for search."},
|
|
"min_duration_ms": {"type": "number", "description": "Minimum span duration (ms)."},
|
|
"max_duration_ms": {"type": "number", "description": "Maximum span duration (ms)."},
|
|
"tags": {
|
|
"type": "object",
|
|
"description": "Span attribute filters (key -> value), applied as span.<key>.",
|
|
},
|
|
"time_range_minutes": {"type": "integer", "default": 60},
|
|
"limit": {"type": "integer", "default": 20},
|
|
},
|
|
"required": [],
|
|
},
|
|
is_available=_tempo_is_available,
|
|
extract_params=_tempo_extract_params,
|
|
)
|
|
def query_tempo(
|
|
action: str = "search",
|
|
trace_id: str | None = None,
|
|
service: str | None = None,
|
|
span_name: str | None = None,
|
|
min_duration_ms: float | None = None,
|
|
max_duration_ms: float | None = None,
|
|
tags: dict[str, str] | None = None,
|
|
time_range_minutes: int = 60,
|
|
limit: int = 20,
|
|
tempo_backend: Any = None,
|
|
**_kwargs: Any,
|
|
) -> dict[str, Any]:
|
|
"""Query Grafana Tempo for traces, a single trace, or registered tag values."""
|
|
if action not in _VALID_ACTIONS:
|
|
action = "search"
|
|
|
|
if tempo_backend is not None:
|
|
return _dispatch(
|
|
tempo_backend,
|
|
action=action,
|
|
trace_id=trace_id,
|
|
service=service,
|
|
span_name=span_name,
|
|
min_duration_ms=min_duration_ms,
|
|
max_duration_ms=max_duration_ms,
|
|
tags=tags,
|
|
time_range_minutes=time_range_minutes,
|
|
limit=limit,
|
|
)
|
|
|
|
config = TempoConfig.model_validate(_kwargs)
|
|
if not config.is_configured:
|
|
return {
|
|
"source": "tempo",
|
|
"action": action,
|
|
"available": False,
|
|
"error": "Tempo not configured. Provide TEMPO_URL.",
|
|
}
|
|
|
|
return _dispatch(
|
|
TempoClient(config),
|
|
action=action,
|
|
trace_id=trace_id,
|
|
service=service,
|
|
span_name=span_name,
|
|
min_duration_ms=min_duration_ms,
|
|
max_duration_ms=max_duration_ms,
|
|
tags=tags,
|
|
time_range_minutes=time_range_minutes,
|
|
limit=limit,
|
|
)
|