diff --git a/README.md b/README.md index d021649..4faab62 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ ## Installation -Plugin can be installed via Git: +Plugin can be installed with GF_INSTALL_PLUGINS="flant-statusmap-panel" or you can use Git and clone this repo: ``` git clone git@github.com:flant/grafana-statusmap.git /var/lib/grafana/plugins/flant-statusmap-panel @@ -26,6 +26,7 @@ git clone git@github.com:flant/grafana-statusmap.git /var/lib/grafana/plugins/fl Alternatively, you can download [ZIP archive](https://github.com/flant/grafana-statusmap/archive/master.zip) of this repo and unpack it into /var/lib/grafana/plugins directory. + ## Motivation We had a desperate need to visualize a set of timeseries statuses over time period, so we can @@ -140,7 +141,13 @@ This plugin is based on "Heatmap" panel by Grafana and partly inspired by ideas #### Changelog +##### v0.0.2 + +- Install with GF_INSTALL_PLUGINS +- Fix legend overlap + ##### v0.0.1 - First public release + diff --git a/dist/README.md b/dist/README.md index e38d659..5a8a40e 100644 --- a/dist/README.md +++ b/dist/README.md @@ -4,11 +4,11 @@ ## Features -* Group values into rows and buckets by query's legend +* Grouping values into rows and buckets using legend from query * User defined color mapping -* Multiple values in bucket display in tooltip -* Interval shaping to better visual representation -* Represent null values as empty bucket or as zero value +* Multiple values in bucket can be displayed via tooltip +* Increasing rows/buckets' interval for better visual representation +* Representing null values as empty bucket or zero value ### Supported environment @@ -17,59 +17,104 @@ ## Installation -Plugin can be installed with git: +Plugin can be installed with GF_INSTALL_PLUGINS="flant-statusmap-panel" or you can use Git and clone this repo: ``` git clone git@github.com:flant/grafana-statusmap.git /var/lib/grafana/plugins/flant-statusmap-panel ``` -Or you can download ZIP archive of this repo and unpack it into /var/lib/grafana/plugins directory. +Alternatively, you can download [ZIP archive](https://github.com/flant/grafana-statusmap/archive/master.zip) +of this repo and unpack it into /var/lib/grafana/plugins directory. + ## Motivation -This plugin emerges from our needs to visually represent history of changes for a set of objects -with discrete statuses. -_Objects_ can be hosts, Kubernetes pods or coffee makers and _discrete statuses_ are a set -of predefined values: something like `ok` = 0, `off` = 1, `fail` = 2. +We had a desperate need to show a set of timeseries statuses over time period, so we can see +a history of changes for objects' statuses. Since we maintain a lot of Kubernetes clusters +(and related infrastructure), our main cases for that are visualization of servers & Kubernetes +pods health states as well as HTTP services health checks. We've tried a variety of Grafana +plugins available (they are listed in *Acknowledgements* below) but none of them could provide +the features and visualization really close to what we've been looking for. + +_Objects_ being visualized with this plugin may be different: not only IT components (e.g. server +hosts and Kubernetes pods) but just anything you can imagine like coffee makers on the picture +above. These objects should have _discrete statuses_ which are sets of predefined values, e.g. +`ok` = 0, `off` = 1, `fail` = 2, etc. ## Configuration -Recommended setup is to create query for each possible status value with similar legend: +### Prometheus + +To work with data from Prometheus you will need to setup discrete statuses of your objects. +Requirements to store these statuses in metrics are as follows: +* metrics should have two values: `0` and `1`; +* there should be a label with status' value. + +When it's done, you can collect all the data via query, e.g.: + +``` +(max_over_time(coffee_maker_status{status=""}[$__interval]) == 1) * +``` + +If there was no such status (``) during query's interval, Prometheus would +return nothing. Otherwise, status' value will be returned. + +For example, if you have 5 statuses and a metric (`coffee_maker_status`) with 5 allowed +values (`0`, `1`, `2`, `3`, `4`), you should transform this metric using the following rule: + +``` +- record: coffee_maker_status:discrete + expr: | + count_values("status", coffee_maker_status) +``` + +That's how `coffee_maker_status` metric with value `3` will be transformed into new metric: + +``` +coffee_maker_status:discrete{status="3"} 1 +``` + +Now, when Prometheus has `0` and `1` values for each status, all these metrics can be +aggregated, so you will get all available statuses of your objects over time. + +### Panel + +First of all, an individual query for each possible status' value should be created. +Each query should also have similar legend for grouping: ![Query setup](https://raw.githubusercontent.com/flant/grafana-statusmap/master/src/img/queries-example.png) -Next define color mapping for status values in __Discrete__ color mode. +Then, color mapping for status' values should be defined in __Discrete__ color mode: ![Color mapping](https://raw.githubusercontent.com/flant/grafana-statusmap/master/src/img/color-mapping.png) - -__Spectrum__ and __Opacity__ color modes works as in a [Heatmap](https://grafana.com/plugins/heatmap) plugin. +_Note: __Spectrum__ and __Opacity__ color modes function the same way they do in [Heatmap](https://grafana.com/plugins/heatmap) plugin._ ### More options ![Bucket options](https://raw.githubusercontent.com/flant/grafana-statusmap/master/src/img/options-bucket.png) -__Multiple values__ check determine multiple values display mode. If check is unset then multiple values -for one bucket treated as error. If check is on then color for bucket determined -by value with least index in color mapping. +__Multiple values__ checkbox specifies how they should be displayed: +* If it's off, multiple values for one bucket are treated as error; +* If it's on, color for such bucket would be determined by the value having least index in color mapping. ![Color mapping](https://raw.githubusercontent.com/flant/grafana-statusmap/master/src/img/multiple-values-error.png) -__Null values__ can be treated as empty buckets or displayed as color of 0 value. +__Null values__ can be treated as empty buckets or displayed with the color of `0` value. ![Color mapping](https://raw.githubusercontent.com/flant/grafana-statusmap/master/src/img/null-as-empty.png) -__Min width__ and __spacing__ are determine minimal bucket width and spacing between buckets. -__Rounding__ is for round edges. +__Min width__ and __spacing__ are used to specify minimal bucket width and spacing between buckets. +__Rounding__ may be used to round edges. ![Min width, spacing, rounding](https://raw.githubusercontent.com/flant/grafana-statusmap/master/src/img/min-width-spacing-rounding.png) ## Development -The easy way to test and develop plugin is to run Grafana instance in docker with following command in the directory containing the plugin. -This will expose the local plugin on your machine to the Grafana container. +To test and improve the plugin you can run Grafana instance in Docker using following command (in +the directory containing Statusmap plugin): ``` docker run --rm -it -v $PWD:/var/lib/grafana/plugins/flant-statusmap-panel \ @@ -78,7 +123,8 @@ docker run --rm -it -v $PWD:/var/lib/grafana/plugins/flant-statusmap-panel \ grafana/grafana:5.1.3 ``` -Now run `grunt` to compile dist directory and start changes watcher: +This will expose local plugin from your machine to Grafana container. Now run `grunt` to compile +dist directory and start changes watcher: ``` grunt watch @@ -86,13 +132,19 @@ grunt watch ## Acknowledgements -Idea of a plugin comes from Dmitry Stolyarov @distol, initial version written by Sergey Gnuskov @gsmetal and final changes made by Ivan Mikheykin @diafour. +The first public release of this plugin has been fully made by [Flant](https://flant.com/) engineers. The whole idea has come from Dmitry Stolyarov ([@distol](https://github.com/distol)), initial version has been written by Sergey Gnuskov ([@gsmetal](https://github.com/gsmetal)) and final changes has been made by Ivan Mikheykin ([@diafour](https://github.com/diafour)). -This plugin is based on a "Heatmap" panel by Grafana and inspired by ideas from Carpet plot, Discrete panel, Status Panel, Status Dot, Status By Group. +This plugin is based on "Heatmap" panel by Grafana and partly inspired by ideas from Carpet plot, Discrete panel, Status Panel, Status Dot, Status By Group. #### Changelog +##### v0.0.2 + +- Install with GF_INSTALL_PLUGINS +- Fix legend overlap + ##### v0.0.1 - First public release + diff --git a/dist/color_legend.js b/dist/color_legend.js index f085483..c165976 100644 --- a/dist/color_legend.js +++ b/dist/color_legend.js @@ -10,7 +10,7 @@ System.register(['angular', 'lodash', 'jquery', 'd3', './libs/d3-scale-chromatic var legend = d3.select(legendElem.get(0)); clearLegend(elem); - var legendWidth = Math.floor(legendElem.outerWidth()) - 30; + var legendWidth = Math.floor(legendElem.outerWidth()) - 30; // narrow legendWidth by 30px to get space for first and last tick values var legendHeight = legendElem.attr("height"); var rangeStep = 1; @@ -27,8 +27,9 @@ System.register(['angular', 'lodash', 'jquery', 'd3', './libs/d3-scale-chromatic var colorScale = getColorScale(colorScheme, maxValue, minValue); legend.selectAll(".status-heatmap-color-legend-rect").data(valuesRange).enter().append("rect").attr("x", function (d) { - return d * widthFactor; - }).attr("y", 0).attr("width", rangeStep * widthFactor + 1) // Overlap rectangles to prevent gaps + return d * widthFactor + 10; + }) // shift all color rectangles to the right + .attr("y", 0).attr("width", rangeStep * widthFactor + 1) // Overlap rectangles to prevent gaps .attr("height", legendHeight).attr("stroke-width", 0).attr("fill", function (d) { return colorScale(d); }); @@ -41,7 +42,7 @@ System.register(['angular', 'lodash', 'jquery', 'd3', './libs/d3-scale-chromatic var legend = d3.select(legendElem.get(0)); clearLegend(elem); - var legendWidth = Math.floor(legendElem.outerWidth()) - 30; + var legendWidth = Math.floor(legendElem.outerWidth()) - 30; // narrow legendWidth by 30px to get space for first and last tick values var legendHeight = legendElem.attr("height"); var rangeStep = 10; @@ -55,8 +56,9 @@ System.register(['angular', 'lodash', 'jquery', 'd3', './libs/d3-scale-chromatic var opacityScale = getOpacityScale(options, maxValue, minValue); legend.selectAll(".status-heatmap-opacity-legend-rect").data(valuesRange).enter().append("rect").attr("x", function (d) { - return d * widthFactor; - }).attr("y", 0).attr("width", rangeStep * widthFactor).attr("height", legendHeight).attr("stroke-width", 0).attr("fill", options.cardColor).style("opacity", function (d) { + return d * widthFactor + 10; + }) // shift all opacity rectangles to the right + .attr("y", 0).attr("width", rangeStep * widthFactor).attr("height", legendHeight).attr("stroke-width", 0).attr("fill", options.cardColor).style("opacity", function (d) { return opacityScale(d); }); diff --git a/dist/img/queries-example.png b/dist/img/queries-example.png index 041567b..7150b44 100644 Binary files a/dist/img/queries-example.png and b/dist/img/queries-example.png differ diff --git a/src/color_legend.js b/src/color_legend.js index cf0a2e1..7c99ffe 100644 --- a/src/color_legend.js +++ b/src/color_legend.js @@ -89,7 +89,7 @@ function drawColorLegend(elem, colorScheme, rangeFrom, rangeTo, maxValue, minVal let legend = d3.select(legendElem.get(0)); clearLegend(elem); - let legendWidth = Math.floor(legendElem.outerWidth()) - 30; + let legendWidth = Math.floor(legendElem.outerWidth()) - 30; // narrow legendWidth by 30px to get space for first and last tick values let legendHeight = legendElem.attr("height"); let rangeStep = 1; @@ -108,7 +108,7 @@ function drawColorLegend(elem, colorScheme, rangeFrom, rangeTo, maxValue, minVal legend.selectAll(".status-heatmap-color-legend-rect") .data(valuesRange) .enter().append("rect") - .attr("x", d => d * widthFactor) + .attr("x", d => d * widthFactor + 10) // shift all color rectangles to the right .attr("y", 0) .attr("width", rangeStep * widthFactor + 1) // Overlap rectangles to prevent gaps .attr("height", legendHeight) @@ -123,7 +123,7 @@ function drawOpacityLegend(elem, options, rangeFrom, rangeTo, maxValue, minValue let legend = d3.select(legendElem.get(0)); clearLegend(elem); - let legendWidth = Math.floor(legendElem.outerWidth()) - 30; + let legendWidth = Math.floor(legendElem.outerWidth()) - 30; // narrow legendWidth by 30px to get space for first and last tick values let legendHeight = legendElem.attr("height"); let rangeStep = 10; @@ -139,7 +139,7 @@ function drawOpacityLegend(elem, options, rangeFrom, rangeTo, maxValue, minValue legend.selectAll(".status-heatmap-opacity-legend-rect") .data(valuesRange) .enter().append("rect") - .attr("x", d => d * widthFactor) + .attr("x", d => d * widthFactor + 10) // shift all opacity rectangles to the right .attr("y", 0) .attr("width", rangeStep * widthFactor) .attr("height", legendHeight)