diff --git a/README.md b/README.md index f6e48d8..c1db0f3 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,7 @@ It exposes the following metrics: |-|-| | `unused_disks_count` | How many unused disks are in this provider | | `unused_disks_size_gb` | Total size of unused disks in this provider in GB | +| `unused_disk_size_bytes` | Size of each disk in bytes | | `unused_disks_last_used_at` | Last timestamp (unix ms) when this disk was used. GCP only! | | `unused_provider_duration_ms` | How long in milliseconds took to fetch this provider information | | `unused_provider_info` | CSP information | diff --git a/aws/disk.go b/aws/disk.go index 8625fa7..0894596 100644 --- a/aws/disk.go +++ b/aws/disk.go @@ -7,6 +7,8 @@ import ( "github.com/grafana/unused" ) +const GiBbytes = 1073741824 + var _ unused.Disk = &Disk{} // Disk holds information about an AWS EC2 volume. @@ -43,9 +45,12 @@ func (d *Disk) CreatedAt() time.Time { return *d.Volume.CreateTime } // Meta returns the disk metadata. func (d *Disk) Meta() unused.Meta { return d.meta } -// SizeGB returns the size of this AWS EC2 volume in GB. +// SizeGB returns the size of this AWS EC2 volume in GiB. func (d *Disk) SizeGB() int { return int(*d.Volume.Size) } +// SizeBytes returns the size of this AWS EC2 volume in bytes. +func (d *Disk) SizeBytes() float64 { return float64(*d.Volume.Size) * GiBbytes } + // LastUsedAt returns a zero [time.Time] value, as AWS does not // provide this information. func (d *Disk) LastUsedAt() time.Time { return time.Time{} } diff --git a/azure/disk.go b/azure/disk.go index a781a95..d2b6cb0 100644 --- a/azure/disk.go +++ b/azure/disk.go @@ -29,6 +29,9 @@ func (d *Disk) Name() string { return *d.Disk.Name } // SizeGB returns the size of this Azure compute disk in GB. func (d *Disk) SizeGB() int { return int(*d.Disk.DiskSizeGB) } +// SizeBytes returns the size of this Azure compute disk in bytes. +func (d *Disk) SizeBytes() float64 { return float64(*d.Disk.DiskSizeBytes) } + // CreatedAt returns the time when this Azure compute disk was // created. func (d *Disk) CreatedAt() time.Time { return d.Disk.TimeCreated.ToTime() } diff --git a/cmd/unused-exporter/exporter.go b/cmd/unused-exporter/exporter.go index 26d06c7..96f2800 100644 --- a/cmd/unused-exporter/exporter.go +++ b/cmd/unused-exporter/exporter.go @@ -32,6 +32,7 @@ type exporter struct { info *prometheus.Desc count *prometheus.Desc + ds *prometheus.Desc size *prometheus.Desc dur *prometheus.Desc suc *prometheus.Desc @@ -63,6 +64,11 @@ func registerExporter(ctx context.Context, providers []unused.Provider, cfg conf "How many unused disks are in this provider", append(labels, "k8s_namespace"), nil), + ds: prometheus.NewDesc( + prometheus.BuildFQName(namespace, "disk", "size_bytes"), + "Disk size in bytes", + append(labels, []string{"disk", "k8s_namespace", "type", "region", "zone"}...), + nil), size: prometheus.NewDesc( prometheus.BuildFQName(namespace, "disks", "size_gb"), @@ -177,6 +183,7 @@ func (e *exporter) pollProvider(p unused.Provider) { } addMetric(&ms, p, e.dlu, lastUsedTS(d), d.ID(), m.CreatedForPV(), m.CreatedForPVC(), m.Zone()) + addMetric(&ms, p, e.ds, d.SizeBytes(), d.ID(), ns, string(d.DiskType()), getRegionFromZone(p, m.Zone()), m.Zone()) } addMetric(&ms, p, e.info, 1) @@ -277,3 +284,12 @@ func lastUsedTS(d unused.Disk) float64 { return float64(lastUsed.UnixMilli()) } + +func getRegionFromZone(p unused.Provider, z string) string { + if strings.ToLower(p.Name()) == "azure" { + return z + } + + // Drop the last character to get the region from the zone for GCP and AWS + return z[:len(z)-1] +} diff --git a/disk.go b/disk.go index c6ca1bf..0a72a04 100644 --- a/disk.go +++ b/disk.go @@ -15,9 +15,12 @@ type Disk interface { // Name returns the disk name. Name() string - // SizeGB returns the disk size in GB. + // SizeGB returns the disk size in GB (Azure/GCP) and GiB for AWS. SizeGB() int + // SizeBytes returns the disk size in bytes. + SizeBytes() float64 + // CreatedAt returns the time when the disk was created. CreatedAt() time.Time diff --git a/gcp/disk.go b/gcp/disk.go index 8cc184e..0ff909e 100644 --- a/gcp/disk.go +++ b/gcp/disk.go @@ -9,6 +9,8 @@ import ( compute "google.golang.org/api/compute/v1" ) +const GBbytes = 1_000_000_000 + // ensure we are properly defining the interface var _ unused.Disk = &Disk{} @@ -51,6 +53,9 @@ func (d *Disk) LastUsedAt() time.Time { // SizeGB returns the size of the GCP compute disk in GB. func (d *Disk) SizeGB() int { return int(d.Disk.SizeGb) } +// SizeBytes returns the size of the GCP compute disk in bytes. +func (d *Disk) SizeBytes() float64 { return float64(d.Disk.SizeGb) * GBbytes } + // DiskType Type returns the type of the GCP compute disk. func (d *Disk) DiskType() unused.DiskType { splitDiskType := strings.Split(d.Disk.Type, "/") diff --git a/unusedtest/disk.go b/unusedtest/disk.go index 3c698b4..abaffb8 100644 --- a/unusedtest/disk.go +++ b/unusedtest/disk.go @@ -8,6 +8,8 @@ import ( var _ unused.Disk = Disk{} +const GBbytes = 1_000_000_000 + // Disk implements [unused.Disk] for testing purposes. type Disk struct { id, name string @@ -30,4 +32,5 @@ func (d Disk) CreatedAt() time.Time { return d.createdAt } func (d Disk) Meta() unused.Meta { return d.meta } func (d Disk) LastUsedAt() time.Time { return d.createdAt.Add(1 * time.Minute) } func (d Disk) SizeGB() int { return d.size } +func (d Disk) SizeBytes() float64 { return float64(d.size) * GBbytes } func (d Disk) DiskType() unused.DiskType { return d.diskType }