Skip to content

Commit

Permalink
Merge pull request #509 from dswarbrick/mdraid-sysfs
Browse files Browse the repository at this point in the history
Implement mdraid sysfs parsing
  • Loading branch information
SuperQ committed May 5, 2023
2 parents 5fce84b + a038d1f commit 663692c
Show file tree
Hide file tree
Showing 4 changed files with 798 additions and 7 deletions.
14 changes: 7 additions & 7 deletions blockdevice/stats_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func TestBlockDevice(t *testing.T) {
if err != nil {
t.Fatal(err)
}
expectedNumOfDevices := 2
expectedNumOfDevices := 8
if len(devices) != expectedNumOfDevices {
t.Fatalf(failMsgFormat, "Incorrect number of devices", expectedNumOfDevices, len(devices))
}
Expand All @@ -93,18 +93,18 @@ func TestBlockDevice(t *testing.T) {
if device0stats.WeightedIOTicks != 6088971 {
t.Errorf(failMsgFormat, "Incorrect time in queue", 6088971, device0stats.WeightedIOTicks)
}
device1stats, count, err := blockdevice.SysBlockDeviceStat(devices[1])
device7stats, count, err := blockdevice.SysBlockDeviceStat(devices[7])
if count != 15 {
t.Errorf(failMsgFormat, "Incorrect number of stats read", 15, count)
}
if err != nil {
t.Fatal(err)
}
if device1stats.WriteSectors != 286915323 {
t.Errorf(failMsgFormat, "Incorrect write merges", 286915323, device1stats.WriteSectors)
if device7stats.WriteSectors != 286915323 {
t.Errorf(failMsgFormat, "Incorrect write merges", 286915323, device7stats.WriteSectors)
}
if device1stats.DiscardTicks != 12 {
t.Errorf(failMsgFormat, "Incorrect discard ticks", 12, device1stats.DiscardTicks)
if device7stats.DiscardTicks != 12 {
t.Errorf(failMsgFormat, "Incorrect discard ticks", 12, device7stats.DiscardTicks)
}
blockQueueStatExpected := BlockQueueStats{
AddRandom: 1,
Expand Down Expand Up @@ -145,7 +145,7 @@ func TestBlockDevice(t *testing.T) {
WriteZeroesMaxBytes: 0,
}

blockQueueStat, err := blockdevice.SysBlockDeviceQueueStats(devices[1])
blockQueueStat, err := blockdevice.SysBlockDeviceQueueStats(devices[7])
if err != nil {
t.Fatal(err)
}
Expand Down
160 changes: 160 additions & 0 deletions sysfs/mdraid.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
// Copyright 2023 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
// +build linux

package sysfs

import (
"fmt"
"path/filepath"
"strings"

"github.com/prometheus/procfs/internal/util"
)

// 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.
ArrayState string // State of the array.
MetadataVersion string // mdraid metadata version.
Disks uint64 // Number of devices in a fully functional array.
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 := util.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 := util.SysReadFile(filepath.Join(path, "array_state")); err == nil {
md.ArrayState = val
} else {
return mdraids, err
}

if val, err := util.SysReadFile(filepath.Join(path, "metadata_version")); err == nil {
md.MetadataVersion = val
} else {
return mdraids, err
}

if val, err := util.ReadUintFromFile(filepath.Join(path, "raid_disks")); err == nil {
md.Disks = val
} else {
return mdraids, err
}

if val, err := util.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 := util.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 := util.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 := util.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 := util.SysReadFile(filepath.Join(path, "sync_action")); err == nil {
md.SyncAction = val
} else {
return mdraids, err
}

if val, err := util.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
}
134 changes: 134 additions & 0 deletions sysfs/mdraid_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// Copyright 2023 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
// +build linux

package sysfs

import (
"testing"

"github.com/google/go-cmp/cmp"
)

func TestMdraidStats(t *testing.T) {
fs, err := NewFS(sysTestFixtures)
if err != nil {
t.Fatal(err)
}

got, err := fs.Mdraids()
if err != nil {
t.Fatal(err)
}

want := []Mdraid{
{
Device: "md0",
Level: "raid0",
ArrayState: "clean",
MetadataVersion: "1.2",
Disks: 2,
Components: []MdraidComponent{
{Device: "sdg", State: "in_sync"},
{Device: "sdh", State: "in_sync"},
},
UUID: "155f29ff-1716-4107-b362-52307ef86cac",
ChunkSize: 524288,
},
{
Device: "md1",
Level: "raid1",
ArrayState: "clean",
MetadataVersion: "1.2",
Disks: 2,
Components: []MdraidComponent{
{Device: "sdi", State: "in_sync"},
{Device: "sdj", State: "in_sync"},
},
UUID: "0fbf5f2c-add2-43c2-bd78-a4be3ab709ef",
SyncAction: "idle",
},
{
Device: "md10",
Level: "raid10",
ArrayState: "clean",
MetadataVersion: "1.2",
Disks: 4,
Components: []MdraidComponent{
{Device: "sdu", State: "in_sync"},
{Device: "sdv", State: "in_sync"},
{Device: "sdw", State: "in_sync"},
{Device: "sdx", State: "in_sync"},
},
UUID: "0c15f7e7-b159-4b1f-a5cd-a79b5c04b6f5",
ChunkSize: 524288,
SyncAction: "idle",
},
{
Device: "md4",
Level: "raid4",
ArrayState: "clean",
MetadataVersion: "1.2",
Disks: 3,
Components: []MdraidComponent{
{Device: "sdk", State: "in_sync"},
{Device: "sdl", State: "in_sync"},
{Device: "sdm", State: "in_sync"},
},
UUID: "67f415d5-2c0c-4b69-8e0d-7e20ef553457",
ChunkSize: 524288,
SyncAction: "idle",
},
{
Device: "md5",
Level: "raid5",
ArrayState: "clean",
MetadataVersion: "1.2",
Disks: 3,
Components: []MdraidComponent{
{Device: "sdaa", State: "spare"},
{Device: "sdn", State: "in_sync"},
{Device: "sdo", State: "in_sync"},
{Device: "sdp", State: "faulty"},
},
UUID: "7615b98d-f2ba-4d99-bee8-6202d8e130b9",
ChunkSize: 524288,
DegradedDisks: 1,
SyncAction: "idle",
},
{
Device: "md6",
Level: "raid6",
ArrayState: "active",
MetadataVersion: "1.2",
Disks: 4,
Components: []MdraidComponent{
{Device: "sdq", State: "in_sync"},
{Device: "sdr", State: "in_sync"},
{Device: "sds", State: "in_sync"},
{Device: "sdt", State: "spare"},
},
UUID: "5f529b25-6efd-46e4-99a2-31f6f597be6b",
ChunkSize: 524288,
DegradedDisks: 1,
SyncAction: "recover",
SyncCompleted: 0.7500458659491194,
},
}

if diff := cmp.Diff(want, got); diff != "" {
t.Fatalf("unexpected Mdraid (-want +got):\n%s", diff)
}
}

0 comments on commit 663692c

Please sign in to comment.