github.com/hhrutter/nomad@v0.6.0-rc2.0.20170723054333-80c4b03f0705/client/stats/host.go (about)

     1  package stats
     2  
     3  import (
     4  	"log"
     5  	"math"
     6  	"runtime"
     7  	"sync"
     8  	"time"
     9  
    10  	"github.com/shirou/gopsutil/cpu"
    11  	"github.com/shirou/gopsutil/disk"
    12  	"github.com/shirou/gopsutil/host"
    13  	"github.com/shirou/gopsutil/mem"
    14  
    15  	shelpers "github.com/hashicorp/nomad/helper/stats"
    16  )
    17  
    18  // HostStats represents resource usage stats of the host running a Nomad client
    19  type HostStats struct {
    20  	Memory           *MemoryStats
    21  	CPU              []*CPUStats
    22  	DiskStats        []*DiskStats
    23  	AllocDirStats    *DiskStats
    24  	Uptime           uint64
    25  	Timestamp        int64
    26  	CPUTicksConsumed float64
    27  }
    28  
    29  // MemoryStats represnts stats related to virtual memory usage
    30  type MemoryStats struct {
    31  	Total     uint64
    32  	Available uint64
    33  	Used      uint64
    34  	Free      uint64
    35  }
    36  
    37  // CPUStats represents stats related to cpu usage
    38  type CPUStats struct {
    39  	CPU    string
    40  	User   float64
    41  	System float64
    42  	Idle   float64
    43  	Total  float64
    44  }
    45  
    46  // DiskStats represents stats related to disk usage
    47  type DiskStats struct {
    48  	Device            string
    49  	Mountpoint        string
    50  	Size              uint64
    51  	Used              uint64
    52  	Available         uint64
    53  	UsedPercent       float64
    54  	InodesUsedPercent float64
    55  }
    56  
    57  // NodeStatsCollector is an interface which is used for the puproses of mocking
    58  // the HostStatsCollector in the tests
    59  type NodeStatsCollector interface {
    60  	Collect() error
    61  	Stats() *HostStats
    62  }
    63  
    64  // HostStatsCollector collects host resource usage stats
    65  type HostStatsCollector struct {
    66  	clkSpeed        float64
    67  	numCores        int
    68  	statsCalculator map[string]*HostCpuStatsCalculator
    69  	logger          *log.Logger
    70  	hostStats       *HostStats
    71  	hostStatsLock   sync.RWMutex
    72  	allocDir        string
    73  }
    74  
    75  // NewHostStatsCollector returns a HostStatsCollector. The allocDir is passed in
    76  // so that we can present the disk related statistics for the mountpoint where
    77  // the allocation directory lives
    78  func NewHostStatsCollector(logger *log.Logger, allocDir string) *HostStatsCollector {
    79  	numCores := runtime.NumCPU()
    80  	statsCalculator := make(map[string]*HostCpuStatsCalculator)
    81  	collector := &HostStatsCollector{
    82  		statsCalculator: statsCalculator,
    83  		numCores:        numCores,
    84  		logger:          logger,
    85  		allocDir:        allocDir,
    86  	}
    87  	return collector
    88  }
    89  
    90  // Collect collects stats related to resource usage of a host
    91  func (h *HostStatsCollector) Collect() error {
    92  	hs := &HostStats{Timestamp: time.Now().UTC().UnixNano()}
    93  	memStats, err := mem.VirtualMemory()
    94  	if err != nil {
    95  		return err
    96  	}
    97  	hs.Memory = &MemoryStats{
    98  		Total:     memStats.Total,
    99  		Available: memStats.Available,
   100  		Used:      memStats.Used,
   101  		Free:      memStats.Free,
   102  	}
   103  
   104  	ticksConsumed := 0.0
   105  	cpuStats, err := cpu.Times(true)
   106  	if err != nil {
   107  		return err
   108  	}
   109  	cs := make([]*CPUStats, len(cpuStats))
   110  	for idx, cpuStat := range cpuStats {
   111  		percentCalculator, ok := h.statsCalculator[cpuStat.CPU]
   112  		if !ok {
   113  			percentCalculator = NewHostCpuStatsCalculator()
   114  			h.statsCalculator[cpuStat.CPU] = percentCalculator
   115  		}
   116  		idle, user, system, total := percentCalculator.Calculate(cpuStat)
   117  		cs[idx] = &CPUStats{
   118  			CPU:    cpuStat.CPU,
   119  			User:   user,
   120  			System: system,
   121  			Idle:   idle,
   122  			Total:  total,
   123  		}
   124  		ticksConsumed += (total / 100) * (shelpers.TotalTicksAvailable() / float64(len(cpuStats)))
   125  	}
   126  	hs.CPU = cs
   127  	hs.CPUTicksConsumed = ticksConsumed
   128  
   129  	partitions, err := disk.Partitions(false)
   130  	if err != nil {
   131  		return err
   132  	}
   133  	var diskStats []*DiskStats
   134  	for _, partition := range partitions {
   135  		usage, err := disk.Usage(partition.Mountpoint)
   136  		if err != nil {
   137  			h.logger.Printf("[WARN] client: error fetching host disk usage stats for %v: %v", partition.Mountpoint, err)
   138  			continue
   139  		}
   140  		ds := h.toDiskStats(usage, &partition)
   141  		diskStats = append(diskStats, ds)
   142  	}
   143  	hs.DiskStats = diskStats
   144  
   145  	// Getting the disk stats for the allocation directory
   146  	usage, err := disk.Usage(h.allocDir)
   147  	if err != nil {
   148  		return err
   149  	}
   150  	hs.AllocDirStats = h.toDiskStats(usage, nil)
   151  
   152  	uptime, err := host.Uptime()
   153  	if err != nil {
   154  		return err
   155  	}
   156  	hs.Uptime = uptime
   157  
   158  	h.hostStatsLock.Lock()
   159  	h.hostStats = hs
   160  	h.hostStatsLock.Unlock()
   161  	return nil
   162  }
   163  
   164  // Stats returns the host stats that has been collected
   165  func (h *HostStatsCollector) Stats() *HostStats {
   166  	h.hostStatsLock.RLock()
   167  	defer h.hostStatsLock.RUnlock()
   168  	return h.hostStats
   169  }
   170  
   171  // toDiskStats merges UsageStat and PartitionStat to create a DiskStat
   172  func (h *HostStatsCollector) toDiskStats(usage *disk.UsageStat, partitionStat *disk.PartitionStat) *DiskStats {
   173  	if usage == nil {
   174  		return nil
   175  	}
   176  	ds := DiskStats{
   177  		Size:              usage.Total,
   178  		Used:              usage.Used,
   179  		Available:         usage.Free,
   180  		UsedPercent:       usage.UsedPercent,
   181  		InodesUsedPercent: usage.InodesUsedPercent,
   182  	}
   183  	if math.IsNaN(ds.UsedPercent) {
   184  		ds.UsedPercent = 0.0
   185  	}
   186  	if math.IsNaN(ds.InodesUsedPercent) {
   187  		ds.InodesUsedPercent = 0.0
   188  	}
   189  
   190  	if partitionStat != nil {
   191  		ds.Device = partitionStat.Device
   192  		ds.Mountpoint = partitionStat.Mountpoint
   193  	}
   194  
   195  	return &ds
   196  }
   197  
   198  // HostCpuStatsCalculator calculates cpu usage percentages
   199  type HostCpuStatsCalculator struct {
   200  	prevIdle   float64
   201  	prevUser   float64
   202  	prevSystem float64
   203  	prevBusy   float64
   204  	prevTotal  float64
   205  }
   206  
   207  // NewHostCpuStatsCalculator returns a HostCpuStatsCalculator
   208  func NewHostCpuStatsCalculator() *HostCpuStatsCalculator {
   209  	return &HostCpuStatsCalculator{}
   210  }
   211  
   212  // Calculate calculates the current cpu usage percentages
   213  func (h *HostCpuStatsCalculator) Calculate(times cpu.TimesStat) (idle float64, user float64, system float64, total float64) {
   214  	currentIdle := times.Idle
   215  	currentUser := times.User
   216  	currentSystem := times.System
   217  	currentTotal := times.Total()
   218  
   219  	deltaTotal := currentTotal - h.prevTotal
   220  	idle = ((currentIdle - h.prevIdle) / deltaTotal) * 100
   221  	user = ((currentUser - h.prevUser) / deltaTotal) * 100
   222  	system = ((currentSystem - h.prevSystem) / deltaTotal) * 100
   223  
   224  	currentBusy := times.User + times.System + times.Nice + times.Iowait + times.Irq +
   225  		times.Softirq + times.Steal + times.Guest + times.GuestNice + times.Stolen
   226  
   227  	total = ((currentBusy - h.prevBusy) / deltaTotal) * 100
   228  
   229  	h.prevIdle = currentIdle
   230  	h.prevUser = currentUser
   231  	h.prevSystem = currentSystem
   232  	h.prevTotal = currentTotal
   233  	h.prevBusy = currentBusy
   234  
   235  	return
   236  }