-
-
Notifications
You must be signed in to change notification settings - Fork 71
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #194 from K-Phoen/gauge-panel
Support "gauge" panel type in the builder
- Loading branch information
Showing
16 changed files
with
1,303 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"net/http" | ||
"os" | ||
|
||
"github.com/K-Phoen/grabana" | ||
"github.com/K-Phoen/grabana/dashboard" | ||
"github.com/K-Phoen/grabana/gauge" | ||
"github.com/K-Phoen/grabana/row" | ||
) | ||
|
||
func main() { | ||
if len(os.Args) != 3 { | ||
fmt.Fprint(os.Stderr, "Usage: go run main.go http://grafana-host:3000 api-key-string-here\n") | ||
os.Exit(1) | ||
} | ||
|
||
ctx := context.Background() | ||
client := grabana.NewClient(&http.Client{}, os.Args[1], grabana.WithAPIToken(os.Args[2])) | ||
|
||
// create the folder holding the dashboard for the service | ||
folder, err := client.FindOrCreateFolder(ctx, "Test Folder") | ||
if err != nil { | ||
fmt.Printf("Could not find or create folder: %s\n", err) | ||
os.Exit(1) | ||
} | ||
|
||
builder, err := dashboard.New( | ||
"Grabana - Gauge example", | ||
dashboard.AutoRefresh("30s"), | ||
dashboard.Time("now-30m", "now"), | ||
dashboard.Row( | ||
"Kubernetes", | ||
row.WithGauge( | ||
"Cluster Pod Usage", | ||
gauge.Span(6), | ||
gauge.Height("400px"), | ||
gauge.Unit("percentunit"), | ||
gauge.Decimals(2), | ||
gauge.AbsoluteThresholds([]gauge.ThresholdStep{ | ||
{Color: "#299c46"}, | ||
{Color: "rgba(237, 129, 40, 0.89)", Value: float64Ptr(0.8)}, | ||
{Color: "#d44a3a", Value: float64Ptr(0.9)}, | ||
}), | ||
gauge.WithPrometheusTarget( | ||
"sum(kube_pod_info{}) / sum(kube_node_status_allocatable{resource=\"pods\"})", | ||
), | ||
), | ||
), | ||
) | ||
if err != nil { | ||
fmt.Printf("Could not build dashboard: %s\n", err) | ||
os.Exit(1) | ||
} | ||
|
||
dash, err := client.UpsertDashboard(ctx, folder, builder) | ||
if err != nil { | ||
fmt.Printf("Could not create dashboard: %s\n", err) | ||
os.Exit(1) | ||
} | ||
|
||
fmt.Printf("The deed is done:\n%s\n", os.Args[1]+dash.URL) | ||
} | ||
|
||
func float64Ptr(input float64) *float64 { | ||
return &input | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,199 @@ | ||
package decoder | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/K-Phoen/grabana/gauge" | ||
"github.com/K-Phoen/grabana/row" | ||
) | ||
|
||
var ErrInvalidGaugeThresholdMode = fmt.Errorf("invalid gauge threshold mode") | ||
var ErrInvalidGaugeValueType = fmt.Errorf("invalid gauge value type") | ||
var ErrInvalidGaugeOrientation = fmt.Errorf("invalid gauge orientation") | ||
|
||
type GaugeThresholdStep struct { | ||
Color string | ||
Value *float64 `yaml:",omitempty"` | ||
} | ||
|
||
type DashboardGauge struct { | ||
Title string | ||
Description string `yaml:",omitempty"` | ||
Span float32 `yaml:",omitempty"` | ||
Height string `yaml:",omitempty"` | ||
Transparent bool `yaml:",omitempty"` | ||
Datasource string `yaml:",omitempty"` | ||
Repeat string `yaml:",omitempty"` | ||
Links DashboardPanelLinks `yaml:",omitempty"` | ||
Targets []Target | ||
|
||
Unit string `yaml:",omitempty"` | ||
Decimals *int `yaml:",omitempty"` | ||
|
||
Orientation string `yaml:",omitempty"` | ||
ValueType string `yaml:"value_type,omitempty"` | ||
TitleFontSize int `yaml:"title_font_size,omitempty"` | ||
ValueFontSize int `yaml:"value_font_size,omitempty"` | ||
|
||
ThresholdMode string `yaml:"threshold_mode,omitempty"` | ||
Thresholds []GaugeThresholdStep `yaml:",omitempty"` | ||
} | ||
|
||
func (gaugePanel DashboardGauge) toOption() (row.Option, error) { | ||
opts := []gauge.Option{} | ||
|
||
if gaugePanel.Description != "" { | ||
opts = append(opts, gauge.Description(gaugePanel.Description)) | ||
} | ||
if gaugePanel.Span != 0 { | ||
opts = append(opts, gauge.Span(gaugePanel.Span)) | ||
} | ||
if gaugePanel.Height != "" { | ||
opts = append(opts, gauge.Height(gaugePanel.Height)) | ||
} | ||
if gaugePanel.Transparent { | ||
opts = append(opts, gauge.Transparent()) | ||
} | ||
if gaugePanel.Datasource != "" { | ||
opts = append(opts, gauge.DataSource(gaugePanel.Datasource)) | ||
} | ||
if gaugePanel.Repeat != "" { | ||
opts = append(opts, gauge.Repeat(gaugePanel.Repeat)) | ||
} | ||
if len(gaugePanel.Links) != 0 { | ||
opts = append(opts, gauge.Links(gaugePanel.Links.toModel()...)) | ||
} | ||
if gaugePanel.Unit != "" { | ||
opts = append(opts, gauge.Unit(gaugePanel.Unit)) | ||
} | ||
if gaugePanel.Decimals != nil { | ||
opts = append(opts, gauge.Decimals(*gaugePanel.Decimals)) | ||
} | ||
if gaugePanel.TitleFontSize != 0 { | ||
opts = append(opts, gauge.TitleFontSize(gaugePanel.TitleFontSize)) | ||
} | ||
if gaugePanel.ValueFontSize != 0 { | ||
opts = append(opts, gauge.ValueFontSize(gaugePanel.ValueFontSize)) | ||
} | ||
|
||
if gaugePanel.Orientation != "" { | ||
opt, err := gaugePanel.orientationOpt() | ||
if err != nil { | ||
return nil, err | ||
} | ||
opts = append(opts, opt) | ||
} | ||
if gaugePanel.ValueType != "" { | ||
opt, err := gaugePanel.valueType() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
opts = append(opts, opt) | ||
} | ||
|
||
if len(gaugePanel.Thresholds) != 0 { | ||
opt, err := gaugePanel.thresholds() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
opts = append(opts, opt) | ||
} | ||
|
||
for _, t := range gaugePanel.Targets { | ||
opt, err := gaugePanel.target(t) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
opts = append(opts, opt) | ||
} | ||
|
||
return row.WithGauge(gaugePanel.Title, opts...), nil | ||
} | ||
|
||
func (gaugePanel DashboardGauge) thresholds() (gauge.Option, error) { | ||
thresholds := make([]gauge.ThresholdStep, 0, len(gaugePanel.Thresholds)) | ||
for _, threshold := range gaugePanel.Thresholds { | ||
thresholds = append(thresholds, gauge.ThresholdStep{ | ||
Color: threshold.Color, | ||
Value: threshold.Value, | ||
}) | ||
} | ||
|
||
switch gaugePanel.ThresholdMode { | ||
case "absolute": | ||
return gauge.AbsoluteThresholds(thresholds), nil | ||
case "": | ||
return gauge.AbsoluteThresholds(thresholds), nil | ||
case "relative": | ||
return gauge.RelativeThresholds(thresholds), nil | ||
} | ||
|
||
return nil, fmt.Errorf("got mode '%s': %w", gaugePanel.ThresholdMode, ErrInvalidGaugeThresholdMode) | ||
} | ||
|
||
func (gaugePanel DashboardGauge) valueType() (gauge.Option, error) { | ||
switch gaugePanel.ValueType { | ||
case "min": | ||
return gauge.ValueType(gauge.Min), nil | ||
case "max": | ||
return gauge.ValueType(gauge.Max), nil | ||
case "avg": | ||
return gauge.ValueType(gauge.Avg), nil | ||
|
||
case "count": | ||
return gauge.ValueType(gauge.Count), nil | ||
case "total": | ||
return gauge.ValueType(gauge.Total), nil | ||
case "range": | ||
return gauge.ValueType(gauge.Range), nil | ||
|
||
case "first": | ||
return gauge.ValueType(gauge.First), nil | ||
case "first_non_null": | ||
return gauge.ValueType(gauge.FirstNonNull), nil | ||
case "last": | ||
return gauge.ValueType(gauge.Last), nil | ||
case "last_non_null": | ||
return gauge.ValueType(gauge.LastNonNull), nil | ||
default: | ||
return nil, ErrInvalidGaugeValueType | ||
} | ||
} | ||
|
||
func (gaugePanel DashboardGauge) orientationOpt() (gauge.Option, error) { | ||
switch gaugePanel.Orientation { | ||
case "horizontal": | ||
return gauge.Orientation(gauge.OrientationHorizontal), nil | ||
case "vertical": | ||
return gauge.Orientation(gauge.OrientationVertical), nil | ||
case "auto": | ||
return gauge.Orientation(gauge.OrientationAuto), nil | ||
default: | ||
return nil, ErrInvalidGaugeOrientation | ||
} | ||
} | ||
|
||
func (gaugePanel DashboardGauge) target(t Target) (gauge.Option, error) { | ||
if t.Prometheus != nil { | ||
return gauge.WithPrometheusTarget(t.Prometheus.Query, t.Prometheus.toOptions()...), nil | ||
} | ||
if t.Graphite != nil { | ||
return gauge.WithGraphiteTarget(t.Graphite.Query, t.Graphite.toOptions()...), nil | ||
} | ||
if t.InfluxDB != nil { | ||
return gauge.WithInfluxDBTarget(t.InfluxDB.Query, t.InfluxDB.toOptions()...), nil | ||
} | ||
if t.Stackdriver != nil { | ||
stackdriverTarget, err := t.Stackdriver.toTarget() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return gauge.WithStackdriverTarget(stackdriverTarget), nil | ||
} | ||
|
||
return nil, ErrTargetNotConfigured | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package decoder | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/K-Phoen/grabana/gauge" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestGaugeValidValueTypes(t *testing.T) { | ||
testCases := []struct { | ||
input string | ||
expected string | ||
}{ | ||
{input: "min", expected: "min"}, | ||
{input: "max", expected: "max"}, | ||
{input: "avg", expected: "mean"}, | ||
{input: "count", expected: "count"}, | ||
{input: "total", expected: "sum"}, | ||
{input: "range", expected: "range"}, | ||
{input: "first", expected: "first"}, | ||
{input: "first_non_null", expected: "firstNotNull"}, | ||
{input: "last", expected: "last"}, | ||
{input: "last_non_null", expected: "lastNotNull"}, | ||
} | ||
|
||
for _, testCase := range testCases { | ||
tc := testCase | ||
|
||
t.Run(tc.input, func(t *testing.T) { | ||
req := require.New(t) | ||
|
||
panel := DashboardGauge{ValueType: tc.input} | ||
|
||
opt, err := panel.valueType() | ||
|
||
req.NoError(err) | ||
|
||
gaugePanel, err := gauge.New("") | ||
req.NoError(err) | ||
|
||
req.NoError(opt(gaugePanel)) | ||
|
||
req.Equal(tc.expected, gaugePanel.Builder.GaugePanel.Options.ReduceOptions.Calcs[0]) | ||
}) | ||
} | ||
} |
Oops, something went wrong.