Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Léo-Paul Géneau
gitlab-ce
Commits
657fea86
Commit
657fea86
authored
Mar 21, 2018
by
Jose Ivan Vargas
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add summary statistics prometheus dashboard
parent
9e3cdc02
Changes
10
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
421 additions
and
299 deletions
+421
-299
app/assets/javascripts/monitoring/components/graph.vue
app/assets/javascripts/monitoring/components/graph.vue
+16
-16
app/assets/javascripts/monitoring/components/graph/axis.vue
app/assets/javascripts/monitoring/components/graph/axis.vue
+127
-0
app/assets/javascripts/monitoring/components/graph/flag.vue
app/assets/javascripts/monitoring/components/graph/flag.vue
+1
-1
app/assets/javascripts/monitoring/components/graph/legend.vue
...assets/javascripts/monitoring/components/graph/legend.vue
+45
-158
app/assets/javascripts/monitoring/utils/multiple_time_series.js
...sets/javascripts/monitoring/utils/multiple_time_series.js
+66
-16
app/assets/stylesheets/pages/environments.scss
app/assets/stylesheets/pages/environments.scss
+16
-21
changelogs/unreleased/jivl-summary-statistics-prometheus-dashboard.yml
...released/jivl-summary-statistics-prometheus-dashboard.yml
+5
-0
spec/javascripts/monitoring/graph/axis_spec.js
spec/javascripts/monitoring/graph/axis_spec.js
+70
-0
spec/javascripts/monitoring/graph/legend_spec.js
spec/javascripts/monitoring/graph/legend_spec.js
+58
-79
spec/javascripts/monitoring/graph_spec.js
spec/javascripts/monitoring/graph_spec.js
+17
-8
No files found.
app/assets/javascripts/monitoring/components/graph.vue
View file @
657fea86
...
@@ -3,6 +3,7 @@ import { scaleLinear, scaleTime } from 'd3-scale';
...
@@ -3,6 +3,7 @@ import { scaleLinear, scaleTime } from 'd3-scale';
import
{
axisLeft
,
axisBottom
}
from
'
d3-axis
'
;
import
{
axisLeft
,
axisBottom
}
from
'
d3-axis
'
;
import
{
max
,
extent
}
from
'
d3-array
'
;
import
{
max
,
extent
}
from
'
d3-array
'
;
import
{
select
}
from
'
d3-selection
'
;
import
{
select
}
from
'
d3-selection
'
;
import
GraphAxis
from
'
./graph/axis.vue
'
;
import
GraphLegend
from
'
./graph/legend.vue
'
;
import
GraphLegend
from
'
./graph/legend.vue
'
;
import
GraphFlag
from
'
./graph/flag.vue
'
;
import
GraphFlag
from
'
./graph/flag.vue
'
;
import
GraphDeployment
from
'
./graph/deployment.vue
'
;
import
GraphDeployment
from
'
./graph/deployment.vue
'
;
...
@@ -18,10 +19,11 @@ const d3 = { scaleLinear, scaleTime, axisLeft, axisBottom, max, extent, select }
...
@@ -18,10 +19,11 @@ const d3 = { scaleLinear, scaleTime, axisLeft, axisBottom, max, extent, select }
export
default
{
export
default
{
components
:
{
components
:
{
Graph
Legend
,
Graph
Axis
,
GraphFlag
,
GraphFlag
,
GraphDeployment
,
GraphDeployment
,
GraphPath
,
GraphPath
,
GraphLegend
,
},
},
mixins
:
[
MonitoringMixin
],
mixins
:
[
MonitoringMixin
],
props
:
{
props
:
{
...
@@ -138,7 +140,7 @@ export default {
...
@@ -138,7 +140,7 @@ export default {
this
.
legendTitle
=
query
.
label
||
'
Average
'
;
this
.
legendTitle
=
query
.
label
||
'
Average
'
;
this
.
graphWidth
=
this
.
$refs
.
baseSvg
.
clientWidth
-
this
.
margin
.
left
-
this
.
margin
.
right
;
this
.
graphWidth
=
this
.
$refs
.
baseSvg
.
clientWidth
-
this
.
margin
.
left
-
this
.
margin
.
right
;
this
.
graphHeight
=
this
.
graphHeight
-
this
.
margin
.
top
-
this
.
margin
.
bottom
;
this
.
graphHeight
=
this
.
graphHeight
-
this
.
margin
.
top
-
this
.
margin
.
bottom
;
this
.
baseGraphHeight
=
this
.
graphHeight
;
this
.
baseGraphHeight
=
this
.
graphHeight
-
50
;
this
.
baseGraphWidth
=
this
.
graphWidth
;
this
.
baseGraphWidth
=
this
.
graphWidth
;
// pixel offsets inside the svg and outside are not 1:1
// pixel offsets inside the svg and outside are not 1:1
...
@@ -177,14 +179,10 @@ export default {
...
@@ -177,14 +179,10 @@ export default {
this
.
graphHeightOffset
,
this
.
graphHeightOffset
,
);
);
if
(
!
this
.
showLegend
)
{
const
axisXScale
=
d3
.
scaleTime
()
this
.
baseGraphHeight
-=
50
;
.
range
([
0
,
this
.
graphWidth
-
70
]);
}
else
if
(
this
.
timeSeries
.
length
>
3
)
{
const
axisYScale
=
d3
.
scaleLinear
()
this
.
baseGraphHeight
=
this
.
baseGraphHeight
+=
(
this
.
timeSeries
.
length
-
3
)
*
20
;
.
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
),
[]);
const
allValues
=
this
.
timeSeries
.
reduce
((
all
,
{
values
})
=>
all
.
concat
(
values
),
[]);
axisXScale
.
domain
(
d3
.
extent
(
allValues
,
d
=>
d
.
time
));
axisXScale
.
domain
(
d3
.
extent
(
allValues
,
d
=>
d
.
time
));
...
@@ -251,17 +249,12 @@ export default {
...
@@ -251,17 +249,12 @@ export default {
class=
"y-axis"
class=
"y-axis"
transform=
"translate(70, 20)"
transform=
"translate(70, 20)"
/>
/>
<graph-
legend
<graph-
axis
:graph-width=
"graphWidth"
:graph-width=
"graphWidth"
:graph-height=
"graphHeight"
:graph-height=
"graphHeight"
:margin=
"margin"
:margin=
"margin"
:measurements=
"measurements"
:measurements=
"measurements"
:legend-title=
"legendTitle"
:y-axis-label=
"yAxisLabel"
:y-axis-label=
"yAxisLabel"
:time-series=
"timeSeries"
:unit-of-display=
"unitOfDisplay"
:current-data-index=
"currentDataIndex"
:show-legend-group=
"showLegend"
/>
/>
<svg
<svg
class=
"graph-data"
class=
"graph-data"
...
@@ -306,5 +299,12 @@ export default {
...
@@ -306,5 +299,12 @@ export default {
:deployment-flag-data=
"deploymentFlagData"
:deployment-flag-data=
"deploymentFlagData"
/>
/>
</div>
</div>
<graph-legend
v-if=
"showLegend"
:legend-title=
"legendTitle"
:time-series=
"timeSeries"
:current-data-index=
"currentDataIndex"
:unit-of-display=
"unitOfDisplay"
/>
</div>
</div>
</
template
>
</
template
>
app/assets/javascripts/monitoring/components/graph/axis.vue
0 → 100644
View file @
657fea86
<
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
>
app/assets/javascripts/monitoring/components/graph/flag.vue
View file @
657fea86
...
@@ -160,7 +160,7 @@ export default {
...
@@ -160,7 +160,7 @@ export default {
</div>
</div>
</div>
</div>
<div
class=
"popover-content"
>
<div
class=
"popover-content"
>
<table>
<table
class=
"prometheus-table"
>
<tr
<tr
v-for=
"(series, index) in timeSeries"
v-for=
"(series, index) in timeSeries"
:key=
"index"
:key=
"index"
...
...
app/assets/javascripts/monitoring/components/graph/legend.vue
View file @
657fea86
<
script
>
<
script
>
import
{
formatRelevantDigits
}
from
'
../../..
/lib/utils/number_utils
'
;
import
{
formatRelevantDigits
}
from
'
~
/lib/utils/number_utils
'
;
export
default
{
export
default
{
props
:
{
props
:
{
graphWidth
:
{
type
:
Number
,
required
:
true
,
},
graphHeight
:
{
type
:
Number
,
required
:
true
,
},
margin
:
{
type
:
Object
,
required
:
true
,
},
measurements
:
{
type
:
Object
,
required
:
true
,
},
legendTitle
:
{
legendTitle
:
{
type
:
String
,
type
:
String
,
required
:
true
,
required
:
true
,
},
},
yAxisLabel
:
{
type
:
String
,
required
:
true
,
},
timeSeries
:
{
timeSeries
:
{
type
:
Array
,
type
:
Array
,
required
:
true
,
required
:
true
,
},
},
unitOfDisplay
:
{
type
:
String
,
required
:
true
,
},
currentDataIndex
:
{
currentDataIndex
:
{
type
:
Number
,
type
:
Number
,
required
:
true
,
required
:
true
,
},
},
showLegendGroup
:
{
unitOfDisplay
:
{
type
:
Boolean
,
type
:
String
,
required
:
false
,
required
:
true
,
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
;
},
},
},
},
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
:
{
methods
:
{
translateLegendGroup
(
index
)
{
return
`translate(0,
${
12
*
index
}
)`
;
},
formatMetricUsage
(
series
)
{
formatMetricUsage
(
series
)
{
const
value
=
const
value
=
series
.
values
[
this
.
currentDataIndex
]
&&
series
.
values
[
this
.
currentDataIndex
].
value
;
series
.
values
[
this
.
currentDataIndex
]
&&
series
.
values
[
this
.
currentDataIndex
].
value
;
if
(
isNaN
(
value
))
{
if
(
isNaN
(
value
))
{
return
'
-
'
;
return
'
-
'
;
}
}
return
`
${
formatRelevantDigits
(
value
)}
${
this
.
unitOfDisplay
}
`
;
return
`
${
formatRelevantDigits
(
value
)}
${
this
.
unitOfDisplay
}
`
;
},
},
createSeriesString
(
index
,
series
)
{
createSeriesString
(
index
,
series
)
{
if
(
series
.
metricTag
)
{
if
(
series
.
metricTag
)
{
return
`
${
series
.
metricTag
}
${
this
.
formatMetricUsage
(
series
)}
`
;
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
)
{
strokeDashArray
(
type
)
{
if
(
type
===
'
dashed
'
)
return
'
6, 3
'
;
if
(
type
===
'
dashed
'
)
return
'
6, 3
'
;
if
(
type
===
'
dotted
'
)
return
'
3, 3
'
;
if
(
type
===
'
dotted
'
)
return
'
3, 3
'
;
...
@@ -116,89 +54,38 @@ export default {
...
@@ -116,89 +54,38 @@ export default {
};
};
</
script
>
</
script
>
<
template
>
<
template
>
<g
class=
"axis-label-container"
>
<div
class=
"prometheus-graph-legends prepend-left-10"
>
<line
<table
class=
"prometheus-table"
>
class=
"label-x-axis-line"
<tr
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"
v-for=
"(series, index) in timeSeries"
v-for=
"(series, index) in timeSeries"
:key=
"index"
:key=
"index"
:transform=
"translateLegendGroup(index)"
>
>
<line
<td>
:stroke=
"series.lineColor"
<svg
:stroke-width=
"measurements.legends.height"
width=
"15"
:stroke-dasharray=
"strokeDashArray(series.lineStyle)"
height=
"6"
:x1=
"measurements.legends.offsetX"
>
:x2=
"measurements.legends.offsetX + measurements.legends.width"
<line
:y1=
"graphHeight - measurements.legends.offsetY"
:stroke-dasharray=
"strokeDashArray(series.lineStyle)"
:y2=
"graphHeight - measurements.legends.offsetY"
:stroke=
"series.lineColor"
/>
stroke-width=
"4"
<text
:x1=
"0"
v-if=
"timeSeries.length > 1"
:x2=
"15"
:y1=
"2"
:y2=
"2"
/>
</svg>
</td>
<td
class=
"legend-metric-title"
class=
"legend-metric-title"
ref=
"legendTitleSvg"
v-if=
"timeSeries.length > 1"
x=
"38"
:y=
"graphHeight - 30"
>
{{
createSeriesString
(
index
,
series
)
}}
</text>
<text
v-else
class=
"legend-metric-title"
ref=
"legendTitleSvg"
x=
"38"
:y=
"graphHeight - 30"
>
>
{{
legendTitle
}}
{{
formatMetricUsage
(
series
)
}}
{{
createSeriesString
(
index
,
series
)
}}
,
{{
summaryMetrics
(
series
)
}}
</text>
</td>
</g>
<td
v-else
>
</
template
>
{{
legendTitle
}}
{{
formatMetricUsage
(
series
)
}}
,
{{
summaryMetrics
(
series
)
}}
</g>
</td>
</tr>
</table>
</div>
</
template
>
</
template
>
app/assets/javascripts/monitoring/utils/multiple_time_series.js
View file @
657fea86
import
_
from
'
underscore
'
;
import
_
from
'
underscore
'
;
import
{
scaleLinear
,
scaleTime
}
from
'
d3-scale
'
;
import
{
scaleLinear
,
scaleTime
}
from
'
d3-scale
'
;
import
{
line
,
area
,
curveLinear
}
from
'
d3-shape
'
;
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
'
;
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
=
{
const
defaultColorPalette
=
{
blue
:
[
'
#1f78d1
'
,
'
#8fbce8
'
],
blue
:
[
'
#1f78d1
'
,
'
#8fbce8
'
],
...
@@ -18,7 +28,15 @@ const defaultColorOrder = ['blue', 'orange', 'red', 'green', 'purple'];
...
@@ -18,7 +28,15 @@ const defaultColorOrder = ['blue', 'orange', 'red', 'green', 'purple'];
const
defaultStyleOrder
=
[
'
solid
'
,
'
dashed
'
,
'
dotted
'
];
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
=
[];
let
usedColors
=
[];
function
pickColor
(
name
)
{
function
pickColor
(
name
)
{
...
@@ -42,11 +60,14 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom
...
@@ -42,11 +60,14 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom
let
metricTag
=
''
;
let
metricTag
=
''
;
let
lineColor
=
''
;
let
lineColor
=
''
;
let
areaColor
=
''
;
let
areaColor
=
''
;
const
timeSeriesValues
=
timeSeries
.
values
.
map
(
d
=>
d
.
value
);
const
maximumValue
=
d3
.
max
(
timeSeriesValues
);
const
accum
=
d3
.
sum
(
timeSeriesValues
);
const
timeSeriesScaleX
=
d3
.
scaleTime
()
const
timeSeriesScaleX
=
d3
.
scaleTime
().
range
([
0
,
graphWidth
-
70
]);
.
range
([
0
,
graphWidth
-
70
]);
const
timeSeriesScaleY
=
d3
.
scaleLinear
()
const
timeSeriesScaleY
=
d3
.
scaleLinear
()
.
range
([
graphHeight
-
graphHeightOffset
,
0
]);
.
range
([
graphHeight
-
graphHeightOffset
,
0
]);
timeSeriesScaleX
.
domain
(
xDom
);
timeSeriesScaleX
.
domain
(
xDom
);
...
@@ -55,28 +76,35 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom
...
@@ -55,28 +76,35 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom
const
defined
=
d
=>
!
isNaN
(
d
.
value
)
&&
d
.
value
!=
null
;
const
defined
=
d
=>
!
isNaN
(
d
.
value
)
&&
d
.
value
!=
null
;
const
lineFunction
=
d3
.
line
()
const
lineFunction
=
d3
.
line
()
.
defined
(
defined
)
.
defined
(
defined
)
.
curve
(
d3
.
curveLinear
)
// d3 v4 uses curbe instead of interpolate
.
curve
(
d3
.
curveLinear
)
// d3 v4 uses curbe instead of interpolate
.
x
(
d
=>
timeSeriesScaleX
(
d
.
time
))
.
x
(
d
=>
timeSeriesScaleX
(
d
.
time
))
.
y
(
d
=>
timeSeriesScaleY
(
d
.
value
));
.
y
(
d
=>
timeSeriesScaleY
(
d
.
value
));
const
areaFunction
=
d3
.
area
()
const
areaFunction
=
d3
.
area
()
.
defined
(
defined
)
.
defined
(
defined
)
.
curve
(
d3
.
curveLinear
)
.
curve
(
d3
.
curveLinear
)
.
x
(
d
=>
timeSeriesScaleX
(
d
.
time
))
.
x
(
d
=>
timeSeriesScaleX
(
d
.
time
))
.
y0
(
graphHeight
-
graphHeightOffset
)
.
y0
(
graphHeight
-
graphHeightOffset
)
.
y1
(
d
=>
timeSeriesScaleY
(
d
.
value
));
.
y1
(
d
=>
timeSeriesScaleY
(
d
.
value
));
const
timeSeriesMetricLabel
=
timeSeries
.
metric
[
Object
.
keys
(
timeSeries
.
metric
)[
0
]];
const
timeSeriesMetricLabel
=
const
seriesCustomizationData
=
query
.
series
!=
null
&&
timeSeries
.
metric
[
Object
.
keys
(
timeSeries
.
metric
)[
0
]];
const
seriesCustomizationData
=
query
.
series
!=
null
&&
_
.
findWhere
(
query
.
series
[
0
].
when
,
{
value
:
timeSeriesMetricLabel
});
_
.
findWhere
(
query
.
series
[
0
].
when
,
{
value
:
timeSeriesMetricLabel
});
if
(
seriesCustomizationData
)
{
if
(
seriesCustomizationData
)
{
metricTag
=
seriesCustomizationData
.
value
||
timeSeriesMetricLabel
;
metricTag
=
seriesCustomizationData
.
value
||
timeSeriesMetricLabel
;
[
lineColor
,
areaColor
]
=
pickColor
(
seriesCustomizationData
.
color
);
[
lineColor
,
areaColor
]
=
pickColor
(
seriesCustomizationData
.
color
);
}
else
{
}
else
{
metricTag
=
timeSeriesMetricLabel
||
query
.
label
||
`series
${
timeSeriesNumber
+
1
}
`
;
metricTag
=
timeSeriesMetricLabel
||
query
.
label
||
`series
${
timeSeriesNumber
+
1
}
`
;
[
lineColor
,
areaColor
]
=
pickColor
();
[
lineColor
,
areaColor
]
=
pickColor
();
}
}
...
@@ -89,6 +117,8 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom
...
@@ -89,6 +117,8 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom
areaPath
:
areaFunction
(
timeSeries
.
values
),
areaPath
:
areaFunction
(
timeSeries
.
values
),
timeSeriesScaleX
,
timeSeriesScaleX
,
values
:
timeSeries
.
values
,
values
:
timeSeries
.
values
,
max
:
maximumValue
,
average
:
accum
/
timeSeries
.
values
.
length
,
lineStyle
,
lineStyle
,
lineColor
,
lineColor
,
areaColor
,
areaColor
,
...
@@ -97,10 +127,22 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom
...
@@ -97,10 +127,22 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom
});
});
}
}
export
default
function
createTimeSeries
(
queries
,
graphWidth
,
graphHeight
,
graphHeightOffset
)
{
export
default
function
createTimeSeries
(
const
allValues
=
queries
.
reduce
((
allQueryResults
,
query
)
=>
allQueryResults
.
concat
(
queries
,
query
.
result
.
reduce
((
allResults
,
result
)
=>
allResults
.
concat
(
result
.
values
),
[]),
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
xDom
=
d3
.
extent
(
allValues
,
d
=>
d
.
time
);
const
yDom
=
[
0
,
d3
.
max
(
allValues
.
map
(
d
=>
d
.
value
))];
const
yDom
=
[
0
,
d3
.
max
(
allValues
.
map
(
d
=>
d
.
value
))];
...
@@ -108,7 +150,15 @@ export default function createTimeSeries(queries, graphWidth, graphHeight, graph
...
@@ -108,7 +150,15 @@ export default function createTimeSeries(queries, graphWidth, graphHeight, graph
return
queries
.
reduce
((
series
,
query
,
index
)
=>
{
return
queries
.
reduce
((
series
,
query
,
index
)
=>
{
const
lineStyle
=
defaultStyleOrder
[
index
%
defaultStyleOrder
.
length
];
const
lineStyle
=
defaultStyleOrder
[
index
%
defaultStyleOrder
.
length
];
return
series
.
concat
(
return
series
.
concat
(
queryTimeSeries
(
query
,
graphWidth
,
graphHeight
,
graphHeightOffset
,
xDom
,
yDom
,
lineStyle
),
queryTimeSeries
(
query
,
graphWidth
,
graphHeight
,
graphHeightOffset
,
xDom
,
yDom
,
lineStyle
,
),
);
);
},
[]);
},
[]);
}
}
app/assets/stylesheets/pages/environments.scss
View file @
657fea86
...
@@ -273,21 +273,6 @@
...
@@ -273,21 +273,6 @@
line-height
:
1
.2
;
line-height
:
1
.2
;
}
}
table
{
border-collapse
:
collapse
;
padding
:
0
;
margin
:
0
;
}
td
{
vertical-align
:
middle
;
+
td
{
padding-left
:
5px
;
vertical-align
:
top
;
}
}
.deploy-meta-content
{
.deploy-meta-content
{
border-bottom
:
1px
solid
$white-dark
;
border-bottom
:
1px
solid
$white-dark
;
...
@@ -323,6 +308,21 @@
...
@@ -323,6 +308,21 @@
}
}
}
}
.prometheus-table
{
border-collapse
:
collapse
;
padding
:
0
;
margin
:
0
;
td
{
vertical-align
:
middle
;
+
td
{
padding-left
:
5px
;
vertical-align
:
top
;
}
}
}
.prometheus-svg-container
{
.prometheus-svg-container
{
position
:
relative
;
position
:
relative
;
height
:
0
;
height
:
0
;
...
@@ -330,8 +330,7 @@
...
@@ -330,8 +330,7 @@
padding
:
0
;
padding
:
0
;
padding-bottom
:
100%
;
padding-bottom
:
100%
;
.text-metric-usage
,
.text-metric-usage
{
.legend-metric-title
{
fill
:
$black
;
fill
:
$black
;
font-weight
:
$gl-font-weight-normal
;
font-weight
:
$gl-font-weight-normal
;
font-size
:
12px
;
font-size
:
12px
;
...
@@ -374,10 +373,6 @@
...
@@ -374,10 +373,6 @@
}
}
}
}
.text-metric-title
{
font-size
:
12px
;
}
.y-label-text
,
.y-label-text
,
.x-label-text
{
.x-label-text
{
fill
:
$gray-darkest
;
fill
:
$gray-darkest
;
...
...
changelogs/unreleased/jivl-summary-statistics-prometheus-dashboard.yml
0 → 100644
View file @
657fea86
---
title
:
Add average and maximum summary statistics to the prometheus dashboard
merge_request
:
17921
author
:
type
:
changed
spec/javascripts/monitoring/graph/axis_spec.js
0 → 100644
View file @
657fea86
import
Vue
from
'
vue
'
;
import
GraphAxis
from
'
~/monitoring/components/graph/axis.vue
'
;
import
measurements
from
'
~/monitoring/utils/measurements
'
;
const
createComponent
=
propsData
=>
{
const
Component
=
Vue
.
extend
(
GraphAxis
);
return
new
Component
({
propsData
,
}).
$mount
();
};
const
defaultValuesComponent
=
{
graphWidth
:
500
,
graphHeight
:
300
,
graphHeightOffset
:
120
,
margin
:
measurements
.
large
.
margin
,
measurements
:
measurements
.
large
,
yAxisLabel
:
'
Values
'
,
};
function
getTextFromNode
(
component
,
selector
)
{
return
component
.
$el
.
querySelector
(
selector
).
firstChild
.
nodeValue
.
trim
();
}
describe
(
'
Axis
'
,
()
=>
{
describe
(
'
Computed props
'
,
()
=>
{
it
(
'
textTransform
'
,
()
=>
{
const
component
=
createComponent
(
defaultValuesComponent
);
expect
(
component
.
textTransform
).
toContain
(
'
translate(15, 120) rotate(-90)
'
,
);
});
it
(
'
xPosition
'
,
()
=>
{
const
component
=
createComponent
(
defaultValuesComponent
);
expect
(
component
.
xPosition
).
toEqual
(
180
);
});
it
(
'
yPosition
'
,
()
=>
{
const
component
=
createComponent
(
defaultValuesComponent
);
expect
(
component
.
yPosition
).
toEqual
(
240
);
});
it
(
'
rectTransform
'
,
()
=>
{
const
component
=
createComponent
(
defaultValuesComponent
);
expect
(
component
.
rectTransform
).
toContain
(
'
translate(0, 120) rotate(-90)
'
,
);
});
});
it
(
'
has 2 rect-axis-text rect svg elements
'
,
()
=>
{
const
component
=
createComponent
(
defaultValuesComponent
);
expect
(
component
.
$el
.
querySelectorAll
(
'
.rect-axis-text
'
).
length
).
toEqual
(
2
);
});
it
(
'
contains text to signal the usage, title and time with multiple time series
'
,
()
=>
{
const
component
=
createComponent
(
defaultValuesComponent
);
expect
(
getTextFromNode
(
component
,
'
.y-label-text
'
)).
toEqual
(
component
.
yAxisLabel
,
);
});
});
spec/javascripts/monitoring/graph/legend_spec.js
View file @
657fea86
import
Vue
from
'
vue
'
;
import
Vue
from
'
vue
'
;
import
GraphLegend
from
'
~/monitoring/components/graph/legend.vue
'
;
import
GraphLegend
from
'
~/monitoring/components/graph/legend.vue
'
;
import
measurements
from
'
~/monitoring/utils/measurements
'
;
import
createTimeSeries
from
'
~/monitoring/utils/multiple_time_series
'
;
import
createTimeSeries
from
'
~/monitoring/utils/multiple_time_series
'
;
import
{
singleRowMetricsMultipleSeries
,
convertDatesMultipleSeries
}
from
'
../mock_data
'
;
import
mountComponent
from
'
spec/helpers/vue_mount_component_helper
'
;
import
{
const
createComponent
=
(
propsData
)
=>
{
singleRowMetricsMultipleSeries
,
const
Component
=
Vue
.
extend
(
GraphLegend
);
convertDatesMultipleSeries
,
}
from
'
../mock_data
'
;
return
new
Component
({
propsData
,
}).
$mount
();
};
const
convertedMetrics
=
convertDatesMultipleSeries
(
singleRowMetricsMultipleSeries
);
const
defaultValuesComponent
=
{
graphWidth
:
500
,
graphHeight
:
300
,
graphHeightOffset
:
120
,
margin
:
measurements
.
large
.
margin
,
measurements
:
measurements
.
large
,
areaColorRgb
:
'
#f0f0f0
'
,
legendTitle
:
'
Title
'
,
yAxisLabel
:
'
Values
'
,
metricUsage
:
'
Value
'
,
unitOfDisplay
:
'
Req/Sec
'
,
currentDataIndex
:
0
,
};
const
timeSeries
=
createTimeSeries
(
convertedMetrics
[
0
].
queries
,
defaultValuesComponent
.
graphWidth
,
defaultValuesComponent
.
graphHeight
,
defaultValuesComponent
.
graphHeightOffset
);
defaultValuesComponent
.
timeSeries
=
timeSeries
;
const
convertedMetrics
=
convertDatesMultipleSeries
(
singleRowMetricsMultipleSeries
,
function
getTextFromNode
(
component
,
selector
)
{
);
return
component
.
$el
.
querySelector
(
selector
).
firstChild
.
nodeValue
.
trim
();
}
describe
(
'
GraphLegend
'
,
()
=>
{
const
defaultValuesComponent
=
{};
describe
(
'
Computed props
'
,
()
=>
{
it
(
'
textTransform
'
,
()
=>
{
const
component
=
createComponent
(
defaultValuesComponent
);
expect
(
component
.
textTransform
).
toContain
(
'
translate(15, 120) rotate(-90)
'
);
const
timeSeries
=
createTimeSeries
(
convertedMetrics
[
0
].
queries
,
500
,
300
,
120
);
});
it
(
'
xPosition
'
,
()
=>
{
defaultValuesComponent
.
timeSeries
=
timeSeries
;
const
component
=
createComponent
(
defaultValuesComponent
);
expect
(
component
.
xPosition
).
toEqual
(
180
);
describe
(
'
Legend Component
'
,
()
=>
{
});
let
vm
;
let
Legend
;
it
(
'
yPosition
'
,
()
=>
{
beforeEach
(()
=>
{
const
component
=
createComponent
(
defaultValuesComponent
);
Legend
=
Vue
.
extend
(
GraphLegend
);
});
expect
(
component
.
yPosition
).
toEqual
(
240
);
describe
(
'
Methods
'
,
()
=>
{
beforeEach
(()
=>
{
vm
=
mountComponent
(
Legend
,
{
legendTitle
:
'
legend
'
,
timeSeries
,
currentDataIndex
:
0
,
unitOfDisplay
:
'
Req/Sec
'
,
});
});
});
it
(
'
rectTransform
'
,
()
=>
{
it
(
'
formatMetricUsage should contain the unit of display and the current value selected via "currentDataIndex"
'
,
()
=>
{
const
component
=
createComponent
(
defaultValuesComponent
);
const
formattedMetricUsage
=
vm
.
formatMetricUsage
(
timeSeries
[
0
]);
const
valueFromSeries
=
timeSeries
[
0
].
values
[
vm
.
currentDataIndex
].
value
;
expect
(
component
.
rectTransform
).
toContain
(
'
translate(0, 120) rotate(-90)
'
);
expect
(
formattedMetricUsage
.
indexOf
(
vm
.
unitOfDisplay
)).
not
.
toEqual
(
-
1
);
expect
(
formattedMetricUsage
.
indexOf
(
valueFromSeries
)).
not
.
toEqual
(
-
1
);
});
});
});
describe
(
'
methods
'
,
()
=>
{
it
(
'
strokeDashArray
'
,
()
=>
{
it
(
'
translateLegendGroup should only change Y direction
'
,
()
=>
{
const
dashedArray
=
vm
.
strokeDashArray
(
'
dashed
'
);
const
component
=
createComponent
(
defaultValuesComponent
);
const
dottedArray
=
vm
.
strokeDashArray
(
'
dotted
'
);
const
translatedCoordinate
=
component
.
translateLegendGroup
(
1
);
expect
(
dashedArray
).
toEqual
(
'
6, 3
'
);
expect
(
translatedCoordinate
.
indexOf
(
'
translate(0,
'
)).
not
.
toEqual
(
-
1
);
expect
(
dottedArray
).
toEqual
(
'
3, 3
'
);
});
});
it
(
'
formatMetricUsage should contain the unit of display and the current value selected via "currentDataIndex"
'
,
()
=>
{
it
(
'
summaryMetrics gets the average and max of a series
'
,
()
=>
{
const
component
=
createComponent
(
defaultValuesComponent
);
const
summary
=
vm
.
summaryMetrics
(
timeSeries
[
0
]
);
const
formattedMetricUsage
=
component
.
formatMetricUsage
(
timeSeries
[
0
]);
expect
(
summary
.
indexOf
(
'
Max
'
)).
not
.
toEqual
(
-
1
);
const
valueFromSeries
=
timeSeries
[
0
].
values
[
component
.
currentDataIndex
].
value
;
expect
(
summary
.
indexOf
(
'
Avg
'
)).
not
.
toEqual
(
-
1
);
expect
(
formattedMetricUsage
.
indexOf
(
component
.
unitOfDisplay
)).
not
.
toEqual
(
-
1
);
expect
(
formattedMetricUsage
.
indexOf
(
valueFromSeries
)).
not
.
toEqual
(
-
1
);
});
});
});
});
it
(
'
has 2 rect-axis-text rect svg elements
'
,
()
=>
{
describe
(
'
View
'
,
()
=>
{
const
component
=
createComponent
(
defaultValuesComponent
);
beforeEach
(()
=>
{
vm
=
mountComponent
(
Legend
,
{
expect
(
component
.
$el
.
querySelectorAll
(
'
.rect-axis-text
'
).
length
).
toEqual
(
2
);
legendTitle
:
'
legend
'
,
});
timeSeries
,
currentDataIndex
:
0
,
it
(
'
contains text to signal the usage, title and time with multiple time series
'
,
()
=>
{
unitOfDisplay
:
'
Req/Sec
'
,
const
component
=
createComponent
(
defaultValuesComponent
);
}
);
const
titles
=
component
.
$el
.
querySelectorAll
(
'
.legend-metric-title
'
);
}
);
expect
(
titles
[
0
].
textContent
.
indexOf
(
'
1xx
'
)).
not
.
toEqual
(
-
1
);
it
(
'
should render the usage, title and time with multiple time series
'
,
()
=>
{
expect
(
titles
[
1
].
textContent
.
indexOf
(
'
2xx
'
)).
not
.
toEqual
(
-
1
);
const
titles
=
vm
.
$el
.
querySelectorAll
(
'
.legend-metric-title
'
);
expect
(
getTextFromNode
(
component
,
'
.y-label-text
'
)).
toEqual
(
component
.
yAxisLabel
);
});
it
(
'
should contain the same number of legend groups as the timeSeries length
'
,
()
=>
{
expect
(
titles
[
0
].
textContent
.
indexOf
(
'
1xx
'
)).
not
.
toEqual
(
-
1
);
const
component
=
createComponent
(
defaultValuesComponent
);
expect
(
titles
[
1
].
textContent
.
indexOf
(
'
2xx
'
)).
not
.
toEqual
(
-
1
);
});
expect
(
component
.
$el
.
querySelectorAll
(
'
.legend-group
'
).
length
).
toEqual
(
component
.
timeSeries
.
length
);
it
(
'
should container the same number of rows in the table as time series
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelectorAll
(
'
.prometheus-table tr
'
).
length
).
toEqual
(
vm
.
timeSeries
.
length
,
);
});
});
});
});
});
spec/javascripts/monitoring/graph_spec.js
View file @
657fea86
...
@@ -2,11 +2,15 @@ import Vue from 'vue';
...
@@ -2,11 +2,15 @@ import Vue from 'vue';
import
Graph
from
'
~/monitoring/components/graph.vue
'
;
import
Graph
from
'
~/monitoring/components/graph.vue
'
;
import
MonitoringMixins
from
'
~/monitoring/mixins/monitoring_mixins
'
;
import
MonitoringMixins
from
'
~/monitoring/mixins/monitoring_mixins
'
;
import
eventHub
from
'
~/monitoring/event_hub
'
;
import
eventHub
from
'
~/monitoring/event_hub
'
;
import
{
deploymentData
,
convertDatesMultipleSeries
,
singleRowMetricsMultipleSeries
}
from
'
./mock_data
'
;
import
{
deploymentData
,
convertDatesMultipleSeries
,
singleRowMetricsMultipleSeries
,
}
from
'
./mock_data
'
;
const
tagsPath
=
'
http://test.host/frontend-fixtures/environments-project/tags
'
;
const
tagsPath
=
'
http://test.host/frontend-fixtures/environments-project/tags
'
;
const
projectPath
=
'
http://test.host/frontend-fixtures/environments-project
'
;
const
projectPath
=
'
http://test.host/frontend-fixtures/environments-project
'
;
const
createComponent
=
(
propsData
)
=>
{
const
createComponent
=
propsData
=>
{
const
Component
=
Vue
.
extend
(
Graph
);
const
Component
=
Vue
.
extend
(
Graph
);
return
new
Component
({
return
new
Component
({
...
@@ -14,7 +18,9 @@ const createComponent = (propsData) => {
...
@@ -14,7 +18,9 @@ const createComponent = (propsData) => {
}).
$mount
();
}).
$mount
();
};
};
const
convertedMetrics
=
convertDatesMultipleSeries
(
singleRowMetricsMultipleSeries
);
const
convertedMetrics
=
convertDatesMultipleSeries
(
singleRowMetricsMultipleSeries
,
);
describe
(
'
Graph
'
,
()
=>
{
describe
(
'
Graph
'
,
()
=>
{
beforeEach
(()
=>
{
beforeEach
(()
=>
{
...
@@ -31,7 +37,9 @@ describe('Graph', () => {
...
@@ -31,7 +37,9 @@ describe('Graph', () => {
projectPath
,
projectPath
,
});
});
expect
(
component
.
$el
.
querySelector
(
'
.text-center
'
).
innerText
.
trim
()).
toBe
(
component
.
graphData
.
title
);
expect
(
component
.
$el
.
querySelector
(
'
.text-center
'
).
innerText
.
trim
()).
toBe
(
component
.
graphData
.
title
,
);
});
});
describe
(
'
Computed props
'
,
()
=>
{
describe
(
'
Computed props
'
,
()
=>
{
...
@@ -46,8 +54,9 @@ describe('Graph', () => {
...
@@ -46,8 +54,9 @@ describe('Graph', () => {
});
});
const
transformedHeight
=
`
${
component
.
graphHeight
-
100
}
`
;
const
transformedHeight
=
`
${
component
.
graphHeight
-
100
}
`
;
expect
(
component
.
axisTransform
.
indexOf
(
transformedHeight
))
expect
(
component
.
axisTransform
.
indexOf
(
transformedHeight
)).
not
.
toEqual
(
.
not
.
toEqual
(
-
1
);
-
1
,
);
});
});
it
(
'
outerViewBox gets a width and height property based on the DOM size of the element
'
,
()
=>
{
it
(
'
outerViewBox gets a width and height property based on the DOM size of the element
'
,
()
=>
{
...
@@ -63,11 +72,11 @@ describe('Graph', () => {
...
@@ -63,11 +72,11 @@ describe('Graph', () => {
const
viewBoxArray
=
component
.
outerViewBox
.
split
(
'
'
);
const
viewBoxArray
=
component
.
outerViewBox
.
split
(
'
'
);
expect
(
typeof
component
.
outerViewBox
).
toEqual
(
'
string
'
);
expect
(
typeof
component
.
outerViewBox
).
toEqual
(
'
string
'
);
expect
(
viewBoxArray
[
2
]).
toEqual
(
component
.
graphWidth
.
toString
());
expect
(
viewBoxArray
[
2
]).
toEqual
(
component
.
graphWidth
.
toString
());
expect
(
viewBoxArray
[
3
]).
toEqual
(
component
.
graphHeight
.
toString
());
expect
(
viewBoxArray
[
3
]).
toEqual
(
(
component
.
graphHeight
-
50
)
.
toString
());
});
});
});
});
it
(
'
sends an event to the eventhub when it has finished resizing
'
,
(
done
)
=>
{
it
(
'
sends an event to the eventhub when it has finished resizing
'
,
done
=>
{
const
component
=
createComponent
({
const
component
=
createComponent
({
graphData
:
convertedMetrics
[
1
],
graphData
:
convertedMetrics
[
1
],
classType
:
'
col-md-6
'
,
classType
:
'
col-md-6
'
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment