SUSE/saptune

View on GitHub
system/sys.go

Summary

Maintainability
A
1 hr
Test Coverage
A
96%
package system

// Manipulate /sys/ switches.

import (
    "bytes"
    "os"
    "path"
    "regexp"
    "strconv"
    "strings"
)

// GetSysString read a /sys/ key and return the string value.
func GetSysString(parameter string) (string, error) {
    return getKeyStringFromPath("/sys", parameter, "sys")
}

// getKeyStringFromPath generalizes the extraction of a string from path
func getKeyStringFromPath(basePath string, parameter string, logFrom string) (string, error) {
    // Seams that os.ReadFile reads only 512 Bytes, if it can not detect the
    // filesize (which is the case for /proc/sys files, returns always 0)
    // This might not enough for sysctl parameter like
    // 'net.ipv4.ip_local_reserved_ports'
    srcFile := path.Join(basePath, strings.Replace(parameter, ".", "/", -1))
    file, err := os.Open(srcFile)
    if err != nil {
        WarningLog("failed to open %s : %v", srcFile, err)
        return "PNA", err
    }
    defer file.Close()
    val := make([]byte, 1024)
    bytesread, err := file.Read(val)
    DebugLog("getKeyStringFromPath - bytes read from '%s': '%+v'\n", srcFile, bytesread)
    if err != nil {
        WarningLog("failed to read %v string key '%s': %v", logFrom, parameter, err)
        return "PNA", err
    }
    // remove NULL
    val = bytes.Trim(val, "\x00")
    return strings.TrimSpace(string(val)), nil
}

// GetSysChoice read a /sys/ key that comes with current value and alternative
// choices, return the current choice or empty string.
func GetSysChoice(parameter string) (string, error) {
    val, err := os.ReadFile(path.Join("/sys", strings.Replace(parameter, ".", "/", -1)))
    if err != nil {
        WarningLog("failed to read sys key of choices '%s': %v", parameter, err)
        return "PNA", err
    }
    // Split up the choices
    allChoices := consecutiveSpaces.Split(string(val), -1)
    for _, choice := range allChoices {
        if len(choice) > 2 && choice[0] == '[' && choice[len(choice)-1] == ']' {
            return choice[1 : len(choice)-1], nil
        }
    }
    return "", nil
}

// GetSysInt read an integer /sys/ key.
func GetSysInt(parameter string) (int, error) {
    value, err := GetSysString(parameter)
    if err != nil {
        WarningLog("failed to read integer sys key '%s': %v", parameter, err)
        return 0, err
    }
    return strconv.Atoi(value)
}

// SetSysString write a string /sys/ value.
func SetSysString(parameter, value string) error {
    if value == "PNA" {
        WarningLog("value is '%s', so sys key '%s' is/was not supported by os, skipping.", value, parameter)
        return nil
    }
    err := os.WriteFile(path.Join("/sys", strings.Replace(parameter, ".", "/", -1)), []byte(value), 0644)
    if os.IsNotExist(err) {
        WarningLog("sys key '%s' is not supported by os, skipping.", parameter)
    } else if err != nil {
        WarningLog("failed to set sys key '%s' to string '%s': %v", parameter, value, err)
        return err
    }
    return nil
}

// SetSysInt write an integer /sys/ value.
func SetSysInt(parameter string, value int) error {
    return SetSysString(parameter, strconv.Itoa(value))
}

// TestSysString Test writing a string /sys/ value.
func TestSysString(parameter, value string) error {
    save, err := GetSysString(parameter)
    if err != nil {
        WarningLog("failed to get sys key '%s': %v", parameter, err)
        return err
    }
    if err = os.WriteFile(path.Join("/sys", strings.Replace(parameter, ".", "/", -1)), []byte(value), 0644); err == nil {
        // set key back to previous value, because this was only a test
        err = os.WriteFile(path.Join("/sys", strings.Replace(parameter, ".", "/", -1)), []byte(save), 0644)
    }
    return err
}

// GetSysSearchParam returns the search pattern for a given sys key
// and the conterpart section
func GetSysSearchParam(syskey string) (string, string) {
    searchParam := ""
    sect := ""
    // blkdev
    sched := regexp.MustCompile(`block.*queue\.scheduler$`)
    nrreq := regexp.MustCompile(`block.*queue\.nr_requests$`)
    rakb := regexp.MustCompile(`block.*queue\.read_ahead_kb$`)
    mskb := regexp.MustCompile(`block.*queue\.max_sectors_kb$`)
    dev := regexp.MustCompile(`block\.(.*)\.queue\..*$`)
    d := dev.FindStringSubmatch(syskey)
    bdev := ""
    if len(d) > 0 {
        bdev = d[1]
    } else {
        dev = regexp.MustCompile(`.*_(\w+)$`)
        d = dev.FindStringSubmatch(syskey)
        if len(d) > 0 {
            bdev = d[1]
        }
    }

    switch {
    case syskey == "THP":
        searchParam = "sys:" + SysKernelTHPEnabled
        sect = "sys"
    case syskey == "sys:"+SysKernelTHPEnabled:
        searchParam = "THP"
        sect = "vm"
    case syskey == "KSM":
        searchParam = "sys:" + SysKSMRun
        sect = "sys"
    case syskey == "sys:"+SysKSMRun:
        searchParam = "KSM"
        sect = "vm"
    case IsSched.MatchString(syskey):
        searchParam = "sys:block." + bdev + ".queue.scheduler"
        sect = "sys"
    case sched.MatchString(syskey):
        searchParam = "IO_SCHEDULER_" + bdev
        sect = "block"
    case IsNrreq.MatchString(syskey):
        searchParam = "sys:block." + bdev + ".queue.nr_requests"
        sect = "sys"
    case nrreq.MatchString(syskey):
        searchParam = "NRREQ_" + bdev
        sect = "block"
    case IsRahead.MatchString(syskey):
        searchParam = "sys:block." + bdev + ".queue.read_ahead_kb"
        sect = "sys"
    case rakb.MatchString(syskey):
        searchParam = "READ_AHEAD_KB_" + bdev
        sect = "block"
    case IsMsect.MatchString(syskey):
        searchParam = "sys:block." + bdev + ".queue.max_sectors_kb"
        sect = "sys"
    case mskb.MatchString(syskey):
        searchParam = "MAX_SECTORS_KB_" + bdev
        sect = "block"
    }
    return searchParam, sect
}

// GetNrTags returns the value from /sys/block/<bdev>/mq/0/nr_tags and the
// related scheduler
func GetNrTags(key string) (int, string, string) {
    nrtags := 0
    elev := ""
    disk := ""
    dname := regexp.MustCompile(`^NRREQ_(\w+\-?\d*)$`)
    bdev := dname.FindStringSubmatch(key)
    if len(bdev) > 0 {
        nrTagsFile := path.Join("block", bdev[1], "mq", "0", "nr_tags")
        if _, err := os.Stat(path.Join("/sys", nrTagsFile)); err == nil {
            nrtags, _ = GetSysInt(nrTagsFile)
        }
        elev, _ = GetSysChoice(path.Join("block", bdev[1], "queue", "scheduler"))
        disk = bdev[1]
    }
    return nrtags, elev, disk
}