# 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 | Use kubelet PodResources socket; joins GPU UUID → `k8s.pod.name` / namespace / container | | `OPENLIT_K8S_POD_LOOKUP` | `true` in K8s when `K8S_NODE_NAME` is set | List pods on this node via the Kubernetes API (needs `list` on pods) for UID/container-id joins | | `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 | ## 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://otel-collector:4317" # 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 K8s node label / providerID discovery, 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"] --- 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 ``` 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 a local OTel Collector ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 \ ./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://otel-collector: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://otel-collector: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://otel-collector.monitoring.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 ``` Docker `--pid=host` (or Kubernetes `hostPID: true`) and `/dev/dri` are required for per-process GPU attribution via DRM fdinfo. *** Full metrics list with types, units, and attributes 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. 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://otel-collector: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://otel-collector: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://otel-collector.monitoring.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 ``` Docker `--pid=host` (or Kubernetes `hostPID: true`) and `/dev/dri` are required for per-process GPU attribution via DRM fdinfo. *** Full metrics list with types, units, and attributes 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://otel-collector: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://otel-collector: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://otel-collector.monitoring.svc.cluster.local:4318 resources: limits: nvidia.com/gpu: 1 securityContext: privileged: false ``` 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`). *** Full metrics list with types, units, and attributes 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 ``` 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"] --- 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://otel-collector:4317" # 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 ``` `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. The `nodes/get` ClusterRole enables OpenCost-style `host.type` / `cloud.provider` discovery from node labels without relying on IMDS (helpful when EKS hop limit is 1). 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 * 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 * 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` * 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) * 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`= | `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` | Via kubelet PodResources (GPU device join) and/or pod API lookup | | `container.id` | Runtime container ID when resolvable | `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. *** ## 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). # Overview Source: https://docs.openlit.io/latest/gpu-collector/overview OpenTelemetry-native GPU and host metrics collector for NVIDIA, AMD, and Intel GPUs 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 Utilization, memory, temperature, power, energy, clocks, PCIe/interconnect throughput, throttle, ECC/XID/RAS — NVIDIA, AMD, and Intel on Linux and Windows Per-process VRAM/util plus cmdline, OS state (zombie), owner, uptime, and LLM framework labels (`vllm`, `ollama`, …). Kubernetes pod name/namespace via PodResources 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 ``` 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. 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. 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. Metrics are exported via OTLP to any compatible backend - OpenLIT, Grafana, Datadog, New Relic, or a standard OTel Collector. *** Get the collector running in under 5 minutes with Docker 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) ```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`. ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} docker pull ghcr.io/openlit/otel-gpu-collector:latest ``` ```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). ```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). ```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. ```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. ```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" ``` 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://otel-collector:4318 deploy: resources: reservations: devices: - driver: nvidia count: all capabilities: [gpu] depends_on: - otel-collector 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. *** Full reference for all environment variables and defaults 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`. # 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). *** Provider, model, and Vault API key Aggregate tokens and cost across Otter features # Overview 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, and manage resources through conversation **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. Conversations, SQL results, widgets, and resource actions Pick AI Provider, Model, and a Vault API key Token and cost attribution by feature, provider, model, and date 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 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. 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. 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 Yes. Until a provider, model, and Vault API key are saved, Otter prompts you to configure Chat Settings. 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. *** Add providers and models Otter can select 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. *** Start conversations that show up in usage 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 the OpenLIT collector 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`. *** Drill from an aggregate metric down to the individual sessions behind it The same dashboard, scoped to one developer # Overview Source: https://docs.openlit.io/latest/openlit/coding-agents/overview OpenLIT ships first-class observability for AI coding agents - Claude Code, Cursor, Codex, and Windsurf - with no SDK and no code changes 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 existing OpenLIT collector. 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. 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 Install the CLI, wire Claude Code, Cursor, or Codex, and stop tracking when you need to The seeded dashboard - sessions, cost, acceptance, top tools and repos, session outcomes Every coding session, with a full conversational drill-in per session Per-developer rollups of activity, cost, and code impact Content capture modes, redaction tiers, and attribution safeguards Diagnose a missing row, silent hooks, or a stale database config ## 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 collector (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. *** 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. 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). *** 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 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`. `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. ```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 collector 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. ```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. 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. ```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`. 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 collector | | `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`) | - | | `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. *** 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/`). 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`). *** 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. 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. *** 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. **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 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 ``` 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 ``` 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 ``` Seed-only: username stored on the default Database Config. **Example**: ```yaml theme={"theme":{"light":"github-light","dark":"github-dark"}} export INIT_DB_USERNAME=default ``` 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 ``` 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 For detailed OAuth setup instructions, see the [OAuth Authentication Setup](/latest/openlit/oauth) guide. 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 ``` 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 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 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 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 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 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 ``` 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 ``` 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 ``` 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. 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="* * * * *" ``` 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 ``` 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 ``` 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 ``` 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 ``` ## Telemetry variables See [Anonymous Telemetry](/latest/openlit/developer-resources/anonymous-telemetry) for what OpenLIT's own usage telemetry collects and why. 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 ``` 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. 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 ``` 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 ``` 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 ``` 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 ``` 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 ``` `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 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. 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. 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 custom visualizations with flexible widgets, queries, and real-time AI monitoring Version, deploy, and collaborate on prompts with centralized management and tracking 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. 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 | 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. ## Add a Jaeger connector Add source dialog with the connector type menu open, including Jaeger Use the header selectors to pick the [project](/latest/openlit/organisation/projects) and [environment](/latest/openlit/organisation/environments). Go to **Configuration → Connectors** and choose **Add connector** / **Add source**. Select **Jaeger** from the data-source connector list. Set the Query URL (local all-in-one: `http://localhost:16686`) and auth if needed. Bind **traces** to the connector under [signal routing](/latest/openlit/organisation/signal-routing). 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 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 Claude, Mem0, and Zep for agent memory. Bind traces, logs, and metrics independently. Env vars → ClickHouse Database Config. Shared connector model and project scope. # Memory connectors Source: https://docs.openlit.io/latest/openlit/connectors/memory Connect OpenLIT to Claude, 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). 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 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 | 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 | \* Claude has no dedicated search API — OpenLIT filters listed memories locally for search queries. † Zep delete targets graph edges/nodes, not an entire session. ## Add a memory connector Use the header selectors for the target [project](/latest/openlit/organisation/projects) and [environment](/latest/openlit/organisation/environments). Go to **Configuration → Connectors**, open the **Memory** section in the catalog, and choose **Add connector**. Select **Claude**, **Mem0**, or **Zep**. 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. Run a health check, then save. Open **Memory** (`/memory`) and select the connector from the dropdown. 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 builds apply the same RBAC and audit 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 | API keys are stored encrypted on the connector (`enc:v1:…`) and decrypted only server-side for outbound requests. ## 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 Copy requires `connectors:update` (or equivalent memory mutation permission in enterprise). ## 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 Anthropic Managed Agents memory stores. Mem0 Platform REST API reference. Zep Cloud API and graph memory. ## Related Shared connector registry and project scope. Tempo, Loki, Prometheus, Jaeger, and ClickHouse. Chat with Otter across OpenLIT surfaces. Partition memory connectors by environment. # Connectors Source: https://docs.openlit.io/latest/openlit/connectors/overview Atomic integrations that connect OpenLIT to observability backends and external memory providers — 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 Connectors share one registry model across categories. **Data-source connectors** route telemetry reads; **memory connectors** connect OpenLIT to agent memory stores for browsing, editing, and Otter-powered search. ## Connector categories ClickHouse, Tempo, Loki, Prometheus, and Jaeger — read traces, logs, and metrics with per-signal routing. Claude, Mem0, and Zep — browse, search, write, and copy agent memories from the Memory page and Otter. ## 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] end proj --> env bindT --> DS1[Tempo] bindL --> DS2[Loki] bindM --> DS3[Prometheus] mem --> M1[Mem0] mem --> M2[Zep] ``` * **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. ## Common actions | Action | Data-source | Memory | | -------------------------------- | -------------------------------------- | -------------------------- | | Add / edit connector | Yes | Yes | | Store credentials | Vault (ClickHouse) or connector secret | Encrypted on connector | | Health check / test connection | Yes | Yes | | Validate AI signal | Yes (`gen_ai.*` markers) | — | | Bind signal | traces / logs / metrics | — | | Browse / search / write memories | — | Yes (capability-dependent) | Enterprise audit logs record connector create, update, delete, test, bind, and unbind events. RBAC permissions are `connectors:read|create|update|delete|test|bind` (owner/admin by default). ## Related How projects and environments scope connectors. Bind traces, logs, and metrics to data-source connectors. ClickHouse app store and built-in connector. Partition connectors by environment. # 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**. *** Edit the prices Analytics uses for LLM cost 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. Toggle **Enable Auto Pricing** on. Enter a standard cron expression in **Cron Schedule**. Examples: * `*/15 * * * *` - every 15 minutes * `0 * * * *` - every hour * `0 0 * * *` - once a day at midnight 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). 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 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. No. Auto Pricing only fills in missing or zero-cost spans - it never overwrites a cost your SDK or an upstream vendor already set. Active Auto Pricing schedules are restored automatically when OpenLIT starts again - you don't need to re-enable them after a redeploy. *** See spend and Auto Pricing run history Edit LLM model pricing used for cost calculation # Overview Source: https://docs.openlit.io/latest/openlit/costs/manage-models/overview Manage models on the Costs page - edit 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. Add or edit LLM providers (Provider ID, display name, Vault requirement) Browse, edit, add, or delete models and per-1M-token pricing Import Pricing JSON or Export Pricing for backup and sync 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. 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 It multiplies each span's input and output token counts by that model's input and output price per 1M tokens from Manage models. Yes. Add a provider if needed, then add a model with your own Model ID and pricing - or bulk-import via Import Pricing JSON. **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. *** Backfill or refresh gen\_ai.usage.cost from Manage models prices Compare models side-by-side using the same provider catalog # Overview Source: https://docs.openlit.io/latest/openlit/costs/overview AI cost tracking and AI cost analytics in OpenLIT - monitor LLM spend, manage model pricing, and backfill missing costs from Monitor → 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: Cost usage, Auto Pricing runs, and optimization charts Providers, per-model token prices, import/export, SDK pricing URL 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 They live on the Costs page under Monitor. Pricing settings are the **Configuration** tab; model catalog and prices are the **Manage models** tab. 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