123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370 |
- // Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
- // Use of this source code is governed by a MIT license that can
- // be found in the LICENSE file.
- // +build ignore
- package main
- import (
- "bufio"
- "errors"
- "fmt"
- "io"
- "os"
- "regexp"
- "runtime"
- "sort"
- "strconv"
- "strings"
- "notabug.org/themusicgod1/termui"
- "notabug.org/themusicgod1/termui/extra"
- )
- const statFilePath = "/proc/stat"
- const meminfoFilePath = "/proc/meminfo"
- type CpuStat struct {
- user float32
- nice float32
- system float32
- idle float32
- }
- type CpusStats struct {
- stat map[string]CpuStat
- proc map[string]CpuStat
- }
- func NewCpusStats(s map[string]CpuStat) *CpusStats {
- return &CpusStats{stat: s, proc: make(map[string]CpuStat)}
- }
- func (cs *CpusStats) String() (ret string) {
- for key, _ := range cs.proc {
- ret += fmt.Sprintf("%s: %.2f %.2f %.2f %.2f\n", key, cs.proc[key].user, cs.proc[key].nice, cs.proc[key].system, cs.proc[key].idle)
- }
- return
- }
- func subCpuStat(m CpuStat, s CpuStat) CpuStat {
- return CpuStat{user: m.user - s.user,
- nice: m.nice - s.nice,
- system: m.system - s.system,
- idle: m.idle - s.idle}
- }
- func procCpuStat(c CpuStat) CpuStat {
- sum := c.user + c.nice + c.system + c.idle
- return CpuStat{user: c.user / sum * 100,
- nice: c.nice / sum * 100,
- system: c.system / sum * 100,
- idle: c.idle / sum * 100}
- }
- func (cs *CpusStats) tick(ns map[string]CpuStat) {
- for key, _ := range cs.stat {
- proc := subCpuStat(ns[key], cs.stat[key])
- cs.proc[key] = procCpuStat(proc)
- cs.stat[key] = ns[key]
- }
- }
- type errIntParser struct {
- err error
- }
- func (eip *errIntParser) parse(s string) (ret int64) {
- if eip.err != nil {
- return 0
- }
- ret, eip.err = strconv.ParseInt(s, 10, 0)
- return
- }
- type LineProcessor interface {
- process(string) error
- finalize() interface{}
- }
- type CpuLineProcessor struct {
- m map[string]CpuStat
- }
- func (clp *CpuLineProcessor) process(line string) (err error) {
- r := regexp.MustCompile("^cpu([0-9]*)")
- if r.MatchString(line) {
- tab := strings.Fields(line)
- if len(tab) < 5 {
- err = errors.New("cpu info line has not enough fields")
- return
- }
- parser := errIntParser{}
- cs := CpuStat{user: float32(parser.parse(tab[1])),
- nice: float32(parser.parse(tab[2])),
- system: float32(parser.parse(tab[3])),
- idle: float32(parser.parse(tab[4]))}
- clp.m[tab[0]] = cs
- err = parser.err
- if err != nil {
- return
- }
- }
- return
- }
- func (clp *CpuLineProcessor) finalize() interface{} {
- return clp.m
- }
- type MemStat struct {
- total int64
- free int64
- }
- func (ms MemStat) String() (ret string) {
- ret = fmt.Sprintf("TotalMem: %d, FreeMem: %d\n", ms.total, ms.free)
- return
- }
- func (ms *MemStat) process(line string) (err error) {
- rtotal := regexp.MustCompile("^MemTotal:")
- rfree := regexp.MustCompile("^MemFree:")
- var aux int64
- if rtotal.MatchString(line) || rfree.MatchString(line) {
- tab := strings.Fields(line)
- if len(tab) < 3 {
- err = errors.New("mem info line has not enough fields")
- return
- }
- aux, err = strconv.ParseInt(tab[1], 10, 0)
- }
- if err != nil {
- return
- }
- if rtotal.MatchString(line) {
- ms.total = aux
- }
- if rfree.MatchString(line) {
- ms.free = aux
- }
- return
- }
- func (ms *MemStat) finalize() interface{} {
- return *ms
- }
- func processFileLines(filePath string, lp LineProcessor) (ret interface{}, err error) {
- var statFile *os.File
- statFile, err = os.Open(filePath)
- if err != nil {
- fmt.Printf("open: %v\n", err)
- }
- defer statFile.Close()
- statFileReader := bufio.NewReader(statFile)
- for {
- var line string
- line, err = statFileReader.ReadString('\n')
- if err == io.EOF {
- err = nil
- break
- }
- if err != nil {
- fmt.Printf("open: %v\n", err)
- break
- }
- line = strings.TrimSpace(line)
- err = lp.process(line)
- }
- ret = lp.finalize()
- return
- }
- func getCpusStatsMap() (m map[string]CpuStat, err error) {
- var aux interface{}
- aux, err = processFileLines(statFilePath, &CpuLineProcessor{m: make(map[string]CpuStat)})
- return aux.(map[string]CpuStat), err
- }
- func getMemStats() (ms MemStat, err error) {
- var aux interface{}
- aux, err = processFileLines(meminfoFilePath, &MemStat{})
- return aux.(MemStat), err
- }
- type CpuTabElems struct {
- GMap map[string]*termui.Gauge
- LChart *termui.LineChart
- }
- func NewCpuTabElems(width int) *CpuTabElems {
- lc := termui.NewLineChart()
- lc.Width = width
- lc.Height = 12
- lc.X = 0
- lc.Mode = "dot"
- lc.BorderLabel = "CPU"
- return &CpuTabElems{GMap: make(map[string]*termui.Gauge),
- LChart: lc}
- }
- func (cte *CpuTabElems) AddGauge(key string, Y int, width int) *termui.Gauge {
- cte.GMap[key] = termui.NewGauge()
- cte.GMap[key].Width = width
- cte.GMap[key].Height = 3
- cte.GMap[key].Y = Y
- cte.GMap[key].BorderLabel = key
- cte.GMap[key].Percent = 0 //int(val.user + val.nice + val.system)
- return cte.GMap[key]
- }
- func (cte *CpuTabElems) Update(cs CpusStats) {
- for key, val := range cs.proc {
- p := int(val.user + val.nice + val.system)
- cte.GMap[key].Percent = p
- if key == "cpu" {
- cte.LChart.Data = append(cte.LChart.Data, 0)
- copy(cte.LChart.Data[1:], cte.LChart.Data[0:])
- cte.LChart.Data[0] = float64(p)
- }
- }
- }
- type MemTabElems struct {
- Gauge *termui.Gauge
- SLines *termui.Sparklines
- }
- func NewMemTabElems(width int) *MemTabElems {
- g := termui.NewGauge()
- g.Width = width
- g.Height = 3
- g.Y = 0
- sline := termui.NewSparkline()
- sline.Title = "MEM"
- sline.Height = 8
- sls := termui.NewSparklines(sline)
- sls.Width = width
- sls.Height = 12
- sls.Y = 3
- return &MemTabElems{Gauge: g, SLines: sls}
- }
- func (mte *MemTabElems) Update(ms MemStat) {
- used := int((ms.total - ms.free) * 100 / ms.total)
- mte.Gauge.Percent = used
- mte.SLines.Lines[0].Data = append(mte.SLines.Lines[0].Data, 0)
- copy(mte.SLines.Lines[0].Data[1:], mte.SLines.Lines[0].Data[0:])
- mte.SLines.Lines[0].Data[0] = used
- if len(mte.SLines.Lines[0].Data) > mte.SLines.Width-2 {
- mte.SLines.Lines[0].Data = mte.SLines.Lines[0].Data[0 : mte.SLines.Width-2]
- }
- }
- func main() {
- if runtime.GOOS != "linux" {
- panic("Currently works only on Linux")
- }
- err := termui.Init()
- if err != nil {
- panic(err)
- }
- defer termui.Close()
- termWidth := 70
- //termui.UseTheme("helloworld")
- header := termui.NewPar("Press q to quit, Press j or k to switch tabs")
- header.Height = 1
- header.Width = 50
- header.Border = false
- header.TextBgColor = termui.ColorBlue
- tabCpu := extra.NewTab("CPU")
- tabMem := extra.NewTab("MEM")
- tabpane := extra.NewTabpane()
- tabpane.Y = 1
- tabpane.Width = 30
- tabpane.Border = false
- cs, errcs := getCpusStatsMap()
- cpusStats := NewCpusStats(cs)
- if errcs != nil {
- panic("error")
- }
- cpuTabElems := NewCpuTabElems(termWidth)
- Y := 0
- cpuKeys := make([]string, 0, len(cs))
- for key := range cs {
- cpuKeys = append(cpuKeys, key)
- }
- sort.Strings(cpuKeys)
- for _, key := range cpuKeys {
- g := cpuTabElems.AddGauge(key, Y, termWidth)
- Y += 3
- tabCpu.AddBlocks(g)
- }
- cpuTabElems.LChart.Y = Y
- tabCpu.AddBlocks(cpuTabElems.LChart)
- memTabElems := NewMemTabElems(termWidth)
- ms, errm := getMemStats()
- if errm != nil {
- panic(errm)
- }
- memTabElems.Update(ms)
- tabMem.AddBlocks(memTabElems.Gauge)
- tabMem.AddBlocks(memTabElems.SLines)
- tabpane.SetTabs(*tabCpu, *tabMem)
- termui.Render(header, tabpane)
- termui.Handle("/sys/kbd/q", func(termui.Event) {
- termui.StopLoop()
- })
- termui.Handle("/sys/kbd/j", func(termui.Event) {
- tabpane.SetActiveLeft()
- termui.Render(header, tabpane)
- })
- termui.Handle("/sys/kbd/k", func(termui.Event) {
- tabpane.SetActiveRight()
- termui.Render(header, tabpane)
- })
- termui.Handle("/timer/1s", func(e termui.Event) {
- cs, errcs := getCpusStatsMap()
- if errcs != nil {
- panic(errcs)
- }
- cpusStats.tick(cs)
- cpuTabElems.Update(*cpusStats)
- ms, errm := getMemStats()
- if errm != nil {
- panic(errm)
- }
- memTabElems.Update(ms)
- termui.Render(header, tabpane)
- })
- termui.Loop()
- }
|