| // 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 ( |
| "errors" |
| "fmt" |
| "os" |
| "path/filepath" |
| "strconv" |
| "strings" |
| "syscall" |
| |
| "github.com/prometheus/procfs/internal/util" |
| ) |
| |
| const sasPhyClassPath = "class/sas_phy" |
| |
| type SASPhy struct { |
| Name string // /sys/class/sas_phy/<Name> |
| SASAddress string // /sys/class/sas_phy/<Name>/sas_address |
| SASPort string // /sys/class/sas_phy/<Name>/device/ports |
| DeviceType string // /sys/class/sas_phy/<Name>/device_type |
| InitiatorPortProtocols []string // /sys/class/sas_phy/<Name>/initiator_port_protocols |
| InvalidDwordCount int // /sys/class/sas_phy/<Name>/invalid_dword_count |
| LossOfDwordSyncCount int // /sys/class/sas_phy/<Name>/loss_of_dword_sync_count |
| MaximumLinkrate float64 // /sys/class/sas_phy/<Name>/maximum_linkrate |
| MaximumLinkrateHW float64 // /sys/class/sas_phy/<Name>/maximum_linkrate_hw |
| MinimumLinkrate float64 // /sys/class/sas_phy/<Name>/minimum_linkrate |
| MinimumLinkrateHW float64 // /sys/class/sas_phy/<Name>/minimum_linkrate_hw |
| NegotiatedLinkrate float64 // /sys/class/sas_phy/<Name>/negotiated_linkrate |
| PhyIdentifier string // /sys/class/sas_phy/<Name>/phy_identifier |
| PhyResetProblemCount int // /sys/class/sas_phy/<Name>/phy_reset_problem_count |
| RunningDisparityErrorCount int // /sys/class/sas_phy/<Name>/running_disparity_error_count |
| TargetPortProtocols []string // /sys/class/sas_phy/<Name>/target_port_protocols |
| } |
| |
| type SASPhyClass map[string]*SASPhy |
| |
| // SASPhyClass parses entries in /sys/class/sas_phy. |
| func (fs FS) SASPhyClass() (SASPhyClass, error) { |
| path := fs.sys.Path(sasPhyClassPath) |
| |
| dirs, err := os.ReadDir(path) |
| if err != nil { |
| return nil, err |
| } |
| |
| spc := make(SASPhyClass, len(dirs)) |
| |
| for _, d := range dirs { |
| phy, err := fs.parseSASPhy(d.Name()) |
| if err != nil { |
| return nil, err |
| } |
| |
| spc[phy.Name] = phy |
| } |
| |
| return spc, nil |
| } |
| |
| // Parse a single sas_phy. |
| func (fs FS) parseSASPhy(name string) (*SASPhy, error) { |
| phy := SASPhy{Name: name} |
| |
| phypath := fs.sys.Path(filepath.Join(sasPhyClassPath, name)) |
| phydevicepath := filepath.Join(phypath, "device") |
| |
| link, err := os.Readlink(filepath.Join(phydevicepath, "port")) |
| |
| if err == nil { |
| if sasPortDeviceRegexp.MatchString(filepath.Base(link)) { |
| phy.SASPort = filepath.Base(link) |
| } |
| } |
| |
| files, err := os.ReadDir(phypath) |
| if err != nil { |
| return nil, err |
| } |
| for _, f := range files { |
| name := filepath.Join(phypath, f.Name()) |
| fileinfo, _ := os.Stat(name) |
| if fileinfo.Mode().IsRegular() { |
| value, err := util.SysReadFile(name) |
| if err != nil { |
| if os.IsPermission(err) || errors.Is(err, syscall.EINVAL) { |
| continue |
| } |
| return nil, fmt.Errorf("failed to read file %q: %w", name, err) |
| } |
| |
| vp := util.NewValueParser(value) |
| switch f.Name() { |
| case "sas_address": |
| phy.SASAddress = value |
| case "device_type": |
| phy.DeviceType = value |
| case "initiator_port_protocols": |
| phy.InitiatorPortProtocols = strings.Split(value, ", ") |
| case "invalid_dword_count": |
| phy.InvalidDwordCount = vp.Int() |
| case "loss_of_dword_sync_count": |
| phy.LossOfDwordSyncCount = vp.Int() |
| case "maximum_linkrate": |
| phy.MaximumLinkrate = parseLinkrate(value) |
| case "maximum_linkrate_hw": |
| phy.MaximumLinkrateHW = parseLinkrate(value) |
| case "minimum_linkrate": |
| phy.MinimumLinkrate = parseLinkrate(value) |
| case "minimum_linkrate_hw": |
| phy.MinimumLinkrateHW = parseLinkrate(value) |
| case "negotiated_linkrate": |
| phy.NegotiatedLinkrate = parseLinkrate(value) |
| case "phy_identifier": |
| phy.PhyIdentifier = value |
| case "phy_reset_problem_count": |
| phy.PhyResetProblemCount = vp.Int() |
| case "running_disparity_error_count": |
| phy.RunningDisparityErrorCount = vp.Int() |
| case "target_port_protocols": |
| phy.TargetPortProtocols = strings.Split(value, ", ") |
| } |
| |
| if err := vp.Err(); err != nil { |
| return nil, err |
| } |
| } |
| } |
| |
| return &phy, nil |
| } |
| |
| // parseLinkRate turns the kernel's SAS linkrate values into floats. |
| // The kernel returns values like "12.0 Gbit". Valid speeds are |
| // currently 1.5, 3.0, 6.0, 12.0, and up. This is a float to cover |
| // the 1.5 Gbps case. A value of 0 is returned if the speed can't be |
| // parsed. |
| func parseLinkrate(value string) float64 { |
| f := strings.Split(value, " ")[0] |
| gb, err := strconv.ParseFloat(f, 64) |
| if err != nil { |
| return 0 |
| } |
| return gb |
| } |
| |
| // GetByName returns the SASPhy with the provided name. |
| func (spc *SASPhyClass) GetByName(name string) *SASPhy { |
| return (*spc)[name] |
| } |