package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
"strconv"
"strings"
)
// https://kernel.googlesource.com/pub/scm/linux/kernel/git/torvalds/linux/+/v4.17/include/linux/sched/topology.h#20
var SDFlags = map[string]uint64{
"SD_LOAD_BALANCE": 0x0001,
"SD_BALANCE_NEWIDLE": 0x0002,
"SD_BALANCE_EXEC": 0x0004,
"SD_BALANCE_FORK": 0x0008,
"SD_BALANCE_WAKE": 0x0010,
"SD_WAKE_AFFINE": 0x0020,
"SD_ASYM_CPUCAPACITY": 0x0040,
"SD_SHARE_CPUCAPACITY": 0x0080,
"SD_SHARE_POWERDOMAIN": 0x0100,
"SD_SHARE_PKG_RESOURCES": 0x0200,
"SD_SERIALIZE": 0x0400,
"SD_ASYM_PACKING": 0x0800,
"SD_PREFER_SIBLING": 0x1000,
"SD_OVERLAP": 0x2000,
"SD_NUMA": 0x4000,
}
type Scheduler map[string][]Domain
type Domain struct {
Name string `json:"name"`
Type string `json:"type"`
Flags []string `json:"flags"`
Indexes map[string]string `json:"indexes"`
}
func main() {
cpus, err := CPUs()
if err != nil {
fmt.Fprint(os.Stderr, err)
os.Exit(1)
}
if len(cpus) == 0 {
fmt.Fprint(os.Stderr, "there is no scheduler domains\n")
os.Exit(1)
}
sched := Scheduler{}
for _, cpu := range cpus {
_, cpuID := path.Split(cpu)
domains, err := domains(cpu)
if err != nil {
fmt.Fprint(os.Stderr, err)
os.Exit(1)
}
sched[cpuID] = domains
}
out, err := json.Marshal(sched)
if err != nil {
fmt.Fprint(os.Stderr, err)
os.Exit(1)
}
fmt.Println(string(out))
}
func domains(cpuPath string) ([]Domain, error) {
domainPath := fmt.Sprintf("%s/domain*", cpuPath)
availDomains, err := filepath.Glob(domainPath)
if err != nil {
return nil, fmt.Errorf("failed to get domains under %s: %v", cpuPath, err)
}
domains := []Domain{}
if len(availDomains) == 0 {
return domains, nil
}
for _, d := range availDomains {
_, dName := path.Split(d)
dType := getContent(d, "name")
flags, err := domainFlags(d)
if err != nil {
return nil, err
}
indexes := domainIndexes(d)
domain := Domain{
Name: dName,
Type: dType,
Flags: flags,
Indexes: indexes,
}
domains = append(domains, domain)
}
return domains, nil
}
func domainFlags(path string) ([]string, error) {
flagPath := fmt.Sprintf("%s/flags", path)
content, err := ioutil.ReadFile(flagPath)
if err != nil {
return nil, fmt.Errorf("failed to read %s: %v", flagPath, err)
}
flags, err := strconv.ParseUint(strings.TrimSpace(string(content)), 0, 64)
if err != nil {
return nil, fmt.Errorf("failed to convert flags %s: %v", flagPath, err)
}
supportedFlags := []string{}
for k, v := range SDFlags {
if flags&v > 0 {
supportedFlags = append(supportedFlags, k)
}
}
return supportedFlags, nil
}
func domainIndexes(path string) map[string]string {
indexes := map[string]string{
"busy": getContent(path, "busy_idx"),
"idle": getContent(path, "idle_idx"),
"new_idle": getContent(path, "newidle_idx"),
"wake": getContent(path, "wake_idx"),
"fork_exec": getContent(path, "forkexec_idx"),
}
return indexes
}
func getContent(path, fileName string) string {
domainName := fmt.Sprintf("%s/%s", path, fileName)
name, err := ioutil.ReadFile(domainName)
if err != nil {
return ""
}
return strings.TrimSpace(string(name))
}
func CPUs() ([]string, error) {
cpus, err := filepath.Glob("/proc/sys/kernel/sched_domain/cpu*")
if err != nil {
return nil, fmt.Errorf("failed to get a list of cpus: %v", err)
}
return cpus, nil
}