v.0.2.3: Add thicknessquery and highlightedquery. Add human readable formatting for main|secondarystats
This commit is contained in:
parent
c64327ad0b
commit
cc3fcba1d7
@ -1,3 +1,6 @@
|
||||
# Formatting metrics in main & secondarystat. Supported: "english", "french", "german", "ukrainian", "chinese", "arabic"
|
||||
language: 'english'
|
||||
|
||||
# datasource describe a way to get prometheus metrics.
|
||||
# Properties :
|
||||
# - name: name of the query. To be used in edges or nodes mainstatquery or secondarystatquery. Result will be output in mainstat, or secondarystat.
|
||||
@ -17,6 +20,12 @@ datasources:
|
||||
address: 'http://prometheus.local.lan:9090'
|
||||
query: 'sum(rate(node_cpu_seconds_total{instance="router01.local.lan:9100",job="node",mode!~"idle"}[30s]))*100'
|
||||
timeout: 10
|
||||
- name: node_cpu_metric_over_80
|
||||
type: query
|
||||
address: 'http://prometheus.local.lan:9090'
|
||||
# Return 1 if cpu rate > 80%
|
||||
query: '(sum(rate(node_cpu_seconds_total{instance="router01.local.lan:9100",job="node",mode!~"idle"}[30s]))*100) > bool 80'
|
||||
timeout: 10
|
||||
- name: router01_net_down_rate
|
||||
# Range query. Return all metrics from a time range. Result will be averaged from these metrics. Time range will be provided by Grafana.
|
||||
type: query_range
|
||||
@ -38,6 +47,11 @@ datasources:
|
||||
address: 'http://prometheus.local.lan:9090'
|
||||
query: 'rate(node_network_transmit_bytes_total{device="ix3", instance="router01.local.lan:9100", job="node"}[30s])'
|
||||
timeout: 10
|
||||
- name: router01_net_down_rate_perten
|
||||
type: query
|
||||
address: 'http://prometheus.moon.lan:9090'
|
||||
query: 'rate(node_network_receive_bytes_total{device="igb0", instance="router01.local.lan:9100", job="node"}[30s])/62500000*10'
|
||||
timeout: 10
|
||||
|
||||
# graphs identifies context for a nodegraph. You can have many contexts, and your grafana query will mention this context name.
|
||||
# For this example named "internet", grafana URL will be :
|
||||
@ -47,16 +61,25 @@ datasources:
|
||||
# - name: name of the context
|
||||
# - nodes: list of nodegraph nodes
|
||||
# - edges: list of nodegraph edges
|
||||
#
|
||||
# Dynamic fields :
|
||||
# nodes:
|
||||
# - mainstat : use mainstatquery and mainstatformat. mainstatquery should return a metric, mainstatformat is a printf format specifier.
|
||||
# - secondarystat : use secondarystatquery and secondarystatformat. same as mainstat(query|format).
|
||||
# - hightlighted : use highlightedquery. if result return > 0, item will be highligthed.
|
||||
# edges:
|
||||
# - same list as nodes.
|
||||
# - thickness: use thicknessquery.
|
||||
graphs:
|
||||
- name: internet
|
||||
nodes:
|
||||
- name: internet
|
||||
id: internet
|
||||
title: "internet"
|
||||
subtitle: "The internets"
|
||||
color: "grey"
|
||||
# icons come from https://developers.grafana.com/ui/latest/index.html?path=/story/docs-overview-icon--icons-overview
|
||||
icon: "globe"
|
||||
- name: host01
|
||||
id: host01
|
||||
title: host01
|
||||
subtitle: "A workstation"
|
||||
color: "green"
|
||||
# icon come from https://developers.grafana.com/ui/latest/index.html?path=/story/docs-overview-icon--icons-overview
|
||||
icon: "user"
|
||||
- name: router01
|
||||
id: router01
|
||||
title: router01
|
||||
@ -65,39 +88,43 @@ graphs:
|
||||
mainstatquery: '{{ node_cpu_metric }}'
|
||||
# Use %% if you want to display '%' in you metric label
|
||||
mainstatformat: '%0.2f%% cpu'
|
||||
- name: host01
|
||||
id: host01
|
||||
title: host01
|
||||
subtitle: "A workstation"
|
||||
color: "green"
|
||||
icon: "user"
|
||||
# highlight router if cpu > 80%
|
||||
highlightedquery: '{{ node_cpu_metric_over_80 }}'
|
||||
- name: internet
|
||||
id: internet
|
||||
title: "internet"
|
||||
subtitle: "The internets"
|
||||
color: "grey"
|
||||
icon: "globe"
|
||||
edges:
|
||||
- id: edge0
|
||||
source: internet
|
||||
target: router01
|
||||
mainstatquery: '{{ router01_net_up_rate }}'
|
||||
mainstatformat: 'up %0.0f bps'
|
||||
secondarystatquery: '{{ router01_net_down_rate }}'
|
||||
secondarystatformat: 'down %0.0f bps'
|
||||
- id: edge1
|
||||
source: router01
|
||||
target: internet
|
||||
mainstatquery: '{{ router01_net_up_rate }}'
|
||||
mainstatformat: 'up %0.0f bps'
|
||||
secondarystatquery: '{{ router01_net_down_rate }}'
|
||||
secondarystatformat: 'down %0.0f bps'
|
||||
- id: edge2
|
||||
source: router01
|
||||
target: host01
|
||||
mainstatquery: '{{ router01_lan_down_rate }}'
|
||||
mainstatformat: 'up %0.0f bps'
|
||||
secondarystatquery: '{{ router01_lan_up_rate }}'
|
||||
secondarystatformat: 'down %0.0f bps'
|
||||
- id: edge3
|
||||
source: host01
|
||||
target: router01
|
||||
mainstatquery: '{{ router01_lan_down_rate }}'
|
||||
mainstatformat: 'up %0.0f bps'
|
||||
secondarystatquery: '{{ router01_lan_up_rate }}'
|
||||
secondarystatformat: 'down %0.0f bps'
|
||||
secondarystatformat: 'down %0.0f Bps'
|
||||
- id: edge1
|
||||
source: router01
|
||||
target: host01
|
||||
mainstatquery: '{{ router01_lan_down_rate }}'
|
||||
mainstatformat: 'up %0.0f bps'
|
||||
secondarystatquery: '{{ router01_lan_up_rate }}'
|
||||
secondarystatformat: 'down %0.0f Bps'
|
||||
thicknessquery: '{{ router01_net_down_rate_perten }}'
|
||||
- id: edge2
|
||||
source: router01
|
||||
target: internet
|
||||
mainstatquery: '{{ router01_net_up_rate }}'
|
||||
mainstatformat: 'up %0.0f bps'
|
||||
secondarystatquery: '{{ router01_net_down_rate }}'
|
||||
secondarystatformat: 'down %0.0f Bps'
|
||||
- id: edge3
|
||||
source: internet
|
||||
target: router01
|
||||
mainstatquery: '{{ router01_net_up_rate }}'
|
||||
mainstatformat: 'up %0.0f bps'
|
||||
secondarystatquery: '{{ router01_net_down_rate }}'
|
||||
secondarystatformat: 'down %0.0f Bps'
|
||||
thicknessquery: '{{ router01_net_down_rate_perten }}'
|
||||
|
||||
|
22
edges.go
22
edges.go
@ -20,7 +20,9 @@ type Edge struct {
|
||||
SecondaryStatFormat string `yaml:"secondarystatformat,omitempty" json:"secondarystatformat,omitempty"`
|
||||
Color string `yaml:"color,omitempty" json:"color,omitempty"`
|
||||
Thickness int `yaml:"thickness,omitempty" json:"thickness,omitempty"`
|
||||
HighLighted bool `yaml:"highlighted,omitempty" json:"hightlighted,omitempty"`
|
||||
ThicknessQuery string `yaml:"thicknessquery,omitempty" json:"thicknessquery,omitempty"`
|
||||
Highlighted bool `yaml:"highlighted,omitempty" json:"hightlighted,omitempty"`
|
||||
HighlightedQuery string `yaml:"highlightedquery,omitempty" json:"highlightedquery,omitempty"`
|
||||
StrokeDashArray float32 `yaml:"strokeDasharray,omitempty" json:"strokeDasharray,omitempty"`
|
||||
}
|
||||
|
||||
@ -56,10 +58,26 @@ func (e *Edge) SetMainStat(stat string) {
|
||||
e.MainStat = fmt.Sprintf("%s", stat)
|
||||
}
|
||||
|
||||
func (e *Edge) GetThicknessQuery() string {
|
||||
return e.ThicknessQuery
|
||||
}
|
||||
|
||||
func (e *Edge) SetThickness(thickness float64) {
|
||||
e.Thickness = int(thickness)
|
||||
}
|
||||
|
||||
func (e *Edge) SetSecondaryStat(stat string) {
|
||||
e.SecondaryStat = fmt.Sprintf("%s", stat)
|
||||
}
|
||||
|
||||
func (e *Edge) GetHighlightedQuery() string {
|
||||
return e.HighlightedQuery
|
||||
}
|
||||
|
||||
func (e *Edge) SetHighlighted(highlighted bool) {
|
||||
e.Highlighted = highlighted
|
||||
}
|
||||
|
||||
// Custom marshaler to not send (main|secondary)statquery
|
||||
func (e Edge) MarshalJSON() ([]byte, error) {
|
||||
jsonRes := `{"id":"` + e.Id + `","source":"` + e.Source + `","target":"` + e.Target + `"`
|
||||
@ -75,7 +93,7 @@ func (e Edge) MarshalJSON() ([]byte, error) {
|
||||
if e.Thickness > 0 {
|
||||
jsonRes += `,"thickness":` + strconv.Itoa(e.Thickness)
|
||||
}
|
||||
if e.HighLighted {
|
||||
if e.Highlighted {
|
||||
jsonRes += `,"highlighted":true`
|
||||
}
|
||||
// TODO : e.StrokeDashArray
|
||||
|
103
main.go
103
main.go
@ -18,20 +18,23 @@ import (
|
||||
"syscall"
|
||||
"net/http"
|
||||
"os/signal"
|
||||
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
|
||||
"golang.org/x/text/language"
|
||||
"golang.org/x/text/message"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/prometheus/client_golang/api"
|
||||
v1 "github.com/prometheus/client_golang/api/prometheus/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
gVersion = "0.2.1"
|
||||
gVersion = "0.2.3"
|
||||
)
|
||||
|
||||
type PromDataSourceConfig struct {
|
||||
@ -57,7 +60,11 @@ type Item interface {
|
||||
GetSecondaryStat() string
|
||||
GetSecondaryStatQuery() string
|
||||
GetSecondaryStatFormat() string
|
||||
SetSecondaryStat(string)
|
||||
SetSecondaryStat(string)
|
||||
GetThicknessQuery() string
|
||||
SetThickness(float64)
|
||||
GetHighlightedQuery() string
|
||||
SetHighlighted(bool)
|
||||
}
|
||||
|
||||
// Query arguments. Based on grafana internal variables.
|
||||
@ -75,6 +82,7 @@ type MyRange struct {
|
||||
}
|
||||
|
||||
var (
|
||||
gPrinter *message.Printer
|
||||
gDebug bool
|
||||
gGraphs []Graph
|
||||
gDataSources []PromDataSourceConfig
|
||||
@ -118,7 +126,7 @@ func (d *PromDataSourceConfig) GetData(timeRange *MyRange) (float64, error) {
|
||||
if len(warnings) > 0 {
|
||||
log.Warningf("DataSourceConfig.GetData: Warnings: %v\n", warnings)
|
||||
}
|
||||
log.Debugf("DataSourceConfig.GetData: Result: %v\n", result)
|
||||
//log.Debugf("DataSourceConfig.GetData: Result: %v\n", result)
|
||||
|
||||
switch {
|
||||
case result.Type() == model.ValScalar:
|
||||
@ -163,8 +171,9 @@ func (g Graph) BuildMetrics(items *[]Item, timeRange *MyRange) (*[]Item, error)
|
||||
// Acquire Read Lock. This won't prevent other queries being answered, but reloadConfig WLock will have to wait until we finish
|
||||
gCfgMutex.RLock()
|
||||
defer gCfgMutex.RUnlock()
|
||||
|
||||
|
||||
for _, item := range *items {
|
||||
// Handle mainStat
|
||||
if len(item.GetMainStatQuery()) > 0 {
|
||||
log.Debugf("Item %s have mainstatquery: %s\n", item.GetId(), item.GetMainStatQuery())
|
||||
r := gDSVarCompRegex.FindStringSubmatch(item.GetMainStatQuery())
|
||||
@ -185,12 +194,13 @@ func (g Graph) BuildMetrics(items *[]Item, timeRange *MyRange) (*[]Item, error)
|
||||
if len(format) == 0 {
|
||||
format = "%f"
|
||||
}
|
||||
log.Debugf("buildMetrics: Replace %s mainstat with %s\n", item.GetId(), fmt.Sprintf(format, value))
|
||||
item.SetMainStat(fmt.Sprintf(format, value))
|
||||
log.Debugf("buildMetrics: Replace %s mainstat with %s\n", item.GetId(), gPrinter.Sprintf(format, value))
|
||||
item.SetMainStat(gPrinter.Sprintf(format, value))
|
||||
} else {
|
||||
log.Errorf("buildMetrics: Item %s mainstatquery unparseable: %s\n", item.GetId(), item.GetMainStatQuery())
|
||||
}
|
||||
}
|
||||
// Handle secondaryStat
|
||||
if len(item.GetSecondaryStatQuery()) > 0 {
|
||||
log.Debugf("Item %s have mainstatquery: %s\n", item.GetId(), item.GetSecondaryStatQuery())
|
||||
r := gDSVarCompRegex.FindStringSubmatch(item.GetSecondaryStatQuery())
|
||||
@ -211,12 +221,66 @@ func (g Graph) BuildMetrics(items *[]Item, timeRange *MyRange) (*[]Item, error)
|
||||
if len(format) == 0 {
|
||||
format = "%f"
|
||||
}
|
||||
log.Debugf("buildMetrics: Replace %s secondarystat with %s\n", item.GetId(), fmt.Sprintf(format, value))
|
||||
item.SetSecondaryStat(fmt.Sprintf(format, value))
|
||||
log.Debugf("buildMetrics: Replace %s secondarystat with %s\n", item.GetId(), gPrinter.Sprintf(format, value))
|
||||
item.SetSecondaryStat(gPrinter.Sprintf(format, value))
|
||||
} else {
|
||||
log.Errorf("buildMetrics: Item %s secondarystatquery unparseable: %s\n", item.GetId(), item.GetSecondaryStatQuery())
|
||||
}
|
||||
}
|
||||
// Handle highlighted
|
||||
if len(item.GetHighlightedQuery()) > 0 {
|
||||
log.Debugf("buildMetrics: Item %s have highlightedquery: %s\n", item.GetId(), item.GetHighlightedQuery())
|
||||
r := gDSVarCompRegex.FindStringSubmatch(item.GetHighlightedQuery())
|
||||
if len(r) > 1 {
|
||||
var value float64
|
||||
dsname := strings.TrimSpace(r[1])
|
||||
log.Debugf("buildMetrics: datasource from highlightedquery : %s\n", dsname)
|
||||
for _, d := range gDataSources {
|
||||
if strings.EqualFold(d.Name, dsname) {
|
||||
value, err = d.GetData(timeRange)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
highlight := false
|
||||
if value == 0 {
|
||||
highlight = false
|
||||
} else {
|
||||
highlight = true
|
||||
}
|
||||
log.Debugf("buildMetrics: Replace %s highlighted with %t\n", item.GetId(), highlight)
|
||||
item.SetHighlighted(highlight)
|
||||
} else {
|
||||
log.Errorf("buildMetrics: Item %s highlightedquery unparseable: %s\n", item.GetId(), item.GetHighlightedQuery())
|
||||
}
|
||||
}
|
||||
switch item.(type) {
|
||||
case *Edge:
|
||||
if len(item.GetThicknessQuery()) > 0 {
|
||||
log.Debugf("buildMetrics: Item %s have thicknessquery: %s\n", item.GetId(), item.GetThicknessQuery())
|
||||
r := gDSVarCompRegex.FindStringSubmatch(item.GetThicknessQuery())
|
||||
if len(r) > 1 {
|
||||
var value float64
|
||||
dsname := strings.TrimSpace(r[1])
|
||||
log.Debugf("buildMetrics: datasource from thicknessquery : %s\n", dsname)
|
||||
for _, d := range gDataSources {
|
||||
if strings.EqualFold(d.Name, dsname) {
|
||||
value, err = d.GetData(timeRange)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
log.Debugf("buildMetrics: Replace %s thickness with %s\n", item.GetId(), fmt.Sprintf("%0.0f", value))
|
||||
item.SetThickness(value)
|
||||
} else {
|
||||
log.Errorf("buildMetrics: Item %s thicknessquery unparseable: %s\n", item.GetId(), item.GetThicknessQuery())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
@ -372,6 +436,25 @@ func reloadConfigFile() {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
switch viper.Get("language").(string) {
|
||||
case "english":
|
||||
gPrinter = message.NewPrinter(language.English)
|
||||
case "french":
|
||||
gPrinter = message.NewPrinter(language.French)
|
||||
case "german":
|
||||
gPrinter = message.NewPrinter(language.German)
|
||||
case "ukrainian":
|
||||
gPrinter = message.NewPrinter(language.Ukrainian)
|
||||
case "arabic":
|
||||
gPrinter = message.NewPrinter(language.Arabic)
|
||||
case "chinese":
|
||||
gPrinter = message.NewPrinter(language.Chinese)
|
||||
default:
|
||||
log.Errorf("Language not implented: %s. Fallback to english\n", viper.Get("language").(string))
|
||||
gPrinter = message.NewPrinter(language.English)
|
||||
}
|
||||
|
||||
// then clear current config, after acquiring WriteLock
|
||||
gCfgMutex.Lock()
|
||||
defer gCfgMutex.Unlock()
|
||||
|
24
nodes.go
24
nodes.go
@ -22,7 +22,8 @@ type Node struct {
|
||||
Color string `yaml:"color,omitempty" json:"color,omitempty"`
|
||||
Icon string `yaml:"icon,omitempty" json:"icon,omitempty"`
|
||||
NodeRadius int `yaml:"noderadius,omitempty" json:"noderadius,omitempty"`
|
||||
HighLighted bool `yaml:"highlighted,omitempty" json:"hightlighted,omitempty"`
|
||||
Highlighted bool `yaml:"highlighted,omitempty" json:"hightlighted,omitempty"`
|
||||
HighlightedQuery string `yaml:"highlightedquery,omitempty" json:"highlightedquery,omitempty"`
|
||||
}
|
||||
|
||||
func (n Node) GetId() string {
|
||||
@ -61,6 +62,25 @@ func (n *Node) SetSecondaryStat(stat string) {
|
||||
n.SecondaryStat = fmt.Sprintf("%s", stat)
|
||||
}
|
||||
|
||||
// Have to be implemented to satisfy Item interface
|
||||
func (n *Node) GetThicknessQuery() string {
|
||||
panic("Not an Edge")
|
||||
return ""
|
||||
}
|
||||
|
||||
// Have to be implemented to satisfy Item interface
|
||||
func (n *Node) SetThickness(thickness float64) {
|
||||
panic("Not an Edge")
|
||||
}
|
||||
|
||||
func (n *Node) GetHighlightedQuery() string {
|
||||
return n.HighlightedQuery
|
||||
}
|
||||
|
||||
func (n *Node) SetHighlighted(highlighted bool) {
|
||||
n.Highlighted = highlighted
|
||||
}
|
||||
|
||||
// Custom marshaler to not send (main|secondary)statquery
|
||||
func (n Node) MarshalJSON() ([]byte, error) {
|
||||
jsonRes := `{"name":"` + n.Name + `","id":"` + n.Id + `"`
|
||||
@ -85,7 +105,7 @@ func (n Node) MarshalJSON() ([]byte, error) {
|
||||
if n.NodeRadius > 0 {
|
||||
jsonRes += `,"noderadius":` + strconv.Itoa(n.NodeRadius)
|
||||
}
|
||||
if n.HighLighted {
|
||||
if n.Highlighted {
|
||||
jsonRes += `,"highlighted":true`
|
||||
}
|
||||
// TODO : n.StrokeDashArray
|
||||
|
Loading…
Reference in New Issue
Block a user