Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
61 changes: 61 additions & 0 deletions internal/monitoring/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package monitoring

import (
"bufio"
"fmt"
"os"
"os/exec"
"regexp"
"strings"
)

Expand Down Expand Up @@ -109,6 +111,65 @@ func getDiskDevice(diskDevice string) string {
return diskDevice
}

// getVolumeID extracts the EBS volume ID from a disk device name
// It reads the MODEL and SERIAL from /sys/block/${BASE}/device/ and converts
// the serial to the volume ID format (vol-...)
func getVolumeID(diskDevice string) (string, error) {
// Extract base device name (remove partition suffix like p1, p2, etc.)
base := diskDevice
// Remove partition suffix pattern: p1, p2, etc. (e.g., nvme0n1p1 -> nvme0n1)
re := regexp.MustCompile(`p\d+$`)
base = re.ReplaceAllString(base, "")

// Read SERIAL first - this is the definitive way to identify EBS volumes
serialPath := fmt.Sprintf("/sys/block/%s/device/serial", base)
serialBytes, err := os.ReadFile(serialPath)
if err != nil {
return "", fmt.Errorf("failed to read serial: %w", err)
}
serial := strings.TrimSpace(string(serialBytes))
// Remove all whitespace
serial = strings.ReplaceAll(serial, " ", "")
serial = strings.ReplaceAll(serial, "\t", "")
serial = strings.ReplaceAll(serial, "\n", "")

// Convert SERIAL to VOLUME_ID format
// Pattern: vol00bb... -> vol-00bb...
volPattern := regexp.MustCompile(`^vol[0-9a-f]+$`)
volWithDashPattern := regexp.MustCompile(`^vol-[0-9a-f]+$`)

var volumeID string
if volPattern.MatchString(serial) {
// vol00bb... -> vol-00bb...
volumeID = "vol-" + serial[3:]
} else if volWithDashPattern.MatchString(serial) {
// Already in correct format
volumeID = serial
} else {
// Serial doesn't match volume ID pattern - check model for better error message
modelPath := fmt.Sprintf("/sys/block/%s/device/model", base)
modelBytes, err := os.ReadFile(modelPath)
if err == nil {
model := strings.TrimSpace(string(modelBytes))
return "", fmt.Errorf("%s does not look like an EBS NVMe device (model=%s, serial=%s)", base, model, serial)
}
return "", fmt.Errorf("unexpected NVMe serial format: %s", serial)
}

// Optionally validate MODEL (but don't fail if serial already indicates it's a volume)
modelPath := fmt.Sprintf("/sys/block/%s/device/model", base)
modelBytes, err := os.ReadFile(modelPath)
if err == nil {
model := strings.TrimSpace(string(modelBytes))
if !strings.Contains(strings.ToLower(model), "amazon elastic block store") {
// Model doesn't match, but serial does - this might be instance storage with volume-like serial
// We'll still return the volume ID since the serial pattern matched
}
}

return volumeID, nil
}

// calculateMin returns the minimum value in a slice
func calculateMin(data []float64) float64 {
if len(data) == 0 {
Expand Down
17 changes: 17 additions & 0 deletions internal/monitoring/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,11 +178,24 @@ func GenerateMetricsSummary(action *githubactions.Action, metrics []string, form
networkInterface = getNetworkInterface(networkInterface)
diskDevice = getDiskDevice(diskDevice)

// Get volume ID from disk device
volumeID := ""
if diskDevice != "" {
var err error
volumeID, err = getVolumeID(diskDevice)
if err != nil {
action.Warningf("Failed to get volume ID from disk device %s: %v", diskDevice, err)
}
}

action.Infof("## CloudWatch Metrics Summary\n")
action.Infof("Enabled metrics: %s", strings.Join(metrics, ", "))
action.Infof("Namespace: %s", NAMESPACE)
action.Infof("Network interface: %s", networkInterface)
action.Infof("Disk device: %s", diskDevice)
if volumeID != "" {
action.Infof("Volume ID: %s", volumeID)
}
action.Infof("")
showLinks(action, metrics)

Expand Down Expand Up @@ -225,6 +238,10 @@ func GenerateMetricsSummary(action *githubactions.Action, metrics []string, form
Name: aws.String("path"),
Value: aws.String("/"),
})
dimensions = append(dimensions, types.Dimension{
Name: aws.String("volume_id"),
Value: aws.String(volumeID),
})
}
if metricType == "io" {
dimensions = append(dimensions, types.Dimension{
Expand Down