SUSE/saptune

View on GitHub
system/sysctl.go

Summary

Maintainability
A
35 mins
Test Coverage
A
90%
package system

// Manipulate sysctl switches.

import (
    "fmt"
    "io/ioutil"
    "os"
    "path"
    "path/filepath"
    "strconv"
    "strings"
)

// mapping of system parameter names to configuration names
const (
    SysctlPagecacheLimitMB          = "vm.pagecache_limit_mb"
    SysctlPagecacheLimitIgnoreDirty = "vm.pagecache_limit_ignore_dirty"
    SysctlDirtyRatio                = "vm.dirty_ratio"
    SysctlDirtyBackgroundRatio      = "vm.dirty_background_ratio"
    SysKernelTHPEnabled             = "kernel.mm.transparent_hugepage.enabled"
    SysKSMRun                       = "kernel.mm.ksm.run"
)

// sysctlDirs contains all locations sysctl is searching for parameter settings.
// see comment in /etc/sysctl.conf and man page sysctl.conf(5)
var sysctlDirs = []string{"/etc/sysctl.conf", "/run/sysctl.d/", "/etc/sysctl.d/", "/usr/local/lib/sysctl.d/", "/usr/lib/sysctl.d/", "/lib/sysctl.d/", "/boot/"}

var sysctlParms = sysctlDefined{}
var sysctlWarn = map[string]string{}
var sysctlExcludeList = map[string]string{}

// sysctlEntry contains the 'sysctl config filename - value' pair
type sysctlEntry struct {
    File  string
    Value string
}

// sysctlConf contains a list of all files-value pairs for the related
// sysctl parameter
type sysctlConf []sysctlEntry

// sysctlDefined contains all sysctl parameter, which are defined in the
// sysctl config files of the system
type sysctlDefined map[string]sysctlConf

// ChkForSysctlDoubles checks if the given sysctl parameter is additional set
// in a sysctl system configuration file
func ChkForSysctlDoubles(param string) string {
    info := ""
    if len(sysctlParms[param]) > 0 {
        // found double
        for _, entries := range sysctlParms[param] {
            if _, ok := sysctlExcludeList[entries.File]; ok {
                continue
            }
            txt := entries.File + "(" + entries.Value + ")"
            if info == "" {
                info = "sysctl config file " + txt
            } else {
                info = info + ", " + txt
            }
        }
        if info != "" {
            printDoubleWarning(param, info)
        }
    }
    return info
}

// printDoubleWarning checks, if we need to print a sysctl double warning
func printDoubleWarning(param, info string) {
    if _, ok := sysctlWarn[param]; !ok {
        // print warning
        WarningLog("Parameter '%s' additional defined in the following %s.", param, info)
        sysctlWarn[param] = info
    }
}

// CollectGlobalSysctls collects all sysctl parameters defined in all
// of the sysctl.conf related files
func CollectGlobalSysctls(excludeDirs []string) {
    fileList := make(map[string]string)

    getSysctlFilelist(excludeDirs, sysctlExcludeList, true)
    getSysctlFilelist(sysctlDirs, fileList, false)
    for _, sfile := range fileList {
        sconf, err := parseSysctlConfFile(sfile)
        if err != nil {
            // skip file
            continue
        }
        for param := range sconf {
            sysctlcnf := append(sysctlParms[param], sconf[param])
            sysctlParms[param] = sysctlcnf
        }
    }
}

// getSysctlFilelist builds a file list with all available sysctl conf files
// following symlinks
func getSysctlFilelist(dirs []string, fileList map[string]string, exclude bool) {
    for _, file := range getAllSysctlFiles(dirs) {
        // check all config files mentioned in /etc/sysctl.conf and
        // the sysctl.conf(5) man page
        info, err := os.Lstat(file)
        if err != nil {
            // file or directory does not exist
            continue
        }
        switch mode := info.Mode(); {
        case mode.IsRegular():
            fileList[file] = file
        case mode&os.ModeSymlink != 0:
            // symlink
            origFile, err := filepath.EvalSymlinks(file)
            if err != nil {
                continue
            }
            fileList[origFile] = file
            if exclude {
                fileList[file] = origFile
            }
        }
    }
}

// getAllSysctlFiles retrieves all sysctl config files from all
// locations/directories
func getAllSysctlFiles(dirs []string) []string {
    files := []string{}
    for _, file := range dirs {
        file = strings.TrimSpace(file)
        info, err := os.Stat(file)
        if err != nil {
            // file or directory does not exist
            continue
        }
        if info.IsDir() {
            if !strings.HasSuffix(file, "/") {
                file = file + "/"
            }
            for f := range GetFiles(file) {
                if (file == "/boot/" && !strings.HasPrefix(f, "sysctl.conf-")) && !strings.HasSuffix(f, ".conf") {
                    // wrong file name format, skip file
                    continue
                }
                files = append(files, file+f)
            }
        } else {
            files = append(files, file)
        }
    }
    return files
}

// parseSysctlConfFile parses a special sysctl config file and returns
// the key-value pairs of the contained sysctl parameters
func parseSysctlConfFile(file string) (map[string]sysctlEntry, error) {
    entries := make(map[string]sysctlEntry)
    content, err := ReadConfigFile(file, false)
    if err != nil {
        return nil, err
    }
    for _, line := range strings.Split(string(content), "\n") {
        line = strings.TrimSpace(line)
        if strings.HasPrefix(line, "#") {
            // Line is a comment
            continue
        }
        if eqChar := strings.IndexRune(line, '='); eqChar != -1 {
            // Line is a key-value pair
            key := strings.TrimSpace(line[0:eqChar])
            entries[key] = sysctlEntry{
                File:  file,
                Value: strings.Trim(strings.TrimSpace(line[eqChar+1:]), `"`),
            }
        }
    }
    return entries, nil
}

// GetSysctlString read a sysctl key and return the string value.
func GetSysctlString(parameter string) (string, error) {
    val, err := ioutil.ReadFile(path.Join("/proc/sys", strings.Replace(parameter, ".", "/", -1)))
    if err != nil {
        WarningLog("Failed to read sysctl key '%s': %v", parameter, err)
        return "PNA", err
    }
    return strings.TrimSpace(string(val)), nil
}

// GetSysctlInt read an integer sysctl key.
func GetSysctlInt(parameter string) (int, error) {
    value, err := GetSysctlString(parameter)
    if err != nil {
        return 0, err
    }
    return strconv.Atoi(value)
}

// GetSysctlUint64 read an uint64 sysctl key.
func GetSysctlUint64(parameter string) (uint64, error) {
    value, err := GetSysctlString(parameter)
    if err != nil {
        return 0, err
    }
    return strconv.ParseUint(value, 10, 64)
}

// GetSysctlUint64Field extracts an uint64 value from a sysctl key of many fields.
func GetSysctlUint64Field(param string, field int) (uint64, error) {
    fields, err := GetSysctlString(param)
    if err == nil {
        allFields := consecutiveSpaces.Split(fields, -1)
        if field < len(allFields) {
            value, err := strconv.ParseUint(allFields[field], 10, 64)
            if err != nil {
                return 0, fmt.Errorf("Failed to read sysctl key field '%s' %d: %v", param, field, err)
            }
            return value, nil
        }
    }
    return 0, err
}

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

// SetSysctlInt write an integer sysctl value.
func SetSysctlInt(parameter string, value int) error {
    err := SetSysctlString(parameter, strconv.Itoa(value))
    return err
}

// SetSysctlUint64 write an integer sysctl value.
func SetSysctlUint64(parameter string, value uint64) error {
    err := SetSysctlString(parameter, strconv.FormatUint(value, 10))
    return err
}

// SetSysctlUint64Field write an integer sysctl value into the specified field pf the key.
func SetSysctlUint64Field(param string, field int, value uint64) error {
    fields, err := GetSysctlString(param)
    if err != nil {
        return err
    }
    allFields := consecutiveSpaces.Split(fields, -1)
    if field < len(allFields) {
        allFields[field] = strconv.FormatUint(value, 10)
        err = SetSysctlString(param, strings.Join(allFields, " "))
    } else {
        err = fmt.Errorf("Failed to write sysctl key field '%s' %d: %v", param, field, err)
    }
    return err
}

// IsPagecacheAvailable check, if system supports pagecache limit
func IsPagecacheAvailable() bool {
    _, err := ioutil.ReadFile(path.Join("/proc/sys", strings.Replace(SysctlPagecacheLimitMB, ".", "/", -1)))
    return err == nil
}