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

[aws-ec2-ebs] fix calcurate procedure of Nitro instance #916

Merged
merged 1 commit into from Aug 3, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
108 changes: 103 additions & 5 deletions mackerel-plugin-aws-ec2-ebs/lib/aws-ec2-ebs.go
Expand Up @@ -3,6 +3,7 @@ package mpawsec2ebs
import (
"errors"
"flag"
"fmt"
"log"
"strings"
"time"
Expand Down Expand Up @@ -43,10 +44,17 @@ var io1Graphs = append([]string{
"ec2.ebs.consumed_ops.#",
}, baseGraphs...)

type additionalCloudWatchSetting struct {
MetricName string
Statistics string
CalcFunc func(float64, float64) float64
}

type cloudWatchSetting struct {
MetricName string
Statistics string
CalcFunc func(float64) float64
Additional *additionalCloudWatchSetting
}

func value(val float64) float64 {
Expand All @@ -61,6 +69,10 @@ func sec2msec(val float64) float64 {
return val * 1000
}

func valPerOps(val, ops float64) float64 {
return val / ops
}

// http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/monitoring-volume-status.html
var cloudwatchdefs = map[string](cloudWatchSetting){
"ec2.ebs.bandwidth.#.read": cloudWatchSetting{
Expand Down Expand Up @@ -117,6 +129,37 @@ var cloudwatchdefs = map[string](cloudWatchSetting){
},
}

var cloudwatchdefsNitro = map[string](cloudWatchSetting){
"ec2.ebs.size_per_op.#.read": cloudWatchSetting{
MetricName: "VolumeReadBytes", Statistics: "Sum",
Additional: &additionalCloudWatchSetting{
MetricName: "VolumeReadOps", Statistics: "Sum",
CalcFunc: valPerOps,
},
},
"ec2.ebs.size_per_op.#.write": cloudWatchSetting{
MetricName: "VolumeWriteBytes", Statistics: "Sum",
Additional: &additionalCloudWatchSetting{
MetricName: "VolumeWriteOps", Statistics: "Sum",
CalcFunc: valPerOps,
},
},
"ec2.ebs.latency.#.read": cloudWatchSetting{
MetricName: "VolumeTotalReadTime", Statistics: "Sum",
Additional: &additionalCloudWatchSetting{
MetricName: "VolumeReadOps", Statistics: "Sum",
CalcFunc: valPerOps,
},
},
"ec2.ebs.latency.#.write": cloudWatchSetting{
MetricName: "VolumeTotalWriteTime", Statistics: "Sum",
Additional: &additionalCloudWatchSetting{
MetricName: "VolumeWriteOps", Statistics: "Sum",
CalcFunc: valPerOps,
},
},
}

var graphdef = map[string]mp.Graphs{
"ec2.ebs.bandwidth.#": {
Label: "EBS Bandwidth",
Expand Down Expand Up @@ -202,6 +245,7 @@ type EBSPlugin struct {
EC2 *ec2.EC2
CloudWatch *cloudwatch.CloudWatch
Volumes []*ec2.Volume
Hypervisor string
}

func (p *EBSPlugin) prepare() error {
Expand All @@ -210,6 +254,36 @@ func (p *EBSPlugin) prepare() error {
}

p.EC2 = ec2.New(session.New(&aws.Config{Credentials: p.Credentials, Region: &p.Region}))

var instanceType string
instance, err := p.EC2.DescribeInstances(&ec2.DescribeInstancesInput{
InstanceIds: []*string{&p.InstanceID},
})
if err != nil {
return err
}
if instance.NextToken != nil {
return errors.New("DescribeInstances response has NextToken")
}
for i := range instance.Reservations {
for j := range instance.Reservations[i].Instances {
instanceType = *instance.Reservations[i].Instances[j].InstanceType
}
}

instanceDetail, err := p.EC2.DescribeInstanceTypes(&ec2.DescribeInstanceTypesInput{
InstanceTypes: []*string{&instanceType},
})
if err != nil {
return err
}
if instanceDetail.NextToken != nil {
return errors.New("DescribeInstanceTypes response has NextToken")
}
for i := range instanceDetail.InstanceTypes {
p.Hypervisor = *instanceDetail.InstanceTypes[i].Hypervisor
}

resp, err := p.EC2.DescribeVolumes(&ec2.DescribeVolumesInput{
Filters: []*ec2.Filter{
{
Expand Down Expand Up @@ -288,9 +362,34 @@ func (p EBSPlugin) getLastPoint(vol *ec2.Volume, metricName string, statType str
return latestVal, nil
}

func (p EBSPlugin) fetch(volume *ec2.Volume, setting cloudWatchSetting) (float64, error) {
val, err := p.getLastPoint(volume, setting.MetricName, setting.Statistics)
if err != nil {
return 0, fmt.Errorf("%s %w : %s", *volume.VolumeId, err, setting.MetricName)
}

if setting.Additional == nil {
return setting.CalcFunc(val), nil
}

val2, err := p.getLastPoint(volume, setting.Additional.MetricName, setting.Additional.Statistics)
if err != nil {
return 0, fmt.Errorf("%s %w : %s", *volume.VolumeId, err, setting.Additional.MetricName)
}
return setting.Additional.CalcFunc(val, val2), nil
}

// FetchMetrics fetch the metrics
func (p EBSPlugin) FetchMetrics() (map[string]interface{}, error) {
stat := make(map[string]interface{})

// Override when Nitro instance.
if p.Hypervisor == "nitro" {
for i := range cloudwatchdefsNitro {
cloudwatchdefs[i] = cloudwatchdefsNitro[i]
}
}

p.CloudWatch = cloudwatch.New(session.New(&aws.Config{Credentials: p.Credentials, Region: &p.Region}))
for _, vol := range p.Volumes {
volumeID := normalizeVolumeID(*vol.VolumeId)
Expand All @@ -304,16 +403,15 @@ func (p EBSPlugin) FetchMetrics() (map[string]interface{}, error) {
for _, metric := range graphdef[graphName].Metrics {
metricKey := graphName + "." + metric.Name
cloudwatchdef := cloudwatchdefs[metricKey]
val, err := p.getLastPoint(vol, cloudwatchdef.MetricName, cloudwatchdef.Statistics)
val, err := p.fetch(vol, cloudwatchdef)
if err != nil {
retErr := errors.New(volumeID + " " + err.Error() + ":" + cloudwatchdef.MetricName)
if err == errNoDataPoint {
if errors.Is(err, errNoDataPoint) {
// nop
} else {
return nil, retErr
return nil, err
}
} else {
stat[strings.Replace(metricKey, "#", volumeID, -1)] = cloudwatchdef.CalcFunc(val)
stat[strings.Replace(metricKey, "#", volumeID, -1)] = val
}
}
}
Expand Down