SUSE/saptune

View on GitHub
sap/param/io.go

Summary

Maintainability
C
7 hrs
Test Coverage
B
89%
package param

import (
    "fmt"
    "github.com/SUSE/saptune/system"
    "path"
    "strconv"
    "strings"
)

// BlockDeviceQueue is the data structure for block devices
// for schedulers, IO nr_request, read_ahead_kb and max_sectors_kb changes
type BlockDeviceQueue struct {
    BlockDeviceSchedulers
    BlockDeviceNrRequests
    BlockDeviceReadAheadKB
    BlockDeviceMaxSectorsKB
}

var blkDev *system.BlockDev

// BlockDeviceSchedulers changes IO elevators on all IO devices
type BlockDeviceSchedulers struct {
    SchedulerChoice map[string]string
}

// Inspect retrieves the current scheduler from the system
func (ioe BlockDeviceSchedulers) Inspect() (Parameter, error) {
    if len(ioe.SchedulerChoice) != 0 {
        // inspect needs to run only once per saptune call
        return ioe, nil
    }
    if blkDev == nil || (len(blkDev.AllBlockDevs) == 0 && len(blkDev.BlockAttributes) == 0) {
        blkDev, _ = system.GetBlockDeviceInfo()
    }
    newIOE := BlockDeviceSchedulers{SchedulerChoice: make(map[string]string)}
    for _, entry := range blkDev.AllBlockDevs {
        elev := blkDev.BlockAttributes[entry]["IO_SCHEDULER"]
        if elev != "" {
            newIOE.SchedulerChoice[entry] = elev
        } else {
            newIOE.SchedulerChoice[entry] = "NA"
        }
    }
    return newIOE, nil
}

// Optimise gets the expected scheduler value from the configuration
func (ioe BlockDeviceSchedulers) Optimise(newElevatorName interface{}) (Parameter, error) {
    newIOE := ioe
    fields := strings.Fields(newElevatorName.(string))
    if len(fields) > 1 {
        bdev := fields[0]
        newSched := fields[1]
        newIOE.SchedulerChoice[bdev] = newSched
        /* Future
        if bdev == "all" {
            // all devices with same scheduler
            for k := range ioe.SchedulerChoice {
                if !IsValidScheduler(k, newSched) {
                    continue
                }
                newIOE.SchedulerChoice[k] = newSched
            }
        } else {
            if IsValidScheduler(bdev, newSched) {
                newIOE.SchedulerChoice[bdev] = newSched
            }
        }
        */
    }
    return newIOE, nil
}

// Apply sets the new scheduler value in the system
func (ioe BlockDeviceSchedulers) Apply(blkdev interface{}) error {
    //errs := make([]error, 0, 0)
    bdev := blkdev.(string)
    elevator := ioe.SchedulerChoice[bdev]
    err := system.SetSysString(path.Join("block", bdev, "queue", "scheduler"), elevator)

    /* reuse in future
    for name, elevator := range ioe.SchedulerChoice {
        errs = append(errs, system.SetSysString(path.Join("block", name, "queue", "scheduler"), elevator))
    }
    err := sap.PrintErrors(errs)
    */
    return err
}

// BlockDeviceNrRequests changes IO nr_requests on all block devices
type BlockDeviceNrRequests struct {
    NrRequests map[string]int
}

// Inspect retrieves the current nr_requests from the system
func (ior BlockDeviceNrRequests) Inspect() (Parameter, error) {
    if len(ior.NrRequests) != 0 {
        // inspect needs to run only once per saptune call
        return ior, nil
    }
    if blkDev == nil || (len(blkDev.AllBlockDevs) == 0 && len(blkDev.BlockAttributes) == 0) {
        blkDev, _ = system.GetBlockDeviceInfo()
    }
    newIOR := BlockDeviceNrRequests{NrRequests: make(map[string]int)}
    for _, entry := range blkDev.AllBlockDevs {
        nrreq := blkDev.BlockAttributes[entry]["NRREQ"]
        if nrreq != "" {
            ival, _ := strconv.Atoi(nrreq)
            if ival >= 0 {
                newIOR.NrRequests[entry] = ival
            }
        }
    }
    return newIOR, nil
}

// Optimise gets the expected nr_requests value from the configuration
func (ior BlockDeviceNrRequests) Optimise(newNrRequestValue interface{}) (Parameter, error) {
    newIOR := BlockDeviceNrRequests{NrRequests: make(map[string]int)}
    for k := range ior.NrRequests {
        newIOR.NrRequests[k] = newNrRequestValue.(int)
    }
    return newIOR, nil
}

// Apply sets the new nr_requests value in the system
func (ior BlockDeviceNrRequests) Apply(blkdev interface{}) error {
    bdev := blkdev.(string)
    nrreq := ior.NrRequests[bdev]
    err := system.SetSysInt(path.Join("block", bdev, "queue", "nr_requests"), nrreq)
    if err != nil {
        // do not use the stored (and may be outdated) value from
        // blkDev.BlockAttributes[bdev]["IO_SCHEDULER"]
        // need the current elevator, so need to ask again.
        // and in case of 'revert' we do not have blkDev.BlockAttributes
        elev, _ := system.GetSysChoice(path.Join("block", bdev, "queue", "scheduler"))
        if elev == "none" {
            nrtags, _ := system.GetSysString(path.Join("block", bdev, "mq", "0", "nr_tags"))
            // future - change message in case of 'revert'
            system.ErrorLog("skipping device '%s', not valid for setting 'number of requests' to '%v'.\n The SAP recommendation does not work in the context of multiqueue block framework. Maximal supported value by the hardware is '%v'\n.", bdev, nrreq, nrtags)
        } else {
            system.WarningLog("skipping device '%s', not valid for setting 'number of requests' to '%v'", bdev, nrreq)
        }
    }
    /* for future use
    errs := make([]error, 0, 0)
    for name, nrreq := range ior.NrRequests {
        if !IsValidforNrRequests(name, strconv.Itoa(nrreq)) {
            system.WarningLog("skipping device '%s', not valid for setting 'number of requests' to '%v'", name, nrreq)
            continue
        }
        errs = append(errs, system.SetSysInt(path.Join("block", name, "queue", "nr_requests"), nrreq))
    }
    err := sap.PrintErrors(errs)
    return err
    */
    return nil
}

// BlockDeviceReadAheadKB changes the read_ahead_kb value on all block devices
type BlockDeviceReadAheadKB struct {
    ReadAheadKB map[string]int
}

// Inspect retrieves the current read_ahead_kb from the system
func (rakb BlockDeviceReadAheadKB) Inspect() (Parameter, error) {
    if len(rakb.ReadAheadKB) != 0 {
        // inspect needs to run only once per saptune call
        return rakb, nil
    }
    if blkDev == nil || (len(blkDev.AllBlockDevs) == 0 && len(blkDev.BlockAttributes) == 0) {
        blkDev, _ = system.GetBlockDeviceInfo()
    }
    newRAKB := BlockDeviceReadAheadKB{ReadAheadKB: make(map[string]int)}
    for _, entry := range blkDev.AllBlockDevs {
        readahead := blkDev.BlockAttributes[entry]["READ_AHEAD_KB"]
        if readahead != "" {
            ival, _ := strconv.Atoi(readahead)
            if ival >= 0 {
                newRAKB.ReadAheadKB[entry] = ival
            }
        }
    }
    return newRAKB, nil
}

// Optimise gets the expected read_ahead_kb value from the configuration
func (rakb BlockDeviceReadAheadKB) Optimise(newReadAheadKBValue interface{}) (Parameter, error) {
    newRAKB := BlockDeviceReadAheadKB{ReadAheadKB: make(map[string]int)}
    for k := range rakb.ReadAheadKB {
        newRAKB.ReadAheadKB[k] = newReadAheadKBValue.(int)
    }
    return newRAKB, nil
}

// Apply sets the new read_ahead_kb value in the system
func (rakb BlockDeviceReadAheadKB) Apply(blkdev interface{}) error {
    bdev := blkdev.(string)
    bfile := path.Join("block", bdev, "queue", "read_ahead_kb")
    readahead := rakb.ReadAheadKB[bdev]
    oldval, _ := system.GetSysInt(bfile)
    err := system.SetSysInt(bfile, readahead)
    if err != nil {
        system.WarningLog("skipping device '%s', not valid for setting 'read_ahead_kb' to '%v'", bdev, readahead)
    } else {
        chkval, _ := system.GetSysInt(bfile)
        if chkval != readahead {
            system.InfoLog("value '%v' for setting 'read_ahead_kb' is not valid for device '%s', will be changed by the kernel to '%v'.", readahead, bdev, chkval)
            if oldval > 0 {
                system.InfoLog("skipping device '%s'. Please check and adapt the value in the Note definition file.", bdev)
                _ = system.SetSysInt(bfile, oldval)
            }
        }
    }
    /* for future use
    errs := make([]error, 0, 0)
    for name, readahead := range rakb.ReadAheadKB {
        if !IsValidforReadAheadKB(name, strconv.Itoa(readahead)) {
            system.WarningLog("skipping device '%s', not valid for setting 'read_ahead_kb' to '%v'", name, readahead)
            continue
        }
        errs = append(errs, system.SetSysInt(path.Join("block", name, "queue", "read_ahead_kb"), readahead))
    }
    err := sap.PrintErrors(errs)
    return err
    */
    return nil
}

// BlockDeviceMaxSectorsKB changes the max_sectors_kb value on all block devices
type BlockDeviceMaxSectorsKB struct {
    MaxSectorsKB map[string]int
}

// Inspect retrieves the current max_sectors_kb from the system
func (mskb BlockDeviceMaxSectorsKB) Inspect() (Parameter, error) {
    if len(mskb.MaxSectorsKB) != 0 {
        // inspect needs to run only once per saptune call
        return mskb, nil
    }
    if blkDev == nil || (len(blkDev.AllBlockDevs) == 0 && len(blkDev.BlockAttributes) == 0) {
        blkDev, _ = system.GetBlockDeviceInfo()
    }
    newMSKB := BlockDeviceMaxSectorsKB{MaxSectorsKB: make(map[string]int)}
    for _, entry := range blkDev.AllBlockDevs {
        maxsector := blkDev.BlockAttributes[entry]["MAX_SECTORS_KB"]
        if maxsector != "" {
            ival, _ := strconv.Atoi(maxsector)
            if ival >= 0 {
                newMSKB.MaxSectorsKB[entry] = ival
            }
        }
    }
    return newMSKB, nil
}

// Optimise gets the expected max_sectors_kb value from the configuration
func (mskb BlockDeviceMaxSectorsKB) Optimise(newMaxSectorsKBValue interface{}) (Parameter, error) {
    newMSKB := BlockDeviceMaxSectorsKB{MaxSectorsKB: make(map[string]int)}
    for k := range mskb.MaxSectorsKB {
        newMSKB.MaxSectorsKB[k] = newMaxSectorsKBValue.(int)
    }
    return newMSKB, nil
}

// Apply sets the new max_sectors_kb value in the system
func (mskb BlockDeviceMaxSectorsKB) Apply(blkdev interface{}) error {
    bdev := blkdev.(string)
    maxsector := mskb.MaxSectorsKB[bdev]
    err := system.SetSysInt(path.Join("block", bdev, "queue", "max_sectors_kb"), maxsector)
    if err != nil {
        system.WarningLog("skipping device '%s', not valid for setting 'max_sectors_kb' to '%v'", bdev, maxsector)
    }
    /* for future use
    errs := make([]error, 0, 0)
    for name, maxsector := range mskb.MaxSectorsKB {
        if !IsValidforMaxSectorsKB(name, strconv.Itoa(maxsector)) {
            system.WarningLog("skipping device '%s', not valid for setting 'max_sectors_kb' to '%v'", name, maxsector)
            continue
        }
        errs = append(errs, system.SetSysInt(path.Join("block", name, "queue", "max_sectors_kb"), maxsector))
    }
    err := sap.PrintErrors(errs)
    return err
    */
    return nil
}

// IsValidScheduler checks, if the scheduler value is supported by the system.
// only used during optimize
// During initialize, the scheduler is read from the system, so no check needed.
// Only needed during optimize, as apply is using the value from optimize and
// revert is using the stored valid old values from before apply.
// And a scheduler can only change during a system reboot
// (single-queued -> multi-queued)
func IsValidScheduler(blockdev, scheduler string) bool {
    if blkDev == nil || (len(blkDev.AllBlockDevs) == 0 && len(blkDev.BlockAttributes) == 0) {
        blkDev, _ = system.GetBlockDeviceInfo()
    }
    val := blkDev.BlockAttributes[blockdev]["VALID_SCHEDS"]
    actsched := fmt.Sprintf("[%s]", scheduler)
    if val != "" {
        for _, s := range strings.Split(string(val), " ") {
            s = strings.TrimSpace(s)
            if s == scheduler || s == actsched {
                return true
            }
        }
    }
    system.InfoLog("'%s' is not a valid scheduler for device '%s', skipping.", scheduler, blockdev)
    return false
}

// IsValidforNrRequests checks, if the nr_requests value is supported by the system
// it's not a good idea to use this during optimize, as it will write a new
// value to the device, so only used during apply, but this can be performed
// in a better way.
func IsValidforNrRequests(blockdev, nrreq string) bool {
    file := path.Join("block", blockdev, "queue", "nr_requests")
    return checkIfBlockIsValid(blockdev, nrreq, file)
}

// IsValidforReadAheadKB checks, if the read_ahead_kb value is supported by the system
// it's not a good idea to use this during optimize, as it will write a new
// value to the device, so only used during apply, but this can be performed
// in a better way.
func IsValidforReadAheadKB(blockdev, readahead string) bool {
    file := path.Join("block", blockdev, "queue", "read_ahead_kb")
    return checkIfBlockIsValid(blockdev, readahead, file)
}

func checkIfBlockIsValid(blockdev string, testString string, file string) bool {
    elev, _ := system.GetSysChoice(path.Join("block", blockdev, "queue", "scheduler"))
    if elev != "" && elev != "NA" && elev != "PNA" {
        if tstErr := system.TestSysString(file, testString); tstErr == nil {
            return true
        }
    }
    return false
}