Skip to content

Commit

Permalink
Merge pull request #1227 from titanventura/develop
Browse files Browse the repository at this point in the history
[FEAT-778] Calculate cost for GCP Redis instances
  • Loading branch information
jakepage91 authored Dec 15, 2023
2 parents 36c9c89 + c1839aa commit c2abda5
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 1 deletion.
12 changes: 11 additions & 1 deletion providers/gcp/redis/instances.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package redis

import (
"context"

"fmt"
"regexp"
"time"
Expand All @@ -18,7 +19,14 @@ import (
"github.com/tailwarden/komiser/utils"
)



func Instances(ctx context.Context, client providers.ProviderClient) ([]models.Resource, error) {
pricing, err := FetchPricing()
if err != nil {
return nil, err
}

resources := make([]models.Resource, 0)

regions, err := utils.FetchGCPRegionsInRealtime(client.GCPClient.Credentials.ProjectID, option.WithCredentials(client.GCPClient.Credentials))
Expand Down Expand Up @@ -57,6 +65,8 @@ RegionsLoop:
re := regexp.MustCompile(`instances\/(.+)$`)
redisInstanceName := re.FindStringSubmatch(redis.Name)[1]

cost := calculateRedisCost(redis, pricing)

resources = append(resources, models.Resource{
Provider: "GCP",
Account: client.Name,
Expand All @@ -65,7 +75,7 @@ RegionsLoop:
Name: redis.DisplayName,
Region: regionName,
CreatedAt: redis.CreateTime.AsTime(),
Cost: 0,
Cost: cost,
FetchedAt: time.Now(),
Link: fmt.Sprintf("https://console.cloud.google.com/memorystore/redis/locations/%s/instances/%s/details/overview?project=%s", regionName, redisInstanceName, client.GCPClient.Credentials.ProjectID),
})
Expand Down
112 changes: 112 additions & 0 deletions providers/gcp/redis/pricing.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package redis

import (
"encoding/json"
"fmt"
"math"
"net/http"
"time"

"cloud.google.com/go/redis/apiv1/redispb"
)

const (
M1GbLimit = 4
M2GbLimit = 10
M3GbLimit = 35
M4GbLimit = 100
)

type RedisPrice []struct {
Val int `json:"val"`
Currency string `json:"currnecy"`
Nanos float64 `json:"nanos"`
}

type RegionBasedPricing struct {
Regions map[string]struct {
Price RedisPrice `json:"price"`
} `json:"regions"`
}

type GcpDatabasePricing struct {
Gcp struct {
Databases struct {
CloudMemorystore struct {
Redis struct {
Basic map[string]RegionBasedPricing `json:"basic"`
Standard map[string]RegionBasedPricing `json:"standard"`
} `json:"redis"`
} `json:"cloud_memorystore"`
} `json:"databases"`
} `json:"gcp"`
}

func FetchPricing() (*GcpDatabasePricing, error) {
res, err := http.Get("https://www.gstatic.com/cloud-site-ux/pricing/data/gcp-databases.json")
if err != nil {
return nil, err
}

var pricing GcpDatabasePricing
err = json.NewDecoder(res.Body).Decode(&pricing)
if err != nil {
return nil, err
}

return &pricing, nil
}

func calculateRedisCost(redis *redispb.Instance, pricing *GcpDatabasePricing) float64 {
var priceMap map[string]RegionBasedPricing
var priceKey string

prices := []int32{M1GbLimit, M2GbLimit, M3GbLimit, M4GbLimit}
capacityTier := getCapacityTier(redis.MemorySizeGb, prices)

if redis.Tier == redispb.Instance_BASIC {
priceMap = pricing.Gcp.Databases.CloudMemorystore.Redis.Basic
priceKey = fmt.Sprintf("Rediscapacitybasicm%ddefault", capacityTier)
} else if redis.Tier == redispb.Instance_STANDARD_HA {
priceMap = pricing.Gcp.Databases.CloudMemorystore.Redis.Standard
if redis.ReadReplicasMode == redispb.Instance_READ_REPLICAS_DISABLED {
priceKey = fmt.Sprintf("Rediscapacitystandardm%ddefault", capacityTier)
} else {
priceKey = fmt.Sprintf("Rediscapacitystandardnodem%d", capacityTier)
}
}

pricePerHrPerGbInNanos := priceMap[priceKey].Regions[redis.LocationId].Price[0].Nanos
pricePerHrPerGbInDollars := pricePerHrPerGbInNanos / math.Pow(10, 9)

now := time.Now().UTC()
startTime := getStartTime(redis.GetCreateTime().AsTime(), now)

hours := now.Sub(startTime).Hours()

cost := hours * pricePerHrPerGbInDollars

if redis.ReadReplicasMode == redispb.Instance_READ_REPLICAS_ENABLED {
cost *= float64(redis.ReplicaCount)
}

return cost
}

func getStartTime(createTime, now time.Time) time.Time {
firstOfCurrentMonth := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, time.UTC)
if createTime.After(firstOfCurrentMonth) {
return createTime
}
return firstOfCurrentMonth
}

func getCapacityTier(memorySizeGb int32, prices []int32) int {
capacityTier := 5
for idx, price := range prices {
if memorySizeGb <= price {
capacityTier = idx + 1
}
}
return capacityTier
}

0 comments on commit c2abda5

Please sign in to comment.