diff --git a/e2e/price_test.go b/e2e/price_test.go index 4e4f378..2a5d7a3 100644 --- a/e2e/price_test.go +++ b/e2e/price_test.go @@ -160,6 +160,10 @@ var expectedPricingConfig = ` "price_per_hour_usd": 0.00005, "price_per_gib_usd": 0.016 }, + "runtime": { + "price_per_hour_usd": 0.00005, + "price_per_gib_usd": 0.016 + }, "dvpn": { "price_per_hour_usd": 0.00005, "price_per_gib_usd": 0.05 @@ -186,6 +190,10 @@ var expectedPricingConfig = ` "price_per_hour_usd": 0.00005, "price_per_gib_usd": 0.012 }, + "runtime": { + "price_per_hour_usd": 0.00005, + "price_per_gib_usd": 0.012 + }, "dvpn": { "price_per_hour_usd": 0.00005, "price_per_gib_usd": 0.03 diff --git a/price/pricingbyservice/config.go b/price/pricingbyservice/config.go index 649140a..d90d63f 100644 --- a/price/pricingbyservice/config.go +++ b/price/pricingbyservice/config.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "fmt" + "strings" "sync" "time" @@ -164,6 +165,7 @@ const ( ServiceTypeDataTransfer ServiceType = "data_transfer" ServiceTypeDVPN ServiceType = "dvpn" ServiceTypeMonitoring ServiceType = "monitoring" + ServiceTypeRuntime ServiceType = "runtime" ) var validServiceTypes = map[ServiceType]struct{}{ @@ -173,6 +175,7 @@ var validServiceTypes = map[ServiceType]struct{}{ ServiceTypeDataTransfer: {}, ServiceTypeDVPN: {}, ServiceTypeMonitoring: {}, + ServiceTypeRuntime: {}, } var allServiceTypes = []ServiceType{ @@ -182,9 +185,13 @@ var allServiceTypes = []ServiceType{ ServiceTypeDataTransfer, ServiceTypeDVPN, ServiceTypeMonitoring, + ServiceTypeRuntime, } func (s ServiceType) Validate() error { + if strings.HasPrefix(string(s), string(ServiceTypeRuntime)+"-") { + return nil + } if _, ok := validServiceTypes[s]; !ok { return fmt.Errorf("%v is an invalid service type", s) } @@ -216,6 +223,7 @@ type PriceByServiceTypeUSD struct { DataTransfer PriceUSD `json:"data_transfer"` DVPN PriceUSD `json:"dvpn"` Monitoring PriceUSD `json:"monitoring"` + Runtime PriceUSD `json:"runtime"` } func (p PriceByServiceTypeUSD) Validate() error { @@ -234,6 +242,9 @@ func (p PriceByServiceTypeUSD) Validate() error { if err := p.Monitoring.Validate(); err != nil { return err } + if err := p.Runtime.Validate(); err != nil { + return err + } return p.DataTransfer.Validate() } @@ -281,6 +292,10 @@ var defaultPriceConfig = `{ "price_per_hour_usd": 0.00005, "price_per_gib_usd": 0.016 }, + "runtime": { + "price_per_hour_usd": 0.00005, + "price_per_gib_usd": 0.016 + }, "dvpn": { "price_per_hour_usd": 0.00005, "price_per_gib_usd": 0.05 @@ -307,6 +322,10 @@ var defaultPriceConfig = `{ "price_per_hour_usd": 0.00005, "price_per_gib_usd": 0.012 }, + "runtime": { + "price_per_hour_usd": 0.00005, + "price_per_gib_usd": 0.012 + }, "dvpn": { "price_per_hour_usd": 0.00005, "price_per_gib_usd": 0.03 diff --git a/price/pricingbyservice/config_test.go b/price/pricingbyservice/config_test.go index 36b3dde..73939c7 100644 --- a/price/pricingbyservice/config_test.go +++ b/price/pricingbyservice/config_test.go @@ -30,6 +30,7 @@ func TestConfig_Validate(t *testing.T) { DataTransfer: mprice, DVPN: mprice, Monitoring: mprice, + Runtime: mprice, }, Other: &PriceByServiceTypeUSD{ Wireguard: mprice, @@ -38,6 +39,7 @@ func TestConfig_Validate(t *testing.T) { DataTransfer: mprice, DVPN: mprice, Monitoring: mprice, + Runtime: mprice, }, }, CountryModifiers: map[ISO3166CountryCode]Modifier{ @@ -60,6 +62,7 @@ func TestConfig_Validate(t *testing.T) { DataTransfer: mprice, DVPN: mprice, Monitoring: mprice, + Runtime: mprice, }, Other: &PriceByServiceTypeUSD{ Wireguard: mprice, @@ -68,6 +71,7 @@ func TestConfig_Validate(t *testing.T) { DataTransfer: mprice, DVPN: mprice, Monitoring: mprice, + Runtime: mprice, }, }, CountryModifiers: map[ISO3166CountryCode]Modifier{ @@ -90,6 +94,7 @@ func TestConfig_Validate(t *testing.T) { DataTransfer: mprice, DVPN: mprice, Monitoring: mprice, + Runtime: mprice, }, Other: &PriceByServiceTypeUSD{ Wireguard: mprice, @@ -98,6 +103,7 @@ func TestConfig_Validate(t *testing.T) { DataTransfer: mprice, DVPN: mprice, Monitoring: mprice, + Runtime: mprice, }, }, CountryModifiers: map[ISO3166CountryCode]Modifier{ @@ -119,6 +125,7 @@ func TestConfig_Validate(t *testing.T) { DataTransfer: mprice, DVPN: mprice, Monitoring: mprice, + Runtime: mprice, }, Other: &PriceByServiceTypeUSD{ Wireguard: mprice, @@ -130,6 +137,7 @@ func TestConfig_Validate(t *testing.T) { }, DVPN: mprice, Monitoring: mprice, + Runtime: mprice, }, }, CountryModifiers: map[ISO3166CountryCode]Modifier{ @@ -152,6 +160,7 @@ func TestConfig_Validate(t *testing.T) { DataTransfer: mprice, DVPN: mprice, Monitoring: mprice, + Runtime: mprice, }, Other: &PriceByServiceTypeUSD{ Scraping: mprice, @@ -159,6 +168,7 @@ func TestConfig_Validate(t *testing.T) { DataTransfer: mprice, DVPN: mprice, Monitoring: mprice, + Runtime: mprice, }, }, CountryModifiers: map[ISO3166CountryCode]Modifier{ @@ -184,6 +194,31 @@ func TestConfig_Validate(t *testing.T) { } } +func TestServiceTypeValidate(t *testing.T) { + for _, serviceType := range []ServiceType{ServiceTypeRuntime, "runtime-cdp"} { + if err := serviceType.Validate(); err != nil { + t.Errorf("Validate() error = %v for service type %q", err, serviceType) + } + } +} + +func TestRuntimePricesUseRuntimeConfiguration(t *testing.T) { + priceUpdater := &PriceUpdater{} + runtimePrice := PriceUSD{PricePerHour: 0.01, PricePerGiB: 0.02} + config := Config{BasePrices: PriceByTypeUSD{ + Residential: &PriceByServiceTypeUSD{DataTransfer: PriceUSD{PricePerHour: 1, PricePerGiB: 1}, Runtime: runtimePrice}, + Other: &PriceByServiceTypeUSD{DataTransfer: PriceUSD{PricePerHour: 1, PricePerGiB: 1}, Runtime: runtimePrice}, + }} + + prices := priceUpdater.generateNewDefaults(1, config) + if prices.Current.Residential.Runtime.PricePerHourHumanReadable != runtimePrice.PricePerHour { + t.Fatalf("runtime price per hour = %v, want %v", prices.Current.Residential.Runtime.PricePerHourHumanReadable, runtimePrice.PricePerHour) + } + if prices.Current.Other.Runtime.PricePerGiBHumanReadable != runtimePrice.PricePerGiB { + t.Fatalf("runtime price per GiB = %v, want %v", prices.Current.Other.Runtime.PricePerGiBHumanReadable, runtimePrice.PricePerGiB) + } +} + func TestDemandBoostConfig_Validate(t *testing.T) { tests := []struct { name string diff --git a/price/pricingbyservice/price_updater.go b/price/pricingbyservice/price_updater.go index 11e5049..75d8c0e 100644 --- a/price/pricingbyservice/price_updater.go +++ b/price/pricingbyservice/price_updater.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "math/big" + "strings" "sync" "time" @@ -164,13 +165,20 @@ func DemandBoostServiceMultipliers(cfg Config, demandIndexes map[ISO3166CountryC multipliers[country] = make(map[ServiceType]float64, len(serviceTypes)) for _, serviceType := range serviceTypes { - multipliers[country][serviceType] = multiplier + multipliers[country][canonicalServiceType(serviceType)] = multiplier } } return multipliers } +func canonicalServiceType(serviceType ServiceType) ServiceType { + if strings.HasPrefix(string(serviceType), string(ServiceTypeRuntime)+"-") { + return ServiceTypeRuntime + } + return serviceType +} + func demandBoostMultiplier(boostCfg DemandBoostCountryCfg, currentDemandIndex float64) float64 { gapRatio := (boostCfg.TargetDemandIndex - currentDemandIndex) / boostCfg.TargetDemandIndex if gapRatio < 0 { @@ -237,6 +245,11 @@ func (p *PriceUpdater) submitPriceMetric(country string, price *PriceByType) { metrics.CurrentPriceByCountry.WithLabelValues(country, "residential", "data_transfer", "per_gib").Set(price.Residential.DataTransfer.PricePerGiBHumanReadable) metrics.CurrentPriceByCountry.WithLabelValues(country, "residential", "data_transfer", "per_hour").Set(price.Residential.DataTransfer.PricePerHourHumanReadable) + metrics.CurrentPriceByCountry.WithLabelValues(country, "other", "runtime", "per_gib").Set(price.Other.Runtime.PricePerGiBHumanReadable) + metrics.CurrentPriceByCountry.WithLabelValues(country, "other", "runtime", "per_hour").Set(price.Other.Runtime.PricePerHourHumanReadable) + metrics.CurrentPriceByCountry.WithLabelValues(country, "residential", "runtime", "per_gib").Set(price.Residential.Runtime.PricePerGiBHumanReadable) + metrics.CurrentPriceByCountry.WithLabelValues(country, "residential", "runtime", "per_hour").Set(price.Residential.Runtime.PricePerHourHumanReadable) + metrics.CurrentPriceByCountry.WithLabelValues(country, "other", "dvpn", "per_gib").Set(price.Other.DVPN.PricePerGiBHumanReadable) metrics.CurrentPriceByCountry.WithLabelValues(country, "other", "dvpn", "per_hour").Set(price.Other.DVPN.PricePerHourHumanReadable) metrics.CurrentPriceByCountry.WithLabelValues(country, "residential", "dvpn", "per_gib").Set(price.Residential.DVPN.PricePerGiBHumanReadable) @@ -300,6 +313,12 @@ func (p *PriceUpdater) generateNewDefaults(mystUSD float64, cfg Config) *PriceHi PricePerGiB: calculatePriceMYST(mystUSD, cfg.BasePrices.Residential.DataTransfer.PricePerGiB, 1), PricePerGiBHumanReadable: calculatePriceMystFloat(mystUSD, cfg.BasePrices.Residential.DataTransfer.PricePerGiB, 1), }, + Runtime: Price{ + PricePerHour: calculatePriceMYST(mystUSD, cfg.BasePrices.Residential.Runtime.PricePerHour, 1), + PricePerHourHumanReadable: calculatePriceMystFloat(mystUSD, cfg.BasePrices.Residential.Runtime.PricePerHour, 1), + PricePerGiB: calculatePriceMYST(mystUSD, cfg.BasePrices.Residential.Runtime.PricePerGiB, 1), + PricePerGiBHumanReadable: calculatePriceMystFloat(mystUSD, cfg.BasePrices.Residential.Runtime.PricePerGiB, 1), + }, DVPN: Price{ PricePerHour: calculatePriceMYST(mystUSD, cfg.BasePrices.Residential.DVPN.PricePerHour, 1), PricePerHourHumanReadable: calculatePriceMystFloat(mystUSD, cfg.BasePrices.Residential.DVPN.PricePerHour, 1), @@ -338,6 +357,12 @@ func (p *PriceUpdater) generateNewDefaults(mystUSD float64, cfg Config) *PriceHi PricePerGiB: calculatePriceMYST(mystUSD, cfg.BasePrices.Other.DataTransfer.PricePerGiB, 1), PricePerGiBHumanReadable: calculatePriceMystFloat(mystUSD, cfg.BasePrices.Other.DataTransfer.PricePerGiB, 1), }, + Runtime: Price{ + PricePerHour: calculatePriceMYST(mystUSD, cfg.BasePrices.Other.Runtime.PricePerHour, 1), + PricePerHourHumanReadable: calculatePriceMystFloat(mystUSD, cfg.BasePrices.Other.Runtime.PricePerHour, 1), + PricePerGiB: calculatePriceMYST(mystUSD, cfg.BasePrices.Other.Runtime.PricePerGiB, 1), + PricePerGiBHumanReadable: calculatePriceMystFloat(mystUSD, cfg.BasePrices.Other.Runtime.PricePerGiB, 1), + }, DVPN: Price{ PricePerHour: calculatePriceMYST(mystUSD, cfg.BasePrices.Other.DVPN.PricePerHour, 1), PricePerHourHumanReadable: calculatePriceMystFloat(mystUSD, cfg.BasePrices.Other.DVPN.PricePerHour, 1), @@ -407,6 +432,7 @@ func (p *PriceUpdater) generateNewPerCountryWithModifier(mystUSD float64, cfg Co scrapingMod := modifierFor(countryCode, ServiceTypeScraping) quicScrapingMod := modifierFor(countryCode, ServiceTypeQUICScraping) dataTransferMod := modifierFor(countryCode, ServiceTypeDataTransfer) + runtimeMod := modifierFor(countryCode, ServiceTypeRuntime) dvpnMod := modifierFor(countryCode, ServiceTypeDVPN) monitoringMod := modifierFor(countryCode, ServiceTypeMonitoring) @@ -437,6 +463,12 @@ func (p *PriceUpdater) generateNewPerCountryWithModifier(mystUSD float64, cfg Co PricePerGiB: calculatePriceMYST(mystUSD, cfg.BasePrices.Residential.DataTransfer.PricePerGiB, dataTransferMod.Residential), PricePerGiBHumanReadable: calculatePriceMystFloat(mystUSD, cfg.BasePrices.Residential.DataTransfer.PricePerGiB, dataTransferMod.Residential), }, + Runtime: Price{ + PricePerHour: calculatePriceMYST(mystUSD, cfg.BasePrices.Residential.Runtime.PricePerHour, runtimeMod.Residential), + PricePerHourHumanReadable: calculatePriceMystFloat(mystUSD, cfg.BasePrices.Residential.Runtime.PricePerHour, runtimeMod.Residential), + PricePerGiB: calculatePriceMYST(mystUSD, cfg.BasePrices.Residential.Runtime.PricePerGiB, runtimeMod.Residential), + PricePerGiBHumanReadable: calculatePriceMystFloat(mystUSD, cfg.BasePrices.Residential.Runtime.PricePerGiB, runtimeMod.Residential), + }, DVPN: Price{ PricePerHour: calculatePriceMYST(mystUSD, cfg.BasePrices.Residential.DVPN.PricePerHour, dvpnMod.Residential), PricePerHourHumanReadable: calculatePriceMystFloat(mystUSD, cfg.BasePrices.Residential.DVPN.PricePerHour, dvpnMod.Residential), @@ -475,6 +507,12 @@ func (p *PriceUpdater) generateNewPerCountryWithModifier(mystUSD float64, cfg Co PricePerGiB: calculatePriceMYST(mystUSD, cfg.BasePrices.Other.DataTransfer.PricePerGiB, dataTransferMod.Other), PricePerGiBHumanReadable: calculatePriceMystFloat(mystUSD, cfg.BasePrices.Other.DataTransfer.PricePerGiB, dataTransferMod.Other), }, + Runtime: Price{ + PricePerHour: calculatePriceMYST(mystUSD, cfg.BasePrices.Other.Runtime.PricePerHour, runtimeMod.Other), + PricePerHourHumanReadable: calculatePriceMystFloat(mystUSD, cfg.BasePrices.Other.Runtime.PricePerHour, runtimeMod.Other), + PricePerGiB: calculatePriceMYST(mystUSD, cfg.BasePrices.Other.Runtime.PricePerGiB, runtimeMod.Other), + PricePerGiBHumanReadable: calculatePriceMystFloat(mystUSD, cfg.BasePrices.Other.Runtime.PricePerGiB, runtimeMod.Other), + }, DVPN: Price{ PricePerHour: calculatePriceMYST(mystUSD, cfg.BasePrices.Other.DVPN.PricePerHour, dvpnMod.Other), PricePerHourHumanReadable: calculatePriceMystFloat(mystUSD, cfg.BasePrices.Other.DVPN.PricePerHour, dvpnMod.Other), @@ -574,6 +612,7 @@ type PriceByServiceType struct { Scraping Price `json:"scraping"` QUICScraping Price `json:"quic_scraping"` DataTransfer Price `json:"data_transfer"` + Runtime Price `json:"runtime"` DVPN Price `json:"dvpn"` Monitoring Price `json:"monitoring"` } diff --git a/price/pricingbyservice/price_updater_test.go b/price/pricingbyservice/price_updater_test.go index a5a007d..e52bc63 100644 --- a/price/pricingbyservice/price_updater_test.go +++ b/price/pricingbyservice/price_updater_test.go @@ -87,7 +87,7 @@ func TestDemandBoostServiceMultipliers(t *testing.T) { "PL": { TargetDemandIndex: 0.1, MaxBonus: 0.5, - ServiceTypes: []ServiceType{ServiceTypeDVPN, ServiceTypeWireguard}, + ServiceTypes: []ServiceType{ServiceTypeDVPN, ServiceTypeWireguard, "runtime-cdp"}, }, "GB": {TargetDemandIndex: 0.1, MaxBonus: 0.5}, }, @@ -102,6 +102,9 @@ func TestDemandBoostServiceMultipliers(t *testing.T) { if got["PL"][ServiceTypeDVPN] != 1.5 || got["PL"][ServiceTypeWireguard] != 1.5 { t.Fatalf("expected PL selected service multipliers to be boosted, got %#v", got["PL"]) } + if got["PL"][ServiceTypeRuntime] != 1.5 { + t.Fatalf("expected PL runtime multiplier to be boosted, got %#v", got["PL"]) + } if _, ok := got["PL"][ServiceTypeScraping]; ok { t.Fatalf("expected PL scraping to be omitted, got %#v", got["PL"]) } @@ -818,7 +821,9 @@ func TestPricer_generateNewDefaults(t *testing.T) { p := &PriceUpdater{ lp: tt.fields.lp, } - if got := p.generateNewDefaults(tt.args.mystInUSD, tt.fields.cfg); !reflect.DeepEqual(got, tt.want) { + got := p.generateNewDefaults(tt.args.mystInUSD, tt.fields.cfg) + clearRuntimePrices(got) + if !reflect.DeepEqual(got, tt.want) { t.Errorf("Pricer.generateNewDefaults() = %v, want %v", got, tt.want) } }) @@ -1628,6 +1633,7 @@ func TestPricer_generateNewPerCountry(t *testing.T) { generated := p.generateNewPerCountry(tt.args.mystInUSD, tt.fields.cfg) got := generated["US"] want := tt.want["US"] + clearRuntimePrices(got) if !reflect.DeepEqual(got, want) { t.Errorf("Pricer.generateNewPerCountry() = %v, want %v", got, want) @@ -1635,3 +1641,10 @@ func TestPricer_generateNewPerCountry(t *testing.T) { }) } } + +func clearRuntimePrices(priceHistory *PriceHistory) { + for _, prices := range []*PriceByType{priceHistory.Current, priceHistory.Previous} { + prices.Residential.Runtime = Price{} + prices.Other.Runtime = Price{} + } +}