diff options
author | Jose Ivan Vargas <jvargas@gitlab.com> | 2018-03-21 22:36:43 +0300 |
---|---|---|
committer | Jose Ivan Vargas <jvargas@gitlab.com> | 2018-04-06 22:37:46 +0300 |
commit | 657fea868834ef239ab312381ac0956f3a84a07c (patch) | |
tree | c89112229b808ea79e74365fdbd67ae28cc27cfd /app/assets/javascripts | |
parent | 9e3cdc02e62c3fc24cbd82615b9e7733136e34b7 (diff) |
Add summary statistics prometheus dashboard
Diffstat (limited to 'app/assets/javascripts')
5 files changed, 255 insertions, 191 deletions
diff --git a/app/assets/javascripts/monitoring/components/graph.vue b/app/assets/javascripts/monitoring/components/graph.vue index 04d546fafa0..9bf32d8a2da 100644 --- a/app/assets/javascripts/monitoring/components/graph.vue +++ b/app/assets/javascripts/monitoring/components/graph.vue @@ -3,6 +3,7 @@ import { scaleLinear, scaleTime } from 'd3-scale'; import { axisLeft, axisBottom } from 'd3-axis'; import { max, extent } from 'd3-array'; import { select } from 'd3-selection'; +import GraphAxis from './graph/axis.vue'; import GraphLegend from './graph/legend.vue'; import GraphFlag from './graph/flag.vue'; import GraphDeployment from './graph/deployment.vue'; @@ -18,10 +19,11 @@ const d3 = { scaleLinear, scaleTime, axisLeft, axisBottom, max, extent, select } export default { components: { - GraphLegend, + GraphAxis, GraphFlag, GraphDeployment, GraphPath, + GraphLegend, }, mixins: [MonitoringMixin], props: { @@ -138,7 +140,7 @@ export default { this.legendTitle = query.label || 'Average'; this.graphWidth = this.$refs.baseSvg.clientWidth - this.margin.left - this.margin.right; this.graphHeight = this.graphHeight - this.margin.top - this.margin.bottom; - this.baseGraphHeight = this.graphHeight; + this.baseGraphHeight = this.graphHeight - 50; this.baseGraphWidth = this.graphWidth; // pixel offsets inside the svg and outside are not 1:1 @@ -177,14 +179,10 @@ export default { this.graphHeightOffset, ); - if (!this.showLegend) { - this.baseGraphHeight -= 50; - } else if (this.timeSeries.length > 3) { - this.baseGraphHeight = this.baseGraphHeight += (this.timeSeries.length - 3) * 20; - } - - const axisXScale = d3.scaleTime().range([0, this.graphWidth - 70]); - const axisYScale = d3.scaleLinear().range([this.graphHeight - this.graphHeightOffset, 0]); + const axisXScale = d3.scaleTime() + .range([0, this.graphWidth - 70]); + const axisYScale = d3.scaleLinear() + .range([this.graphHeight - this.graphHeightOffset, 0]); const allValues = this.timeSeries.reduce((all, { values }) => all.concat(values), []); axisXScale.domain(d3.extent(allValues, d => d.time)); @@ -251,17 +249,12 @@ export default { class="y-axis" transform="translate(70, 20)" /> - <graph-legend + <graph-axis :graph-width="graphWidth" :graph-height="graphHeight" :margin="margin" :measurements="measurements" - :legend-title="legendTitle" :y-axis-label="yAxisLabel" - :time-series="timeSeries" - :unit-of-display="unitOfDisplay" - :current-data-index="currentDataIndex" - :show-legend-group="showLegend" /> <svg class="graph-data" @@ -306,5 +299,12 @@ export default { :deployment-flag-data="deploymentFlagData" /> </div> + <graph-legend + v-if="showLegend" + :legend-title="legendTitle" + :time-series="timeSeries" + :current-data-index="currentDataIndex" + :unit-of-display="unitOfDisplay" + /> </div> </template> diff --git a/app/assets/javascripts/monitoring/components/graph/axis.vue b/app/assets/javascripts/monitoring/components/graph/axis.vue new file mode 100644 index 00000000000..ae397fc39e2 --- /dev/null +++ b/app/assets/javascripts/monitoring/components/graph/axis.vue @@ -0,0 +1,127 @@ +<script> +export default { + props: { + graphWidth: { + type: Number, + required: true, + }, + graphHeight: { + type: Number, + required: true, + }, + margin: { + type: Object, + required: true, + }, + measurements: { + type: Object, + required: true, + }, + yAxisLabel: { + type: String, + required: true, + }, + }, + data() { + return { + yLabelWidth: 0, + yLabelHeight: 0, + }; + }, + computed: { + textTransform() { + const yCoordinate = + (this.graphHeight - + this.margin.top + + this.measurements.axisLabelLineOffset) / + 2 || 0; + + return `translate(15, ${yCoordinate}) rotate(-90)`; + }, + + rectTransform() { + const yCoordinate = + (this.graphHeight - + this.margin.top + + this.measurements.axisLabelLineOffset) / + 2 + + this.yLabelWidth / 2 || 0; + + return `translate(0, ${yCoordinate}) rotate(-90)`; + }, + + xPosition() { + return ( + (this.graphWidth + this.measurements.axisLabelLineOffset) / 2 - + this.margin.right || 0 + ); + }, + + yPosition() { + return ( + this.graphHeight - + this.margin.top + + this.measurements.axisLabelLineOffset || 0 + ); + }, + }, + mounted() { + this.$nextTick(() => { + const bbox = this.$refs.ylabel.getBBox(); + this.yLabelWidth = bbox.width + 10; // Added some padding + this.yLabelHeight = bbox.height + 5; + }); + }, +}; +</script> +<template> + <g class="axis-label-container"> + <line + class="label-x-axis-line" + stroke="#000000" + stroke-width="1" + x1="10" + :y1="yPosition" + :x2="graphWidth + 20" + :y2="yPosition" + /> + <line + class="label-y-axis-line" + stroke="#000000" + stroke-width="1" + x1="10" + y1="0" + :x2="10" + :y2="yPosition" + /> + <rect + class="rect-axis-text" + :transform="rectTransform" + :width="yLabelWidth" + :height="yLabelHeight" + /> + <text + class="label-axis-text y-label-text" + text-anchor="middle" + :transform="textTransform" + ref="ylabel" + > + {{ yAxisLabel }} + </text> + <rect + class="rect-axis-text" + :x="xPosition + 60" + :y="graphHeight - 80" + width="35" + height="50" + /> + <text + class="label-axis-text x-label-text" + :x="xPosition + 60" + :y="yPosition" + dy=".35em" + > + Time + </text> + </g> +</template> diff --git a/app/assets/javascripts/monitoring/components/graph/flag.vue b/app/assets/javascripts/monitoring/components/graph/flag.vue index 906c7c51f52..8e6a830f08c 100644 --- a/app/assets/javascripts/monitoring/components/graph/flag.vue +++ b/app/assets/javascripts/monitoring/components/graph/flag.vue @@ -160,7 +160,7 @@ export default { </div> </div> <div class="popover-content"> - <table> + <table class="prometheus-table"> <tr v-for="(series, index) in timeSeries" :key="index" diff --git a/app/assets/javascripts/monitoring/components/graph/legend.vue b/app/assets/javascripts/monitoring/components/graph/legend.vue index a7a058a9203..ac1c6e34cc4 100644 --- a/app/assets/javascripts/monitoring/components/graph/legend.vue +++ b/app/assets/javascripts/monitoring/components/graph/legend.vue @@ -1,112 +1,50 @@ <script> -import { formatRelevantDigits } from '../../../lib/utils/number_utils'; +import { formatRelevantDigits } from '~/lib/utils/number_utils'; export default { props: { - graphWidth: { - type: Number, - required: true, - }, - graphHeight: { - type: Number, - required: true, - }, - margin: { - type: Object, - required: true, - }, - measurements: { - type: Object, - required: true, - }, legendTitle: { type: String, required: true, }, - yAxisLabel: { - type: String, - required: true, - }, timeSeries: { type: Array, required: true, }, - unitOfDisplay: { - type: String, - required: true, - }, currentDataIndex: { type: Number, required: true, }, - showLegendGroup: { - type: Boolean, - required: false, - default: true, - }, - }, - data() { - return { - yLabelWidth: 0, - yLabelHeight: 0, - seriesXPosition: 0, - metricUsageXPosition: 0, - }; - }, - computed: { - textTransform() { - const yCoordinate = - (this.graphHeight - this.margin.top + this.measurements.axisLabelLineOffset) / 2 || 0; - - return `translate(15, ${yCoordinate}) rotate(-90)`; - }, - rectTransform() { - const yCoordinate = - (this.graphHeight - this.margin.top + this.measurements.axisLabelLineOffset) / 2 + - this.yLabelWidth / 2 || 0; - - return `translate(0, ${yCoordinate}) rotate(-90)`; - }, - xPosition() { - return (this.graphWidth + this.measurements.axisLabelLineOffset) / 2 - this.margin.right || 0; - }, - yPosition() { - return this.graphHeight - this.margin.top + this.measurements.axisLabelLineOffset || 0; + unitOfDisplay: { + type: String, + required: true, }, }, - mounted() { - this.$nextTick(() => { - const bbox = this.$refs.ylabel.getBBox(); - this.metricUsageXPosition = 0; - this.seriesXPosition = 0; - if (this.$refs.legendTitleSvg != null) { - this.seriesXPosition = this.$refs.legendTitleSvg[0].getBBox().width; - } - if (this.$refs.seriesTitleSvg != null) { - this.metricUsageXPosition = this.$refs.seriesTitleSvg[0].getBBox().width; - } - this.yLabelWidth = bbox.width + 10; // Added some padding - this.yLabelHeight = bbox.height + 5; - }); - }, methods: { - translateLegendGroup(index) { - return `translate(0, ${12 * index})`; - }, formatMetricUsage(series) { const value = - series.values[this.currentDataIndex] && series.values[this.currentDataIndex].value; + series.values[this.currentDataIndex] && + series.values[this.currentDataIndex].value; if (isNaN(value)) { return '-'; } return `${formatRelevantDigits(value)} ${this.unitOfDisplay}`; }, + createSeriesString(index, series) { if (series.metricTag) { return `${series.metricTag} ${this.formatMetricUsage(series)}`; } - return `${this.legendTitle} series ${index + 1} ${this.formatMetricUsage(series)}`; + return `${this.legendTitle} series ${index + 1} ${this.formatMetricUsage( + series, + )}`; + }, + + summaryMetrics(series) { + return `Avg: ${formatRelevantDigits(series.average)} ${this.unitOfDisplay}, + Max: ${formatRelevantDigits(series.max)} ${this.unitOfDisplay}`; }, + strokeDashArray(type) { if (type === 'dashed') return '6, 3'; if (type === 'dotted') return '3, 3'; @@ -116,89 +54,38 @@ export default { }; </script> <template> - <g class="axis-label-container"> - <line - class="label-x-axis-line" - stroke="#000000" - stroke-width="1" - x1="10" - :y1="yPosition" - :x2="graphWidth + 20" - :y2="yPosition" - /> - <line - class="label-y-axis-line" - stroke="#000000" - stroke-width="1" - x1="10" - y1="0" - :x2="10" - :y2="yPosition" - /> - <rect - class="rect-axis-text" - :transform="rectTransform" - :width="yLabelWidth" - :height="yLabelHeight" - /> - <text - class="label-axis-text y-label-text" - text-anchor="middle" - :transform="textTransform" - ref="ylabel" - > - {{ yAxisLabel }} - </text> - <rect - class="rect-axis-text" - :x="xPosition + 60" - :y="graphHeight - 80" - width="35" - height="50" - /> - <text - class="label-axis-text x-label-text" - :x="xPosition + 60" - :y="yPosition" - dy=".35em" - > - Time - </text> - <template v-if="showLegendGroup"> - <g - class="legend-group" + <div class="prometheus-graph-legends prepend-left-10"> + <table class="prometheus-table"> + <tr v-for="(series, index) in timeSeries" :key="index" - :transform="translateLegendGroup(index)" > - <line - :stroke="series.lineColor" - :stroke-width="measurements.legends.height" - :stroke-dasharray="strokeDashArray(series.lineStyle)" - :x1="measurements.legends.offsetX" - :x2="measurements.legends.offsetX + measurements.legends.width" - :y1="graphHeight - measurements.legends.offsetY" - :y2="graphHeight - measurements.legends.offsetY" - /> - <text - v-if="timeSeries.length > 1" + <td> + <svg + width="15" + height="6" + > + <line + :stroke-dasharray="strokeDashArray(series.lineStyle)" + :stroke="series.lineColor" + stroke-width="4" + :x1="0" + :x2="15" + :y1="2" + :y2="2" + /> + </svg> + </td> + <td class="legend-metric-title" - ref="legendTitleSvg" - x="38" - :y="graphHeight - 30" - > - {{ createSeriesString(index, series) }} - </text> - <text - v-else - class="legend-metric-title" - ref="legendTitleSvg" - x="38" - :y="graphHeight - 30" + v-if="timeSeries.length > 1" > - {{ legendTitle }} {{ formatMetricUsage(series) }} - </text> - </g> - </template> - </g> + {{ createSeriesString(index, series) }}, {{ summaryMetrics(series) }} + </td> + <td v-else> + {{ legendTitle }} {{ formatMetricUsage(series) }}, {{ summaryMetrics(series) }} + </td> + </tr> + </table> + </div> </template> diff --git a/app/assets/javascripts/monitoring/utils/multiple_time_series.js b/app/assets/javascripts/monitoring/utils/multiple_time_series.js index b5b8e3c255d..95db31c7a2a 100644 --- a/app/assets/javascripts/monitoring/utils/multiple_time_series.js +++ b/app/assets/javascripts/monitoring/utils/multiple_time_series.js @@ -1,10 +1,20 @@ import _ from 'underscore'; import { scaleLinear, scaleTime } from 'd3-scale'; import { line, area, curveLinear } from 'd3-shape'; -import { extent, max } from 'd3-array'; +import { extent, max, sum } from 'd3-array'; import { timeMinute } from 'd3-time'; -const d3 = { scaleLinear, scaleTime, line, area, curveLinear, extent, max, timeMinute }; +const d3 = { + scaleLinear, + scaleTime, + line, + area, + curveLinear, + extent, + max, + timeMinute, + sum, +}; const defaultColorPalette = { blue: ['#1f78d1', '#8fbce8'], @@ -18,7 +28,15 @@ const defaultColorOrder = ['blue', 'orange', 'red', 'green', 'purple']; const defaultStyleOrder = ['solid', 'dashed', 'dotted']; -function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom, yDom, lineStyle) { +function queryTimeSeries( + query, + graphWidth, + graphHeight, + graphHeightOffset, + xDom, + yDom, + lineStyle, +) { let usedColors = []; function pickColor(name) { @@ -42,11 +60,14 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom let metricTag = ''; let lineColor = ''; let areaColor = ''; + const timeSeriesValues = timeSeries.values.map(d => d.value); + const maximumValue = d3.max(timeSeriesValues); + const accum = d3.sum(timeSeriesValues); - const timeSeriesScaleX = d3.scaleTime() - .range([0, graphWidth - 70]); + const timeSeriesScaleX = d3.scaleTime().range([0, graphWidth - 70]); - const timeSeriesScaleY = d3.scaleLinear() + const timeSeriesScaleY = d3 + .scaleLinear() .range([graphHeight - graphHeightOffset, 0]); timeSeriesScaleX.domain(xDom); @@ -55,28 +76,35 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom const defined = d => !isNaN(d.value) && d.value != null; - const lineFunction = d3.line() + const lineFunction = d3 + .line() .defined(defined) .curve(d3.curveLinear) // d3 v4 uses curbe instead of interpolate .x(d => timeSeriesScaleX(d.time)) .y(d => timeSeriesScaleY(d.value)); - const areaFunction = d3.area() + const areaFunction = d3 + .area() .defined(defined) .curve(d3.curveLinear) .x(d => timeSeriesScaleX(d.time)) .y0(graphHeight - graphHeightOffset) .y1(d => timeSeriesScaleY(d.value)); - const timeSeriesMetricLabel = timeSeries.metric[Object.keys(timeSeries.metric)[0]]; - const seriesCustomizationData = query.series != null && + const timeSeriesMetricLabel = + timeSeries.metric[Object.keys(timeSeries.metric)[0]]; + const seriesCustomizationData = + query.series != null && _.findWhere(query.series[0].when, { value: timeSeriesMetricLabel }); if (seriesCustomizationData) { metricTag = seriesCustomizationData.value || timeSeriesMetricLabel; [lineColor, areaColor] = pickColor(seriesCustomizationData.color); } else { - metricTag = timeSeriesMetricLabel || query.label || `series ${timeSeriesNumber + 1}`; + metricTag = + timeSeriesMetricLabel || + query.label || + `series ${timeSeriesNumber + 1}`; [lineColor, areaColor] = pickColor(); } @@ -89,6 +117,8 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom areaPath: areaFunction(timeSeries.values), timeSeriesScaleX, values: timeSeries.values, + max: maximumValue, + average: accum / timeSeries.values.length, lineStyle, lineColor, areaColor, @@ -97,10 +127,22 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom }); } -export default function createTimeSeries(queries, graphWidth, graphHeight, graphHeightOffset) { - const allValues = queries.reduce((allQueryResults, query) => allQueryResults.concat( - query.result.reduce((allResults, result) => allResults.concat(result.values), []), - ), []); +export default function createTimeSeries( + queries, + graphWidth, + graphHeight, + graphHeightOffset, +) { + const allValues = queries.reduce( + (allQueryResults, query) => + allQueryResults.concat( + query.result.reduce( + (allResults, result) => allResults.concat(result.values), + [], + ), + ), + [], + ); const xDom = d3.extent(allValues, d => d.time); const yDom = [0, d3.max(allValues.map(d => d.value))]; @@ -108,7 +150,15 @@ export default function createTimeSeries(queries, graphWidth, graphHeight, graph return queries.reduce((series, query, index) => { const lineStyle = defaultStyleOrder[index % defaultStyleOrder.length]; return series.concat( - queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom, yDom, lineStyle), + queryTimeSeries( + query, + graphWidth, + graphHeight, + graphHeightOffset, + xDom, + yDom, + lineStyle, + ), ); }, []); } |