shirou_gopsutil/disk/disk_solaris.go

261 lines
6.9 KiB
Go

//go:build solaris
// +build solaris
package disk
import (
"bufio"
"bytes"
"context"
"fmt"
"math"
"os"
"path/filepath"
"regexp"
"runtime"
"strconv"
"strings"
"github.com/shirou/gopsutil/v3/internal/common"
"golang.org/x/sys/unix"
)
const (
// _DEFAULT_NUM_MOUNTS is set to `cat /etc/mnttab | wc -l` rounded up to the
// nearest power of two.
_DEFAULT_NUM_MOUNTS = 32
// _MNTTAB default place to read mount information
_MNTTAB = "/etc/mnttab"
)
// A blacklist of read-only virtual filesystems. Writable filesystems are of
// operational concern and must not be included in this list.
var fsTypeBlacklist = map[string]struct{}{
"ctfs": {},
"dev": {},
"fd": {},
"lofs": {},
"lxproc": {},
"mntfs": {},
"objfs": {},
"proc": {},
}
func PartitionsWithContext(ctx context.Context, all bool) ([]PartitionStat, error) {
ret := make([]PartitionStat, 0, _DEFAULT_NUM_MOUNTS)
// Scan mnttab(4)
f, err := os.Open(_MNTTAB)
if err != nil {
}
defer func() {
if err == nil {
err = f.Close()
} else {
f.Close()
}
}()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
fields := strings.Split(scanner.Text(), "\t")
if _, found := fsTypeBlacklist[fields[2]]; found {
continue
}
ret = append(ret, PartitionStat{
// NOTE(seanc@): Device isn't exactly accurate: from mnttab(4): "The name
// of the resource that has been mounted." Ideally this value would come
// from Statvfs_t.Fsid but I'm leaving it to the caller to traverse
// unix.Statvfs().
Device: fields[0],
Mountpoint: fields[1],
Fstype: fields[2],
Opts: strings.Split(fields[3], ","),
})
}
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("unable to scan %q: %w", _MNTTAB, err)
}
return ret, err
}
func IOCountersWithContext(ctx context.Context, names ...string) (map[string]IOCountersStat, error) {
var issolaris bool
if runtime.GOOS == "illumos" {
issolaris = false
} else {
issolaris = true
}
// check disks instead of zfs pools
filterstr := "/[^zfs]/:::/^nread$|^nwritten$|^reads$|^writes$|^rtime$|^wtime$/"
kstatSysOut, err := invoke.CommandWithContext(ctx, "kstat", "-c", "disk", "-p", filterstr)
if err != nil {
return nil, fmt.Errorf("cannot execute kstat: %w", err)
}
lines := strings.Split(strings.TrimSpace(string(kstatSysOut)), "\n")
if len(lines) == 0 {
return nil, fmt.Errorf("no disk class found")
}
dnamearr := make(map[string]string)
nreadarr := make(map[string]uint64)
nwrittenarr := make(map[string]uint64)
readsarr := make(map[string]uint64)
writesarr := make(map[string]uint64)
rtimearr := make(map[string]uint64)
wtimearr := make(map[string]uint64)
re := regexp.MustCompile(`[:\s]+`)
// in case the name is "/dev/sda1", then convert to "sda1"
for i, name := range names {
names[i] = filepath.Base(name)
}
for _, line := range lines {
fields := re.Split(line, -1)
if len(fields) == 0 {
continue
}
moduleName := fields[0]
instance := fields[1]
dname := fields[2]
if len(names) > 0 && !common.StringsHas(names, dname) {
continue
}
dnamearr[moduleName+instance] = dname
// fields[3] is the statistic label, fields[4] is the value
switch fields[3] {
case "nread":
nreadarr[moduleName+instance], err = strconv.ParseUint((fields[4]), 10, 64)
if err != nil {
return nil, err
}
case "nwritten":
nwrittenarr[moduleName+instance], err = strconv.ParseUint((fields[4]), 10, 64)
if err != nil {
return nil, err
}
case "reads":
readsarr[moduleName+instance], err = strconv.ParseUint((fields[4]), 10, 64)
if err != nil {
return nil, err
}
case "writes":
writesarr[moduleName+instance], err = strconv.ParseUint((fields[4]), 10, 64)
if err != nil {
return nil, err
}
case "rtime":
if issolaris {
// from sec to milli secs
var frtime float64
frtime, err = strconv.ParseFloat((fields[4]), 64)
rtimearr[moduleName+instance] = uint64(frtime * 1000)
} else {
// from nano to milli secs
rtimearr[moduleName+instance], err = strconv.ParseUint((fields[4]), 10, 64)
rtimearr[moduleName+instance] = rtimearr[moduleName+instance] / 1000 / 1000
}
if err != nil {
return nil, err
}
case "wtime":
if issolaris {
// from sec to milli secs
var fwtime float64
fwtime, err = strconv.ParseFloat((fields[4]), 64)
wtimearr[moduleName+instance] = uint64(fwtime * 1000)
} else {
// from nano to milli secs
wtimearr[moduleName+instance], err = strconv.ParseUint((fields[4]), 10, 64)
wtimearr[moduleName+instance] = wtimearr[moduleName+instance] / 1000 / 1000
}
if err != nil {
return nil, err
}
}
}
ret := make(map[string]IOCountersStat, 0)
for k := range dnamearr {
d := IOCountersStat{
Name: dnamearr[k],
ReadBytes: nreadarr[k],
WriteBytes: nwrittenarr[k],
ReadCount: readsarr[k],
WriteCount: writesarr[k],
ReadTime: rtimearr[k],
WriteTime: wtimearr[k],
}
ret[d.Name] = d
}
return ret, nil
}
func UsageWithContext(ctx context.Context, path string) (*UsageStat, error) {
statvfs := unix.Statvfs_t{}
if err := unix.Statvfs(path, &statvfs); err != nil {
return nil, fmt.Errorf("unable to call statvfs(2) on %q: %w", path, err)
}
usageStat := &UsageStat{
Path: path,
Fstype: common.IntToString(statvfs.Basetype[:]),
Total: statvfs.Blocks * statvfs.Frsize,
Free: statvfs.Bfree * statvfs.Frsize,
Used: (statvfs.Blocks - statvfs.Bfree) * statvfs.Frsize,
// NOTE: ZFS (and FreeBZSD's UFS2) use dynamic inode/dnode allocation.
// Explicitly return a near-zero value for InodesUsedPercent so that nothing
// attempts to garbage collect based on a lack of available inodes/dnodes.
// Similarly, don't use the zero value to prevent divide-by-zero situations
// and inject a faux near-zero value. Filesystems evolve. Has your
// filesystem evolved? Probably not if you care about the number of
// available inodes.
InodesTotal: 1024.0 * 1024.0,
InodesUsed: 1024.0,
InodesFree: math.MaxUint64,
InodesUsedPercent: (1024.0 / (1024.0 * 1024.0)) * 100.0,
}
usageStat.UsedPercent = (float64(usageStat.Used) / float64(usageStat.Total)) * 100.0
return usageStat, nil
}
func SerialNumberWithContext(ctx context.Context, name string) (string, error) {
out, err := invoke.CommandWithContext(ctx, "cfgadm", "-ls", "select=type(disk),cols=ap_id:info,cols2=,noheadings")
if err != nil {
return "", fmt.Errorf("exec cfgadm: %w", err)
}
suf := "::" + strings.TrimPrefix(name, "/dev/")
s := bufio.NewScanner(bytes.NewReader(out))
for s.Scan() {
flds := strings.Fields(s.Text())
if strings.HasSuffix(flds[0], suf) {
flen := len(flds)
if flen >= 3 {
for i, f := range flds {
if i > 0 && i < flen-1 && f == "SN:" {
return flds[i+1], nil
}
}
}
return "", nil
}
}
if err := s.Err(); err != nil {
return "", err
}
return "", nil
}
func LabelWithContext(ctx context.Context, name string) (string, error) {
return "", common.ErrNotImplementedError
}