| // Copyright The Prometheus Authors |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| //go:build linux |
| |
| package sysfs |
| |
| import ( |
| "fmt" |
| "os" |
| "path/filepath" |
| "strconv" |
| "strings" |
| |
| "github.com/prometheus/procfs/internal/parsers" |
| ) |
| |
| // Mdraid holds info parsed from relevant files in the /sys/block/md*/md directory. |
| type Mdraid struct { |
| Device string // Kernel device name of array. |
| Level string // mdraid level. Empty if level file is unreadable. |
| ArrayState string // State of the array. |
| MetadataVersion string // mdraid metadata version. |
| Disks *uint64 // Number of devices in a fully functional array. Nil when unavailable (e.g. container/imsm/ddf, empty level, or empty raid_disks). |
| Components []MdraidComponent // mdraid component devices. |
| UUID string // UUID of the array. |
| |
| // The following item is only valid for raid0, 4, 5, 6 and 10. |
| ChunkSize uint64 // Chunk size |
| |
| // The following items are only valid for raid1, 4, 5, 6 and 10. |
| DegradedDisks uint64 // Number of degraded disks in the array. |
| SyncAction string // Current sync action. |
| SyncCompleted float64 // Fraction (0-1) representing the completion status of current sync operation. |
| } |
| |
| type MdraidComponent struct { |
| Device string // Kernel device name. |
| State string // Current state of device. |
| } |
| |
| // Mdraids gathers information and statistics about mdraid devices present. Based on upstream |
| // kernel documentation https://docs.kernel.org/admin-guide/md.html. |
| func (fs FS) Mdraids() ([]Mdraid, error) { |
| matches, err := filepath.Glob(fs.sys.Path("block/md*/md")) |
| if err != nil { |
| return nil, err |
| } |
| |
| mdraids := make([]Mdraid, 0) |
| |
| for _, m := range matches { |
| md := Mdraid{Device: filepath.Base(filepath.Dir(m))} |
| path := fs.sys.Path("block", md.Device, "md") |
| |
| if val, err := parsers.SysReadFile(filepath.Join(path, "level")); err == nil { |
| md.Level = val |
| } else { |
| return mdraids, err |
| } |
| |
| // Array state can be one of: clear, inactive, readonly, read-auto, clean, active, |
| // write-pending, active-idle. |
| if val, err := parsers.SysReadFile(filepath.Join(path, "array_state")); err == nil { |
| md.ArrayState = val |
| } else { |
| return mdraids, err |
| } |
| |
| if val, err := parsers.SysReadFile(filepath.Join(path, "metadata_version")); err == nil { |
| md.MetadataVersion = val |
| } else { |
| return mdraids, err |
| } |
| |
| // Parse raid_disks: |
| // - Container types (container/imsm/ddf) have no physical disks assigned. |
| // - Empty level file means we can't determine the array type, skip parsing. |
| // - During reshape/grow, raid_disks may contain "current (previous)" (e.g. "11 (10)"). |
| // - Normal case is a plain integer. |
| switch md.Level { |
| case "container", "imsm", "ddf": |
| // No disks for container-type arrays, Disks remains nil. |
| case "": |
| // Empty level file, cannot determine disk count, Disks remains nil. |
| default: |
| data, err := os.ReadFile(filepath.Join(path, "raid_disks")) |
| if err != nil { |
| return mdraids, fmt.Errorf("reading raid_disks for %s: %w", md.Device, err) |
| } |
| content := strings.TrimSpace(string(data)) |
| if content == "" { |
| // Empty file, Disks remains nil. |
| break |
| } |
| |
| // Try normal integer parse first. |
| if val, parseErr := strconv.ParseUint(content, 10, 64); parseErr == nil { |
| md.Disks = &val |
| } else if idx := strings.Index(content, " "); idx > 0 { |
| // Reshape format: "current (previous)", extract the first number. |
| if val, parseErr := strconv.ParseUint(content[:idx], 10, 64); parseErr == nil { |
| md.Disks = &val |
| } |
| // If parsing fails, Disks remains nil (reshape in progress, count may be inaccurate). |
| } |
| // If neither format matches, Disks remains nil. |
| } |
| |
| if val, err := parsers.SysReadFile(filepath.Join(path, "uuid")); err == nil { |
| md.UUID = val |
| } else { |
| return mdraids, err |
| } |
| |
| if devs, err := filepath.Glob(filepath.Join(path, "dev-*")); err == nil { |
| for _, dev := range devs { |
| comp := MdraidComponent{Device: strings.TrimPrefix(filepath.Base(dev), "dev-")} |
| |
| // Component state can be a comma-separated list of: faulty, in_sync, writemostly, |
| // blocked, spare, write_error, want_replacement, replacement. |
| if val, err := parsers.SysReadFile(filepath.Join(dev, "state")); err == nil { |
| comp.State = val |
| } else { |
| return mdraids, err |
| } |
| |
| md.Components = append(md.Components, comp) |
| } |
| } else { |
| return mdraids, err |
| } |
| |
| switch md.Level { |
| case "raid0", "raid4", "raid5", "raid6", "raid10": |
| if val, err := parsers.ReadUintFromFile(filepath.Join(path, "chunk_size")); err == nil { |
| md.ChunkSize = val |
| } else { |
| return mdraids, err |
| } |
| } |
| |
| switch md.Level { |
| case "raid1", "raid4", "raid5", "raid6", "raid10": |
| if val, err := parsers.ReadUintFromFile(filepath.Join(path, "degraded")); err == nil { |
| md.DegradedDisks = val |
| } else { |
| return mdraids, err |
| } |
| |
| // Array sync action can be one of: resync, recover, idle, check, repair. |
| if val, err := parsers.SysReadFile(filepath.Join(path, "sync_action")); err == nil { |
| md.SyncAction = val |
| } else { |
| return mdraids, err |
| } |
| |
| if val, err := parsers.SysReadFile(filepath.Join(path, "sync_completed")); err == nil { |
| if val != "none" { |
| var a, b uint64 |
| |
| // File contains two values representing the fraction of number of completed |
| // sectors divided by number of total sectors to process. |
| if _, err := fmt.Sscanf(val, "%d / %d", &a, &b); err == nil { |
| md.SyncCompleted = float64(a) / float64(b) |
| } else { |
| return mdraids, err |
| } |
| } |
| } else { |
| return mdraids, err |
| } |
| } |
| |
| mdraids = append(mdraids, md) |
| } |
| |
| return mdraids, nil |
| } |