From 5a9c8e46d0d6a074e6e52ad34a9c8e7cd1961c5a Mon Sep 17 00:00:00 2001 From: Gabi Beyer Date: Fri, 23 Feb 2024 16:28:57 +0100 Subject: [PATCH] Update CPU calculations and comments * Incorporate methodologies in comments * Hot fix GridCO2e to be in grams not metric tonnes --- pkg/calculator/calculator.go | 50 +++++++++++++++++------------------- pkg/calculator/handler.go | 5 +++- 2 files changed, 28 insertions(+), 27 deletions(-) diff --git a/pkg/calculator/calculator.go b/pkg/calculator/calculator.go index f889f35..f339117 100644 --- a/pkg/calculator/calculator.go +++ b/pkg/calculator/calculator.go @@ -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 @@ -56,33 +54,31 @@ 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)) @@ -90,7 +86,9 @@ func cubicSplineInterpolation(wattage []data.Wattage, value float64) float64 { } 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 diff --git a/pkg/calculator/handler.go b/pkg/calculator/handler.go index ff502f0..9ab6af1 100644 --- a/pkg/calculator/handler.go +++ b/pkg/calculator/handler.go @@ -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,