Skip to content

Commit

Permalink
add cpu header
Browse files Browse the repository at this point in the history
  • Loading branch information
inv2004 committed Apr 22, 2022
1 parent c68cfcc commit a9857c1
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 58 deletions.
19 changes: 0 additions & 19 deletions src/ttop.nim
Original file line number Diff line number Diff line change
@@ -1,23 +1,4 @@
# This is just an example to get you started. A typical binary package
# uses this
import ttop/procfs
import ttop/tui
import terminaltables
import strutils

# proc ps(): string =
# let tbl = newUnicodeTable()
# tbl.separateRows = false
# tbl.setHeaders(@["pid", "user", "name", "state", "vsize", "rss", "cpu", "mem", "cmd"])
# for p in pidsInfo():
# tbl.addRow p.row()
# tbl.render()

# proc writeM(str: string, tb: var TerminalBuffer, x, y: int) =
# var yy = y
# for line in str.splitLines(false):
# tb.write(x, yy, line)
# yy.inc()

proc main() =
run()
Expand Down
106 changes: 75 additions & 31 deletions src/ttop/procfs.nim
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import os
import strutils
import posix_utils, posix
import algorithm
import times
import strformat
import tables
import sequtils

const PROCFS = "/proc"
const PROCFSLEN = PROCFS.len
Expand All @@ -26,11 +27,28 @@ type PidInfo = object
state*: string
vsize*: uint
rss*: uint
cpuTime*: int
cpu*: float
mem*: float
cmd*: string
uptimeHz*: int
uptime*: int

type SysInfo = object
datetime*: times.DateTime
cpu*: float
cpus*: seq[float]

proc pidsInfo*(sortOrder = Rss): OrderedTable[uint, PidInfo]
let hz = sysconf(SC_CLK_TCK)
var prev = pidsInfo()
sleep hz

proc sysInfo*(): SysInfo
echo sysInfo()

# quit 1

proc formatT*(ts: int): string =
let d = initDuration(seconds = ts)
let p = d.toParts()
Expand Down Expand Up @@ -69,7 +87,7 @@ proc cut*(str: string, size: int, right: bool, scroll: int): string =
if max >= str.high:
str[^size..max] & ' '
else:
str[scroll..<max] & "."
str[scroll..<max] & "."
else:
if right:
' '.repeat(size - l) & str
Expand All @@ -79,9 +97,9 @@ proc cut*(str: string, size: int, right: bool, scroll: int): string =
proc cut*(i: int | uint, size: int, right: bool, scroll: int): string =
cut($i, size, right, scroll)

proc parseUptime(file: string): int =
let line = readLines(file, 1)[0]
int line.split()[0].parseFloat()
proc parseUptime(): int =
let line = readLines(PROCFS & "/uptime", 1)[0]
int float(hz) * line.split()[0].parseFloat()

proc parseSize(str: string): int =
let normStr = str.strip(true, false)
Expand All @@ -90,8 +108,8 @@ proc parseSize(str: string): int =
else:
parseInt(normStr)

proc parseMemInfo(file: string): MemInfo =
for line in lines(file):
proc parseMemInfo(): MemInfo =
for line in lines(PROCFS & "/meminfo"):
let parts = line.split(":", 1)
case parts[0]
of "MemTotal": result.MemTotal = parseSize(parts[1])
Expand All @@ -100,7 +118,8 @@ proc parseMemInfo(file: string): MemInfo =
of "Buffers": result.Buffers = parseSize(parts[1])
of "Cached": result.Cached = parseSize(parts[1])

proc parseStat(file: string, uptime: int, hz: int, mem: MemInfo, pageSize: uint): PidInfo =
proc parseStat(pid: uint, uptime: int, mem: MemInfo, pageSize: uint): PidInfo =
let file = PROCFS & "/" & $pid & "/stat"
let stat = stat(file)
result.uid = stat.st_uid
let userInfo = getpwuid(result.uid)
Expand All @@ -113,13 +132,17 @@ proc parseStat(file: string, uptime: int, hz: int, mem: MemInfo, pageSize: uint)
result.state = parts[2]
result.vsize = parts[22].parseUInt()
result.rss = pageSize * parts[23].parseUInt()
result.uptime = uptime - parts[21].parseInt() div hz
let totalTime = (parts[13].parseInt() + parts[14].parseInt()) div hz
result.cpu = 100 * total_time / result.uptime
result.uptimeHz = uptime - parts[21].parseInt()
# result.uptimeHz = parts[21].parseInt()
result.uptime = result.uptimeHz div hz
result.cpuTime = parts[13].parseInt() + parts[14].parseInt()
let prevCpuTime = prev.getOrDefault(pid).cpuTime
let prevUptimeHz = prev.getOrDefault(pid).uptimeHz
result.cpu = 100 * (result.cpuTime - prevCpuTime) / (result.uptimeHz - prevUptimeHz)
result.mem = float(100 * result.rss) / float(mem.MemTotal)

proc parsePid(pid: uint, uptime: int, hz: int, mem: MemInfo, pageSize: uint): PidInfo =
result = parseStat(PROCFS & "/" & $pid & "/stat", uptime, hz, mem, pageSize)
proc parsePid(pid: uint, uptime: int, mem: MemInfo, pageSize: uint): PidInfo =
result = parseStat(pid, uptime, mem, pageSize)
result.cmd = readLines(PROCFS & "/" & $pid & "/cmdline", 1)[0].replace('\0', ' ').strip(false, true, {'\0'})

proc pids*(): seq[uint] =
Expand All @@ -131,23 +154,44 @@ proc pids*(): seq[uint] =
except:
discard

proc sortFunc(sort: SortField): auto =
case sort
of Pid: return proc(a, b: PidInfo): int =
cmp a.pid, b.pid
of Name: return proc(a, b: PidInfo): int =
cmp a.name, b.name
of Rss: return proc(a, b: PidInfo): int =
cmp b.rss, a.rss
of Cpu: return proc(a, b: PidInfo): int =
cmp b.cpu, a.cpu

proc pidsInfo*(sort = Rss): seq[PidInfo] =
let hz = sysconf(SC_CLK_TCK)
proc sortFunc(sortOrder: SortField): auto =
case sortOrder
of Pid: return proc(a, b: (uint, PidInfo)): int =
cmp a[1].pid, b[1].pid
of Name: return proc(a, b: (uint, PidInfo)): int =
cmp a[1].name, b[1].name
of Rss: return proc(a, b: (uint, PidInfo)): int =
cmp b[1].rss, a[1].rss
of Cpu: return proc(a, b: (uint, PidInfo)): int =
cmp b[1].cpu, a[1].cpu

proc pidsInfo*(sortOrder = Rss): OrderedTable[uint, PidInfo] =
let pageSize = uint sysconf(SC_PAGESIZE)
let memInfo = parseMemInfo(PROCFS & "/meminfo")
let uptime = parseUptime(PROCFS & "/uptime")
let memInfo = parseMemInfo()
let uptime = parseUptime()
for pid in pids():
result.add parsePid(pid, uptime, hz, memInfo, pageSize)

result.sort sortFunc(sort)
result[pid] = parsePid(pid, uptime, memInfo, pageSize)

if sortOrder != Pid:
sort(result, sortFunc(sortOrder))
prev = result

proc parseStat(): (float, seq[float]) =
for line in lines(PROCFS & "/stat"):
if line.startsWith("cpu"):
let parts = line.split()
var off = 1
if parts[1] == "":
off = 2
let all = parts[off..<off+8].map(parseInt)
let total = all.foldl(a+b)
let idle = all[3] + all[4]
let cpu = 100 * (total - idle) / total
if off == 1:
result[1].add cpu
else:
result[0] = cpu

proc sysInfo*(): SysInfo =
result.datetime = times.now()
(result.cpu, result.cpus) = parseStat()
33 changes: 25 additions & 8 deletions src/ttop/tui.nim
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import os
import procfs
import strutils
import strformat
import tables
import times

proc exitProc() {.noconv.} =
illwillDeinit()
Expand All @@ -14,15 +16,29 @@ const offset = 2
# proc header*(tb TerminalBuffer) =
proc header(tb: var TerminalBuffer) =

tb.write(offset, 1, fgWhite, "Press any key to display its name")
tb.write(offset, 2, "Press ", fgYellow, "ESC", fgWhite,
let sys = sysInfo()

tb.write(offset, 1, fgWhite, "RTC: ", $sys.datetime)
tb.write(offset, 2, fgWhite)
tb.write "CPU: ", sys.cpu.formatF().cut(4, false, 0), " %|"
for i, cpu in sys.cpus:
if i == 0:
tb.write " "
tb.write cpu.formatF().cut(4, true, 0)
tb.write " |%"
tb.write(offset, 3, "Press ", fgYellow, "ESC", fgWhite,
" or ", fgYellow, "Q", fgWhite, " to quit")
# tb.drawHorizLine(offset, tb.width - offset - 1, 3, doubleStyle=false)
tb.write(offset, 3, bgMagenta, fmt"""{"PID":>5} {"USER":<11} {"S":1} {"VIRT":>10} {"RSS":>10} {"MEM%":>5} {"CPU%":>5} {"UP":>8}""", ' '.repeat(tb.width-66), bgNone)

proc table(tb: var TerminalBuffer, curSort: SortField, scrollX, scrollY: int) =
var y = 4
for p in pidsInfo(curSort)[scrollY..^1]:

tb.write(offset, y, bgMagenta, fmt"""{"PID":>5} {"USER":<11} {"S":1} {"VIRT":>10} {"RSS":>10} {"MEM%":>5} {"CPU%":>5} {"UP":>8}""", ' '.repeat(tb.width-66), bgNone)
inc y

for (i, p) in pidsInfo(curSort).pairs:
if (y-4) < scrollY:
continue
tb.setCursorPos offset, y
tb.write p.pid.cut(5, true, scrollX), " "
if p.user == "":
Expand All @@ -32,10 +48,10 @@ proc table(tb: var TerminalBuffer, curSort: SortField, scrollX, scrollY: int) =
tb.write p.state, fgWhite, " "
tb.write p.vsize.formatU().cut(10, true, scrollX), fgWhite, " "
tb.write p.rss.formatU().cut(10, true, scrollX), fgWhite, " "
tb.write p.cpu.formatF().cut(5, true, scrollX), fgWhite, " "
tb.write p.mem.formatF().cut(5, true, scrollX), fgWhite, " "
tb.write p.cpu.formatF().cut(5, true, scrollX), fgWhite, " "
tb.write p.uptime.formatT().cut(8, false, scrollX)
tb.write fgCyan, p.cmd.cut(tb.width - 67, false, scrollX), fgWhite
tb.write fgCyan, p.cmd.cut(tb.width - 68, false, scrollX), fgWhite

inc y
if y > tb.height-2:
Expand Down Expand Up @@ -66,7 +82,7 @@ proc run*() =
while true:
var key = getKey()
if key != Key.None:
tb.write(30, 2, resetStyle, "Key pressed: ", fgGreen, $key)
tb.write(60, 1, resetStyle, "Key pressed: ", fgGreen, $key)
tb.write(30, 2, resetStyle)
case key
of Key.Escape, Key.Q: exitProc()
Expand All @@ -90,7 +106,8 @@ proc run*() =
if refresh == 20:
table(tb, curSort, scrollX, scrollY)
refresh = 0
inc refresh
else:
inc refresh

tb.display()
sleep(100)

0 comments on commit a9857c1

Please sign in to comment.