# Agent Harness Engineering Glossary Source: https://docs.openlit.io/latest/concepts/glossary Short definitions of agent harness, harness engineering, agent observability, agent evals, guardrails, and trajectory and tool-call evaluation. This glossary defines the core terms OpenLIT uses for **agent harness engineering**. Each entry opens with a one-sentence definition you can quote. ## Agent harness An **agent harness** is everything in an AI agent except the model: tools, context, prompts, memory, hooks, guardrails, and feedback loops that turn a model into a working agent. Claude Code, Codex, and frameworks such as LangGraph or CrewAI are harnesses. OpenLIT does not replace your harness; it observes, evaluates, and improves it via OpenTelemetry. Related: [AI agent observability](/latest/openlit/observability/agents/overview) · [Coding agent observability](/latest/openlit/coding-agents/overview) ## Agent harness engineering **Agent harness engineering** (or harness engineering) is the discipline of designing, measuring, and improving the harness around a model so agents are reliable in production. The core loop is *run → observe → evaluate → fix the harness → verify*. OpenLIT is the open-source platform for that loop: tracing, evals, guardrails, prompt and context management, and cost and GPU monitoring. Related: [What is OpenLIT?](/latest/overview) ## Agent observability **Agent observability** is the practice of tracing every LLM call, tool call, MCP request, retrieval, and agent step so you can see cost, latency, errors, and behavior across the full harness — not only a single model response. OpenLIT provides OpenTelemetry-native agent observability for application agents and coding agents. Related: [Agents](/latest/openlit/observability/agents/overview) · [Telemetry](/latest/openlit/observability/telemetry/overview) · [What is AI Observability?](/latest/glossary/what-is-ai-observability) ## Agent evals **Agent evals** (agent evaluation) score agent and LLM outputs for quality, safety, and task success. In OpenLIT you can run LLM-as-a-judge and programmatic evals on production traces, collect human feedback, and use the same criteria as CI/CD regression gates. Related: [Evaluations](/latest/openlit/evaluations/overview) · [LLM-as-a-judge](/latest/openlit/evaluations/llm-as-a-judge) · [Programmatic evals](/latest/openlit/evaluations/programmatic-evals) · [What is AI Evaluation?](/latest/glossary/what-is-ai-evaluation) ## Guardrails **Guardrails** are runtime checks that block or flag risky agent behavior before it reaches users or downstream systems — for example prompt injection, sensitive topics, and topic restriction. OpenLIT SDK guardrails emit OpenTelemetry traces so blocked requests stay visible in the same observability loop. Related: [Guardrails](/latest/sdk/features/guardrails) · [Guardrails quickstart](/latest/openlit/quickstart-guard) ## Trajectory evaluation / tool-call evaluation **Trajectory evaluation** scores the path an agent took (which tools it called, in what order, and with what outcomes), not only the final answer. **Tool-call evaluation** focuses on whether individual tool invocations were correct, necessary, and successful. Both help turn a failed run into a harness fix (better tools, prompts, or rules) rather than a one-off prompt tweak. Related: [Agent evals](/latest/openlit/evaluations/overview) · [Agent observability](/latest/openlit/observability/agents/overview) ## Prompt management **Prompt management** is versioning, publishing, and fetching prompts at runtime so you can fix the harness without redeploying application code. OpenLIT Prompt Hub links prompt versions to traces and evaluations. Related: [Prompt Hub](/latest/openlit/prompts-experiments/prompt-hub/overview) · [What is Prompt Engineering?](/latest/glossary/what-is-prompt-engineering) ## OpenTelemetry GenAI semantic conventions **OpenTelemetry GenAI semantic conventions** are the standard attribute and span names (`gen_ai.*`) for LLM and agent telemetry. OpenLIT follows these conventions so traces export cleanly to OpenLIT or any OTLP backend. Related: [SDK overview](/latest/sdk/overview) · [Destinations](/latest/sdk/destinations/overview) *** > **[What is OpenLIT?](/latest/overview)** > > Platform overview and the harness engineering loop > **[Agent observability](/latest/openlit/observability/agents/overview)** > > Trace every agent step from OpenTelemetry data > **[Agent evals](/latest/openlit/evaluations/overview)** > > Score production traces and gate releases in CI # What is AI Evaluation? Source: https://docs.openlit.io/latest/glossary/what-is-ai-evaluation AI evaluation explained: how LLM-as-a-judge scoring works, online vs. offline evaluation, and how to catch quality regressions AI evaluation is the process of scoring LLM outputs for quality, safety, and correctness - using an LLM-as-a-judge, rule-based checks, or human review - so you can catch bad responses before or after they reach users. ## Why does AI evaluation matter? An LLM can produce a fluent, confident-sounding response that's factually wrong, biased, or off-topic, without ever raising an error. AI evaluation gives you a quality signal traditional monitoring can't: a score for hallucination, bias, toxicity, relevance, and more, attached to the exact prompt version and model that produced the response. ## What is LLM-as-a-judge? LLM-as-a-judge uses a separate LLM call to grade a response against a rubric - for example, "does this response contain a hallucinated fact?" - and returns a score and explanation. It scales further than human review and can run automatically on every response in production. ## What's the difference between online and offline evaluation? * **Online evaluation** runs automatically against live production traces - manually triggered, or on an automatic schedule with sampling to control cost. * **Offline evaluation** runs programmatically, typically in CI/CD, against a fixed dataset before you ship a prompt or model change - catching regressions before they reach production. ## How does OpenLIT provide AI evaluation? OpenLIT includes 11 built-in evaluation types (hallucination, bias, toxicity, and more), runs them automatically in production from [Configuration](/latest/openlit/evaluations/configuration) (Auto Evaluation), and exposes the same evaluators [programmatically via the SDK](/latest/sdk/features/evaluations) for offline testing in CI/CD. *** > **[Evaluations overview](/latest/openlit/evaluations/overview)** > > The 11 built-in evaluation types and how automated scoring works > **[Configuration](/latest/openlit/evaluations/configuration)** > > Auto Evaluation schedule and judge model under Monitor > **[Offline evaluations (SDK)](/latest/sdk/features/evaluations)** > > Run evaluations programmatically in CI/CD and development pipelines # What is AI Observability? Source: https://docs.openlit.io/latest/glossary/what-is-ai-observability AI observability explained: what it is, why it matters, and how to add it to LLM apps and agents with zero code changes AI observability is the practice of collecting traces, metrics, and logs from LLM applications and AI agents so you can see exactly what happened on every request - which model was called, how long it took, what it cost, and whether it failed. ## Why does AI observability matter? LLM applications fail in ways traditional monitoring doesn't catch: a model can return a slow, expensive, or low-quality response without ever throwing an error. Without AI observability, you only find out from a user complaint. With it, you can see the exact prompt, model, latency, and cost behind every call - and catch regressions before they reach production. ## What data does AI observability cover? * **Traces**: the full request flow - model calls, tool calls, retries, and agent steps, in order. * **Metrics**: aggregated numbers over time, like latency, token throughput, and error rate. * **Logs**: discrete events emitted by your application and its dependencies. * **Cost and token usage**: spend per request, model, and provider, computed from token counts and per-model pricing. ## How is AI observability different from traditional APM? Traditional application performance monitoring (APM) tracks HTTP requests, database queries, and infrastructure health. AI observability adds what APM tools don't understand: which model and provider handled a call, the prompt and completion content, token usage, per-call cost, and the tool-call graph an agent followed to produce its answer. ## How does OpenLIT provide AI observability? OpenLIT auto-instruments 90+ LLMs, agent frameworks, and vector databases via OpenTelemetry - with zero code changes - and surfaces traces, metrics, and logs in one [Telemetry](/latest/openlit/observability/telemetry/overview) page, plus a trace-derived call graph for every [agent](/latest/openlit/observability/agents/overview) it observes. *** > **[Telemetry](/latest/openlit/observability/telemetry/overview)** > > View traces, metrics, logs, and exceptions in one place > **[Agents](/latest/openlit/observability/agents/overview)** > > See every agent's trace-derived call graph, tools, and versions > **[Quickstart: AI Observability](/latest/openlit/quickstart-ai-observability)** > > Instrument an LLM app with zero code changes in 2 steps # What is Prompt Engineering? Source: https://docs.openlit.io/latest/glossary/what-is-prompt-engineering Prompt engineering explained: versioning, testing, and deploying prompts as managed, trackable artifacts Prompt engineering is the practice of designing, testing, and iterating on the instructions you send to an LLM to get reliable, high-quality output. In production, that means treating prompts as versioned artifacts you can edit, test, roll back, and link to the results they produced - not just strings hardcoded in your application. ## Why does prompt engineering need tooling? A prompt that works well in testing can quietly degrade after a small wording change, a model swap, or a new edge case in production traffic. Without version history and a link back to the traces and evaluations a prompt produced, it's hard to tell what changed or why quality shifted. ## What does managing prompts as artifacts involve? * **Versioning**: track major, minor, and patch changes to a prompt over time. * **Variables**: use `{{placeholder}}` syntax so one prompt template serves many runtime inputs. * **Testing before shipping**: compare how a prompt performs across different models before deploying it. * **Traceability**: link each prompt version to the traces and evaluation scores it produced in production. ## How does OpenLIT support prompt engineering? [Prompt Hub](/latest/openlit/prompts-experiments/prompt-hub/overview) manages prompts as versioned artifacts with variable substitution and full history, [OpenGround](/latest/openlit/prompts-experiments/openground/overview) lets you test a prompt across multiple models side-by-side before shipping, and every prompt version is linked back to the traces and evaluation results it produced in production. *** > **[Prompt Hub](/latest/openlit/prompts-experiments/prompt-hub/overview)** > > Version, deploy, and collaborate on prompts with centralized management and tracking > **[OpenGround](/latest/openlit/prompts-experiments/openground/overview)** > > Test the same prompt across multiple LLM providers and compare cost, speed, and quality > **[Rule Engine](/latest/openlit/prompts-experiments/rule-engine/overview)** > > Match runtime conditions to the right prompt, context, or evaluation config # Configuration Source: https://docs.openlit.io/latest/gpu-collector/configuration Environment variables reference for the OpenTelemetry GPU Collector The collector is configured entirely via environment variables. All variables follow the standard [OpenTelemetry SDK configuration](https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/) spec where applicable. ## Collector mode `OTEL_GPU_COLLECTOR_MODE` selects a use-case preset (`all` | `light` | `serving` | `training` | `deep`). **Default: `all`.** Presets only fill **unset** feature envs; explicit flags always win. See [Modes](/latest/gpu-collector/modes) for the full matrix, cost notes, and hosting checklist. | Variable | Default | Description | | - | - | - | | `OTEL_GPU_COLLECTOR_MODE` | `all` | Feature preset. Invalid values warn and fall back to `all` | ## Environment Variables | Variable | Default | Description | | - | - | - | | `OTEL_EXPORTER_OTLP_ENDPOINT` | *(required)* | OTLP endpoint URL, e.g. `http://localhost:4318` | | `OTEL_EXPORTER_OTLP_HEADERS` | | Auth headers in `key=val,key2=val2` format | | `OTEL_EXPORTER_OTLP_PROTOCOL` | `grpc` | `grpc` or `http/protobuf` | | `OTEL_SERVICE_NAME` | *(unset)* | Optional. Only set if your backend requires `service.name`. This agent is **not** an application service — product identity uses `telemetry.distro.name=opentelemetry-gpu-collector` and `telemetry.distro.version` | | `OTEL_RESOURCE_ATTRIBUTES` | `deployment.environment=default` | Resource attributes (`key=val,...`). Prefer setting `host.name`, `k8s.*`, `cloud.provider`, `host.type`, `cloud.region` here — overrides auto-detect. Org tags such as `team` or `datacenter` are not auto-detected; set them here. | | `OTEL_METRIC_EXPORT_INTERVAL` | `60000` | Metric polling interval in **milliseconds**. For self-hosted LLM hosts, `15000` is recommended | | `OTEL_GPU_EBPF_ENABLED` | mode-dependent (`true` on Linux for `serving`+) | eBPF CUDA activity tracing + stream-sync occupancy (Linux/NVIDIA only). Discovers `libcudart` and `libcuda` via FS + `/proc` maps (fleet-friendly with host PID). Soft-fails without caps; set `false` to disable | | `OPENLIT_HOST_METRICS` | `true` | Collect system + collector-process host metrics. Set `false` for a GPU-only light footprint | | `OTEL_GPU_FS_TYPES_EXCLUDE` | `squashfs,erofs,iso9660,cramfs,romfs,cd9660,CDFS,UDF` | Filesystem types excluded from `system.filesystem.*` metrics (case-sensitive). Default skips image-based and optical filesystems that are 100% full by construction (e.g. snap mounts). Set to an empty string to report all types | | `OTEL_GPU_PROCESS_CMDLINE` | `true` | Export truncated `process.command_line` on GPU process metrics | | `OTEL_GPU_PROCESS_CMDLINE_MAX_LEN` | `512` | Max characters for `process.command_line` | | `OTEL_GPU_ALLOCATED_UTIL_THRESHOLD` | `0.05` | Util threshold (0–1) used with process memory for `hw.gpu.allocated` | | `OTEL_GPU_INTERCONNECT_ENABLED` | `true` | Export NVLink/XGMI interconnect throughput when available | | `K8S_NODE_NAME` | | Kubernetes node name via downward API (`spec.nodeName`). Also accepts `OTEL_RESOURCE_ATTRIBUTES_NODE_NAME` (Operator) or legacy `NODE_NAME` | | `K8S_CLUSTER_NAME` | | Explicit cluster name when cloud auto-detect fails (on-prem). Alias: `OPENLIT_K8S_CLUSTER_NAME`. Only applied in Kubernetes | | `OPENLIT_K8S_NODE_LOOKUP` | `true` | When `false`, skip `GET /api/v1/nodes/$K8S_NODE_NAME` for instance-type / provider discovery | | `OPENLIT_K8S_POD_RESOURCES` | `true` in K8s | Kubelet PodResources socket. Names a process only when it has no pod of its own, or the allocation is confirmed as its pod. See below | | `OPENLIT_K8S_POD_LOOKUP` | `true` in K8s when `K8S_NODE_NAME` is set | List pods on this node (`list` on pods). Resolves a containerized process's cgroup UID to its own `k8s.pod.name` and namespace | | `POD_RESOURCES_SOCKET` | OS default | Override kubelet PodResources socket / named pipe path | | `OPENLIT_CLOUD_DETECT` | `true` | When `false`, skip AWS/GCP/Azure IMDS probes (recommended on bare metal to avoid link-local timeouts) | | `OTEL_GPU_DCGM_ENABLED` | mode-dependent (`true` for `serving`+) | Optional NVIDIA DCGM profiling (soft-skipped without libdcgm / when DCGM cannot init) | | `OTEL_GPU_DCGM_LIB_PATH` | `/lib64/libdcgm.so` | Path to libdcgm | | `OTEL_GPU_DCGM_ADDRESS` | | Empty = embedded; else hostengine address | | `OTEL_GPU_DCGM_INTERVAL` | `10` | DCGM sample interval in **seconds** (warns if \< 10s; DCP multiplexing) | | `OTEL_GPU_DCGM_PREFER` | mode-dependent (`true` for `serving`+) | When true and DCGM is up: DCGM owns overlapping `hw.gpu.io` / interconnect / power / util / SM clock / `hw.gpu.memory.controller.utilization`; NVML suppresses those series. Spec `hw.gpu.memory.utilization` (usage/limit) stays on the vendor path | | `OTEL_GPU_DCGM_FIELDS` | `50,100,155,203,204,1001–1012` | Comma-separated DCGM field IDs (`155`=power, `203`=GPU util, `204`=mem util, `1001–1012`=DCP) | | `OTEL_GPU_RDC_ENABLED` | mode-dependent (`true` for `all`) | Optional AMD RDC profiling (soft-skipped without librdc) | | `OTEL_GPU_RDC_LIB_PATH` | `librdc.so` | Path / soname for librdc | | `OTEL_GPU_KINETO_ENABLED` | mode-dependent (`true` for `deep`/`all`) | On-demand Kineto IPC + control profiling | | `OTEL_GPU_PROMETHEUS_ADDR` | | When set (e.g. `:9464`), serve Prometheus `/metrics` alongside OTLP | | `OTEL_GPU_CONTROL_ADDR` | mode-dependent (`127.0.0.1:1919` for `deep`/`all`) | Loopback control HTTP; empty disables | | `OTEL_GPU_CONTROL_TOKEN` | | Optional Bearer token for control API (**required** if allow-remote) | | `OTEL_GPU_CONTROL_ALLOW_REMOTE` | `false` | Allow non-loopback bind for multi-node fan-out (requires token) | | `OTEL_HOST_CPU_HIGHRES` | mode-dependent (`true` for `deep`/`all`) | 100ms CPU ring buffer for `GET /v1/cpu/highres` (requires control addr; Linux) | | `OTEL_HOST_PMU_ENABLED` | mode-dependent (`true` for `training`+) | Core CPU PMU counters (`hw.cpu.instructions`, …) | | `OTEL_HOST_PMU_EVENTS` | `instructions,cycles` or +`memory_bandwidth` in training+ | Include `memory_bandwidth` / `uncore` for IMC `hw.cpu.memory.io` | | `OTEL_HOST_INTEL_PT_ENABLED` | mode-dependent (`true` for `deep`/`all`) | On-demand Intel PT via `POST /v1/profile/cpu/pt` (needs `perf` + intel\_pt) | | `OTEL_HOST_INTEL_PT_MAX_DURATION_MS` | `2000` | Hard cap for PT capture duration | | `OTEL_HOST_INTEL_PT_MAX_CPUS` | `4` | Max CPUs traced per PT capture | | `OTEL_HOST_INTEL_PT_MAX_BUFFER_PAGES` | `64` | AUX mmap pages passed to `perf -m` | | `OTEL_TPU_ENABLED` | mode-dependent (`true` for `all`) | Scrape TPU Prometheus endpoint | | `OTEL_HOST_KVM_ENABLED` | mode-dependent (`true` for `all`) | KVM exit / VM metrics | | `OTEL_HOST_INTERRUPTS_ENABLED` | mode-dependent (`true` for `all`) | Host interrupt counters | | `OTEL_HOST_NIC_ENABLED` | mode-dependent (`true` for `training`+) | Per-NIC `hw.network.*` (ethtool/sysfs). When enabled, `system.network.*` is skipped to avoid duplicates | | `OTEL_HOST_RDMA_ENABLED` | mode-dependent (`true` for `training`+) | RDMA device counters | > [!NOTE] > > **Pod name on `process.gpu.*`.** `OPENLIT_K8S_POD_LOOKUP` joins the process cgroup UID to that pod's name and namespace. It needs `list` on pods (see the DaemonSet role below). The list call is field-selected to this node; the permission itself is cluster-scoped. > > Without that list, kubelet PodResources supplies a name for a host process (the owner of the GPU being scraped) or when a container's `/proc//root/etc/hostname` confirms an allocation. The kubelet writes the pod name there. `setHostnameAsFQDN` counts only when the owner's namespace is in the name (`...svc.`). `hostNetwork` sees the node hostname, and `spec.hostname` sees the override, so those stay UID-only unless that hostname still names the owner. Two pods with the same name in different namespaces are left UID-only. Confirmation looks at every GPU allocation on the node, so the name does not depend on which GPU is scraped first. `k8s.pod.uid` stays the cgroup value either way. ## Host, Kubernetes, and cloud identity Resource attributes follow [OpenTelemetry semantic conventions](https://opentelemetry.io/docs/specs/semconv/resource/) for host, K8s, and cloud: | Attribute | When set | | - | - | | `host.name` | Always (from `OTEL_RESOURCE_ATTRIBUTES`, or `K8S_NODE_NAME` → GCE hostname in K8s → OS hostname) | | `k8s.node.name` | When a node env is set (`K8S_NODE_NAME` / Operator / legacy `NODE_NAME`) | | `k8s.cluster.name` | In Kubernetes only: `K8S_CLUSTER_NAME` / `OTEL_RESOURCE_ATTRIBUTES` → GKE / AKS / EKS metadata | | `cloud.provider` | Auto: K8s `Node.spec.providerID` → AWS/GCP/Azure IMDS → DMI vendor hint | | `cloud.platform` | e.g. `aws_eks`, `gcp_kubernetes_engine`, `aws_ec2`, `azure_aks` | | `host.type` | Instance type (e.g. `g4dn.xlarge`, `a2-highgpu-1g`, `Standard_NC6s_v3`) from node labels or IMDS | | `cloud.region` / `cloud.availability_zone` | Topology labels or IMDS | | `cloud.account.id` | AWS account / GCP project / Azure subscription when available | | `host.id` | Cloud instance ID from providerID or IMDS | | `openlit.host.type.source` | Which tier filled `host.type`: `k8s_label`, `imds`, or `dmi` | **Discovery order** (later tiers only fill missing fields; never blocks startup; never emits `"unknown"`): 1. Explicit `OTEL_RESOURCE_ATTRIBUTES` (wins via SDK `WithFromEnv`) 2. Kubernetes Node `GET` (needs `get` on `nodes` + `K8S_NODE_NAME`) — OpenCost-style labels / providerID 3. Parallel AWS / GCP / Azure instance metadata (short timeout) 4. DMI `sys_vendor` hint for provider only Use `cloud.provider` + `host.type` + `cloud.region` as join keys for future UI cost attribution. The collector does **not** compute prices. Kubernetes is detected via `KUBERNETES_SERVICE_HOST`. On GKE, AKS, and EKS the cluster name is read from the instance metadata service (short timeout; failures are ignored). Self-managed clusters should set `k8s.cluster.name` via `OTEL_RESOURCE_ATTRIBUTES` or `K8S_CLUSTER_NAME`. ### Recommended Kubernetes DaemonSet (OTel-native) ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} env: - name: K8S_NODE_NAME valueFrom: fieldRef: fieldPath: spec.nodeName - name: OTEL_RESOURCE_ATTRIBUTES value: "host.name=$(K8S_NODE_NAME),k8s.node.name=$(K8S_NODE_NAME)" - name: OTEL_EXPORTER_OTLP_ENDPOINT value: "http://openlit:4318" # Optional when cloud auto-detect is unavailable (on-prem / self-managed): # - name: K8S_CLUSTER_NAME # value: my-cluster # Or append to OTEL_RESOURCE_ATTRIBUTES: # ,k8s.cluster.name=my-cluster,cloud.provider=aws,host.type=g4dn.xlarge,cloud.region=us-east-1 ``` If you only set `K8S_NODE_NAME` (without packing it into `OTEL_RESOURCE_ATTRIBUTES`), the collector still maps it to `host.name` and `k8s.node.name` automatically. For node label / providerID discovery and for resolving `k8s.pod.name`, grant the DaemonSet ServiceAccount: ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: otel-gpu-collector-node-get rules: - apiGroups: [""] resources: ["nodes"] verbs: ["get"] - apiGroups: [""] resources: ["pods"] verbs: ["list"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: otel-gpu-collector-node-get roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: otel-gpu-collector-node-get subjects: - kind: ServiceAccount name: otel-gpu-collector namespace: monitoring ``` > [!NOTE] > > On EKS, pods without `hostNetwork` may fail IMDSv2 when the node `httpPutResponseHopLimit` is `1`. Prefer K8s node lookup (above), raise the hop limit to `2+`, or run with `hostNetwork: true`. Timeouts are soft-fail and do not stop the collector. ## Common configurations ### Minimal - send to OpenLIT or any OTLP endpoint ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 \ ./opentelemetry-gpu-collector ``` ### Production - with service name, environment, and auth header ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} OTEL_SERVICE_NAME=gpu-worker \ OTEL_RESOURCE_ATTRIBUTES=deployment.environment=production,team=ml \ OTEL_EXPORTER_OTLP_ENDPOINT=https://ingest.example.com:4317 \ OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer\ my-token \ OTEL_METRIC_EXPORT_INTERVAL=30000 \ ./opentelemetry-gpu-collector ``` ### HTTP/protobuf instead of gRPC ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf \ OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 \ ./opentelemetry-gpu-collector ``` ### Disable eBPF CUDA tracing On Linux, eBPF CUDA tracing is **on by default**. It discovers `libcudart` and `libcuda` from the filesystem and from `/proc/*/maps` (no CUDA volume mount required when Docker `--pid=host` / Kubernetes `hostPID: true` is set). Soft-fails without `CAP_BPF` + `CAP_PERFMON` (or root). Containers typically also need `--ulimit memlock=-1:-1` so BPF maps can be created. Set `false` to skip: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} OTEL_GPU_EBPF_ENABLED=false \ OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 \ ./opentelemetry-gpu-collector ``` eBPF activity/occupancy is **NVIDIA/CUDA only**. AMD and Intel use the same host-PID process attribution for DRM fdinfo metrics; they do not need `libcudart`. ## Notes * `OTEL_METRIC_EXPORT_INTERVAL` is in **milliseconds** per the OTel spec. For a 30-second interval, set `30000`. * `deployment.environment` is extracted from `OTEL_RESOURCE_ATTRIBUTES` and attached as a resource attribute. Any key-value pairs in `OTEL_RESOURCE_ATTRIBUTES` are also forwarded to the OTel SDK resource via `resource.WithFromEnv()`. * If `OTEL_EXPORTER_OTLP_ENDPOINT` is not set, the collector starts but no metrics are exported. Check the logs for a warning. * Auto-detected `host.name` / `k8s.*` attributes are logged at startup as `resolved resource identity`. # AMD GPUs Source: https://docs.openlit.io/latest/gpu-collector/gpus/amd Monitor AMD GPU metrics via sysfs/hwmon using the OpenTelemetry GPU Collector The collector monitors AMD GPUs directly from the Linux kernel's sysfs and hwmon interfaces. No ROCm, no user-space libraries, and no additional drivers are needed beyond the standard AMDGPU kernel module. ## Requirements * Linux with the `amdgpu` kernel driver * Kernel 5.x+ (sysfs/hwmon paths are stable from 5.x onwards) ## Collected metrics | Metric | Description | | - | - | | `hw.gpu.utilization` | Compute utilization (0.0–1.0) | | `hw.gpu.memory.utilization` | Fraction of GPU memory used (usage / limit) | | `hw.gpu.memory.controller.utilization` | Memory controller busy fraction (extension) | | `hw.gpu.memory.limit` | Total VRAM (bytes) | | `hw.gpu.memory.usage` | Used VRAM (bytes) | | `hw.gpu.memory.free` | Free VRAM (bytes) | | `hw.temperature` | Die temperature (°C) | | `hw.fan.speed` | Fan speed (RPM) | | `hw.power` | Current power draw (W) | | `hw.power.limit` | Power cap (W) | | `hw.energy` | Cumulative energy (J) | ## Docker ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} docker run -d \ --name otel-gpu-collector \ --device /dev/kfd:/dev/kfd \ --device /dev/dri:/dev/dri \ --pid=host \ -e OTEL_SERVICE_NAME=my-app \ -e OTEL_RESOURCE_ATTRIBUTES='deployment.environment=production' \ -e OTEL_EXPORTER_OTLP_ENDPOINT=http://openlit:4318 \ ghcr.io/openlit/otel-gpu-collector:latest ``` `--pid=host` is required for per-process GPU attribution (cmdline, PID, zombie state). ## Docker Compose ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} services: otel-gpu-collector: image: ghcr.io/openlit/otel-gpu-collector:latest pid: host environment: OTEL_SERVICE_NAME: my-app OTEL_RESOURCE_ATTRIBUTES: deployment.environment=production OTEL_EXPORTER_OTLP_ENDPOINT: http://openlit:4318 devices: - /dev/kfd:/dev/kfd - /dev/dri:/dev/dri restart: always ``` ## Kubernetes (DaemonSet) ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} apiVersion: apps/v1 kind: DaemonSet metadata: name: otel-gpu-collector namespace: monitoring spec: selector: matchLabels: app: otel-gpu-collector template: metadata: labels: app: otel-gpu-collector spec: hostPID: true containers: - name: collector image: ghcr.io/openlit/otel-gpu-collector:latest env: - name: OTEL_SERVICE_NAME value: gpu-collector - name: OTEL_RESOURCE_ATTRIBUTES value: deployment.environment=production - name: OTEL_EXPORTER_OTLP_ENDPOINT value: http://openlit.openlit.svc.cluster.local:4318 securityContext: privileged: false volumeMounts: - name: sys mountPath: /sys readOnly: true - name: dri mountPath: /dev/dri volumes: - name: sys hostPath: path: /sys - name: dri hostPath: path: /dev/dri ``` > [!NOTE] > > Docker `--pid=host` (or Kubernetes `hostPID: true`) and `/dev/dri` are required for per-process GPU attribution via DRM fdinfo. *** > **[Metrics reference](/latest/gpu-collector/metrics#gpu-hardware-telemetry)** > > Full metrics list with types, units, and attributes > **[Configuration](/latest/gpu-collector/configuration)** > > All environment variables and defaults # Intel GPUs Source: https://docs.openlit.io/latest/gpu-collector/gpus/intel Monitor Intel GPU metrics via sysfs/hwmon using the OpenTelemetry GPU Collector The collector monitors Intel GPUs via the Linux kernel's sysfs, hwmon, and DRM interfaces exposed by the i915 and Xe drivers. No Intel GPU tools, no OneAPI, and no user-space libraries are required. > [!NOTE] > > Intel GPU support provides thermal, power, energy, and clock metrics. Utilization and memory metrics are not available via the sysfs/hwmon interface - these would require the [Intel XPU Manager](https://github.com/intel/xpumanager) or similar tooling. ## Requirements * Linux with the `i915` or `xe` kernel driver * Kernel 5.10+ for sysfs metric exposure * Kernel 6.16+ for fan speed (`fan1_input`) ## Collected metrics | Metric | Source | Requirement | | - | - | - | | `hw.temperature` | hwmon `temp1_input` | kernel 5.10+ | | `hw.power` | hwmon `power1_average` | kernel 5.10+ | | `hw.power.limit` | hwmon `power1_max` | kernel 5.10+ | | `hw.energy` | hwmon `energy1_input` | kernel 5.10+ | | `hw.gpu.speed` (`clock_domain=graphics`) | DRM `gt_cur_freq_mhz` | Xe driver | | `hw.fan.speed` | hwmon `fan1_input` | kernel 6.16+ | ## Docker ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} docker run -d \ --name otel-gpu-collector \ --device /dev/dri:/dev/dri \ --pid=host \ -e OTEL_SERVICE_NAME=my-app \ -e OTEL_RESOURCE_ATTRIBUTES='deployment.environment=production' \ -e OTEL_EXPORTER_OTLP_ENDPOINT=http://openlit:4318 \ ghcr.io/openlit/otel-gpu-collector:latest ``` `--pid=host` is required for per-process GPU attribution (cmdline, PID, zombie state). ## Docker Compose ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} services: otel-gpu-collector: image: ghcr.io/openlit/otel-gpu-collector:latest pid: host environment: OTEL_SERVICE_NAME: my-app OTEL_RESOURCE_ATTRIBUTES: deployment.environment=production OTEL_EXPORTER_OTLP_ENDPOINT: http://openlit:4318 devices: - /dev/dri:/dev/dri restart: always ``` ## Kubernetes (DaemonSet) ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} apiVersion: apps/v1 kind: DaemonSet metadata: name: otel-gpu-collector namespace: monitoring spec: selector: matchLabels: app: otel-gpu-collector template: metadata: labels: app: otel-gpu-collector spec: hostPID: true containers: - name: collector image: ghcr.io/openlit/otel-gpu-collector:latest env: - name: OTEL_SERVICE_NAME value: gpu-collector - name: OTEL_RESOURCE_ATTRIBUTES value: deployment.environment=production - name: OTEL_EXPORTER_OTLP_ENDPOINT value: http://openlit.openlit.svc.cluster.local:4318 securityContext: privileged: false volumeMounts: - name: sys mountPath: /sys readOnly: true - name: dri mountPath: /dev/dri volumes: - name: sys hostPath: path: /sys - name: dri hostPath: path: /dev/dri ``` > [!NOTE] > > Docker `--pid=host` (or Kubernetes `hostPID: true`) and `/dev/dri` are required for per-process GPU attribution via DRM fdinfo. *** > **[Metrics reference](/latest/gpu-collector/metrics#gpu-hardware-telemetry)** > > Full metrics list with types, units, and attributes > **[Configuration](/latest/gpu-collector/configuration)** > > All environment variables and defaults # NVIDIA GPUs Source: https://docs.openlit.io/latest/gpu-collector/gpus/nvidia Monitor NVIDIA GPU metrics via NVML using the OpenTelemetry GPU Collector The collector monitors NVIDIA GPUs via [NVML](https://developer.nvidia.com/nvidia-management-library-nvml) using the [go-nvml](https://github.com/NVIDIA/go-nvml) library, which loads `libnvidia-ml.so` at runtime. No CUDA toolkit or DCGM daemon is needed. ## Requirements * Linux with NVIDIA GPU drivers installed * `libnvidia-ml.so` present on the host (installed with the NVIDIA driver) * For Docker: [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html) ## Collected metrics | Metric | Description | | - | - | | `hw.gpu.utilization` | Compute, encoder, and decoder utilization (0.0–1.0) via `hw.gpu.task` | | `hw.gpu.memory.utilization` | Fraction of GPU memory used (usage / limit) | | `hw.gpu.memory.controller.utilization` | Memory controller busy fraction (extension) | | `hw.gpu.memory.limit` | Total VRAM (bytes) | | `hw.gpu.memory.usage` | Used VRAM (bytes) | | `hw.gpu.memory.free` | Free VRAM (bytes) | | `hw.temperature` | Die and memory temperature (°C) via `hw.sensor_location` | | `hw.fan.speed_ratio` | Fan speed as fraction of max (NVML %; rpm not available) | | `hw.power` | Current power draw (W) | | `hw.power.limit` | Power cap (W) | | `hw.energy` | Cumulative energy (J) | | `hw.gpu.speed` | Clock frequency in Hz (`hw.gpu.clock_domain`=`graphics`/`sm`/`memory`) | | `hw.status` | Hardware status (`ok` / `degraded` / `failed`) | | `hw.errors` | ECC correctable/uncorrectable errors and PCIe replay errors | ## Docker ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} docker run -d \ --name otel-gpu-collector \ --gpus all \ --pid=host \ -e OTEL_SERVICE_NAME=my-app \ -e OTEL_RESOURCE_ATTRIBUTES='deployment.environment=production' \ -e OTEL_EXPORTER_OTLP_ENDPOINT=http://openlit:4318 \ ghcr.io/openlit/otel-gpu-collector:latest ``` `--pid=host` is required for per-process GPU attribution (cmdline, PID, zombie state). ## Docker Compose ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} services: otel-gpu-collector: image: ghcr.io/openlit/otel-gpu-collector:latest pid: host environment: OTEL_SERVICE_NAME: my-app OTEL_RESOURCE_ATTRIBUTES: deployment.environment=production OTEL_EXPORTER_OTLP_ENDPOINT: http://openlit:4318 deploy: resources: reservations: devices: - driver: nvidia count: all capabilities: [gpu] restart: always ``` ## Kubernetes (DaemonSet) To monitor GPUs on every node in a cluster, deploy the collector as a DaemonSet: ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} apiVersion: apps/v1 kind: DaemonSet metadata: name: otel-gpu-collector namespace: monitoring spec: selector: matchLabels: app: otel-gpu-collector template: metadata: labels: app: otel-gpu-collector spec: hostPID: true tolerations: - key: nvidia.com/gpu operator: Exists effect: NoSchedule containers: - name: collector image: ghcr.io/openlit/otel-gpu-collector:latest env: - name: OTEL_SERVICE_NAME value: gpu-collector - name: OTEL_RESOURCE_ATTRIBUTES value: deployment.environment=production - name: OTEL_EXPORTER_OTLP_ENDPOINT value: http://openlit.openlit.svc.cluster.local:4318 resources: limits: nvidia.com/gpu: 1 securityContext: privileged: false ``` > [!NOTE] > > The collector does not need privileged mode for NVML device metrics — just access to `libnvidia-ml.so`. Per-process attribution needs Docker `--pid=host` or Kubernetes `hostPID: true`. eBPF tracing / stream-sync occupancy requires `CAP_BPF` + `CAP_PERFMON` (or privileged) and raised memlock (`--ulimit memlock=-1:-1`). *** > **[Metrics reference](/latest/gpu-collector/metrics#gpu-hardware-telemetry)** > > Full metrics list with types, units, and attributes > **[Configuration](/latest/gpu-collector/configuration)** > > All environment variables and defaults # Installation Source: https://docs.openlit.io/latest/gpu-collector/installation Install the OpenTelemetry GPU Collector via Docker, binary, or from source ## Docker (recommended) The easiest way to run the collector. The image is published to GitHub Container Registry and supports `linux/amd64` and `linux/arm64`. ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} docker pull ghcr.io/openlit/otel-gpu-collector:latest ``` For per-process GPU attribution (cmdline, PID, zombie/`process.state`, owner), run with host PID namespace access: Docker `--pid=host`, Compose `pid: host`, or Kubernetes `hostPID: true`. Device-level `hw.gpu.*` metrics work without it. ### Tags | Tag | Description | | - | - | | `latest` | Most recent release | | `1.2.3` | Specific version | | `1.2` | Latest patch of a minor version | *** ## Pre-built binaries Download a binary for your platform from the [GitHub Releases](https://github.com/openlit/openlit/releases) page. Binaries are available for: | Platform | Architecture | | - | - | | Linux | amd64, arm64, armv7 | | macOS | amd64 (Intel), arm64 (Apple Silicon) | | Windows | amd64, arm64 | ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} # Example: Linux amd64 curl -L https://github.com/openlit/openlit/releases/latest/download/opentelemetry-gpu-collector--linux-amd64 \ -o opentelemetry-gpu-collector chmod +x opentelemetry-gpu-collector OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 \ ./opentelemetry-gpu-collector ``` > [!NOTE] > > GPU device and process metrics work on **Linux and Windows** (NVIDIA via NVML; AMD/Intel via sysfs on Linux or DXGI+PDH on Windows). eBPF CUDA tracing and occupancy are **Linux only**. On macOS the binary runs with host and process metrics only. Verify the SHA256 checksum from the `SHA256SUMS.txt` file in the release: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} sha256sum -c SHA256SUMS.txt --ignore-missing ``` *** ## Build from source Requirements: Go 1.21+, CGO enabled (required for NVML on Linux). ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} git clone https://github.com/openlit/openlit.git cd openlit/opentelemetry-gpu-collector make build ./opentelemetry-gpu-collector ``` For eBPF CUDA tracing support, also run: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} make setup-bpf # installs bpftool, generates vmlinux.h make generate # runs bpf2go code generation make build ``` *** ## Kubernetes DaemonSet Run one collector per GPU node. Use the OpenTelemetry-recommended pattern: downward API → `K8S_NODE_NAME` → `OTEL_RESOURCE_ATTRIBUTES` with `host.name` and `k8s.node.name`. On GKE, AKS, and EKS the collector also auto-detects `k8s.cluster.name`, `cloud.provider`, and `host.type` (instance type) from the Kubernetes Node object and/or cloud metadata. ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} apiVersion: v1 kind: ServiceAccount metadata: name: otel-gpu-collector --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: otel-gpu-collector-node-get rules: - apiGroups: [""] resources: ["nodes"] verbs: ["get"] - apiGroups: [""] resources: ["pods"] verbs: ["list"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: otel-gpu-collector-node-get roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: otel-gpu-collector-node-get subjects: - kind: ServiceAccount name: otel-gpu-collector namespace: default # change to your namespace --- apiVersion: apps/v1 kind: DaemonSet metadata: name: otel-gpu-collector spec: selector: matchLabels: app: otel-gpu-collector template: metadata: labels: app: otel-gpu-collector spec: serviceAccountName: otel-gpu-collector hostPID: true containers: - name: otel-gpu-collector image: ghcr.io/openlit/otel-gpu-collector:latest env: - name: K8S_NODE_NAME valueFrom: fieldRef: fieldPath: spec.nodeName - name: OTEL_SERVICE_NAME value: otel-gpu-collector - name: OTEL_RESOURCE_ATTRIBUTES value: "host.name=$(K8S_NODE_NAME),k8s.node.name=$(K8S_NODE_NAME)" - name: OTEL_EXPORTER_OTLP_ENDPOINT value: "http://openlit:4318" # Optional for on-prem / self-managed clusters: # - name: K8S_CLUSTER_NAME # value: my-cluster # eBPF CUDA tracing is on by default on Linux; set false to disable: # - name: OTEL_GPU_EBPF_ENABLED # value: "false" volumeMounts: - name: pod-resources mountPath: /var/lib/kubelet/pod-resources readOnly: true # AMD/Intel DRM: # - name: dri # mountPath: /dev/dri securityContext: capabilities: add: ["SYS_ADMIN"] # or privileged / CAP_BPF+CAP_PERFMON for eBPF volumes: - name: pod-resources hostPath: path: /var/lib/kubelet/pod-resources # - name: dri # hostPath: # path: /dev/dri ``` > [!NOTE] > > `hostPID: true` (same role as Docker `--pid=host`) is required for per-process and per-pod GPU attribution so the collector can see workload PIDs under `/proc`. Without it, device-level `hw.gpu.*` metrics still work. `nodes/get` enables OpenCost-style `host.type` / `cloud.provider` discovery from node labels without relying on IMDS (helpful when EKS hop limit is 1). `pods/list` is what `OPENLIT_K8S_POD_LOOKUP` uses to turn a process's cgroup pod UID into `k8s.pod.name`. The list call is field-selected to this node; the permission is cluster-scoped. See [Configuration](/latest/gpu-collector/configuration) for identity env vars and detection order. *** ## Upgrade ### Docker ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} docker pull ghcr.io/openlit/otel-gpu-collector:latest docker stop otel-gpu-collector docker rm otel-gpu-collector # re-run with same flags ``` ### Binary Download the new binary from the [Releases](https://github.com/openlit/openlit/releases) page, replace the existing file, and restart the process. *** ## Uninstall ### Docker ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} docker stop otel-gpu-collector docker rm otel-gpu-collector docker rmi ghcr.io/openlit/otel-gpu-collector:latest ``` ### Binary ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} rm /usr/local/bin/opentelemetry-gpu-collector ``` *** ## Troubleshooting > **No GPU metrics - collector starts but reports no hw\.gpu.* metrics*\* > > * Confirm the host has a supported GPU: `lspci | grep -E 'VGA|3D|Display'` > * For NVIDIA: verify `libnvidia-ml.so` is present: `ldconfig -p | grep nvidia-ml` > * For Docker: ensure `--gpus all` (NVIDIA) or `--device /dev/dri` (AMD/Intel) is passed > * Check logs: `docker logs otel-gpu-collector` for `"discovered GPU"` entries > **eBPF tracing not working** > > * On Linux it is enabled by default; confirm it is not disabled via `OTEL_GPU_EBPF_ENABLED=false` > * Check kernel version: `uname -r` (requires 5.8+) > * The process needs `CAP_BPF` and `CAP_PERFMON`, or run as root > * For Docker: `--cap-add CAP_BPF --cap-add CAP_PERFMON`, `--pid=host`, and `--ulimit memlock=-1:-1` (BPF maps need locked memory; no CUDA mount needed) > * For Kubernetes: `hostPID: true` plus BPF capabilities (or `SYS_ADMIN`); raise memlock if map create returns EPERM > * If no CUDA process is running yet, the collector rescans `/proc` every 30s > * Verify a workload has loaded CUDA: `grep -E 'libcudart|libcuda.so' /proc/*/maps 2>/dev/null | head` > **Connection refused / no data reaching backend** > > * Verify `OTEL_EXPORTER_OTLP_ENDPOINT` is reachable from the container: `curl http:///health` > * For Docker networking: use the host IP or service name, not `localhost` > * Check if gRPC vs HTTP/protobuf matches the backend: set `OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf` for HTTP backends (port 4318) > **Intel GPU not detected** > > * Verify the i915 or Xe driver is loaded: `lsmod | grep -E 'i915|xe'` > * Check DRM entries exist: `ls /sys/class/drm/` > * Requires Linux kernel 5.10+ for sysfs metric exposure > * Fan speed requires kernel 6.16+ # Metrics Reference Source: https://docs.openlit.io/latest/gpu-collector/metrics Complete list of all metrics exported by the OpenTelemetry GPU Collector All metric names and attributes follow the [OpenTelemetry semantic conventions for hardware](https://opentelemetry.io/docs/specs/semconv/hardware/gpu/) and [system metrics](https://opentelemetry.io/docs/specs/semconv/system/). See the [semconv registry](/latest/gpu-collector/semconv) for the full instrument list. ## GPU Hardware Telemetry Collected for each detected GPU on **Linux and Windows**. Availability depends on vendor, OS, and GPU model. ### Device metrics | Metric | Type | Unit | Description | NVIDIA | AMD | Intel | | - | - | - | - | :-: | :-: | :-: | | `hw.gpu.utilization` | Gauge | `1` | GPU compute/encoder/decoder utilization (0.0–1.0) | Yes | Yes | Yes\* | | `hw.gpu.memory.utilization` | Gauge | `1` | Fraction of GPU memory used (usage / limit) | Yes | Yes | Yes\* | | `hw.gpu.memory.controller.utilization` | Gauge | `1` | Memory controller busy fraction (extension; NVML/AMD) | Yes | Yes | - | | `hw.gpu.memory.limit` | UpDownCounter | `By` | Total GPU memory | Yes | Yes | Yes\* | | `hw.gpu.memory.usage` | UpDownCounter | `By` | Used GPU memory | Yes | Yes | Yes\* | | `hw.gpu.memory.free` | UpDownCounter | `By` | Free GPU memory | Yes | Yes | Yes\* | | `hw.temperature` | Gauge | `Cel` | Die or memory temperature | Yes | Yes | Yes | | `hw.fan.speed` | Gauge | `rpm` | Fan speed | -† | Yes | Yes\* | | `hw.fan.speed_ratio` | Gauge | `1` | Fan speed as fraction of max | Yes | - | - | | `hw.power` | Gauge | `W` | Current power draw | Yes | Yes | Yes | | `hw.power.limit` | Gauge | `W` | Power limit/cap | Yes | Yes | Yes | | `hw.energy` | Counter | `J` | Cumulative energy consumed | Yes | Yes | Yes | | `hw.gpu.speed` | Gauge | `Hz` | Clock frequency (`hw.gpu.clock_domain`) | Yes | Yes | Yes\* | | `hw.gpu.io` | Counter | `By` | Cumulative PCIe I/O bytes | Yes | Yes¶ | Soft‡ | | `hw.gpu.interconnect.io` | Counter | `By` | Cumulative NVLink / XGMI bytes | Yes§ | Yes¶ | - | | `hw.status` | UpDownCounter | `1` | Hardware status (up / throttled states) | Yes | Yes | Yes | | `hw.gpu.allocated` | Gauge | `1` | `1` when process memory or util ≥ threshold | Yes | Yes | Yes | | `hw.gpu.idle` | Gauge | `1` | Idle ratio (`1 - utilization`) when util known | Yes | Yes | Yes\* | | `hw.errors` | Counter | `{error}` | ECC, PCIe, XID, and RAS error counts | Yes | Yes (RAS + PCIe replay¶) | - | \* Intel support depends on driver (i915/Xe) and kernel version. † NVIDIA NVML reports fan speed as a percentage — use `hw.fan.speed_ratio`; `hw.fan.speed` (rpm) is omitted for NVIDIA. § Requires `OTEL_GPU_INTERCONNECT_ENABLED` (default true). ¶ AMD XGMI / PCIe when the driver exposes counters. ‡ Soft / best-effort when the kernel exposes the counters. \* Intel device util/VRAM/power/throttle/media use Level Zero Sysman when available (Linux/Windows); otherwise DRM/hwmon/PDH subset. Encoder/decoder util comes from media engine groups or Windows PDH VideoEncode/VideoDecode. † NVIDIA NVML exposes fan speed as a percentage, not RPM, so `hw.fan.speed` is not emitted for NVIDIA — use `hw.fan.speed_ratio` instead. ‡ Soft-omitted when the vendor library/driver path does not expose the counter. Core util/memory/power still collect without those libraries. Windows AMD has no PCIe/XGMI throughput API (ADL). § NVLink on Linux and Windows NVIDIA when the GPU exposes enabled NVLink links (rates after the second sample). ¶ AMD Linux via `libamd_smi` (`amdsmi_get_gpu_pci_throughput`, `amdsmi_get_link_metrics`, `amdsmi_get_violation_status`) when present; soft-omitted on VMs/consumer cards without those counters. AMD media util (`MmActivity`) is exported as `hw.gpu.task=encoder` when the driver only exposes a combined VCN counter (decoder stays unset). ### Attributes All GPU metrics carry these base attributes: | Attribute | Description | Example | | - | - | - | | `hw.id` | Unique device identifier (required by spec) | `GPU-a1b2c3d4-5678-...` | | `hw.name` | Product name | `NVIDIA A100-SXM4-80GB` | | `hw.vendor` | Vendor name | `nvidia`, `amd`, `intel` | | `gpu.index` | Zero-based device index | `0`, `1` | | `gpu.pci_address` | PCI bus address | `0000:01:00.0` | Additional per-metric attributes: | Metric | Attribute | Values | | - | - | - | | `hw.gpu.utilization` | `hw.gpu.task` | `general`, `encoder`, `decoder` | | `hw.temperature` | `hw.sensor_location` | `die`, `memory` | | `hw.gpu.speed` | `hw.gpu.clock_domain` | `graphics`, `memory`, `sm` | | `hw.gpu.io` | `network.io.direction` | `receive`, `transmit` | | `hw.gpu.interconnect.io` | `network.io.direction` | `receive`, `transmit` | | `hw.gpu.interconnect.io` | `hw.gpu.interconnect.type` | `nvlink`, `xgmi`, `other` | | `hw.status` | status / throttle attrs | up / throttled + reason labels | | `hw.errors` | `error.type` | `corrected`, `uncorrected`, `pcie_replay`, `xid`, `ras_corrected`, `ras_uncorrected` | | `hw.errors` | `hw.type` | `gpu` | | MIG devices | `gpu.mig.enabled`, `gpu.mig.device_id`, `gpu.parent.uuid`, `gpu.mig.instance_id`, `gpu.mig.profile` | Linux NVIDIA only (MIG is not available on Windows) | | Partitions (RDC) | `hw.parent`, `hw.gpu.partition` | AMD compute partitions when available | *** ## System Metrics Collected on all platforms (Linux, macOS, Windows) via [gopsutil](https://github.com/shirou/gopsutil). Follows the [OTel semantic conventions for system metrics](https://opentelemetry.io/docs/specs/semconv/system/system-metrics/). | Metric | Type | Unit | Description | Attributes | | - | - | - | - | - | | `system.cpu.utilization` | Gauge | `1` | CPU utilization per logical core (0.0–1.0) | `cpu.logical_number` | | `system.cpu.logical.count` | UpDownCounter | `{cpu}` | Number of logical CPU cores | | | `system.memory.usage` | UpDownCounter | `By` | Memory bytes by state | `system.memory.state`= | | `system.memory.utilization` | Gauge | `1` | Memory utilization (0.0–1.0) | | | `system.disk.io` | Counter | `By` | Disk I/O bytes | `system.device`, `disk.io.direction`= | | `system.disk.operations` | Counter | `{operation}` | Disk I/O operations | `system.device`, `disk.io.direction`= | | `system.filesystem.usage` | UpDownCounter | `By` | Filesystem space by state | `system.device`, `system.filesystem.mountpoint`, `system.filesystem.type`, `system.filesystem.state`= | | `system.filesystem.utilization` | Gauge | `1` | Filesystem utilization (0.0–1.0) | `system.device`, `system.filesystem.mountpoint`, `system.filesystem.type` | | `system.network.io` | Counter | `By` | Network I/O bytes | `network.interface.name`, `network.io.direction`= | | `system.network.errors` | Counter | `{error}` | Network errors | `network.interface.name`, `network.io.direction`= | > [!NOTE] > > `system.memory.state` values `cached` and `buffers` are only reported on Linux. Loopback interfaces (`lo`, `lo0`) are excluded from network metrics. *** ## Process Metrics Self-monitoring of the collector process. Follows the [OTel semantic conventions for process metrics](https://opentelemetry.io/docs/specs/semconv/system/process-metrics/). | Metric | Type | Unit | Description | Attributes | | - | - | - | - | - | | `process.cpu.time` | Counter | `s` | Cumulative CPU time | `cpu.mode`= | | `process.cpu.utilization` | Gauge | `1` | CPU utilization (0.0–1.0) | | | `process.memory.usage` | UpDownCounter | `By` | Resident memory (RSS) | | | `process.memory.virtual` | UpDownCounter | `By` | Virtual memory size | | | `process.thread.count` | UpDownCounter | `{thread}` | OS thread count | | | `process.unix.file_descriptor.count` | UpDownCounter | `{file_descriptor}` | Open file descriptors (Linux/macOS) | | | `process.runtime.go.goroutines` | Gauge | `{goroutine}` | Go goroutine count | | | `process.runtime.go.mem.heap_alloc` | Gauge | `By` | Go heap memory allocated | | *** ## Per-process / per-pod GPU attribution Collected on each scrape from NVML (NVIDIA) or DRM fdinfo (AMD/Intel). Requires host PID visibility — Docker `--pid=host` / Compose `pid: host`, or Kubernetes `hostPID: true` — so `/proc` shows workload PIDs. Without it, device-level `hw.gpu.*` metrics still work, but per-process cmdline, state (including zombies), and GPU process metrics will be missing or incomplete. These attributes support views like zombie-process triage: filter `process.state == "zombie"` and show `process.command_line`, `process.pid`, `process.owner`, and `process.uptime` (age). | Metric | Type | Unit | Description | NVIDIA | AMD | Intel | | - | - | - | - | :-: | :-: | :-: | | `process.gpu.memory.usage` | UpDownCounter | `By` | GPU memory used by a process on a device | Yes | Yes | Yes | | `process.gpu.memory.utilization` | Gauge | `1` | Process memory / device memory limit (0–1); omitted when limit unknown | Yes | Yes | Yes | | `process.gpu.utilization` | Gauge | `1` | Per-process GPU utilization (0.0–1.0) | Yes | Yes | Yes | | `process.uptime` | Gauge | `s` | Process uptime for GPU-attributed PIDs | Yes | Yes | Yes | ### Attributes | Attribute | Description | | - | - | | `process.pid` | Host process ID | | `process.executable.name` | Short name from `/proc//comm` (or Windows image basename) | | `process.command_line` | Truncated command line (see `OTEL_GPU_PROCESS_CMDLINE`) | | `process.state` | `running`, `sleeping`, `zombie`, `stopped`, `dead`, `unknown` | | `process.owner` / `process.owner.userid` | Username and UID/SID | | `process.workload.kind` | `llm_inference`, `llm_training`, or `other` | | `process.workload.framework` | `vllm`, `ollama`, `llama.cpp`, `sglang`, `tgi`, `triton`, `ray`, `pytorch`, `unknown` | | `hw.id`, `hw.name`, `hw.vendor`, `gpu.index`, `gpu.pci_address` | Same device attrs as `hw.gpu.*` | | `hw.gpu.task` | On utilization only: `general`, `encoder`, `decoder` | | `k8s.pod.uid` | From cgroup path when present | | `k8s.pod.name`, `k8s.namespace.name`, `k8s.container.name` | The process's own pod (cgroup container id / pod UID joined with the pod API). PodResources is a fallback: per device for host processes, and for a containerized process only when an allocation is confirmed as that pod | | `container.id` | Runtime container ID when resolvable | > [!NOTE] > > `process.start_time` is not exported as a metric attribute (cardinality). Use `process.uptime` for lifetime. On MIG devices, NVML often provides **memory only** (no per-process SM util). Process util may not sum to device util (time-slicing / sampling windows differ). Use `process.state == zombie` to find defunct processes still holding GPU contexts. > > PodResources carries no pod UID. A containerized process is confirmed by its own `/etc/hostname` (the kubelet writes the pod name; with `setHostnameAsFQDN`, the owner's namespace must appear in it) matched against allocations on the node, not against whichever GPU is scraped first. `hostNetwork` uses the node hostname. `spec.hostname` uses the override. Those stay UID-only unless the hostname still names the owner. Two pods with the same name in different namespaces stay UID-only unless `OPENLIT_K8S_POD_LOOKUP` can list pods. A process on GPUs allocated to other pods keeps its own pod name. `k8s.pod.uid` is the cgroup value. *** ## eBPF CUDA Metrics Enable with `OTEL_GPU_EBPF_ENABLED` (on by default on Linux for `serving`+ / `all` modes; off in `light`). Soft-fails without caps. Discovers `libcudart.so*` (CUDA runtime API) and `libcuda.so*` (CUDA driver API) from common install paths **and** from `/proc/*/maps` (with Docker `--pid=host` / Kubernetes `hostPID: true`), so fleet DaemonSets do not need a CUDA toolkit mount. Driver-API probes extend coverage to frameworks that never load `libcudart` (llama.cpp, Ollama). Attaches uprobes/uretprobes for launches (`cudaLaunchKernel`, `cudaLaunchKernelExC`, cooperative, `cuLaunchKernel`, `cuLaunchKernelEx`), graph replays (`cudaGraphLaunch`, `cuGraphLaunch`), alloc/free, memcpy, stream/device/event sync, and `cudaSetDevice`. PIDs that map `libcudart` skip driver launch/graph events so runtime wrappers are not double-counted. AMD/Intel do not use this path — their per-process metrics come from DRM fdinfo. ### Activity metrics | Metric | Type | Unit | Description | Attributes | | - | - | - | - | - | | `gpu.kernel.launch.calls` | Counter | `{call}` | CUDA kernel launch count (runtime and driver APIs) | `process.pid`, `process.executable.name`, `cuda.kernel.name`, `hw.id` / `gpu.index` when known | | `gpu.kernel.grid.size` | Histogram | `{thread}` | Total threads in grid per launch | same | | `gpu.kernel.block.size` | Histogram | `{thread}` | Threads per block per launch | same | | `gpu.kernel.shared_memory` | Histogram | `By` | Dynamic shared memory per launch (`cudaLaunchKernel` sharedMem) | same | | `gpu.kernel.duration` | Histogram | `s` | Model estimate of launch→sync duration (shared store with occupancy; cardinality-capped names) | same + optional `cuda.launch.kind=graph` | | `gpu.graph.launch.calls` | Counter | `{call}` | CUDA graph **replay** invocations (`cudaGraphLaunch` / `cuGraphLaunch`). Counts replays, **not** the kernels executed inside each replay — that number is not observable at this API-tracing layer | `process.pid`, `process.executable.name`, `hw.id` / `gpu.index` when known | | `gpu.memory.allocations` | Counter | `By` | Bytes allocated via cudaMalloc | `process.pid`, `process.executable.name`, `hw.id` / `gpu.index` when known | | `gpu.memory.copies` | Histogram | `By` | Bytes per cudaMemcpyAsync | `process.pid`, `cuda.memcpy.kind`, `hw.id` / `gpu.index` when known | `hw.id` is set after `cudaSetDevice` for that thread, or immediately on single-GPU hosts. On multi-GPU hosts before `cudaSetDevice`, join via `process.pid` as before. **Linux/NVIDIA (CUDA) only** — same eBPF path for Kubernetes and non-Kubernetes when host PID is visible. AMD/Intel use device/process metrics via DRM fdinfo, not these kernel series. ### Stream-sync occupancy model These are **CPU-side model estimates**, not hardware SM occupancy (warps resident / max warps). Spans run from kernel launch to sync API return; thread-seconds are clamped to NVML CUDA core count and normalized so multi-process totals do not exceed device capacity. | Metric | Type | Unit | Description | | - | - | - | - | | `process.gpu.core.usage` | Gauge | `{cores}` | Normalized avg CUDA cores from launch→sync thread-seconds | | `process.gpu.estimated.sm_active` | Gauge | `1` | Fraction of interval with any launch→sync span (`gpu.measurement.source=ebpf_model`) | | `hw.gpu.core.limit` | Gauge | `{cores}` | NVML CUDA core count | | `hw.gpu.estimated.sm_active` | Gauge | `1` | Device-wide union of launch→sync spans | **Prefer** NVML `process.gpu.utilization` for sampled SM%. These estimates are **not** DCGM `hw.gpu.sm.utilization` / `hw.gpu.sm.occupancy`. Use them when NVML process util is unavailable or when correlating launch→sync activity with device util. **Known limits:** no GPU completion signal (late sync inflates usage); `cudaGraphLaunch` / `cuGraphLaunch` is one composite span and `gpu.graph.launch.calls` counts **replays, not kernels** (no per-node kernels inside a graph); `cudaStreamWaitEvent` wait edges are not modeled; sync `cudaMemcpy` and default-stream sync are treated as device-wide (legacy default-stream semantics). *** ## DCGM profiling (optional) Enable with `OTEL_GPU_DCGM_ENABLED=true`. Soft-fails when `libdcgm` is missing. **DCGM is optional** — continuous clocks (`graphics` / `sm` / `memory`), util, and power work from NVML alone. Metrics carry `gpu.measurement.source=dcgm`. Set `OTEL_GPU_DCGM_PREFER=true` so DCGM owns overlapping IO / power / util / graphics clock / idle (NVML suppresses those series — single producer). Memory clock stays on NVML. | Metric | Type | Unit | Attributes | | - | - | - | - | | `hw.gpu.engine.utilization` | Gauge | `1` | `hw.gpu.engine` | | `hw.gpu.sm.utilization` | Gauge | `1` | DCP SM active | | `hw.gpu.sm.occupancy` | Gauge | `1` | DCP SM occupancy | | `hw.gpu.pipe.utilization` | Gauge | `1` | `hw.gpu.pipe`=`tensor\|fp16\|fp32\|fp64` | | `hw.gpu.memory.bandwidth.utilization` | Gauge | `1` | | | `hw.gpu.io` / `hw.gpu.interconnect.io` | Counter | `By` | Prefer only; rate-integrated PCIe / NVLink | | `hw.gpu.speed` | Gauge | `Hz` | Prefer only; `hw.gpu.clock_domain=graphics` (field 100) | | `hw.gpu.idle` | Gauge | `1` | Prefer only; derived from Prefer util | | `hw.power` / `hw.gpu.utilization` / `hw.gpu.memory.controller.utilization` | Gauge | Prefer only | Fields 155 / 203 / 204 (204 → controller util) | | `openlit.collector.gpu.dcgm.sample_valid` | UpDownCounter | `1` | 0 on blank sample | Control plane: `POST /v1/dcgm/pause` and `POST /v1/dcgm/resume` (loopback `OTEL_GPU_CONTROL_ADDR`). *** ## AMD RDC profiling (optional) Enable with `OTEL_GPU_RDC_ENABLED=true`. Soft-fails when `librdc` is missing. Metrics carry `gpu.measurement.source=rdc`. Partition samples may set `hw.parent`. | Metric | Type | Unit | Notes | | - | - | - | - | | `hw.gpu.sm.utilization` | Gauge | `1` | SM/CU active | | `hw.gpu.sm.occupancy` | Gauge | `1` | CU / SM occupancy | | `hw.gpu.pipe.utilization` | Gauge | `1` | `fp16` / `fp32` / `fp64` from EVAL\_FLOPS percent | | `hw.gpu.simd.utilization` | Gauge | `1` | SIMD utilization | *** ## Kineto on-demand profiling (optional) Enable with `OTEL_GPU_KINETO_ENABLED=true`. Does **not** emit continuous metrics. Use the control API: * `POST /v1/profile/gpu` — match GPU PIDs and write on-demand libkineto config * Requires `OTEL_GPU_CONTROL_ADDR` (loopback by default; set `OTEL_GPU_CONTROL_ALLOW_REMOTE=true` + token for multi-node) Self-metric: `openlit.collector.profile.requests`. Multi-node fan-out (no always-on collector cost): ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} go run ./cmd/profile-fanout --hosts node1,node2 --port 1919 --token "$TOKEN" --duration-ms 500 # or SLURM: go run ./cmd/profile-fanout --job-id 12345 --port 1919 --token "$TOKEN" ``` *** ## Intel PT on-demand (optional) Enable with `OTEL_HOST_INTEL_PT_ENABLED=true` and a control address. Does **not** run continuously. * `POST /v1/profile/cpu/pt` with `{"duration_ms":500}` * Requires Linux `intel_pt` PMU and `perf` in `PATH` * Caps: duration, CPU count, AUX buffer pages (see configuration) Fan-out: `profile-fanout --profile cpu-pt ...` *** ## CPU PMU / uncore memory IO (optional) Enable with `OTEL_HOST_PMU_ENABLED=true`. | Metric | Notes | | - | - | | `hw.cpu.instructions` / `hw.cpu.cycles` / cache / branch / TLB | Core PMU | | `hw.cpu.memory.io` | When `memory_bandwidth` or `uncore` is in `OTEL_HOST_PMU_EVENTS`; IMC CAS×64 bytes | *** ## TPU scrape (optional) Enable with `OTEL_TPU_ENABLED=true`. Scrapes a Prometheus-format TPU endpoint (`OTEL_TPU_ENDPOINT`, default `http://127.0.0.1:2112/metrics`) and re-exports allowlisted series. Soft-fails when the endpoint is unreachable. *** ## NIC / RDMA (optional) | Flag | Default | Metrics surface | | - | - | - | | `OTEL_HOST_NIC_ENABLED` | `false` | Per-NIC hardware counters beyond basic `system.network.*` | | `OTEL_HOST_RDMA_ENABLED` | `false` | RDMA device counters (`OTEL_HOST_RDMA_COUNTERS` allowlist) | Soft-omitted when sysfs/counters are unavailable. *** ## Capability matrix | Feature | NVIDIA | AMD | Intel | | - | :-: | :-: | :-: | | Device `hw.gpu.*` / spec `hw.*` | Yes | Yes | Partial | | `process.gpu.memory.usage` / `memory.utilization` / `utilization` | NVML | DRM fdinfo | DRM fdinfo | | Pod UID on process metrics | cgroup | cgroup | cgroup | | eBPF activity + occupancy | Yes (default on Linux) | — | — | | DCGM profiling | Optional | — | — | | RDC profiling | — | Optional (soft-skip without librdc) | — | # Collector modes Source: https://docs.openlit.io/latest/gpu-collector/modes Use-case presets for the OpenTelemetry GPU Collector (all, light, serving, training, deep) `OTEL_GPU_COLLECTOR_MODE` applies **defaults** for optional feature flags. Any explicitly set `OTEL_GPU_*` / `OTEL_HOST_*` / related env var always wins over the preset. **Default:** `all` (when unset or empty). Invalid values warn at startup and fall back to `all`. ## Mode hierarchy `light` → `serving` → `training` → `deep` → `all` | Mode | Intent | | - | - | | `light` | Basic accelerator + host health | | `serving` | Inference hosting (kernels, process share, DCGM SM/pipe) | | `training` | Multi-GPU / fabric / CPU-memory bandwidth | | `deep` | Same continuous as `training`, plus on-demand profiling tools | | `all` | Superset of `deep` + platform extras (RDC, TPU, KVM, interrupts) | **Naming tip:** `all` means everything enabled. `deep` means deep-dive profiling (Kineto / Intel PT / high-res), not the kitchen sink. ## Feature matrix `Y` = mode turns the feature on when the env is **unset**. Soft-fail at runtime if libraries or hardware are missing. | Feature | light | serving | training | deep | all | | - | - | - | - | - | - | | Core NVML/amdsmi scrape + host `system.*` + interconnect | Y | Y | Y | Y | Y | | eBPF (Linux) + process cmdline / workload classify | — | Y | Y | Y | Y | | DCGM enabled + Prefer | — | Y | Y | Y | Y | | RDC (AMD) | — | — | — | — | Y | | NIC + RDMA | — | — | Y | Y | Y | | PMU + `instructions,cycles,memory_bandwidth` | — | — | Y | Y | Y | | TPU scrape | — | — | — | — | Y | | KVM + interrupts | — | — | — | — | Y | | Control API (default `127.0.0.1:1919` if unset) | — | — | — | Y | Y | | Kineto listener | — | — | — | Y | Y | | Intel PT armed | — | — | — | Y | Y | | CPU high-res ring | — | — | — | Y | Y | ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} # Full capability (default) export OTEL_GPU_COLLECTOR_MODE=all # Inference hosts export OTEL_GPU_COLLECTOR_MODE=serving # Smaller footprint (pre-mode optional-off behavior) export OTEL_GPU_COLLECTOR_MODE=light # Override a single flag on top of a mode export OTEL_GPU_COLLECTOR_MODE=serving export OTEL_HOST_NIC_ENABLED=true ``` ## Cost model | Kind | When it costs | | - | - | | Continuous scrapes | Every collection interval (NVML, eBPF, DCGM, NIC, PMU, …) | | Armed on-demand | Idle until RPC — Kineto unix socket, control HTTP, Intel PT, high-res ring | | Active profile | CUPTI / `perf` only while a `POST /v1/profile/*` runs (DCGM pauses during Kineto duration capture) | ## Production readiness * **Linux (amd64/arm64)** is the production target for eBPF, DCGM Prefer, NIC/PMU, Kineto, Intel PT, and deep/`all` extras. * **Environment soft-skip:** missing libraries (`libdcgm`, `librdc`), capabilities (`CAP_BPF` / `CAP_PERFMON`), non-Linux platforms, and absent hardware are logged as `feature unavailable` and skipped — the process stays up. * **Collector faults are fatal:** OpenTelemetry instrument-registration errors, control-plane misconfiguration (bad bind addr / remote without token), and a broken embedded BPF object stop startup with `feature fault`. These cannot be silenced. * Startup logs the mode **feature summary** (requested), **effective feature availability** (what started), plus `unavailable` and `faults` lists. * Prefer DCGM only when `libdcgm` is available; otherwise Prefer is inactive and NVML keeps overlapping series. * For inference fleets that want a smaller continuous footprint, use `serving` (or `light` for scrape-only). Use `training`/`deep` when host caps and libraries match. ## Smaller continuous footprint Previously most optional features defaulted **off**. Default `all` now enables more continuous scrapes and arms on-demand listeners. For a smaller footprint: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} export OTEL_GPU_COLLECTOR_MODE=light # or serving / training, or pin individual OTEL_GPU_* / OTEL_HOST_* flags ``` ## Hosting checklist: collector vs Kineto vs SDK | Need | Continuous collector | On-demand Kineto (`deep` / `all`) | Needs SDK / serving instrumentation | | - | - | - | - | | ECC, temps, power, clocks, PCIe | Yes | — | — | | NVLink / RDMA | Yes (mode-dependent) | — | — | | Multi-workload VRAM / util share | `process.gpu.*` | Per-PID artifacts | Request/tenant inside one process | | Kernel launches / splits (non-graph) | eBPF + `gpu.kernel.duration` | Timeline deep-dive | — | | CUDA graph breaks | Composite `cudaGraphLaunch`/`cuGraphLaunch` span + `gpu.graph.launch.calls` (replays, not per-node kernels) | Strong timeline | Optional framework break reasons | | Input / tensor **shapes** | No | **Yes** (`record_shapes`) | Continuous request-level shapes | | FLOPs / stacks / modules / memory snapshot | No | **Yes** (profile flags) | Continuous budgets | | Dynamic batching queue wait vs exec | No | Weak / incidental | **Yes** (vLLM / Triton / SGLang) | | Padding vs varlen / seqlen | No | Shapes only | **Yes** | | `torch.compile` recompiles | Indirect util correlation | Only if profiling during compile | **Yes** | Join keys today: `process.pid`, `hw.id`, time, `process.workload.*`. W3C `trace_id` on GPU series is future SDK correlation work. See [Configuration](/latest/gpu-collector/configuration) and [Metrics](/latest/gpu-collector/metrics). # GPU Monitoring for LLM Inference Source: https://docs.openlit.io/latest/gpu-collector/overview OpenTelemetry-native GPU monitoring for LLM inference on NVIDIA, AMD, and Intel GPUs — utilization, memory, power, and temperature. The **OpenTelemetry GPU Collector** is a lightweight, single-binary metrics collector written in Go. It exports GPU hardware telemetry, host system metrics, and process metrics via OpenTelemetry (OTLP) - with no Python dependencies, no DCGM daemon, and no vendor-specific agents. It is fully configured via standard OpenTelemetry environment variables and follows the [OTel semantic conventions for hardware metrics](https://opentelemetry.io/docs/specs/semconv/hardware/gpu/). Use [collector modes](/latest/gpu-collector/modes) (`OTEL_GPU_COLLECTOR_MODE`, default `all`) to turn optional features on by use case. Resource identity models this process as a **GPU metrics agent** (host + accelerator telemetry), not an application service: `telemetry.sdk.*`, `telemetry.distro.name=opentelemetry-gpu-collector`, `telemetry.distro.version`, plus host/k8s/cloud attributes. Set `OTEL_SERVICE_NAME` only if your backend requires `service.name`. ## Goals * **OpenTelemetry-native** - uses standard `OTEL_*` env vars, exports via OTLP gRPC or HTTP to any OTel-compatible backend * **Cross-vendor GPU support** - NVIDIA (NVML), AMD, and Intel on Linux and Windows from a single binary * **OTel semantic conventions** - `hw.gpu.*` metric names, `hw.id` / `hw.name` / `hw.vendor` attributes per spec * **Zero dependencies** - no DCGM, no Python, no CUDA toolkit needed at runtime for hardware metrics * **Resilient** - continues exporting host metrics even when no GPUs are present; retries GPU discovery every 30s ## What it collects > **GPU Hardware Telemetry** > > Utilization, memory, temperature, power, energy, clocks, PCIe/interconnect throughput, throttle, ECC/XID/RAS — NVIDIA, AMD, and Intel on Linux and Windows > **Process + workload attribution** > > Per-process VRAM/util plus cmdline, OS state (zombie), owner, uptime, and LLM framework labels (`vllm`, `ollama`, …). Kubernetes pod name/namespace via PodResources > **eBPF CUDA Tracing** > > Kernel launch counts, grid/block sizes, graph replays, memory allocations, and memory copies via uprobes on `libcudart.so` (runtime) and `libcuda.so` (driver) — covers llama.cpp/Ollama as well as PyTorch (on by default on Linux; discovers libs from `/proc` with host PID — no CUDA mount) ## GPU vendor support | Vendor | Linux | Windows | | - | - | - | | **NVIDIA** | NVML (+ MIG, NVLink, XID events) | NVML (`nvml.dll`, NVLink) + PDH process attribution (MIG not available on Windows) | | **AMD** | sysfs/hwmon/DRM + `libamd_smi` (PCIe/XGMI/throttle/RAS when present) | DXGI + PDH (incl. encode/decode) + ADL (util/temp/clocks/fan) | | **Intel** | DRM/hwmon + Level Zero Sysman (util/VRAM/power/media) | DXGI + PDH (incl. encode/decode) + Level Zero Sysman | ## Platform support | Feature | Linux | macOS | Windows | | - | :-: | :-: | :-: | | System metrics (CPU, memory, disk, network) | Yes | Yes | Yes | | Process metrics (CPU, memory, threads, FDs) | Yes | Yes | Yes | | GPU device + process metrics | Yes | - | Yes | | eBPF CUDA tracing / occupancy | Yes | - | - | ## How it works ``` Host Metrics (all platforms via gopsutil) +-- CPU utilization, memory, disk I/O, filesystem, network +-- Process: self CPU, memory, threads, FDs, Go runtime GPU Metrics (Linux + Windows) +-- Linux: PCI scan → NVML / sysfs / DRM (+ eBPF CUDA, default on) +-- Windows: NVML.dll / DXGI + PDH (no eBPF) Export +-- OTel SDK --> OTLP gRPC/HTTP --> your OTel collector / backend ``` **1. Discovers GPUs** On Linux, the collector scans `/sys/bus/pci/devices/` for GPU class codes. On Windows, it loads `nvml.dll` when present and enumerates adapters via DXGI. **2. Initialises vendor backend** Each detected GPU is handed to its vendor-specific backend. NVIDIA uses NVML. Linux AMD/Intel read sysfs/hwmon; Windows AMD/Intel use DXGI memory totals and PDH for utilization and per-process attribution. **3. Registers OTel instruments** Observable gauge and counter instruments are registered with the OTel SDK meter. On each collection tick, the SDK calls back into the collector to read fresh values from each GPU. **4. Exports via OTLP** Metrics are exported via OTLP to any compatible backend - OpenLIT, Grafana, Datadog, New Relic, or a standard OTel Collector. *** > **[Quickstart](/latest/gpu-collector/quickstart)** > > Get the collector running in under 5 minutes with Docker > **[Configuration](/latest/gpu-collector/configuration)** > > Full reference for all environment variables # Quickstart Source: https://docs.openlit.io/latest/gpu-collector/quickstart Get the OpenTelemetry GPU Collector running in under 5 minutes In this guide you'll pull the collector Docker image, point it at your OTel backend, and start seeing GPU and host metrics within minutes. ## Prerequisites * **Linux host** with NVIDIA, AMD, or Intel GPU (for GPU metrics) * **Docker** installed * An **OpenTelemetry-compatible backend** (OpenLIT, Grafana, Datadog, or any OTLP endpoint) > **Don't have an OTel backend? Start OpenLIT locally** > > ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} > docker run -d \ > --name openlit \ > -p 3000:3000 \ > -p 4318:4318 \ > ghcr.io/openlit/openlit:latest > ``` > > Then use `http://localhost:4318` as your `OTEL_EXPORTER_OTLP_ENDPOINT`. ### 1. Pull the collector image ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} docker pull ghcr.io/openlit/otel-gpu-collector:latest ``` ### 2. Run the collector **NVIDIA GPU** ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} docker run -d \ --name otel-gpu-collector \ --gpus all \ --pid=host \ -e OTEL_SERVICE_NAME=my-app \ -e OTEL_RESOURCE_ATTRIBUTES='deployment.environment=production' \ -e OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 \ ghcr.io/openlit/otel-gpu-collector:latest ``` Requires the [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html) on the host. `--pid=host` is required for per-process GPU attribution (cmdline, PID, zombie state). **AMD GPU** ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} docker run -d \ --name otel-gpu-collector \ --device /dev/kfd:/dev/kfd \ --device /dev/dri:/dev/dri \ --pid=host \ -e OTEL_SERVICE_NAME=my-app \ -e OTEL_RESOURCE_ATTRIBUTES='deployment.environment=production' \ -e OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 \ ghcr.io/openlit/otel-gpu-collector:latest ``` `--pid=host` is required for per-process GPU attribution (cmdline, PID, zombie state). **Intel GPU** ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} docker run -d \ --name otel-gpu-collector \ --device /dev/dri:/dev/dri \ --pid=host \ -e OTEL_SERVICE_NAME=my-app \ -e OTEL_RESOURCE_ATTRIBUTES='deployment.environment=production' \ -e OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 \ ghcr.io/openlit/otel-gpu-collector:latest ``` Requires Linux kernel 5.10+ with the i915 or Xe driver. `--pid=host` is required for per-process GPU attribution. **Host metrics only** ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} docker run -d \ --name otel-gpu-collector \ -e OTEL_SERVICE_NAME=my-app \ -e OTEL_RESOURCE_ATTRIBUTES='deployment.environment=production' \ -e OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 \ ghcr.io/openlit/otel-gpu-collector:latest ``` The collector will export host and process metrics even without GPU access. ### 3. Verify it's running ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} docker logs otel-gpu-collector ``` You should see output like: ``` time=2024-01-01T00:00:00Z level=INFO msg="starting opentelemetry-gpu-collector" time=2024-01-01T00:00:00Z level=INFO msg="discovered GPU" address=0000:01:00.0 vendor=nvidia time=2024-01-01T00:00:00Z level=INFO msg="system metrics collector initialized" time=2024-01-01T00:00:00Z level=INFO msg="process metrics collector initialized" time=2024-01-01T00:00:00Z level=INFO msg="collector running" ``` ### 4. View metrics in your backend Open your OTel backend and look for metrics in the `hw.gpu.*`, `system.*`, and `process.*` namespaces. If using OpenLIT, navigate to `http://localhost:3000` and go to the **Metrics** section. ## Docker Compose Add the collector as a service alongside your existing stack: ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} services: otel-gpu-collector: image: ghcr.io/openlit/otel-gpu-collector:latest pid: host environment: OTEL_SERVICE_NAME: my-app OTEL_RESOURCE_ATTRIBUTES: deployment.environment=production OTEL_EXPORTER_OTLP_ENDPOINT: http://openlit:4318 deploy: resources: reservations: devices: - driver: nvidia count: all capabilities: [gpu] depends_on: - openlit restart: always ``` `pid: host` (Docker `--pid=host`) is required so the collector can see host workload PIDs under `/proc` for per-process GPU metrics, cmdline, and zombie detection. *** > **[Configuration](/latest/gpu-collector/configuration)** > > Full reference for all environment variables and defaults > **[Metrics reference](/latest/gpu-collector/metrics)** > > Complete list of all metrics, types, units, and attributes # Collector Roadmap Source: https://docs.openlit.io/latest/gpu-collector/roadmap Deep plans for upcoming OpenLIT GPU Collector work (collector-only, no app SDK) Implemented optional features are documented under [Metrics](/latest/gpu-collector/metrics), [Configuration](/latest/gpu-collector/configuration), and [Semantic conventions](/latest/gpu-collector/semconv). Plans below are **collector-side only** — no application SDK, no framework instrumentation dependency. Correlation stays on `hw.id`, `process.pid`, time, cgroup/pod attrs, and control-plane artifacts. ## Recently added | Item | How to enable | Notes | | - | - | - | | Collector modes | `OTEL_GPU_COLLECTOR_MODE` | See [Modes](/latest/gpu-collector/modes) | | Fail loud on faults | always on | Instrument / config faults stop startup; missing libs/caps/OS soft-skip | | Prefer blank fallback | `OTEL_GPU_DCGM_PREFER` | NVML fills overlap series until Prefer sample is healthy | | `gpu.kernel.duration` | eBPF (`serving`+) | Launch→sync histogram | | Uncore / IMC memory IO | PMU + `memory_bandwidth` | `hw.cpu.memory.io` | | On-demand Intel PT | control API | Caps on duration / CPUs / buffer | | Multi-node profile fan-out | `cmd/profile-fanout` | Default control port `1919` | ## Deep plans (out of scope today) ### 1. HIP / ROCm eBPF activity (AMD) **Goal:** Parity with CUDA eBPF for AMD — launch counts, grid/block, memcpy, stream sync occupancy estimates — without ROCm app SDK hooks. **Approach** 1. Inventory stable userspace symbols in `libamdhip64.so` / `libhsa-runtime64.so` (launch, memcpy, stream sync, set-device). 2. Reuse the existing uprobe tracer skeleton (`internal/ebpf`) with a HIP event ABI parallel to CUDA `EventRecord`. 3. Device resolve via `/dev/kfd` + DRM minor → UUID/`hw.id` (same attr contract as NVML). 4. Occupancy model: launch→sync thread-seconds clamped to device compute-unit count from sysfs/amdsmi (not DCGM SM fields). 5. Soft attach policy: only when AMD GPUs present; missing HIP libs/caps soft-skip (instrument faults still fatal). **Non-goals:** Per-wavefront hardware occupancy; HIP graph per-node expand (phase 2 with CUDA graphs). **Milestones:** symbol map + stub tracer → single-GPU occupancy → multi-GPU set-device → docs + DaemonSet caps. ### 2. True GPU completion (vs launch→sync estimates) **Goal:** End spans on device completion, not CPU sync return — without CUPTI/app SDK. **Options (pick one primary)** | Option | Mechanism | Pros | Cons | | - | - | - | - | | A. CUDA event polling | Inject/observe `cudaEventRecord` + `cudaEventQuery` via uprobes; poll in userspace | No kernel module | Still needs app/runtime to record events; partial | | B. Nouveau/DRM completion (research) | Fence / timeline signals in kernel | True completion | Driver-specific; not on proprietary NVIDIA | | C. NVML / DCGM activity edges | Infer idle transitions | Already have DCGM | Coarse; not per-kernel | | D. Proprietary ioctl trace (last resort) | Trace driver submit/complete | Accurate | Fragile across driver versions | **Recommended path:** A for CUDA when events exist; keep launch→sync as fallback labeled `gpu.measurement.source=ebpf_model`; never rename to imply hardware SM occupancy. **Exit criteria:** Histogram of `gpu.kernel.duration` with `completion=event|sync_fallback` attribute; documented error bars. ### 3. CUDA Graph per-node kernels **Goal:** Expand `cudaGraphLaunch` composite span into per-node kernel spans. **Approach** 1. Uprobe `cudaGraphGetNodes` / instantiate / launch paths; build node-id → kernel-name map at graph instantiate time. 2. On launch, emit N child spans or N duration samples with `cuda.graph.id` + `cuda.graph.node_id`. 3. Cardinality: hash node names; cap distinct nodes per process (same budget as kernel-name cap). 4. Without stable instantiate hooks: keep composite span (current behavior). **Non-goals:** Replay/capture API coverage in v1; CUPTI graph callbacks. ### 4. `cudaStreamWaitEvent` / cross-stream edges **Goal:** Model wait edges so occupancy does not count blocked wait time as SM-busy. **Approach** 1. Uprobe wait-event + record-event; store event→stream edges in BPF map. 2. Occupancy engine subtracts wait intervals from active thread-seconds. 3. Attribute `cuda.wait.event` on duration samples when edge known. **Risk:** Map size under high event churn — bounded LRU + drop counter self-metric. ### 5. Intel GPU util / memory without XPU Manager **Goal:** Close Intel util/memory gap using collector-only sources. **Approach** 1. Probe `sysfs`/`hwmon` + DRM fdinfo (already used for process mem). 2. Optional: dynamic load of Level Zero loader for engine utilization if present (dlopen; soft-skip when missing). 3. Document engine util as extension `hw.gpu.engine.utilization` with `gpu.measurement.source=level0`. **Non-goals:** Shipping XPU Manager as a dependency. ### 6. Broader AMD DF / UMC PMU encodings **Goal:** Stable `hw.cpu.memory.io` across EPYC SKUs beyond current `uncore_imc*` / `amd_umc*` sysfs discovery. **Approach:** Per-family event tables keyed by CPUID; discovery still prefers sysfs; tables fill gaps; self-metric when fallback used. ### 7. Native Intel PT AUX (no `perf` binary) **Goal:** Capture PT without shelling out to `perf record`. **Approach:** `perf_event_open` + AUX mmap in `internal/intelpt`; keep duration/CPU/buffer caps; decode optional/offline. Fail-closed when PT enabled and open fails. ### 8. Proprietary fleet warehouses **Goal:** Not in scope. Export is **OTLP** (and optional Prometheus). Fleet warehouses / custom backends stay with the customer’s collector pipeline. **Collector work:** Keep resource identity + `hw.id` joins solid; no proprietary export protocol. ### 9. Process-side correlation without an SDK **Goal:** Richer join keys without instrumenting apps. **Approach (collector-only)** 1. Read `/proc//environ` for `TRACEPARENT` / `OTEL_RESOURCE_ATTRIBUTES` when present (best-effort, capped). 2. Join GPU series to cgroup → pod via existing PodResources path. 3. Optional: annotate on-demand profile artifacts with pid cmdline + container id only. **Non-goals:** Generating W3C trace IDs inside the collector; requiring OpenLIT SDK in training/serving jobs. ## Tracked (smaller) | Item | Status | Notes | | - | - | - | | Broader AMD DF / UMC | Tracked | See plan §6 | | Native Intel PT AUX | Tracked | See plan §7 | ## Feature failure policy Two outcomes at init — never conflated: | Outcome | Cause | Behavior | | - | - | - | | **Unavailable** | Missing vendor library, capability, platform, or hardware | Log `feature unavailable` (info); skip; process stays up | | **Fault** | OTel instrument registration bug, control addr misconfiguration, broken embedded BPF object | Log `feature fault` (error); **exit non-zero**; cannot be silenced | TPU scrape endpoint misses and DCGM/RDC blank samples are handled at scrape time (self-metrics), not as startup faults. # Semantic Conventions Source: https://docs.openlit.io/latest/gpu-collector/semconv Registry of OpenLIT GPU Collector metrics — instruments, units, attributes, and sources Metric names follow the [OpenTelemetry hardware GPU semantic conventions](https://opentelemetry.io/docs/specs/semconv/hardware/gpu/) where specified. OpenLIT **extension** metrics are listed explicitly below. ## Core device metrics (always registered) | Instrument | Type | Unit | Key attributes | Source | Notes | | - | - | - | - | - | - | | `hw.gpu.utilization` | Gauge | `1` | `hw.id`, `hw.gpu.task` | NVML / AMD / Intel | — | | `hw.gpu.memory.utilization` | Gauge | `1` | `hw.id` | vendor (usage/limit) | — | | `hw.gpu.memory.controller.utilization` | Gauge | `1` | `hw.id` | vendor / DCGM Prefer | extension | | `hw.gpu.memory.limit` | UpDownCounter | `By` | `hw.id` | vendor | — | | `hw.gpu.memory.usage` | UpDownCounter | `By` | `hw.id` | vendor | — | | `hw.gpu.memory.free` | UpDownCounter | `By` | `hw.id` | vendor | — | | `hw.power` | Gauge | `W` | `hw.id` | vendor | — | | `hw.power.limit` | Gauge | `W` | `hw.id` | vendor | — | | `hw.energy` | Counter | `J` | `hw.id` | vendor | — | | `hw.temperature` | Gauge | `Cel` | `hw.id`, `hw.sensor_location` | vendor | — | | `hw.fan.speed` | Gauge | `rpm` | `hw.id` | vendor | — | | `hw.fan.speed_ratio` | Gauge | `1` | `hw.id` | vendor (NVIDIA %) | — | | `hw.gpu.speed` | Gauge | `Hz` | `hw.id`, `hw.gpu.clock_domain` | vendor | `graphics` / `sm` / `memory` | | `hw.gpu.io` | Counter | `By` | `hw.id`, `network.io.direction` | vendor / DCGM | — | | `hw.gpu.interconnect.io` | Counter | `By` | `hw.id`, `hw.gpu.interconnect.type` | vendor / DCGM | — | | `hw.status` | UpDownCounter | `1` | `hw.id`, state attrs | vendor | — | | `hw.errors` | Counter | `{error}` | `error.type`, `hw.type=gpu` | vendor | — | | `hw.gpu.allocated` | Gauge | `1` | `hw.id` | derived | — | | `hw.gpu.idle` | Gauge | `1` | `hw.id` | derived | — | ## Process / workload attribution | Instrument | Type | Unit | Key attributes | Source | | - | - | - | - | - | | `process.gpu.memory.usage` | UpDownCounter | `By` | `process.pid`, `hw.id`, k8s.\* | NVML / DRM fdinfo | | `process.gpu.utilization` | Gauge | `1` | `process.pid`, `hw.id` | NVML / DRM fdinfo | | `process.uptime` | Gauge | `s` | `process.pid` | `/proc` enrichment | ## DCGM extension (`gpu.measurement.source=dcgm`) DCP-only series always emit when DCGM is enabled. Overlapping series (`hw.gpu.io`, interconnect, `hw.power`, `hw.gpu.utilization`, `hw.gpu.memory.controller.utilization`, SM `hw.gpu.speed`) emit **only** when `OTEL_GPU_DCGM_PREFER=true` and the sample is non-blank; the vendor (NVML) path then suppresses the same instruments so there is a single producer. Blank Prefer samples re-enable NVML until the next healthy DCGM scrape. Spec `hw.gpu.memory.utilization` (usage/limit) always comes from the vendor path. | Instrument | Type | Unit | Key attributes | Notes | | - | - | - | - | - | | `hw.gpu.engine.utilization` | Gauge | `1` | `hw.gpu.engine` | DCGM eng util | | `hw.gpu.sm.utilization` | Gauge | `1` | | SM active (DCP) | | `hw.gpu.sm.occupancy` | Gauge | `1` | | SM occupancy (DCP) | | `hw.gpu.pipe.utilization` | Gauge | `1` | `hw.gpu.pipe`=`tensor\|fp16\|fp32\|fp64` | DCP pipe | | `hw.gpu.memory.bandwidth.utilization` | Gauge | `1` | | DRAM util | | `hw.gpu.io` / `hw.gpu.interconnect.io` | Counter | `By` | Prefer only | Rate-integrated int64 | | `hw.gpu.speed` | Gauge | `Hz` | Prefer only; `clock_domain=graphics` | Field 100 (keeps NVML graphics series identity) | | `hw.gpu.idle` | Gauge | `1` | Prefer only | Derived from Prefer util | | `hw.power` / `hw.gpu.utilization` / `hw.gpu.memory.controller.utilization` | Gauge | Prefer only | Fields 155 / 203 / 204 | | | `openlit.collector.gpu.dcgm.sample_valid` | UpDownCounter | `1` | `hw.id` | 0 on blank sample | ## AMD RDC extension (`gpu.measurement.source=rdc`) | Instrument | Type | Unit | Key attributes | Notes | | - | - | - | - | - | | `hw.gpu.sm.utilization` | Gauge | `1` | | `RDC_FI_PROF_SM_ACTIVE` | | `hw.gpu.sm.occupancy` | Gauge | `1` | `hw.parent` (partitions) | Soft-fail when librdc missing | | `hw.gpu.pipe.utilization` | Gauge | `1` | `hw.gpu.pipe`=`fp16\|fp32\|fp64` | EVAL\_FLOPS percent fields | | `hw.gpu.simd.utilization` | Gauge | `1` | | `RDC_FI_PROF_SIMD_UTILIZATION` | ## eBPF CUDA (Linux/NVIDIA) | Instrument | Type | Unit | Family | | - | - | - | - | | `gpu.kernel.launch.calls` | Counter | `{call}` | activity | | `gpu.kernel.grid.size` / `block.size` | Histogram | `{thread}` | activity | | `gpu.kernel.shared_memory` | Histogram | `By` | activity | | `gpu.kernel.duration` | Histogram | `s` | launch→sync model; cardinality-capped `cuda.kernel.name` | | `gpu.graph.launch.calls` | Counter | `{call}` | graph **replay** invocations, not kernels inside the replay | | `gpu.memory.allocations` | Counter | `By` | activity | | `gpu.memory.copies` | Histogram | `By` | activity | | `process.gpu.core.usage` / `process.gpu.estimated.sm_active` | Gauge | `{cores}` / `1` | occupancy model (`gpu.measurement.source=ebpf_model`) | | `hw.gpu.core.limit` / `hw.gpu.estimated.sm_active` | Gauge | `{cores}` / `1` | **not** DCGM `hw.gpu.sm.*` | ## Collector self-metrics | Instrument | Type | Unit | Notes | | - | - | - | - | | `openlit.collector.gpu.scrape.errors` | Counter | `{error}` | Optional backends (DCGM/RDC) | | `openlit.collector.gpu.dcgm.sample_valid` | UpDownCounter | `1` | Per-GPU blank-sample health | | `openlit.collector.profile.requests` | Counter | `{request}` | Control plane profiling | ## Correlation keys Join continuous GPU, host, and on-demand profile data with these keys. Prefer (`OTEL_GPU_DCGM_PREFER=true`) keeps a **single producer** for overlapping NVML/DCGM series; if a Prefer sample is blank, NVML fills until the next healthy DCGM sample. | Join | Attributes | | - | - | | GPU across NVML / DCGM / RDC / eBPF | `hw.id` (UUID) + `gpu.index` + `gpu.pci_address` + `gpu.measurement.source` | | Host NIC vs (when NIC off) system network | `network.interface.name` (= `hw.id` on `hw.network.*`) | | TPU vs GPU | `hw.type` + namespaced `hw.id` (`tpu:`) + `hw.tpu.accelerator_id` | | Soft util overlap | NVML `hw.gpu.utilization` ≠ DCGM `hw.gpu.sm.utilization` ≠ eBPF `hw.gpu.estimated.sm_active` — different semantics; do not sum or equate | | eBPF activity join | `gpu.measurement.source=ebpf` (+ `hw.id` / `hw.type` when device known) | | On-demand profiles | Control API default `127.0.0.1:1919` (`profile-fanout --port 1919`) | ## Common resource / identity attributes `hw.id`, `hw.name`, `hw.vendor`, `hw.type`, `gpu.index`, `gpu.pci_address`, `gpu.measurement.source`, optional MIG attrs, and host/k8s/cloud resource attributes from identity detection. Partition samples may set `hw.parent` and `hw.gpu.partition`. # Alerts Source: https://docs.openlit.io/latest/openlit/alerts Fire within seconds when cost, latency, tokens, or errors cross a threshold, and get notified in OpenLIT, Slack, email, PagerDuty, and more Open **Alerts** from the sidebar under **Monitor** (`/alerts`). There are two kinds of alert: | Kind | Watches | Notifies | Edition | | - | - | - | - | | **Threshold** | Live GenAI spans: error rate, error count, span count, latency, tokens, cost | In OpenLIT (Alerts pages and the header bell). Enterprise adds Slack, email, webhooks, and other destinations | All editions | | **Event** | Changes made in OpenLIT, such as access updates, API keys, Vault secrets, scanner results, or threshold alerts firing | Slack, email, PagerDuty, webhooks, and other destinations | Enterprise | The Alerts area has these pages: * **Overview** (`/alerts`): what is firing now, by severity, recent activity, and the most-triggered alerts. In Enterprise it also shows event alerts and notification delivery for the last 24 hours. * **Alerts** (`/alerts/list`): every alert in the current project and environment. In Enterprise, the **Alerts** tab lists threshold and event alerts together with **All**, **Firing**, **Threshold**, and **Event** filters, and the **Activity** tab lists every notification sent. * **Destinations** (`/alerts/destinations`, Enterprise): where notifications are sent. Create an alert with the **+** button in the page header. Its menu lists the kinds of alert you can create. ![Alerts overview with firing counts, firing by severity, recent activity, and most-triggered alerts](https://mintcdn.com/openlit/G-vQ13EYWTGJ0Z0a/images/alerts-overview.png?fit=max\&auto=format\&n=G-vQ13EYWTGJ0Z0a\&q=85\&s=bd275dad678125e99f10a64bbedd5771) ## How threshold alerts work Threshold alerts are evaluated on the live span stream, not by querying ClickHouse: ```mermaid theme={"theme":{"light":"github-light","dark":"github-dark"}} flowchart LR App[AI app / SDK] -->|OTLP + API key| Rec[OpenLIT OTLP receiver] Rec --> CH[(ClickHouse)] Rec -->|"openlit.signal.<org>.<project>.<env>"| NATS[(NATS JetStream)] NATS --> Eng[openlit-engine] Eng -->|firing / resolved| UI[OpenLIT UI: Alerts, bell, destinations] ``` 1. The [OTLP receiver](/latest/openlit/otlp-receiver#realtime-signals) writes each span to ClickHouse and, for GenAI spans sent with an API key, publishes a compact signal to NATS JetStream on a subject scoped to the organisation, project, and environment. 2. The bundled `openlit-engine` keeps a sliding window per alert and group, and checks every enabled alert every 5 seconds. 3. When an alert breaches or clears, the engine sends the change to OpenLIT, which records it and notifies. A new or edited alert is picked up within about 10 seconds. Realtime needs `NATS_URL`; Docker Compose runs NATS by default. See [Realtime variables](/latest/openlit/configuration#realtime-variables). Signals never include prompts, completions, or messages. Threshold alerts only see spans sent to the project with an [API key](/latest/openlit/otlp-receiver#authentication). ## Create a threshold alert **1. Start from a template** Select **+** in the header, or **New alert**. Pick a template (**Error rate spike**, **P95 latency breach**, **Token usage spike**, **Cost spike**) or **Blank**. A template only fills in the form; nothing runs until you save. ![New threshold alert form with templates, details, and condition](https://mintcdn.com/openlit/G-vQ13EYWTGJ0Z0a/images/alerts-new-threshold.png?fit=max\&auto=format\&n=G-vQ13EYWTGJ0Z0a\&q=85\&s=329bd7284bd8460977689bdeec7d2069) **2. Name the alert** Under **Details**, enter a **Name**, an optional **Description**, and a **Severity** (info, warning, or critical). **3. Set the condition** * **Metric**: error rate, error count, span count, p95 latency, average latency, total tokens, or cost. * **Operator** and **Threshold**, for example `> 25` for cost in USD or `> 0.25` for a 25% error rate. * **Window**: the period the metric is computed over (1 minute to 1 hour in the form; 30 seconds to 1 hour through the API). * **Group by** (up to three fields): each value, such as each model or service, is tracked and alerted on separately. Pick fields with a bounded set of values (model, service, route). Each alert tracks up to 200 groups (1,000 per project environment); new values beyond that are not evaluated until older groups go quiet, so grouping by a per-request value such as a response ID makes the alert miss values. * **Minimum spans** in the window before it can fire, and a **Cooldown** between two firings of the same group (at least 60 seconds). **4. Narrow it with span filters** Add **span filters** to evaluate only some spans, for example `model = gpt-4o` or `service in checkout-bot, search`. All filters must match, and text comparisons are case-insensitive. Operators: `=`, `≠`, `in`, `not in`, `exists`, `>`, `≥`, `<`, `≤`, and `contains`. **5. Choose who to notify and save** Under **Notify**, firing alerts always appear in OpenLIT. In Enterprise you can also pick destinations (see [Notify destinations](#notify-destinations)). The bar at the bottom describes the alert in plain language, for example *Fires when Cost > \$25.00 over 5m · per model, for model = gpt-4o*. Turn **Enabled** off to save it paused, then select **Create alert**. ### Span fields and attributes **Group by** and **span filters** use a searchable field picker: * **Span fields**: `service`, `model`, `provider`, `operation`, `status`, `error_type`, `span_name`, and, for filters, the numbers `http_status`, `duration_ms`, `input_tokens`, `output_tokens`, `total_tokens`, and `cost`. * **Span attributes**: common keys such as `gen_ai.tool.name` or `http.route`, plus the attribute keys seen on this project's spans in the last 24 hours. * **Any other key**: type it, for example `app.tier` or `k8s.namespace.name`, and select **Use attribute**. ![Span filter field picker listing span fields and offering a typed attribute key](https://mintcdn.com/openlit/G-vQ13EYWTGJ0Z0a/images/alerts-span-filter-picker.png?fit=max\&auto=format\&n=G-vQ13EYWTGJ0Z0a\&q=85\&s=3ca395396af9115ec3bdc1ded2cf7a41) Span and resource attributes both work (span values win), up to 64 per span, with values truncated to 256 bytes and credential-like values redacted. Some keys are restricted: * **Never available**: keys containing `prompt`, `completion`, `message`, `content`, `secret`, `password`, `authorization`, `api_key`, `credential`, `cookie`, `bearer`, or `instruction`. * **Personal keys, off by default**: any key with a `user`, `enduser`, `session`, `conversation`, `email`, `phone`, `ip`, `address`, or `path` segment, such as `user.id`, `session.id`, `url.path`, or `server.address`. List the keys you need in [`OPENLIT_SIGNAL_ATTRIBUTES`](/latest/openlit/configuration#realtime-variables); until then, saving an alert that uses one returns `400`. ### Firing and resolved Each group an alert fires for, such as one model, is tracked separately as a **finding**: * **Firing**: the metric over the window breaches the threshold and the window has at least the minimum spans. A group fires again only after its cooldown. * **Resolved**: the metric over the window no longer breaches, for example because the slow or failing spans aged out of the window. If it breaches again, the same finding reopens. Resolved history is kept for 30 days by default (`OPENLIT_REALTIME_FINDING_RETENTION_DAYS`). Each environment can have up to 1,000 firing groups at once (`OPENLIT_REALTIME_MAX_OPEN_FINDINGS`); beyond that, new groups are not recorded. ### Alert details Select an alert to open it. The header shows its status, severity, condition, filters, how many groups are firing now, and when it last fired. The tabs show: * **Activity**: every group the alert fired for, with its state, value, first and last seen, when it resolved, and how many times it fired. Filter by **All**, **Firing**, or **Resolved**. * **Live spans**: the latest spans that match the alert's span filters. Select a span to open the same trace view as Telemetry in a side panel, or select **Full screen** for the trace page. * **Configuration**: the saved condition, group by, minimum spans, cooldown, span filters, and the engine rule ID. In Enterprise, the detail page also lists the **Notifications** this alert sent and the result for each destination. ![Trace view opened in a side panel from an alert's live spans](https://mintcdn.com/openlit/G-vQ13EYWTGJ0Z0a/images/alerts-trace-sheet.png?fit=max\&auto=format\&n=G-vQ13EYWTGJ0Z0a\&q=85\&s=f56ac499d6728fd6417afab5521a86a5) ### Notify destinations > [!NOTE] > > **Enterprise feature.** This is part of [OpenLIT Enterprise](/latest/openlit/enterprise) and needs an enterprise license applied to your organisation; the community edition doesn't include it. [Book a call](https://cal.com/aman.openlit/30min) or email [developers@openlit.io](mailto:developers@openlit.io) to get access to the enterprise build and a license. With the **Alerts** feature, the **Notify** section of a threshold alert lists your organisation's destinations. Select **Add destination** to create one without leaving the form. For each selected destination you can set: * **Active for this alert**: turn it off to keep the destination attached without sending to it. * **Start from a template**: **Summary**, **Incident detail**, or **Short** fill in the title and message. * **Channel** (Slack): overrides the connector's default channel for this alert. * **Title template** and **Message template**. * **Card style** (Default, Compact, Detailed, or Incident) and an **Accent** colour. * **Include matched payload fields** in the notification. ![Notify section with a Slack destination and its message template editor](https://mintcdn.com/openlit/G-vQ13EYWTGJ0Z0a/images/enterprise/alerts-destination-template.png?fit=max\&auto=format\&n=G-vQ13EYWTGJ0Z0a\&q=85\&s=e50d4b52adcf7433144c7ce805eca5d2) Templates can use `{{alert.name}}`, `{{alert.severity}}`, and these payload fields: | Variable | Value | | - | - | | `{{payload.message}}` | A readable summary, for example *Demo cost spike is firing for model: gpt-4o: cost 36 vs threshold 25 (production).* | | `{{payload.event}}` | `realtime_finding_opened`, `realtime_finding_reopened`, or `realtime_finding_resolved` | | `{{payload.state}}` | `firing` or `resolved` | | `{{payload.rule_name}}`, `{{payload.severity}}`, `{{payload.environment}}` | The alert's name, severity, and environment | | `{{payload.metric}}`, `{{payload.value}}`, `{{payload.threshold}}` | What breached, the observed value, and the threshold | | `{{payload.group.}}` | The group value, such as `{{payload.group.model}}` | | `{{payload.occurrences}}`, `{{payload.first_seen_at}}`, `{{payload.last_seen_at}}` | How often and when the group fired | A notification is sent when a group starts firing, fires again, and resolves. Destinations without a template send `{{payload.message}}`. Each delivery is listed on the alert and in **Alerts → Activity**. Without the Alerts license, the **Notify** section shows that destinations are locked, and threshold alerts keep notifying in OpenLIT. ## Event alerts > [!NOTE] > > **Enterprise feature.** This is part of [OpenLIT Enterprise](/latest/openlit/enterprise) and needs an enterprise license applied to your organisation; the community edition doesn't include it. [Book a call](https://cal.com/aman.openlit/30min) or email [developers@openlit.io](mailto:developers@openlit.io) to get access to the enterprise build and a license. Event alerts notify your team when something important changes in OpenLIT, such as a member's access changing, an API key being created, a Vault secret being deleted, a scanner finishing with findings, or a threshold alert firing in any project. Each alert watches one or more **triggers**, filters them with optional **conditions**, and delivers to one or more **destinations** like Slack, PagerDuty, or a webhook. ### Create an event alert **1. Add a destination** Open **Destinations** and select **Add destination**, or select **Add destination** while creating an alert. Choose a provider and fill in its settings. See [Notification connectors](/latest/openlit/connectors/notification) for the fields each provider needs. Destinations are shared by every alert in the organisation. **2. Switch to Event** Select **+** in the header and choose **Event alert**, or open **New alert** and select **Event** at the top of the form. The form switches in place; **Threshold** is selected by default. ![New alert page with the Event type selected, showing details and trigger sections](https://mintcdn.com/openlit/G-vQ13EYWTGJ0Z0a/images/enterprise/alerts-event-form.png?fit=max\&auto=format\&n=G-vQ13EYWTGJ0Z0a\&q=85\&s=ba3827e566bf3a2c83198bfc38017f26) **3. Describe the alert** Under **Details**, enter a **Name**, a **Severity** (low, medium, high, or critical), a **Status** (**Active** or **Paused**), and an optional **Description**. **4. Choose triggers** Under **Trigger**, pick one or more triggers. The alert fires when any selected trigger matches its own conditions. Leave **Conditions** empty to fire on every event of that trigger. **5. Pick destinations** Under **Destinations**, select where to send the alert and customise each destination's message in the same way as for [threshold alerts](#notify-destinations). **6. Set delivery options and save** Under **Delivery**, set **Cooldown seconds** (default 300) and an optional **Dedupe key**, then select **Save**. Use the send button on the alert to send a test notification. ### Conditions Conditions narrow when a trigger fires. Each trigger has its own condition groups: * Every group with conditions must match, and within a group you choose whether conditions are combined with **AND** or **OR**. * Text fields support equals, not equals, contains, not contains, starts with, ends with, regex, in, and not in. For **in** and **not in**, separate values with commas. * Number fields support `=`, `≠`, `>`, `≥`, `<`, `≤`, and **between** (enter `min,max`). * Text fields suggest values that OpenLIT has already seen for that trigger, and you can type your own. For example, to alert only when someone grants full access to an API key, use the **API keys** trigger with the conditions `event` equals `api_key_access_updated` and `access` equals `full`. Event message templates can use `{{alert.id}}`, `{{alert.name}}`, `{{alert.severity}}`, `{{event.id}}`, `{{event.triggerType}}`, `{{payload.message}}`, and `{{payload.}}` for any trigger field. ### Triggers | Trigger | Fires on | Fields you can filter on | | - | - | - | | **Access updates** | A member's project access, role, or direct permissions change | event, target email, target user ID, actor email, old role, new role, project count, project IDs, permission count, permissions | | **Invites** | A member is invited or added | event, email, invitee email, actor email, role, project count | | **Prompt versions** | A prompt version is created or updated | event, prompt ID, version, status | | **Fleet Hub configuration** | A Fleet Hub agent configuration changes | event, agent ID, config key | | **Vault keys** | A Vault secret is created, updated, or deleted | event, secret ID, key | | **Contexts** | A context is created, updated, or deleted | event, context ID, name, status | | **Rule engine** | A rule is created, updated, or deleted, its conditions change, or an entity is linked or unlinked | event, rule ID, rule name, status, entity type | | **Memories** | A memory is created, updated, or deleted | event, memory ID, source type, application | | **API keys** | An API key is created or deleted, or its access changes | event, name, environment, access (`full` or `restricted`), features | | **Connectors** | A connector is created, updated, or deleted | event, connector ID, name, type, environment | | **Scanners** | A scanner is created, updated, or deleted, or a scan finishes | event, scanner ID, name, type, environment, target, status, finding count | | **Realtime findings** | A threshold alert starts firing, fires again, or resolves | event, rule ID, rule name, severity, kind, environment, state, metric, value, threshold | ### How delivery works When a matching change happens, OpenLIT records an **alert event** and sends it to every active destination attached to the alert. * **Cooldown:** an alert doesn't fire again until **Cooldown seconds** have passed since its last event. Set `0` to turn cooldown off. * **Dedupe key:** when set, repeated events with the same key are dropped within the cooldown window, or within 300 seconds if cooldown is off. * **Retries:** a failed delivery is retried once after 2 seconds. * **Paused alerts** don't fire. Each event ends in one of these states: **success** (all deliveries succeeded), **partial\_failure** (some failed), or **failure** (all failed, or the alert has no destinations). ## Review notifications > [!NOTE] > > **Enterprise feature.** This is part of [OpenLIT Enterprise](/latest/openlit/enterprise) and needs an enterprise license applied to your organisation; the community edition doesn't include it. [Book a call](https://cal.com/aman.openlit/30min) or email [developers@openlit.io](mailto:developers@openlit.io) to get access to the enterprise build and a license. **Alerts → Activity** shows a timeline of notifications from every alert, threshold and event, and a list you can filter by status, trigger, alert, provider, and delivery status. Select an event to see its payload, the conditions that matched, and the result of each delivery. Alert events are kept for 90 days. The old `/alerts/events` address opens this tab. ![Alerts Activity tab with the notification timeline and fired events](https://mintcdn.com/openlit/G-vQ13EYWTGJ0Z0a/images/enterprise/alerts-activity.png?fit=max\&auto=format\&n=G-vQ13EYWTGJ0Z0a\&q=85\&s=25cf075e2add8ac95b96a9ad75274db1) The **Overview** adds an **Event alerts and notifications** section: active event alerts, notifications sent in the last 24 hours, how many were delivered or failed, the delivery timeline, and the latest notifications. ![Alerts overview section with event alert and notification counts, delivery timeline, and recent notifications](https://mintcdn.com/openlit/G-vQ13EYWTGJ0Z0a/images/enterprise/alerts-overview-events.png?fit=max\&auto=format\&n=G-vQ13EYWTGJ0Z0a\&q=85\&s=2754c30c452e1fba7bba7256e38984c2) ## Availability | Capability | Community | Enterprise | | - | - | - | | Threshold alerts, findings, live spans, in-app notifications and header bell | ✓ | ✓ | | Threshold alert destinations and message templates | | ✓ (**Alerts** feature) | | Event alerts, Activity tab, destinations | | ✓ (**Alerts** feature) | Event alerts and destinations need an [OpenLIT Enterprise](/latest/openlit/enterprise) license that includes the **Alerts** feature. Sending to destinations such as Slack or PagerDuty uses [notification connectors](/latest/openlit/connectors/notification), which are premium connectors and also need the **Premium connectors** feature. Without the Alerts license, event alerts and destinations are locked and their APIs return `403` with `upgrade_required`. ![Enterprise Alerts page with Alerts and Activity tabs, type filters, and threshold and event alerts in one list](https://mintcdn.com/openlit/G-vQ13EYWTGJ0Z0a/images/enterprise/alerts.png?fit=max\&auto=format\&n=G-vQ13EYWTGJ0Z0a\&q=85\&s=c18ca96036a8d80e0c406e8c252f1565) ## Permissions In Community, every signed-in member of a project can view and manage its threshold alerts. In Enterprise, these permissions apply to both kinds of alert: | Permission | Allows | Default roles | | - | - | - | | `alerts:read` | View alerts, findings, notifications, and destinations | Owner, Admin, Member | | `alerts:create` | Create alerts | Owner, Admin | | `alerts:update` | Edit, pause, and resume alerts | Owner, Admin | | `alerts:delete` | Delete alerts | Owner, Admin | | `alerts:test` | Send test alerts | Owner, Admin | | `alerts:manage_destinations` | Add, edit, and delete destinations | Owner, Admin | With [RBAC](/latest/openlit/rbac), you can grant these to custom role groups. ## Threshold alerts API Requests act on the current project and the environment in the `x-openlit-environment` header (default `production`); the organisation, project, and environment are never read from the body. In Enterprise, each endpoint needs the matching `alerts:*` permission. | Endpoint | Description | | - | - | | `GET /api/alerts/threshold` | List threshold alerts with status, firing groups, and last trigger time | | `POST /api/alerts/threshold` | Create a threshold alert | | `GET`, `PATCH`, `DELETE /api/alerts/threshold/{id}` | Read, update (send `{ "enabled": false }` alone to pause), or delete an alert and its history | | `GET /api/alerts/threshold/{id}/findings` | Firing and resolved history (`state`, `limit`) | | `GET /api/alerts/threshold/{id}/signals` | Recent spans that match the alert's span filters | | `GET /api/alerts/threshold/{id}/deliveries` | Notifications this alert sent, with per-destination results (`limit`, Enterprise) | | `GET /api/alerts/threshold/overview` | Counts, firing by severity, recent activity, and most-triggered alerts | | `GET /api/alerts/threshold/templates` | Templates used by the New alert form | The request body for create and update: ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "name": "gpt-4o cost above budget", "severity": "critical", "enabled": true, "definition": { "metric": "cost", "operator": "gt", "threshold": 25, "windowSec": 300, "minSamples": 1, "cooldownSec": 900, "groupBy": ["model"], "match": [{ "field": "model", "op": "eq", "values": ["gpt-4o"] }] }, "delivery": { "destinations": [ { "destinationId": "", "overrides": { "titleTemplate": "{{payload.rule_name}}: {{payload.state}}", "messageTemplate": "{{payload.message}}", "channel": "#oncall", "cardStyle": "incident", "accentColor": "#e11d48", "includeFields": false, "enabled": true } } ] } } ``` * `metric`: `error_rate`, `error_count`, `count`, `p95_latency_ms`, `avg_latency_ms`, `total_tokens`, or `cost`. `operator`: `gt`, `gte`, `lt`, or `lte`. * `match[].op`: `eq`, `neq`, `in`, `not_in`, `exists`, `gt`, `gte`, `lt`, `lte`, or `contains`. * `delivery` is Enterprise only and optional. Omit it on update to keep the current destinations; send `{ "destinations": [] }` to notify in OpenLIT only. `{ "destinationIds": ["..."] }` is still accepted and uses default message settings. Unknown `overrides` keys are dropped; `titleTemplate` is limited to 300 characters, `messageTemplate` to 2,000, `channel` to 100, `cardStyle` must be `default`, `compact`, `detailed`, or `incident`, and `accentColor` a `#rrggbb` colour. ## Event alerts API > [!NOTE] > > **Enterprise feature.** This is part of [OpenLIT Enterprise](/latest/openlit/enterprise) and needs an enterprise license applied to your organisation; the community edition doesn't include it. [Book a call](https://cal.com/aman.openlit/30min) or email [developers@openlit.io](mailto:developers@openlit.io) to get access to the enterprise build and a license. Signed-in users can manage event alerts through the API. Every endpoint needs the Alerts license and the matching permission. Threshold alerts are not listed here. | Endpoint | Description | | - | - | | `GET /api/alerts` | List event alerts for the organisation and current project | | `POST /api/alerts` | Create an event alert | | `GET`, `PATCH`, `DELETE /api/alerts/{id}` | Read (with its last 25 events), update, or delete an alert | | `POST /api/alerts/{id}/test` | Send a test alert | | `GET /api/alerts/triggers` | List triggers and their fields | | `GET /api/alerts/events` | List alert events from every alert with filters and pagination | | `GET /api/alert-destinations` | List destinations | | `GET /api/alerts/spec` | OpenAPI specification for the event alerts API | Bind destinations with `destinations: [{ "destinationId": "...", "overrides": { ... } }]` (or `destinationIds`). `overrides` takes the same settings as threshold alerts: `enabled`, `includeFields`, `channel` (up to 100 characters), `titleTemplate` (300), `messageTemplate` (2,000), `cardStyle` (`default`, `compact`, `detailed`, `incident`), and `accentColor` (`#rrggbb`). Unknown keys are dropped; invalid values return `400`. *** > **[Notification connectors](/latest/openlit/connectors/notification)** > > Configure Slack, email, webhook, Discord, PagerDuty, Opsgenie, and Datadog destinations > **[OTLP receiver](/latest/openlit/otlp-receiver)** > > How spans reach ClickHouse and the realtime stream # Audit Logs Source: https://docs.openlit.io/latest/openlit/audit-logs Review who changed what across your organisation, with filters by action, target, project, database config, and actor > [!NOTE] > > **Enterprise feature.** This is part of [OpenLIT Enterprise](/latest/openlit/enterprise) and needs an enterprise license applied to your organisation; the community edition doesn't include it. [Book a call](https://cal.com/aman.openlit/30min) or email [developers@openlit.io](mailto:developers@openlit.io) to get access to the enterprise build and a license. Audit Logs record privileged changes made in OpenLIT, such as member and role changes, license updates, API key changes, connector and configuration updates, so you can see who did what, to which resource, and when. Open **Audit Logs** from the sidebar under **Monitor** (`/audit-logs`). Audit logs are organisation-wide, so you don't need to select a project first. ## Availability Audit Logs need an [OpenLIT Enterprise](/latest/openlit/enterprise) license that includes the **Audit log** feature for your organisation. * Events are recorded only while the organisation is licensed. Changes made while it isn't licensed are never logged, even if a license is applied later. * Without the license, the page is locked and the API returns `403` with `upgrade_required`. ## Permissions Viewing audit logs needs the `audit_logs:read` permission. **Owner** and **Admin** have it by default; **Member** doesn't. With [RBAC](/latest/openlit/rbac) you can grant it to a custom role group. Audit logs are read-only: nobody can edit or delete entries. ## Browse audit logs ![Audit Logs page with an hourly volume chart and a list of recent events](https://mintcdn.com/openlit/Bq8cTvlOcuilnTTA/images/enterprise/audit-logs.png?fit=max\&auto=format\&n=Bq8cTvlOcuilnTTA\&q=85\&s=941ca1596cb1ec3a64c76f96a8588342) The page shows: * **Recent volume**: a bar chart of events per hour for the entries on the current page. * **The event list**: one card per event with the target type, time, and action, plus the target, actor, project, database config, and a short summary of the details. Use the toolbar to choose a time range (**24H**, **7D**, **1M**, **3M**, or **CUSTOM**), change the page size (10, 25, or 50), and **Refresh**. ### Filter events Select **Filters** to narrow the list by: | Filter | Matches | | - | - | | Action | The event action, such as `api_key.created` | | Target type | The kind of resource changed, such as `api_key` or `license` | | Target ID | The ID of the changed resource | | Project | Events in one project | | DB config | Events for one database config; choosing a project limits this list to that project | | Actor | The user who made the change | Each list shows values found in the selected time range. Select **Apply filters** to update the list, or **Clear filters** to reset. ### Event details Select an event to open its details. The panel shows the target, the actor, project, and database config by name where possible, the time, and a **Metadata** section with everything captured for the event: the request path and parameters, the request and response bodies, the status, the client user agent, and IP address. ![Audit event detail for a member role change, with the actor, project, and metadata](https://mintcdn.com/openlit/Bq8cTvlOcuilnTTA/images/enterprise/audit-log-detail.png?fit=max\&auto=format\&n=Bq8cTvlOcuilnTTA\&q=85\&s=696713f6491e17fa398a915fb4594298) > [!NOTE] > > Sensitive values are redacted before they're stored. Any field whose name looks like a password, secret, token, API key, license key, authorization header, cookie, session, or secret value is saved as `[REDACTED]`. ## What gets recorded OpenLIT records an event when a privileged request **succeeds**. Failed or denied requests aren't recorded. | Area | Actions | | - | - | | Organisation | `organisation.selected`, `organisation.updated`, `organisation.deleted` | | Members and invitations | `member.invited`, `member.invitation_cancelled`, `member.invitation_declined`, `member.invitation_accepted`, `member.role_changed`, `member.permissions_updated`, `member.project_access_updated`, `member.ownership_transferred`, `member.removed` | | Roles | `role.created`, `role.updated`, `role.deleted` | | Licenses and entitlements | `license.applied`, `license.revoked`, `entitlement.granted`, `entitlement.revoked` | | Projects | `project.created`, `project.updated`, `project.deleted`, `project.selected`, `project.environment_created` | | Database configs | `db_config.created`, `db_config.updated`, `db_config.deleted`, `db_config.selected`, `db_config.shared` | | API keys | `api_key.created`, `api_key.deleted`, `api_key.access_updated` | | Alerts | `alerts.alert_created`, `alerts.alert_updated`, `alerts.alert_deleted`, `alerts.alert_tested`, `alerts.destination_created`, `alerts.destination_updated`, `alerts.destination_deleted`, `alerts.destination_tested` | | Connectors and telemetry sources | `connectors.connector_created`, `connectors.connector_updated`, `connectors.connector_deleted`, `connectors.connector_tested`, `connectors.connector_bound`, `connectors.connector_unbound` | | Controller | `controller.config_updated`, `controller.service_instrumented`, `controller.service_uninstrumented`, `controller.service_started`, `controller.service_stopped`, `controller.service_restarted`, `controller.agent_instrumented`, `controller.agent_uninstrumented` | | Fleet Hub | `fleet_hub.config_updated`, `fleet_hub.tls_tested` | | Dashboards | `dashboard.board_created`, `dashboard.board_updated`, `dashboard.board_deleted`, `dashboard.layout_updated`, `dashboard.layout_imported`, `dashboard.folder_created`, `dashboard.folder_updated`, `dashboard.folder_deleted`, `dashboard.widget_created`, `dashboard.widget_updated`, `dashboard.widget_deleted` | | Prompts | `prompt.created`, `prompt.deleted`, `prompt.version_created` | | Vault | `vault.secret_created`, `vault.secret_updated`, `vault.secret_deleted` | | Context | `context.created`, `context.updated`, `context.deleted` | | Evaluations | `evaluation.config_updated`, `evaluation.auto_config_updated`, `evaluation.offline_created`, `evaluation.feedback_created`, `evaluation.type_created`, `evaluation.type_updated`, `evaluation.type_deleted` | | Rule engine | `rule_engine.conditions_updated` | | OpenGround and models | `openground.created`, `openground.deleted`, `openground.provider_created`, `openground.provider_updated`, `openground.provider_deleted`, `openground.config_created`, `openground.config_updated`, `openground.config_deleted`, `manage_models.model_created`, `manage_models.model_deleted`, `manage_models.model_imported`, `manage_models.model_cleaned` | | Pricing | `pricing.config_updated`, `pricing.auto_config_updated`, `pricing.exported`, `pricing.usage_refreshed` | | Memory | `memory.memory_created`, `memory.memory_updated`, `memory.memory_deleted`, `memory.memory_feedback`, `memory.memory_copied`, `memory.memory_extracted`, `memory.memory_imported`, `memory.config_updated` | | GPUs | `gpu.price_created`, `gpu.price_updated`, `gpu.price_deleted`, `gpu.recommendation_dismissed`, `gpu.recommendation_reopened` | | Governance | `governance.report_viewed`, `governance.passport_exported` | Each event stores the organisation, the project and database config when known, the actor, the action, the target type and ID, the metadata, and the time. ## Retention Audit logs are kept until the organisation is deleted; deleting a project or database config doesn't remove its events. Each query can cover up to 366 days. If the license lapses, recording stops and the page locks, but existing events are kept and become visible again once the license is reapplied. ## Audit logs API Signed-in users with `audit_logs:read` can query audit logs: ```http theme={"theme":{"light":"github-light","dark":"github-dark"}} GET /api/organisation/{organisationId}/audit-logs ``` | Query parameter | Description | | - | - | | `page` | Page number. Defaults to `1`. | | `take` | Page size. Defaults to `50`, maximum `100`. | | `from`, `to` | ISO 8601 time range. Defaults to the last 30 days; the range can't exceed 366 days. | | `action`, `targetType`, `targetId`, `projectId`, `databaseConfigId`, `actorUserId` | Exact-match filters. | Results are newest first and include pagination totals. This endpoint needs a signed-in session; API keys can't read audit logs. *** > **[Role-based access control](/latest/openlit/rbac)** > > Control who can view audit logs and manage your organisation > **[Alerts](/latest/openlit/alerts)** > > Get notified when important changes happen # Chat with Otter Source: https://docs.openlit.io/latest/openlit/chat/conversations Use Otter's AI chat assistant to query observability data with natural language, save widgets, generate dashboards, and manage OpenLIT resources The main Otter screen is `/chat` (optional `?id=` for a specific thread). Empty-state title: **Your AI-Powered Observability Copilot**. ## Conversations * **New Chat** starts a conversation; titles are generated after the first exchange. * Search with **Search conversations...** in the sidebar. * Delete a conversation from its row (trash on hover). * Each conversation has its own URL so you can bookmark or share the link within your team. Placeholder for the composer: **Ask a question about your data...** (Enter to send, Shift+Enter for a new line). Empty-state example chips include analyzing slow traces from the last 24 hours, token usage and cost by model, recent errors for a busy service, creating a dashboard for volume/errors/latency/cost, improvement analysis on high-cost traces, and creating a rule for failures or slow requests. ## Data questions and SQL When Otter answers with a query, you see a collapsible **SQL Query** block, **Copy** / **Execute** / **Running...**, and results you can view as **Table**, **Bar**, **Line**, or **Pie** (plus an auto **Stat Card** when it fits). Use **Save as Widget** to enter a required **Title**, choose a widget type (**Stat Card**, **Bar Chart**, **Line Chart**, **Pie Chart**, **Area Chart**, **Table**), and optionally add it to a dashboard. Queries are constrained to telemetry data (traces and metrics tables) in read-only mode with a result row cap - Otter is not a general-purpose database admin console. ## Resources and dashboards Ask Otter to create or list rules, contexts, prompts, Vault secrets, custom models, and related resources. Created entities show up as cards with links into the product. For dashboards, Otter can return an importable layout with **Import Dashboard**, **Download JSON**, and **Open Dashboard** after import. Vault key names are normalized to `UPPER_SNAKE_CASE` when Otter creates secrets. ## Analysis and prompt improvement You can ask Otter to analyze traces or spans (including by session or other attributes) or to review a Prompt Hub prompt by name or ID. Review-only asks suggest edits without saving; Otter only writes a new prompt version when you explicitly ask it to save, apply, update, publish, or create a version. For the telemetry-side UI of the same analysis pipeline, see [AI Analysis](/latest/openlit/observability/telemetry/ai-analysis). ## Cost on each message Per-message tokens and estimated cost appear on each reply; the conversation list can show cost when it is greater than zero. Hover the info control near the input for **Active Configuration** details (provider, model, and pricing). Those prices come from [Manage Models](/latest/openlit/costs/manage-models/overview). *** > **[Chat Settings](/latest/openlit/chat/settings)** > > Provider, model, and Vault API key > **[Otter usage](/latest/openlit/chat/usage)** > > Aggregate tokens and cost across Otter features # Otter AI Copilot for Observability Source: https://docs.openlit.io/latest/openlit/chat/overview Otter is OpenLIT's AI chat assistant for observability — ask natural language questions about traces, costs, and tokens. **Otter** is OpenLIT's AI chat assistant for observability data. Ask questions in plain language about traces, costs, and tokens; create dashboards, rules, prompts, and Vault secrets through conversation; and run the same AI analysis you get from the telemetry detail views. Product copy describes it as: *Ask questions about your observability data using natural language*. Open Otter from the **Otter** mode toggle in the sidebar (`/chat`), or from the floating Otter button on other playground pages (hidden while you are already on `/chat*`). Configure a provider first under [Chat Settings](/latest/openlit/chat/settings), then start chatting. Track spend on the [Otter usage](/latest/openlit/chat/usage) page. Trends for narrow phrases like "AI copilot for observability" are low-volume; broader interest sits around AI chat assistants and natural-language querying - Otter is the observability-native version of that workflow. > **[Chat with Otter](/latest/openlit/chat/conversations)** > > Conversations, SQL results, widgets, and resource actions > **[Chat Settings](/latest/openlit/chat/settings)** > > Pick AI Provider, Model, and a Vault API key > **[Otter usage](/latest/openlit/chat/usage)** > > Token and cost attribution by feature, provider, model, and date > **[AI Analysis](/latest/openlit/observability/telemetry/ai-analysis)** > > Trace and span analysis Otter can also run from chat ## What Otter can do * **Natural language data questions** - Otter turns questions into read-only SQL against your telemetry tables and shows results as tables or charts. * **Save as Widget** - turn a query result into a dashboard widget. * **Resource management** - create and manage rules, contexts, prompts, Vault secrets, and custom models through conversation. * **Dashboard generation** - describe a dashboard and import the generated layout. * **Trace / span analysis and prompt improvement** - same capabilities as the telemetry AI Analysis and Prompt Hub Otter flows, reachable from chat. ## Get started **1. Configure Chat Settings** Open `/chat/settings` (gear in the Otter sidebar). Choose **AI Provider**, **Model**, and **API Key (from Vault)**, then **Save Configuration** (or **Update Configuration** if one already exists). Store the key in [Vault](/latest/openlit/developer-resources/vault/overview) first, or use **Create new** on the settings form. **2. Start a conversation** Open `/chat`, click **New Chat**, and try an empty-state example such as analyzing slow traces from the last 24 hours or breaking down token usage and cost by model. **3. Review usage** Open **Otter usage** (`/chat/usage`) to see tokens and cost by provider, model, and where Otter was used (chat, trace analysis, span analysis, prompt improvement). ## Frequently asked questions > **Do I need Chat Settings before I can talk to Otter?** > > Yes. Until a provider, model, and Vault API key are saved, Otter prompts you to configure Chat Settings. > **Can Otter change my data or run destructive SQL?** > > Query tools run read-only against an allowlisted set of telemetry tables. Resource tools only change platform entities (prompts, rules, and so on) when you ask Otter to create or update them. # Chat Settings Source: https://docs.openlit.io/latest/openlit/chat/settings Configure Otter's AI Provider, Model, and Vault API key on the Chat Settings page before using the AI chat assistant **Chat Settings** (`/chat/settings`) configures which model Otter uses. Page title: **Chat Settings**. Description: *Configure the AI provider for the chat feature*. Open it from the gear control in the Otter sidebar, or go directly to `/chat/settings`. ## Fields | Setting | Label in UI | Notes | | - | - | - | | Provider | **AI Provider** | Providers come from [Manage Models](/latest/openlit/costs/manage-models/overview) (built-in plus any you added). | | Model | **Model** | Models for the selected provider, including custom ones. Hint: *Models are managed in Manage Models. Custom models you add there will appear here.* | | API key | **API Key (from Vault)** | Pick a Vault secret, or use **Create new** to open **Create a new secret** (Key, Value, Tags) without leaving Settings. | Click **Save Configuration** the first time, or **Update Configuration** when editing an existing setup. Success toast: **Chat configuration saved**. You must fill all three fields. If no provider is selected yet, the model field prompts you to select a provider first. A **Configured** chip shows the current provider / model / vault key when settings are already saved. ## Related Models and prices are edited on Manage Models; secrets are stored in Vault. Otter only stores a reference to the Vault secret - the key is not sent to the browser as part of chat messages. *** > **[Manage Models](/latest/openlit/costs/manage-models/overview)** > > Add providers and models Otter can select > **[Vault](/latest/openlit/developer-resources/vault/overview)** > > Store the provider API key Otter should use # Otter usage Source: https://docs.openlit.io/latest/openlit/chat/usage Track Otter AI chat assistant token and cost attribution by feature, provider, model, and date **Otter usage** (`/chat/usage`) shows how much Otter has spent across features. Header: **Otter usage**. Description: *Token and cost attribution by feature, provider, model, and date.* ## What you see * Summary tiles: **Total tokens**, **Total cost**, **Prompt / completion**, **Otter actions** * Chat rollups: **Chat conversations**, **Chat messages**, **Avg tokens / chat**, **Avg cost / chat** * **Provider and model spend** - breakdown by provider and model * **Where Otter was used** - activity list with type labels: * **Chat** * **Trace analysis** * **Span analysis** * **Prompt improvement** Filter with the time tabs (**24H**, **7D**, **1M**, **3M**, **CUSTOM**) and click **Refresh** to reload. Empty state: **No Otter usage recorded yet** - *Run a chat or AI analysis to see provider, model, token, and cost attribution here.* ## Why it matters Otter calls your configured provider for chat, telemetry AI Analysis, and Prompt Hub improvements. This page is the single place to see that spend without digging through individual conversations. *** > **[Chat with Otter](/latest/openlit/chat/conversations)** > > Start conversations that show up in usage > **[Chat Settings](/latest/openlit/chat/settings)** > > Change provider or model to control future cost # Analytics Source: https://docs.openlit.io/latest/openlit/coding-agents/analytics The seeded Coding Agents dashboard - sessions, cost, acceptance, top tools and repos, and session outcomes, scoped per vendor or per user Every vendor row on the [Agents page](/latest/openlit/observability/agents/overview)'s Coding Agents tab opens to an **Overview** tab: a seeded, read-only dashboard scoped to that vendor and honoring the global time-range picker. The same dashboard is reused, scoped to a single user, when you click into a row on the [Users](/latest/openlit/coding-agents/users) tab. ## Widgets * **Stat cards**: Total sessions, Active users, Total cost, Total tool calls, Lines of code accepted, Acceptance %, Commits, Pull requests. * **Trends**: Sessions over time, Lines over time (accepted vs. rejected). * **Breakdowns**: Sessions by vendor, Top tools, Top models, Top repositories, Top users, Top users by accepted lines, Acceptance breakdown, Session outcomes (merged, committed, abandoned, cancelled), Edit decisions (auto-applied vs. user-reviewed), MCP servers in use. Clicking **Top users** or **Top repositories** drills into that user's or repository's own scoped view of the same dashboard. ## How the numbers are computed * **Acceptance %** is computed from edit-decision counts - `accept` and `auto_accepted` decisions against `reject` decisions - not from a raw lines-changed ratio. * **Commits** and **Pull requests** are detected heuristically from shell/Bash tool calls (`git commit`, `gh pr ...`), since this isn't a native VCS integration. Treat them as a strong signal rather than an exact count. * Every widget reads the same `{{filter.timeLimit.start}}` / `{{filter.timeLimit.end}}` bindings as the rest of OpenLIT's dashboards, so changing the time range picker updates the whole board consistently. ## Query the same data yourself The Overview dashboard reads from the same `otel_traces` table as everything else in OpenLIT, so you can build your own [custom dashboard widgets](/latest/openlit/dashboards/overview) against it: ```sql theme={"theme":{"light":"github-light","dark":"github-dark"}} SELECT * FROM otel_traces WHERE SpanName IN ( 'coding_agent.session', 'coding_agent.edit.decision', 'coding_agent.git.commit', 'coding_agent.git.pull_request' ) ``` The metrics pipeline emits the same rollups as counters, if you'd rather point a Prometheus/Mimir/Datadog backend at OpenLIT's OTLP endpoint directly: `coding_agent.session.count`, `coding_agent.session.duration`, `coding_agent.tool.call.count`, `coding_agent.lines_of_code.count`, `coding_agent.edit.decision.count`, `coding_agent.commit.count`, `coding_agent.pull_request.count`. *** > **[Sessions](/latest/openlit/coding-agents/sessions)** > > Drill from an aggregate metric down to the individual sessions behind it > **[Users](/latest/openlit/coding-agents/users)** > > The same dashboard, scoped to one developer # Coding Agent Observability: Claude Code, Codex, Cursor Source: https://docs.openlit.io/latest/openlit/coding-agents/overview Coding agent observability for Claude Code, Cursor, Codex, Windsurf, and OpenCode — tokens, cost, tool calls, and outcomes with no SDK. OpenLIT ships first-class observability for AI coding agents. The `openlit` CLI ingests events from each vendor's hook system, normalizes them onto OpenTelemetry's `gen_ai.*` and OpenLIT's `coding_agent.*` semantic conventions, and ships them to your OpenLIT OTLP endpoint. The [Agents page](/latest/openlit/observability/agents/overview)'s Coding Agents tab detects them automatically - no controller, no SDK, no code change in your repos. > [!NOTE] > > You only need an OpenLIT instance and the `openlit` CLI on each developer's machine. The CLI talks **only** to your OpenLIT endpoint. There is no managed cloud for coding-agent telemetry. ## What you get > **[Setup & Configure](/latest/openlit/coding-agents/setup-and-configure)** > > Install the CLI, wire Claude Code, Cursor, Codex, or OpenCode, and stop tracking when you need to > **[Analytics](/latest/openlit/coding-agents/analytics)** > > The seeded dashboard - sessions, cost, acceptance, top tools and repos, session outcomes > **[Sessions](/latest/openlit/coding-agents/sessions)** > > Every coding session, with a full conversational drill-in per session > **[Users](/latest/openlit/coding-agents/users)** > > Per-developer rollups of activity, cost, and code impact > **[Privacy & Governance](/latest/openlit/coding-agents/privacy-and-governance)** > > Content capture modes, redaction tiers, and attribution safeguards > **[Troubleshooting](/latest/openlit/coding-agents/troubleshooting)** > > Diagnose a missing row, silent hooks, or a stale database config > **[Scanner connectors](/latest/openlit/connectors/scanner)** > > Scan the GitHub repo from `vcs.repository.url.full` with Trustabl and review findings on the session ## Prerequisites * A running OpenLIT instance - see [Installation](/latest/openlit/installation) if you don't have one. * An OpenLIT API key (Settings → API Keys). * The OTLP endpoint of your OpenLIT instance (defaults to `http://localhost:4318` for local installs). # Privacy & Governance Source: https://docs.openlit.io/latest/openlit/coding-agents/privacy-and-governance Content capture modes, redaction, and attribution safeguards for coding-agent telemetry Coding-agent sessions can carry sensitive material - prompts, file contents, proprietary code. OpenLIT gives you control over how much of that gets captured, and applies redaction regardless of the mode you choose. ## Content capture modes `OPENLIT_CODING_CONTENT_CAPTURE` controls what lands on spans: | Mode | Identifiers | Tool names + paths | File diffs / message bodies | | - | - | - | - | | `minimal` | ✅ | ❌ | ❌ | | `metadata_only` | ✅ | ✅ | ❌ | | `full` (default) | ✅ | ✅ | ✅ | Set it once via `openlit configure` or the `OPENLIT_CODING_CONTENT_CAPTURE` environment variable - see [Setup & Configure](/latest/openlit/coding-agents/setup-and-configure). `full` is the default so the trace detail view is useful out of the box; switch to `metadata_only` when rolling out across a team where prompts may carry confidential material, or `minimal` if you only need cost and activity dashboards and not the per-event timeline. Sessions captured under `minimal` or `metadata_only` show a banner in their [session detail view](/latest/openlit/coding-agents/sessions) explaining what wasn't recorded. ## Redaction Two redaction tiers run on every export, regardless of capture mode: * **Tier 1** - a token-pattern scrubber that always runs, stripping API keys, tokens, and known secret patterns without breaking JSON structure. * **Tier 2** - a body-scope scrubber, active only in `full` mode, that applies the same scrubbing to file diffs and message bodies before they're exported. The active capture mode is stamped as a resource attribute (`coding_agent.content_capture_mode`) on every span, so you can audit what a given session was recorded under after the fact. ## Trace Governance Passport Coding-agent sessions also appear in the product **Trace Governance** view: open a session or request trace → hierarchy explorer → **Governance** tab. That report surfaces permission mode, capture mode, classification, agent loops, and related findings, with optional JSON passport export for evidence packages. See [Trace Governance](/latest/openlit/observability/telemetry/trace-governance). ## Attribution safeguards Per-user attribution is capped by a cohort floor: viewer-tier accounts cannot see per-user metrics for a user with fewer than five sessions in the selected window. Admin-tier accounts see the full breakdown. This prevents singling out an individual developer from a small sample on the [Users](/latest/openlit/coding-agents/users) tab. ## Threat model The `openlit` CLI runs on the developer's own machine, alongside the coding agent, and authenticates to your collector with an org-scoped API key - the header proves the event came from a machine your org authorized, not that a specific event wasn't spoofed by anyone with shell access on that machine. In practice this is the same trust boundary as any other locally-run developer tool: someone who can run the hook can equally well run any other process under their own user. If you need a stricter guarantee for an untrusted developer fleet, front the OTLP endpoint with a collector that validates the authenticated key's owner against the event's user attribute, or disable the hook tier entirely and rely on a vendor's native OTel exporter (where the vendor signs its own egress) instead. *** > **[Troubleshooting](/latest/openlit/coding-agents/troubleshooting)** > > Diagnose missing rows, silent hooks, or a stale database config # Sessions Source: https://docs.openlit.io/latest/openlit/coding-agents/sessions Every coding session for a vendor, with a full conversational drill-in per session The **Sessions** tab on a coding agent's detail page lists every individual coding session for that vendor, one row per `coding_agent.session.id` (folded up to its chat thread when subagents are linked in). ## Sessions list Columns: Session, User, Started, Duration, Model, Tool calls, Code changed (`+added/-removed`, with accepted/rejected broken out in a tooltip), Acceptance %, Commits, Pull Requests, Tokens, Cost, and Outcome. ## Session detail Clicking a session opens the same trace detail view used for [Traces](/latest/openlit/observability/telemetry/traces), rooted at that session. It defaults to the **Chat** view since a conversational read fits an IDE/CLI session better than a span tree - you'll see prompts, tool calls, and edits laid out like a transcript, with the [AI Analysis](/latest/openlit/observability/telemetry/ai-analysis), Timeline, and Graph views still available if you want a different angle. The header adds session-specific meta pills on top of the usual Duration, Tokens, Cost, and Model stats: Coding Agent (vendor), User, Working Folder, Repository, Branch, Terminal, Commits, Lines added/deleted, Acceptance, Pull Requests, and Outcome. Use **Prev / Next** to step through sessions without returning to the list. > [!NOTE] > > If a session was captured under the `minimal` or `metadata_only` content-capture mode, the detail view shows a banner explaining that prompt bodies and tool I/O weren't recorded for that session - see [Privacy & Governance](/latest/openlit/coding-agents/privacy-and-governance). *** > **[Analytics](/latest/openlit/coding-agents/analytics)** > > Zoom back out to vendor-wide trends and breakdowns # Setup & Configure Source: https://docs.openlit.io/latest/openlit/coding-agents/setup-and-configure Install the openlit CLI, wire each vendor's hooks, then configure the endpoint, content capture, and stopping tracking ## Setup **1. Install the CLI** Pick the path that matches your OS. All paths install the same `openlit` binary built by `release-cli.yml`. ```bash macOS theme={"theme":{"light":"github-light","dark":"github-dark"}} brew install openlit/openlit/openlit ``` ```bash Linux theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -fsSL https://raw.githubusercontent.com/openlit/openlit/main/cli/scripts/install.sh | sh ``` ```powershell Windows theme={"theme":{"light":"github-light","dark":"github-dark"}} iwr -useb https://raw.githubusercontent.com/openlit/openlit/main/cli/scripts/install.ps1 | iex ``` ```bash docker theme={"theme":{"light":"github-light","dark":"github-dark"}} docker run --rm -it ghcr.io/openlit/openlit-cli:latest --help ``` The Linux and Windows scripts install to `~/.openlit/bin/openlit` and `%USERPROFILE%\.openlit\bin\openlit.exe` respectively. The Linux script prints a PATH-add hint if the directory is not already on `$PATH`; the Windows script updates user-scope PATH automatically (open a new terminal to pick it up). If you prefer to build from source: `go install github.com/openlit/openlit/cli/cmd/openlit@latest`. **2. Wire each vendor** `openlit coding install` writes the right hook config for the agent you pass and is fully idempotent. You can also point at the [plugin marketplace](https://github.com/openlit/openlit/tree/main/plugins) and let the agent pull the manifest itself, or invoke the agent through `openlit coding launch` which auto-installs and execs. **Claude Code** ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} # One-time install openlit coding install --vendor=claude-code # Or run Claude Code through the launcher (auto-installs) openlit coding launch claude-code ``` Manifest path: `~/.claude/plugins/openlit-cc/`. Hooks wired: `SessionStart`, `UserPromptSubmit`, `PreToolUse`, `PostToolUse`, `Stop`, `SubagentStop`, `SessionEnd`. Claude Code's transcript JSONL (`transcript_path` in the hook payload) is tailed on `SessionStart` for early model attribution and on `SessionEnd` for authoritative token usage and cost. **Optional: Claude Code's native OpenTelemetry exporter.** If you'd rather not install the plugin, or you want a second signal source for cross-checking, you can point Claude Code's built-in OTel exporter at your OpenLIT OTLP endpoint instead. Both paths can run side by side - OpenLIT dedupes per `session.id` (Claude Code's authoritative cost/tokens from the native path win, while the hook path provides the repository/working-folder context that Claude Code itself can't see). ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} export CLAUDE_CODE_ENABLE_TELEMETRY=1 export OTEL_METRICS_EXPORTER=otlp export OTEL_LOGS_EXPORTER=otlp export OTEL_EXPORTER_OTLP_PROTOCOL=grpc export OTEL_EXPORTER_OTLP_ENDPOINT=$OPENLIT_OTLP_ENDPOINT export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer $OPENLIT_API_KEY" # Optional: also enable traces (beta - gives per-tool / per-LLM-turn spans) export CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1 export OTEL_TRACES_EXPORTER=otlp ``` See [Claude Code's monitoring docs](https://code.claude.com/docs/en/monitoring-usage) for the full environment-variable surface (managed settings, dynamic headers, cardinality controls, content-capture gates). When both paths are active, OpenLIT folds them into one chat thread automatically. **Cursor** ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} openlit coding install --vendor=cursor ``` Manifest path: `~/.cursor/hooks.json` (user scope - applies to every workspace you open in Cursor; works the same on macOS, Linux, and Windows). All Cursor hook events (sessionStart/End, prompt/response/thought, tool + shell + MCP phases, subagent lifecycle, file edits, preCompact) invoke `openlit coding hook --vendor=cursor --event=` directly. At install time the `openlit` token in each `command` is rewritten to the absolute path of the CLI binary that ran `openlit coding install`, which sidesteps the minimal-PATH that GUI-launched Cursor windows inherit on macOS and Linux, and makes the same manifest work on Windows where there is no shell wrapper. > [!NOTE] > > We merge into `~/.cursor/hooks.json` (user scope) rather than drop a plugin tree elsewhere because Cursor only auto-discovers plugins that were registered through its in-app `/add-plugin` flow. User-scope hooks are the supported way to install agent-wide hooks without an in-app step, and re-running `openlit coding install --vendor=cursor` is idempotent: prior openlit entries are dropped before the new ones are appended, and any third-party entries you (or another tool) have added are left in place. **Codex** ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} openlit coding install --vendor=codex ``` Manifest path: `~/.codex/plugins/openlit/`. Hooks wired: `SessionStart`, `UserPromptSubmit`, `PreToolUse`, `PostToolUse`, `Stop`. Codex scopes every hook to a `turn_id` so we emit one `coding_agent.llm.turn` span per turn (built on `Stop`), with the prompt, last-assistant message, and the per-tool calls produced during that turn folded into the OTel-canonical `gen_ai.input.messages` / `gen_ai.output.messages` envelopes. The CLI tails `~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl` on `Stop` to compute the per-turn token delta (input, output, cached, reasoning) from Codex's running `token_count` events, so cost rollups in OpenLIT reflect the same per-turn deltas Codex itself records. Subagent linkage is read from the transcript's `session_meta` record at `SessionStart`; child turns inherit `coding_agent.agent.parent_id` automatically so the UI folds them under the spawning chat. Codex doesn't expose a `SessionEnd` event, so the session row updates incrementally as each `Stop` lands. If `codex features list` doesn't already enable `codex_hooks` on your build, add this to `~/.codex/config.toml`: ```toml theme={"theme":{"light":"github-light","dark":"github-dark"}} [features] codex_hooks = true [plugins."openlit"] enabled = true ``` Older Codex builds use `hooks = true` and `plugin_hooks = true` instead of `codex_hooks`. **OpenCode** ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} openlit coding install --vendor=opencode # Or install and launch OpenCode in one step openlit coding launch opencode ``` Plugin path: `$XDG_CONFIG_HOME/opencode/plugins/openlit.ts` when `XDG_CONFIG_HOME` is set, otherwise `/.config/opencode/plugins/openlit.ts`. OpenCode [automatically loads global JavaScript and TypeScript plugins](https://opencode.ai/docs/plugins/#from-local-files) from this directory at startup, so restart OpenCode after installation. The plugin projects session lifecycle, completed-assistant metadata, and tool execution into `openlit coding hook --vendor=opencode`. OpenLIT emits canonical LLM-turn and tool-call spans with `coding_agent.client=opencode`. Provider-executed tools, which bypass OpenCode's explicit tool hooks, are recovered from terminal tool-part metadata and deduplicated by call ID. OpenCode does not expose a terminal `SessionEnd` hook: `session.idle` is a turn boundary and can also follow errors or cancellation. OpenLIT therefore does not fabricate a completed session root. In `metadata_only` and `full`, it records lifecycle events as they arrive. In `minimal`, it emits non-terminal `coding_agent.session.snapshot.start` / `coding_agent.session.snapshot` spans; token and cost values are per-snapshot deltas, while duration and tool count are cumulative. None of these snapshots carries `coding_agent.session.outcome`. OpenCode message text, reasoning text, system messages, and structured parts are never forwarded. Explicit tool arguments and results are included only when `OPENLIT_CODING_CONTENT_CAPTURE=full`; provider-executed fallback events contain terminal metadata only. Re-running the install is idempotent and replaces only `openlit.ts`; other files in your global OpenCode plugin directory are preserved. **3. Verify** Run any short coding-agent session, then refresh the [Agents page](/latest/openlit/observability/agents/overview) in your OpenLIT UI. You should see a new row labelled with the vendor and a "Coding" badge. Click in for the dedicated Overview / Sessions / Users tabs. ## Configure Everything below is a setting you can change at any point after setup - none of it needs to happen up front. ### Endpoint, API key, and content capture The fastest path is `openlit configure` - it answers the same questions a CI / Helm chart would set via env. The result is written to `~/.config/openlit/config.env` (Linux/macOS) or `%APPDATA%\openlit\config.env` (Windows). For headless / fleet rollouts, prefer env vars or flags directly - they take precedence over the file (flags > env > config file). ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} openlit configure \ --endpoint https://openlit.example.com:4318 \ --api-key $OPENLIT_API_KEY ``` Equivalent environment variables (precedence: flags > env > config file): | Variable | Purpose | | - | - | | `OPENLIT_OTLP_ENDPOINT` | OTLP/HTTP endpoint of your OpenLIT receiver (`:4318`) | | `OPENLIT_API_KEY` | Authenticates the OTLP exporter | | `OPENLIT_USER` | Optional override for `gen_ai.user.name` (email or login) | | `OPENLIT_CODING_CONTENT_CAPTURE` | `minimal` \| `metadata_only` \| `full` *(default: `full`)* - see [Privacy & Governance](/latest/openlit/coding-agents/privacy-and-governance) | Standard `OTEL_EXPORTER_OTLP_*` variables are honoured as fallbacks so existing OTel users can reuse their setup. Re-run `openlit configure` (or update the env vars) any time - changes apply to the next session, no reinstall of the vendor hooks needed. ### Stop tracking / uninstall The inverse of `openlit coding install`. Removes the per-vendor host plugin manifests written by install, deregisters the plugin from the vendor's own CLI where applicable (Claude Code, Codex), and leaves your shared config alone by default so you can re-onboard without re-entering credentials. ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} openlit coding uninstall --vendor=cursor # one vendor openlit coding uninstall --vendor=all # every vendor at once openlit coding uninstall --vendor=all --purge # also drop ~/.config/openlit + session-state cache openlit coding uninstall --vendor=cursor --dry-run # preview without touching disk ``` What each flag removes: | Path | Removed by `--vendor=` | Removed by `--purge` | | - | - | - | | `~/.claude/plugins/openlit-cc/` | ✓ (when `` is `claude-code` or `all`) | - | | `~/.local/share/openlit/claude-marketplace/` | ✓ (when `` is `claude-code` or `all`) | - | | openlit entries in `~/.cursor/hooks.json` (other tools' entries preserved; file deleted if it ends up empty) | ✓ (when `` is `cursor` or `all`) | - | | `~/.local/share/openlit/codex-marketplace/` | ✓ (when `` is `codex` or `all`) | - | | `$XDG_CONFIG_HOME/opencode/plugins/openlit.ts` when set; otherwise `/.config/opencode/plugins/openlit.ts` | ✓ (when `` is `opencode` or `all`) | - | | `claude plugin uninstall openlit-cc@openlit` | ✓ best-effort | - | | `codex plugin remove openlit@openlit` + `codex plugin marketplace remove openlit` | ✓ best-effort | - | | `~/.config/openlit/` (your `configure` output) | - | ✓ | | `/openlit/sessions/` (session-state cache) | - | ✓ | The `openlit` binary itself is **not** touched. Uninstall it the way you installed it: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} brew uninstall openlit # Homebrew rm ~/.openlit/bin/openlit # curl|sh installer rm $(go env GOPATH)/bin/openlit # go install ``` If you only want to pause a vendor temporarily rather than uninstall it, re-run `openlit coding install --vendor=` later - it's idempotent and will re-wire the same manifest. *** > **[Troubleshooting](/latest/openlit/coding-agents/troubleshooting)** > > No row showing up, or hooks running with no spans landing? Start here. # Troubleshooting Source: https://docs.openlit.io/latest/openlit/coding-agents/troubleshooting Diagnose a missing row, silent hooks, or a stale database config for coding-agent telemetry ## First stop: run `openlit doctor` It prints the resolved config, the cached session count, the installed plugin paths, and TCP-dials your OTLP endpoint. Exit code `1` means at least one failure; `0` (possibly with warnings) means the CLI is wired up correctly. ## No row on the Agents page The hook never writes to a log file - it emits to stderr, which is captured by the host coding agent's own logs (Claude Code: `~/.claude/logs/`; Cursor: Developer Tools → Console; Codex: `~/.codex/`). For OpenCode, first run `openlit doctor` and confirm it detects `$XDG_CONFIG_HOME/opencode/plugins/openlit.ts` when `XDG_CONFIG_HOME` is set, or `/.config/opencode/plugins/openlit.ts` otherwise. Set `OPENLIT_DEBUG_PAYLOAD_DIR=/tmp/openlit-debug` to capture the exact JSON the vendor is sending the hook - useful when fields appear to be missing on the resulting spans. ## Hooks ran but no spans landed Confirm `openlit configure --show` reports the right endpoint. The CLI never reads `~/.zshrc`; if you set `OPENLIT_*` in your shell rc, source it before launching the agent. ## "Database config not found" on the Agents page Your OpenLIT installation hasn't run the latest migrations. Restart the `openlit` container or run the migration manually (`pnpm --filter client run migrate`). *** > **[Setup & Configure](/latest/openlit/coding-agents/setup-and-configure)** > > Re-check install, wiring, and endpoint configuration # Users Source: https://docs.openlit.io/latest/openlit/coding-agents/users Per-developer rollups of coding-agent activity, cost, and code impact The **Users** tab on a coding agent's detail page lists every developer who has used that vendor, with per-user rollups: Sessions, Tool calls, Cost, Tokens, Lines added (accepted/rejected in a tooltip), Acceptance %, and Commits. Sort by Last seen, Sessions, Tool calls, Cost, or Tokens. Clicking a user opens the same [Analytics](/latest/openlit/coding-agents/analytics) dashboard used at the vendor level, scoped down to that one developer - so you get the identical stat cards, trends, and breakdowns, just filtered to their activity. > [!NOTE] > > Per-user attribution below a five-session cohort floor is hidden from viewer-tier accounts to avoid singling out low-activity individuals from small samples; admin-tier accounts see the full breakdown. *** > **[Sessions](/latest/openlit/coding-agents/sessions)** > > See the individual sessions behind any user's rollup # Configuration Source: https://docs.openlit.io/latest/openlit/configuration Configuring Options for OpenLIT This guide covers environment variables for installing and fine-tuning OpenLIT. > [!NOTE] > > **ClickHouse connection at runtime is a [Database Config](/latest/openlit/organisation/database-config), not a live env lookup.**\ > `INIT_DB_*` variables **seed** the first Database Config when OpenLIT is first set up. Day-2 host, password, or environment changes belong in **Organisation → Database Config** and **Connectors**. See also [Connectors](/latest/openlit/connectors/overview) and [Signal routing](/latest/openlit/organisation/signal-routing). ## ClickHouse bootstrap (`INIT_DB_*`) Used once at seed/install time to create the default Database Config. After that, the UI (and APIs under `/api/db-config`) own the connection. | Env var | Database Config field | | - | - | | `INIT_DB_HOST` | Host | | `INIT_DB_PORT` | Port | | `INIT_DB_DATABASE` | Database | | `INIT_DB_USERNAME` | Username | | `INIT_DB_PASSWORD` | Password | ## Environment variables **INIT\_DB\_HOST** *required* Seed-only: host address written into the default Database Config on first setup. Not read on every request after the config exists. **Example**: ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} export INIT_DB_HOST=127.0.0.1 ``` **INIT\_DB\_PORT** *required* Seed-only: ClickHouse HTTP port for the default Database Config (commonly `8123`). **Example**: ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} export INIT_DB_PORT=8123 ``` **INIT\_DB\_DATABASE** *required* Seed-only: ClickHouse database name for the default Database Config. **Example**: ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} export INIT_DB_DATABASE=default ``` **INIT\_DB\_USERNAME** *required* Seed-only: username stored on the default Database Config. **Example**: ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} export INIT_DB_USERNAME=default ``` **INIT\_DB\_PASSWORD** *required* Seed-only: password stored on the default Database Config. Rotate later via the Database Config UI, not by relying on this env var alone. **Example**: ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} export INIT_DB_PASSWORD=default ``` **SQLITE\_DATABASE\_URL** *required* Sets the location where SQLITE data is stored. **Example**: ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} export SQLITE_DATABASE_URL=file:/app/client/data/data.db ``` ## OAuth authentication variables > [!NOTE] > > For detailed OAuth setup instructions, see the [OAuth Authentication Setup](/latest/openlit/oauth) guide. **NEXTAUTH\_URL** Sets the canonical URL of your site for NextAuth.js authentication **Example**: ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} export NEXTAUTH_URL=http://localhost:3000 ``` **NEXTAUTH\_SECRET** Used to encrypt the NextAuth.js JWT tokens and email verification hashes **Example**: ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} export NEXTAUTH_SECRET=your-secret-here ``` **Generate with**: `openssl rand -base64 32` **GOOGLE\_CLIENT\_ID** Google OAuth client ID for Google sign-in integration **Example**: ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} export GOOGLE_CLIENT_ID=your-google-client-id ``` **GOOGLE\_CLIENT\_SECRET** Google OAuth client secret for Google sign-in integration **Example**: ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} export GOOGLE_CLIENT_SECRET=your-google-client-secret ``` **GITHUB\_CLIENT\_ID** GitHub OAuth client ID for GitHub sign-in integration **Example**: ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} export GITHUB_CLIENT_ID=your-github-client-id ``` **GITHUB\_CLIENT\_SECRET** GitHub OAuth client secret for GitHub sign-in integration **Example**: ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} export GITHUB_CLIENT_SECRET=your-github-client-secret ``` ## Server variables **PORT** Sets the port the OpenLIT server listens on. For **Docker Compose** deployments, set this in the `.env` file next to `docker-compose.yml` - Compose maps it to both the host port and the container's internal `DOCKER_PORT` for you. For **Kubernetes** or a raw container run, set `DOCKER_PORT` directly instead (see below); the container's entrypoint always derives its actual listening port from `DOCKER_PORT`, defaulting to `3000` if unset. **Example** (Docker Compose `.env`): ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} PORT=3000 ``` **DOCKER\_PORT** Sets the port the OpenLIT container listens on internally. Only relevant for Kubernetes or a raw container run where there's no Docker Compose translating `PORT` for you. **Example**: ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} export DOCKER_PORT=3000 ``` **API\_URL** Sets the base URL OpenLIT uses to call back into its own API - required by the Auto Evaluation, Auto Pricing, Agents materialization, and telemetry-snapshot cron jobs, which run as separate processes and call this URL directly. Defaults to `http://localhost:$PORT`. Set this if you run behind a reverse proxy, a non-default host, or a different container/pod hostname - otherwise these background jobs will fail silently after every restart. **Example**: ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} export API_URL=https://openlit.internal.example.com ``` **OPENLIT\_EDITION** Sets which OpenLIT build is running. Defaults to `oss` if unset. **Example**: ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} export OPENLIT_EDITION=oss ``` ## Agents variables Tune the background job that materializes the [Agents](/latest/openlit/observability/agents/overview) page's call graphs and versions from trace data. **AGENTS\_MATERIALIZE\_SCHEDULE** Cron schedule for the Agents materialization job. Defaults to `* * * * *` (every minute); the job self-throttles when there's no new trace data to process. **Example**: ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} export AGENTS_MATERIALIZE_SCHEDULE="* * * * *" ``` **AGENTS\_MATERIALIZE\_MAX\_PER\_TICK** Maximum number of agents materialized per scheduled run. Defaults to `100`. **Example**: ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} export AGENTS_MATERIALIZE_MAX_PER_TICK=100 ``` **AGENTS\_MATERIALIZE\_PARALLEL** Maximum number of agents materialized concurrently per run. Defaults to `4`. **Example**: ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} export AGENTS_MATERIALIZE_PARALLEL=4 ``` **AGENTS\_LOG\_LEVEL** Log level for the Agents materialization job (`debug`, `info`, `warn`, `error`). Defaults to `info`. **Example**: ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} export AGENTS_LOG_LEVEL=info ``` **AGENTS\_LOG\_STACK** Set to `false` to omit stack traces from Agents materialization error logs. Defaults to including them. **Example**: ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} export AGENTS_LOG_STACK=false ``` ## Realtime variables Realtime evaluation powers [threshold alerts](/latest/openlit/alerts#threshold-alerts). The OTLP receiver publishes each GenAI span sent with an API key to NATS JetStream, and the bundled `openlit-engine` evaluates every enabled alert and records breaches, which appear under **Alerts**. Docker Compose includes a NATS service by default. Realtime is disabled when `NATS_URL` is unset. **NATS\_URL** NATS JetStream server URL. When set, the receiver publishes realtime signals and the container starts the realtime engine. **Example**: ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} export NATS_URL="nats://nats:4222" ``` **NATS\_RECEIVER\_USER / NATS\_RECEIVER\_PASSWORD** NATS user for the OTLP receiver. Docker Compose uses `openlit-receiver`, which [`assets/nats.conf`](https://github.com/openlit/openlit/blob/main/assets/nats.conf) only allows to publish signals and manage the signal stream. `NATS_RECEIVER_TOKEN` and `NATS_RECEIVER_CREDS` are also accepted. **NATS\_ENGINE\_USER / NATS\_ENGINE\_PASSWORD** NATS user for the realtime engine. Docker Compose uses `openlit-engine`, which can only read the signal stream. `NATS_ENGINE_TOKEN` and `NATS_ENGINE_CREDS` are also accepted. `NATS_USER`, `NATS_PASSWORD`, `NATS_TOKEN`, and `NATS_CREDS` are used as a shared fallback for both. > [!WARNING] > > Docker Compose ships placeholder passwords (`openlit-receiver-change-me`, `openlit-engine-change-me`). Set `NATS_RECEIVER_PASSWORD` and `NATS_ENGINE_PASSWORD` for any deployment other people can reach. The same values are passed to the NATS server and to OpenLIT. **NATS\_TLS\_CA / NATS\_TLS\_CERT / NATS\_TLS\_KEY / NATS\_TLS\_REQUIRED** TLS for NATS: a CA bundle, an optional client certificate and key for mutual TLS, and `NATS_TLS_REQUIRED=true` to refuse plaintext connections. Use TLS whenever NATS runs outside the OpenLIT container network. **OPENLIT\_ENGINE\_SECRET** Shared secret between the UI server and the realtime engine, sent as `X-OpenLIT-Engine-Secret`. The Docker image generates one per install and stores it in the data volume. Outside Docker, set the same value for both processes; the engine refuses to start without it, and the internal realtime routes reject every request. It is separate from `CRON_JOB_SECRET`. **OPENLIT\_SIGNAL\_STREAM\_MAX\_BYTES / OPENLIT\_SIGNALS\_MAX\_AGE** Size cap (default 256 MiB) and retention (default `10m`) of the signal stream. The oldest signals are discarded first. **OPENLIT\_ENGINE\_MAX\_WINDOWS\_PER\_RULE / OPENLIT\_ENGINE\_MAX\_WINDOWS\_PER\_TENANT / OPENLIT\_ENGINE\_MAX\_WINDOWS / OPENLIT\_ENGINE\_MAX\_SAMPLES\_PER\_WINDOW / OPENLIT\_ENGINE\_FINDINGS\_PER\_MIN** Engine limits: rule windows per alert (default `200`), per organisation/project/environment (default `1000`), and in total (default `50000`), samples per window (default `5000`), and findings each environment can open per minute (default `60`). Excess is dropped and counted. **OTLP\_STATS\_ADDR / OTLP\_TENANT\_CACHE\_MAX / OTLP\_AUTH\_FAIL\_BURST / OTLP\_AUTH\_FAIL\_PER\_MIN** Receiver hardening: the loopback listener for pipeline counters (default `127.0.0.1:4319`, `off` disables), the API key cache size (default `10000`), and how many rejected API keys a client IP may send (default burst `20`, refilling `60` per minute) before it receives `429`. **OPENLIT\_ENGINE\_URL** Base URL the UI uses to read live spans for an alert from the engine. Defaults to `http://127.0.0.1:4330`, which matches the engine inside the Docker container and `scripts/dev-realtime.sh`. **Example**: ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} export OPENLIT_ENGINE_URL="http://127.0.0.1:4330" ``` **ENGINE\_HTTP\_ADDR / OPENLIT\_URL** The engine's internal HTTP listener (default `127.0.0.1:4330`, which serves live spans to the UI) and the UI URL the engine reads alerts from and posts findings to (default `http://127.0.0.1:3000`; the Docker image sets it from the UI port). Keep `ENGINE_HTTP_ADDR` on loopback or a private network. **ENGINE\_RULES\_REFRESH / ENGINE\_TICK / ENGINE\_REPLAY** How often the engine reloads alerts (default `10s`), how often it evaluates every window (default `5s`), and how much of the signal stream it replays on start (default `10m`), as Go durations. **OPENLIT\_SIGNALS\_STREAM** JetStream stream name for signals (default `OPENLIT_SIGNALS`). The receiver and the engine must use the same name. **OPENLIT\_REALTIME\_FINDING\_RETENTION\_DAYS / OPENLIT\_REALTIME\_MAX\_OPEN\_FINDINGS** How long resolved threshold alert history is kept (default `30` days) and how many groups can be firing at once in each project environment (default `1000`). New groups beyond that limit are not recorded. **OPENLIT\_REALTIME\_WINDOW\_SEC / OPENLIT\_REALTIME\_MIN\_SAMPLES / OPENLIT\_REALTIME\_COOLDOWN\_SEC** Suggested window (default `300`, max `3600`), minimum spans (default `20`), and cooldown (default `900`) in the threshold alert templates. They only change the values a new alert starts with. **OPENLIT\_SIGNAL\_ATTRIBUTES** Personal span and resource attribute keys that realtime signals may carry. Comma-separated. Threshold alerts can filter and group by any span or resource attribute (up to 64 per span, values truncated to 256 bytes) without configuration, except keys that identify people or internal systems: any key with a `user`, `enduser`, `session`, `conversation`, `email`, `phone`, `ip`, `address`, or `path` segment (such as `user.id`, `gen_ai.conversation.id`, `url.path`, or `server.address`). Those are only copied when listed here. Prompt, completion, message, and secret attributes are always ignored, and values that look like credentials are redacted. **Example**: ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} export OPENLIT_SIGNAL_ATTRIBUTES="session.id,url.path" ``` **OPENLIT\_REALTIME\_ERROR\_RATE / OPENLIT\_REALTIME\_P95\_LATENCY\_MS / OPENLIT\_REALTIME\_TOKENS\_PER\_WINDOW / OPENLIT\_REALTIME\_COST\_PER\_WINDOW\_USD** Suggested thresholds in the [threshold alert](/latest/openlit/alerts#threshold-alerts) templates: error rate per service (default `0.25`), p95 latency per model (default `30000`), total tokens per model per window (default `1000000`), and span-reported cost per model per window (default `25`). Nothing alerts until someone creates an alert. **Example**: ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} export OPENLIT_REALTIME_ERROR_RATE=0.1 ``` ### Realtime production checklist * Set strong `NATS_RECEIVER_PASSWORD` and `NATS_ENGINE_PASSWORD` values (or use NATS creds files), and keep NATS on a private network. Docker Compose does not publish NATS ports. * Enable TLS (`NATS_TLS_*`) when NATS is not on the same host or container network. * Keep `OPENLIT_ENGINE_SECRET` and `CRON_JOB_SECRET` distinct and secret, and keep `ENGINE_HTTP_ADDR` and `OTLP_STATS_ADDR` on loopback. * Set `OTLP_REQUIRE_API_KEY=true` so every span is attributed to a project. * Leave personal-data attributes out of `OPENLIT_SIGNAL_ATTRIBUTES` unless rules need them. ## Telemetry variables > [!NOTE] > > See [Anonymous Telemetry](/latest/openlit/developer-resources/anonymous-telemetry) for what OpenLIT's own usage telemetry collects and why. **TELEMETRY\_ENABLED** Set to `false` to disable OpenLIT's anonymous usage telemetry, including the daily instance snapshot. **Example**: ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} export TELEMETRY_ENABLED=false ``` **TELEMETRY\_SNAPSHOT\_SCHEDULE** Cron schedule for the daily anonymous instance telemetry snapshot. Defaults to `17 3 * * *`. **Example**: ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} export TELEMETRY_SNAPSHOT_SCHEDULE="17 3 * * *" ``` ## Security variables OpenLIT enables stricter API protections by default, including security response headers, CSRF checks for browser session API requests, vault secret encryption, and restricted CORS for the vault secrets API. **CRON\_JOB\_SECRET** Token required to trigger internal cron-driven endpoints (Auto Evaluation, Auto Pricing, Agents materialization, telemetry snapshot). **Optional for typical self-hosted installs** - when unset, both the cron scripts and the endpoint check fall back to the same shared default, so scheduled jobs work out of the box with no configuration. Set it only if your instance is exposed such that an untrusted party could otherwise call these endpoints directly. Changing this takes effect on the next restart, since cron entries are re-created from the current environment on every server startup. **Example**: ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} export CRON_JOB_SECRET=your-cron-secret ``` **OPENLIT\_REQUIRE\_ORG\_FILTER** Set to `true` to enforce organisation-scoped isolation on coding-agent telemetry queries in multi-organisation deployments. **Example**: ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} export OPENLIT_REQUIRE_ORG_FILTER=true ``` **OPENLIT\_VAULT\_ENCRYPTION\_KEY** Secret used to encrypt Vault values at rest with AES-256-GCM. If this is not set, OpenLIT falls back to `NEXTAUTH_SECRET`. Use a stable, high-entropy value and keep it unchanged across restarts. Changing this value after secrets are encrypted prevents existing Vault values from being decrypted. **Generate with**: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} openssl rand -base64 32 ``` **Example**: ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} export OPENLIT_VAULT_ENCRYPTION_KEY=your-vault-encryption-key ``` **OPENLIT\_ALLOWED\_CORS\_ORIGINS** Comma-separated list of browser origins that are allowed to call API-key authenticated Vault secret retrieval from another domain. Configure this when a browser application hosted on a different origin needs to call `POST /api/vault/get-secrets`. Server-to-server SDK or REST calls usually do not need this because they do not send a browser `Origin` header. Specify complete origins, including scheme and host. Do not use `*`. **Example**: ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} export OPENLIT_ALLOWED_CORS_ORIGINS=https://app.example.com,https://admin.example.com ``` **OPENLIT\_ALLOWED\_ORIGINS** Backward-compatible alias for `OPENLIT_ALLOWED_CORS_ORIGINS`. **Example**: ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} export OPENLIT_ALLOWED_ORIGINS=https://app.example.com ``` > [!NOTE] > > `NEXTAUTH_URL` is also treated as an allowed same-site origin for Vault CORS checks. Browser requests from other domains must be listed in `OPENLIT_ALLOWED_CORS_ORIGINS` or `OPENLIT_ALLOWED_ORIGINS`. ## Environment file placement Environment variables can be configured in multiple ways depending on your deployment method: ### Development setup **1. Client-side .env** Create a `.env` file in the `src/client/` directory for development: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} src/client/.env ``` This file is automatically loaded by Next.js during development. **2. Docker Compose .env** Create a `.env` file in the same directory as your `docker-compose.yml` file: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} # In the root directory with docker-compose.yml .env ``` This file is automatically loaded by Docker Compose. **3. Development Docker Compose .env** For development Docker setup, create a `.env` file alongside `src/dev-docker-compose.yml`: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} # In the src/ directory with dev-docker-compose.yml src/.env ``` ### Production setup For production deployments, set environment variables directly in your hosting platform or container orchestration system (Kubernetes, Docker Swarm, etc.). ### Applying changes at runtime OpenLIT reads environment variables once when the server process starts - there's no hot-reload, so a config change (editing a `.env` file, updating a Kubernetes Secret, etc.) only takes effect after you restart the container or process. A restart is also all you need for the cron-driven features (Auto Evaluation, Auto Pricing, Agents materialization, telemetry snapshot): their scheduled jobs are re-created from the current environment on every startup, so there's no separate step to "re-save" settings after changing something like `API_URL`, `CRON_JOB_SECRET`, or `AGENTS_MATERIALIZE_SCHEDULE`. ## Sample environment file (.env) ```.env.example .env theme={"theme":{"light":"github-light","dark":"github-dark"}} # ClickHouse bootstrap ONLY — seeds the first Database Config at install. # Day-2 host/password/environment changes: Organisation → Database Config. INIT_DB_HOST="127.0.0.1" INIT_DB_PORT="8123" INIT_DB_DATABASE="default" INIT_DB_USERNAME="default" INIT_DB_PASSWORD="OPENLIT" SQLITE_DATABASE_URL="file:/app/client/data/data.db" # NextAuth Configuration (Optional) NEXTAUTH_URL="http://localhost:3000" NEXTAUTH_SECRET="your-secret-here" # OAuth Providers (Optional) GOOGLE_CLIENT_ID="your-google-client-id" GOOGLE_CLIENT_SECRET="your-google-client-secret" GITHUB_CLIENT_ID="your-github-client-id" GITHUB_CLIENT_SECRET="your-github-client-secret" # Server Configuration (Optional) # PORT is for Docker Compose; use DOCKER_PORT instead for Kubernetes or a raw container run PORT="3000" API_URL="http://localhost:3000" # Agents Materialization (Optional) AGENTS_MATERIALIZE_SCHEDULE="* * * * *" # Telemetry (Optional) TELEMETRY_ENABLED="true" # Security Configuration (Optional) OPENLIT_VAULT_ENCRYPTION_KEY="your-vault-encryption-key" OPENLIT_ALLOWED_CORS_ORIGINS="https://app.example.com,https://admin.example.com" CRON_JOB_SECRET="your-cron-secret" ``` *** > **[Create a dashboard](/latest/openlit/dashboards/overview)** > > Create custom visualizations with flexible widgets, queries, and real-time AI monitoring > **[Manage prompts](/latest/openlit/prompts-experiments/prompt-hub/overview)** > > Version, deploy, and collaborate on prompts with centralized management and tracking > **[LLM playground](/latest/openlit/prompts-experiments/openground/overview)** > > Compare cost, duration, and response tokens across different LLMs to find the most efficient model # Data-source connectors Source: https://docs.openlit.io/latest/openlit/connectors/datasource Connect OpenLIT to ClickHouse and OpenPlait-backed observability backends — atomic connectors, signal bindings, and portable query adapters **Data-source connectors** attach OpenLIT to observability backends for reading traces, logs, and metrics. Each connector is atomic: one Tempo instance, one Loki instance, one Prometheus endpoint. Configure them from **Configuration → Connectors** (`/connectors`) or manage bindings from **Organisation → Project → Connectors**. Select the correct [project](/latest/openlit/organisation/projects) and [environment](/latest/openlit/organisation/environments) before adding or binding connectors. > [!NOTE] > > Several read paths use portable [@openplait](https://github.com/openlit/openplait) adapters so query behavior stays consistent across backends. See the [Connectors overview](/latest/openlit/connectors/overview) for memory connectors and shared registry concepts. ## Mental model ```mermaid theme={"theme":{"light":"github-light","dark":"github-dark"}} flowchart LR subgraph org [Organisation] proj[Project] end subgraph env [Environment] bindT[traces binding] bindL[logs binding] bindM[metrics binding] end proj --> env bindT --> C1[Connector A] bindL --> C2[Connector B] bindM --> C3[Connector C] C1 --> Tempo[Tempo] C2 --> Loki[Loki] C3 --> Prom[Prometheus] ``` * **Atomic connectors** — never a multi-backend blob. One Tempo instance, one Loki instance, one Prometheus endpoint. * **Signal routing** — each of traces / logs / metrics is bound independently. See [Signal routing](/latest/openlit/organisation/signal-routing). * **Database Config** — ClickHouse lives as a Database Config and appears as the built-in connector. See [Database Config](/latest/openlit/organisation/database-config). ## Supported connectors (OpenLIT + OpenPlait) These are the data-source connectors available in open-source OpenLIT. OpenPlait packages power the portable query adapters for ClickHouse, Tempo, Loki, Prometheus, and Jaeger. ### Built-in app store | Connector | Package / implementation | Signals | What it's for | | - | - | - | - | | **ClickHouse** | Database Config + `@openplait/adapter-clickhouse` | traces, logs, metrics (+ intelligence) | Default store; full correlation, raw SQL, evals metadata, vault | ### External data-source connectors | Connector | Package / implementation | Signals | What it's for | | - | - | - | - | | **Grafana Tempo** | `@openplait/adapter-tempo` | traces | TraceQL search, trace tree, span events | | **Grafana Loki** | `@openplait/adapter-loki` | logs | LogQL logs; correlate by trace id / service | | **Prometheus** | `@openplait/adapter-prometheus` | metrics | PromQL HTTP API (also works with Prometheus-compatible endpoints such as Mimir when you point at their query URL) | | **Jaeger** | `@openplait/adapter-jaeger` | traces | Jaeger Query HTTP API; sampled in-process aggregates | > [!TIP] > > Prometheus-compatible APIs (for example Grafana Mimir's PromQL endpoint) use the **Prometheus** connector — there is no separate Mimir connector type in open-source OpenLIT. ## Capability matrix | Connector | Signals | Trace tree | Span events | Server aggregation | Raw SQL | Cross-signal correlation | | - | - | - | - | - | - | - | | ClickHouse | traces, logs, metrics | Yes | Yes | Yes | Yes | Full | | Tempo | traces | Yes | Yes | No\* | No | trace / span / service | | Loki | logs | — | — | No | No | trace id, service | | Prometheus | metrics | — | — | Yes | No | — | | Jaeger | traces | Yes | Yes | No\* | No | trace / span / service | \* Aggregate graphs are reconstructed in-process from a bounded sample of full traces when the backend cannot aggregate server-side. ## Enterprise data-source connectors > [!NOTE] > > **Enterprise feature.** Datadog and New Relic connectors are premium connectors in [OpenLIT Enterprise](/latest/openlit/enterprise) and need a license that includes **Premium connectors**. Without it, they appear in the catalog with a **Premium** badge and a lock, and **Add connector** reads **License required**. | Connector | Signals | What it's for | | - | - | - | | **Datadog** | traces, logs, metrics | Read spans, logs, and metrics from Datadog | | **New Relic** | traces, logs, metrics | Read spans, logs, and metrics from New Relic with NRQL | Add them from **Connectors** like any other data source, then bind them per signal in [signal routing](/latest/openlit/organisation/signal-routing). A bound connector must be in the same environment as the binding. ![Connector catalog filtered to Datadog, showing the premium data source and the premium notification connector](https://mintcdn.com/openlit/Bq8cTvlOcuilnTTA/images/enterprise/connectors-datadog.png?fit=max\&auto=format\&n=Bq8cTvlOcuilnTTA\&q=85\&s=b4c76ab47e12be2e81075d8630d291a6) ### Datadog | Field | Required | Description | | - | - | - | | Datadog site | No | Your Datadog site, such as `datadoghq.com` (default) or `datadoghq.eu`. | | API key | Yes | A Datadog API key. | | Application key | Yes | A Datadog application key with access to spans, logs, and metrics. | * Logs are read without AI filtering, so all logs in the range are returned. * When you don't filter on a metric name, metric series default to `system.cpu.user`. * The Datadog spans API is rate limited, so OpenLIT caches span queries for 30 seconds. ### New Relic | Field | Required | Description | | - | - | - | | Region | No | `US` (default) or `EU`. | | Account ID | Yes | Your New Relic account ID. | | API key | Yes | A user API key with NerdGraph and NRQL access to the account. | * Metric series need a metric-name filter. * Span events are stored as separate rows in New Relic, so they aren't shown on spans. Both connectors support the trace tree and server-side aggregation, and correlate signals by trace ID, span ID, and service. Neither supports raw queries or span events. **Test connection** checks the credentials and looks for AI telemetry from the last hour. ## Add a Jaeger connector ![Add source dialog with the connector type menu open, including Jaeger](https://mintcdn.com/openlit/ARLdG9OdsIzgrIz7/images/organisation/openlit-jaeger-connector.png?fit=max\&auto=format\&n=ARLdG9OdsIzgrIz7\&q=85\&s=10cf33a79ec0756cca6c6e7fd95b4b69) **1. Select project and environment** Use the header selectors to pick the [project](/latest/openlit/organisation/projects) and [environment](/latest/openlit/organisation/environments). **2. Open Connectors** Go to **Configuration → Connectors** and choose **Add connector** / **Add source**. **3. Pick Jaeger** Select **Jaeger** from the data-source connector list. **4. Configure endpoint** Set the Query URL (local all-in-one: `http://localhost:16686`) and auth if needed. **5. Bind traces** Bind **traces** to the connector under [signal routing](/latest/openlit/organisation/signal-routing). > [!TIP] > > Local demo credentials for seeded installs: email `user@openlit.io`, password `openlituser`. Jaeger all-in-one + seed script live in the OpenPlait `adapter-jaeger` integration folder. ## Actions you can take on a connector The connector registry is designed so **actions** attach to a connector type. Today's data-source actions: | Action | When to use it | | - | - | | **Add / edit connector** | Point OpenLIT at a new endpoint or rotate settings | | **Store credentials in Vault** | API keys and tokens stay encrypted; decrypted only server-side | | **Health check / test connection** | Confirm the endpoint is reachable with current auth | | **Validate AI signal** | Confirm recent AI telemetry exists (`gen_ai.*` / OpenLIT markers) | | **Bind signal** | Route traces, logs, or metrics to this connector for an environment | | **Unbind / rebind** | Move a signal to another connector without deleting history in the backend | | **Mark Database Config active** | Choose which ClickHouse app store the UI uses | | **Share Database Config** | Grant edit / delete / re-share on a ClickHouse connection | [Enterprise](/latest/openlit/enterprise) [audit logs](/latest/openlit/audit-logs) record connector create/update/delete/test plus bind/unbind (with signal, environment, and previous source when switching). ## How OpenLIT finds AI telemetry Backends hold all telemetry — not only AI. OpenLIT applies a layered **AI selector** so reads stay focused: * Resource `telemetry.sdk.name = openlit` * Resource `telemetry.distro.name = openlit-cli` * Any `gen_ai.*` attribute * `coding_agent.session.id`, or Claude Code session markers * Known `coding_agent.*` span names ## Authentication cheat sheet | Backend | Typical auth | | - | - | | **Grafana Cloud** (Tempo / Loki) | Basic: instance ID + access policy token (`traces:read` / `logs:read`). Use **query** URLs from the Cloud Portal. | | **Self-hosted Tempo / Loki / Prometheus / Jaeger** | None, Basic, or Bearer; optional tenant / `X-Scope-OrgID` | | **ClickHouse** | Username / password on the Database Config | ## Security and reliability * Endpoints validated (`http`/`https` only); credentials in URLs rejected; private/metadata SSRF targets blocked where applicable. * Secrets live in Vault, redacted from errors, never logged. * Per-source concurrency caps, query budgets, short-lived cache + in-flight de-dupe, and backoff on transient `429`/`5xx`. ## OpenPlait packages OpenLIT wraps these npm packages for portable reads: | npm package | Used for | | - | - | | `@openplait/core` | Query IR and normalized results | | `@openplait/adapter-sdk` | Adapter contracts | | `@openplait/adapter-clickhouse` | ClickHouse / OTel | | `@openplait/adapter-tempo` | Tempo / TraceQL | | `@openplait/adapter-loki` | Loki / LogQL | | `@openplait/adapter-prometheus` | Prometheus / PromQL | | `@openplait/adapter-jaeger` | Jaeger Query HTTP API | | `@openplait/runtime` | Planning and execution helpers | See the [OpenPlait repository](https://github.com/openlit/openplait) for publishing and adapter docs. ## Related > **[Memory connectors](/latest/openlit/connectors/memory)** > > Claude, LangGraph, Mem0, and Zep for agent memory. > **[Scanner connectors](/latest/openlit/connectors/scanner)** > > Trustabl scans of GitHub repositories. > **[Signal routing](/latest/openlit/organisation/signal-routing)** > > Bind traces, logs, and metrics independently. > **[Database Config](/latest/openlit/organisation/database-config)** > > Env vars → ClickHouse Database Config. > **[Connectors overview](/latest/openlit/connectors/overview)** > > Shared connector model and project scope. # Memory connectors Source: https://docs.openlit.io/latest/openlit/connectors/memory Connect OpenLIT to Claude, LangGraph, Mem0, and Zep memory stores — browse, search, write, copy, and ask Otter about agent memories **Memory connectors** attach OpenLIT to external agent memory providers. Use them to browse stored facts, search semantically, add or edit memories, copy between backends, and ask **Otter** questions grounded in connector data. Add memory connectors from **Configuration → Connectors** (`/connectors`) under the **Memory** category, or directly from the **Memory** page (`/memory`) when no connector exists yet. Connectors are scoped to the current [project](/latest/openlit/organisation/projects) and [environment](/latest/openlit/organisation/environments). > [!NOTE] > > Memory connectors use the same atomic registry as [data-source connectors](/latest/openlit/connectors/datasource), but they do not participate in telemetry signal routing. Credentials are encrypted on the connector instance rather than the ClickHouse vault. ## Mental model ```mermaid theme={"theme":{"light":"github-light","dark":"github-dark"}} flowchart LR subgraph proj [Project + environment] MC[Memory connector] end MC --> API[Vendor memory API] MC --> MemPage[Memory page] MC --> Otter[Otter chat tools] MemPage --> Browse[List / graph / detail] Otter --> Tools[list / search / add / update / delete] ``` * **Atomic connectors** — one Mem0 project endpoint, one Zep tenant, one Claude memory store configuration, or one LangGraph server per connector instance. * **Capability-driven UI** — each vendor advertises supported operations (`list`, `search`, `add`, `update`, `delete`, `feedback`). OpenLIT hides actions the vendor does not support. * **Filters** — user, session/run, and agent filters are declared per vendor. Some connectors require a session or user before listing memories. * **Port links** — copying a memory to another connector stores provenance metadata so you can trace copies back to the source. ## Supported memory connectors | Connector | Default endpoint | What it's for | | - | - | - | | **Claude** | `https://api.anthropic.com` | Browse and edit memories in Anthropic Claude memory stores (Managed Agents memory beta) | | **Mem0** | `https://api.mem0.ai` | Store and search long-term agent memories scoped by user, run, or agent | | **Zep** | `https://api.getzep.com` | Session memory and knowledge-graph facts for agents | | **LangGraph** | None — enter your server URL | Long-term memories in the Store of a LangGraph Platform deployment or self-hosted LangGraph server | Self-hosted Mem0 or Zep deployments work when you point the connector at a compatible API URL and supply the same authentication style as the hosted service. ## Capability matrix | Connector | List | Search | Get | Add | Update | Delete | Feedback | Required filters | | - | - | - | - | - | - | - | - | - | | **Claude** | Yes | Yes\* | Yes | Yes | Yes | Yes | No | Memory store (session) | | **Mem0** | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Optional user / run / agent | | **Zep** | Yes | Yes | Yes | Yes | No | Yes† | No | User; session required for writes | | **LangGraph** | Yes | Yes‡ | Yes | Yes | Yes | Yes | No | Namespace (session) for writes, or the template's placeholders | \* Claude has no dedicated search API — OpenLIT filters listed memories locally for search queries. † Zep delete targets graph edges/nodes, not an entire session. ‡ LangGraph search is semantic only when the server has a store index configured. Without one, LangGraph ignores the query and returns items in listing order with no relevance score; OpenLIT does not emulate semantic search. ## Add a memory connector **1. Select project and environment** Use the header selectors for the target [project](/latest/openlit/organisation/projects) and [environment](/latest/openlit/organisation/environments). **2. Open Connectors** Go to **Configuration → Connectors**, open the **Memory** section in the catalog, and choose **Add connector**. **3. Pick a vendor** Select **Claude**, **LangGraph**, **Mem0**, or **Zep**. **4. Configure endpoint and credentials** Set the API URL (defaults work for hosted services), optional SSRF toggles for private networks, and the API key. Mem0 also accepts optional organization and project IDs. LangGraph has no default URL, an optional API key, and an optional namespace template (see [LangGraph namespaces](#langgraph-namespaces)). **5. Test and save** Run a health check, then save. Open **Memory** (`/memory`) and select the connector from the dropdown. > [!TIP] > > You can also add a connector from the Memory page empty state — **Add memory connector** opens the same add dialog. ## Memory page features Once a connector is configured, the **Memory** page provides: | Feature | Description | | - | - | | **Connector selector** | Switch between memory connectors in the current environment | | **Filters** | User, session/run, and agent filters when the vendor supports them | | **Memory list** | Paginated list with search | | **Detail sheet** | Full memory content, metadata, history, and feedback (when supported) | | **Graph view** | Relationship visualization for graph-capable vendors (for example Zep) | | **Write actions** | Add, update, or delete memories when the connector advertises those capabilities | | **Copy** | Copy selected memories to another write-capable connector in the same environment | | **Ask Otter** | Natural-language search and Q\&A grounded in connector list/search APIs | ## Otter memory tools When Otter runs on the Memory page or in chat with memory context, it can call connector-backed tools: | Tool | When available | | - | - | | `list_memories` | Connector supports `list` | | `search_memories` | Connector supports `search` | | `add_memory` | Connector supports `add` | | `update_memory` | Connector supports `update` | | `delete_memory` | Connector supports `delete` | Otter passes required filter arguments (user, session, agent) when the vendor mandates them. [Enterprise](/latest/openlit/enterprise) builds apply the same [RBAC](/latest/openlit/rbac) and [audit](/latest/openlit/audit-logs) hooks as the `/api/memory` routes. ## Authentication | Connector | Auth style | Notes | | - | - | - | | **Claude** | API key (`x-api-key`) | Sends the `agent-memory-2026-07-22` beta header for memory store APIs | | **Mem0** | Token (`Authorization: Token `) | Works with Mem0 Platform or self-hosted compatible endpoints | | **Zep** | API key (`Authorization: Api-Key `) | Works with Zep Cloud or self-hosted compatible endpoints | | **LangGraph** | None, or API key (`x-api-key`) | Leave the key empty for self-hosted servers without auth; use a LangSmith API key for LangGraph Platform. Bearer tokens and custom auth handlers are not supported | API keys are stored encrypted on the connector (`enc:v1:…`) and decrypted only server-side for outbound requests. ## LangGraph namespaces LangGraph Store items live in a namespace (an ordered list of labels) under a key. LangGraph gives namespace labels no meaning of their own, so OpenLIT only maps them to user, session, and agent filters when you declare it with a **namespace template**: | Namespace template | Behaviour | | - | - | | *(empty)* | Each discovered namespace is a **Namespace or thread** (session) filter, for example `memories.u1`. Writes go to the selected namespace. | | `memories` | Same, limited to namespaces under `memories`. | | `memories/{user_id}` | The second label is the user. | | `memories/{user_id}/{thread_id}/{assistant_id}` | Labels map to user, session (thread), and agent filters. Writes need a value for every placeholder. | Fixed template segments must match exactly; placeholders are `{user_id}`, `{thread_id}`, and `{assistant_id}`. Labels cannot be empty or contain `.`. Items deeper than the template still match; items that do not fit it are hidden. Memory content is read from the LangMem shapes `{"kind": ..., "content": {"content": "..."}}` and `{"content": "..."}`, falling back to the JSON of the whole value, including arrays and primitives. The original value is always shown in metadata under `langgraph`. New memories are written as `{"content": "..."}`. Updates change only the field the content was read from and keep every other field. OpenLIT refuses to edit a value that is not an object, or a non-text `content` or `metadata` field, because that would overwrite data. Because LangGraph has no compare-and-swap, an update can overwrite a concurrent change made between OpenLIT's read and write. ## Copy between connectors Use **Copy** on the Memory page to duplicate memories into another write-capable connector in the same project and environment. OpenLIT: 1. Reads the source memory from the origin connector 2. Writes to the destination connector when `add` is supported 3. Stores `metadata.openlit.port` on the destination memory and links the destination connector back to the source for traceability In [OpenLIT Enterprise](/latest/openlit/enterprise), copying requires the `connectors:update` permission, or the equivalent memory permission under [role-based access control](/latest/openlit/rbac). ## Security and reliability * Endpoints validated (`http`/`https` only); credentials in URLs rejected; private/metadata SSRF targets blocked where applicable. * Secrets encrypted on the connector instance, redacted from errors, never logged. * Outbound calls use the shared safe-fetch layer with the same SSRF controls as data-source connectors. * Content limits: memory body up to 20,000 characters; metadata JSON up to 4,000 characters. ## Vendor documentation > **[Claude memory](https://platform.claude.com/docs/en/managed-agents/memory)** > > Anthropic Managed Agents memory stores. > **[Mem0 API](https://docs.mem0.ai/api-reference)** > > Mem0 Platform REST API reference. > **[Zep SDK](https://help.getzep.com/sdk-reference)** > > Zep Cloud API and graph memory. > **[LangGraph Agent Server](https://docs.langchain.com/langsmith/agent-server)** > > LangGraph server and Store API. ## Related > **[Connectors overview](/latest/openlit/connectors/overview)** > > Shared connector registry and project scope. > **[Data-source connectors](/latest/openlit/connectors/datasource)** > > Tempo, Loki, Prometheus, Jaeger, and ClickHouse. > **[Scanner connectors](/latest/openlit/connectors/scanner)** > > Trustabl scans of GitHub repositories. > **[Otter (AI Copilot)](/latest/openlit/chat/overview)** > > Chat with Otter across OpenLIT surfaces. > **[Environments](/latest/openlit/organisation/environments)** > > Partition memory connectors by environment. # Notification connectors Source: https://docs.openlit.io/latest/openlit/connectors/notification Send OpenLIT alerts to Slack, email, webhooks, Discord, PagerDuty, Opsgenie, and Datadog > [!NOTE] > > **Enterprise feature.** This is part of [OpenLIT Enterprise](/latest/openlit/enterprise) and needs an enterprise license applied to your organisation; the community edition doesn't include it. [Book a call](https://cal.com/aman.openlit/30min) or email [developers@openlit.io](mailto:developers@openlit.io) to get access to the enterprise build and a license. Notification connectors deliver [Alerts](/latest/openlit/alerts) to the tools your team already uses. Each connector holds one destination and its credentials, and any alert in the organisation can send to it. ## Availability Notification connectors are **premium connectors**: creating, editing, and testing them needs an [OpenLIT Enterprise](/latest/openlit/enterprise) license that includes **Premium connectors**. Sending alerts also needs the **Alerts** feature. Without the license, the connector catalog shows these types with a **Premium** badge and a lock, and **Add connector** reads **License required**. ## Add a notification connector ![Notification connector catalog with Slack, email, PagerDuty, and Opsgenie marked Premium](https://mintcdn.com/openlit/Bq8cTvlOcuilnTTA/images/enterprise/connectors-notification.png?fit=max\&auto=format\&n=Bq8cTvlOcuilnTTA\&q=85\&s=87cfb40ebad2b669532b9bf3b0f1a75f) You can add a notification connector in either place; both create the same connector and alert destination: * **Connectors** (`/connectors`): find the type in the **Notification** group of the catalog and select **Add connector**. * **Alerts → Destinations**: select **Add destination**, or select **Destination** while creating an alert. Every connector has a **Name** (unique within its environment) and an **Environment**. Fields under **Authentication** are encrypted and only decrypted on the server. Select **Test connection** after saving to send a test notification titled "OpenLIT alert test". Outbound requests must use `http` or `https` and can't target localhost, private, or reserved network addresses. ## Connector types > [!NOTE] > > Microsoft Teams isn't available as a destination yet. ### Slack Posts an alert card to a channel through a Slack [incoming webhook](https://api.slack.com/messaging/webhooks). | Field | Required | Description | | - | - | - | | Webhook URL | Yes | The Slack incoming webhook URL. | | Channel | No | Overrides the webhook's default channel, such as `#alerts`. Each alert can override it again. | The card shows the title, the message, the severity, the trigger, and, if enabled on the alert, the matched fields. ### Email Sends alert emails as JSON to an email relay endpoint that you run or a provider offers. | Field | Required | Description | | - | - | - | | Recipients | Yes | Comma-separated email addresses. | | From address | No | Sender address. Defaults to `alerts@openlit.local`. | | Relay URL | Yes, to send | The JSON relay endpoint that sends the email. | | Authorization header | No | Sent as the `Authorization` header to the relay. | > [!NOTE] > > Email doesn't support SMTP yet. Without a **Relay URL**, sends fail with "Email relay URL is required until a native SMTP adapter is configured." OpenLIT POSTs `to`, `from`, `subject`, `text`, and `html` to the relay. ### Webhook POSTs the alert as JSON to any HTTPS endpoint. | Field | Required | Description | | - | - | - | | Webhook URL | Yes | The endpoint that receives the alert. | | Authorization header | No | Sent as the `Authorization` header. | The payload includes: ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "title": "API key access changed", "message": "API key sdk-key access was updated.", "severity": "high", "alert_id": "...", "alert_name": "API key access changes", "event_id": "...", "trigger_type": "api_key_change", "fields": { "event": "api_key_access_updated", "access": "full" }, "presentation": { "style": "default", "accentColor": "#e11d48", "includeFields": true } } ``` ### Discord Posts an embed to a Discord channel webhook. | Field | Required | Description | | - | - | - | | Webhook URL | Yes | The Discord channel webhook URL. | | Bot username | No | Display name for the message. Defaults to `OpenLIT Alerts`. | The embed colour follows the alert's accent colour, or red for critical, orange for high, and blue otherwise. ### PagerDuty Triggers incidents through the PagerDuty Events API v2. | Field | Required | Description | | - | - | - | | Routing key | Yes | Your Events API v2 integration routing key. | | Source | No | Event source. Defaults to `openlit`. | | Component, Group, Class | No | Optional PagerDuty event fields. Class defaults to the trigger type. | Severity maps to PagerDuty as critical → `critical`, high → `error`, medium → `warning`, and low → `info`. OpenLIT only triggers incidents; resolve them in PagerDuty. ### Opsgenie Creates alerts through the Opsgenie Alerts API. | Field | Required | Description | | - | - | - | | API key | Yes | An Opsgenie API integration key. | | Region | No | `us` (default) or `eu`. | | Responders | No | Comma-separated responder names, teams, or escalations. | Severity maps to priority as critical → `P1`, high → `P2`, medium → `P3`, and low → `P4`. ### Datadog Creates events through the Datadog Events API. | Field | Required | Description | | - | - | - | | Datadog site | No | Your Datadog site. Defaults to `datadoghq.com`. | | API key | Yes | A Datadog API key. | Critical alerts create `error` events; others create `warning` events. Events are tagged with `openlit`, the severity, and the trigger. *** > **[Alerts](/latest/openlit/alerts)** > > Create alerts and attach notification connectors as destinations > **[Connectors overview](/latest/openlit/connectors/overview)** > > How connectors work across data sources, memory, and scanners # Connectors Source: https://docs.openlit.io/latest/openlit/connectors/overview Atomic integrations that connect OpenLIT to observability backends, memory stores, and scanners — scoped to projects and environments **Connectors** are how OpenLIT attaches to external systems. Each connector is an **atomic** integration: one backend, one credential set, and a clear set of **actions** (test connection, validate signals, bind telemetry, browse memories, and more). Open **Connectors** from the sidebar under **Configuration → Connectors** (`/connectors`). Always select the correct [project](/latest/openlit/organisation/projects) and [environment](/latest/openlit/organisation/environments) first — connectors belong to the project, not the organisation root. ![OpenLIT Connectors page showing configured connectors and the connector catalog](https://mintcdn.com/openlit/ARLdG9OdsIzgrIz7/images/organisation/connectors-list.png?fit=max\&auto=format\&n=ARLdG9OdsIzgrIz7\&q=85\&s=864addd68ae34069181cf272b47878a3) > [!NOTE] > > Connectors share one registry model across categories. **Data-source connectors** route telemetry reads; **memory connectors** connect OpenLIT to agent memory stores; **scanner connectors** run Trustabl jobs against GitHub repositories. ## Connector categories > **[Data-source connectors](/latest/openlit/connectors/datasource)** > > ClickHouse, Tempo, Loki, Prometheus, and Jaeger — read traces, logs, and metrics with per-signal routing. > **[Memory connectors](/latest/openlit/connectors/memory)** > > Claude, LangGraph, Mem0, and Zep — browse, search, write, and copy agent memories from the Memory page and Otter. > **[Scanner connectors](/latest/openlit/connectors/scanner)** > > Trustabl — scan GitHub repositories for agent SDK, MCP, and policy findings. > **[Notification connectors](/latest/openlit/connectors/notification)** > > Enterprise — send alerts to Slack, email, webhooks, Discord, PagerDuty, Opsgenie, and Datadog. > [!NOTE] > > Premium connectors (the notification connectors, plus the [Datadog and New Relic data sources](/latest/openlit/connectors/datasource#enterprise-data-source-connectors)) are part of [OpenLIT Enterprise](/latest/openlit/enterprise) and show a **Premium** badge in the catalog. ## Mental model ```mermaid theme={"theme":{"light":"github-light","dark":"github-dark"}} flowchart LR subgraph org [Organisation] proj[Project] end subgraph env [Environment] bindT[traces binding] bindL[logs binding] bindM[metrics binding] mem[memory connectors] scan[scanner connectors] end proj --> env bindT --> DS1[Tempo] bindL --> DS2[Loki] bindM --> DS3[Prometheus] mem --> M1[Mem0] mem --> M2[Zep] mem --> M3[LangGraph] scan --> S1[Trustabl] ``` * **Atomic connectors** — one backend per connector instance. Never a multi-backend blob. * **Project scope** — connectors are created on a project and partitioned by [environment](/latest/openlit/organisation/environments). * **Data-source bindings** — traces, logs, and metrics bind independently. See [Signal routing](/latest/openlit/organisation/signal-routing). * **Memory usage** — memory connectors power the **Memory** page (`/memory`) and Otter memory tools; they do not replace telemetry routing. * **Scanner usage** — scanner connectors power the **Scanner** page (`/scanner`) and Otter `get_scanner_findings`; they do not replace telemetry routing. ## Common actions | Action | Data-source | Memory | Scanner | | - | - | - | - | | Add / edit connector | Yes | Yes | Yes | | Store credentials | Vault (ClickHouse) or connector secret | Encrypted on connector | Encrypted on connector | | Health check / test connection | Yes | Yes | Yes (CLI installed) | | Validate AI signal | Yes (`gen_ai.*` markers) | — | — | | Bind signal | traces / logs / metrics | — | — | | Browse / search / write memories | — | Yes (capability-dependent) | — | | Run scan / list findings | — | — | Yes | [Enterprise](/latest/openlit/enterprise) [audit logs](/latest/openlit/audit-logs) record connector create, update, delete, test, bind, and unbind events. [RBAC](/latest/openlit/rbac) permissions are `connectors:read|create|update|delete|test|bind` (owner/admin by default). ## Related > **[Organisation overview](/latest/openlit/organisation/overview)** > > How projects and environments scope connectors. > **[Signal routing](/latest/openlit/organisation/signal-routing)** > > Bind traces, logs, and metrics to data-source connectors. > **[Database Config](/latest/openlit/organisation/database-config)** > > ClickHouse app store and built-in connector. > **[Environments](/latest/openlit/organisation/environments)** > > Partition connectors by environment. # Scanner connectors Source: https://docs.openlit.io/latest/openlit/connectors/scanner Connect Trustabl to scan GitHub repositories for agent SDK, MCP, and policy findings — then review jobs from Scanner and Otter **Scanner connectors** attach OpenLIT to job-based code scanners. Use them to scan a GitHub repository, collect findings with severity and path, and join those results to coding-agent traces. Add scanner connectors from **Configuration → Connectors** (`/connectors`) under the **Scanners** category, or from the **Scanner** page (`/scanner`) when no connector exists yet. Connectors are scoped to the current [project](/latest/openlit/organisation/projects) and [environment](/latest/openlit/organisation/environments). > [!NOTE] > > Scanner connectors use the same atomic registry as [data-source](/latest/openlit/connectors/datasource) and [memory](/latest/openlit/connectors/memory) connectors. They do not participate in telemetry signal routing. GitHub tokens are encrypted on the connector instance. ## Mental model ```mermaid theme={"theme":{"light":"github-light","dark":"github-dark"}} flowchart LR subgraph proj [Project + environment] SC[Scanner connector] end SC --> CLI[Trustabl CLI on the OpenLIT server] CLI --> GH[GitHub repository] SC --> ScanPage[Scanner page] SC --> Otter[Otter get_scanner_findings] ScanPage --> Jobs[Jobs and findings] ``` * **Atomic connectors** — one Trustabl scanner per connector instance, with its own target, defaults, and token. * **GitHub URL target** — v1 scans `https://github.com/owner/repo` only. Local filesystem paths are rejected. * **On-demand CLI** — OpenLIT installs a checksum-verified Trustabl release onto the server, then runs `trustabl scan`. * **Environment-scoped jobs** — jobs and findings stay on the connector for the selected environment. ## Supported scanner connectors | Connector | Runtime | What it's for | | - | - | - | | **Trustabl** | Trustabl CLI (`v0.1.8` pin, upgradable) | Scan agent SDKs and MCP servers for reliability and safety findings | ## Add a Trustabl scanner **1. Select project and environment** Use the header selectors for the target [project](/latest/openlit/organisation/projects) and [environment](/latest/openlit/organisation/environments). **2. Open Connectors or Scanner** Go to **Configuration → Connectors** and choose **Add connector**, or open **Scanner** (`/scanner`) and add a connector from the empty state. **3. Pick Trustabl** Select **Trustabl** from the **Scanners** category. **4. Set the repository and token** Enter an `https://github.com/owner/repo` URL. Add `/tree/branch` to pin a branch, or set **Ref** separately. Add a GitHub token for private repositories. **5. Install the CLI and run a scan** On the Scanner page, install the Trustabl CLI onto the OpenLIT server, then **Run scan**. Findings appear on the same page. > [!TIP] > > Rules source **Follow environment** maps this OpenLIT environment: production → signed production, staging → signed staging, development → unsigned git. ## Scanner page Once a connector is configured, **Scanner** (`/scanner`) provides: | Feature | Description | | - | - | | **Connector selector** | Switch between scanner connectors in the current environment | | **CLI runtime** | Install or upgrade the Trustabl CLI on the OpenLIT server | | **Run scan** | Run with connector defaults, or override flags for one job | | **Jobs** | Recent scans with status, score, duration, and error | | **Findings** | Severity, rule, path, and suggested fix for the selected job | ## Scan parameters Connector defaults apply when you run with defaults. Override them for one job from **Run with new parameters**. | Setting | Purpose | | - | - | | **Repository URL** | GitHub repo to clone. `/tree/` is stripped for clone; use it only to pin a branch. | | **Ref** | Optional branch, tag, or commit | | **Detectors** | Comma-separated detector ids (for example `claude_sdk,mcp`). Blank runs all detectors. | | **Strict** | Mark the job failed if any finding is low or higher | | **Secret / vuln / license scan** | Extra Trustabl scans on repository files and dependencies | | **Rules source** | Follow environment, or pin Production (signed), Staging (signed), or Git (unsigned) | | **GitHub token** | Stored on the connector. Never sent in the scan request body. | ## Coding agents and Otter Coding-agent spans stamp `vcs.repository.url.full`. OpenLIT matches that URL to the latest **succeeded** scan in the current project and environment (https, `git@`, `.git`, and `/tree/` forms). * The coding-agent **trace detail** page shows a **Scanner** tab when a repo URL is present. * Otter can call `get_scanner_findings` with the GitHub URL. Medium+ findings are listed first. ## API Authenticate with an OpenLIT API key and send organisation, project, and environment headers. See [Settings → OpenAPI Spec](/latest/openlit/developer-resources/api-reference/introduction) for the interactive catalog. | Method | Path | Purpose | | - | - | - | | `POST` | `/api/scanners/{id}/scan` | Run a scan. Optional JSON body overrides connector defaults. Returns `{ connector, job }` with findings. | | `GET` | `/api/scanners` | List connectors in this environment, including recent jobs and findings. | | `GET` | `/api/scanners/findings?repoUrl=` | Latest succeeded scan for a GitHub repository URL. | `POST /api/scanners/{id}/scan` can take several minutes (up to 300 seconds). A second scan is rejected while one is already running on the same connector. ## Security * Scan targets are GitHub HTTPS URLs only. Local paths, SSH URLs with credentials, and whitespace are rejected. * Tokens are encrypted on the connector (`enc:v1:…`), redacted from jobs and API responses, and never logged. * The Trustabl CLI download is checksum-verified before install. ## Related > **[Connectors overview](/latest/openlit/connectors/overview)** > > Shared connector registry and project scope. > **[Data-source connectors](/latest/openlit/connectors/datasource)** > > Tempo, Loki, Prometheus, Jaeger, and ClickHouse. > **[Memory connectors](/latest/openlit/connectors/memory)** > > Claude, LangGraph, Mem0, and Zep for agent memory. > **[Coding agents](/latest/openlit/coding-agents/overview)** > > How coding-agent traces expose `vcs.repository.url.full`. # Analytics Source: https://docs.openlit.io/latest/openlit/costs/analytics AI cost analytics for LLM spend - cost by environment and application, Auto Pricing run history, and optimization charts by provider and model The **Analytics** tab on **Costs** (`/costs`) is OpenLIT's AI cost analytics view. Use the time range tabs at the top (**24H**, **7D**, **1M**, **3M**, or **CUSTOM**) to scope the window, then review usage, Auto Pricing activity, and optimization charts. If Auto Pricing is off and LLM spans in the range are missing cost, a banner appears: **Some LLM traces are missing cost**, with CTA **Configure Auto Pricing** (switches to the [Configuration](/latest/openlit/costs/configuration) tab). ## Cost usage Section title: **Cost usage**. Summary tiles: | Tile | Meaning | | - | - | | **Total AI cost** | Platform-wide spend in the selected range | | **LLM cost** | Cost attributed to LLM/telemetry traffic | | **Evaluations cost** | Spend from running evaluations | | **Openground cost** | Spend from OpenGround comparisons | | **Avg cost per request** | Average cost per request | Charts in this section: **Cost by environment**, **Cost by application**. ## Auto pricing runs Section title: **Auto pricing runs**. Tiles: **Cron runs**, **Successful runs**, **Costs applied**, **Spans scanned**. A run history table shows **Started**, **Status**, **Duration**, **Costs applied**, **Scanned**, **Skipped**, and **Failed**. Empty copy: *No Auto Pricing runs applied costs in this time range.* Hint CTA: **Enable Auto Pricing** → [Configuration](/latest/openlit/costs/configuration). ## Cost optimization Section title: **Cost optimization**. Charts: **Cost over time**, **Cost by provider**, **Cost by model**, and **Openground spend by provider**. *** > **[Manage models](/latest/openlit/costs/manage-models/overview)** > > Edit the prices Analytics uses for LLM cost > **[Configuration](/latest/openlit/costs/configuration)** > > Schedule Auto Pricing to fill missing costs # Configuration Source: https://docs.openlit.io/latest/openlit/costs/configuration AI cost tracking configuration - enable Auto Pricing on a schedule and recalculate LLM costs on individual traces from Monitor → Costs The **Configuration** tab on **Costs** (`/costs?tab=configuration`) controls AI cost tracking backfills. It recalculates cost on existing LLM traces using the per-model prices stored under [Manage models](/latest/openlit/costs/manage-models/overview). Use it when a trace was ingested without cost, pricing changed and you want missing costs filled in, or you added a custom model after traffic already landed. Open **Monitor → Costs**, then select **Configuration**. (`/pricing` redirects here.) ## Auto Pricing The **Auto Pricing** card schedules AI cost tracking for new LLM traces. **1. Enable Auto Pricing** Toggle **Enable Auto Pricing** on. **2. Set the Cron Schedule** Enter a standard cron expression in **Cron Schedule**. Examples: * `*/15 * * * *` - every 15 minutes * `0 * * * *` - every hour * `0 0 * * *` - once a day at midnight **3. Save** Click **Save** (or **Update** if a config already exists). OpenLIT runs Auto Pricing on that schedule for the active database configuration. On each run, OpenLIT finds recent LLM spans that are missing cost (or have cost `0`), looks up each span's provider and model in Manage models, computes the cost from token counts, and writes it back onto the span. Spans that already have a non-zero cost are left alone - Auto Pricing is a backfill, not an overwrite of vendor-reported or previously set costs. Spans with missing provider/model/tokens, or a model that isn't in Manage models, are skipped (not treated as failures). > [!TIP] > > Edit pricing on the [Manage models](/latest/openlit/costs/manage-models/overview) tab first. Auto Pricing and manual recalculation both use those prices. ## Manual Pricing The **Manual Pricing** card explains how to recalculate a single trace on demand: 1. Open any LLM request in [Telemetry](/latest/openlit/observability/telemetry/traces) (**Go to Traces** jumps there). 2. In the trace detail panel, find the **Cost** tile and click the refresh icon. The tooltip reads *Recalculate cost using the model's price in Manage Models* - there is no separate labeled button text. 3. A toast shows **Cost updated** with the new value. If cost is missing or zero, a pinging dot draws attention to that icon. Unlike Auto Pricing, a manual recalculation refreshes the cost even when a value is already present. The control only appears when the span has a model and span id. ## Frequently asked questions > **Why is a trace's cost missing or zero?** > > Usually the SDK didn't send cost, or the model wasn't in Manage models when the span was ingested. Add or fix the model, then recalculate manually or wait for the next Auto Pricing run. > **Does Auto Pricing overwrite an existing cost?** > > No. Auto Pricing only fills in missing or zero-cost spans - it never overwrites a cost your SDK or an upstream vendor already set. > **What happens to Auto Pricing after a restart?** > > Active Auto Pricing schedules are restored automatically when OpenLIT starts again - you don't need to re-enable them after a redeploy. *** > **[Analytics](/latest/openlit/costs/analytics)** > > See spend and Auto Pricing run history > **[Manage models](/latest/openlit/costs/manage-models/overview)** > > Edit LLM model pricing used for cost calculation # Manage LLM Model Pricing Source: https://docs.openlit.io/latest/openlit/costs/manage-models/overview Manage LLM providers and per-model token prices used for AI cost tracking across traces, Otter, OpenGround, and the SDK. **Manage models** is the **Manage models** tab on **Costs** (`/costs?tab=models`) under Monitor. Every connected ClickHouse database is seeded with **16 providers** and a large built-in model catalog (90+ models). You can edit any price, add custom providers and models, import/export pricing JSON, and point the OpenLIT SDK at a public pricing URL so ingest-time cost tracking stays in sync with the UI. Those prices power [Configuration](/latest/openlit/costs/configuration) (Auto Pricing and manual recalculation), [Analytics](/latest/openlit/costs/analytics), cost columns in telemetry, [OpenGround](/latest/openlit/prompts-experiments/openground/overview) comparisons, and Otter's message cost estimates. Open **Monitor → Costs → Manage models**. (`/manage-models` redirects here.) Toolbar actions (Add Provider, SDK Usage, Import, Export) are icon-only with tooltips. > **[Providers](/latest/openlit/costs/manage-models/providers)** > > Add or edit LLM providers (Provider ID, display name, Vault requirement) > **[Models](/latest/openlit/costs/manage-models/models)** > > Browse, edit, add, or delete models and per-1M-token pricing > **[Import and export](/latest/openlit/costs/manage-models/import-and-export)** > > Import Pricing JSON or Export Pricing for backup and sync > **[SDK pricing URL](/latest/openlit/costs/manage-models/sdk-pricing)** > > Copy the public SDK Pricing URL and wire it into openlit.init() ## Built-in providers Out of the box you get: OpenAI, Anthropic, Google AI, Mistral AI, Groq, Perplexity, Azure OpenAI, Cohere, Together AI, Fireworks AI, DeepSeek, xAI, Hugging Face, Replicate, MiniMax, and OrcaRouter. **TypeSafe** is also seeded for Jev evaluation models (`jev-latest`, `jev-1.13.0`, `jev-preview`) at $0.042 per million input tokens and $0 output. Jev is an evaluation provider, not a Chat or OpenGround model. Model lists change as the catalog is updated. Treat Manage models itself as the source of truth for what is installed in your database. ## API reference Authenticated Manage Models APIs and the public SDK export are documented under [API Reference → Manage Models](/latest/openlit/developer-resources/api-reference/endpoint/manage-models/models). Feature pages link there for request shapes and examples instead of duplicating code samples. ## Frequently asked questions > **How does OpenLIT calculate cost from these prices?** > > It multiplies each span's input and output token counts by that model's input and output price per 1M tokens from Manage models. > **Can I add a custom or self-hosted model?** > > Yes. Add a provider if needed, then add a model with your own Model ID and pricing - or bulk-import via Import Pricing JSON. > **Does Default vs Custom matter for pricing?** > > **Default** means the model was seeded by OpenLIT; **Custom** means you added it. Both are fully editable and used the same way for cost tracking. *** > **[Configuration](/latest/openlit/costs/configuration)** > > Backfill or refresh gen\_ai.usage.cost from Manage models prices > **[OpenGround](/latest/openlit/prompts-experiments/openground/overview)** > > Compare models side-by-side using the same provider catalog # LLM Cost Tracking Source: https://docs.openlit.io/latest/openlit/costs/overview LLM cost tracking in OpenLIT: monitor spend per model, user, and agent run, manage model pricing, and backfill missing costs. **Costs** (`/costs`) is OpenLIT's AI cost tracking and analytics page under **Monitor**. It combines spend analytics, LLM model pricing (Manage models), and Auto Pricing configuration in one place - the old Pricing and Manage Models pages redirect here. Open it from **Monitor → Costs**. The page has three tabs: > **[Analytics](/latest/openlit/costs/analytics)** > > Cost usage, Auto Pricing runs, and optimization charts > **[Manage models](/latest/openlit/costs/manage-models/overview)** > > Providers, per-model token prices, import/export, SDK pricing URL > **[Configuration](/latest/openlit/costs/configuration)** > > Enable Auto Pricing and recalculate cost on individual traces Default tab is **Analytics** (`/costs`). Use `?tab=models` or `?tab=configuration` for the other tabs (`/pricing` redirects to configuration; `/manage-models` redirects to models). ## Frequently asked questions > **Where did Pricing and Manage Models go?** > > They live on the Costs page under Monitor. Pricing settings are the **Configuration** tab; model catalog and prices are the **Manage models** tab. > **How does OpenLIT calculate AI cost?** > > From each span's input/output token counts times that model's input/output price per 1M tokens in Manage models. Analytics and Auto Pricing both use those prices. # Auto Refresh & Time Interval Source: https://docs.openlit.io/latest/openlit/dashboards/auto-refresh-and-time-interval Learn how to enable auto-refresh and set time intervals in OpenLIT dashboards to keep your data live and updated in real time. You can configure OpenLIT dashboards to automatically refresh data at set intervals. This feature is essential for live monitoring dashboards, status boards, and any view that requires up-to-date metrics from ClickHouse. ## Why use refresh intervals? Refresh intervals allow you to: * Keep data fresh without manual reloads and automatically re-run queries behind the scenes * Power live dashboards for operations and support teams * Monitor real-time metrics ## How to enable auto-refresh [How to Auto Refresh & Select Time Interval in OpenLIT – Real-Time Dashboards with ClickHouse](https://www.youtube.com/embed/YCplfvm6_W0?list=PLgErWFh-dyiGp9yUDnnx67wHEeNqzzMXZ) Once set, the dashboard will automatically re-fetch data from ClickHouse at the selected interval. ## Available refresh rate | Interval | Use Case | | - | - | | Never | Static dashboards | | 30 seconds | Live dashboards, production stats | | 1 minute | Standard monitoring (default) | | 5 minutes | Low-frequency updates | | 10 minutes | Less load on db | ## Available time intervals | Range | Description | Example Use Case | | - | - | - | | 24H | Last 24 hours from now | Real-time metrics, active users, API logs | | 7D | Last 7 calendar days | Weekly trends, retention, issue tracking | | 1M | Last 1 calendar month | Revenue analysis, product adoption | | 3M | Last 3 calendar months | Quarterly metrics, strategic reviews | | Custom | Select any start and end date manually | Audits, investigations, historical views | Snippet to make use of the time interval in the query: ```sql theme={"theme":{"light":"github-light","dark":"github-dark"}} WHERE event_time BETWEEN {{filter.timeLimit.start}} AND {{filter.timeLimit.end}} ``` > [!TIP] > > Use shorter intervals only for optimized queries. Long-running queries + high refresh rates can overload your ClickHouse backend. ## Best practices * Use dynamic bindings like `{{filter.timeLimit.start}}`, `{{filter.timeLimit.end}}` to make your chart respond to dashboard time filters. * Avoid using `LIMIT 1000` or wide result sets on high-frequency updates * Use refresh intervals in combination with pinned dashboards for kiosk or ops setups # Create Dashboard Source: https://docs.openlit.io/latest/openlit/dashboards/create-dashboard Learn how to create dashboards in OpenLIT with step-by-step instructions for adding widgets, configuring visualizations, and building effective monitoring visualizations. [Creating Your First Dashboard in OpenLIT – Build Visual Insights Fast](https://www.youtube.com/embed/Bq0d8vFvDX8?list=PLgErWFh-dyiGp9yUDnnx67wHEeNqzzMXZ) ## Steps #### 1. Create a new dashboard ![Create a new dashboard or folder form](https://mintcdn.com/openlit/o7M0DoQ9lLUZaVc9/images/create-dashboard-form.png?fit=max\&auto=format\&n=o7M0DoQ9lLUZaVc9\&q=85\&s=fe97ef84bf7026b5f98820526d9aab5c) 1. Navigate to the **Dashboards** section in the main navigation 2. Click the **Create new dashboard** button in the top right corner 3. A **"Create a new dashboard or folder"** popup will appear 4. **Fill in the dashboard details**: * **Name**: Enter a descriptive name (e.g., "LLM Observability") * **Description**: Describe the dashboard's purpose (e.g., "Dashboard to view telemetry from LLM apps and AI Agents") * **Tags**: Add relevant tags for organization and searchability * **Type**: Select **Dashboard** (Dashboard with widgets) 5. Click the **Add** button to create your dashboard 6. You'll be taken to the empty dashboard canvas ready for widgets #### 2. Add your first widget ![Empty dashboard canvas with a Create Your First Widget button](https://mintcdn.com/openlit/o7M0DoQ9lLUZaVc9/images/dashboard-empty.png?fit=max\&auto=format\&n=o7M0DoQ9lLUZaVc9\&q=85\&s=f4bd1e4401d1318ec77336cb62acdb0b) 1. Click **Create Your First Widget** button on the empty dashboard 2. You'll see a **"Select a Widget"** popup with two options: ![Select a Widget popup showing existing pre-built widgets](https://mintcdn.com/openlit/o7M0DoQ9lLUZaVc9/images/dashboard-select-existing-widget-popup.png?fit=max\&auto=format\&n=o7M0DoQ9lLUZaVc9\&q=85\&s=b2470aab8d94e93b5c655eeeb8c22612) * **Choose from existing widgets**: Select from pre-built widgets like "Avg temperature", "Avg utilization percentage", "Requests per time", etc. * **Create new widget**: Click **+ Create New Widget** to build a custom widget ![Create New Widget option in the Select a Widget popup](https://mintcdn.com/openlit/o7M0DoQ9lLUZaVc9/images/dashboard-select-new-widget-popup.png?fit=max\&auto=format\&n=o7M0DoQ9lLUZaVc9\&q=85\&s=40f8b1c1e106f746d97a4d8b74065831) #### 3. Configure widget When creating a new widget, you'll see a configuration panel with three tabs: **General Tab** ![Widget configuration panel General tab](https://mintcdn.com/openlit/o7M0DoQ9lLUZaVc9/images/dashboard-widget-configuration-general.png?fit=max\&auto=format\&n=o7M0DoQ9lLUZaVc9\&q=85\&s=f89baed3f36d0db844a1692842a0230b) 1. **Widget Title**: Enter a descriptive name (e.g., "Total Requests") 2. **Widget Type**: Select from **Stat Card** in this example 3. **Description**: Add context about what the widget shows 4. **Value Path**: Specify which field from your query to display (e.g., `0.count()` in this example) 5. **Prefix**: Add symbols before the value (e.g., `$` for costs) 6. **Suffix**: Add units after the value (e.g., `%` for percentages) **Query Tab** ![Widget configuration panel Query tab with a SQL editor](https://mintcdn.com/openlit/o7M0DoQ9lLUZaVc9/images/dashboard-widget-configuration-query.png?fit=max\&auto=format\&n=o7M0DoQ9lLUZaVc9\&q=85\&s=c3e4561c9b0982194f2f1a8cc8349814) 1. **Write your SQL query** against the ClickHouse database. For example: ```sql theme={"theme":{"light":"github-light","dark":"github-dark"}} SELECT count(*) FROM otel_traces ``` 2. **Test your query** by clicking **Run Query** 3. **Verify the results** - you'll see output like: `{"count()": 622}` #### 4. Save your widget 1. Click **Save Changes** to add the widget to your dashboard 2. The widget will appear on your dashboard canvas 3. You can immediately see your data visualization ![Dashboard canvas showing the newly created widget](https://mintcdn.com/openlit/o7M0DoQ9lLUZaVc9/images/dashboard-widget-created.png?fit=max\&auto=format\&n=o7M0DoQ9lLUZaVc9\&q=85\&s=227e528d21c792b53ca33720c3940c55) #### 5. Your dashboard is ready! * **Add more widgets**: Click **Create Widget** to add additional visualizations * **Resize widgets**: Drag the corners of any widget to adjust its size * **Move widgets**: Drag widgets to rearrange their positions on the dashboard # Create Folder Source: https://docs.openlit.io/latest/openlit/dashboards/create-folder Create folders to organize dashboards into logical collections by team, feature, environment, or product for better management and navigation. [Creating Your First Folder in OpenLIT - Group Your Dashboards Effectively](https://www.youtube.com/embed/bWg1FTj5-Hw) ## Steps #### 1. Create a new folder ![Create a new dashboard or folder form](https://mintcdn.com/openlit/o7M0DoQ9lLUZaVc9/images/create-dashboard-form.png?fit=max\&auto=format\&n=o7M0DoQ9lLUZaVc9\&q=85\&s=fe97ef84bf7026b5f98820526d9aab5c) 1. Navigate to the **Dashboards** section in the main navigation 2. Click the **Create new dashboard** button in the top right corner 3. A **"Create a new dashboard or folder"** popup will appear 4. **Fill in the folder details**: * **Name**: Enter a descriptive name for your folder (e.g., "Production Monitoring") * **Description**: Describe what dashboards this folder will contain * **Tags**: Add relevant tags for organization and searchability * **Type**: Select **Folder** (Container for dashboards and other folders) 5. Click the **Add** button to create your folder 6. Your new folder will appear in the dashboard explorer #### 2. Your folder is created You can now **drag and drop dashboards** into it directly from the dashboard list. # Export Dashboard Source: https://docs.openlit.io/latest/openlit/dashboards/export-dashboard Learn how to export your OpenLIT dashboards for backup, sharing, and migration across different environments. ## What is it? Dashboard export allows you to save your dashboard configurations as JSON files, preserving all widgets, layouts, queries, and settings. This is essential for: * **Backup & Recovery**: Create backup copies of important dashboards * **Team Sharing**: Share proven monitoring setups with team members * **Environment Migration**: Move dashboards between dev, staging, and production * **Template Creation**: Build reusable dashboard templates for common use cases #### 1. Navigate to dashboards Go to your Dashboards Explorer and locate the dashboard you want to export. #### 2. Access export menu ![Dashboard context menu with the Export Layout option](https://mintcdn.com/openlit/o7M0DoQ9lLUZaVc9/images/dashboard-export.png?fit=max\&auto=format\&n=o7M0DoQ9lLUZaVc9\&q=85\&s=d10ddf652a10367be2c748fc4f757128) 1. Click the **three dots (⋯)** menu icon next to your dashboard name to open the context menu. 2. From the dropdown menu, select **Export Layout**. #### 3. Download your dashboard OpenLIT will automatically generate and download a JSON file containing your complete dashboard configuration. The downloaded file will be named after your dashboard (e.g., `llm-dashboard.json`) and saved to your default download location. # Filters & Dynamic Bindings Source: https://docs.openlit.io/latest/openlit/dashboards/filters-and-dynamic-bindings Learn how to make OpenLIT dashboards interactive by using filters and mustache-style dynamic bindings inside ClickHouse queries. OpenLIT makes dashboards interactive by letting you add filters and dynamically bind their values to ClickHouse queries. This enables you to create reusable dashboards that respond to user inputs like time range, environment, region, and more. ## What are filters? **Filters** are interactive controls (e.g. date ranges) placed at the top of a dashboard. You can bind these filters to your widgets so their values get passed into SQL queries in real time. ## What are dynamic bindings? OpenLIT uses **mustache-style bindings** to inject filter values into your queries. ### Format: ```sql theme={"theme":{"light":"github-light","dark":"github-dark"}} {{variable_name}} ``` At runtime, OpenLIT replaces each binding with the user-provided filter value. ### Common examples: * `{{filter.timeLimit.start}}` and `{{filter.timeLimit.end}}` - for date range filters ## How to add and use filters [How to use filters & dynamic bindings in OpenLIT – Create Interactive Dashboards with ClickHouse](https://www.youtube.com/embed/J9TFEWmVczI?list=PLgErWFh-dyiGp9yUDnnx67wHEeNqzzMXZ) ## SQL example with mustache bindings ```sql theme={"theme":{"light":"github-light","dark":"github-dark"}} SELECT toStartOfHour(event_time) AS time, count() AS value FROM user_events WHERE event_time BETWEEN {{filter.timeLimit.start}} AND {{filter.timeLimit.end}} GROUP BY time ORDER BY time ``` In this example: * `{{filter.timeLimit.start}}` & `{{filter.timeLimit.end}}` are bound to a date range filter ## Supported filter types * **Date Range** → `{{filter.timeLimit.start}}`, `{{filter.timeLimit.end}}` * **(Coming soon)**: Dynamic filters etc ## Best practices * Always use meaningful keys like `team_id`, `env`, or `region` * Use `coalesce({{param}}, 'default')` to provide fallback values in ClickHouse * Test your queries in the SQL editor with example values for clarity * Avoid hardcoded constraints - use bindings to keep dashboards flexible # Import Dashboard Source: https://docs.openlit.io/latest/openlit/dashboards/import-dashboard Learn how to import pre-built dashboard layouts in OpenLIT to quickly set up comprehensive monitoring views for your AI applications. ## What is it? Dashboard import lets you load pre-built dashboard configurations from JSON files, instantly setting up complex monitoring layouts without manual widget creation. This is perfect for: * **Quick Setup**: Deploy comprehensive monitoring in seconds * **Best Practices**: Use proven dashboard templates * **Team Sharing**: Share dashboard configurations across teams * **Backup & Restore**: Restore previous dashboard configurations #### 1. Access import Navigate to your Dashboards Explorer and locate the **Import Layout** button in the top-right corner of the interface. ![Import Layout button in the Dashboards Explorer](https://mintcdn.com/openlit/o7M0DoQ9lLUZaVc9/images/dashboard-import-cta.png?fit=max\&auto=format\&n=o7M0DoQ9lLUZaVc9\&q=85\&s=365d77a742125abae8bbc2342ff9e981) Click the **Import Layout** button to open the import dialog. #### 2. Upload your dashboard JSON The "Import Board Layout" dialog will appear with a file upload interface. ![Import Board Layout dialog with a file upload interface](https://mintcdn.com/openlit/o7M0DoQ9lLUZaVc9/images/dashboard-import-popup.png?fit=max\&auto=format\&n=o7M0DoQ9lLUZaVc9\&q=85\&s=3bff9fd2ae7337d9e5ab76b57d5f9021) You can upload your dashboard configuration in two ways: **Option 1 - Drag & Drop**: * Drag your `.json` dashboard file directly from your file explorer * Drop it into the designated upload area in the dialog **Option 2 - Browse & Select**: * Click anywhere in the upload area to open your system's file browser * Navigate to your dashboard file and select it #### 3. Your imported dashboard is now ready You can immediately start monitoring your data with all the pre-configured widgets and layouts. # Organize Dashboards Source: https://docs.openlit.io/latest/openlit/dashboards/organize-dashboards Learn how to organize dashboards in OpenLIT using folders, boards, and drag-and-drop for better structure and navigation. [How to Drag and Drop to Organize Dashboards on OpenLIT – Easy Dashboard Management](https://www.youtube.com/embed/bHwVbJIA6_s?list=PLgErWFh-dyiGp9yUDnnx67wHEeNqzzMXZ) As your dashboard library grows, keeping things organized is key. OpenLIT provides an intuitive folder and board system, along with **drag-and-drop support** to keep your workspace clean and efficient. You can nest dashboards inside folders and even folders inside other folders to reflect your organizational structure. > [!TIP] > > **Organization best practices:** > > * Use consistent naming patterns: e.g., `Product > Retention`, `Infra > API Latency` > * Pin frequently used dashboards for quick access > * Set the most visited dashboard as your [Main Dashboard →](/latest/openlit/dashboards/set-main-dashboard) > * Combine folders and boards for deeply nested structures like `Engineering > Backend > Metrics > Error Logs` # AI Observability Dashboards Source: https://docs.openlit.io/latest/openlit/dashboards/overview Create interactive dashboards to monitor AI application performance, visualize telemetry, and analyze LLM operations in real time. [YouTube video player](https://www.youtube.com/embed/XOvhKAo9XG8?si=IDssTOxW-hmF62P2) ## How it works OpenLIT dashboards transform your telemetry data into actionable insights through a streamlined process: 1. **Data Collection**: AI telemetry flows through OpenTelemetry instrumentation into ClickHouse 2. **Query Execution**: Widgets execute optimized SQL queries against your telemetry data 3. **Visualization**: Results can be displayed as charts, tables, statistics, and custom visualizations 4. **Interactive Analysis**: Filter, drill down, and explore data with dynamic controls and time ranges ## Getting started > **[Create a new dashboard](/latest/openlit/dashboards/create-dashboard)** > > Learn the basics of creating and configuring dashboards > **[Widgets](/latest/openlit/dashboards/widgets/overview)** > > Add visualizations with various chart types and data displays > **[Use filters & variables](/latest/openlit/dashboards/filters-and-dynamic-bindings)** > > Make dashboards interactive with dynamic filters and parameters > **[Organize dashboards](/latest/openlit/dashboards/organize-dashboards)** > > Organize, share, and manage your dashboard collection # Pin a Dashboard Source: https://docs.openlit.io/latest/openlit/dashboards/pin-dashboard Learn how to pin a dashboard in OpenLIT to keep key dashboards easily accessible at the top of your list. Pinning dashboards allows you to keep your most important dashboards always accessible. Pinned dashboards appear at the top of your sidebar or dashboard list, so you can quickly jump to key metrics, live views, or frequently used dashboards. ## Why pin dashboards? * Prioritize dashboards you use every day * Quickly access critical dashboards without searching * Personalize your workspace by surfacing what matters most ## How to pin a dashboard [How to Pin a Dashboard on Openlit – Quick Access to Key Dashboards](https://www.youtube.com/embed/SIQCU8eU5IQ?list=PLgErWFh-dyiGp9yUDnnx67wHEeNqzzMXZ) ## Unpinning a dashboard To unpin a dashboard: 1. Click the **⋮ menu** again on a pinned dashboard. 2. Select **“Unpin Dashboard”**. The dashboard will return to its normal position in the folder/board structure. > [!TIP] > > You can pin multiple dashboards - perfect for surfacing daily dashboards, team-specific views, or in-progress monitoring. # Set a Main Dashboard Source: https://docs.openlit.io/latest/openlit/dashboards/set-main-dashboard Learn how to set a dashboard as your main (home) dashboard in OpenLIT for faster access to your most important views. In OpenLIT, you can designate any dashboard as your **Main Dashboard**, which acts as your default landing page when you open the app. This is useful when you or your team rely on a specific dashboard daily. ## Why set a main dashboard? * Automatically open your most important dashboard on login * Reduce time navigating to critical views * Personalize your OpenLIT experience ## How to set a main dashboard [How to set a Main Dashboard as Your Home Board on OpenLIT – Quick Navigation Setup](https://www.youtube.com/embed/fJNom9ULRIs?list=PLgErWFh-dyiGp9yUDnnx67wHEeNqzzMXZ) Once set, this dashboard will load by default each time you access /home of OpenLIT. ## Change or remove main dashboard You can always change your main dashboard by repeating the steps on another dashboard. To remove the main dashboard setting entirely, Simply go to any other dashboard and set it as main. # Area Chart Widget Source: https://docs.openlit.io/latest/openlit/dashboards/widgets/area-chart-widget Learn how to use the Area Chart Widget in OpenLIT to visualize time-based trends using ClickHouse queries and dynamic parameters. ## When to use Use the Area Chart when you want to: * Show trends over time * Emphasize volume or scale changes * Display multiple series stacked Area charts are particularly effective for metrics that accumulate or change gradually over time. ## How to add an area chart [How to Use the Area Chart Widget in OpenLIT – Visualize Trends Over Time](https://www.youtube.com/embed/4ciEi2LcVNk?list=PLgErWFh-dyiGp9yUDnnx67wHEeNqzzMXZ) ## Example: ```sql theme={"theme":{"light":"github-light","dark":"github-dark"}} WITH parseDateTimeBestEffort('{{filter.timeLimit.start}}') AS start_time, parseDateTimeBestEffort('{{filter.timeLimit.end}}') AS end_time, dateDiff('day', start_time, end_time) AS days_diff, dateDiff('year', start_time, end_time) AS years_diff, multiIf( years_diff >= 1, 'month', days_diff <= 1, 'hour', 'day' ) AS date_granularity SELECT CAST(SUM(toInt64OrZero(SpanAttributes['gen_ai.usage.input_tokens'])) AS INTEGER) AS prompt_tokens, CAST(SUM(toInt64OrZero(SpanAttributes['gen_ai.usage.output_tokens'])) AS INTEGER) AS completion_tokens, formatDateTime(DATE_TRUNC(date_granularity, Timestamp), '%Y/%m/%d %R') AS request_time FROM otel_traces WHERE SpanAttributes['gen_ai.operation.name'] != 'vectordb' AND StatusCode IN ('STATUS_CODE_OK', 'STATUS_CODE_UNSET', 'Ok', 'Unset') AND Timestamp >= start_time AND Timestamp <= end_time GROUP BY request_time ORDER BY request_time; ``` ## Configuration options Customize your Area Chart with the following options: * **X-Axis**: A path to label or the group by param * **Y-Axes**: An array of value path and color combination > [!TIP] > > Use dynamic bindings like `{{filter.timeLimit.start}}`, `{{filter.timeLimit.end}}` to make your chart respond to dashboard time filters. ## Best practices * Limit the number of series to keep the chart readable. * Apply consistent color palettes for related metrics. * Use filters (`{{filter.timeLimit.start}}`, `{{filter.timeLimit.end}}`) to enable dynamic dashboards # Bar Chart Widget Source: https://docs.openlit.io/latest/openlit/dashboards/widgets/bar-chart-widget Learn how to use the Bar Chart Widget in OpenLIT to compare grouped data using ClickHouse queries and dynamic filters. The **Bar Chart Widget** in OpenLIT is great for comparing categorical or grouped data - like errors by type, usage by region, or revenue by plan. You can customize the chart direction, stack behavior, and bind dynamic filters directly into your ClickHouse queries. ## When to use * Comparing usage across different platforms or regions * Visualizing grouped data like plan types, error categories, or product segments * Monitoring metrics over categories with filter support ## How to add a bar chart [How to Use the Bar Chart Widget in OpenLIT – Visualize Trends Over Time](https://www.youtube.com/embed/7Y94KEZI2hM?list=PLgErWFh-dyiGp9yUDnnx67wHEeNqzzMXZ) Example: ```sql theme={"theme":{"light":"github-light","dark":"github-dark"}} WITH parseDateTimeBestEffort('{{filter.timeLimit.start}}') AS start_time, parseDateTimeBestEffort('{{filter.timeLimit.end}}') AS end_time, SELECT SpanAttributes['gen_ai.request.model'] AS model, CAST(COUNT(SpanAttributes['gen_ai.request.model']) AS INTEGER) AS model_count FROM otel_traces WHERE SpanAttributes['gen_ai.operation.name'] != 'vectordb' AND notEmpty(SpanAttributes['gen_ai.request.model']) AND Timestamp >= start_time AND Timestamp <= end_time GROUP BY model ORDER BY model_count DESC LIMIT 3 ``` ## Configuration Options * **Color palette** * **X/Y label & value paths** > [!TIP] > > Use dynamic bindings like `{{filter.timeLimit.start}}`, `{{filter.timeLimit.end}}` to make your chart respond to dashboard time filters. ## Best practices * Sort bars by descending value for clearer insights * Limit category count (e.g., top 10) to avoid clutter # Line Chart Widget Source: https://docs.openlit.io/latest/openlit/dashboards/widgets/line-chart-widget Learn how to use the Line Chart Widget in OpenLIT to plot precise time series metrics using ClickHouse queries and dynamic bindings. The **Line Chart Widget** in OpenLIT helps you visualize changes in your metrics over time with sharp, precise lines. It’s ideal for tracking KPIs like API latency, CPU usage, conversion rate, or funnel performance. ## When to use Use a Line Chart when you want to: * Monitor trends over time with a high degree of accuracy * Display sharp, distinct movements in your data * Compare multiple metrics on the same time scale ## How to add a line chart [How to Use the Line Chart Widget in OpenLIT – Visualize Trends Over Time](https://www.youtube.com/embed/O8vrdaGb8bw?list=PLgErWFh-dyiGp9yUDnnx67wHEeNqzzMXZ) Example: ```sql theme={"theme":{"light":"github-light","dark":"github-dark"}} WITH parseDateTimeBestEffort('{{filter.timeLimit.start}}') AS start_time, parseDateTimeBestEffort('{{filter.timeLimit.end}}') AS end_time, dateDiff('day', start_time, end_time) AS days_diff, dateDiff('year', start_time, end_time) AS years_diff, multiIf( years_diff >= 1, 'month', days_diff <= 1, 'hour', 'day' ) AS date_granularity SELECT CAST(COUNT(*) AS INTEGER) AS total, formatDateTime(DATE_TRUNC(date_granularity, Timestamp), '%Y/%m/%d %R') AS request_time FROM otel_traces WHERE SpanAttributes['gen_ai.operation.name'] != 'vectordb' AND StatusCode IN ('STATUS_CODE_OK', 'STATUS_CODE_UNSET', 'Ok', 'Unset') AND Timestamp >= start_time AND Timestamp <= end_time GROUP BY request_time ORDER BY request_time ``` ## Customization options * \*\*Custom line colors * **X/Y label and value paths** > [!TIP] > > Use dynamic bindings like `{{filter.timeLimit.start}}`, `{{filter.timeLimit.end}}` to make your chart respond to dashboard time filters. ## Best practices * Use `GROUP BY` with consistent time buckets (e.g., minute, hour) * Order your results by time to ensure correct rendering # Markdown Widget Source: https://docs.openlit.io/latest/openlit/dashboards/widgets/markdown-widget Learn how to use the Markdown Widget in OpenLIT to add rich text, annotations, and links to your dashboards. The **Markdown Widget** lets you add formatted text, notes, links, and headings directly to your dashboards. It’s perfect for adding context, documentation, or separating sections visually. ## When to use * Add titles or descriptions to sections of your dashboard * Explain metrics or link to external documentation * Highlight goals, statuses, or next steps * Create visual separators between widgets ## How to use [How to Use the Markdown Widget in OpenLIT – Visualize Trends Over Time](https://www.youtube.com/embed/vBUk6_Z9XLE?list=PLgErWFh-dyiGp9yUDnnx67wHEeNqzzMXZ) Example: ```markdown theme={"theme":{"light":"github-light","dark":"github-dark"}} ### User Growth Traces This section highlights new user signups, retention, and churn over the last 30 days. - Trace updated hourly - Based on `user_events` table in ClickHouse ``` ## Supported markdown features * Headings (`#`, `##`, `###`) * Bold, italic, and strikethrough text * Lists and checkboxes * Code blocks and inline code * Hyperlinks and external references * Emojis 👍 🚀 ✅ ## Best practices * Keep notes concise and relevant to nearby widgets * Use emoji and headings to make sections scannable * Update text as data sources or queries change # Dashboard Widgets Source: https://docs.openlit.io/latest/openlit/dashboards/widgets/overview OpenLIT dashboard widgets for AI observability visualizations: time series charts, statistical summaries, and interactive tables. ## Available widget types > **[Area Chart](/latest/openlit/dashboards/widgets/area-chart-widget)** > > Visualize trends over time with smooth, layered charts. Great for metrics like usage, traffic, or revenue that change over time. > **[Bar Chart](/latest/openlit/dashboards/widgets/bar-chart-widget)** > > Compare categories or segments side-by-side. Ideal for showing grouped metrics like errors by type or revenue by plan. > **[Line Chart](/latest/openlit/dashboards/widgets/line-chart-widget)** > > Plot time-series data with precision. Perfect for tracking latency, load, or other performance metrics over time. > **[Markdown](/latest/openlit/dashboards/widgets/markdown-widget)** > > Add notes, context, or section headers using Markdown. Great for annotating dashboards or linking to documentation. > **[Pie Chart](/latest/openlit/dashboards/widgets/pie-chart-widget)** > > Show proportions and segment distribution clearly. Useful for visualizing things like user roles, traffic sources, or error categories. > **[Stat](/latest/openlit/dashboards/widgets/stat-widget)** > > Highlight key metrics like active users, revenue, or conversion rate. A fast and simple way to display KPIs. > **[Table](/latest/openlit/dashboards/widgets/table-widget)** > > Display structured data in a paginated, sortable table. Perfect for audit logs, event tracking, or error views. # Pie Chart Widget Source: https://docs.openlit.io/latest/openlit/dashboards/widgets/pie-chart-widget Learn how to use the Pie Chart Widget in OpenLIT to display proportions and segment distributions using ClickHouse data. The **Pie Chart Widget** in OpenLIT is perfect for showing how values are distributed across categories. Use it to represent proportions such as traffic sources, user roles, or error types. ## When to use * Visualize category breakdowns * Show relative proportions of segments (e.g. users by role) * Highlight top contributors to a total value * Display simple summaries at a glance ## How to add a pie chart [How to Use the Pie Chart Widget in OpenLIT – Visualize Trends Over Time](https://www.youtube.com/embed/vUEIXLiY2Kk?list=PLgErWFh-dyiGp9yUDnnx67wHEeNqzzMXZ) Example: ```sql theme={"theme":{"light":"github-light","dark":"github-dark"}} WITH parseDateTimeBestEffort('{{filter.timeLimit.start}}') AS start_time, parseDateTimeBestEffort('{{filter.timeLimit.end}}') AS end_time, SELECT DISTINCT ResourceAttributes['service.name'] as applicationName, ROUND(SUM(toFloat64OrZero(SpanAttributes['gen_ai.usage.cost'])), 5) AS cost FROM otel_traces WHERE SpanAttributes['gen_ai.operation.name'] != 'vectordb' AND notEmpty(SpanAttributes['gen_ai.usage.cost']) AND notEmpty(ResourceAttributes['service.name']) AND StatusCode IN ('STATUS_CODE_OK', 'STATUS_CODE_UNSET', 'Ok', 'Unset') AND Timestamp >= start_time AND Timestamp <= end_time GROUP BY applicationName; ``` ## Configuration options * Customize **colors** * Add paths for **labels** & **values** > [!TIP] > > Use dynamic bindings like `{{filter.timeLimit.start}}`, `{{filter.timeLimit.end}}` to make your chart respond to dashboard time filters. ## Best practices * Aggregate less-important categories into an "Other" group in SQL * Use percent labels for quick at-a-glance insights * Match colors with your product or brand theme for consistency # Stats Widget Source: https://docs.openlit.io/latest/openlit/dashboards/widgets/stat-widget Learn how to use the Statistics Widget in OpenLIT to display key metrics, KPIs, and summary values with real-time ClickHouse data. The **Statistics Widget** (or stat card) is designed to highlight your most important metrics in a clean, bold format. It’s ideal for surfacing KPIs, real-time values, or summary statistics at a glance. ## When to use * Display total signups, revenue, or active users * Show conversion rates, success percentages, or API error counts * Track live values on operational dashboards ## How to add a stat card [How to Use the Stat Widget in OpenLIT – Visualize Trends Over Time](https://www.youtube.com/embed/_XY28a192Io?list=PLgErWFh-dyiGp9yUDnnx67wHEeNqzzMXZ) Example: ```sql theme={"theme":{"light":"github-light","dark":"github-dark"}} WITH parseDateTimeBestEffort('{{filter.timeLimit.start}}') AS start_time, parseDateTimeBestEffort('{{filter.timeLimit.end}}') AS end_time, (end_time - start_time) AS duration, (start_time - duration) AS prev_start_time, (end_time - duration) AS prev_end_time\n\nSELECT round(sumIf(toFloat64OrZero(SpanAttributes['gen_ai.usage.cost']), Timestamp >= start_time AND Timestamp <= end_time), 7) AS total_usage_cost,\n round(sumIf(toFloat64OrZero(SpanAttributes['gen_ai.usage.cost']), Timestamp >= prev_start_time AND Timestamp <= prev_end_time), 7) AS total_usage_cost_previous,\n round( if( sumIf(toFloat64OrZero(SpanAttributes['gen_ai.usage.cost']), Timestamp >= prev_start_time AND Timestamp <= prev_end_time) = 0, sumIf(toFloat64OrZero(SpanAttributes['gen_ai.usage.cost']), Timestamp >= start_time AND Timestamp <= end_time) * 100.0, ( sumIf(toFloat64OrZero(SpanAttributes['gen_ai.usage.cost']), Timestamp >= start_time AND Timestamp <= end_time) - sumIf(toFloat64OrZero(SpanAttributes['gen_ai.usage.cost']), Timestamp >= prev_start_time AND Timestamp <= prev_end_time) ) / sumIf(toFloat64OrZero(SpanAttributes['gen_ai.usage.cost']), Timestamp >= prev_start_time AND Timestamp <= prev_end_time) * 100.0 ), 4 ) AS rate\nFROM otel_traces\nWHERE SpanAttributes['gen_ai.operation.name'] != 'vectordb' AND StatusCode IN ('STATUS_CODE_OK', 'STATUS_CODE_UNSET', 'Ok', 'Unset') AND Timestamp >= prev_start_time AND Timestamp <= end_time\n ``` ## Configuration options * Set **prefix/suffix** (e.g. `$`, `%`, `ms`) * Choose **colors** * Add **trend indicators** > [!TIP] > > Use dynamic filters like `{{filter.timeLimit.start}}`, `{{filter.timeLimit.end}}` to make the stat card responsive to dashboard-level controls. ## Best practices * Combine stat cards with charts for more context * Use consistent units (e.g., %, \$, MB) * Avoid overloading the dashboard with too many KPIs - focus on what matters # Table Widget Source: https://docs.openlit.io/latest/openlit/dashboards/widgets/table-widget Learn how to use the Table Widget in OpenLIT to display structured data with sorting, scrolling, and pagination using ClickHouse queries. The **Table Widget** in OpenLIT is designed to display raw or structured data in a readable, paginated table format. It's perfect for logs, event traces, audit trails, or any dataset you want to explore row by row. ## When to use * Displaying raw logs or error records * Viewing user activity, API calls, or event data * Showing detailed reports or audit trails * Exploring top rows of a complex query result ## How to add a table [How to Use the Table Widget in OpenLIT – Visualize Trends Over Time](https://www.youtube.com/embed/PsBBCKXlwsI?list=PLgErWFh-dyiGp9yUDnnx67wHEeNqzzMXZ) Example: ```sql theme={"theme":{"light":"github-light","dark":"github-dark"}} SELECT user_id, action, timestamp, properties FROM user_events WHERE event_time BETWEEN {{filter.timeLimit.start}} AND {{filter.timeLimit.end}} ORDER BY timestamp DESC LIMIT 100 ``` ## Features * **Horizontal and vertical scrolling** * Choose **colors theme** * **Dynamic bindings** for time ranges or filters > [!TIP] > > Use dynamic filters like `{{filter.timeLimit.start}}`, `{{filter.timeLimit.end}}` to make the stat card responsive to dashboard-level controls. ## Best practices * Use aliases for column names for readability (`AS user_email`, `AS created_at`) * Limit unnecessary columns - focus on actionable fields * Use `{{filter.timeLimit.start}}`, `{{filter.timeLimit.end}}` to make tables dynamic # Anonymous Usage Metrics Source: https://docs.openlit.io/latest/openlit/developer-resources/anonymous-telemetry How OpenLIT's anonymous usage metrics work, what is collected, and how to opt out of telemetry with TELEMETRY_ENABLED=false OpenLIT collects anonymous usage metrics by default so we can see which features are used and where to improve the product. Nothing here is meant to identify you personally - there is no email, name, password, secret, or span/trace content in what we send. There is **no in-app settings toggle** for this. Opt out with an environment variable on the OpenLIT deployment, then restart so the change takes effect. ## How to opt out of telemetry Set: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} TELEMETRY_ENABLED=false ``` That disables browser analytics, server-side usage events, and the daily install snapshot. Omit the variable (or set it to anything other than `false`) to leave collection on - Compose and `.env` examples ship with it enabled. ## What identifies an install (anonymously) | Signal | What it is | | - | - | | **User** | An opaque user id - never your email, name, or password | | **Organisation / project** | Opaque organisation and project group ids - never names or member emails | | **Install** | A stable random `install_id` per OpenLIT deployment | | **Environment** | Ordinary analytics context such as browser, OS, and approximate location | ## What we track When enabled, OpenLIT sends: * **Active usage** - opaque user / organisation / project activity, login and registration, and page path visits as people use the UI * **Feature adoption** - create/update/delete-style signals for prompts, rules, evaluations, dashboards, contexts, OpenGround, and database configurations (ids and counts, not names or contents) * **Auto features** - whether Auto Pricing and auto evaluation are turned on, and run outcomes * **SDK-facing API use** - success/failure when applications fetch prompts or Vault secrets through OpenLIT (the OpenLIT server records this; the SDK packages do not phone home on their own) * **Daily install snapshot** - once per day, aggregate totals for that install: OpenLIT version, counts of users / organisations / projects / database configs, whether auto pricing or auto evaluation is configured, and high-level ingestion volume counts We never send emails, names, passwords, secrets, API keys, prompt or rule text, or the contents of your spans and traces. ## Where metrics are stored Usage metrics are sent to PostHog for secure storage and product analytics. ## Frequently asked questions > **Is there a UI switch to disable this?** > > No. Opt out only via `TELEMETRY_ENABLED=false` on the OpenLIT process, then restart. > **Does disabling this stop my own OpenTelemetry traces?** > > No. This only controls OpenLIT's anonymous product usage metrics. Your application's OTLP traces, metrics, and logs are separate. *** > **[Configuration](/latest/openlit/configuration)** > > Full environment variable reference, including TELEMETRY\_ENABLED > **[Installation](/latest/openlit/installation)** > > Deploy OpenLIT and set environment variables for your setup # Create API Key Source: https://docs.openlit.io/latest/openlit/developer-resources/api-reference/endpoint/api-keys/create POST /api/api-key Create an OpenLIT API key, optionally restricted to specific features in OpenLIT Enterprise. # Delete API Key Source: https://docs.openlit.io/latest/openlit/developer-resources/api-reference/endpoint/api-keys/delete DELETE /api/api-key/{id} Revoke an OpenLIT API key. # List API Keys Source: https://docs.openlit.io/latest/openlit/developer-resources/api-reference/endpoint/api-keys/get GET /api/api-key List the API keys for the current database configuration, with each key's access. # Update API Key Access Source: https://docs.openlit.io/latest/openlit/developer-resources/api-reference/endpoint/api-keys/update PATCH /api/api-key/{id} Change which features an API key can use (OpenLIT Enterprise). > [!NOTE] > > **Enterprise feature.** This is part of [OpenLIT Enterprise](/latest/openlit/enterprise) and needs an enterprise license applied to your organisation; the community edition doesn't include it. [Book a call](https://cal.com/aman.openlit/30min) or email [developers@openlit.io](mailto:developers@openlit.io) to get access to the enterprise build and a license. # Export Pricing Source: https://docs.openlit.io/latest/openlit/developer-resources/api-reference/endpoint/manage-models/export GET /api/openground/models/export Returns pricing grouped by model type (chat, embeddings, images, audio) in OpenLIT SDK pricing_json shape. Authenticated export used by the **Export Pricing** button on Manage Models. ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X GET "/api/openground/models/export" \ -H "Cookie: " \ -o pricing.json ``` For a public URL the SDK can call without a session cookie, use [Public pricing export](/latest/openlit/developer-resources/api-reference/endpoint/manage-models/public-pricing). # Import Pricing Source: https://docs.openlit.io/latest/openlit/developer-resources/api-reference/endpoint/manage-models/import POST /api/openground/models/import Accepts a structured providers and models payload, or the SDK pricing_json object shape keyed by model type (chat, embeddings, and so on). Bulk import used by **Import Pricing JSON** on Manage Models. Existing provider + model ID pairs are skipped. ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST "/api/openground/models/import" \ -H "Content-Type: application/json" \ -H "Cookie: " \ -d '{ "providers": [ { "providerId": "my-provider", "displayName": "My Provider", "description": "Custom LLM provider" } ], "models": [ { "provider": "my-provider", "model_id": "my-model-v1", "displayName": "My Model v1", "inputPricePerMToken": 2.5, "outputPricePerMToken": 10 } ] }' ``` # Models Source: https://docs.openlit.io/latest/openlit/developer-resources/api-reference/endpoint/manage-models/models GET /api/openground/models Returns models grouped by provider. Optional `provider` query filters to one provider. Session-authenticated APIs used by the Manage Models UI for the active database configuration. ## Operations | Method | Path | Purpose | | - | - | - | | `GET` | `/api/openground/models` | List models (optional `?provider=`) | | `POST` | `/api/openground/models` | Create or update a model | | `DELETE` | `/api/openground/models?model_id=&provider=` | Delete a model | ## Examples **List** ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X GET "/api/openground/models" \ -H "Cookie: " ``` **Create or update** ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST "/api/openground/models" \ -H "Content-Type: application/json" \ -H "Cookie: " \ -d '{ "provider": "openai", "model": { "id": "gpt-4o-custom", "model_id": "gpt-4o-custom", "displayName": "GPT-4o Custom", "modelType": "chat", "contextWindow": 128000, "inputPricePerMToken": 2.5, "outputPricePerMToken": 10, "capabilities": ["function-calling"] } }' ``` **Delete** ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X DELETE "/api/openground/models?model_id=gpt-4o-custom&provider=openai" \ -H "Cookie: " ``` > [!NOTE] > > These routes require a signed-in OpenLIT browser session. They are not the same as API-key authenticated SDK endpoints such as Prompt Hub or Vault. # Providers Source: https://docs.openlit.io/latest/openlit/developer-resources/api-reference/endpoint/manage-models/providers GET /api/openground/providers Returns providers with supported models. Optional `provider` or `search` query params. Session-authenticated provider APIs for Manage Models. ## Operations | Method | Path | Purpose | | - | - | - | | `GET` | `/api/openground/providers` | List providers (optional `?provider=` or `?search=`) | | `POST` | `/api/openground/providers` | Create a provider | | `PUT` | `/api/openground/providers` | Update provider metadata | | `DELETE` | `/api/openground/providers?provider=` | Delete a provider and all its models | ## Examples **Create** ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X POST "/api/openground/providers" \ -H "Content-Type: application/json" \ -H "Cookie: " \ -d '{ "providerId": "my-provider", "displayName": "My Provider", "description": "Custom LLM provider", "requiresVault": true }' ``` **Delete** ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X DELETE "/api/openground/providers?provider=my-provider" \ -H "Cookie: " ``` > [!NOTE] > > Provider delete is available on this API only - the Manage Models UI does not expose a delete button for providers. # Public pricing export Source: https://docs.openlit.io/latest/openlit/developer-resources/api-reference/endpoint/manage-models/public-pricing GET /api/pricing/export/{dbConfigId} Returns SDK-compatible pricing for all models in that database config. No auth. Cached for 5 minutes. Public endpoint behind the **SDK Pricing URL** on Manage Models. No API key or session required. Copy the URL from Manage Models (it includes your database config id), or call: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -X GET "/api/pricing/export/" ``` ## SDK usage Replace the host and database config id with values from your OpenLIT deployment and the Manage Models **SDK Pricing URL** bar. **Python** ```python theme={"theme":{"light":"github-light","dark":"github-dark"}} import openlit openlit.init( otlp_endpoint="http://:4318", pricing_json="https:///api/pricing/export/", ) ``` **TypeScript** ```typescript theme={"theme":{"light":"github-light","dark":"github-dark"}} import openlit from "openlit"; openlit.init({ otlpEndpoint: "http://:4318", pricingJson: "https:///api/pricing/export/", }); ``` **Go** ```go theme={"theme":{"light":"github-light","dark":"github-dark"}} package main import openlit "github.com/openlit/openlit/sdk/go" func main() { openlit.Init(openlit.Config{ OtlpEndpoint: "http://:4318", PricingJson: "https:///api/pricing/export/", }) } ``` > [!WARNING] > > The path contains your database config id. The response is pricing only (no secrets or traces), but share the URL only with systems that should read your model prices. # Get Prompt Source: https://docs.openlit.io/latest/openlit/developer-resources/api-reference/endpoint/prompt-hub/get POST /api/prompt/get-compiled Fetches a compiled prompt using the provided prompt ID, version, and variables. ## SDK usage `url` and `api_key` (or `apiKey`) default to the `OPENLIT_URL` and `OPENLIT_API_KEY` environment variables if you don't pass them explicitly. **Python** ```python theme={"theme":{"light":"github-light","dark":"github-dark"}} import openlit response = openlit.get_prompt( name="prompt_name", should_compile=True, variables={ "name": "John", }, ) print(response) ``` ```shell Output theme={"theme":{"light":"github-light","dark":"github-dark"}} { err: null, res: { promptId: '88c4cbcd-87f9-4957-a37b-b41066e17471', name: 'prompt_name', version: '3.3.3', tags: [ 'user', 'greeting' ], metaProperties: { model: 'gpt4' }, prompt: 'Hello {{name}}, how are you today?', compiledPrompt: 'Hello John, how are you today?' } } ``` `url`, `api_key`, `name`, `prompt_id`, `version`, `should_compile`, `variables`, and `meta_properties` are all available parameters, matching the request body above. **TypeScript** ```typescript theme={"theme":{"light":"github-light","dark":"github-dark"}} import Openlit from 'openlit'; const response = await Openlit.getPrompts({ name: "prompt_name", shouldCompile: true, variables: { name: "John", } }); console.log(response); ``` ```shell Output theme={"theme":{"light":"github-light","dark":"github-dark"}} { err: null, res: { promptId: '88c4cbcd-87f9-4957-a37b-b41066e17471', name: 'prompt_name', version: '3.3.3', tags: [ 'user', 'greeting' ], metaProperties: { model: 'gpt4' }, prompt: 'Hello {{name}}, how are you today?', compiledPrompt: 'Hello John, how are you today?' } } ``` `url`, `apiKey`, `name`, `promptId`, `version`, `shouldCompile`, `variables`, and `metaProperties` are all available parameters, matching the request body above. # Evaluate Rules Source: https://docs.openlit.io/latest/openlit/developer-resources/api-reference/endpoint/rule-engine/evaluate POST /api/rule-engine/evaluate Evaluates all active rules against the provided input fields and returns matching rule IDs, linked entities, and optionally their full data. # Get scan findings Source: https://docs.openlit.io/latest/openlit/developer-resources/api-reference/endpoint/scanners/findings GET /api/scanners/findings Return the latest succeeded scan in this environment whose target matches repoUrl. Return the latest **succeeded** scan in the current project and environment whose target matches `repoUrl`. Accepts `https://github.com/owner/repo`, `.git`, `/tree/`, and `git@github.com:owner/repo` forms. Medium+ findings are listed first. When no scan matches, `matched` is `false`. See [Scanner connectors](/latest/openlit/connectors/scanner) for setup. # List scan jobs Source: https://docs.openlit.io/latest/openlit/developer-resources/api-reference/endpoint/scanners/list GET /api/scanners Return scanner connectors for the current project and environment, including recent jobs and findings. List scanner connectors for the current project and environment. Each connector includes recent scan **jobs** and their **findings** — use this to read the data of a scan after [Run scan](/latest/openlit/developer-resources/api-reference/endpoint/scanners/scan). Secrets are never returned. See [Scanner connectors](/latest/openlit/connectors/scanner) for setup. # Run scan Source: https://docs.openlit.io/latest/openlit/developer-resources/api-reference/endpoint/scanners/scan POST /api/scanners/{id}/scan Start a Trustabl scan for the connector. Missing body fields fall back to connector defaults. Returns the finished job and findings. Start a Trustabl scan on a scanner connector. Missing body fields use the connector defaults. The response includes the finished **job** and its **findings**. Do not send a GitHub token in the body — the token stays on the connector. The request can take several minutes (up to 300 seconds). See [Scanner connectors](/latest/openlit/connectors/scanner) for setup. # Get Secret(s) Source: https://docs.openlit.io/latest/openlit/developer-resources/api-reference/endpoint/vault/get POST /api/vault/get-secrets Fetches secret(s) using the provided key or tags. ## SDK usage `url` and `api_key` (or `apiKey`) default to the `OPENLIT_URL` and `OPENLIT_API_KEY` environment variables if you don't pass them explicitly. **Python** ```python theme={"theme":{"light":"github-light","dark":"github-dark"}} import openlit response = openlit.get_secrets( should_set_env=True ) print(response) ``` ```shell Output theme={"theme":{"light":"github-light","dark":"github-dark"}} { err: null, res: { ANTHROPIC_API_KEY: 'ANTHROPIC_API_VALUE', OPENAI_API_KEY: 'OPENAI_API_VALUE' } } ``` | Parameter | Description | | - | - | | `url` | Sets the OpenLIT URL. Defaults to the `OPENLIT_URL` environment variable. | | `api_key` | Sets the OpenLIT API Key. Can also be provided via the `OPENLIT_API_KEY` environment variable. | | `key` | Sets the key to fetch a specific secret. Optional | | `should_set_env` | Boolean value that sets all the secrets as environment variables for the application. Optional | | `tags` | Sets the tags for fetching only the secrets that have the mentioned tags assigned. Optional | `should_set_env` is client-side only: it doesn't change what's sent to OpenLIT, or what comes back in the response. It only controls whether the SDK, after receiving the secrets, also sets them directly as environment variables in your running process. **TypeScript** ```typescript theme={"theme":{"light":"github-light","dark":"github-dark"}} import Openlit from 'openlit'; const response = await Openlit.getSecrets({ shouldSetEnv: true, }); console.log(response); ``` ```shell Output theme={"theme":{"light":"github-light","dark":"github-dark"}} { err: null, res: { ANTHROPIC_API_KEY: 'ANTHROPIC_API_VALUE', OPENAI_API_KEY: 'OPENAI_API_VALUE' } } ``` | Parameter | Description | | - | - | | `url` | Sets the OpenLIT URL. Defaults to the `OPENLIT_URL` environment variable. | | `apiKey` | Sets the OpenLIT API Key. Can also be provided via the `OPENLIT_API_KEY` environment variable. | | `key` | Sets the key to fetch a specific secret. Optional | | `tags` | Sets the tags for fetching only the secrets that have the mentioned tags assigned. Optional | | `shouldSetEnv` | Boolean value that sets all the secrets as environment variables for the application. Optional | > [!NOTE] > > This endpoint always responds successfully (HTTP 200), even for an invalid API key. Check the `err` field in the response body rather than relying on the HTTP status code alone to know whether the request actually succeeded. ## Cross-origin browser requests `POST /api/vault/get-secrets` is an API-key authenticated endpoint for retrieving Vault secrets. Browser requests from a different origin are blocked unless the calling origin is explicitly allowed. To allow a browser application hosted on another domain, configure the OpenLIT deployment with a comma-separated origin allowlist: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} OPENLIT_ALLOWED_CORS_ORIGINS="https://app.example.com,https://admin.example.com" ``` `OPENLIT_ALLOWED_ORIGINS` is also supported as a backward-compatible alias. `NEXTAUTH_URL` is automatically treated as an allowed same-site origin. Use complete origins such as `https://app.example.com`. Do not configure wildcard origins for this endpoint. > [!NOTE] > > Server-to-server SDK or REST requests usually do not need CORS configuration because CORS is enforced by browsers. # Introduction Source: https://docs.openlit.io/latest/openlit/developer-resources/api-reference/introduction OpenAPI specification for API Endpoints in OpenLIT > **[Prompt Hub API](/latest/openlit/developer-resources/api-reference/endpoint/prompt-hub/get)** > **[Vault API](/latest/openlit/developer-resources/api-reference/endpoint/vault/get)** > **[Rule Engine API](/latest/openlit/developer-resources/api-reference/endpoint/rule-engine/evaluate)** > **[Manage Models API](/latest/openlit/developer-resources/api-reference/endpoint/manage-models/models)** > **[Scanner API](/latest/openlit/developer-resources/api-reference/endpoint/scanners/scan)** > **[API Keys API](/latest/openlit/developer-resources/api-reference/endpoint/api-keys/create)** ## Product surfaces In addition to the SDK-facing endpoints above, the OpenLIT client ships an interactive OpenAPI reference at **Settings → OpenAPI Spec** (`/openapi-spec`). That catalog also covers: * **AI Analysis** — `GET`/`POST /api/chat/improvement/{spanId}` for saved runs and streaming reviews of traces/spans * **Ask Otter** — `/api/chat/*` for config, conversations, streaming messages, SQL execute, widgets, and usage * **Telemetry** — logs, metrics, traces, exceptions, and span hierarchy APIs * **Scanner** — `POST /api/scanners/{id}/scan`, `GET /api/scanners`, and `GET /api/scanners/findings` for Trustabl jobs and findings See [AI Analysis](/latest/openlit/observability/telemetry/ai-analysis), [Chat with Otter](/latest/openlit/chat/overview), and [Scanner connectors](/latest/openlit/connectors/scanner) for product docs. ## Request context headers OpenLIT scopes data by **Organisation → Project → Environment**. **Signal routing** (latest) picks the connector for each signal from the active project + environment. ```http theme={"theme":{"light":"github-light","dark":"github-dark"}} Authorization: Bearer x-openlit-organisation-id: x-openlit-project-id: x-openlit-environment: production ``` ### Non-SDK APIs (telemetry, Ask Otter, AI Analysis, Scanner, …) Use signal routing headers only. Do **not** send `x-openlit-database-config-id`. The API key still binds a vault/credential database config (middleware injects `x-database-config-id`). ### SDK-facing APIs (Prompt Hub, Vault, Rule Engine) Prefer signal routing (`project` + `environment`) to resolve the intelligence ClickHouse that stores prompts, secrets, and rules. Existing SDKs that only send `Authorization: Bearer …` keep working via the API key's bound database config. Those endpoints also accept an explicit `x-openlit-database-config-id` when it belongs to the same project as the key. ## Authentication All documented endpoints require an Authorization header: ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} Authorization: Bearer ``` [Manage Models](/latest/openlit/developer-resources/api-reference/endpoint/manage-models/models) UI pages also accept a signed-in OpenLIT session in the browser. The [public pricing export](/latest/openlit/developer-resources/api-reference/endpoint/manage-models/public-pricing) requires no auth. ## API key access Create and manage keys under **Settings → API Keys**. Each key shows its access in the **Access** column. * **Community edition:** every API key has full access to every endpoint that accepts API keys. * **[OpenLIT Enterprise](/latest/openlit/enterprise):** with role-based access control, a key can be restricted to specific features when it's created, or later with **Edit access**. A restricted key gets a `403` (`This API key does not have access to this feature`) on endpoints outside its features, and OTLP ingestion needs the **Telemetry ingestion** feature. | Feature | Endpoints | | - | - | | Telemetry ingestion (`ingest`) | OTLP `/v1/traces`, `/v1/metrics`, `/v1/logs` | | Telemetry & metrics read (`telemetry`) | `/api/telemetry/*`, `/api/metrics/*` | | Prompt Hub (`prompts`) | `/api/prompt/*` | | Vault (`vault`) | `/api/vault/*` | | Rule engine (`rule_engine`) | `/api/rule-engine/*` | | Evaluations (`evaluation`) | `/api/evaluation/*` | | Otter chat (`chat`) | `/api/chat/*` | | Controller (`controller`) | `/api/controller/*` | | Database config (`db_config`) | `/api/db-config*` | Use [Create API Key](/latest/openlit/developer-resources/api-reference/endpoint/api-keys/create) and [Update API Key Access](/latest/openlit/developer-resources/api-reference/endpoint/api-keys/update) to manage access programmatically. API key management (`/api/api-key`) requires a signed-in session, so an API key can't list, create, change, or delete keys, including its own. # Local development Source: https://docs.openlit.io/latest/openlit/developer-resources/local-development Run the OpenLIT UI, OTLP receiver, realtime engine, and NATS from source with one command Use this page to run OpenLIT from a clone of the repository while you work on it. One script starts every service with hot reload for the UI. To run OpenLIT without changing it, use [Docker Compose](/latest/openlit/installation#docker) instead. `scripts/dev-realtime.sh` starts and supervises: | Service | Runs as | Default address | | - | - | - | | OpenLIT UI | `npm run dev` in `src/client` | `http://localhost:3000` | | OTLP receiver | `go run` in `src/otlp-receiver` | `127.0.0.1:4318` (HTTP), `127.0.0.1:4317` (gRPC) | | Realtime engine | `go run` in `src/engine` | `127.0.0.1:4330` (health and stats) | | NATS JetStream | Local `nats-server`, or Docker | `127.0.0.1:4222` | | ClickHouse | Docker (`src/dev-docker-compose.yml`), unless one already answers on `127.0.0.1:8123` | `127.0.0.1:8123` | Logs from every service share one terminal, prefixed with the service name. `Ctrl+C` stops everything, and if any service exits, the rest stop too. ## Prerequisites * Node.js 20 or later * Go 1.25 or later * Docker with Docker Compose v2.24 or later, for ClickHouse and NATS * `curl`, `openssl`, and `lsof` (preinstalled on macOS and most Linux distributions; on Windows, use WSL) * Python 3, only to send sample spans ## Start OpenLIT **1. Clone the repository and install the UI dependencies** ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} git clone https://github.com/openlit/openlit.git cd openlit/src/client npm install ``` **2. Create src/client/.env** ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} cp .env.example .env ``` Set at least these values: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} NEXTAUTH_SECRET= NEXTAUTH_URL=http://localhost:3000/ SQLITE_DATABASE_URL=file:./data.db ``` `file:./data.db` is relative to `src/client/prisma`, and `.db` files are ignored by git. The script reads the UI port from `NEXTAUTH_URL`. **3. Run the script from the repository root** The first time, add `SEED=1` to create the default user, organisation, project, and database configuration: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} cd ../.. SEED=1 scripts/dev-realtime.sh ``` After that, start it without `SEED`: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} scripts/dev-realtime.sh ``` The script applies Prisma migrations, starts ClickHouse if needed, then starts NATS, the UI, the receiver, and the engine. It prints the address of each service when they are up. **4. Sign in** Open `http://localhost:3000` and sign in with `user@openlit.io` and `openlituser`. ## Send sample spans Threshold [alerts](/latest/openlit/alerts) only see spans sent with an API key. 1. In OpenLIT, open **Settings → API Keys** and create a key. 2. Send spans to the local receiver: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} OPENLIT_API_KEY= scripts/dev-realtime-send-spans.py --count 20 --error-rate 1 ``` Useful flags include `--service`, `--model`, `--latency-ms`, `--cost`, and `--http-status`. Run the script with `--help` for the full list. If the dev script reported that port 4318 was busy and used 14318, pass `--endpoint http://127.0.0.1:14318`. 3. Open **Alerts**, create an alert from the **Error rate spike** template, and send the spans again. The alert fires within a few seconds. To point your own app at the local stack, use `openlit.init(otlp_endpoint="http://127.0.0.1:4318", otlp_headers={"Authorization": "Bearer "})`. ## Options Set any of these before the script, for example `UI_PORT=3001 scripts/dev-realtime.sh`. | Variable | Default | Effect | | - | - | - | | `UI_PORT` | Port of `NEXTAUTH_URL`, else `3000` | Next.js port | | `OTLP_HTTP_PORT` / `OTLP_GRPC_PORT` | `4318` / `4317` | Receiver ports. If the default is busy, the script falls back to `14318` / `14317` | | `NATS_PORT` / `NATS_MONITOR_PORT` | `4222` / `8222` | NATS ports | | `ENGINE_HTTP_ADDR` | `127.0.0.1:4330` | Engine health and stats address | | `INIT_DB_HOST`, `INIT_DB_PORT`, `INIT_DB_USERNAME`, `INIT_DB_PASSWORD`, `INIT_DB_DATABASE` | `127.0.0.1`, `8123`, `default`, `OPENLIT`, `openlit` | ClickHouse connection. Point these at your own ClickHouse to skip the Docker one | | `SEED=1` | Off | Seed the default user, organisation, project, and database configuration | | `SKIP_MIGRATE=1` | Off | Skip `prisma migrate deploy` | | `NATS_USE_DOCKER=1` | Off | Use Docker for NATS even when `nats-server` is installed | | `KEEP_NATS=1` | Off | Leave the NATS container running after the script exits | | `OPENLIT_SIGNAL_ATTRIBUTES` | Empty | Personal span attributes that alerts may match. See [Configuration](/latest/openlit/configuration#realtime-variables) | NATS uses `assets/nats.conf`, so local development has the same permissions as production. Its passwords, the engine secret, and the cron secret are random for each run. To skip Docker for NATS, install the server once: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} go install github.com/nats-io/nats-server/v2@v2.12.15 ``` ## Work on the UI only If you are not working on alerts or the realtime pipeline, you can run the UI on its own against ClickHouse: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} docker compose -f src/dev-docker-compose.yml up -d clickhouse export INIT_DB_HOST=127.0.0.1 INIT_DB_PORT=8123 INIT_DB_USERNAME=default INIT_DB_PASSWORD=OPENLIT INIT_DB_DATABASE=openlit cd src/client npx prisma migrate deploy && npx prisma generate npx prisma db seed # first run only npm run dev ``` Threshold alerts stay idle without the receiver, engine, and NATS. The rest of the UI works as usual. ## Run the checks Before you open a pull request, run the checks for what you changed: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} # UI cd src/client npm run lint npx tsc --noEmit npm test # OTLP receiver or engine cd src/otlp-receiver # or src/engine gofmt -l . go vet ./... go test ./... ``` See [CONTRIBUTING.md](https://github.com/openlit/openlit/blob/main/CONTRIBUTING.md) for the pull request process. ## Troubleshooting * **`UI port 3000 is already in use`**: stop the other process or set `UI_PORT`, and update `NEXTAUTH_URL` to match. * **`prisma migrate deploy failed`**: the SQLite file was created by a different branch. Point `SQLITE_DATABASE_URL` at a new file, or rerun with `SKIP_MIGRATE=1`. * **Alerts never fire**: spans must be sent with an API key from the same project, and the alert must be in the environment you send to (default `production`). * **`Docker Compose ... is too old`**: `src/dev-docker-compose.yml` needs Compose v2.24 or later. Upgrade Docker, or run your own ClickHouse and set `INIT_DB_HOST` and `INIT_DB_PORT`. * **`timed out waiting for NATS JetStream`**: the script prints the NATS container logs above this message. Rerun it, or install `nats-server` locally (see [Options](#options)) so NATS runs without Docker. # Connect Multiple Databases Source: https://docs.openlit.io/latest/openlit/developer-resources/multiple-db Add and switch between multiple ClickHouse Database Configs per project and environment in OpenLIT OpenLIT lets you attach multiple **Database Configs** (ClickHouse connections) inside a project, each tagged with an [environment](/latest/openlit/organisation/environments). Use this to separate staging vs production data, tenants, or retention tiers without redeploying `INIT_DB_*` env vars. Day-2 connection changes belong in **Organisation → Project → Database configurations / Connectors**, not in live container env vars. See [Database Config](/latest/openlit/organisation/database-config). ## When to use this feature * **Different environments** — keep `production` and `staging` ClickHouse clusters on separate configs * **Multi-tenancy** — isolate datasets per customer while sharing one OpenLIT deployment * **Centralized management** — switch active Database Config from the header **Databases** control ## Get started ![OpenLIT database configuration list on a project](https://mintlify.s3.us-west-1.amazonaws.com/openlit/images/organisation/database-config-list.png) 1. **Log in** to OpenLIT (`user@openlit.io` / `openlituser` on local seed installs). 2. Select **organisation**, **project**, and **environment** in the header. 3. Open **Organisation → Project → Database configurations** (or **Connectors → ClickHouse**). 4. Click **Add**, enter host, port, database, credentials, and environment. 5. **Save**, then mark the config **active** for your user. 6. Dashboards, requests, Prompt Hub, and Vault use the active Database Config for that project.