Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Methodologies #45

Merged
merged 2 commits into from
Feb 27, 2024
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
2 changes: 1 addition & 1 deletion Dockerfile.dev
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM golang:1.21
FROM golang:1.22

WORKDIR /src

Expand Down
61 changes: 34 additions & 27 deletions pkg/calculator/calculator.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ import (
"github.com/cnkei/gospline"
v1 "github.com/re-cinq/cloud-carbon/pkg/types/v1"
data "github.com/re-cinq/emissions-data/pkg/types/v2"
"k8s.io/klog/v2"
)

type parameters struct {
gridCO2e float64
pue float64
wattage []data.Wattage
metric v1.Metric
metric *v1.Metric
vCPU float64
embodiedFactor float64
}
Expand All @@ -38,12 +39,10 @@ func operationalEmissions(interval time.Duration, p *parameters) (float64, error

// cpu calculates the CO2e operational emissions for the CPU utilization of
// a Cloud VM instance over an interval of time.
// More information of the calculation can be found in the docs.
//
// The initial calculation uses the wattage conversion factor based on the turbostat and
// turbostress to stress test the CPU on baremetal servers as inspired by Teads.
// If those datasets do not exist, we fall back to calculate based on min and max
// wattage from SPECpower data as inspired by CCF and Etsy.
// More information can be found in our docs/METHODOLOGIES.md
func cpu(interval time.Duration, p *parameters) (float64, error) {
vCPU := p.vCPU
// vCPU are virtual CPUs that are mapped to physical cores (a core is a physical
Expand All @@ -56,41 +55,49 @@ func cpu(interval time.Duration, p *parameters) (float64, error) {
vCPU = p.metric.UnitAmount()
}

// Calculate CPU energy consumption as a rate by the interval time for 1 hour.
// For example, if the machine has 4 vCPUs and the interval of time is
// 5 minutes: The hourly time is 5/60 (0.083333333) * 4 vCPU = 0.33333334
// vCPUHours represents the count of virtual CPUs within a specific time frame.
// To get vCPUHours, we first get the interval in hours and multiply that by the
// number of vCPUs.
// For example, if the machine has 4 vCPUs and an interval of time of 5 minutes
// The hourly time is 5/60 (0.083333333) * 4 vCPU = 0.33333334
vCPUHours := (interval.Minutes() / float64(60)) * vCPU

// if there pkgWatt dataset values, then use interpolation
// to calculate the wattage based on the utilization, otherwise, calculate
// based on SPECpower min and max data
watts := cubicSplineInterpolation(p.wattage, p.metric.Usage())
if len(p.wattage) == 0 || watts == 0 {
// Average Watts is the average energy consumption of the service. It is based on
// CPU utilization and Minimum and Maximum wattage of the server. If the machine
// architecture is unknown the Min and Max wattage is the average of all machines
// for that provider, and is supplied in the provider defaults. This is being
// handled in the types/factors package (the point of reading in coefficient data).
minWatts := p.wattage[0].Wattage
maxWatts := p.wattage[1].Wattage
watts = minWatts + p.metric.Usage()*(maxWatts-minWatts)
// usageCPUkw is the CPU energy consumption in kilowatts.
// If pkgWatt values exist from the dataset, then use cubic spline interpolation
// to calculate the wattage based on utilization.
usageCPUkw, err := cubicSplineInterpolation(p.wattage, p.metric.Usage())
if err != nil {
return 0, err
}
// Operational Emissions are calculated by multiplying the avgWatts, vCPUHours, PUE,
// and region grid CO2e. The PUE is collected from the providers. The CO2e grid data
// is the electrical grid emissions for the region at the specified time.
return watts * vCPUHours * p.pue * p.gridCO2e, nil

// Operational Emissions are calculated by multiplying the usageCPUkw, vCPUHours, PUE,
// and region gridCO2e. The PUE is collected from the providers. The CO2e grid data
// is the grid carbon intensity coefficient for the region at the specified time.
klog.Infof("CPU calculation: %+v, %+v, %+v, %+v\n", usageCPUkw, vCPUHours, p.pue, p.gridCO2e)
return usageCPUkw * vCPUHours * p.pue * p.gridCO2e, nil
}

// TODO add comment
func cubicSplineInterpolation(wattage []data.Wattage, value float64) float64 {
// cubicSplineInterpolation is a piecewise cubic polynomials that takes the
// four measured wattage data points at 0%, 10%, 50%, and 100% utilization
// and interpolates a value for the usage (%) value and returns the energy
// in kilowatts.
func cubicSplineInterpolation(wattage []data.Wattage, value float64) (float64, error) {
if len(wattage) == 0 {
return 0, errors.New("error: cannot calculate CPU energy, no wattage found")
}

// split the wattage slice into a slice of
// float percentages and a slice of wattages
var x, y = []float64{}, []float64{}
for _, w := range wattage {
x = append(x, float64(w.Percentage))
y = append(y, w.Wattage)
}

s := gospline.NewCubicSpline(x, y)
return s.At(value)
// s.At returns the cubic spline value in Wattage
// divide by 1000 to get kilowatts.
return s.At(value) / 1000, nil
}

// EmbodiedEmissions are the released emissions of production and destruction of the
Expand Down
Loading
Loading