Skip to content

Commit

Permalink
Update CPU calculations and comments
Browse files Browse the repository at this point in the history
* Incorporate methodologies in comments
* Hot fix GridCO2e to be in grams not metric tonnes
  • Loading branch information
gabibeyer committed Feb 23, 2024
1 parent 9c4e347 commit 5a9c8e4
Show file tree
Hide file tree
Showing 2 changed files with 28 additions and 27 deletions.
50 changes: 24 additions & 26 deletions pkg/calculator/calculator.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,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 +54,41 @@ 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)
}
// 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
// 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 := cubicSplineInterpolation(p.wattage, p.metric.Usage())

// 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.
return usageCPUkw * vCPUHours * p.pue * p.gridCO2e, nil
}

// TODO add comment
// 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 {
// 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
}

// EmbodiedEmissions are the released emissions of production and destruction of the
Expand Down
5 changes: 4 additions & 1 deletion pkg/calculator/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,14 @@ func (ec *EmissionCalculator) Apply(event bus.Event) {
return
}

gridCO2e, ok := emFactors.Coefficient[eventInstance.Region()]
gridCO2eTons, ok := emFactors.Coefficient[eventInstance.Region()]
if !ok {
klog.Errorf("error region: %s does not exist in factors for %s", eventInstance.Region(), "gcp")
return
}
// TODO: hotfix until updated in emissions data
// convert gridCO2e from metric tonnes to grams
gridCO2e := gridCO2eTons * (1000 * 1000)

params := parameters{
gridCO2e: gridCO2e,
Expand Down

0 comments on commit 5a9c8e4

Please sign in to comment.