From 7d3c13c66885e64a9f603cdeeb8f25d31f41f739 Mon Sep 17 00:00:00 2001 From: Vitalii Date: Mon, 12 Feb 2024 16:07:43 +0200 Subject: [PATCH] satellite/emission: refactor dimension handling logic Make dimension handling more efficient. It avoids the string manipulation and removes the need for simplify. Resolves post-merge comment of #12318 Issue: https://github.com/storj/storj/issues/6694 Change-Id: Ic5d802afbec4f9e92e35dd660b30c517e3af32b8 --- satellite/emission/dimen.go | 253 +++++++++++++------------------ satellite/emission/dimen_test.go | 39 +++++ satellite/emission/service.go | 172 ++++++++++----------- 3 files changed, 223 insertions(+), 241 deletions(-) create mode 100644 satellite/emission/dimen_test.go diff --git a/satellite/emission/dimen.go b/satellite/emission/dimen.go index 28e45ef04299..c44327db7a8a 100644 --- a/satellite/emission/dimen.go +++ b/satellite/emission/dimen.go @@ -4,179 +4,138 @@ package emission import ( + "bytes" "fmt" - "sort" - "strings" "github.com/zeebo/errs" - "golang.org/x/exp/slices" ) -// Q is a Val constructor function without any dimension. -var Q = ValMaker("") +// Unit represents a set of unit dimensions. +// Unit{byte: 0, watt: 0, hour: 1, kilogram: 0} means hour (H). +// Unit{byte: 1, watt: 0, hour: -1, kilogram: 0} means byte/hour (B/H). +// Unit{byte: -1, watt: 0, hour: -1, kilogram: 1} means kg/byte-hour (kg/B*H). +type Unit struct { + byte int8 + watt int8 + hour int8 + kilogram int8 +} -// Val represents a value which consists of the numeric value itself and it's dimensions e.g. 1 kW. -// It may be used to represent a really complex value e.g. 1 kW / H or 0.005 W * H / GB. -type Val struct { - Amount float64 - Num []string - Denom []string +// Value creates new Val from existing Unit. +func (u *Unit) Value(v float64) Val { + return Val{Value: v, Unit: *u} } -// ValMaker creates new Val constructor function by given string representation of the unit e.g. kg. -// By providing amount value to a constructor function we create a value instance. -// kg := ValMaker("kg") - kg is a constructor function here. -// kg(1) is a 1 kilogram Val. -func ValMaker(unit string) func(val float64) *Val { - if unit == "" { - return func(val float64) *Val { - return &Val{Amount: val} - } - } - return func(val float64) *Val { - return &Val{Amount: val, Num: []string{unit}} - } +// Mul multiplies existing Unit by a given one. +func (u *Unit) Mul(b Unit) { + u.byte += b.byte + u.watt += b.watt + u.hour += b.hour + u.kilogram += b.kilogram } -// Maker creates a new Val constructor function from already existing Val. -// This is used to handle dimension factor differences. -// B := ValMaker("B") - B is a constructor function here. -// B(1) returns 1 byte Val. -// KB := B(1000).Maker() returns a construction function for a KB value. -// KB(1) returns 1 kilobyte Val but under the hood it's still 1000 B value. -func (v *Val) Maker() func(val float64) *Val { - return func(val float64) *Val { - return v.Mul(Q(val)) - } +// Div divides existing Unit by a given one. +func (u *Unit) Div(b Unit) { + u.byte -= b.byte + u.watt -= b.watt + u.hour -= b.hour + u.kilogram -= b.kilogram } -// Mul multiplies existing Val with a given one and returns new Val. -// It adjusts both the amount and the dimensions accordingly. -// Q := ValMaker("") - Q is a constructor function which has no dimension. -// Q(0.005) returns 0.005 Val. -// Q(0.005).Mul(W(1)) means 0.005 * 1 W = 0.005 W. -// Q(0.005).Mul(W(1)).Mul(H(1)) means 0.005 * 1 W * 1 H = 0.005 W * 1 H = 0.005 W * H. -func (v *Val) Mul(rhs *Val) *Val { - rv := &Val{Amount: v.Amount * rhs.Amount} - rv.Num = append(rv.Num, v.Num...) - rv.Num = append(rv.Num, rhs.Num...) - rv.Denom = append(rv.Denom, v.Denom...) - rv.Denom = append(rv.Denom, rhs.Denom...) - rv.simplify() - return rv +// String returns string representation of the Unit. +func (u *Unit) String() string { + var num bytes.Buffer + var div bytes.Buffer + + a := func(prefix string, v int8) { + if v == 0 { + return + } + + target := &num + if v < 0 { + target = &div + v = -v + } + + switch v { + case 1: + target.WriteString(prefix) + case 2: + target.WriteString(prefix + "²") + case 3: + target.WriteString(prefix + "³") + default: + target.WriteString(fmt.Sprintf("%s^%d", prefix, v)) + } + } + + a("B", u.byte) + a("W", u.watt) + a("H", u.hour) + a("kg", u.kilogram) + + n := num.String() + d := div.String() + + switch { + case n == "" && d == "": + return "" + case d == "": + return n + case n == "": + return "1/" + d + default: + return n + "/" + d + } } -// Div divides one Val by a given one and returns new Val. -// It adjusts both the amount and the dimensions accordingly. -// Q := ValMaker("") - Q is a constructor function which has no dimension. -// Q(0.005) returns 0.005 Val. -// Q(0.005).Mul(W(1)) means 0.005 * 1 W = 0.005 W. -// Q(0.005).Mul(W(1)).Div(H(1)) means 0.005 * 1 W / 1 H = 0.005 W / 1 H = 0.005 W / H. -func (v *Val) Div(rhs *Val) *Val { - rv := &Val{Amount: v.Amount / rhs.Amount} - rv.Num = append(rv.Num, v.Num...) - rv.Num = append(rv.Num, rhs.Denom...) - rv.Denom = append(rv.Denom, v.Denom...) - rv.Denom = append(rv.Denom, rhs.Num...) - rv.simplify() - return rv +// Val represents a value which consists of the numeric value itself and it's dimensions e.g. 1 W. +// It may be used to represent a really complex value e.g. 1 W / H or 0.005 W * H / B. +type Val struct { + Value float64 + Unit Unit } // Add sums two Val instances with the same dimensions. -func (v *Val) Add(rhs *Val) (*Val, error) { - v.simplify() - rhs.simplify() - if !slices.Equal(v.Num, rhs.Num) { - return nil, errs.New(fmt.Sprintf("cannot add units %s, %s", v, rhs)) - } - if !slices.Equal(v.Denom, rhs.Denom) { - return nil, errs.New(fmt.Sprintf("cannot add units %s, %s", v, rhs)) +func (a Val) Add(b Val) (Val, error) { + if a.Unit != b.Unit { + return Val{}, errs.New(fmt.Sprintf("cannot add units %s, %s", a.Unit.String(), b.Unit.String())) } - return &Val{ - Amount: v.Amount + rhs.Amount, - Num: slices.Clone(v.Num), - Denom: slices.Clone(v.Denom), - }, nil + r := a + r.Value += b.Value + return r, nil } // Sub subtracts one Val from another if they have the same dimensions. -func (v *Val) Sub(rhs *Val) (*Val, error) { - v.simplify() - rhs.simplify() - if !slices.Equal(v.Num, rhs.Num) { - return nil, errs.New(fmt.Sprintf("cannot subtract units %s, %s", v, rhs)) - } - if !slices.Equal(v.Denom, rhs.Denom) { - return nil, errs.New(fmt.Sprintf("cannot subtract units %s, %s", v, rhs)) +func (a Val) Sub(b Val) (Val, error) { + if a.Unit != b.Unit { + return Val{}, errs.New(fmt.Sprintf("cannot subtract units %s, %s", a.Unit.String(), b.Unit.String())) } - return &Val{ - Amount: v.Amount - rhs.Amount, - Num: slices.Clone(v.Num), - Denom: slices.Clone(v.Denom), - }, nil + r := a + r.Value -= b.Value + return r, nil } -// InUnits converts a Val into the specified units, if possible. -func (v *Val) InUnits(units *Val) (float64, error) { - x := v.Div(units) - if len(x.Num) != 0 { - return 0, errs.New(fmt.Sprintf("cannot convert %s to units %s", v, units)) - } - if len(x.Denom) != 0 { - return 0, errs.New(fmt.Sprintf("cannot convert %s to units %s", v, units)) - } - return x.Amount, nil +// Mul multiplies existing Val with a given one and returns new Val. +// It adjusts both the amount and the dimensions accordingly. +func (a Val) Mul(b Val) Val { + r := a + r.Unit.Mul(b.Unit) + r.Value *= b.Value + return r } -// String returns string representation of the Val. -// Q := ValMaker("") - Q is a constructor function which has no dimension. -// Q(0.005).String is just 0.005. -// Q(0.005).Mul(W(1)).Mul(H(1)).Div(MB(1)).String() is 0.005 W * H / MB. -// KB(1).String() returns 1000 B because KB Val was created from a B Val. -func (v *Val) String() string { - var b strings.Builder - fmt.Fprintf(&b, "%v", v.Amount) - if len(v.Num) > 0 { - b.WriteByte(' ') - for i, num := range v.Num { - if i != 0 { - b.WriteByte('*') - } - b.WriteString(num) - } - } - if len(v.Denom) > 0 { - b.WriteString("/") - for i, num := range v.Denom { - if i != 0 { - b.WriteByte('/') - } - b.WriteString(num) - } - } - return b.String() +// Div divides one Val by a given one and returns new Val. +// It adjusts both the amount and the dimensions accordingly. +func (a Val) Div(b Val) Val { + r := a + r.Unit.Div(b.Unit) + r.Value /= b.Value + return r } -func (v *Val) simplify() { - counts := map[string]int{} - for _, num := range v.Num { - counts[num]++ - } - for _, denom := range v.Denom { - counts[denom]-- - } - v.Num = v.Num[:0] - v.Denom = v.Denom[:0] - for name, count := range counts { - for count > 0 { - v.Num = append(v.Num, name) - count-- - } - for count < 0 { - v.Denom = append(v.Denom, name) - count++ - } - } - sort.Strings(v.Num) - sort.Strings(v.Denom) +// String returns string representation of the Val. +func (a Val) String() string { + return fmt.Sprintf("%f[%s]", a.Value, a.Unit.String()) } diff --git a/satellite/emission/dimen_test.go b/satellite/emission/dimen_test.go new file mode 100644 index 000000000000..77e364d94f0e --- /dev/null +++ b/satellite/emission/dimen_test.go @@ -0,0 +1,39 @@ +// Copyright (C) 2024 Storj Labs, Inc. +// See LICENSE for copying information. + +package emission + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestUnit_String(t *testing.T) { + cases := []struct { + unit Unit + expected string + }{ + {unit: Unit{}, expected: ""}, + {unit: Unit{byte: 1}, expected: "B"}, + {unit: Unit{watt: 1}, expected: "W"}, + {unit: Unit{hour: 1}, expected: "H"}, + {unit: Unit{hour: -1}, expected: "1/H"}, + {unit: Unit{kilogram: 1}, expected: "kg"}, + {unit: Unit{kilogram: -1}, expected: "1/kg"}, + {unit: Unit{byte: 1, watt: 1}, expected: "BW"}, + {unit: Unit{byte: 1, watt: -1}, expected: "B/W"}, + {unit: Unit{byte: 1, watt: -1, hour: -1}, expected: "B/WH"}, + {unit: Unit{byte: 1, kilogram: 1, watt: -1, hour: -1}, expected: "Bkg/WH"}, + {unit: Unit{byte: 2, kilogram: 1, watt: -2, hour: -1}, expected: "B²kg/W²H"}, + {unit: Unit{byte: 3, watt: -1, hour: -2}, expected: "B³/WH²"}, + {unit: Unit{byte: 2, watt: -4, hour: -1}, expected: "B²/W^4H"}, + } + + for _, c := range cases { + t.Run(fmt.Sprintf("expected:%s", c.expected), func(t *testing.T) { + require.Equal(t, c.unit.String(), c.expected) + }) + } +} diff --git a/satellite/emission/service.go b/satellite/emission/service.go index 744ae2ebff6a..e07429c0d2b6 100644 --- a/satellite/emission/service.go +++ b/satellite/emission/service.go @@ -13,13 +13,12 @@ import ( var Error = errs.Class("emission service") const ( - decimalMultiplier = 1000 - dayHours = 24 - yearDays = 365.25 - byteLabel = "B" - wattLabel = "W" - hourLabel = "H" - kilogramLabel = "kg" + decimalMultiplier = 1000 + tbToBytesMultiplier = 1e-12 + gbToBytesMultiplier = 1e-9 + dayHours = 24 + yearDays = 365.25 + twelveMonths = 12 ) const ( @@ -32,29 +31,15 @@ const ( ) var ( - // B is a Val constructor function with byte (B) dimension. - B = ValMaker(byteLabel) - // KB is a Val constructor function with kilobyte (KB) dimension. - KB = B(decimalMultiplier).Maker() - // MB is a Val constructor function with megabyte (MB) dimension. - MB = KB(decimalMultiplier).Maker() - // GB is a Val constructor function with gigabyte (GB) dimension. - GB = MB(decimalMultiplier).Maker() - // TB is a Val constructor function with terabyte (TB) dimension. - TB = GB(decimalMultiplier).Maker() - - // W is a Val constructor function with watt (W) dimension. - W = ValMaker(wattLabel) - // kW is a Val constructor function with kilowatt (kW) dimension. - kW = W(decimalMultiplier).Maker() - - // H is a Val constructor function with hour (H) dimension. - H = ValMaker(hourLabel) - // Y is a Val constructor function with year (Y) dimension. - Y = H(dayHours * yearDays).Maker() - - // kg is a Val constructor function with kilogram (kg) dimension. - kg = ValMaker(kilogramLabel) + unitless = Unit{} + byteUnit = Unit{byte: 1} + hour = Unit{hour: 1} + kilogram = Unit{kilogram: 1} + + wattHourPerByte = Unit{watt: 1, hour: 1, byte: -1} + kilogramPerWattHour = Unit{kilogram: 1, watt: -1, hour: -1} + kilogramPerByte = Unit{kilogram: 1, byte: -1} + kilogramPerByteHour = Unit{kilogram: 1, byte: -1, hour: -1} ) // Service is an emission service. @@ -80,7 +65,7 @@ type Impact struct { } // Row holds data row of predefined number of values. -type Row [modalityCount]*Val +type Row [modalityCount]Val // CalculationInput holds input data needed to perform emission impact calculations. type CalculationInput struct { @@ -118,7 +103,7 @@ func (sv *Service) CalculateImpact(input *CalculationInput) (*Impact, error) { // Define a data row of services carbon emission from powering hard drives. carbonFromPower := sv.prepareCarbonFromDrivePoweringRow() - timeStored := H(input.Duration.Seconds() / (60 * 60)) + timeStored := hour.Value(input.Duration.Seconds() / 3600) // Define a data row of services carbon emission from write and repair actions. carbonFromWritesAndRepairs, err := sv.prepareCarbonFromWritesAndRepairsRow(timeStored) @@ -151,44 +136,40 @@ func (sv *Service) CalculateImpact(input *CalculationInput) (*Impact, error) { } // Calculate emission impact per service. - estimatedKgCO2eStorj, err := storjBlended.InUnits(kg(1)) - if err != nil { - return nil, Error.Wrap(err) - } - - estimatedKgCO2eHyperscaler, err := totalCarbon[hyperscaler].InUnits(kg(1)) - if err != nil { - return nil, Error.Wrap(err) - } - - estimatedKgCO2eCorporateDC, err := totalCarbon[corporateDC].InUnits(kg(1)) - if err != nil { - return nil, Error.Wrap(err) - } - + oneKilogram := kilogram.Value(1) rv := &Impact{ - EstimatedKgCO2eStorj: estimatedKgCO2eStorj, - EstimatedKgCO2eHyperscaler: estimatedKgCO2eHyperscaler, - EstimatedKgCO2eCorporateDC: estimatedKgCO2eCorporateDC, + EstimatedKgCO2eStorj: storjBlended.Div(oneKilogram).Value, + EstimatedKgCO2eHyperscaler: totalCarbon[hyperscaler].Div(oneKilogram).Value, + EstimatedKgCO2eCorporateDC: totalCarbon[corporateDC].Div(oneKilogram).Value, } if rv.EstimatedKgCO2eHyperscaler != 0 { - rv.EstimatedFractionSavingsAgainstHyperscaler = 1 - (rv.EstimatedKgCO2eStorj / rv.EstimatedKgCO2eHyperscaler) + estimatedFractionSavingsAgainstHyperscaler, err := unitless.Value(1).Sub(unitless.Value(rv.EstimatedKgCO2eStorj / rv.EstimatedKgCO2eHyperscaler)) + if err != nil { + return nil, Error.Wrap(err) + } + + rv.EstimatedFractionSavingsAgainstHyperscaler = estimatedFractionSavingsAgainstHyperscaler.Value } if rv.EstimatedKgCO2eCorporateDC != 0 { - rv.EstimatedFractionSavingsAgainstCorporateDC = 1 - (rv.EstimatedKgCO2eStorj / rv.EstimatedKgCO2eCorporateDC) + estimatedFractionSavingsAgainstCorporateDC, err := unitless.Value(1).Sub(unitless.Value(rv.EstimatedKgCO2eStorj / rv.EstimatedKgCO2eCorporateDC)) + if err != nil { + return nil, Error.Wrap(err) + } + + rv.EstimatedFractionSavingsAgainstCorporateDC = estimatedFractionSavingsAgainstCorporateDC.Value } return rv, nil } func (sv *Service) prepareExpansionFactorRow() *Row { - storjExpansionFactor := Q(sv.config.StorjExpansionFactor) + storjExpansionFactor := unitless.Value(sv.config.StorjExpansionFactor) row := new(Row) - row[hyperscaler] = Q(sv.config.HyperscalerExpansionFactor) - row[corporateDC] = Q(sv.config.CorporateDCExpansionFactor) + row[hyperscaler] = unitless.Value(sv.config.HyperscalerExpansionFactor) + row[corporateDC] = unitless.Value(sv.config.CorporateDCExpansionFactor) row[storjStandard] = storjExpansionFactor row[storjReused] = storjExpansionFactor row[storjNew] = storjExpansionFactor @@ -197,11 +178,11 @@ func (sv *Service) prepareExpansionFactorRow() *Row { } func (sv *Service) prepareRegionCountRow() *Row { - storjRegionCount := Q(sv.config.StorjRegionCount) + storjRegionCount := unitless.Value(sv.config.StorjRegionCount) row := new(Row) - row[hyperscaler] = Q(sv.config.HyperscalerRegionCount) - row[corporateDC] = Q(sv.config.CorporateDCRegionCount) + row[hyperscaler] = unitless.Value(sv.config.HyperscalerRegionCount) + row[corporateDC] = unitless.Value(sv.config.CorporateDCRegionCount) row[storjStandard] = storjRegionCount row[storjReused] = storjRegionCount row[storjNew] = storjRegionCount @@ -211,10 +192,9 @@ func (sv *Service) prepareRegionCountRow() *Row { func (sv *Service) prepareNetworkWeightingRow() (*Row, error) { row := new(Row) - row[storjStandard] = Q(sv.config.StorjStandardNetworkWeighting) - row[storjNew] = Q(sv.config.StorjNewNetworkWeighting) - - storjNotNewNodesFraction, err := Q(1).Sub(row[storjNew]) + row[storjStandard] = unitless.Value(sv.config.StorjStandardNetworkWeighting) + row[storjNew] = unitless.Value(sv.config.StorjNewNetworkWeighting) + storjNotNewNodesFraction, err := unitless.Value(1).Sub(row[storjNew]) if err != nil { return nil, err } @@ -230,11 +210,11 @@ func (sv *Service) prepareNetworkWeightingRow() (*Row, error) { } func (sv *Service) prepareUtilizationRow() *Row { - storjUtilizationFraction := Q(sv.config.StorjUtilizationFraction) + storjUtilizationFraction := unitless.Value(sv.config.StorjUtilizationFraction) row := new(Row) - row[hyperscaler] = Q(sv.config.HyperscalerUtilizationFraction) - row[corporateDC] = Q(sv.config.CorporateDCUtilizationFraction) + row[hyperscaler] = unitless.Value(sv.config.HyperscalerUtilizationFraction) + row[corporateDC] = unitless.Value(sv.config.CorporateDCUtilizationFraction) row[storjStandard] = storjUtilizationFraction row[storjReused] = storjUtilizationFraction row[storjNew] = storjUtilizationFraction @@ -243,9 +223,9 @@ func (sv *Service) prepareUtilizationRow() *Row { } func (sv *Service) prepareDriveLifetimeRow() *Row { - standardDriveLife := Y(sv.config.StandardDriveLife) - shortenedDriveLife := Y(sv.config.ShortenedDriveLife) - extendedDriveLife := Y(sv.config.ExtendedDriveLife) + standardDriveLife := yearsToHours(sv.config.StandardDriveLife) + shortenedDriveLife := yearsToHours(sv.config.ShortenedDriveLife) + extendedDriveLife := yearsToHours(sv.config.ExtendedDriveLife) row := new(Row) row[hyperscaler] = standardDriveLife @@ -258,8 +238,8 @@ func (sv *Service) prepareDriveLifetimeRow() *Row { } func (sv *Service) prepareDriveEmbodiedCarbonEmissionRow() *Row { - newDriveEmbodiedCarbon := kg(sv.config.NewDriveEmbodiedCarbon).Div(TB(1)) - noEmbodiedCarbon := kg(0).Div(TB(1)) + newDriveEmbodiedCarbon := kilogramPerByte.Value(sv.config.NewDriveEmbodiedCarbon * tbToBytesMultiplier) + noEmbodiedCarbon := kilogramPerByte.Value(0) row := new(Row) row[hyperscaler] = newDriveEmbodiedCarbon @@ -281,8 +261,8 @@ func prepareDriveAmortizedEmbodiedCarbonEmissionRow(driveCarbonRow, driveLifetim } func (sv *Service) prepareCarbonFromDrivePoweringRow() *Row { - carbonFromDrivePowering := kg(sv.config.CarbonFromDrivePowering).Div(TB(1).Mul(Y(1))) - noCarbonFromDrivePowering := kg(0).Div(B(1).Mul(H(1))) + carbonFromDrivePowering := kilogramPerByteHour.Value(sv.config.CarbonFromDrivePowering * tbToBytesMultiplier / dayHours / yearDays) + noCarbonFromDrivePowering := kilogramPerByteHour.Value(0) row := new(Row) row[hyperscaler] = carbonFromDrivePowering @@ -294,19 +274,19 @@ func (sv *Service) prepareCarbonFromDrivePoweringRow() *Row { return row } -func (sv *Service) prepareCarbonFromWritesAndRepairsRow(timeStored *Val) (*Row, error) { - writeEnergy := Q(sv.config.WriteEnergy).Mul(W(1)).Mul(H(1)).Div(GB(1)) - CO2PerEnergy := kg(sv.config.CO2PerEnergy).Div(kW(1).Mul(H(1))) - noCarbonFromWritesAndRepairs := kg(0).Div(B(1).Mul(H(1))) +func (sv *Service) prepareCarbonFromWritesAndRepairsRow(timeStored Val) (*Row, error) { + writeEnergy := wattHourPerByte.Value(sv.config.WriteEnergy * gbToBytesMultiplier) + CO2PerEnergy := kilogramPerWattHour.Value(sv.config.CO2PerEnergy / decimalMultiplier) + noCarbonFromWritesAndRepairs := kilogramPerByteHour.Value(0) // this raises 1+monthlyFractionOfDataRepaired to power of 12 // TODO(jt): should we be doing this? - dataRepaired := TB(sv.config.RepairedData) - expandedData := TB(sv.config.ExpandedData) + dataRepaired := byteUnit.Value(sv.config.RepairedData / tbToBytesMultiplier) + expandedData := byteUnit.Value(sv.config.ExpandedData / tbToBytesMultiplier) monthlyFractionOfDataRepaired := dataRepaired.Div(expandedData) - repairFactor := Q(1) - for i := 0; i < 12; i++ { - monthlyFraction, err := Q(1).Add(monthlyFractionOfDataRepaired) + repairFactor := unitless.Value(1) + for i := 0; i < twelveMonths; i++ { + monthlyFraction, err := unitless.Value(1).Add(monthlyFractionOfDataRepaired) if err != nil { return nil, err } @@ -325,30 +305,30 @@ func (sv *Service) prepareCarbonFromWritesAndRepairsRow(timeStored *Val) (*Row, } func (sv *Service) prepareCarbonPerByteMetadataOverheadRow() (*Row, error) { - noCarbonPerByteMetadataOverhead := kg(0).Div(B(1).Mul(H(1))) + noCarbonPerByteMetadataOverhead := kilogramPerByteHour.Value(0) row := new(Row) row[hyperscaler] = noCarbonPerByteMetadataOverhead row[corporateDC] = noCarbonPerByteMetadataOverhead - storjGCPCarbon := kg(sv.config.StorjGCPCarbon) - storjCRDBCarbon := kg(sv.config.StorjCRDBCarbon) + storjGCPCarbon := kilogram.Value(sv.config.StorjGCPCarbon) + storjCRDBCarbon := kilogram.Value(sv.config.StorjCRDBCarbon) monthlyStorjGCPAndCRDBCarbon, err := storjGCPCarbon.Add(storjCRDBCarbon) if err != nil { return nil, err } - storjEdgeCarbon := kg(sv.config.StorjEdgeCarbon) + storjEdgeCarbon := kilogram.Value(sv.config.StorjEdgeCarbon) monthlyStorjGCPAndCRDBAndEdgeCarbon, err := monthlyStorjGCPAndCRDBCarbon.Add(storjEdgeCarbon) if err != nil { return nil, err } - storjAnnualCarbon := monthlyStorjGCPAndCRDBAndEdgeCarbon.Mul(Q(12)) - storjExpandedNetworkStorage := TB(sv.config.StorjExpandedNetworkStorage) - carbonOverheadPerByte := storjAnnualCarbon.Div(storjExpandedNetworkStorage.Mul(Y(1))) + storjAnnualCarbon := monthlyStorjGCPAndCRDBAndEdgeCarbon.Mul(unitless.Value(twelveMonths)) + storjExpandedNetworkStorage := byteUnit.Value(sv.config.StorjExpandedNetworkStorage / tbToBytesMultiplier) + carbonOverheadPerByte := storjAnnualCarbon.Div(storjExpandedNetworkStorage.Mul(yearsToHours(1))) for modality := storjStandard; modality < modalityCount; modality++ { row[modality] = carbonOverheadPerByte @@ -357,6 +337,10 @@ func (sv *Service) prepareCarbonPerByteMetadataOverheadRow() (*Row, error) { return row, nil } +func yearsToHours(v float64) Val { + return hour.Value(v * yearDays * dayHours) +} + func prepareEffectiveCarbonPerByteRow(carbonTotalPerByteRow, utilizationRow *Row) *Row { row := new(Row) for modality := 0; modality < modalityCount; modality++ { @@ -366,12 +350,12 @@ func prepareEffectiveCarbonPerByteRow(carbonTotalPerByteRow, utilizationRow *Row return row } -func prepareTotalCarbonRow(input *CalculationInput, effectiveCarbonPerByteRow, expansionFactorRow, regionCountRow *Row, timeStored *Val) *Row { - amountOfData := TB(input.AmountOfDataInTB) +func prepareTotalCarbonRow(input *CalculationInput, effectiveCarbonPerByteRow, expansionFactorRow, regionCountRow *Row, timeStored Val) *Row { + amountOfData := byteUnit.Value(input.AmountOfDataInTB / tbToBytesMultiplier) // We don't include timeStored amount value if data type is already TB-duration. if input.IsTBDuration { - timeStored = H(1) + timeStored = hour.Value(1) } row := new(Row) @@ -382,16 +366,16 @@ func prepareTotalCarbonRow(input *CalculationInput, effectiveCarbonPerByteRow, e return row } -func calculateStorjBlended(networkWeightingRow, totalCarbonRow *Row) (*Val, error) { +func calculateStorjBlended(networkWeightingRow, totalCarbonRow *Row) (Val, error) { storjReusedTotalCarbon := networkWeightingRow[storjReused].Mul(totalCarbonRow[storjReused]) storjNewAndReusedTotalCarbon, err := networkWeightingRow[storjNew].Mul(totalCarbonRow[storjNew]).Add(storjReusedTotalCarbon) if err != nil { - return nil, err + return Val{}, err } storjBlended, err := networkWeightingRow[storjStandard].Mul(totalCarbonRow[storjStandard]).Add(storjNewAndReusedTotalCarbon) if err != nil { - return nil, err + return Val{}, err } return storjBlended, nil