Просмотр исходного кода

Migrate AI agent instructions to AGENTS.md following agents.md convention (#3903)

Signed-off-by: Claude <noreply@anthropic.com>
Co-authored-by: Claude <noreply@anthropic.com>
Warwick 14 часов назад
Родитель
Сommit
f23faaab98

+ 306 - 0
AGENTS.md

@@ -0,0 +1,306 @@
+# AGENTS.md - OpenCost AI Agent Guide
+
+This document provides guidance for AI assistants working with the OpenCost codebase.
+
+## AI Assistant Behaviour
+
+- Never include AI assistant session links or URLs (e.g. claude.ai) in commit messages or pull request bodies.
+
+## Project Overview
+
+OpenCost is an open source Kubernetes cost monitoring tool maintained by the Cloud Native Computing Foundation (CNCF). It provides real-time cost allocation, asset tracking, and cloud cost monitoring for Kubernetes clusters across multiple cloud providers.
+
+**Key Features:**
+- Real-time cost allocation by namespace, pod, controller, service, etc.
+- Multi-cloud cost monitoring (AWS, Azure, GCP, Alibaba, Oracle, OTC, DigitalOcean, Scaleway)
+- Dynamic on-demand pricing via cloud provider APIs
+- CSV-based custom pricing for on-prem clusters
+- MCP (Model Context Protocol) server for AI agent integration
+- Prometheus metrics export
+
+## Repository Structure
+
+```
+opencost/
+├── cmd/costmodel/          # Main entry point (main.go)
+├── core/                   # Core module (shared libraries)
+│   └── pkg/
+│       ├── clusters/       # Cluster management
+│       ├── env/            # Environment variable utilities
+│       ├── filter/         # Query filter implementations
+│       ├── log/            # Structured logging
+│       ├── model/          # Core data models
+│       ├── opencost/       # OpenCost domain types (Allocation, Asset, CloudCost)
+│       ├── storage/        # Storage abstractions
+│       └── util/           # Utility packages
+├── modules/
+│   ├── collector-source/   # Custom metrics collector (alternative to Prometheus)
+│   └── prometheus-source/  # Prometheus data source implementation
+├── pkg/
+│   ├── cloud/              # Cloud provider implementations
+│   │   ├── aws/
+│   │   ├── azure/
+│   │   ├── gcp/
+│   │   ├── alibaba/
+│   │   ├── oracle/
+│   │   ├── digitalocean/
+│   │   ├── scaleway/
+│   │   └── otc/            # Open Telekom Cloud
+│   ├── cloudcost/          # Cloud cost processing pipeline
+│   ├── clustercache/       # Kubernetes cluster caching
+│   ├── cmd/costmodel/      # Cost model command implementation
+│   ├── config/             # Configuration management
+│   ├── costmodel/          # Core cost model logic and API handlers
+│   ├── customcost/         # Custom cost plugin support
+│   ├── env/                # Environment variable definitions
+│   ├── mcp/                # MCP server implementation
+│   └── metrics/            # Prometheus metrics
+├── configs/                # Default pricing configurations
+├── kubernetes/             # Kubernetes manifests (deprecated - use Helm)
+├── protos/                 # Protocol buffer definitions
+├── spec/                   # OpenCost specification
+└── ui/                     # UI components (main UI in opencost/opencost-ui repo)
+```
+
+## Development Setup
+
+### Prerequisites
+
+- Go (see go.mod for the required version)
+- Docker with `buildx` support
+- [just](https://github.com/casey/just) - command runner
+- [Tilt](https://tilt.dev/) - for local Kubernetes development
+- Kubernetes cluster (local or remote)
+- Prometheus instance
+
+### Quick Start Commands
+
+```bash
+# Run all unit tests
+just test
+
+# Run tests for specific module
+just test-core
+just test-opencost
+just test-prometheus-source
+just test-collector-source
+
+# Build local binary
+just build-local
+
+# Run locally (requires Prometheus and optionally Kubernetes access)
+PROMETHEUS_SERVER_ENDPOINT="http://127.0.0.1:9080" go run ./cmd/costmodel/main.go
+
+# Start development environment with Tilt
+tilt up
+```
+
+### Running Locally Without Kubernetes
+
+Set `PROMETHEUS_SERVER_ENDPOINT` to your Prometheus URL:
+
+```bash
+# Port-forward to Prometheus in your cluster
+kubectl port-forward svc/prometheus-server 9080:80
+
+# Run OpenCost
+PROMETHEUS_SERVER_ENDPOINT="http://127.0.0.1:9080" go run ./cmd/costmodel/main.go
+```
+
+### Running Integration Tests
+
+```bash
+INTEGRATION=true just test-integration
+```
+
+## Build Commands
+
+```bash
+# Build local binary
+just build-local
+
+# Build multi-arch binaries
+just build-binary <version>
+
+# Build and push Docker image
+just build <image-tag> <release-version>
+
+# Validate protobuf definitions
+just validate-protobuf
+```
+
+## Key Environment Variables
+
+### Core Configuration
+
+| Variable | Default | Description |
+|----------|---------|-------------|
+| `PROMETHEUS_SERVER_ENDPOINT` | (required) | Prometheus server URL |
+| `API_PORT` | `9003` | OpenCost API port |
+| `CLUSTER_ID` | auto-detected | Cluster identifier |
+| `CONFIG_PATH` | `/var/configs` | Configuration directory |
+
+### MCP Server
+
+| Variable | Default | Description |
+|----------|---------|-------------|
+| `MCP_SERVER_ENABLED` | `false` | Enable MCP server |
+| `MCP_HTTP_PORT` | `8081` | MCP server HTTP port |
+
+### Cloud Providers
+
+| Variable | Description |
+|----------|-------------|
+| `AWS_ACCESS_KEY_ID` | AWS authentication |
+| `AWS_SECRET_ACCESS_KEY` | AWS authentication |
+| `AZURE_OFFER_ID` | Azure pricing offer ID |
+| `AZURE_BILLING_ACCOUNT` | Azure billing account |
+| `CLOUD_PROVIDER` | Force cloud provider (aws, azure, gcp, etc.) |
+| `USE_CSV_PROVIDER` | Enable CSV-based custom pricing |
+| `CSV_PATH` | Path to CSV pricing file |
+
+### Prometheus Settings
+
+| Variable | Default | Description |
+|----------|---------|-------------|
+| `PROMETHEUS_QUERY_TIMEOUT` | `120s` | Query timeout |
+| `PROMETHEUS_QUERY_RESOLUTION_SECONDS` | `300` | Query resolution |
+| `MAX_QUERY_CONCURRENCY` | `5` | Concurrent queries |
+| `PROM_CLUSTER_ID_LABEL` | `cluster_id` | Cluster ID label name |
+
+### Feature Flags
+
+| Variable | Default | Description |
+|----------|---------|-------------|
+| `CLOUD_COST_ENABLED` | `false` | Enable cloud cost ingestion |
+| `CARBON_ESTIMATES_ENABLED` | `false` | Enable carbon estimation |
+| `COLLECTOR_DATA_SOURCE_ENABLED` | `false` | Use collector instead of Prometheus |
+
+## API Endpoints
+
+Main API runs on port 9003 by default:
+
+| Endpoint | Description |
+|----------|-------------|
+| `GET /allocation` | Cost allocation data |
+| `GET /allocation/summary` | Summarized allocation |
+| `GET /assets` | Asset cost data |
+| `GET /assets/carbon` | Asset carbon estimates |
+| `GET /cloudCost` | Cloud cost data |
+| `GET /customCost/status` | Custom cost status |
+| `GET /metrics` | Prometheus metrics |
+
+## Code Conventions
+
+### Go Style
+
+- Use structured logging via `github.com/opencost/opencost/core/pkg/log`
+- Environment variables accessed through `pkg/env` or `core/pkg/env`
+- Errors should be wrapped with context
+
+**Before committing, always run:**
+```bash
+go fmt ./...
+go vet ./...
+```
+
+### Module Structure
+
+OpenCost uses Go workspace with multiple modules:
+- `github.com/opencost/opencost` - Main module
+- `github.com/opencost/opencost/core` - Core shared library
+- `github.com/opencost/opencost/modules/prometheus-source` - Prometheus integration
+- `github.com/opencost/opencost/modules/collector-source` - Metrics collector
+
+When adding dependencies, ensure they're added to the correct module.
+
+### Testing
+
+- Unit tests use standard Go testing (`*_test.go` files)
+- Integration tests require `INTEGRATION=true` environment variable
+- Use mocks for external dependencies
+- Test files should be co-located with implementation
+
+### Logging
+
+```go
+import "github.com/opencost/opencost/core/pkg/log"
+
+log.Infof("Processing allocation for window: %s", window)
+log.Errorf("Failed to query Prometheus: %v", err)
+log.Warnf("Missing pricing data, using defaults")
+log.Debugf("Detailed debug information")
+```
+
+## Pull Request Guidelines
+
+1. Link related issues using: `Fixes #123`, `Closes #456`
+2. Describe user-facing changes and breaking changes
+3. Include test coverage for new functionality
+4. Run `just test` before submitting
+5. Use signed commits (`Signed-off-by` header required)
+
+## Architecture Notes
+
+### Data Flow
+
+1. **Prometheus** collects Kubernetes metrics (CPU, memory, etc.)
+2. **OpenCost** queries Prometheus for resource usage data
+3. **Cloud Provider** APIs provide pricing information
+4. **Cost Model** combines usage × pricing to compute costs
+5. **API/MCP** exposes cost data to users and AI agents
+
+### Key Types
+
+- `Allocation` - Cost allocation for a workload over a time window
+- `Asset` - Infrastructure asset (node, disk, load balancer)
+- `CloudCost` - Cloud service costs from billing APIs
+- `Window` - Time range for queries
+
+### Cloud Provider Detection
+
+OpenCost auto-detects the cloud provider from:
+1. `CLOUD_PROVIDER` environment variable (explicit override)
+2. Kubernetes node labels
+3. Instance metadata services
+
+## Common Tasks
+
+### Adding a New Cloud Provider
+
+1. Create package under `pkg/cloud/<provider>/`
+2. Implement the `models.Provider` interface
+3. Add environment variables in `pkg/env/costmodel.go`
+4. Register in `pkg/cloud/provider/provider.go`
+5. Add default pricing config in `configs/`
+
+### Adding a New API Endpoint
+
+1. Add handler method to `pkg/costmodel/router.go` or appropriate file
+2. Register route in `pkg/cmd/costmodel/costmodel.go`
+3. Add tests in corresponding `*_test.go` file
+
+### Modifying Protobuf Definitions
+
+1. Edit `.proto` files in `protos/`
+2. Run `./generate.sh` to regenerate Go code
+3. Run `just validate-protobuf` to verify
+
+## Cost Model Concepts
+
+Core formulas from the OpenCost Specification (`spec/opencost-specv01.md`):
+
+- **Total Cluster Costs** = Cluster Asset Costs + Cluster Overhead Costs
+- **Cluster Asset Costs** = Resource Allocation Costs + Resource Usage Costs
+- **Workload Costs** = max(request, usage) for CPU/memory resources
+- **Idle Costs** = Allocation costs not attributed to any workload
+
+## Useful Links
+
+- [OpenCost Documentation](https://www.opencost.io/docs/)
+- [OpenCost Specification](spec/opencost-specv01.md)
+- [Helm Chart](https://github.com/opencost/opencost-helm-chart)
+- [UI Repository](https://github.com/opencost/opencost-ui)
+- [Integration Tests](https://github.com/opencost/opencost-integration-tests)
+- [Plugins](https://github.com/opencost/opencost-plugins)
+- [CNCF Slack #opencost](https://cloud-native.slack.com/archives/C03D56FPD4G)

+ 2 - 305
CLAUDE.md

@@ -1,306 +1,3 @@
-# CLAUDE.md - OpenCost AI Assistant Guide
+This project's AI agent instructions live in [AGENTS.md](AGENTS.md), following the [agents.md](https://agents.md) convention. The import below pulls them in for Claude Code.
 
 
-This document provides guidance for AI assistants working with the OpenCost codebase.
-
-## AI Assistant Behaviour
-
-- Never include claude.ai session links or URLs in commit messages or pull request bodies.
-
-## Project Overview
-
-OpenCost is an open source Kubernetes cost monitoring tool maintained by the Cloud Native Computing Foundation (CNCF). It provides real-time cost allocation, asset tracking, and cloud cost monitoring for Kubernetes clusters across multiple cloud providers.
-
-**Key Features:**
-- Real-time cost allocation by namespace, pod, controller, service, etc.
-- Multi-cloud cost monitoring (AWS, Azure, GCP, Alibaba, Oracle, OTC, DigitalOcean, Scaleway)
-- Dynamic on-demand pricing via cloud provider APIs
-- CSV-based custom pricing for on-prem clusters
-- MCP (Model Context Protocol) server for AI agent integration
-- Prometheus metrics export
-
-## Repository Structure
-
-```
-opencost/
-├── cmd/costmodel/          # Main entry point (main.go)
-├── core/                   # Core module (shared libraries)
-│   └── pkg/
-│       ├── clusters/       # Cluster management
-│       ├── env/            # Environment variable utilities
-│       ├── filter/         # Query filter implementations
-│       ├── log/            # Structured logging
-│       ├── model/          # Core data models
-│       ├── opencost/       # OpenCost domain types (Allocation, Asset, CloudCost)
-│       ├── storage/        # Storage abstractions
-│       └── util/           # Utility packages
-├── modules/
-│   ├── collector-source/   # Custom metrics collector (alternative to Prometheus)
-│   └── prometheus-source/  # Prometheus data source implementation
-├── pkg/
-│   ├── cloud/              # Cloud provider implementations
-│   │   ├── aws/
-│   │   ├── azure/
-│   │   ├── gcp/
-│   │   ├── alibaba/
-│   │   ├── oracle/
-│   │   ├── digitalocean/
-│   │   ├── scaleway/
-│   │   └── otc/            # Open Telekom Cloud
-│   ├── cloudcost/          # Cloud cost processing pipeline
-│   ├── clustercache/       # Kubernetes cluster caching
-│   ├── cmd/costmodel/      # Cost model command implementation
-│   ├── config/             # Configuration management
-│   ├── costmodel/          # Core cost model logic and API handlers
-│   ├── customcost/         # Custom cost plugin support
-│   ├── env/                # Environment variable definitions
-│   ├── mcp/                # MCP server implementation
-│   └── metrics/            # Prometheus metrics
-├── configs/                # Default pricing configurations
-├── kubernetes/             # Kubernetes manifests (deprecated - use Helm)
-├── protos/                 # Protocol buffer definitions
-├── spec/                   # OpenCost specification
-└── ui/                     # UI components (main UI in opencost/opencost-ui repo)
-```
-
-## Development Setup
-
-### Prerequisites
-
-- Go 1.25+ (see go.mod for exact version)
-- Docker with `buildx` support
-- [just](https://github.com/casey/just) - command runner
-- [Tilt](https://tilt.dev/) - for local Kubernetes development
-- Kubernetes cluster (local or remote)
-- Prometheus instance
-
-### Quick Start Commands
-
-```bash
-# Run all unit tests
-just test
-
-# Run tests for specific module
-just test-core
-just test-opencost
-just test-prometheus-source
-just test-collector-source
-
-# Build local binary
-just build-local
-
-# Run locally (requires Prometheus and optionally Kubernetes access)
-PROMETHEUS_SERVER_ENDPOINT="http://127.0.0.1:9080" go run ./cmd/costmodel/main.go
-
-# Start development environment with Tilt
-tilt up
-```
-
-### Running Locally Without Kubernetes
-
-Set `PROMETHEUS_SERVER_ENDPOINT` to your Prometheus URL:
-
-```bash
-# Port-forward to Prometheus in your cluster
-kubectl port-forward svc/prometheus-server 9080:80
-
-# Run OpenCost
-PROMETHEUS_SERVER_ENDPOINT="http://127.0.0.1:9080" go run ./cmd/costmodel/main.go
-```
-
-### Running Integration Tests
-
-```bash
-INTEGRATION=true just test-integration
-```
-
-## Build Commands
-
-```bash
-# Build local binary
-just build-local
-
-# Build multi-arch binaries
-just build-binary <version>
-
-# Build and push Docker image
-just build <image-tag> <release-version>
-
-# Validate protobuf definitions
-just validate-protobuf
-```
-
-## Key Environment Variables
-
-### Core Configuration
-
-| Variable | Default | Description |
-|----------|---------|-------------|
-| `PROMETHEUS_SERVER_ENDPOINT` | (required) | Prometheus server URL |
-| `API_PORT` | `9003` | OpenCost API port |
-| `CLUSTER_ID` | auto-detected | Cluster identifier |
-| `CONFIG_PATH` | `/var/configs` | Configuration directory |
-
-### MCP Server
-
-| Variable | Default | Description |
-|----------|---------|-------------|
-| `MCP_SERVER_ENABLED` | `false` | Enable MCP server |
-| `MCP_HTTP_PORT` | `8081` | MCP server HTTP port |
-
-### Cloud Providers
-
-| Variable | Description |
-|----------|-------------|
-| `AWS_ACCESS_KEY_ID` | AWS authentication |
-| `AWS_SECRET_ACCESS_KEY` | AWS authentication |
-| `AZURE_OFFER_ID` | Azure pricing offer ID |
-| `AZURE_BILLING_ACCOUNT` | Azure billing account |
-| `CLOUD_PROVIDER` | Force cloud provider (aws, azure, gcp, etc.) |
-| `USE_CSV_PROVIDER` | Enable CSV-based custom pricing |
-| `CSV_PATH` | Path to CSV pricing file |
-
-### Prometheus Settings
-
-| Variable | Default | Description |
-|----------|---------|-------------|
-| `PROMETHEUS_QUERY_TIMEOUT` | `120s` | Query timeout |
-| `PROMETHEUS_QUERY_RESOLUTION_SECONDS` | `300` | Query resolution |
-| `MAX_QUERY_CONCURRENCY` | `5` | Concurrent queries |
-| `PROM_CLUSTER_ID_LABEL` | `cluster_id` | Cluster ID label name |
-
-### Feature Flags
-
-| Variable | Default | Description |
-|----------|---------|-------------|
-| `CLOUD_COST_ENABLED` | `false` | Enable cloud cost ingestion |
-| `CARBON_ESTIMATES_ENABLED` | `false` | Enable carbon estimation |
-| `COLLECTOR_DATA_SOURCE_ENABLED` | `false` | Use collector instead of Prometheus |
-
-## API Endpoints
-
-Main API runs on port 9003 by default:
-
-| Endpoint | Description |
-|----------|-------------|
-| `GET /allocation` | Cost allocation data |
-| `GET /allocation/summary` | Summarized allocation |
-| `GET /assets` | Asset cost data |
-| `GET /assets/carbon` | Asset carbon estimates |
-| `GET /cloudCost` | Cloud cost data |
-| `GET /customCost/status` | Custom cost status |
-| `GET /metrics` | Prometheus metrics |
-
-## Code Conventions
-
-### Go Style
-
-- Use structured logging via `github.com/opencost/opencost/core/pkg/log`
-- Environment variables accessed through `pkg/env` or `core/pkg/env`
-- Errors should be wrapped with context
-
-**Before committing, always run:**
-```bash
-go fmt ./...
-go vet ./...
-```
-
-### Module Structure
-
-OpenCost uses Go workspace with multiple modules:
-- `github.com/opencost/opencost` - Main module
-- `github.com/opencost/opencost/core` - Core shared library
-- `github.com/opencost/opencost/modules/prometheus-source` - Prometheus integration
-- `github.com/opencost/opencost/modules/collector-source` - Metrics collector
-
-When adding dependencies, ensure they're added to the correct module.
-
-### Testing
-
-- Unit tests use standard Go testing (`*_test.go` files)
-- Integration tests require `INTEGRATION=true` environment variable
-- Use mocks for external dependencies
-- Test files should be co-located with implementation
-
-### Logging
-
-```go
-import "github.com/opencost/opencost/core/pkg/log"
-
-log.Infof("Processing allocation for window: %s", window)
-log.Errorf("Failed to query Prometheus: %v", err)
-log.Warnf("Missing pricing data, using defaults")
-log.Debugf("Detailed debug information")
-```
-
-## Pull Request Guidelines
-
-1. Link related issues using: `Fixes #123`, `Closes #456`
-2. Describe user-facing changes and breaking changes
-3. Include test coverage for new functionality
-4. Run `just test` before submitting
-5. Use signed commits (`Signed-off-by` header required)
-
-## Architecture Notes
-
-### Data Flow
-
-1. **Prometheus** collects Kubernetes metrics (CPU, memory, etc.)
-2. **OpenCost** queries Prometheus for resource usage data
-3. **Cloud Provider** APIs provide pricing information
-4. **Cost Model** combines usage × pricing to compute costs
-5. **API/MCP** exposes cost data to users and AI agents
-
-### Key Types
-
-- `Allocation` - Cost allocation for a workload over a time window
-- `Asset` - Infrastructure asset (node, disk, load balancer)
-- `CloudCost` - Cloud service costs from billing APIs
-- `Window` - Time range for queries
-
-### Cloud Provider Detection
-
-OpenCost auto-detects the cloud provider from:
-1. `CLOUD_PROVIDER` environment variable (explicit override)
-2. Kubernetes node labels
-3. Instance metadata services
-
-## Common Tasks
-
-### Adding a New Cloud Provider
-
-1. Create package under `pkg/cloud/<provider>/`
-2. Implement the `models.Provider` interface
-3. Add environment variables in `pkg/env/costmodel.go`
-4. Register in `pkg/cloud/provider/provider.go`
-5. Add default pricing config in `configs/`
-
-### Adding a New API Endpoint
-
-1. Add handler method to `pkg/costmodel/router.go` or appropriate file
-2. Register route in `pkg/cmd/costmodel/costmodel.go`
-3. Add tests in corresponding `*_test.go` file
-
-### Modifying Protobuf Definitions
-
-1. Edit `.proto` files in `protos/`
-2. Run `./generate.sh` to regenerate Go code
-3. Run `just validate-protobuf` to verify
-
-## Cost Model Concepts
-
-Core formulas from the OpenCost Specification (`spec/opencost-specv01.md`):
-
-- **Total Cluster Costs** = Cluster Asset Costs + Cluster Overhead Costs
-- **Cluster Asset Costs** = Resource Allocation Costs + Resource Usage Costs
-- **Workload Costs** = max(request, usage) for CPU/memory resources
-- **Idle Costs** = Allocation costs not attributed to any workload
-
-## Useful Links
-
-- [OpenCost Documentation](https://www.opencost.io/docs/)
-- [OpenCost Specification](spec/opencost-specv01.md)
-- [Helm Chart](https://github.com/opencost/opencost-helm-chart)
-- [UI Repository](https://github.com/opencost/opencost-ui)
-- [Integration Tests](https://github.com/opencost/opencost-integration-tests)
-- [Plugins](https://github.com/opencost/opencost-plugins)
-- [CNCF Slack #opencost](https://cloud-native.slack.com/archives/C03D56FPD4G)
+@./AGENTS.md

+ 3 - 3
core/pkg/source/decoders.go

@@ -2140,7 +2140,7 @@ func DecodeInferenceTokensResult(result *QueryResult) *InferenceTokensResult {
 	modelName, _ := result.GetString("model_name")
 	modelName, _ := result.GetString("model_name")
 	namespace, _ := result.GetString("namespace")
 	namespace, _ := result.GetString("namespace")
 	key := modelName + ":" + namespace
 	key := modelName + ":" + namespace
-	
+
 	// Get the value from the last vector point if available
 	// Get the value from the last vector point if available
 	var value float64
 	var value float64
 	if len(result.Values) > 0 {
 	if len(result.Values) > 0 {
@@ -2158,7 +2158,7 @@ func DecodeInferenceProcessingTimeResult(result *QueryResult) *InferenceProcessi
 	modelName, _ := result.GetString("model_name")
 	modelName, _ := result.GetString("model_name")
 	namespace, _ := result.GetString("namespace")
 	namespace, _ := result.GetString("namespace")
 	key := modelName + ":" + namespace
 	key := modelName + ":" + namespace
-	
+
 	// Get the value from the last vector point if available
 	// Get the value from the last vector point if available
 	var value float64
 	var value float64
 	if len(result.Values) > 0 {
 	if len(result.Values) > 0 {
@@ -2176,7 +2176,7 @@ func DecodeInferenceCacheConfigResult(result *QueryResult) *InferenceCacheConfig
 	modelName, _ := result.GetString("model_name")
 	modelName, _ := result.GetString("model_name")
 	namespace, _ := result.GetString("namespace")
 	namespace, _ := result.GetString("namespace")
 	key := modelName + ":" + namespace
 	key := modelName + ":" + namespace
-	
+
 	// Get the value from the last vector point if available
 	// Get the value from the last vector point if available
 	var prefixCachingEnabled float64
 	var prefixCachingEnabled float64
 	if len(result.Values) > 0 {
 	if len(result.Values) > 0 {

+ 0 - 1
modules/collector-source/pkg/collector/metricsquerier.go

@@ -749,7 +749,6 @@ func (c *collectorMetricsQuerier) QueryDataCoverage(limitDays int) (time.Time, t
 	return c.collectorProvider.GetDailyDataCoverage(limitDays)
 	return c.collectorProvider.GetDailyDataCoverage(limitDays)
 }
 }
 
 
-
 // Inference cost methods - not supported by collector source (only available via Prometheus)
 // Inference cost methods - not supported by collector source (only available via Prometheus)
 func (c *collectorMetricsQuerier) QueryInferencePromptTokens(start, end time.Time) *source.Future[source.InferenceTokensResult] {
 func (c *collectorMetricsQuerier) QueryInferencePromptTokens(start, end time.Time) *source.Future[source.InferenceTokensResult] {
 	ch := make(source.QueryResultsChan, 1)
 	ch := make(source.QueryResultsChan, 1)

+ 30 - 30
modules/prometheus-source/pkg/prom/inference_queries.go

@@ -13,10 +13,10 @@ import (
 // QueryInferencePromptTokens implements MetricsQuerier.QueryInferencePromptTokens
 // QueryInferencePromptTokens implements MetricsQuerier.QueryInferencePromptTokens
 func (pds *PrometheusMetricsQuerier) QueryInferencePromptTokens(start, end time.Time) *source.Future[source.InferenceTokensResult] {
 func (pds *PrometheusMetricsQuerier) QueryInferencePromptTokens(start, end time.Time) *source.Future[source.InferenceTokensResult] {
 	ctx := pds.promContexts.NewNamedContext(ClusterContextName)
 	ctx := pds.promContexts.NewNamedContext(ClusterContextName)
-	
+
 	// Create a channel for the async result
 	// Create a channel for the async result
 	resultsChan := make(source.QueryResultsChan, 1)
 	resultsChan := make(source.QueryResultsChan, 1)
-	
+
 	// Execute query asynchronously
 	// Execute query asynchronously
 	go func() {
 	go func() {
 		values, err := queryCounterDelta(ctx, "vllm:prompt_tokens_total", start, end)
 		values, err := queryCounterDelta(ctx, "vllm:prompt_tokens_total", start, end)
@@ -24,112 +24,112 @@ func (pds *PrometheusMetricsQuerier) QueryInferencePromptTokens(start, end time.
 			resultsChan <- &source.QueryResults{Error: err}
 			resultsChan <- &source.QueryResults{Error: err}
 			return
 			return
 		}
 		}
-		
+
 		// Convert map to QueryResults format
 		// Convert map to QueryResults format
 		results := mapToQueryResults(values)
 		results := mapToQueryResults(values)
 		resultsChan <- &source.QueryResults{Results: results}
 		resultsChan <- &source.QueryResults{Results: results}
 	}()
 	}()
-	
+
 	return source.NewFuture(decodeInferenceTokensResult, resultsChan)
 	return source.NewFuture(decodeInferenceTokensResult, resultsChan)
 }
 }
 
 
 // QueryInferenceGenerationTokens implements MetricsQuerier.QueryInferenceGenerationTokens
 // QueryInferenceGenerationTokens implements MetricsQuerier.QueryInferenceGenerationTokens
 func (pds *PrometheusMetricsQuerier) QueryInferenceGenerationTokens(start, end time.Time) *source.Future[source.InferenceTokensResult] {
 func (pds *PrometheusMetricsQuerier) QueryInferenceGenerationTokens(start, end time.Time) *source.Future[source.InferenceTokensResult] {
 	ctx := pds.promContexts.NewNamedContext(ClusterContextName)
 	ctx := pds.promContexts.NewNamedContext(ClusterContextName)
-	
+
 	resultsChan := make(source.QueryResultsChan, 1)
 	resultsChan := make(source.QueryResultsChan, 1)
-	
+
 	go func() {
 	go func() {
 		values, err := queryCounterDelta(ctx, "vllm:generation_tokens_total", start, end)
 		values, err := queryCounterDelta(ctx, "vllm:generation_tokens_total", start, end)
 		if err != nil {
 		if err != nil {
 			resultsChan <- &source.QueryResults{Error: err}
 			resultsChan <- &source.QueryResults{Error: err}
 			return
 			return
 		}
 		}
-		
+
 		results := mapToQueryResults(values)
 		results := mapToQueryResults(values)
 		resultsChan <- &source.QueryResults{Results: results}
 		resultsChan <- &source.QueryResults{Results: results}
 	}()
 	}()
-	
+
 	return source.NewFuture(decodeInferenceTokensResult, resultsChan)
 	return source.NewFuture(decodeInferenceTokensResult, resultsChan)
 }
 }
 
 
 // QueryInferenceInputProcessingTime implements MetricsQuerier.QueryInferenceInputProcessingTime
 // QueryInferenceInputProcessingTime implements MetricsQuerier.QueryInferenceInputProcessingTime
 func (pds *PrometheusMetricsQuerier) QueryInferenceInputProcessingTime(start, end time.Time) *source.Future[source.InferenceProcessingTimeResult] {
 func (pds *PrometheusMetricsQuerier) QueryInferenceInputProcessingTime(start, end time.Time) *source.Future[source.InferenceProcessingTimeResult] {
 	ctx := pds.promContexts.NewNamedContext(ClusterContextName)
 	ctx := pds.promContexts.NewNamedContext(ClusterContextName)
-	
+
 	resultsChan := make(source.QueryResultsChan, 1)
 	resultsChan := make(source.QueryResultsChan, 1)
-	
+
 	go func() {
 	go func() {
 		values, err := queryCounterDelta(ctx, "vllm:request_prefill_time_seconds_sum", start, end)
 		values, err := queryCounterDelta(ctx, "vllm:request_prefill_time_seconds_sum", start, end)
 		if err != nil {
 		if err != nil {
 			resultsChan <- &source.QueryResults{Error: err}
 			resultsChan <- &source.QueryResults{Error: err}
 			return
 			return
 		}
 		}
-		
+
 		results := mapToQueryResults(values)
 		results := mapToQueryResults(values)
 		resultsChan <- &source.QueryResults{Results: results}
 		resultsChan <- &source.QueryResults{Results: results}
 	}()
 	}()
-	
+
 	return source.NewFuture(decodeInferenceProcessingTimeResult, resultsChan)
 	return source.NewFuture(decodeInferenceProcessingTimeResult, resultsChan)
 }
 }
 
 
 // QueryInferenceOutputProcessingTime implements MetricsQuerier.QueryInferenceOutputProcessingTime
 // QueryInferenceOutputProcessingTime implements MetricsQuerier.QueryInferenceOutputProcessingTime
 func (pds *PrometheusMetricsQuerier) QueryInferenceOutputProcessingTime(start, end time.Time) *source.Future[source.InferenceProcessingTimeResult] {
 func (pds *PrometheusMetricsQuerier) QueryInferenceOutputProcessingTime(start, end time.Time) *source.Future[source.InferenceProcessingTimeResult] {
 	ctx := pds.promContexts.NewNamedContext(ClusterContextName)
 	ctx := pds.promContexts.NewNamedContext(ClusterContextName)
-	
+
 	resultsChan := make(source.QueryResultsChan, 1)
 	resultsChan := make(source.QueryResultsChan, 1)
-	
+
 	go func() {
 	go func() {
 		values, err := queryCounterDelta(ctx, "vllm:request_time_per_output_token_seconds_sum", start, end)
 		values, err := queryCounterDelta(ctx, "vllm:request_time_per_output_token_seconds_sum", start, end)
 		if err != nil {
 		if err != nil {
 			resultsChan <- &source.QueryResults{Error: err}
 			resultsChan <- &source.QueryResults{Error: err}
 			return
 			return
 		}
 		}
-		
+
 		results := mapToQueryResults(values)
 		results := mapToQueryResults(values)
 		resultsChan <- &source.QueryResults{Results: results}
 		resultsChan <- &source.QueryResults{Results: results}
 	}()
 	}()
-	
+
 	return source.NewFuture(decodeInferenceProcessingTimeResult, resultsChan)
 	return source.NewFuture(decodeInferenceProcessingTimeResult, resultsChan)
 }
 }
 
 
 // QueryInferenceCachedTokens implements MetricsQuerier.QueryInferenceCachedTokens
 // QueryInferenceCachedTokens implements MetricsQuerier.QueryInferenceCachedTokens
 func (pds *PrometheusMetricsQuerier) QueryInferenceCachedTokens(start, end time.Time) *source.Future[source.InferenceTokensResult] {
 func (pds *PrometheusMetricsQuerier) QueryInferenceCachedTokens(start, end time.Time) *source.Future[source.InferenceTokensResult] {
 	ctx := pds.promContexts.NewNamedContext(ClusterContextName)
 	ctx := pds.promContexts.NewNamedContext(ClusterContextName)
-	
+
 	resultsChan := make(source.QueryResultsChan, 1)
 	resultsChan := make(source.QueryResultsChan, 1)
-	
+
 	go func() {
 	go func() {
 		values, err := queryCounterDelta(ctx, "vllm:prefix_cache_hits_total", start, end)
 		values, err := queryCounterDelta(ctx, "vllm:prefix_cache_hits_total", start, end)
 		if err != nil {
 		if err != nil {
 			resultsChan <- &source.QueryResults{Error: err}
 			resultsChan <- &source.QueryResults{Error: err}
 			return
 			return
 		}
 		}
-		
+
 		results := mapToQueryResults(values)
 		results := mapToQueryResults(values)
 		resultsChan <- &source.QueryResults{Results: results}
 		resultsChan <- &source.QueryResults{Results: results}
 	}()
 	}()
-	
+
 	return source.NewFuture(decodeInferenceTokensResult, resultsChan)
 	return source.NewFuture(decodeInferenceTokensResult, resultsChan)
 }
 }
 
 
 // QueryInferenceCacheConfig implements MetricsQuerier.QueryInferenceCacheConfig
 // QueryInferenceCacheConfig implements MetricsQuerier.QueryInferenceCacheConfig
 func (pds *PrometheusMetricsQuerier) QueryInferenceCacheConfig(t time.Time) *source.Future[source.InferenceCacheConfigResult] {
 func (pds *PrometheusMetricsQuerier) QueryInferenceCacheConfig(t time.Time) *source.Future[source.InferenceCacheConfigResult] {
 	ctx := pds.promContexts.NewNamedContext(ClusterContextName)
 	ctx := pds.promContexts.NewNamedContext(ClusterContextName)
-	
+
 	resultsChan := make(source.QueryResultsChan, 1)
 	resultsChan := make(source.QueryResultsChan, 1)
-	
+
 	go func() {
 	go func() {
 		configs, err := queryCacheConfigs(ctx, t)
 		configs, err := queryCacheConfigs(ctx, t)
 		if err != nil {
 		if err != nil {
 			resultsChan <- &source.QueryResults{Error: err}
 			resultsChan <- &source.QueryResults{Error: err}
 			return
 			return
 		}
 		}
-		
+
 		results := cacheConfigMapToQueryResults(configs)
 		results := cacheConfigMapToQueryResults(configs)
 		resultsChan <- &source.QueryResults{Results: results}
 		resultsChan <- &source.QueryResults{Results: results}
 	}()
 	}()
-	
+
 	return source.NewFuture(decodeInferenceCacheConfigResult, resultsChan)
 	return source.NewFuture(decodeInferenceCacheConfigResult, resultsChan)
 }
 }
 
 
@@ -138,7 +138,7 @@ func (pds *PrometheusMetricsQuerier) QueryInferenceCacheConfig(t time.Time) *sou
 func decodeInferenceTokensResult(result *source.QueryResult) *source.InferenceTokensResult {
 func decodeInferenceTokensResult(result *source.QueryResult) *source.InferenceTokensResult {
 	key, _ := result.GetString("key")
 	key, _ := result.GetString("key")
 	value := result.Values[0].Value
 	value := result.Values[0].Value
-	
+
 	return &source.InferenceTokensResult{
 	return &source.InferenceTokensResult{
 		Values: map[string]float64{key: value},
 		Values: map[string]float64{key: value},
 	}
 	}
@@ -147,7 +147,7 @@ func decodeInferenceTokensResult(result *source.QueryResult) *source.InferenceTo
 func decodeInferenceProcessingTimeResult(result *source.QueryResult) *source.InferenceProcessingTimeResult {
 func decodeInferenceProcessingTimeResult(result *source.QueryResult) *source.InferenceProcessingTimeResult {
 	key, _ := result.GetString("key")
 	key, _ := result.GetString("key")
 	value := result.Values[0].Value
 	value := result.Values[0].Value
-	
+
 	return &source.InferenceProcessingTimeResult{
 	return &source.InferenceProcessingTimeResult{
 		Values: map[string]float64{key: value},
 		Values: map[string]float64{key: value},
 	}
 	}
@@ -156,7 +156,7 @@ func decodeInferenceProcessingTimeResult(result *source.QueryResult) *source.Inf
 func decodeInferenceCacheConfigResult(result *source.QueryResult) *source.InferenceCacheConfigResult {
 func decodeInferenceCacheConfigResult(result *source.QueryResult) *source.InferenceCacheConfigResult {
 	key, _ := result.GetString("key")
 	key, _ := result.GetString("key")
 	enabled := result.Values[0].Value > 0
 	enabled := result.Values[0].Value > 0
-	
+
 	return &source.InferenceCacheConfigResult{
 	return &source.InferenceCacheConfigResult{
 		Configs: map[string]*source.InferenceCacheConfig{
 		Configs: map[string]*source.InferenceCacheConfig{
 			key: {PrefixCachingEnabled: enabled},
 			key: {PrefixCachingEnabled: enabled},
@@ -316,9 +316,9 @@ func queryCacheConfigs(ctx *Context, t time.Time) (map[string]*source.InferenceC
 		if rawErr == nil {
 		if rawErr == nil {
 			diagResults := NewQueryResults(rawQuery, rawResult, source.ClusterKeyWithDefaults(ctx.config.ClusterLabel))
 			diagResults := NewQueryResults(rawQuery, rawResult, source.ClusterKeyWithDefaults(ctx.config.ClusterLabel))
 			if diagResults.Error == nil && len(diagResults.Results) > 0 {
 			if diagResults.Error == nil && len(diagResults.Results) > 0 {
-				log.Warnf("InferenceCost: vllm:cache_config_info exists in Prometheus but the join with "+
-					"vllm:prompt_tokens_total produced no results — likely a pod-label mismatch between "+
-					"the two metrics (check that both carry matching 'namespace' and 'pod' labels). "+
+				log.Warnf("InferenceCost: vllm:cache_config_info exists in Prometheus but the join with " +
+					"vllm:prompt_tokens_total produced no results — likely a pod-label mismatch between " +
+					"the two metrics (check that both carry matching 'namespace' and 'pod' labels). " +
 					"prefix_caching_off detection will be disabled; allocation method will be 'compute_time'.")
 					"prefix_caching_off detection will be disabled; allocation method will be 'compute_time'.")
 			}
 			}
 		}
 		}

+ 5 - 6
pkg/env/costmodel.go

@@ -104,11 +104,11 @@ const (
 	MetricsEmitterQueryWindowEnvVar = "METRICS_EMITTER_QUERY_WINDOW"
 	MetricsEmitterQueryWindowEnvVar = "METRICS_EMITTER_QUERY_WINDOW"
 
 
 	// Inference Cost
 	// Inference Cost
-	InferenceCostEnabledEnvVar               = "INFERENCE_COST_ENABLED"
-	InferenceModelLabelEnvVar                = "INFERENCE_MODEL_LABEL"
-	InferenceSharedInfraLabelEnvVar          = "INFERENCE_SHARED_INFRA_LABEL"
-	InferenceSharedInfraLabelValueEnvVar     = "INFERENCE_SHARED_INFRA_LABEL_VALUE"
-	InferenceCollectionIntervalEnvVar        = "INFERENCE_COLLECTION_INTERVAL"
+	InferenceCostEnabledEnvVar           = "INFERENCE_COST_ENABLED"
+	InferenceModelLabelEnvVar            = "INFERENCE_MODEL_LABEL"
+	InferenceSharedInfraLabelEnvVar      = "INFERENCE_SHARED_INFRA_LABEL"
+	InferenceSharedInfraLabelValueEnvVar = "INFERENCE_SHARED_INFRA_LABEL_VALUE"
+	InferenceCollectionIntervalEnvVar    = "INFERENCE_COLLECTION_INTERVAL"
 )
 )
 
 
 func GetGCPAuthSecretFilePath() string {
 func GetGCPAuthSecretFilePath() string {
@@ -453,4 +453,3 @@ func GetInferenceSharedInfraLabelValue() string {
 func GetInferenceCollectionInterval() time.Duration {
 func GetInferenceCollectionInterval() time.Duration {
 	return env.GetDuration(InferenceCollectionIntervalEnvVar, 2*time.Minute)
 	return env.GetDuration(InferenceCollectionIntervalEnvVar, 2*time.Minute)
 }
 }
-

+ 3 - 1
pkg/inferencecost/aggregate.go

@@ -224,7 +224,9 @@ type filterSpec struct {
 }
 }
 
 
 // parseFilter parses a Phase-1 filter string of the form
 // parseFilter parses a Phase-1 filter string of the form
-//   prop:"value"[+prop:"value"]*
+//
+//	prop:"value"[+prop:"value"]*
+//
 // All terms are ANDed. Only dimensions in supportedAggregateProperties are
 // All terms are ANDed. Only dimensions in supportedAggregateProperties are
 // accepted. Values are unquoted if surrounded by double-quotes.
 // accepted. Values are unquoted if surrounded by double-quotes.
 //
 //

+ 2 - 3
pkg/inferencecost/aggregate_test.go

@@ -411,7 +411,6 @@ func TestMatchesFilter_NoMatch(t *testing.T) {
 	}
 	}
 }
 }
 
 
-
 func TestMatchesFilter_Pod(t *testing.T) {
 func TestMatchesFilter_Pod(t *testing.T) {
 	ic := &InferenceCostResponse{
 	ic := &InferenceCostResponse{
 		Properties: InferenceCostAPIProperties{
 		Properties: InferenceCostAPIProperties{
@@ -504,13 +503,13 @@ func TestParseFilter_NewDimensions(t *testing.T) {
 	if len(specs) != 3 {
 	if len(specs) != 3 {
 		t.Fatalf("expected 3 specs, got %d", len(specs))
 		t.Fatalf("expected 3 specs, got %d", len(specs))
 	}
 	}
-	
+
 	expectedSpecs := []filterSpec{
 	expectedSpecs := []filterSpec{
 		{property: "pod", value: "llama-pod-123"},
 		{property: "pod", value: "llama-pod-123"},
 		{property: "controller", value: "llama-deployment"},
 		{property: "controller", value: "llama-deployment"},
 		{property: "container", value: "vllm"},
 		{property: "container", value: "vllm"},
 	}
 	}
-	
+
 	for i, expected := range expectedSpecs {
 	for i, expected := range expectedSpecs {
 		if specs[i].property != expected.property || specs[i].value != expected.value {
 		if specs[i].property != expected.property || specs[i].value != expected.value {
 			t.Errorf("spec[%d] = %+v, want %+v", i, specs[i], expected)
 			t.Errorf("spec[%d] = %+v, want %+v", i, specs[i], expected)

+ 5 - 5
pkg/inferencecost/calculator_test.go

@@ -22,11 +22,11 @@ func floatEq(a, b float64) bool { return math.Abs(a-b) < 1e-9 }
 func TestCalculator_BlendedCostPerMillionTokens(t *testing.T) {
 func TestCalculator_BlendedCostPerMillionTokens(t *testing.T) {
 	cfg := defaultConfig()
 	cfg := defaultConfig()
 	m := &InferenceCost{
 	m := &InferenceCost{
-		AllocationTotalCost: 4.0,
-		UsageTotalCost:      1.0,
-		PromptTokens:        800_000,
-		GenerationTokens:    200_000,
-		TotalTokens:         1_000_000,
+		AllocationTotalCost:  4.0,
+		UsageTotalCost:       1.0,
+		PromptTokens:         800_000,
+		GenerationTokens:     200_000,
+		TotalTokens:          1_000_000,
 		EffectiveInputTokens: 800_000,
 		EffectiveInputTokens: 800_000,
 		// no timing data → multiplier fallback
 		// no timing data → multiplier fallback
 	}
 	}

+ 9 - 10
pkg/inferencecost/collector.go

@@ -170,8 +170,8 @@ func (c *Collector) queryAllocationCosts(ctx context.Context, start, end time.Ti
 	for key, result := range results {
 	for key, result := range results {
 		modelName, namespace := parseKey(key)
 		modelName, namespace := parseKey(key)
 		if result.allocationTotalCost > 0 {
 		if result.allocationTotalCost > 0 {
-			log.Debugf("InferenceCost: model=%s ns=%s alloc=$%.4f usage=$%.4f (%.1f%% of alloc)", 
-				modelName, namespace, result.allocationTotalCost, result.usageTotalCost, 
+			log.Debugf("InferenceCost: model=%s ns=%s alloc=$%.4f usage=$%.4f (%.1f%% of alloc)",
+				modelName, namespace, result.allocationTotalCost, result.usageTotalCost,
 				(result.usageTotalCost/result.allocationTotalCost)*100)
 				(result.usageTotalCost/result.allocationTotalCost)*100)
 		}
 		}
 	}
 	}
@@ -263,7 +263,7 @@ func (c *Collector) extractAllocationResults(as *opencost.AllocationSet, isAlloc
 		controller := ""
 		controller := ""
 		controllerKind := ""
 		controllerKind := ""
 		container := ""
 		container := ""
-		
+
 		if alloc.Properties != nil {
 		if alloc.Properties != nil {
 			namespace = alloc.Properties.Namespace
 			namespace = alloc.Properties.Namespace
 			cluster = alloc.Properties.Cluster
 			cluster = alloc.Properties.Cluster
@@ -296,7 +296,7 @@ func (c *Collector) extractAllocationResults(as *opencost.AllocationSet, isAlloc
 			// For usage cost: use TotalCost() from the ShareNone query (no idle)
 			// For usage cost: use TotalCost() from the ShareNone query (no idle)
 			existing.usageTotalCost += alloc.TotalCost()
 			existing.usageTotalCost += alloc.TotalCost()
 		}
 		}
-		
+
 		// When aggregating multiple allocations, preserve the first non-empty values
 		// When aggregating multiple allocations, preserve the first non-empty values
 		// for pod, controller, and container. This provides representative values
 		// for pod, controller, and container. This provides representative values
 		// when costs are aggregated across multiple pods/containers.
 		// when costs are aggregated across multiple pods/containers.
@@ -344,11 +344,11 @@ func canonicalModelName(modelName string) string {
 // namespace.
 // namespace.
 //
 //
 // Two common mismatch examples:
 // Two common mismatch examples:
-//   1. Fully-qualified vLLM model name vs short allocation label:
-//      "google/gemma-4-31B:llm-d-pic" -> "gemma-4-31B:llm-d-pic"
-//   2. Fully-qualified vLLM model name vs short allocation label with a
-//      different vendor/org prefix:
-//      "MiniMaxAI/MiniMax-M2.7:llm-d-pic" -> "MiniMax-M2.7:llm-d-pic"
+//  1. Fully-qualified vLLM model name vs short allocation label:
+//     "google/gemma-4-31B:llm-d-pic" -> "gemma-4-31B:llm-d-pic"
+//  2. Fully-qualified vLLM model name vs short allocation label with a
+//     different vendor/org prefix:
+//     "MiniMaxAI/MiniMax-M2.7:llm-d-pic" -> "MiniMax-M2.7:llm-d-pic"
 //
 //
 // Exact matches are preserved. Keys with no matching allocation-backed target
 // Exact matches are preserved. Keys with no matching allocation-backed target
 // are also preserved unchanged. A warning is logged for every remapped key so
 // are also preserved unchanged. A warning is logged for every remapped key so
@@ -571,7 +571,6 @@ func parseKey(key string) (modelName, namespace string) {
 	return key[:idx], key[idx+1:]
 	return key[:idx], key[idx+1:]
 }
 }
 
 
-
 // mergeTokenResults merges multiple InferenceTokensResult into a single map
 // mergeTokenResults merges multiple InferenceTokensResult into a single map
 func mergeTokenResults(results []*source.InferenceTokensResult) map[string]float64 {
 func mergeTokenResults(results []*source.InferenceTokensResult) map[string]float64 {
 	merged := make(map[string]float64)
 	merged := make(map[string]float64)

+ 8 - 8
pkg/inferencecost/collector_test.go

@@ -22,7 +22,7 @@ func (m *mockQuerier) ComputeAllocation(start, end time.Time) (*opencost.Allocat
 	if m.err != nil {
 	if m.err != nil {
 		return nil, m.err
 		return nil, m.err
 	}
 	}
-	
+
 	// If multiple sets are provided, return them in sequence
 	// If multiple sets are provided, return them in sequence
 	if len(m.sets) > 0 {
 	if len(m.sets) > 0 {
 		if m.callCount < len(m.sets) {
 		if m.callCount < len(m.sets) {
@@ -33,7 +33,7 @@ func (m *mockQuerier) ComputeAllocation(start, end time.Time) (*opencost.Allocat
 		// Return last set for any additional calls
 		// Return last set for any additional calls
 		return m.sets[len(m.sets)-1], nil
 		return m.sets[len(m.sets)-1], nil
 	}
 	}
-	
+
 	// Otherwise return the single set
 	// Otherwise return the single set
 	return m.set, nil
 	return m.set, nil
 }
 }
@@ -48,7 +48,7 @@ func newMockMetricsQuerierWithInferenceMetrics(
 	cacheConfigs map[string]*source.InferenceCacheConfig,
 	cacheConfigs map[string]*source.InferenceCacheConfig,
 ) *source.MockMetricsQuerier {
 ) *source.MockMetricsQuerier {
 	mock := source.NewMockMetricsQuerier()
 	mock := source.NewMockMetricsQuerier()
-	
+
 	// Set up inference metric overrides
 	// Set up inference metric overrides
 	if promptTokens != nil {
 	if promptTokens != nil {
 		mock.SetOverride(source.QueryInferencePromptTokens, []*source.InferenceTokensResult{
 		mock.SetOverride(source.QueryInferencePromptTokens, []*source.InferenceTokensResult{
@@ -80,7 +80,7 @@ func newMockMetricsQuerierWithInferenceMetrics(
 			{Configs: cacheConfigs},
 			{Configs: cacheConfigs},
 		})
 		})
 	}
 	}
-	
+
 	return mock
 	return mock
 }
 }
 
 
@@ -121,7 +121,7 @@ func TestCollector_ExtractAllocationResults(t *testing.T) {
 	now := time.Now()
 	now := time.Now()
 	cfg := baseConfig()
 	cfg := baseConfig()
 	c := &Collector{config: cfg}
 	c := &Collector{config: cfg}
-	
+
 	// Test allocation cost extraction (with idle)
 	// Test allocation cost extraction (with idle)
 	allocWithIdle := &opencost.Allocation{
 	allocWithIdle := &opencost.Allocation{
 		Name:    "llama-3",
 		Name:    "llama-3",
@@ -145,7 +145,7 @@ func TestCollector_ExtractAllocationResults(t *testing.T) {
 	if !ok {
 	if !ok {
 		t.Fatal("expected allocation result for llama-3/llm-prod")
 		t.Fatal("expected allocation result for llama-3/llm-prod")
 	}
 	}
-	
+
 	if !floatEq(r.allocationTotalCost, 4.0) {
 	if !floatEq(r.allocationTotalCost, 4.0) {
 		t.Errorf("allocationTotalCost want 4.0 got %f", r.allocationTotalCost)
 		t.Errorf("allocationTotalCost want 4.0 got %f", r.allocationTotalCost)
 	}
 	}
@@ -175,7 +175,7 @@ func TestCollector_ExtractAllocationResults(t *testing.T) {
 	if !ok {
 	if !ok {
 		t.Fatal("expected usage result for llama-3/llm-prod")
 		t.Fatal("expected usage result for llama-3/llm-prod")
 	}
 	}
-	
+
 	if !floatEq(r2.usageTotalCost, 2.6) {
 	if !floatEq(r2.usageTotalCost, 2.6) {
 		t.Errorf("usageTotalCost want 2.6 got %f", r2.usageTotalCost)
 		t.Errorf("usageTotalCost want 2.6 got %f", r2.usageTotalCost)
 	}
 	}
@@ -396,7 +396,7 @@ func TestCollector_CollectMetrics_EmptyMetrics(t *testing.T) {
 
 
 	now := time.Now()
 	now := time.Now()
 	querier := &mockQuerier{set: opencost.NewAllocationSet(now.Add(-5*time.Minute), now)}
 	querier := &mockQuerier{set: opencost.NewAllocationSet(now.Add(-5*time.Minute), now)}
-	
+
 	// Use the standard mock - it will return empty results by default
 	// Use the standard mock - it will return empty results by default
 	metricsQuerier := source.NewMockMetricsQuerier()
 	metricsQuerier := source.NewMockMetricsQuerier()
 
 

+ 5 - 5
pkg/inferencecost/env.go

@@ -8,8 +8,8 @@ import (
 )
 )
 
 
 func isInferenceCostEnabled() bool         { return env.IsInferenceCostEnabled() }
 func isInferenceCostEnabled() bool         { return env.IsInferenceCostEnabled() }
-func getPrometheusURL() string              { return coreenv.Get("PROMETHEUS_SERVER_ENDPOINT", "") }
-func getModelLabel() string                 { return env.GetInferenceModelLabel() }
-func getSharedInfraLabel() string           { return env.GetInferenceSharedInfraLabel() }
-func getSharedInfraLabelValue() string      { return env.GetInferenceSharedInfraLabelValue() }
-func getCollectionInterval() time.Duration  { return env.GetInferenceCollectionInterval() }
+func getPrometheusURL() string             { return coreenv.Get("PROMETHEUS_SERVER_ENDPOINT", "") }
+func getModelLabel() string                { return env.GetInferenceModelLabel() }
+func getSharedInfraLabel() string          { return env.GetInferenceSharedInfraLabel() }
+func getSharedInfraLabelValue() string     { return env.GetInferenceSharedInfraLabelValue() }
+func getCollectionInterval() time.Duration { return env.GetInferenceCollectionInterval() }

+ 5 - 5
pkg/inferencecost/exporter_test.go

@@ -35,12 +35,12 @@ func sampleMetric(method AllocationMethod) *InferenceCost {
 			ModelVersion: "v1",
 			ModelVersion: "v1",
 			Namespace:    "llm-prod",
 			Namespace:    "llm-prod",
 		},
 		},
-		AllocationTotalCost: 4.0,
-		UsageTotalCost:      1.0,
-		TotalTokens:         1_000_000,
+		AllocationTotalCost:  4.0,
+		UsageTotalCost:       1.0,
+		TotalTokens:          1_000_000,
 		EffectiveInputTokens: 800_000,
 		EffectiveInputTokens: 800_000,
-		GenerationTokens:    200_000,
-		AllocationMethod:    method,
+		GenerationTokens:     200_000,
+		AllocationMethod:     method,
 		CostPerMillionTokens: map[CostBasis]float64{
 		CostPerMillionTokens: map[CostBasis]float64{
 			CostBasisAllocation: 4.0,
 			CostBasisAllocation: 4.0,
 			CostBasisUsage:      1.0,
 			CostBasisUsage:      1.0,

+ 6 - 6
pkg/inferencecost/queryservice_helper.go

@@ -11,13 +11,13 @@ import (
 
 
 // QueryRequest holds the parsed parameters for an inference cost query.
 // QueryRequest holds the parsed parameters for an inference cost query.
 type QueryRequest struct {
 type QueryRequest struct {
-	Start      time.Time
-	End        time.Time
-	CostBasis  CostBasis
+	Start       time.Time
+	End         time.Time
+	CostBasis   CostBasis
 	AggregateBy []string
 	AggregateBy []string
-	Accumulate opencost.AccumulateOption
-	Filter     []filterSpec
-	Step       time.Duration
+	Accumulate  opencost.AccumulateOption
+	Filter      []filterSpec
+	Step        time.Duration
 }
 }
 
 
 // ParseInferenceCostRequest parses the common query parameters from an HTTP
 // ParseInferenceCostRequest parses the common query parameters from an HTTP

+ 0 - 1
pkg/inferencecost/types.go

@@ -145,7 +145,6 @@ type Config struct {
 	// OutputTokenCostMultiplier is the output/input cost ratio used when
 	// OutputTokenCostMultiplier is the output/input cost ratio used when
 	// AllocationMode is "multiplier".
 	// AllocationMode is "multiplier".
 	OutputTokenCostMultiplier float64
 	OutputTokenCostMultiplier float64
-
 }
 }
 
 
 const (
 const (