Skip to content
Merged
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
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,7 @@ GITHUB_WEBHOOK_SECRET=

# --- Grafana Security ---
GF_SECURITY_ADMIN_PASSWORD=

# --- Finops/GreenOps ---
ENERGY_COST_CAD_KWH=0.15
CARBON_INTENSITY_G_KWH=150.0
43 changes: 36 additions & 7 deletions internal/analytics/analytics.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ import (
"fmt"
"net/http"
"net/url"
"os"
"strconv"
"time"

"observability-hub/internal/telemetry"
)

// MetricBatch represents a collection of metric samples keyed by type (cpu, ram, disk, network, temp).
Expand Down Expand Up @@ -107,6 +110,17 @@ type ThanosResourceProvider struct {
}

func NewThanosResourceProvider(client *ThanosClient) *ThanosResourceProvider {
// Log initial factors on startup
carbon := os.Getenv("CARBON_INTENSITY_G_KWH")
if carbon == "" {
carbon = "150.0 (default)"
}
cost := os.Getenv("ENERGY_COST_CAD_KWH")
if cost == "" {
cost = "0.15 (default)"
}
telemetry.Info("analytics_factors_loaded", "carbon_intensity", carbon, "energy_cost", cost)

return &ThanosResourceProvider{Client: client}
}

Expand Down Expand Up @@ -178,6 +192,7 @@ func (p *ThanosResourceProvider) GetHostServiceCPU(ctx context.Context, start, e

func (p *ThanosResourceProvider) GetValueUnits(ctx context.Context, start, end time.Time) (map[string]float64, error) {
// 1. Define queries for all business value counters
// In the future, this could be loaded from a config file.
queries := map[string]string{
"ingestion": "sum(increase(second_brain_sync_processed_total[15m])) + sum(increase(reading_sync_processed_total[15m]))",
"proxy": "sum(increase(proxy_webhook_received_total[15m])) + sum(increase(proxy_synthetic_request_total[15m]))",
Expand All @@ -199,15 +214,29 @@ func (p *ThanosResourceProvider) GetValueUnits(ctx context.Context, start, end t
}

func (p *ThanosResourceProvider) GetCarbonIntensity(ctx context.Context) (float64, error) {

// Default: ~150g CO2 per kWh (Sample value for a "greenish" grid)
// In a real implementation, this could call an external API.
return 150.0, nil
// Use environment variable if present, otherwise default to 150.0
valStr := os.Getenv("CARBON_INTENSITY_G_KWH")
if valStr == "" {
return 150.0, nil
}
val, err := strconv.ParseFloat(valStr, 64)
if err != nil {
return 150.0, nil
}
return val, nil
}

func (p *ThanosResourceProvider) GetCostFactor(ctx context.Context) (float64, error) {
// Default: $0.15 CAD per kWh (Sample price)
// Use environment variable if present, otherwise default to 0.15 CAD/kWh
valStr := os.Getenv("ENERGY_COST_CAD_KWH")
pricePerKWh := 0.15
if valStr != "" {
if val, err := strconv.ParseFloat(valStr, 64); err == nil {
pricePerKWh = val
}
}

// 1 kWh = 3_600_000 Joules
// Cost per Joule = 0.15 / 3_600_000
return 0.15 / 3600000.0, nil
// Cost per Joule = pricePerKWh / 3_600_000
return pricePerKWh / 3600000.0, nil
}
2 changes: 2 additions & 0 deletions internal/analytics/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ func (s *Service) recordMetricsForFeature(ctx context.Context, t time.Time, feat

telemetry.Info("feature_analytics_recorded", "feature_id", featureID, "joules", joules, "host", hostName)
}

func mapContainerToFeature(container string) string {
// Simple mapping based on known service names (containers or systemd units)
mapping := map[string]string{
Expand All @@ -278,6 +279,7 @@ func mapContainerToFeature(container string) string {
"analytics": "analytics-engine",
"mcp-telemetry": "agentic-telemetry",
"mcp-pods": "agentic-kubernetes",
"mcp-hub": "agentic-hub",
"postgresql": "database-core",
"postgres": "database-core",
"prometheus-server": "observability-infra",
Expand Down
2 changes: 2 additions & 0 deletions k3s/analytics/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ nameOverride: ""
fullnameOverride: ""

podAnnotations: {}
podLabels:
app.kubernetes.io/feature: analytics-engine

podSecurityContext: {}
# fsGroup: 2000
Expand Down
49 changes: 45 additions & 4 deletions k3s/grafana/dashboards/pod-resources.json
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@
"uid": "prometheus-provisioned"
},
"editorMode": "code",
"expr": "sum(rate(kepler_container_cpu_joules_total{container_name!=\"\", pod_id!=\"\", zone=\"package\"}[5m]) * on(pod_id) group_left(namespace) label_replace(kube_pod_info{namespace=\"observability\"}, \"pod_id\", \"$1\", \"uid\", \"(.*)\")) / 1000 * 0.15 * 24",
"expr": "sum(rate(kepler_container_cpu_joules_total{container_name!=\"\", pod_id!=\"\", zone=\"package\"}[5m]) * on(pod_id) group_left(namespace) label_replace(kube_pod_info{namespace=\"observability\"}, \"pod_id\", \"$1\", \"uid\", \"(.*)\")) / 1000 * $energy_cost_kwh * 24",
"legendFormat": "CAD/Day",
"range": true,
"refId": "A"
Expand Down Expand Up @@ -327,7 +327,7 @@
"uid": "prometheus-provisioned"
},
"editorMode": "code",
"expr": "sum(rate(kepler_container_cpu_joules_total{container_name!=\"\", pod_id!=\"\", zone=\"package\"}[5m]) * on(pod_id) group_left(namespace) label_replace(kube_pod_info{namespace=\"observability\"}, \"pod_id\", \"$1\", \"uid\", \"(.*)\")) / 1000 * 0.15 * 24 * 30",
"expr": "sum(rate(kepler_container_cpu_joules_total{container_name!=\"\", pod_id!=\"\", zone=\"package\"}[5m]) * on(pod_id) group_left(namespace) label_replace(kube_pod_info{namespace=\"observability\"}, \"pod_id\", \"$1\", \"uid\", \"(.*)\")) / 1000 * $energy_cost_kwh * 24 * 30",
"legendFormat": "CAD/Month",
"range": true,
"refId": "A"
Expand Down Expand Up @@ -390,7 +390,7 @@
"uid": "prometheus-provisioned"
},
"editorMode": "code",
"expr": "sum(rate(kepler_container_cpu_joules_total{container_name!=\"\", pod_id!=\"\", zone=\"package\"}[5m]) * on(pod_id) group_left(namespace) label_replace(kube_pod_info{namespace=\"observability\"}, \"pod_id\", \"$1\", \"uid\", \"(.*)\")) / 1000 * 0.15 * 24 * 365",
"expr": "sum(rate(kepler_container_cpu_joules_total{container_name!=\"\", pod_id!=\"\", zone=\"package\"}[5m]) * on(pod_id) group_left(namespace) label_replace(kube_pod_info{namespace=\"observability\"}, \"pod_id\", \"$1\", \"uid\", \"(.*)\")) / 1000 * $energy_cost_kwh * 24 * 365",
"legendFormat": "CAD/Year",
"range": true,
"refId": "A"
Expand Down Expand Up @@ -732,7 +732,48 @@
"k3s"
],
"templating": {
"list": []
"list": [
{
"current": {
"selected": true,
"text": "0.15",
"value": "0.15"
},
"hide": 0,
"label": "Energy Cost (CAD/kWh)",
"name": "energy_cost_kwh",
"options": [
{
"selected": true,
"text": "0.15",
"value": "0.15"
}
],
"query": "0.15",
"skipUrlSync": false,
"type": "custom"
},
{
"current": {
"selected": true,
"text": "150",
"value": "150"
},
"hide": 0,
"label": "Carbon Intensity (g/kWh)",
"name": "carbon_intensity_kwh",
"options": [
{
"selected": true,
"text": "150",
"value": "150"
}
],
"query": "150",
"skipUrlSync": false,
"type": "custom"
}
]
},
"time": {
"from": "now-1h",
Expand Down
63 changes: 52 additions & 11 deletions k3s/grafana/dashboards/sustainability-hub.json
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@
"uid": "prometheus-provisioned"
},
"editorMode": "code",
"expr": "sum(rate(kepler_node_cpu_joules_total{zone=\"package\"}[5m])) * 0.15",
"expr": "sum(rate(kepler_node_cpu_joules_total{zone=\"package\"}[5m])) * ($carbon_intensity_kwh / 1000)",
"legendFormat": "gCO2/h",
"range": true,
"refId": "A"
Expand Down Expand Up @@ -262,7 +262,7 @@
"uid": "prometheus-provisioned"
},
"editorMode": "code",
"expr": "sum(rate(kepler_node_cpu_joules_total{zone=\"package\"}[5m])) / 1000 * 0.15 * 24",
"expr": "sum(rate(kepler_node_cpu_joules_total{zone=\"package\"}[5m])) / 1000 * $energy_cost_kwh * 24",
"legendFormat": "CAD/Day",
"range": true,
"refId": "A"
Expand Down Expand Up @@ -323,7 +323,7 @@
"uid": "prometheus-provisioned"
},
"editorMode": "code",
"expr": "((sum(rate(kepler_node_cpu_joules_total{zone=\"package\"}[5m])) * 0.15 * 24 * 365) / 1000) / 21",
"expr": "((sum(rate(kepler_node_cpu_joules_total{zone=\"package\"}[5m])) * ($carbon_intensity_kwh / 1000) * 24 * 365) / 1000) / 21",
"legendFormat": "Trees",
"range": true,
"refId": "A"
Expand Down Expand Up @@ -512,7 +512,7 @@
"uid": "prometheus-provisioned"
},
"editorMode": "code",
"expr": "sum(rate(kepler_node_cpu_joules_total{zone=\"package\"}[5m])) * 0.15",
"expr": "sum(rate(kepler_node_cpu_joules_total{zone=\"package\"}[5m])) * ($carbon_intensity_kwh / 1000)",
"legendFormat": "Host Carbon",
"refId": "A"
},
Expand All @@ -522,7 +522,7 @@
"uid": "prometheus-provisioned"
},
"editorMode": "code",
"expr": "sum(rate(kepler_container_cpu_joules_total{container_name!=\"\", pod_id!=\"\", zone=\"package\"}[5m])) * 0.15",
"expr": "sum(rate(kepler_container_cpu_joules_total{container_name!=\"\", pod_id!=\"\", zone=\"package\"}[5m])) * ($carbon_intensity_kwh / 1000)",
"legendFormat": "Total Pods Carbon",
"refId": "B"
}
Expand Down Expand Up @@ -770,7 +770,7 @@
"uid": "prometheus-provisioned"
},
"editorMode": "code",
"expr": "sum(rate(kepler_node_cpu_joules_total{zone=\"package\"}[5m])) / 1000 * 0.15 * 24 * 30",
"expr": "sum(rate(kepler_node_cpu_joules_total{zone=\"package\"}[5m])) / 1000 * $energy_cost_kwh * 24 * 30",
"legendFormat": "CAD/Month",
"range": true,
"refId": "A"
Expand Down Expand Up @@ -831,7 +831,7 @@
"uid": "prometheus-provisioned"
},
"editorMode": "code",
"expr": "sum(rate(kepler_node_cpu_joules_total{zone=\"package\"}[5m])) / 1000 * 0.15 * 24 * 365",
"expr": "sum(rate(kepler_node_cpu_joules_total{zone=\"package\"}[5m])) / 1000 * $energy_cost_kwh * 24 * 365",
"legendFormat": "CAD/Year",
"range": true,
"refId": "A"
Expand Down Expand Up @@ -893,7 +893,7 @@
"uid": "prometheus-provisioned"
},
"editorMode": "code",
"expr": "(sum(rate(kepler_node_cpu_joules_total{zone=\"package\"}[5m])) * 0.15 * 24 * 365) / 1000",
"expr": "(sum(rate(kepler_node_cpu_joules_total{zone=\"package\"}[5m])) * ($carbon_intensity_kwh / 1000) * 24 * 365) / 1000",
"legendFormat": "kgCO2/Year",
"range": true,
"refId": "A"
Expand Down Expand Up @@ -955,7 +955,7 @@
"uid": "prometheus-provisioned"
},
"editorMode": "code",
"expr": "((sum(rate(kepler_node_cpu_joules_total{zone=\"package\"}[5m])) - sum(rate(kepler_container_cpu_joules_total{container_name!=\"\", pod_id!=\"\", zone=\"package\"}[5m]))) * 0.15 * 24 * 365) / 1000",
"expr": "((sum(rate(kepler_node_cpu_joules_total{zone=\"package\"}[5m])) - sum(rate(kepler_container_cpu_joules_total{container_name!=\"\", pod_id!=\"\", zone=\"package\"}[5m]))) * ($carbon_intensity_kwh / 1000) * 24 * 365) / 1000",
"legendFormat": "kgCO2/Year",
"range": true,
"refId": "A"
Expand Down Expand Up @@ -1086,7 +1086,7 @@
"uid": "prometheus-provisioned"
},
"editorMode": "code",
"expr": "sum(rate(kepler_container_cpu_joules_total{container_name!=\"\", pod_id!=\"\", zone=\"package\"}[5m]) * on(pod_id) group_left(namespace) label_replace(kube_pod_info, \"pod_id\", \"$1\", \"uid\", \"(.*)\")) by (namespace) * 0.15",
"expr": "sum(rate(kepler_container_cpu_joules_total{container_name!=\"\", pod_id!=\"\", zone=\"package\"}[5m]) * on(pod_id) group_left(namespace) label_replace(kube_pod_info, \"pod_id\", \"$1\", \"uid\", \"(.*)\")) by (namespace) * ($carbon_intensity_kwh / 1000)",
"legendFormat": "{{namespace}}",
"refId": "A"
}
Expand Down Expand Up @@ -1170,7 +1170,48 @@
"finops"
],
"templating": {
"list": []
"list": [
{
"current": {
"selected": true,
"text": "0.15",
"value": "0.15"
},
"hide": 0,
"label": "Energy Cost (CAD/kWh)",
"name": "energy_cost_kwh",
"options": [
{
"selected": true,
"text": "0.15",
"value": "0.15"
}
],
"query": "0.15",
"skipUrlSync": false,
"type": "custom"
},
{
"current": {
"selected": true,
"text": "150",
"value": "150"
},
"hide": 0,
"label": "Carbon Intensity (g/kWh)",
"name": "carbon_intensity_kwh",
"options": [
{
"selected": true,
"text": "150",
"value": "150"
}
],
"query": "150",
"skipUrlSync": false,
"type": "custom"
}
]
},
"time": {
"from": "now-1h",
Expand Down
Loading