205 lines
6.1 KiB
Go
205 lines
6.1 KiB
Go
// Copyright 2014 Google Inc. All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package procfs
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/google/cadvisor/utils/fs"
|
|
)
|
|
|
|
type SchedulerLoadReader interface {
|
|
// Load() returns the load of each core of given container. If there's
|
|
// no information of given container, it should return nil, nil. If the
|
|
// returned load is not nil, the number of elements in the returned
|
|
// slice must be the same as the number of cores on the machine. Each
|
|
// element in the returned slices represents number of tasks/threads in
|
|
// the container and waiting for the CPU, i.e. number of runnable
|
|
// tasks.
|
|
Load(container string) ([]int, error)
|
|
|
|
AllContainers() ([]string, error)
|
|
}
|
|
|
|
func NewSchedulerLoadReader() (SchedulerLoadReader, error) {
|
|
schedDebug, err := fs.Open("/proc/sched_debug")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
scanner := bufio.NewScanner(schedDebug)
|
|
stateMachine := newSchedDebugReader()
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
err = stateMachine.ProcessLine(line)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if err = scanner.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return stateMachine.Load()
|
|
}
|
|
|
|
type schedDebugReaderStateMachine struct {
|
|
currentState schedDebugReaderState
|
|
context *schedDebugContext
|
|
}
|
|
|
|
func newSchedDebugReader() *schedDebugReaderStateMachine {
|
|
return &schedDebugReaderStateMachine{
|
|
currentState: &schedDebugReaderStateReadingVersion{},
|
|
context: &schedDebugContext{
|
|
loadMap: make(map[string][]int, 8),
|
|
},
|
|
}
|
|
}
|
|
|
|
func (self *schedDebugReaderStateMachine) ProcessLine(line string) error {
|
|
nextState, err := self.currentState.Transit(self.context, line)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
self.currentState = nextState
|
|
return nil
|
|
}
|
|
|
|
func (self *schedDebugReaderStateMachine) Load() (SchedulerLoadReader, error) {
|
|
return self.context.loadMap, nil
|
|
}
|
|
|
|
type schedDebugContext struct {
|
|
loadMap simpleSchedulerLoadReader
|
|
numCores int
|
|
}
|
|
|
|
// Key: container name
|
|
// Value: number of runnable processes in each core
|
|
type simpleSchedulerLoadReader map[string][]int
|
|
|
|
func (self simpleSchedulerLoadReader) Load(container string) ([]int, error) {
|
|
if load, ok := self[container]; ok {
|
|
return load, nil
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func (self simpleSchedulerLoadReader) AllContainers() ([]string, error) {
|
|
ret := make([]string, 0, len(self))
|
|
for c := range self {
|
|
ret = append(ret, c)
|
|
}
|
|
return ret, nil
|
|
}
|
|
|
|
// This interface is used to represent a state when reading sched_debug. The
|
|
// reader of sched_debug is a state machine and will transit to another state
|
|
// (and change its behavior accordingly) when a certain line is read.
|
|
// The state machine is implemented using the State Pattern.
|
|
type schedDebugReaderState interface {
|
|
// Transit() consumes a line from sched_debug and returns a new state
|
|
// for the next line. It may change the context accordingly.
|
|
Transit(context *schedDebugContext, line string) (schedDebugReaderState, error)
|
|
}
|
|
|
|
// State: Reading version
|
|
// In this state, the state machine is waiting for the version of the Sched Debug message.
|
|
// If the version is supported, it will transit to WaitingHeader state.
|
|
// Otherwise, it will report error
|
|
type schedDebugReaderStateReadingVersion struct {
|
|
}
|
|
|
|
func (self *schedDebugReaderStateReadingVersion) Transit(context *schedDebugContext, line string) (schedDebugReaderState, error) {
|
|
line = strings.TrimSpace(line)
|
|
if strings.HasPrefix(line, "Sched Debug Version") {
|
|
if !strings.HasPrefix(line, "Sched Debug Version: v0.11") {
|
|
return nil, fmt.Errorf("unsupported sched_debug version: %v", line)
|
|
}
|
|
return &schedDebugReaderStateWaitingHeader{}, nil
|
|
}
|
|
return self, nil
|
|
}
|
|
|
|
// State: WaitingHeader
|
|
// In this state the state machine is waiting for the header of the stats table.
|
|
// It will transit to ReadingTask state once it received a header.
|
|
type schedDebugReaderStateWaitingHeader struct {
|
|
}
|
|
|
|
func (self *schedDebugReaderStateWaitingHeader) isSeparator(line string) bool {
|
|
line = strings.TrimSpace(line)
|
|
if len(line) == 0 {
|
|
return false
|
|
}
|
|
for _, ch := range line {
|
|
if ch != '-' {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (self *schedDebugReaderStateWaitingHeader) Transit(context *schedDebugContext, line string) (schedDebugReaderState, error) {
|
|
if self.isSeparator(line) {
|
|
// A new table of runnable tasks
|
|
context.numCores++
|
|
loadMap := make(map[string][]int, len(context.loadMap))
|
|
for container, loads := range context.loadMap {
|
|
for len(loads) < context.numCores {
|
|
loads = append(loads, 0)
|
|
}
|
|
loadMap[container] = loads
|
|
}
|
|
context.loadMap = loadMap
|
|
ret := &schedDebugReaderStateReadingTasks{}
|
|
return ret, nil
|
|
}
|
|
return self, nil
|
|
}
|
|
|
|
// State: ReadingTasks
|
|
// In this state, the state machine expects that each line contains a stats info of a thread.
|
|
// It will transit to WaitingHeader state once it reads an empty line.
|
|
type schedDebugReaderStateReadingTasks struct {
|
|
}
|
|
|
|
func (self *schedDebugReaderStateReadingTasks) Transit(context *schedDebugContext, line string) (schedDebugReaderState, error) {
|
|
line = strings.TrimSpace(line)
|
|
if len(line) == 0 {
|
|
// End of a table. Waiting for another table.
|
|
ret := &schedDebugReaderStateWaitingHeader{}
|
|
return ret, nil
|
|
}
|
|
fields := strings.Fields(line)
|
|
|
|
// Example of one row:
|
|
// rcuos/2 10 153908711.511953 9697302 120 153908711.511953 462044.084299 4409304806.819551 0 /
|
|
|
|
container := fields[len(fields)-1]
|
|
var loads []int
|
|
ok := false
|
|
if loads, ok = context.loadMap[container]; !ok {
|
|
loads = make([]int, context.numCores)
|
|
}
|
|
for len(loads) < context.numCores {
|
|
loads = append(loads, 0)
|
|
}
|
|
loads[len(loads)-1]++
|
|
context.loadMap[container] = loads
|
|
return self, nil
|
|
}
|