Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add graphing output adapters #37

Merged
merged 4 commits into from
Dec 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading