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
9 changes: 5 additions & 4 deletions cmd/bot/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,11 @@ type config struct {

GitHub github.Config

IsCI bool `envconfig:"CI"`
PR int `envconfig:"GITHUB_PULL_REQUEST" required:"true"`
Event string `envconfig:"GITHUB_EVENT_NAME"`
LogLevel string `envconfig:"LOG_LEVEL" default:"info"`
IsCI bool `envconfig:"CI"`
PR int `envconfig:"GITHUB_PULL_REQUEST" required:"true"`
Event string `envconfig:"GITHUB_EVENT_NAME"`
LogLevel string `envconfig:"LOG_LEVEL" default:"info"`
UseCloudCostExporterMetrics bool `envconfig:"USE_CLOUD_COST_EXPORTER" default:"false"`
}

const pullRequestEvent = "pull_request"
Expand Down
18 changes: 10 additions & 8 deletions cmd/bot/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,16 +56,18 @@ func realMain(ctx context.Context) error {

prometheusClients, err := costmodel.NewClients(
&costmodel.ClientConfig{
Address: cfg.Prometheus.Prod.Address,
HTTPConfigFile: cfg.Prometheus.Prod.HTTPConfigFile,
Username: cfg.Prometheus.Prod.Username,
Password: cfg.Prometheus.Prod.Password,
Address: cfg.Prometheus.Prod.Address,
HTTPConfigFile: cfg.Prometheus.Prod.HTTPConfigFile,
Username: cfg.Prometheus.Prod.Username,
Password: cfg.Prometheus.Prod.Password,
UseCloudCostExporterMetrics: cfg.UseCloudCostExporterMetrics,
},
&costmodel.ClientConfig{
Address: cfg.Prometheus.Dev.Address,
HTTPConfigFile: cfg.Prometheus.Dev.HTTPConfigFile,
Username: cfg.Prometheus.Dev.Username,
Password: cfg.Prometheus.Dev.Password,
Address: cfg.Prometheus.Dev.Address,
HTTPConfigFile: cfg.Prometheus.Dev.HTTPConfigFile,
Username: cfg.Prometheus.Dev.Username,
Password: cfg.Prometheus.Dev.Password,
UseCloudCostExporterMetrics: cfg.UseCloudCostExporterMetrics,
})
if err != nil {
return fmt.Errorf("creating cost model client: %w", err)
Expand Down
15 changes: 9 additions & 6 deletions cmd/estimator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,27 @@ import (

func main() {
var fromFile, toFile, prometheusAddress, httpConfigFile, reportType, username, password string
var useCloudCostExporterMetrics bool
flag.StringVar(&fromFile, "from", "", "The file to compare from")
flag.StringVar(&toFile, "to", "", "The file to compare to")
flag.StringVar(&prometheusAddress, "prometheus.address", "http://localhost:9093/prometheus", "The Address of the prometheus server")
flag.StringVar(&httpConfigFile, "http.config.file", "", "The path to the http config file")
flag.StringVar(&username, "username", "", "Mimir username")
flag.StringVar(&password, "password", "", "Mimir password")
flag.StringVar(&reportType, "report.type", "table", "The type of report to generate. Options are: table, summary")
flag.BoolVar(&useCloudCostExporterMetrics, "use.cloud.cost.exporter.metrics", false, "Whether to use the cloud cost exporter metrics")
flag.Parse()

clusters := flag.Args()

ctx := context.Background()
if err := run(ctx, fromFile, toFile, prometheusAddress, httpConfigFile, reportType, username, password, clusters); err != nil {
if err := run(ctx, fromFile, toFile, prometheusAddress, httpConfigFile, reportType, username, password, clusters, useCloudCostExporterMetrics); err != nil {
fmt.Printf("Could not run: %s\n", err)
os.Exit(1)
}
}

func run(ctx context.Context, fromFile, toFile, address, httpConfigFile, reportType, username, password string, clusters []string) error {
func run(ctx context.Context, fromFile, toFile, address, httpConfigFile, reportType, username, password string, clusters []string, useCloudCostExporterMetrics bool) error {
from, err := os.ReadFile(fromFile)
if err != nil {
return fmt.Errorf("could not read file: %s", err)
Expand All @@ -42,10 +44,11 @@ func run(ctx context.Context, fromFile, toFile, address, httpConfigFile, reportT
}

client, err := costmodel.NewClient(&costmodel.ClientConfig{
Address: address,
HTTPConfigFile: httpConfigFile,
Username: username,
Password: password,
Address: address,
HTTPConfigFile: httpConfigFile,
Username: username,
Password: password,
UseCloudCostExporterMetrics: useCloudCostExporterMetrics,
})

if err != nil {
Expand Down
49 changes: 43 additions & 6 deletions pkg/costmodel/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@ avg by (spot) (node_cpu_hourly_cost{cluster="%s"}
)
)
)
`
cloudcostExporterQueryCostPerCpu = `
avg by (price_tier) (
cloudcost_aws_ec2_instance_cpu_usd_per_core_hour{cluster_name="%s"}
or
cloudcost_azure_aks_instance_cpu_usd_per_core_hour{cluster_name="%s"}
or
cloudcost_gcp_gke_instance_cpu_usd_per_core_hour{cluster_name="%s"}
)
`

queryMemoryCost = `
Expand All @@ -48,6 +57,15 @@ avg by (spot) (node_ram_hourly_cost{cluster="%s"}
)
)
)
`
cloudcostExporterQueryMemoryCost = `
avg by (price_tier) (
cloudcost_aws_ec2_instance_memory_usd_per_gib_hour{cluster_name="%s"}
or
cloudcost_azure_aks_instance_memory_usd_per_gib_hour{cluster_name="%s"}
or
cloudcost_gcp_gke_instance_memory_usd_per_gib_hour{cluster_name="%s"}
)
`
queryPersistentVolumeCost = "avg_over_time(avg(pv_hourly_cost{cluster=\"%s\"})[24h:1m])"

Expand All @@ -70,7 +88,8 @@ var (

// Client is a client for the cost model.
type Client struct {
client api.Client
client api.Client
useCloudCostExporterMetrics bool
}

// Clients bundles the dev and prod client in one struct.
Expand All @@ -81,10 +100,11 @@ type Clients struct {

// ClientConfig is the configuration for the cost model client.
type ClientConfig struct {
Address string
HTTPConfigFile string
Username string
Password string
Address string
HTTPConfigFile string
Username string
Password string
UseCloudCostExporterMetrics bool
}

// NewClient creates a new cost model client with the given configuration.
Expand Down Expand Up @@ -123,7 +143,10 @@ func NewClient(config *ClientConfig) (*Client, error) {
if err != nil {
return nil, err
}
return &Client{client: client}, nil
return &Client{
client: client,
useCloudCostExporterMetrics: config.UseCloudCostExporterMetrics,
}, nil
}

// NewClients creates a new cost model clients with the given configuration.
Expand Down Expand Up @@ -153,6 +176,9 @@ func (c *Client) GetCostPerCPU(ctx context.Context, cluster string) (Cost, error
// GetMemoryCost returns the cost per memory for a given cluster
func (c *Client) GetMemoryCost(ctx context.Context, cluster string) (Cost, error) {
query := fmt.Sprintf(queryMemoryCost, cluster)
if c.useCloudCostExporterMetrics {
query = fmt.Sprintf(cloudcostExporterQueryMemoryCost, cluster, cluster, cluster)
}
results, err := c.query(ctx, query)
if err != nil {
return Cost{}, err
Expand Down Expand Up @@ -206,6 +232,17 @@ func (c *Client) parseResults(results model.Value) (Cost, error) {
// This is when there is no spot/non-spot label
cost.Dollars = value
}
// Handles the case for cloudcost exporter metrics where `price_tier` is the label for spot/non-spot
// TODO: Delete after removing support for OpenCost
switch sample.Metric["price_tier"] {
case "ondemand":
cost.NonSpot = value
case "spot":
cost.Spot = value
default:
// This is when there is no spot/non-spot label
cost.Dollars = value
}
Comment on lines +235 to +245
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really the heart of the change. The biggest difference between the queries is that the price_tier label is used to signify if the price is for an ondemand or spot instance.

}

return cost, nil
Expand Down
2 changes: 1 addition & 1 deletion pkg/costmodel/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ func TestParseResults(t *testing.T) {
&model.Sample{Metric: model.Metric{"spot": "false"}, Value: 2.71},
&model.Sample{Metric: model.Metric{"spot": "true"}, Value: 1.41},
},
Cost{Spot: 1.41, NonSpot: 2.71},
Cost{Spot: 1.41, NonSpot: 2.71, Dollars: 1.41},
nil,
},
}
Expand Down
Loading