Skip to content

Commit

Permalink
Merge pull request #37 from cyberbit/feature/graphs
Browse files Browse the repository at this point in the history
Add graphing output adapters
  • Loading branch information
cyberbit authored Dec 29, 2023
2 parents 2b51b66 + 715301d commit 2de70e5
Show file tree
Hide file tree
Showing 10 changed files with 1,153 additions and 2 deletions.
2 changes: 2 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
"command": "./build.sh",
"presentation": {
"close": true,
"focus": false,
"reveal": "silent",
"panel": "shared"
}
}
Expand Down
1 change: 1 addition & 0 deletions LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ Portions of this software are copyright of their respective authors and released
- ECNet2, Copyright 2020 Miguel Oliveira. For licensing see src/telem/vendor/LICENSE-ECNet2.txt
- CCryptoLib, Copyright 2023 Miguel Oliveira. For licensing see src/telem/vendor/LICENSE-CCryptoLib.txt
- RedRun, By JackMacWindows. For licensing see src/telem/vendor/redrun.lua
- Plotter, Copyright 2023 Daniel Marcolesco. For licensing see src/telem/vendor/LICENSE-Plotter.txt
4 changes: 4 additions & 0 deletions build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,9 @@ mkdir -p dist/release
luamin -f dist/telem.lua > dist/release/telem.min.lua
luamin -f dist/vendor.lua > dist/release/vendor.min.lua

# echo 'publishing to computer #0...'
# cp dist/telem.lua /home/codespace/.local/share/craftos-pc/computer/0/telem/init.lua
# cp dist/vendor.lua /home/codespace/.local/share/craftos-pc/computer/0/telem/vendor.lua

# echo 'tarring...'
# tar -cf src.tar src
6 changes: 6 additions & 0 deletions src/telem/lib/output.lua
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ return {
-- Basalt
basalt = {
label = require 'telem.lib.output.basalt.LabelOutputAdapter',
graph = require 'telem.lib.output.basalt.GraphOutputAdapter',
},

-- Plotter
plotter = {
line = require 'telem.lib.output.plotter.ChartLineOutputAdapter',
},

-- Modem
Expand Down
131 changes: 131 additions & 0 deletions src/telem/lib/output/basalt/GraphOutputAdapter.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
local o = require 'telem.lib.ObjectModel'
local t = require 'telem.lib.util'

local OutputAdapter = require 'telem.lib.OutputAdapter'
local MetricCollection = require 'telem.lib.MetricCollection'

local GraphOutputAdapter = o.class(OutputAdapter)
GraphOutputAdapter.type = 'GraphOutputAdapter'

GraphOutputAdapter.MAX_ENTRIES = 50
GraphOutputAdapter.SCALE_TICK = 10

local function graphtrackrange (self)
local min = self.graphdata[1]
local max = self.graphdata[1]

for k,v in ipairs(self.graphdata) do
if v < min then min = v end
if v > max then max = v end
end

return min,max
end

function GraphOutputAdapter:constructor (frame, filter, bg, fg, fontSize)
self:super('constructor')

self.bBaseFrame = assert(frame, 'Frame is required')
self.filter = assert(filter, 'Filter is required')

self.graphdata = {}

self:register(bg, fg, fontSize)
end

function GraphOutputAdapter:register (bg, fg, fontSize)
local currentmin = 0
local currentmax = 1000

self.tick = 0

self.bInnerFrame = self.bBaseFrame:addFrame()
:setBackground(bg)
:setSize('{parent.w}', '{parent.h}')

local fGraph = self.bInnerFrame:addFrame('fGraph'):setBackground(colors.black)
:setPosition(1,1)
:setSize('{parent.w - 2}', '{parent.h - 6}')

local fLabel = self.bInnerFrame:addFrame('fLabel'):setBackground(colors.black)
:setSize('{parent.w - 2}', 4)
:setPosition(1,'{parent.h - 5}')

local fLabelMax = self.bInnerFrame:addFrame('fLabelMax'):setBackground(colors.black)
:setSize(6, 1)
:setPosition('{parent.w - 7}',1)

local fLabelMin = self.bInnerFrame:addFrame('fLabelMin'):setBackground(colors.black)
:setSize(6, 1)
:setPosition('{parent.w - 7}','{fLabel.y - 2}')

self.label = fLabel:addLabel()
:setText("-----")
:setPosition('{parent.w/2-self.w/2}', 2)
:setForeground(colors.white)
:setBackground(colors.black)

self.graph = fGraph:addGraph()
:setPosition(1,1)
:setSize('{parent.w - 1}', '{parent.h - 1}')
:setMaxEntries(self.MAX_ENTRIES)
:setBackground(colors.black)
:setGraphColor(colors.red)
:setGraphSymbol('\127')

self.graphscale = fGraph:addGraph()
:setGraphType('scatter')
:setPosition(1,'{parent.h - 1}')
:setSize('{parent.w - 1}', 2)
:setMaxEntries(self.MAX_ENTRIES)
:setBackground(colors.transparent)
:setGraphSymbol('|')

self.labelmax = fLabelMax:addLabel()
:setPosition(1,1)
:setText('-----')
:setForeground(colors.white)
:setBackground(colors.black)

self.labelmin = fLabelMin:addLabel()
:setPosition(1,1)
:setText('-----')
:setForeground(colors.white)
:setBackground(colors.black)

self.graph:setMinValue(currentmin):setMaxValue(currentmax)
end

function GraphOutputAdapter:write (collection)
assert(o.instanceof(collection, MetricCollection), 'Collection must be a MetricCollection')

local resultMetric = collection:find(self.filter)

assert(resultMetric, 'could not find metric')

t.constrainAppend(self.graphdata, resultMetric.value, self.MAX_ENTRIES)

local newmin, newmax = graphtrackrange(self)

self.graph:setMinValue(newmin):setMaxValue(newmax)

self.graph:addDataPoint(resultMetric.value)

self.label:setFontSize(2)
self.label:setText(t.shortnum(resultMetric.value))

if self.tick == self.SCALE_TICK then
self.graphscale:addDataPoint(100)
self.tick = 1
else
self.graphscale:addDataPoint(50)
self.tick = self.tick + 1
end

self.labelmax:setText(t.shortnum(newmax))
self.labelmin:setText(t.shortnum(newmin))

return self
end

return GraphOutputAdapter
141 changes: 141 additions & 0 deletions src/telem/lib/output/plotter/ChartLineOutputAdapter.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
local o = require 'telem.lib.ObjectModel'
local t = require 'telem.lib.util'
local vendor
local plotterFactory

local OutputAdapter = require 'telem.lib.OutputAdapter'
local MetricCollection = require 'telem.lib.MetricCollection'

local ChartLineOutputAdapter = o.class(OutputAdapter)
ChartLineOutputAdapter.type = 'ChartLineOutputAdapter'

ChartLineOutputAdapter.MAX_ENTRIES = 50
ChartLineOutputAdapter.X_TICK = 10

function ChartLineOutputAdapter:constructor (win, filter, bg, fg)
self:super('constructor')

self.win = assert(win, 'Window is required')
self.filter = assert(filter, 'Filter is required')

self.plotter = nil
self.plotData = {}
self.gridOffsetX = 0

self.filter = filter

self.bg = bg or win.getBackgroundColor() or colors.black
self.fg = fg or win.getTextColor() or colors.white

self:register()
end

function ChartLineOutputAdapter:register ()
if not vendor then
self:dlog('ChartLineOutputAdapter:boot :: Loading vendor modules...')

vendor = require 'telem.vendor'

self:dlog('ChartLineOutputAdapter:boot :: Vendor modules ready.')
end

if not plotterFactory then
self:dlog('ChartLineOutputAdapter:boot :: Loading plotter...')

plotterFactory = vendor.plotter

self:dlog('ChartLineOutputAdapter:boot :: plotter ready.')
end

self.plotter = plotterFactory(self.win)

for i = 1, self.MAX_ENTRIES do
t.constrainAppend(self.plotData, self.plotter.NAN, self.MAX_ENTRIES)
end
end

function ChartLineOutputAdapter:write (collection)
assert(o.instanceof(collection, MetricCollection), 'Collection must be a MetricCollection')

local resultMetric = collection:find(self.filter)

assert(resultMetric, 'could not find metric')

-- TODO data width setting
self.gridOffsetX = self.gridOffsetX - t.constrainAppend(self.plotData, resultMetric and resultMetric.value or self.plotter.NAN, self.MAX_ENTRIES)

-- TODO X_TICK setting
if self.gridOffsetX % self.X_TICK == 0 then
self.gridOffsetX = 0
end

local dataw = #{self.plotData}

local actualmin, actualmax = math.huge, -math.huge

for _, v in ipairs(self.plotData) do
-- skip NAN
if v ~= self.plotter.NAN then
if v < actualmin then actualmin = v end
if v > actualmax then actualmax = v end
end
end

local flatlabel = nil

-- NaN data
if actualmin == math.huge then
flatlabel = 'NaN'

actualmin, actualmax = 0, 0
end

-- flat data
if actualmin == actualmax then
local minrange = 0.000001

if not flatlabel then
flatlabel = t.shortnum2(actualmin)
end

actualmin = actualmin - minrange / 2
actualmax = actualmax + minrange / 2
end

self.plotter:clear(self.bg)

self.plotter:chartGrid(self.MAX_ENTRIES, actualmin, actualmax, self.gridOffsetX, colors.gray, {
xGap = 10,
yLinesMin = 5, -- yLinesMin: number >= 1
yLinesFactor = 2 -- yLinesFactor: integer >= 2
-- effective max density = yMinDensity * yBasis
})

self.plotter:chartLine(self.plotData, self.MAX_ENTRIES, actualmin, actualmax, self.fg)

local maxString = t.shortnum2(actualmax)
local minString = t.shortnum2(actualmin)

self.win.setVisible(false)

self.plotter:render()

self.win.setTextColor(self.fg)
self.win.setBackgroundColor(self.bg)
if not flatlabel then
self.win.setCursorPos(self.plotter.box.term_width - #maxString + 1, 1)
self.win.write(maxString)

self.win.setCursorPos(self.plotter.box.term_width - #minString + 1, self.plotter.box.term_height)
self.win.write(minString)
else
self.win.setCursorPos(self.plotter.box.term_width - #flatlabel + 1, self.plotter.math.round(self.plotter.box.term_height / 2))
self.win.write(flatlabel)
end

self.win.setVisible(true)

return self
end

return ChartLineOutputAdapter
53 changes: 53 additions & 0 deletions src/telem/lib/util.lua
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,64 @@ local function shortnum(n)
end
end

-- based on https://rosettacode.org/wiki/Suffixation_of_decimal_numbers#Python
local function shortnum2(num, digits, base)
if not base then base = 10 end

local suffixes = {'', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y', 'X', 'W', 'V', 'U', 'googol'}

local exponent_distance = 10
if base == 2 then
exponent_distance = 10
else
exponent_distance = 3
end

num = string.gsub(num, ',', '')
local num_sign = string.sub(num, 1, 1) == '+' or string.sub(num, 1, 1) == '-' and string.sub(num, 1, 1) or ''

num = math.abs(tonumber(num))

local suffix_index = 0
if base == 10 and num >= 1e100 then
suffix_index = 13
num = num / 1e100
elseif num > 1 then
local magnitude = math.floor(math.log(num, base))
suffix_index = math.min(math.floor(magnitude / exponent_distance), 12)
num = num / (base ^ (exponent_distance * suffix_index))
end

local num_str = ''
if digits then
num_str = string.format('%.' .. digits .. 'f', num)
else
num_str = string.format('%.3f', num):gsub('0+$', ''):gsub('%.$', '')
end

return num_sign .. num_str .. suffixes[suffix_index + 1] .. (base == 2 and 'i' or '')
end

local function constrainAppend (data, value, width)
local removed = 0

table.insert(data, value)

while #data > width do
table.remove(data, 1)
removed = removed + 1
end

return removed
end

return {
log = log,
err = err,
pprint = pprint,
skpairs = skpairs,
sleep = os.sleep or tsleep,
shortnum = shortnum,
shortnum2 = shortnum2,
constrainAppend = constrainAppend,
}
Loading

0 comments on commit 2de70e5

Please sign in to comment.