Skip to content

Commit 757109e

Browse files
authored
Add support for several /sys/class/sas_* classes (#453)
* Initial addition of several sysfs/class_sas* files, plus fixtures. These parse several SAS classes from /sys/class/ - sas_host - sas_device - sas_end_device - sas_expander - sas_port - sas_phy The included fixtures include examples of all of these. Signed-off-by: Scott Laird <[email protected]>
1 parent c8aa9d7 commit 757109e

File tree

9 files changed

+7304
-0
lines changed

9 files changed

+7304
-0
lines changed

sysfs/class_sas_device.go

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
// Copyright 2022 The Prometheus Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
//go:build linux
15+
// +build linux
16+
17+
package sysfs
18+
19+
import (
20+
"io/ioutil"
21+
"os"
22+
"path/filepath"
23+
"regexp"
24+
25+
"github.com/prometheus/procfs/internal/util"
26+
)
27+
28+
const (
29+
sasDeviceClassPath = "class/sas_device"
30+
sasEndDeviceClassPath = "class/sas_end_device"
31+
sasExpanderClassPath = "class/sas_expander"
32+
)
33+
34+
type SASDevice struct {
35+
Name string // /sys/class/sas_device/<Name>
36+
SASAddress string // /sys/class/sas_device/<Name>/sas_address
37+
SASPhys []string // /sys/class/sas_device/<Name>/device/phy-*
38+
SASPorts []string // /sys/class/sas_device/<Name>/device/ports-*
39+
BlockDevices []string // /sys/class/sas_device/<Name>/device/target*/*/block/*
40+
}
41+
42+
type SASDeviceClass map[string]*SASDevice
43+
44+
var (
45+
sasTargetDeviceRegexp = regexp.MustCompile(`^target[0-9:]+$`)
46+
sasTargetSubDeviceRegexp = regexp.MustCompile(`[0-9]+:.*`)
47+
)
48+
49+
// sasDeviceClasses reads all of the SAS devices from a specific set
50+
// of /sys/class/sas*/ entries. The sas_device, sas_end_device, and
51+
// sas_expander classes are all nearly identical and can be handled by the same basic code.
52+
53+
func (fs FS) parseSASDeviceClass(dir string) (SASDeviceClass, error) {
54+
path := fs.sys.Path(dir)
55+
56+
dirs, err := ioutil.ReadDir(path)
57+
if err != nil {
58+
return nil, err
59+
}
60+
61+
sdc := make(SASDeviceClass, len(dirs))
62+
63+
for _, d := range dirs {
64+
device, err := fs.parseSASDevice(d.Name())
65+
if err != nil {
66+
return nil, err
67+
}
68+
69+
sdc[device.Name] = device
70+
}
71+
72+
return sdc, nil
73+
}
74+
75+
// SASDeviceClass parses devices in /sys/class/sas_device.
76+
func (fs FS) SASDeviceClass() (SASDeviceClass, error) {
77+
return fs.parseSASDeviceClass(sasDeviceClassPath)
78+
}
79+
80+
// SASEndDeviceClass parses devices in /sys/class/sas_end_device.
81+
// This is a subset of sas_device, and excludes expanders.
82+
func (fs FS) SASEndDeviceClass() (SASDeviceClass, error) {
83+
return fs.parseSASDeviceClass(sasEndDeviceClassPath)
84+
}
85+
86+
// SASExpanderClass parses devices in /sys/class/sas_expander.
87+
// This is a subset of sas_device, but only includes expanders.
88+
func (fs FS) SASExpanderClass() (SASDeviceClass, error) {
89+
return fs.parseSASDeviceClass(sasExpanderClassPath)
90+
}
91+
92+
// Parse a single sas_device. This uses /sys/class/sas_device, as
93+
// it's a superset of the other two directories so there's no reason
94+
// to plumb the path through to here.
95+
func (fs FS) parseSASDevice(name string) (*SASDevice, error) {
96+
device := SASDevice{Name: name}
97+
98+
devicepath := fs.sys.Path(filepath.Join(sasDeviceClassPath, name, "device"))
99+
100+
dirs, err := ioutil.ReadDir(devicepath)
101+
if err != nil {
102+
return nil, err
103+
}
104+
105+
for _, d := range dirs {
106+
if sasPhyDeviceRegexp.MatchString(d.Name()) {
107+
device.SASPhys = append(device.SASPhys, d.Name())
108+
}
109+
if sasPortDeviceRegexp.MatchString(d.Name()) {
110+
device.SASPorts = append(device.SASPorts, d.Name())
111+
}
112+
}
113+
114+
address := fs.sys.Path(sasDeviceClassPath, name, "sas_address")
115+
value, err := util.SysReadFile(address)
116+
if err != nil {
117+
return nil, err
118+
}
119+
device.SASAddress = value
120+
121+
device.BlockDevices, err = fs.blockSASDeviceBlockDevices(name)
122+
if err != nil {
123+
return nil, err
124+
}
125+
126+
return &device, nil
127+
}
128+
129+
// Identify block devices that map to a specific SAS Device
130+
// This info comes from (for example)
131+
// /sys/class/sas_device/end_device-11:2/device/target11:0:0/11:0:0:0/block/sdp
132+
//
133+
// To find that, we have to look in the device directory for target$X
134+
// subdirs, then specific subdirs of $X, then read from directory
135+
// names in the 'block/' subdirectory under that. This really
136+
// shouldn't be this hard.
137+
func (fs FS) blockSASDeviceBlockDevices(name string) ([]string, error) {
138+
var devices []string
139+
140+
devicepath := fs.sys.Path(filepath.Join(sasDeviceClassPath, name, "device"))
141+
142+
dirs, err := ioutil.ReadDir(devicepath)
143+
if err != nil {
144+
return nil, err
145+
}
146+
147+
for _, d := range dirs {
148+
if sasTargetDeviceRegexp.MatchString(d.Name()) {
149+
targetdir := d.Name()
150+
151+
subtargets, err := ioutil.ReadDir(filepath.Join(devicepath, targetdir))
152+
if err != nil {
153+
return nil, err
154+
}
155+
156+
for _, targetsubdir := range subtargets {
157+
158+
if !sasTargetSubDeviceRegexp.MatchString(targetsubdir.Name()) {
159+
// need to skip 'power', 'subsys', etc.
160+
continue
161+
}
162+
163+
blocks, err := ioutil.ReadDir(filepath.Join(devicepath, targetdir, targetsubdir.Name(), "block"))
164+
if err != nil {
165+
if os.IsNotExist(err) {
166+
continue
167+
} else {
168+
return nil, err
169+
}
170+
}
171+
172+
for _, blockdevice := range blocks {
173+
devices = append(devices, blockdevice.Name())
174+
}
175+
}
176+
}
177+
}
178+
179+
return devices, nil
180+
}
181+
182+
// GetByName returns the SASDevice with the provided name.
183+
func (sdc *SASDeviceClass) GetByName(name string) *SASDevice {
184+
return (*sdc)[name]
185+
}
186+
187+
// GetByPhy finds the SASDevice that contains the provided PHY name.
188+
func (sdc *SASDeviceClass) GetByPhy(name string) *SASDevice {
189+
for _, d := range *sdc {
190+
for _, p := range d.SASPhys {
191+
if p == name {
192+
return d
193+
}
194+
}
195+
}
196+
return nil
197+
}
198+
199+
// GetByPort finds the SASDevice that contains the provided SAS Port name.
200+
func (sdc *SASDeviceClass) GetByPort(name string) *SASDevice {
201+
for _, d := range *sdc {
202+
for _, p := range d.SASPorts {
203+
if p == name {
204+
return d
205+
}
206+
}
207+
}
208+
return nil
209+
}

0 commit comments

Comments
 (0)