diff options
-rw-r--r-- | tools/scheddomain/go.mod | 3 | ||||
-rw-r--r-- | tools/scheddomain/main.go | 153 |
2 files changed, 156 insertions, 0 deletions
diff --git a/tools/scheddomain/go.mod b/tools/scheddomain/go.mod new file mode 100644 index 0000000..afbc83a --- /dev/null +++ b/tools/scheddomain/go.mod @@ -0,0 +1,3 @@ +module golang.fcuny.net/scheddomain + +go 1.17 diff --git a/tools/scheddomain/main.go b/tools/scheddomain/main.go new file mode 100644 index 0000000..1d0f5d3 --- /dev/null +++ b/tools/scheddomain/main.go @@ -0,0 +1,153 @@ +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 +} |