open-nomad/tools/ec2info/aws.go
Seth Hoenig e693d15a5b env_aws: get ec2 cpu perf data from AWS API
Previously, Nomad was using a hand-made lookup table for looking
up EC2 CPU performance characteristics (core count + speed = ticks).

This data was incomplete and incorrect depending on region. The AWS
API has the correct data but requires API keys to use (i.e. should not
be queried directly from Nomad).

This change introduces a lookup table generated by a small command line
tool in Nomad's tools module which uses the Amazon AWS API.

Running the tool requires AWS_* environment variables set.
  $ # in nomad/tools/cpuinfo
  $ go run .

Going forward, Nomad can incorporate regeneration of the lookup table
somewhere in the CI pipeline so that we remain up-to-date on the latest
offerings from EC2.

Fixes #7830
2020-10-08 12:01:09 -05:00

125 lines
2.9 KiB
Go

package main
import (
"fmt"
"log"
"sort"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ec2"
)
func clientForRegion(region string) (*ec2.EC2, error) {
sess, err := session.NewSession(&aws.Config{
Region: &region,
})
if err != nil {
return nil, err
}
return ec2.New(sess), nil
}
func getRegions(client *ec2.EC2) ([]*ec2.Region, error) {
all := false // beyond account access
regions, err := client.DescribeRegions(&ec2.DescribeRegionsInput{
AllRegions: &all,
})
if err != nil {
return nil, err
}
return regions.Regions, nil
}
type specs struct {
Cores int
Speed float64
}
func (s specs) String() string {
return fmt.Sprintf("(%d %.2f)", s.Cores, s.Speed)
}
func getData(regions []*ec2.Region, verbose bool) (map[string]map[string]specs, error) {
data := make(map[string]map[string]specs)
for _, region := range regions {
rData, rProblems, err := getDataForRegion(*region.RegionName)
if err != nil {
return nil, err
}
data[*region.RegionName] = rData
if verbose {
log.Println("region", *region.RegionName, "got data for", len(rData), "instance types", len(rProblems), "incomplete")
instanceProblems(rProblems)
}
}
return data, nil
}
func instanceProblems(problems map[string]string) {
types := make([]string, 0, len(problems))
for k := range problems {
types = append(types, k)
}
sort.Strings(types)
for _, iType := range types {
log.Println(" ->", iType, problems[iType])
}
}
func getDataForRegion(region string) (map[string]specs, map[string]string, error) {
client, err := clientForRegion(region)
if err != nil {
return nil, nil, err
}
data := make(map[string]specs)
problems := make(map[string]string)
regionInfoPage(client, true, region, nil, data, problems)
return data, problems, nil
}
func regionInfoPage(client *ec2.EC2, first bool, region string, token *string, data map[string]specs, problems map[string]string) {
if first || token != nil {
output, err := client.DescribeInstanceTypes(&ec2.DescribeInstanceTypesInput{
NextToken: token,
})
if err != nil {
log.Fatal(err)
}
// recursively accumulate each page of data
regionInfoAccumulate(output, data, problems)
regionInfoPage(client, false, region, output.NextToken, data, problems)
}
}
func regionInfoAccumulate(output *ec2.DescribeInstanceTypesOutput, data map[string]specs, problems map[string]string) {
for _, iType := range output.InstanceTypes {
switch {
case iType.ProcessorInfo == nil:
fallthrough
case iType.ProcessorInfo.SustainedClockSpeedInGhz == nil:
problems[*iType.InstanceType] = "missing clock Speed"
continue
case iType.VCpuInfo == nil:
fallthrough
case iType.VCpuInfo.DefaultVCpus == nil:
problems[*iType.InstanceType] = "missing virtual cpu Cores"
continue
default:
data[*iType.InstanceType] = specs{
Speed: *iType.ProcessorInfo.SustainedClockSpeedInGhz,
Cores: int(*iType.VCpuInfo.DefaultVCpus),
}
continue
}
}
}