...

Source file src/golang.conradwood.net/go-easyops/linux/loadavg.go

Documentation: golang.conradwood.net/go-easyops/linux

     1  package linux
     2  
     3  import (
     4  	"bufio"
     5  	"context"
     6  	"fmt"
     7  	"golang.conradwood.net/go-easyops/prometheus"
     8  	"golang.conradwood.net/apis/common"
     9  	"os"
    10  	"runtime"
    11  	"strconv"
    12  	"strings"
    13  	"sync"
    14  	"time"
    15  )
    16  
    17  const (
    18  	READ_MILLIS = 5000
    19  )
    20  
    21  var (
    22  	loadGauge = prometheus.NewGaugeVec(
    23  		prometheus.GaugeOpts{
    24  			Name: "goeasyops_cpu_percent",
    25  			Help: "ticks spent per type in a cpu",
    26  		},
    27  		[]string{"type"},
    28  	)
    29  	ctr           = 0
    30  	proclock      sync.Mutex
    31  	procstat      = make([]uint64, 10)
    32  	procstat_diff = make([]uint64, 10)
    33  	total         uint64
    34  )
    35  
    36  func init() {
    37  	prometheus.MustRegister(loadGauge)
    38  	go load_calc_loop()
    39  }
    40  
    41  type Loadavg struct {
    42  }
    43  
    44  func (g *Loadavg) GetCPULoad(ctx context.Context, req *common.Void) (*common.CPULoad, error) {
    45  	if ctr < 3 {
    46  		return nil, fmt.Errorf("not ready yet - initialising. try later")
    47  	}
    48  	res := &common.CPULoad{}
    49  	file, err := os.Open("/proc/loadavg")
    50  	if err != nil {
    51  		return nil, err
    52  	}
    53  	defer file.Close()
    54  
    55  	var line string
    56  	scanner := bufio.NewScanner(file)
    57  	for scanner.Scan() {
    58  		line = scanner.Text()
    59  		break
    60  	}
    61  
    62  	if err := scanner.Err(); err != nil {
    63  		return nil, err
    64  	}
    65  	avgs := strings.Split(line, " ")
    66  	if len(avgs) < 3 {
    67  		return nil, fmt.Errorf("Invalid line read from loadavg: \"%s\" (splits into %d parts)", line, len(avgs))
    68  	}
    69  	res.Avg1, err = strconv.ParseFloat(avgs[0], 32)
    70  	if err != nil {
    71  		return nil, err
    72  	}
    73  
    74  	res.Avg5, err = strconv.ParseFloat(avgs[1], 32)
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  
    79  	res.Avg15, err = strconv.ParseFloat(avgs[2], 32)
    80  	if err != nil {
    81  		return nil, err
    82  	}
    83  
    84  	// get number of cpus
    85  	res.CPUCount = uint32(runtime.NumCPU())
    86  	res.PerCPU = res.Avg1 / float64(res.CPUCount)
    87  	proclock.Lock()
    88  	res.Sum = 0
    89  	for _, r := range procstat_diff {
    90  		res.Sum = res.Sum + r
    91  	}
    92  	res.RawSum = total
    93  	res.User = procstat_diff[0]
    94  	res.Nice = procstat_diff[1]
    95  	res.System = procstat_diff[2]
    96  	res.Idle = procstat_diff[3]
    97  	res.IOWait = procstat_diff[4]
    98  	res.IRQ = procstat_diff[5]
    99  	res.SoftIRQ = procstat_diff[6]
   100  	proclock.Unlock()
   101  	res.IdleTime = float64(res.Idle) / float64(res.Sum) * 100
   102  	return res, nil
   103  }
   104  
   105  func load_calc_loop() {
   106  	def := time.Duration(READ_MILLIS) * time.Millisecond
   107  	for {
   108  		if ctr < 3 { // fast startup
   109  			time.Sleep(time.Duration(100) * time.Millisecond)
   110  			ctr++
   111  		} else {
   112  			time.Sleep(def)
   113  		}
   114  		err := load_calc()
   115  		if err != nil {
   116  			fmt.Printf("Failed to process /proc/stat: %s\n", err)
   117  			continue
   118  		}
   119  	}
   120  }
   121  
   122  // we read the usage regularly and compare with previous reading so to get result
   123  func load_calc() error {
   124  	// scan short-term utilisation
   125  
   126  	file, err := os.Open("/proc/stat")
   127  	if err != nil {
   128  		return err
   129  	}
   130  	defer file.Close()
   131  
   132  	scanner := bufio.NewScanner(file)
   133  	var line string
   134  	for scanner.Scan() {
   135  		line = scanner.Text()
   136  		break
   137  	}
   138  
   139  	if err := scanner.Err(); err != nil {
   140  		return err
   141  	}
   142  	ns := strings.Fields(line)
   143  	if len(ns) < 8 {
   144  		return fmt.Errorf("invalid /proc/stat: %s (%d nums)\n", line, len(ns))
   145  	}
   146  	var nums []uint64
   147  	for i, n := range ns {
   148  		if i == 0 {
   149  			continue
   150  		}
   151  		num, err := strconv.ParseUint(n, 10, 64)
   152  		if err != nil {
   153  			return err
   154  		}
   155  		nums = append(nums, num)
   156  	}
   157  	proclock.Lock()
   158  	total = 0
   159  	for i, n := range nums {
   160  		total = total + n
   161  		procstat_diff[i] = n - procstat[i]
   162  		procstat[i] = n
   163  	}
   164  	proclock.Unlock()
   165  	dt := uint64(0)
   166  	for _, r := range procstat_diff {
   167  		dt = dt + r
   168  	}
   169  	gauge("idle", float64(procstat_diff[3])/float64(dt)*100)
   170  	gauge("busy", 100.0-(float64(procstat_diff[3])/float64(dt)*100))
   171  	return nil
   172  }
   173  
   174  func gauge(name string, perc float64) {
   175  	loadGauge.With(prometheus.Labels{"type": name}).Set(perc)
   176  }
   177  

View as plain text