refactoring, added sequential triggers execution per component
This commit is contained in:
parent
22fbf3e796
commit
7db33312b6
|
@ -7,28 +7,41 @@ import (
|
|||
"log"
|
||||
)
|
||||
|
||||
func Beep() error {
|
||||
type AudioPlayer struct {
|
||||
player *oto.Player
|
||||
beep []byte
|
||||
}
|
||||
|
||||
func NewAudioPlayer() *AudioPlayer {
|
||||
|
||||
bytes, err := Asset("quindar-tone.mp3")
|
||||
if err != nil {
|
||||
log.Fatal("Can't find asset file")
|
||||
log.Fatal("Can't find audio file")
|
||||
}
|
||||
|
||||
d, err := mp3.NewDecoder(NewAssetFile(bytes))
|
||||
player, err := oto.NewPlayer(44100, 2, 2, 8192)
|
||||
if err != nil {
|
||||
return err
|
||||
panic(err)
|
||||
}
|
||||
defer d.Close()
|
||||
|
||||
p, err := oto.NewPlayer(d.SampleRate(), 2, 2, 8192)
|
||||
return &AudioPlayer{
|
||||
player: player,
|
||||
beep: bytes,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AudioPlayer) Beep() {
|
||||
|
||||
decoder, err := mp3.NewDecoder(NewAssetFile(a.beep))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer p.Close()
|
||||
|
||||
if _, err := io.Copy(p, d); err != nil {
|
||||
return err
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
if _, err := io.Copy(a.player, decoder); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AudioPlayer) Close() {
|
||||
_ = a.player.Close()
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
"github.com/sqshq/sampler/config"
|
||||
"github.com/sqshq/sampler/console"
|
||||
"github.com/sqshq/sampler/data"
|
||||
"github.com/sqshq/sampler/trigger"
|
||||
"image"
|
||||
"math"
|
||||
"strconv"
|
||||
|
@ -39,7 +38,6 @@ const (
|
|||
type RunChart struct {
|
||||
ui.Block
|
||||
data.Consumer
|
||||
triggers []trigger.Trigger
|
||||
lines []TimeLine
|
||||
grid ChartGrid
|
||||
timescale time.Duration
|
||||
|
|
14
config.yml
14
config.yml
|
@ -7,13 +7,13 @@ runcharts:
|
|||
w: 53
|
||||
h: 16
|
||||
triggers:
|
||||
- title: PROCESSING STARTED # ${prev} ${cur} ${lavel} echo $(( 3 < 4 && 1 > 2 ))
|
||||
condition: ((${prev} == 0 && ${cur} > 0))
|
||||
- title: LATENCY
|
||||
condition: echo "$prev < 0.4 && $cur > 0.4" |bc -l
|
||||
actions:
|
||||
terminal-bell: true
|
||||
sound: false
|
||||
sound: true
|
||||
visual: true
|
||||
script: say ${lavel} initiated with value ${cur}
|
||||
script: 'say alert: ${label} latency exceeded ${cur} second'
|
||||
scale: 3
|
||||
items:
|
||||
- label: GOOGLE
|
||||
|
@ -100,6 +100,12 @@ gauges:
|
|||
size:
|
||||
w: 27
|
||||
h: 2
|
||||
triggers:
|
||||
- title: CLOCK BELL EVERY MINUTE
|
||||
condition: '[ $label == "cur" ] && [ $cur -eq 0 ] && echo 1'
|
||||
actions:
|
||||
sound: true
|
||||
script: say -v samantha `date +%I:%M%p`
|
||||
values:
|
||||
cur: date +%S
|
||||
max: echo 60
|
||||
|
|
|
@ -104,9 +104,9 @@ func (c *Config) setDefaultValues() {
|
|||
|
||||
func setDefaultTriggersValues(triggers []TriggerConfig) {
|
||||
|
||||
defaultTerminalBell := true
|
||||
defaultTerminalBell := false
|
||||
defaultSound := false
|
||||
defaultVisual := true
|
||||
defaultVisual := false
|
||||
|
||||
for i, trigger := range triggers {
|
||||
|
||||
|
@ -137,20 +137,20 @@ func (c *Config) setDefaultColors() {
|
|||
palette := console.GetPalette(*c.Theme)
|
||||
colorsCount := len(palette.Colors)
|
||||
|
||||
for _, chart := range c.RunCharts {
|
||||
for j, item := range chart.Items {
|
||||
for _, ch := range c.RunCharts {
|
||||
for j, item := range ch.Items {
|
||||
if item.Color == nil {
|
||||
item.Color = &palette.Colors[j%colorsCount]
|
||||
chart.Items[j] = item
|
||||
ch.Items[j] = item
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, chart := range c.BarCharts {
|
||||
for j, item := range chart.Items {
|
||||
for _, b := range c.BarCharts {
|
||||
for j, item := range b.Items {
|
||||
if item.Color == nil {
|
||||
item.Color = &palette.Colors[j%colorsCount]
|
||||
chart.Items[j] = item
|
||||
b.Items[j] = item
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,10 @@ const (
|
|||
|
||||
type Console struct{}
|
||||
|
||||
const (
|
||||
BellCharacter = "\a"
|
||||
)
|
||||
|
||||
func (self *Console) Init() {
|
||||
|
||||
fmt.Printf("\033]0;%s\007", AppTitle)
|
||||
|
|
|
@ -11,6 +11,7 @@ type Sample struct {
|
|||
}
|
||||
|
||||
type Alert struct {
|
||||
Title string
|
||||
Text string
|
||||
}
|
||||
|
||||
|
|
15
data/item.go
15
data/item.go
|
@ -1,6 +1,7 @@
|
|||
package data
|
||||
|
||||
import (
|
||||
"github.com/sqshq/sampler/config"
|
||||
ui "github.com/sqshq/termui"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
@ -9,7 +10,19 @@ import (
|
|||
type Item struct {
|
||||
Label string
|
||||
Script string
|
||||
Color ui.Color
|
||||
Color *ui.Color
|
||||
}
|
||||
|
||||
func NewItems(cfgs []config.Item) []Item {
|
||||
|
||||
items := make([]Item, 0)
|
||||
|
||||
for _, i := range cfgs {
|
||||
item := Item{Label: *i.Label, Script: i.Script, Color: i.Color}
|
||||
items = append(items, item)
|
||||
}
|
||||
|
||||
return items
|
||||
}
|
||||
|
||||
func (i *Item) nextValue() (value string, err error) {
|
||||
|
|
|
@ -1,43 +1,63 @@
|
|||
package data
|
||||
|
||||
import (
|
||||
"github.com/sqshq/sampler/trigger"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Sampler struct {
|
||||
consumer Consumer
|
||||
item Item
|
||||
triggers []trigger.Trigger
|
||||
items []Item
|
||||
triggers []Trigger
|
||||
triggersChannel chan Sample
|
||||
}
|
||||
|
||||
func NewSampler(consumer Consumer, item Item, triggers []trigger.Trigger, rateMs int) Sampler {
|
||||
func NewSampler(consumer Consumer, items []Item, triggers []Trigger, rateMs int) Sampler {
|
||||
|
||||
ticker := time.NewTicker(time.Duration(rateMs * int(time.Millisecond)))
|
||||
sampler := Sampler{consumer, item, triggers}
|
||||
ticker := time.NewTicker(
|
||||
time.Duration(rateMs * int(time.Millisecond)),
|
||||
)
|
||||
|
||||
sampler := Sampler{
|
||||
consumer,
|
||||
items,
|
||||
triggers,
|
||||
make(chan Sample),
|
||||
}
|
||||
|
||||
go func() {
|
||||
sampler.sample()
|
||||
for ; true; <-ticker.C {
|
||||
sampler.sample()
|
||||
for _, item := range sampler.items {
|
||||
go sampler.sample(item)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case sample := <-sampler.triggersChannel:
|
||||
for _, t := range sampler.triggers {
|
||||
t.Execute(sample)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return sampler
|
||||
}
|
||||
|
||||
func (s *Sampler) sample() {
|
||||
value, err := s.item.nextValue()
|
||||
func (s *Sampler) sample(item Item) {
|
||||
|
||||
val, err := item.nextValue()
|
||||
|
||||
if err == nil {
|
||||
|
||||
sample := Sample{Value: value, Label: s.item.Label}
|
||||
sample := Sample{Label: item.Label, Value: val}
|
||||
s.consumer.SampleChannel <- sample
|
||||
|
||||
for _, t := range s.triggers {
|
||||
t.Execute(s.item.Label, value)
|
||||
}
|
||||
|
||||
s.triggersChannel <- sample
|
||||
} else {
|
||||
s.consumer.AlertChannel <- Alert{Text: err.Error()}
|
||||
s.consumer.AlertChannel <- Alert{
|
||||
Title: "SAMPLING FAILURE",
|
||||
Text: err.Error(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
package data
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/sqshq/sampler/asset"
|
||||
"github.com/sqshq/sampler/config"
|
||||
"github.com/sqshq/sampler/console"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
const (
|
||||
TrueIndicator = "1"
|
||||
InitialValue = "0"
|
||||
)
|
||||
|
||||
type Trigger struct {
|
||||
title string
|
||||
condition string
|
||||
actions Actions
|
||||
consumer Consumer
|
||||
valuesByLabel map[string]Values
|
||||
player *asset.AudioPlayer
|
||||
digitsRegexp *regexp.Regexp
|
||||
}
|
||||
|
||||
type Actions struct {
|
||||
terminalBell bool
|
||||
sound bool
|
||||
visual bool
|
||||
script *string
|
||||
}
|
||||
|
||||
type Values struct {
|
||||
current string
|
||||
previous string
|
||||
}
|
||||
|
||||
func NewTriggers(cfgs []config.TriggerConfig, consumer Consumer, player *asset.AudioPlayer) []Trigger {
|
||||
|
||||
triggers := make([]Trigger, 0)
|
||||
|
||||
for _, cfg := range cfgs {
|
||||
triggers = append(triggers, NewTrigger(cfg, consumer, player))
|
||||
}
|
||||
|
||||
return triggers
|
||||
}
|
||||
|
||||
func NewTrigger(config config.TriggerConfig, consumer Consumer, player *asset.AudioPlayer) Trigger {
|
||||
return Trigger{
|
||||
title: config.Title,
|
||||
condition: config.Condition,
|
||||
consumer: consumer,
|
||||
valuesByLabel: make(map[string]Values),
|
||||
player: player,
|
||||
digitsRegexp: regexp.MustCompile("[^0-9]+"),
|
||||
actions: Actions{
|
||||
terminalBell: *config.Actions.TerminalBell,
|
||||
sound: *config.Actions.Sound,
|
||||
visual: *config.Actions.Visual,
|
||||
script: config.Actions.Script,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Trigger) Execute(sample Sample) {
|
||||
if t.evaluate(sample) {
|
||||
|
||||
if t.actions.terminalBell {
|
||||
fmt.Print(console.BellCharacter)
|
||||
}
|
||||
|
||||
if t.actions.sound {
|
||||
t.player.Beep()
|
||||
}
|
||||
|
||||
if t.actions.visual {
|
||||
//t.consumer.AlertChannel <- Alert{
|
||||
// Title: "TRIGGER ALERT", Text: sample.Label,
|
||||
//}
|
||||
}
|
||||
|
||||
if t.actions.script != nil {
|
||||
_, _ = runScript(*t.actions.script, sample.Label, t.valuesByLabel[sample.Label])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Trigger) evaluate(sample Sample) bool {
|
||||
|
||||
if values, ok := t.valuesByLabel[sample.Label]; ok {
|
||||
values.previous = values.current
|
||||
values.current = sample.Value
|
||||
t.valuesByLabel[sample.Label] = values
|
||||
} else {
|
||||
t.valuesByLabel[sample.Label] = Values{previous: InitialValue, current: sample.Value}
|
||||
}
|
||||
|
||||
output, err := runScript(t.condition, sample.Label, t.valuesByLabel[sample.Label])
|
||||
|
||||
if err != nil {
|
||||
//t.consumer.AlertChannel <- Alert{Title: "TRIGGER CONDITION FAILURE", Text: err.Error()}
|
||||
}
|
||||
|
||||
return t.digitsRegexp.ReplaceAllString(string(output), "") == TrueIndicator
|
||||
}
|
||||
|
||||
func runScript(script, label string, data Values) ([]byte, error) {
|
||||
|
||||
cmd := exec.Command("sh", "-c", script)
|
||||
cmd.Env = os.Environ()
|
||||
cmd.Env = append(cmd.Env,
|
||||
fmt.Sprintf("prev=%v", data.previous),
|
||||
fmt.Sprintf("cur=%v", data.current),
|
||||
fmt.Sprintf("label=%v", label))
|
||||
|
||||
return cmd.Output()
|
||||
}
|
35
main.go
35
main.go
|
@ -1,6 +1,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/sqshq/sampler/asset"
|
||||
"github.com/sqshq/sampler/component"
|
||||
"github.com/sqshq/sampler/component/asciibox"
|
||||
"github.com/sqshq/sampler/component/barchart"
|
||||
|
@ -11,7 +12,6 @@ import (
|
|||
"github.com/sqshq/sampler/console"
|
||||
"github.com/sqshq/sampler/data"
|
||||
"github.com/sqshq/sampler/event"
|
||||
"github.com/sqshq/sampler/trigger"
|
||||
ui "github.com/sqshq/termui"
|
||||
)
|
||||
|
||||
|
@ -25,51 +25,50 @@ func main() {
|
|||
width, height := ui.TerminalDimensions()
|
||||
lout := layout.NewLayout(width, height, component.NewStatusLine(flg.ConfigFileName), component.NewMenu())
|
||||
|
||||
player := asset.NewAudioPlayer()
|
||||
defer player.Close()
|
||||
|
||||
for _, c := range cfg.RunCharts {
|
||||
|
||||
legend := runchart.Legend{Enabled: c.Legend.Enabled, Details: c.Legend.Details}
|
||||
chart := runchart.NewRunChart(c, legend)
|
||||
lout.AddComponent(config.TypeRunChart, chart, c.Title, c.Position, c.Size, *c.RefreshRateMs)
|
||||
triggers := trigger.NewTriggers(c.Triggers)
|
||||
triggers := data.NewTriggers(c.Triggers, chart.Consumer, player)
|
||||
items := data.NewItems(c.Items)
|
||||
data.NewSampler(chart.Consumer, items, triggers, *c.RefreshRateMs)
|
||||
|
||||
for _, i := range c.Items {
|
||||
item := data.Item{Label: *i.Label, Script: i.Script, Color: *i.Color}
|
||||
chart.AddLine(item.Label, item.Color)
|
||||
data.NewSampler(chart.Consumer, item, triggers, *c.RefreshRateMs)
|
||||
chart.AddLine(*i.Label, *i.Color)
|
||||
}
|
||||
}
|
||||
|
||||
for _, a := range cfg.AsciiBoxes {
|
||||
box := asciibox.NewAsciiBox(a)
|
||||
item := data.Item{Label: *a.Label, Script: a.Script, Color: *a.Color}
|
||||
triggers := trigger.NewTriggers(a.Triggers)
|
||||
item := data.Item{Label: *a.Label, Script: a.Script, Color: a.Color}
|
||||
lout.AddComponent(config.TypeAsciiBox, box, a.Title, a.Position, a.Size, *a.RefreshRateMs)
|
||||
data.NewSampler(box.Consumer, item, triggers, *a.RefreshRateMs)
|
||||
triggers := data.NewTriggers(a.Triggers, box.Consumer, player)
|
||||
data.NewSampler(box.Consumer, []data.Item{item}, triggers, *a.RefreshRateMs)
|
||||
}
|
||||
|
||||
for _, b := range cfg.BarCharts {
|
||||
|
||||
chart := barchart.NewBarChart(b.Title, *b.Scale)
|
||||
triggers := trigger.NewTriggers(b.Triggers)
|
||||
triggers := data.NewTriggers(b.Triggers, chart.Consumer, player)
|
||||
lout.AddComponent(config.TypeBarChart, chart, b.Title, b.Position, b.Size, *b.RefreshRateMs)
|
||||
items := data.NewItems(b.Items)
|
||||
data.NewSampler(chart.Consumer, items, triggers, *b.RefreshRateMs)
|
||||
|
||||
for _, i := range b.Items {
|
||||
item := data.Item{Label: *i.Label, Script: i.Script, Color: *i.Color}
|
||||
chart.AddBar(*i.Label, *i.Color)
|
||||
data.NewSampler(chart.Consumer, item, triggers, *b.RefreshRateMs)
|
||||
}
|
||||
}
|
||||
|
||||
for _, gc := range cfg.Gauges {
|
||||
|
||||
g := gauge.NewGauge(gc.Title, *gc.Scale, *gc.Color)
|
||||
triggers := trigger.NewTriggers(gc.Triggers)
|
||||
triggers := data.NewTriggers(gc.Triggers, g.Consumer, player)
|
||||
lout.AddComponent(config.TypeGauge, g, gc.Title, gc.Position, gc.Size, *gc.RefreshRateMs)
|
||||
|
||||
for _, i := range gc.Items {
|
||||
item := data.Item{Label: *i.Label, Script: i.Script}
|
||||
data.NewSampler(g.Consumer, item, triggers, *gc.RefreshRateMs)
|
||||
}
|
||||
items := data.NewItems(gc.Items)
|
||||
data.NewSampler(g.Consumer, items, triggers, *gc.RefreshRateMs)
|
||||
}
|
||||
|
||||
handler := event.NewHandler(lout)
|
||||
|
|
|
@ -1,111 +0,0 @@
|
|||
package trigger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/sqshq/sampler/asset"
|
||||
"github.com/sqshq/sampler/config"
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
const (
|
||||
TrueIndicator = "1"
|
||||
BellCharacter = "\a"
|
||||
)
|
||||
|
||||
type Trigger struct {
|
||||
title string
|
||||
condition string
|
||||
actions Actions
|
||||
data map[string]Data
|
||||
}
|
||||
|
||||
type Actions struct {
|
||||
terminalBell bool
|
||||
sound bool
|
||||
visual bool
|
||||
script *string
|
||||
}
|
||||
|
||||
type Data struct {
|
||||
previousValue interface{}
|
||||
currentValue interface{}
|
||||
}
|
||||
|
||||
func NewTriggers(configs []config.TriggerConfig) []Trigger {
|
||||
|
||||
triggers := make([]Trigger, len(configs))
|
||||
|
||||
for _, c := range configs {
|
||||
triggers = append(triggers, NewTrigger(c))
|
||||
}
|
||||
|
||||
return triggers
|
||||
}
|
||||
|
||||
func NewTrigger(config config.TriggerConfig) Trigger {
|
||||
return Trigger{
|
||||
title: config.Title,
|
||||
condition: config.Condition,
|
||||
data: make(map[string]Data),
|
||||
actions: Actions{
|
||||
terminalBell: *config.Actions.TerminalBell,
|
||||
sound: *config.Actions.Sound,
|
||||
visual: *config.Actions.Visual,
|
||||
script: config.Actions.Script,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (t Trigger) Execute(label string, value interface{}) {
|
||||
|
||||
if data, ok := t.data[label]; ok {
|
||||
data.previousValue = data.currentValue
|
||||
data.currentValue = value
|
||||
} else {
|
||||
t.data[label] = Data{previousValue: nil, currentValue: value}
|
||||
}
|
||||
|
||||
t.evaluate(label, t.data[label])
|
||||
}
|
||||
|
||||
func (t Trigger) evaluate(label string, data Data) {
|
||||
|
||||
output, err := runScript(t.condition, label, data)
|
||||
|
||||
if err != nil {
|
||||
//println(err) // TODO visual notification
|
||||
}
|
||||
|
||||
if string(output) != TrueIndicator {
|
||||
return
|
||||
}
|
||||
|
||||
if t.actions.terminalBell {
|
||||
fmt.Print(BellCharacter)
|
||||
}
|
||||
|
||||
if t.actions.sound {
|
||||
_ = asset.Beep()
|
||||
}
|
||||
|
||||
if t.actions.visual {
|
||||
// TODO visual notification
|
||||
}
|
||||
|
||||
if t.actions.script != nil {
|
||||
_, _ = runScript(*t.actions.script, label, data)
|
||||
}
|
||||
}
|
||||
|
||||
func runScript(script, label string, data Data) ([]byte, error) {
|
||||
cmd := exec.Command("sh", "-c", script)
|
||||
|
||||
cmd.Env = os.Environ()
|
||||
cmd.Env = append(cmd.Env,
|
||||
fmt.Sprintf("prev=%v", data.previousValue),
|
||||
fmt.Sprintf("cur=%v", data.currentValue),
|
||||
fmt.Sprintf("label=%v", label))
|
||||
|
||||
return cmd.Output()
|
||||
}
|
Loading…
Reference in New Issue