Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions pkg/plugins/genai/cmd/main/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package main

import (
"fmt"
"log"
"os"

"github.com/hashicorp/go-plugin"
"github.com/opencost/opencost-plugins/pkg/plugins/genai/genaiprovider"
"github.com/opencost/opencost-plugins/pkg/plugins/genai/internal"
)

type GenAISource struct {
Config *genaiprovider.Config
}

// GetCustomCosts is called by OpenCost to retrieve the enriched GenAI costs.
func (s *GenAISource) GetCustomCosts(req *genaiprovider.CustomCostRequest) ([]*genaiprovider.GenAIWorkloadData, error) {
log.Printf("GenAI Plugin: Fetching costs for window %s - %s", req.Start, req.End)

// 1. Call your internal math logic (from internal/join.go)
// This performs the Prometheus fetch and the MIG-to-Node join.
workloads, err := internal.CalculateGenAIWorkloads(req.Start, req.End, s.Config)
if err != nil {
return nil, fmt.Errorf("failed to calculate GenAI workloads: %w", err)
}

var responses []*genaiprovider.GenAIWorkloadData
for _, w := range workloads {
// Map internal results to the genaiprovider format
responses = append(responses, &genaiprovider.GenAIWorkloadData{
PodName: w.PodName,
ModelName: w.ModelName,
TotalTokens: w.TotalTokens,
TotalCost: w.TotalCost,
TenantID: w.TenantID,
WorkflowPhase: w.WorkflowPhase,
MIGProfile: w.MIGProfile,
GPUEfficiency: w.GPUEfficiency,
})
}

return responses, nil
}

func main() {
// 1. Load plugin configuration (e.g., Prometheus URL, Cluster ID)
configPath := os.Getenv("PLUGIN_CONFIG_PATH")
if configPath == "" {
configPath = "config/genai-config.json"
}

cfg, err := genaiprovider.LoadConfig(configPath)
if err != nil {
log.Fatalf("GenAI Plugin: Failed to load config: %v", err)
}

// 2. Create the source instance
source := &GenAISource{Config: cfg}

// 3. Define the Handshake (Security "Cookie" required by go-plugin)
var handshakeConfig = plugin.HandshakeConfig{
ProtocolVersion: 1,
MagicCookieKey: "OPENCOST_PLUGIN_MAGIC_COOKIE",
MagicCookieValue: "genai-visibility",
}

// 4. Start the gRPC Server
plugin.Serve(&plugin.ServeConfig{
HandshakeConfig: handshakeConfig,
Plugins: map[string]plugin.Plugin{
"customcost": &genaiprovider.CustomCostPlugin{
Impl: source,
},
},
GRPCServer: plugin.DefaultGRPCServer,
})
}
4 changes: 4 additions & 0 deletions pkg/plugins/genai/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"prometheusUrl": "http://your-prometheus-endpoint:9090",
"metrics": { "inputTokensMetric": "genai_input_tokens_total" }
}
5 changes: 5 additions & 0 deletions pkg/plugins/genai/config/genai-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"prometheus_url": "http://prometheus:9090",
"cluster_id": "test-cluster",
"log_level": "info"
}
5 changes: 5 additions & 0 deletions pkg/plugins/genai/dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
FROM debian:bookworm-slim
WORKDIR /app
COPY genai-plugin /app/genai-plugin
RUN chmod +x /app/genai-plugin
ENTRYPOINT ["/app/genai-plugin"]
Binary file added pkg/plugins/genai/genai-plugin
Binary file not shown.
44 changes: 44 additions & 0 deletions pkg/plugins/genai/genaiplugin/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package genaiplugin

type MetricMapping struct {
InputTokens string
OutputTokens string
GPUActiveSec string
GPUUtil string
}

func DefaultMetricMapping() MetricMapping {
return MetricMapping{
InputTokens: "vllm:prompt_tokens_total",
OutputTokens: "vllm:generation_tokens_total",
GPUUtil: "DCGM_FI_DEV_GPU_UTIL",
GPUActiveSec: "DCGM_FI_DEV_GPU_UTIL",
}
}

func GetMapping(annotations map[string]string) MetricMapping {

// "fallback" if no annotations are present
m := MetricMapping{
InputTokens: "llm_tokens_input_total",
OutputTokens: "llm_tokens_output_total",
GPUActiveSec: "llm_gpu_active_seconds_total",
GPUUtil: "llm_gpu_utilization_percent",
}

// Override only if specific annotations exist
if val, ok := annotations["opencost.io/metric-input"]; ok {
m.InputTokens = val
}
if val, ok := annotations["opencost.io/metric-output"]; ok {
m.OutputTokens = val
}
if val, ok := annotations["opencost.io/metric-gpu-sec"]; ok {
m.GPUActiveSec = val
}
if val, ok := annotations["opencost.io/metric-gpu-util"]; ok {
m.GPUUtil = val
}

return m
}
Loading