From 23f51f9f7bbbbdf80f76ae01cb88076fc0bd4a2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Wed, 20 Mar 2019 20:30:58 +0000 Subject: [PATCH 1/3] fix(demo): add missing job labels We have config for job labels, but no labels on alerts, fix it --- demo/generator.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/demo/generator.py b/demo/generator.py index de2ba2f02..a5b1a8c52 100755 --- a/demo/generator.py +++ b/demo/generator.py @@ -137,7 +137,7 @@ class AlwaysOnAlert(AlertGenerator): def _gen(size, cluster): return [newAlert( self._labels(instance="server{}".format(i), cluster=cluster, - severity="info"), + severity="info", job="node_exporter"), self._annotations( summary="Silence this alert, it's always firing") ) for i in xrange(1, size)] @@ -153,7 +153,7 @@ class RandomInstances(AlertGenerator): return [ newAlert( self._labels(instance="server{}".format(i), cluster="staging", - severity="warning"), + severity="warning", job="node_exporter"), self._annotations( dashboard="https://www.google.com/search?q=" "server{}".format(i)) @@ -173,7 +173,8 @@ class RandomName(AlertGenerator): newAlert( self._labels(alertname="Alert Nr {}".format(throw), instance="server{}".format(i), - cluster="dev", severity="info"), + cluster="dev", severity="info", + job="random_exporter"), self._annotations( summary="This is a random alert", dashboard="https://www.google.com/search?q=" @@ -194,7 +195,7 @@ class LowChance(AlertGenerator): return [ newAlert( self._labels(instance="server{}".format(i), cluster="dev", - severity="critical"), + severity="critical", job="random_exporter"), self._annotations() ) for i in xrange(0, 3) ] @@ -208,7 +209,7 @@ class TimeAnnotation(AlertGenerator): def alerts(self): return [ newAlert(self._labels(instance="server1", cluster="prod", - severity="warning"), + severity="warning", job="ntp_exporter"), self._annotations(time=str(int(time.time()))) ) ] @@ -226,7 +227,8 @@ class DiskFreeLowAlert(AlertGenerator): newAlert(self._labels(instance="server{}".format(i), cluster="prod", device="/dev/sda{}".format(i), - mount_point="/disk", severity="critical"), + mount_point="/disk", severity="critical", + job="node_exporter"), self._annotations( summary="Only {}% free space left on /disk".format( spaceFree), @@ -243,7 +245,7 @@ class SilencedAlert(AlertGenerator): def alerts(self): return [ newAlert(self._labels(instance="server1", cluster="prod", - severity="info"), + severity="info", job="mysql_exporter"), self._annotations( alertReference="https://www." "youtube.com/watch?v=dQw4w9WgXcQ") @@ -279,7 +281,7 @@ class MixedAlerts(AlertGenerator): def alerts(self): return [ newAlert(self._labels(instance="server{}".format(i), cluster="prod", - severity="warning"), + severity="warning", job="node_exporter"), self._annotations( alertReference="https://www." "youtube.com/watch?v=dQw4w9WgXcQ") @@ -310,7 +312,7 @@ class LongNameAlerts(AlertGenerator): def _gen(size, cluster): return [newAlert( self._labels(instance="server{}".format(i), cluster=cluster, - severity="info"), + severity="info", job="textfile_exporter"), self._annotations( verylong="Lorem ipsum dolor sit amet, consectetur " "adipiscing elit, sed do eiusmod tempor incididunt" @@ -336,7 +338,7 @@ class InhibitedAlert(AlertGenerator): def alerts(self): return [ newAlert(self._labels(instance="server1", cluster="prod", - severity="warning"), + severity="warning", job="textfile_exporter"), self._annotations() ) ] @@ -349,7 +351,7 @@ class InhibitingAlert(AlertGenerator): def alerts(self): return [ newAlert(self._labels(instance="server1", cluster="prod", - severity="critical"), + severity="critical", job="textfile_exporter"), self._annotations() ) ] @@ -362,7 +364,7 @@ class SilencedAlertWithJiraLink(AlertGenerator): def alerts(self): return [ newAlert(self._labels(instance="server%d" % i, cluster="staging", - severity="critical"), + severity="critical", job="node_exporter"), self._annotations( dashboard="https://www.atlassian.com/software/jira") ) for i in xrange(1, 9) From 4ef9052a98d9cd886239e7e65b82a15ec6efa1fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Wed, 20 Mar 2019 21:15:04 +0000 Subject: [PATCH 2/3] feat(api): allow using wildcards with custom colors Fixes #537 --- docs/CONFIGURATION.md | 19 ++++++++++- internal/transform/color_test.go | 9 +++++ internal/transform/colors.go | 58 +++++++++++++++++++------------- 3 files changed, 62 insertions(+), 24 deletions(-) diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index 6b39c1304..8f18b4f6d 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -345,7 +345,10 @@ labels: to configure a set of labels with custom predefined colors applied to them rather than generated. Colors can be defined as RGB or HEX values. Value is a mapping with `label name` -> `label value` -> `color`, see examples - below. + below. Wildcard values (`*`) are allowed to provide a fallback custom color, + which can also be used instead of `color:static` option. Wildcard options + are evaluated after exact matches, so it's possible to mix explicit and + wildcard values. Note: this option is not available via environment variables, you can only set it via the config file. - `keep` - list of allowed labels, if empty all labels are allowed. @@ -398,6 +401,20 @@ labels: critical: "#ff220c" ``` +Example with a wildcard value, `info`, `warning` and `critical` will get colors +as below, but any value not matching those 3 values will use the color from `*`: + +```YAML +labels: + color: + custom: + severity: + "*": "#736598" + info: "#87c4e0" + warning: "#ffae42" + critical: "#ff220c" +``` + Defaults: ```YAML diff --git a/internal/transform/color_test.go b/internal/transform/color_test.go index d0a993891..85b17a4c7 100644 --- a/internal/transform/color_test.go +++ b/internal/transform/color_test.go @@ -75,6 +75,15 @@ var colorTests = []colorTest{ }, colors: map[string]string{}, }, + { + customLabels: map[string]map[string]string{ + "node": map[string]string{"*": "#123"}, + }, + labels: map[string]string{ + "node": "localhost", + }, + colors: map[string]string{"node": "localhost"}, + }, } func TestColorLabel(t *testing.T) { diff --git a/internal/transform/colors.go b/internal/transform/colors.go index 9800a1f22..32c48718e 100644 --- a/internal/transform/colors.go +++ b/internal/transform/colors.go @@ -39,6 +39,29 @@ func rgbToBrightness(r, g, b uint8) int32 { return ((int32(r) * 299) + (int32(g) * 587) + (int32(b) * 114)) / 1000 } +func parseCustomColor(colorStore models.LabelsColorMap, key, val, customColor string) { + color, err := plcolors.Parse(customColor) + if err != nil { + log.Warningf("Failed to parse custom color for %s=%s: %s", key, val, err) + return + } + rgb := color.ToRGB() + bc := models.Color{ + Red: rgb.R, + Green: rgb.G, + Blue: rgb.B, + Alpha: 255, + } + brightness := rgbToBrightness(bc.Red, bc.Green, bc.Blue) + if _, found := colorStore[key]; !found { + colorStore[key] = make(map[string]models.LabelColors) + } + colorStore[key][val] = models.LabelColors{ + Brightness: brightness, + Background: bc, + } +} + // ColorLabel update karmaColorMap object with a color object generated // from label key and value passed here // It's used to generate unique colors for configured labels @@ -46,30 +69,19 @@ func ColorLabel(colorStore models.LabelsColorMap, key string, val string) { // first handle custom colors _, ok := config.Config.Labels.Color.Custom[key] if ok { - c, ol := config.Config.Labels.Color.Custom[key][val] - if ol { - color, err := plcolors.Parse(c) - if err != nil { - log.Warningf("Failed to parse custom color for %s=%s: %s", key, val, err) - return - } - rgb := color.ToRGB() - bc := models.Color{ - Red: rgb.R, - Green: rgb.G, - Blue: rgb.B, - Alpha: 255, - } - brightness := rgbToBrightness(bc.Red, bc.Green, bc.Blue) - if _, found := colorStore[key]; !found { - colorStore[key] = make(map[string]models.LabelColors) - } - colorStore[key][val] = models.LabelColors{ - Brightness: brightness, - Background: bc, - } + // try matching both key and value + customColor, found := config.Config.Labels.Color.Custom[key][val] + if found { + parseCustomColor(colorStore, key, val, customColor) + return + } + + // if not found try matching key and wildcard (*) + customColor, found = config.Config.Labels.Color.Custom[key]["*"] + if found { + parseCustomColor(colorStore, key, val, customColor) + return } - return } // if no custom color is found then generate unique colors if needed From 67db581aa5f67cc74bc7510ab85bac6fd20a4fcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Wed, 20 Mar 2019 21:15:26 +0000 Subject: [PATCH 3/3] chore(demo): add a region label with wildcard color --- demo/generator.py | 33 +++++++++++++++++++++------------ demo/karma.yaml | 2 ++ 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/demo/generator.py b/demo/generator.py index a5b1a8c52..07537b476 100755 --- a/demo/generator.py +++ b/demo/generator.py @@ -137,7 +137,7 @@ class AlwaysOnAlert(AlertGenerator): def _gen(size, cluster): return [newAlert( self._labels(instance="server{}".format(i), cluster=cluster, - severity="info", job="node_exporter"), + severity="info", job="node_exporter", region="US"), self._annotations( summary="Silence this alert, it's always firing") ) for i in xrange(1, size)] @@ -153,7 +153,8 @@ class RandomInstances(AlertGenerator): return [ newAlert( self._labels(instance="server{}".format(i), cluster="staging", - severity="warning", job="node_exporter"), + severity="warning", job="node_exporter", + region="US"), self._annotations( dashboard="https://www.google.com/search?q=" "server{}".format(i)) @@ -174,7 +175,7 @@ class RandomName(AlertGenerator): self._labels(alertname="Alert Nr {}".format(throw), instance="server{}".format(i), cluster="dev", severity="info", - job="random_exporter"), + job="random_exporter", region="EU"), self._annotations( summary="This is a random alert", dashboard="https://www.google.com/search?q=" @@ -195,7 +196,8 @@ class LowChance(AlertGenerator): return [ newAlert( self._labels(instance="server{}".format(i), cluster="dev", - severity="critical", job="random_exporter"), + severity="critical", job="random_exporter", + region="EU"), self._annotations() ) for i in xrange(0, 3) ] @@ -209,7 +211,8 @@ class TimeAnnotation(AlertGenerator): def alerts(self): return [ newAlert(self._labels(instance="server1", cluster="prod", - severity="warning", job="ntp_exporter"), + severity="warning", job="ntp_exporter", + region="AP"), self._annotations(time=str(int(time.time()))) ) ] @@ -228,7 +231,7 @@ class DiskFreeLowAlert(AlertGenerator): cluster="prod", device="/dev/sda{}".format(i), mount_point="/disk", severity="critical", - job="node_exporter"), + job="node_exporter", region="AP"), self._annotations( summary="Only {}% free space left on /disk".format( spaceFree), @@ -245,7 +248,8 @@ class SilencedAlert(AlertGenerator): def alerts(self): return [ newAlert(self._labels(instance="server1", cluster="prod", - severity="info", job="mysql_exporter"), + severity="info", job="mysql_exporter", + region="SA"), self._annotations( alertReference="https://www." "youtube.com/watch?v=dQw4w9WgXcQ") @@ -281,7 +285,8 @@ class MixedAlerts(AlertGenerator): def alerts(self): return [ newAlert(self._labels(instance="server{}".format(i), cluster="prod", - severity="warning", job="node_exporter"), + severity="warning", job="node_exporter", + region="SA"), self._annotations( alertReference="https://www." "youtube.com/watch?v=dQw4w9WgXcQ") @@ -312,7 +317,8 @@ class LongNameAlerts(AlertGenerator): def _gen(size, cluster): return [newAlert( self._labels(instance="server{}".format(i), cluster=cluster, - severity="info", job="textfile_exporter"), + severity="info", job="textfile_exporter", + region="CN"), self._annotations( verylong="Lorem ipsum dolor sit amet, consectetur " "adipiscing elit, sed do eiusmod tempor incididunt" @@ -338,7 +344,8 @@ class InhibitedAlert(AlertGenerator): def alerts(self): return [ newAlert(self._labels(instance="server1", cluster="prod", - severity="warning", job="textfile_exporter"), + severity="warning", job="textfile_exporter", + region="CN"), self._annotations() ) ] @@ -351,7 +358,8 @@ class InhibitingAlert(AlertGenerator): def alerts(self): return [ newAlert(self._labels(instance="server1", cluster="prod", - severity="critical", job="textfile_exporter"), + severity="critical", job="textfile_exporter", + region="CN"), self._annotations() ) ] @@ -364,7 +372,8 @@ class SilencedAlertWithJiraLink(AlertGenerator): def alerts(self): return [ newAlert(self._labels(instance="server%d" % i, cluster="staging", - severity="critical", job="node_exporter"), + severity="critical", job="node_exporter", + region="AF"), self._annotations( dashboard="https://www.atlassian.com/software/jira") ) for i in xrange(1, 9) diff --git a/demo/karma.yaml b/demo/karma.yaml index 8d8e27c9a..e62342c37 100644 --- a/demo/karma.yaml +++ b/demo/karma.yaml @@ -41,6 +41,8 @@ labels: - instance - "@receiver" custom: + region: + "*": "#736598" severity: info: "#87c4e0" warning: "#ffae42"