diff options
Diffstat (limited to 'tools/schedlatency')
-rw-r--r-- | tools/schedlatency/go.mod | 3 | ||||
-rw-r--r-- | tools/schedlatency/main.go | 254 |
2 files changed, 0 insertions, 257 deletions
diff --git a/tools/schedlatency/go.mod b/tools/schedlatency/go.mod deleted file mode 100644 index 9a073ac..0000000 --- a/tools/schedlatency/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module golang.fcuny.net/schedlatency - -go 1.17 diff --git a/tools/schedlatency/main.go b/tools/schedlatency/main.go deleted file mode 100644 index 7dd709e..0000000 --- a/tools/schedlatency/main.go +++ /dev/null @@ -1,254 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "os" - "strconv" - "strings" - "time" -) - -type SchedStat struct { - Pid int `json:"pid"` - RunTicks int `json:"run_ticks"` - WaitTicks int `json:"wait_ticks"` - SlicesRan int `json:"ran_slices"` - AverageRun float64 `json:"avg_run"` - AverageWait float64 `json:"avg_wait"` -} - -func usage() { - fmt.Fprintf(os.Stderr, "usage: %s <pid>\n", os.Args[0]) -} - -func main() { - if len(os.Args) == 1 { - usage() - os.Exit(1) - } - - input := os.Args[1] - pid, err := strconv.Atoi(input) - if err != nil { - fmt.Fprintf(os.Stderr, "failed to convert %s to a PID: %v", input, err) - os.Exit(1) - } - - p := Proc{ - PID: pid, - } - oran := 0 - owait_ticks := 0 - orun_ticks := 0 - for { - stat, err := p.SchedStat() - if err != nil { - fmt.Fprintf(os.Stderr, "failed to get schedstat for %d: %v\n", p.PID, err) - os.Exit(1) - } - diff := stat.SlicesRan - oran - var avgrun, avgwait float64 - - if diff > 0 { - avgrun = float64((stat.RunTicks - orun_ticks) / diff) - avgwait = float64((stat.WaitTicks - owait_ticks) / diff) - } else { - avgrun = 0 - avgwait = 0 - } - - stat.AverageRun = avgrun - stat.AverageWait = avgwait - - out, err := json.Marshal(stat) - if err != nil { - fmt.Fprintln(err) - os.Exit(1) - } - fmt.Println(string(out)) - oran = stat.SlicesRan - orun_ticks = stat.RunTicks - owait_ticks = stat.WaitTicks - time.Sleep(5 * time.Second) - } -} - -// This the the path that contains the scheduler statistics. -// Note that they are not populated unless the value for -// /proc/sys/kernel/sched_schedstats is 1 -const procSchedStat = "/proc/schedstat" - -var idleness = []string{"idle", "busy", "newlyIdle"} - -type ProcSchedStat struct { - RunTicks int `json:"run_ticks"` - WaitTicks int `json:"wait_ticks"` - SlicesRan int `json:"ran_slices"` - AverageRun float64 `json:"avg_run"` - AverageWait float64 `json:"avg_wait"` -} - -// SchedCPUStat contains the load balancer statistics for a CPU. -type SchedCPUStat struct { - YieldCount uint64 `json:"yield_count"` - SchedulerCount uint64 `json:"sched_count"` - SchedulerGoIdle uint64 `json:"sched_go_idle"` - TryToWakeUp uint64 `json:"try_to_wake"` - TryToWakeUpLocal uint64 `json:"try_to_wake_local"` - Running uint64 `json:"running"` - Waiting uint64 `json:"waiting"` - Slices uint64 `json:"slices"` - Domains map[string]SchedDomain `json:"domains"` -} - -// SchedLoadBalance contains the load balancer statistics for a domain -// in a given domain. -type SchedLoadBalance struct { - LBCount uint64 `json:"lb_count"` - LBBalanced uint64 `json:"lb_balanced"` - LBFailed uint64 `json:"lb_failed"` - LBImbalanced uint64 `json:"lb_imbalanced"` - LBGained uint64 `json:"lb_gained"` - LBHotGain uint64 `json:"lb_hot_gain"` - LBNoBusyQueue uint64 `json:"lb_no_busy_queue"` - LBNoBusyGroup uint64 `json:"lb_no_busy_group"` -} - -// SchedDomain contains the statistics for a domain. -type SchedDomain struct { - LoadBalancers map[string]SchedLoadBalance `json:"lbs"` - ActiveLoadBalanceCount uint64 `json:"active_lb_count"` - ActiveLoadBalanceFailed uint64 `json:"active_lb_failed"` - ActiveLoadBalancePushed uint64 `json:"active_lb_pushed"` - TryToWakeUpRemote uint64 `json:"try_to_wake_up_remote"` - TryToWakeUpMoveAffine uint64 `json:"try_to_wake_up_move_affine"` - TryToWakeUpMoveBalance uint64 `json:"try_to_wake_up_move_balance"` -} - -// Proc provides information about a running process. -type Proc struct { - // The process ID. - PID int -} - -// SchedStat returns scheduler statistics for the process. -// The information available are: -// 1. time spent on the cpu -// 2. time spent waiting on a runqueue -// 3. # of timeslices run on this cpu -func (p Proc) SchedStat() (ProcSchedStat, error) { - path := fmt.Sprintf("/proc/%d/schedstat", p.PID) - b, err := ioutil.ReadFile(path) - if err != nil { - return ProcSchedStat{}, err - } - content := string(b) - stats := strings.Fields(content) - - run_ticks, err := strconv.Atoi(stats[0]) - if err != nil { - return ProcSchedStat{}, err - } - - wait_ticks, err := strconv.Atoi(stats[1]) - if err != nil { - return ProcSchedStat{}, err - } - - nran, err := strconv.Atoi(stats[2]) - if err != nil { - return ProcSchedStat{}, err - } - - stat := ProcSchedStat{ - RunTicks: run_ticks, - WaitTicks: wait_ticks, - SlicesRan: nran, - } - return stat, nil -} - -// ReadSchedstat returns statistics from the scheduler. -// Information about the statistics can be found at -// https://www.kernel.org/doc/html/latest/scheduler/sched-stats.html. -func ReadSchedStat() (map[string]SchedCPUStat, error) { - b, err := ioutil.ReadFile(procSchedStat) - if err != nil { - return nil, fmt.Errorf("procfs: failed to open %s: %v", procSchedStat, err) - } - content := string(b) - - cpus := map[string]SchedCPUStat{} - - lines := strings.Split(content, "\n") - - var currentCpu string - - // The first line is the version of the stats - // TODO(fcuny): we should check which version is used, because the - // format changes. - for _, line := range lines[2:] { - // The format is as follow: - // cpu<N> 1 2 3 4 5 6 7 8 9 - // domain<N> <cpumask> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 - if strings.HasPrefix(line, "cpu") { - // meaning of the fields: https://www.kernel.org/doc/html/latest/scheduler/sched-stats.html#cpu-statistics - fields := strings.Fields(line) - cpuStat := SchedCPUStat{ - YieldCount: convertField(fields[1]), - SchedulerCount: convertField(fields[3]), - SchedulerGoIdle: convertField(fields[4]), - TryToWakeUp: convertField(fields[5]), - TryToWakeUpLocal: convertField(fields[6]), - Running: convertField(fields[7]), - Waiting: convertField(fields[8]), - Slices: convertField(fields[9]), - Domains: map[string]SchedDomain{}, - } - currentCpu = fields[0] - cpus[currentCpu] = cpuStat - } else if strings.HasPrefix(line, "domain") { - // meaning of the fields: https://www.kernel.org/doc/html/latest/scheduler/sched-stats.html#domain-statistics - fields := strings.Fields(line) - i := 2 - lbs := map[string]SchedLoadBalance{} - for _, idle := range idleness { - lb := SchedLoadBalance{ - LBCount: convertField(fields[i]), - LBBalanced: convertField(fields[i+1]), - LBFailed: convertField(fields[i+2]), - LBImbalanced: convertField(fields[i+3]), - LBGained: convertField(fields[i+4]), - LBHotGain: convertField(fields[i+5]), - LBNoBusyQueue: convertField(fields[i+6]), - LBNoBusyGroup: convertField(fields[i+7]), - } - i = i + 8 - lbs[idle] = lb - } - domain := SchedDomain{ - LoadBalancers: lbs, - ActiveLoadBalanceCount: convertField(fields[26]), - ActiveLoadBalanceFailed: convertField(fields[27]), - ActiveLoadBalancePushed: convertField(fields[28]), - TryToWakeUpRemote: convertField(fields[35]), - TryToWakeUpMoveAffine: convertField(fields[36]), - TryToWakeUpMoveBalance: convertField(fields[37]), - } - c := cpus[currentCpu] - c.Domains[fields[0]] = domain - cpus[currentCpu] = c - } - } - return cpus, nil -} - -func convertField(field string) uint64 { - val, err := strconv.ParseUint(field, 10, 64) - if err != nil { - return 0 - } - return val -} |