Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
stapelberg committed Jun 27, 2020
0 parents commit a0095d8
Show file tree
Hide file tree
Showing 17 changed files with 1,077 additions and 0 deletions.
24 changes: 24 additions & 0 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: CI

on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

jobs:
testandinstall:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2

- name: Ensure all files were formatted as per gofmt
run: |
gofmt -l $(find . -name '*.go') >/dev/null
- name: run tests
run: go test ./...

- name: install binaries
run: go install github.com/gokrazy/stat/cmd/...
Binary file added 2020-06-27-midna-webstat.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added 2020-06-27-scan2drive-scan-gokrazy-stat.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 27 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
Copyright (c) 2017 the gokrazy authors. All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of gokrazy nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# gokrazy/stat

`gokrazy/stat` is a program to visualize resource usage, producing output that
looks very similar to [Dag Wieers’s
`dstat`](https://github.com/dstat-real/dstat) default output.

As the repository path implies, this program is meant to be used on the
https://gokrazy.org/ Raspberry Pi platform, so it does not have any external
runtime dependencies and is implemented natively in Go (not using any cgo). The
resulting static binary might come in handy in other Linux environments, too!


## github.com/gokrazy/stat/cmd/gokr-webstat

`gokr-webstat` displays system resource usage in your browser, with output
matching `gokr-stat` or `dstat`!

It uses the [EventSource
API](https://developer.mozilla.org/en-US/docs/Web/API/EventSource) to stream new
lines into your browser.

![webstat screenshot](2020-06-27-midna-webstat.jpg)

## github.com/gokrazy/stat/cmd/gokr-stat

The `gokr-stat` program is a terminal program that displays system resource
usage, like `dstat`.

Here is the `gokr-stat` terminal output (via
[`gokrazy/breakglass`](https://github.com/gokrazy/breakglass)) when scanning one
double-sided piece of A4 paper with
[scan2drive](https://github.com/stapelberg/scan2drive) on a Raspberry Pi 4:

![stat terminal output](2020-06-27-scan2drive-scan-gokrazy-stat.jpg)
167 changes: 167 additions & 0 deletions cmd/gokr-stat/stat.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package main

import (
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"os/signal"
"strings"
"sync"
"syscall"
"time"
"unsafe"

"github.com/gokrazy/stat"
"github.com/gokrazy/stat/internal/cpu"
"github.com/gokrazy/stat/internal/disk"
"github.com/gokrazy/stat/internal/mem"
"github.com/gokrazy/stat/internal/net"
"github.com/gokrazy/stat/internal/sys"
)

func formatCols(cols []stat.Col) string {
formatted := make([]string, len(cols))
for idx, col := range cols {
formatted[idx] = col.String()
}
return strings.Join(formatted, " ")
}

const (
TIOCGWINSZ = 0x5413
)

type window struct {
Row uint16
Col uint16
Xpixel uint16
Ypixel uint16
}

func terminalSize() (*window, error) {
w := new(window)
tio := syscall.TIOCGWINSZ
res, _, err := syscall.Syscall(syscall.SYS_IOCTL,
uintptr(syscall.Stdin),
uintptr(tio),
uintptr(unsafe.Pointer(w)),
)
if int(res) == -1 {
if err == syscall.ENOTTY {
return &window{Row: 80}, nil
}
return nil, err
}
return w, nil
}

func printStats() error {
flag.Parse()

ts, err := terminalSize()
if err != nil {
return err
}
var rowsMu sync.Mutex
rows := int(ts.Row) - 1
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGWINCH)
go func() {
for range ch {
ts, err := terminalSize()
if err != nil {
log.Print(err)
continue
}
rowsMu.Lock()
rows = int(ts.Row) - 1
rowsMu.Unlock()
}
}()

type processAndFormatter interface {
ProcessAndFormat(map[string][]byte) []stat.Col
}
modules := []processAndFormatter{
&cpu.Stats{},
&disk.Stats{},
&sys.Stats{},
&net.Stats{},
&mem.Stats{},
}
header := func() {
const blue = "\033[1;34m"
fmt.Printf(blue + "usr sys idl wai stl | ")
fmt.Printf("_read _writ | ")
fmt.Printf("_int_ _csw_ | ")
fmt.Printf("_recv _send | ")
fmt.Printf("_used _free _buff _cach\n")
}
parts := make([]string, len(modules))
files := make(map[string]*os.File)
for _, mod := range modules {
// When a stats module implements the FileContents() interface, we
// ensure all returned file contents are read and passed to
// ProcessAndFormat.
fc, ok := mod.(interface{ FileContents() []string })
if !ok {
continue
}
for _, f := range fc.FileContents() {
if _, ok := files[f]; ok {
continue // already requested
}
fl, err := os.Open(f)
if err != nil {
return err
}
files[f] = fl
}
}
for i := 0; ; i++ {
rowsMu.Lock()
showHeader := i%(int(rows)-1) == 0
rowsMu.Unlock()
if showHeader {
header()
}
contents := make(map[string][]byte)
for path, fl := range files {
if _, err := fl.Seek(0, io.SeekStart); err != nil {
return err
}
b, err := ioutil.ReadAll(fl)
if err != nil {
return err
}
contents[path] = b
}

for idx, mod := range modules {
parts[idx] = formatCols(mod.ProcessAndFormat(contents))
}

if i > 0 {
const darkblue = "\033[0;34m"
fmt.Println(strings.Join(parts, darkblue+" | "))
// TODO: clear colors at the end of line so that program can be interrupted
}

time.Sleep(1 * time.Second)
}
}

func main() {
if os.Getenv("GOKRAZY_FIRST_START") == "1" {
// Do not supervise this process: it is meant for interactive usage on a
// terminal. If you are looking for a daemon, use gokr-webstat instead.
os.Exit(125)
}

if err := printStats(); err != nil {
log.Fatal(err)
}
}
73 changes: 73 additions & 0 deletions cmd/gokr-webstat/statustmpl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package main

const statusTmpl = `<!DOCTYPE html>
<html>
<head>
<title>{{ .Hostname }} - webstat</title>
<style type="text/css">
body {
background-color: #eee;
}
#readings {
background-color: black;
border: 1px solid grey;
font-family: monospace;
}
th {
color: white;
text-align: left;
}
</style>
</head>
<body>
<h1>{{ .Hostname }} - webstat</h1>
<table id="readings">
<thead>
<tr>
{{ range $idx, $val := .Headers }}
<th>{{ $val }}</th>
{{ end }}
</tr>
</thead>
<tbody>
<tr><td>&nbsp;</td></tr>
<tr><td>&nbsp;</td></tr>
<tr><td>&nbsp;</td></tr>
<tr><td>&nbsp;</td></tr>
<tr><td>&nbsp;</td></tr>
<tr><td>&nbsp;</td></tr>
<tr><td>&nbsp;</td></tr>
<tr><td>&nbsp;</td></tr>
<tr><td>&nbsp;</td></tr>
<tr><td>&nbsp;</td></tr>
<tr><td>&nbsp;</td></tr>
<tr><td>&nbsp;</td></tr>
<tr><td>&nbsp;</td></tr>
<tr><td>&nbsp;</td></tr>
<tr><td>&nbsp;</td></tr>
<tr><td>&nbsp;</td></tr>
<tr><td>&nbsp;</td></tr>
<tr><td>&nbsp;</td></tr>
<tr><td>&nbsp;</td></tr>
<tr><td>&nbsp;</td></tr>
</tbody>
</table>
<script type="text/javascript">
var readingstbody = document.getElementById('readings').children[1];
var readings = new EventSource('/readings');
readings.onmessage = function(e) {
var parts = JSON.parse(e.data);
var innerHTML = '';
for (part of parts) {
innerHTML += part;
}
for (var i = 0; i < 19; i++) {
// copy from i+1 to i, overwriting the oldest entry
readingstbody.children[i].innerHTML = readingstbody.children[i+1].innerHTML
}
readingstbody.children[19].innerHTML = innerHTML;
}
</script>
</body>
</html>`
Loading

0 comments on commit a0095d8

Please sign in to comment.