Skip to content

Instantly share code, notes, and snippets.

@fuzzy
Last active August 1, 2019 06:22
Show Gist options
  • Save fuzzy/a606f429fb80fa6f17e2c54770b7de46 to your computer and use it in GitHub Desktop.
Save fuzzy/a606f429fb80fa6f17e2c54770b7de46 to your computer and use it in GitHub Desktop.
// Copyright (c) 2019 Mike 'Fuzzy' Partin
// Usage of this source code is governed by the 3 clause BSD License
// that can be found in the LICENSE.md file
module main
// link in the libc for network sockets. This is only temporary until the vlib bugs get ironed out
import os
import log
import net
import flag
import json
import time
//---------------------
// Config file handling
struct VstatDCfg {
cpu bool
mem bool
net bool
i2c bool
spi bool
gpio bool
disk bool
load bool
interval int
graphite_host string
graphite_port int
}
fn read_config(l string) ?VstatDCfg {
data := os.read_file(l) or {
return error(err)
}
cfg := json.decode(VstatDCfg, data) or {
return error(err)
}
return cfg
}
//--------------
// Metric object
struct Metric {
name string
value i64
time i64
}
fn (m Metric) str() string {
return '${m.name} ${m.value} ${m.time}'
}
fn (m Metric) send(h string, p int) {
sock := net.dial(h, p) or {
LOG.error(err)
return
}
mdata := m.str()
data := '${mdata}\n'
LOG.debug('$data $h:$p')
sock.send(data.str, data.len)
sock.close()
}
fn new_metric(n string, v, t i64) ?Metric {
hname := net.hostname() or {
return error(err)
}
return Metric{'${hname}.${n}', v, t}
}
//----------------
// Check structure
struct Check {
mut:
kind string
name string
data string
}
const (
// Setup our logger
LOG = log.Log{log.DEBUG, 'terminal'}
// Set our app information
APP_NAME = 'VstatD'
APP_VERS = '0.1.0'
APP_AUTH = 'Mike "Fuzzy" Partin'
APP_COPY = 'Copyright (c) 2019'
APP_HOME = 'https://git.devfu.net/fuzzy/vstatd'
APP_DESC = 'A lightweight system metrics collection utility for Linux.'
// cpu fields for Linux (TODO: Support other platforms, preferably Open|FreeBSD)
CPU_FIELDS = [
'user', 'nice',
'system', 'idle',
'iowait', 'irq',
'softirq', 'steal',
'guest', 'guest_nice'
]
)
//-----------
// The checks
fn collect_cpu_metrics(t i64, h string, p int) {
for line in os.read_lines('/proc/stat') {
mut name := ''
cpu := line.substr(0, 4)
if cpu.contains('cpu') {
match cpu {
'cpu ' => name = 'total'
else => name = cpu
}
data := line.split(' ')
mut idx := 1
for field in CPU_FIELDS {
v := data[idx].i64()
m := new_metric('cpu.${name}.${field}', v, t) or {
LOG.error('Failed to create metric: ${name}')
return
}
m.send(h, p)
idx += 1
}
}
}
}
fn collect_mem_metrics(t i64, h string, p int) {
lines := os.read_lines('/proc/meminfo')
for line in lines {
data1 := line.split(' ')
mname := data1[0].replace(':', '')
value := data1[1].i64() * 1024
name := 'mem.${mname}'
m := new_metric(name, value, t) or {
LOG.error('Failed to create metric: ${name}')
return
}
m.send(h, p)
}
}
fn collect_net_metrics(t i64, h string, p int) {
fields := [
'bytes', 'packets',
'errs', 'drop',
'fifo', 'frame',
'compressed', 'multicast'
]
lines := os.read_lines('/proc/net/dev')
for line in lines {
if line.contains(':') {
mdata := line.split(' ')
iface := mdata[0].replace(':', '')
mut i := 1
// do this for RX
for field in fields {
d := mdata[i].i64()
name := 'net.${iface}.${field}.rx'
m := new_metric(name, d, t) or {
LOG.error('Failed to create metric: ${name}')
return
}
m.send(h, p)
i += 1
}
// now do this again for TX
for field in fields {
d := mdata[i].i64()
name := 'net.${iface}.${field}.rx'
m := new_metric(name, d, t) or {
LOG.error('Failed to create metric: ${name}')
return
}
m.send(h, p)
i += 1
}
}
}
}
fn collect_disk_metrics(t i64, h string, p int) {
fields := [
'reads.completed', 'reads.merged', 'reads.sectors',
'writes.completed', 'writes.merged', 'writes.sectors',
'writes.time', 'io.current', 'io.time',
'io.wtime', 'discards.completed', 'discards.merged',
'discards.sectors', 'discards.time'
]
lines := os.read_lines('/proc/diskstats')
for line in lines {
mut i := 3
tdata := line.split(' ')
devnm := tdata[2]
for field in fields {
fvalu := tdata[i].i64()
name := 'disk.${devnm}.${field}'
m := new_metric(name, fvalu, t) or {
LOG.error('Failed to create metric: ${name}')
return
}
m.send(h, p)
i += 1
}
}
}
fn collect_i2c_metrics(t i64, h string, p int) { return }
fn collect_spi_metrics(t i64, h string, p int) { return }
fn collect_load_metrics(t i64, h string, p int) { return }
fn collect_gpio_metrics(t i64, h string, p int) { return }
//-----------
// the engine
fn executor(f VstatDCfg, c []Check) {
for {
tt := time.now()
tstamp := tt.calc_unix()
for check in c {
match check.kind {
'cpu' => collect_cpu_metrics(tstamp, f.graphite_host, f.graphite_port)
'mem' => collect_mem_metrics(tstamp, f.graphite_host, f.graphite_port)
'net' => collect_net_metrics(tstamp, f.graphite_host, f.graphite_port)
'i2c' => collect_i2c_metrics(tstamp, f.graphite_host, f.graphite_port)
'spi' => collect_spi_metrics(tstamp, f.graphite_host, f.graphite_port)
'load' => collect_load_metrics(tstamp, f.graphite_host, f.graphite_port)
'disk' => collect_disk_metrics(tstamp, f.graphite_host, f.graphite_port)
'gpio' => collect_gpio_metrics(tstamp, f.graphite_host, f.graphite_port)
}
}
time.sleep(f.interval)
}
}
// and set things off
fn main() {
// setup the flag parser
mut flagp := flag.new_flag_parser(os.args)
flagp.application(APP_NAME)
flagp.version('v${APP_VERS}')
flagp.description(APP_DESC)
flagp.skip_executable()
// setup the options, of which, we only have 3
cfg_f := flagp.string('config', './vstatd.json', 'Specify the config file to use.')
hlp_f := flagp.bool('help', false, 'Show this help output.')
vrs_f := flagp.bool('version', false, 'Show program version info.')
// check version and help flags and do the appropriate thing
if vrs_f {
println('${APP_NAME} v${APP_VERS} ${APP_COPY} by ${APP_AUTH}')
println('${APP_DESC}')
println('${APP_HOME}')
return
} else if hlp_f {
println(flagp.usage())
return
}
// setup our config structure
config := read_config(cfg_f) or {
LOG.error(err)
return
}
// setup our list of checks
LOG.info('Config file loaded: ${cfg_f}')
mut checks := []Check{}
if config.cpu {
LOG.info('Enabling CPU checks')
checks << Check{'cpu', 'cpu', ''}
}
if config.mem {
LOG.info('Enabling RAM checks')
checks << Check{'mem', 'mem', ''}
}
if config.net {
LOG.info('Enabling NET checks')
checks << Check{'net', 'net', ''}
}
if config.disk {
LOG.info('Enabling DISK checks')
checks << Check{'disk', 'disk', ''}
}
if config.load {
LOG.info('Enabling LOAD checks')
checks << Check{'load', 'load', ''}
}
LOG.info('Graphite output: tcp://${config.graphite_host}:${config.graphite_port}')
// and fire things off
executor(config, checks)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment