ttop.go 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. // Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
  2. // Use of this source code is governed by a MIT license that can
  3. // be found in the LICENSE file.
  4. // +build ignore
  5. package main
  6. import (
  7. "bufio"
  8. "errors"
  9. "fmt"
  10. "io"
  11. "os"
  12. "regexp"
  13. "runtime"
  14. "sort"
  15. "strconv"
  16. "strings"
  17. "notabug.org/themusicgod1/termui"
  18. "notabug.org/themusicgod1/termui/extra"
  19. )
  20. const statFilePath = "/proc/stat"
  21. const meminfoFilePath = "/proc/meminfo"
  22. type CpuStat struct {
  23. user float32
  24. nice float32
  25. system float32
  26. idle float32
  27. }
  28. type CpusStats struct {
  29. stat map[string]CpuStat
  30. proc map[string]CpuStat
  31. }
  32. func NewCpusStats(s map[string]CpuStat) *CpusStats {
  33. return &CpusStats{stat: s, proc: make(map[string]CpuStat)}
  34. }
  35. func (cs *CpusStats) String() (ret string) {
  36. for key, _ := range cs.proc {
  37. 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)
  38. }
  39. return
  40. }
  41. func subCpuStat(m CpuStat, s CpuStat) CpuStat {
  42. return CpuStat{user: m.user - s.user,
  43. nice: m.nice - s.nice,
  44. system: m.system - s.system,
  45. idle: m.idle - s.idle}
  46. }
  47. func procCpuStat(c CpuStat) CpuStat {
  48. sum := c.user + c.nice + c.system + c.idle
  49. return CpuStat{user: c.user / sum * 100,
  50. nice: c.nice / sum * 100,
  51. system: c.system / sum * 100,
  52. idle: c.idle / sum * 100}
  53. }
  54. func (cs *CpusStats) tick(ns map[string]CpuStat) {
  55. for key, _ := range cs.stat {
  56. proc := subCpuStat(ns[key], cs.stat[key])
  57. cs.proc[key] = procCpuStat(proc)
  58. cs.stat[key] = ns[key]
  59. }
  60. }
  61. type errIntParser struct {
  62. err error
  63. }
  64. func (eip *errIntParser) parse(s string) (ret int64) {
  65. if eip.err != nil {
  66. return 0
  67. }
  68. ret, eip.err = strconv.ParseInt(s, 10, 0)
  69. return
  70. }
  71. type LineProcessor interface {
  72. process(string) error
  73. finalize() interface{}
  74. }
  75. type CpuLineProcessor struct {
  76. m map[string]CpuStat
  77. }
  78. func (clp *CpuLineProcessor) process(line string) (err error) {
  79. r := regexp.MustCompile("^cpu([0-9]*)")
  80. if r.MatchString(line) {
  81. tab := strings.Fields(line)
  82. if len(tab) < 5 {
  83. err = errors.New("cpu info line has not enough fields")
  84. return
  85. }
  86. parser := errIntParser{}
  87. cs := CpuStat{user: float32(parser.parse(tab[1])),
  88. nice: float32(parser.parse(tab[2])),
  89. system: float32(parser.parse(tab[3])),
  90. idle: float32(parser.parse(tab[4]))}
  91. clp.m[tab[0]] = cs
  92. err = parser.err
  93. if err != nil {
  94. return
  95. }
  96. }
  97. return
  98. }
  99. func (clp *CpuLineProcessor) finalize() interface{} {
  100. return clp.m
  101. }
  102. type MemStat struct {
  103. total int64
  104. free int64
  105. }
  106. func (ms MemStat) String() (ret string) {
  107. ret = fmt.Sprintf("TotalMem: %d, FreeMem: %d\n", ms.total, ms.free)
  108. return
  109. }
  110. func (ms *MemStat) process(line string) (err error) {
  111. rtotal := regexp.MustCompile("^MemTotal:")
  112. rfree := regexp.MustCompile("^MemFree:")
  113. var aux int64
  114. if rtotal.MatchString(line) || rfree.MatchString(line) {
  115. tab := strings.Fields(line)
  116. if len(tab) < 3 {
  117. err = errors.New("mem info line has not enough fields")
  118. return
  119. }
  120. aux, err = strconv.ParseInt(tab[1], 10, 0)
  121. }
  122. if err != nil {
  123. return
  124. }
  125. if rtotal.MatchString(line) {
  126. ms.total = aux
  127. }
  128. if rfree.MatchString(line) {
  129. ms.free = aux
  130. }
  131. return
  132. }
  133. func (ms *MemStat) finalize() interface{} {
  134. return *ms
  135. }
  136. func processFileLines(filePath string, lp LineProcessor) (ret interface{}, err error) {
  137. var statFile *os.File
  138. statFile, err = os.Open(filePath)
  139. if err != nil {
  140. fmt.Printf("open: %v\n", err)
  141. }
  142. defer statFile.Close()
  143. statFileReader := bufio.NewReader(statFile)
  144. for {
  145. var line string
  146. line, err = statFileReader.ReadString('\n')
  147. if err == io.EOF {
  148. err = nil
  149. break
  150. }
  151. if err != nil {
  152. fmt.Printf("open: %v\n", err)
  153. break
  154. }
  155. line = strings.TrimSpace(line)
  156. err = lp.process(line)
  157. }
  158. ret = lp.finalize()
  159. return
  160. }
  161. func getCpusStatsMap() (m map[string]CpuStat, err error) {
  162. var aux interface{}
  163. aux, err = processFileLines(statFilePath, &CpuLineProcessor{m: make(map[string]CpuStat)})
  164. return aux.(map[string]CpuStat), err
  165. }
  166. func getMemStats() (ms MemStat, err error) {
  167. var aux interface{}
  168. aux, err = processFileLines(meminfoFilePath, &MemStat{})
  169. return aux.(MemStat), err
  170. }
  171. type CpuTabElems struct {
  172. GMap map[string]*termui.Gauge
  173. LChart *termui.LineChart
  174. }
  175. func NewCpuTabElems(width int) *CpuTabElems {
  176. lc := termui.NewLineChart()
  177. lc.Width = width
  178. lc.Height = 12
  179. lc.X = 0
  180. lc.Mode = "dot"
  181. lc.BorderLabel = "CPU"
  182. return &CpuTabElems{GMap: make(map[string]*termui.Gauge),
  183. LChart: lc}
  184. }
  185. func (cte *CpuTabElems) AddGauge(key string, Y int, width int) *termui.Gauge {
  186. cte.GMap[key] = termui.NewGauge()
  187. cte.GMap[key].Width = width
  188. cte.GMap[key].Height = 3
  189. cte.GMap[key].Y = Y
  190. cte.GMap[key].BorderLabel = key
  191. cte.GMap[key].Percent = 0 //int(val.user + val.nice + val.system)
  192. return cte.GMap[key]
  193. }
  194. func (cte *CpuTabElems) Update(cs CpusStats) {
  195. for key, val := range cs.proc {
  196. p := int(val.user + val.nice + val.system)
  197. cte.GMap[key].Percent = p
  198. if key == "cpu" {
  199. cte.LChart.Data = append(cte.LChart.Data, 0)
  200. copy(cte.LChart.Data[1:], cte.LChart.Data[0:])
  201. cte.LChart.Data[0] = float64(p)
  202. }
  203. }
  204. }
  205. type MemTabElems struct {
  206. Gauge *termui.Gauge
  207. SLines *termui.Sparklines
  208. }
  209. func NewMemTabElems(width int) *MemTabElems {
  210. g := termui.NewGauge()
  211. g.Width = width
  212. g.Height = 3
  213. g.Y = 0
  214. sline := termui.NewSparkline()
  215. sline.Title = "MEM"
  216. sline.Height = 8
  217. sls := termui.NewSparklines(sline)
  218. sls.Width = width
  219. sls.Height = 12
  220. sls.Y = 3
  221. return &MemTabElems{Gauge: g, SLines: sls}
  222. }
  223. func (mte *MemTabElems) Update(ms MemStat) {
  224. used := int((ms.total - ms.free) * 100 / ms.total)
  225. mte.Gauge.Percent = used
  226. mte.SLines.Lines[0].Data = append(mte.SLines.Lines[0].Data, 0)
  227. copy(mte.SLines.Lines[0].Data[1:], mte.SLines.Lines[0].Data[0:])
  228. mte.SLines.Lines[0].Data[0] = used
  229. if len(mte.SLines.Lines[0].Data) > mte.SLines.Width-2 {
  230. mte.SLines.Lines[0].Data = mte.SLines.Lines[0].Data[0 : mte.SLines.Width-2]
  231. }
  232. }
  233. func main() {
  234. if runtime.GOOS != "linux" {
  235. panic("Currently works only on Linux")
  236. }
  237. err := termui.Init()
  238. if err != nil {
  239. panic(err)
  240. }
  241. defer termui.Close()
  242. termWidth := 70
  243. //termui.UseTheme("helloworld")
  244. header := termui.NewPar("Press q to quit, Press j or k to switch tabs")
  245. header.Height = 1
  246. header.Width = 50
  247. header.Border = false
  248. header.TextBgColor = termui.ColorBlue
  249. tabCpu := extra.NewTab("CPU")
  250. tabMem := extra.NewTab("MEM")
  251. tabpane := extra.NewTabpane()
  252. tabpane.Y = 1
  253. tabpane.Width = 30
  254. tabpane.Border = false
  255. cs, errcs := getCpusStatsMap()
  256. cpusStats := NewCpusStats(cs)
  257. if errcs != nil {
  258. panic("error")
  259. }
  260. cpuTabElems := NewCpuTabElems(termWidth)
  261. Y := 0
  262. cpuKeys := make([]string, 0, len(cs))
  263. for key := range cs {
  264. cpuKeys = append(cpuKeys, key)
  265. }
  266. sort.Strings(cpuKeys)
  267. for _, key := range cpuKeys {
  268. g := cpuTabElems.AddGauge(key, Y, termWidth)
  269. Y += 3
  270. tabCpu.AddBlocks(g)
  271. }
  272. cpuTabElems.LChart.Y = Y
  273. tabCpu.AddBlocks(cpuTabElems.LChart)
  274. memTabElems := NewMemTabElems(termWidth)
  275. ms, errm := getMemStats()
  276. if errm != nil {
  277. panic(errm)
  278. }
  279. memTabElems.Update(ms)
  280. tabMem.AddBlocks(memTabElems.Gauge)
  281. tabMem.AddBlocks(memTabElems.SLines)
  282. tabpane.SetTabs(*tabCpu, *tabMem)
  283. termui.Render(header, tabpane)
  284. termui.Handle("/sys/kbd/q", func(termui.Event) {
  285. termui.StopLoop()
  286. })
  287. termui.Handle("/sys/kbd/j", func(termui.Event) {
  288. tabpane.SetActiveLeft()
  289. termui.Render(header, tabpane)
  290. })
  291. termui.Handle("/sys/kbd/k", func(termui.Event) {
  292. tabpane.SetActiveRight()
  293. termui.Render(header, tabpane)
  294. })
  295. termui.Handle("/timer/1s", func(e termui.Event) {
  296. cs, errcs := getCpusStatsMap()
  297. if errcs != nil {
  298. panic(errcs)
  299. }
  300. cpusStats.tick(cs)
  301. cpuTabElems.Update(*cpusStats)
  302. ms, errm := getMemStats()
  303. if errm != nil {
  304. panic(errm)
  305. }
  306. memTabElems.Update(ms)
  307. termui.Render(header, tabpane)
  308. })
  309. termui.Loop()
  310. }