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

Make carbonifer available as Go library #41

Merged
merged 5 commits into from
Mar 3, 2023
Merged
Show file tree
Hide file tree
Changes from 4 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
9 changes: 1 addition & 8 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,7 @@ func init() {

// initConfig reads in config file and ENV variables if set.
func initConfig() {
utils.InitConfig(cfgFile)

// Set working directory
currentDir, err := os.Getwd()
if err != nil {
log.Fatal(err)
}
viper.SetDefault("workdir", currentDir)
utils.InitWithConfig(cfgFile)

// Set log level from command flags
info, _ := RootCmd.Flags().GetBool("info")
Expand Down
3 changes: 2 additions & 1 deletion internal/testutils/testutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ func init() {
if err != nil {
panic(err)
}
utils.InitConfig(path.Join(RootDir, "test/config/default_conf.yaml"))
configFile := path.Join(RootDir, "test/config/default_conf.yaml")
utils.InitWithConfig(configFile)

// Set fake GCP auth
os.Setenv("GOOGLE_OAUTH_ACCESS_TOKEN", "foo")
Expand Down
26 changes: 24 additions & 2 deletions internal/utils/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"os"
"path"
"path/filepath"
"runtime"
"sort"

"github.com/carboniferio/carbonifer/internal/estimate/estimation"
Expand All @@ -17,8 +18,14 @@ import (
"gopkg.in/yaml.v3"
)

func InitConfig(configFilePath string) {
initViper(configFilePath)
func InitWithDefaultConfig() {
initViper("")
initLogger()
checkDataConfig()
}

func InitWithConfig(customConfigFilePath string) {
initViper(customConfigFilePath)
initLogger()
checkDataConfig()
}
Expand All @@ -42,8 +49,15 @@ func loadViperDefaults() {
log.Debug(settings)
}

func BasePath() string {
_, b, _, _ := runtime.Caller(0)
d := filepath.Dir(b)
return filepath.Join(d, "../..")
}

func initViper(configFilePath string) {
loadViperDefaults()

if configFilePath != "" {
// Use config file from the flag.
viper.SetConfigFile(configFilePath)
Expand All @@ -70,6 +84,14 @@ func initViper(configFilePath string) {
}
}

// Set absolute data directory
dataPath := viper.GetString("data.path")
if dataPath != "" && !filepath.IsAbs(dataPath) {
basedir := BasePath()
dataPath = filepath.Join(basedir, dataPath)
}
viper.Set("data.path", dataPath)

if viper.ConfigFileUsed() != "" {
log.Infof("Using config file: %v", viper.ConfigFileUsed())
}
Expand Down
68 changes: 68 additions & 0 deletions pkg/estimate/estimate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package estimate

import (
"github.com/carboniferio/carbonifer/internal/estimate"
internalResources "github.com/carboniferio/carbonifer/internal/resources"
"github.com/carboniferio/carbonifer/internal/utils"
"github.com/carboniferio/carbonifer/pkg/providers"
"github.com/carboniferio/carbonifer/pkg/resources"
"github.com/shopspring/decimal"
)

type EstimationReport struct {
Resource resources.GenericResource
Power decimal.Decimal `json:"PowerPerInstance"`
CarbonEmissions decimal.Decimal `json:"CarbonEmissionsPerInstance"`
AverageCPUUsage decimal.Decimal
Count decimal.Decimal
}

func GetEstimation(resource resources.GenericResource) (EstimationReport, error) {
estimation, err := estimate.EstimateResource(toInternalComputeResource(resource))
if err != nil {
return EstimationReport{}, err
}
// Exponent is enforced to avoid equality issues when comparing reports.
// Indeed, if we don't truncate the values, we might have value with various exponent,
// which will make the equality check fail during test.
// TODO: Find a better way to handle this
return EstimationReport{
Resource: resource,
Power: estimation.Power.Truncate(10),
CarbonEmissions: estimation.CarbonEmissions.Truncate(10),
AverageCPUUsage: estimation.AverageCPUUsage.Truncate(10),
Count: estimation.Count.Truncate(10),
}, nil
}

func GetEstimationFromInstanceType(instanceType string, zone string, provider providers.Provider) (EstimationReport, error) {
resource, err := resources.GetResource(instanceType, zone, provider)
if err != nil {
return EstimationReport{}, err
}
estimation, err := GetEstimation(resource)
if err != nil {
return EstimationReport{}, err
}
return estimation, nil
}

func toInternalComputeResource(resource resources.GenericResource) internalResources.ComputeResource {
// TODO: Support multiple CPU types
// Check this PR for more info: https://github.com/carboniferio/carbonifer/pull/41
return internalResources.ComputeResource{
Identification: resource.GetIdentification(),
Specs: &internalResources.ComputeResourceSpecs{
GpuTypes: resource.GPUTypes,
HddStorage: resource.Storage.HddStorage,
SsdStorage: resource.Storage.SsdStorage,
MemoryMb: resource.MemoryMb,
VCPUs: resource.VCPUs,
ReplicationFactor: resource.ReplicationFactor,
},
}
}

func init() {
utils.InitWithDefaultConfig()
}
76 changes: 76 additions & 0 deletions pkg/estimate/estimate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package estimate

import (
"github.com/carboniferio/carbonifer/pkg/providers"
"github.com/carboniferio/carbonifer/pkg/resources"
"github.com/shopspring/decimal"
"reflect"
"testing"
)

func TestGetEstimation(t *testing.T) {
type args struct {
resource resources.GenericResource
}
tests := []struct {
name string
args args
want EstimationReport
wantErr bool
}{
{
name: "e2-standard-2",
args: args{
resource: resources.GenericResource{
Name: "e2-standard-2",
Region: "europe-west4",
Provider: providers.GCP,
CPUTypes: []string{
"Skylake",
"Broadwell",
"Haswell",
"AMD EPYC Rome",
"AMD EPYC Milan",
},
VCPUs: 2,
MemoryMb: 8192,
Storage: resources.Storage{},
ReplicationFactor: 0,
},
},
want: EstimationReport{
Resource: resources.GenericResource{
Name: "e2-standard-2",
Region: "europe-west4",
Provider: providers.GCP,
CPUTypes: []string{
"Skylake",
"Broadwell",
"Haswell",
"AMD EPYC Rome",
"AMD EPYC Milan",
},
MemoryMb: 8192,
VCPUs: 2,
},
Power: decimal.NewFromFloatWithExponent(8.9166, -10), // Refer to estimate.go for other indications
CarbonEmissions: decimal.NewFromFloatWithExponent(2.5233978, -10),
AverageCPUUsage: decimal.NewFromFloat(0.5),
Count: decimal.NewFromInt(1),
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := GetEstimation(tt.args.resource)
if (err != nil) != tt.wantErr {
t.Errorf("GetEstimation() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("GetEstimation() got = %v, want %v", got, tt.want)
}
})
}
}
22 changes: 22 additions & 0 deletions pkg/providers/provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package providers

type Provider int

const (
AWS Provider = iota
AZURE
GCP
)

func (p Provider) String() string {
switch p {
case AWS:
return "AWS"
case AZURE:
return "Azure"
case GCP:
return "GCP"
default:
return "unknown"
}
}
81 changes: 81 additions & 0 deletions pkg/resources/ressource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package resources

import (
"fmt"

internalProvider "github.com/carboniferio/carbonifer/internal/providers"
"github.com/carboniferio/carbonifer/internal/providers/gcp"
"github.com/carboniferio/carbonifer/internal/resources"
"github.com/carboniferio/carbonifer/internal/utils"
"github.com/carboniferio/carbonifer/pkg/providers"
"github.com/shopspring/decimal"
)

type GenericResource struct {
Name string
Region string
Provider providers.Provider
GPUTypes []string
CPUTypes []string
VCPUs int32
MemoryMb int32
Storage Storage
ReplicationFactor int32
}

// IsSupported returns true if the resource is supported by carbonifer. At the moment, only GCP is supported.
func (g GenericResource) IsSupported() bool {
// Use a switch to make it easier to add new providers
switch g.Provider {
case providers.GCP:
return true
default:
return false
}
}

func (g GenericResource) GetIdentification() *resources.ResourceIdentification {
return &resources.ResourceIdentification{
Name: g.Name,
ResourceType: "compute",
Provider: internalProvider.Provider(g.Provider),
Region: g.Region,
Count: 1,
}
}

func (g GenericResource) GetAddress() string {
return fmt.Sprintf("%v.%v", g.GetIdentification().ResourceType, g.GetIdentification().Name)
}

type Storage struct {
HddStorage decimal.Decimal
SsdStorage decimal.Decimal
}

func GetResource(instanceType string, zone string, provider providers.Provider) (GenericResource, error) {
switch provider {
case providers.GCP:
return fromGCPMachineTypeToResource(zone, gcp.GetGCPMachineType(instanceType, zone)), nil
default:
return GenericResource{}, fmt.Errorf("provider %s not supported", provider.String())
}
}

func fromGCPMachineTypeToResource(region string, machineType gcp.MachineType) GenericResource {
return GenericResource{
Name: machineType.Name,
Region: region,
Provider: providers.GCP,
GPUTypes: machineType.GPUTypes,
MemoryMb: machineType.MemoryMb,
CPUTypes: machineType.CpuTypes,
VCPUs: machineType.Vcpus,
Storage: Storage{},
ReplicationFactor: 0,
}
}

func init() {
utils.InitWithDefaultConfig()
}
Loading