Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
李少辉-开发者
gitlab-foss
提交
657fea86
G
gitlab-foss
项目概览
李少辉-开发者
/
gitlab-foss
通知
15
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
G
gitlab-foss
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
657fea86
编写于
3月 21, 2018
作者:
J
Jose Ivan Vargas
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Add summary statistics prometheus dashboard
上级
9e3cdc02
变更
10
隐藏空白更改
内联
并排
Showing
10 changed file
with
421 addition
and
299 deletion
+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
未找到文件。
app/assets/javascripts/monitoring/components/graph.vue
浏览文件 @
657fea86
...
...
@@ -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
:
{
Graph
Legend
,
Graph
Axis
,
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
>
app/assets/javascripts/monitoring/components/graph/axis.vue
0 → 100644
浏览文件 @
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
浏览文件 @
657fea86
...
...
@@ -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"
...
...
app/assets/javascripts/monitoring/components/graph/legend.vue
浏览文件 @
657fea86
<
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
>
app/assets/javascripts/monitoring/utils/multiple_time_series.js
浏览文件 @
657fea86
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
,
),
);
},
[]);
}
app/assets/stylesheets/pages/environments.scss
浏览文件 @
657fea86
...
...
@@ -273,21 +273,6 @@
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
{
border-bottom
:
1px
solid
$white-dark
;
...
...
@@ -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
{
position
:
relative
;
height
:
0
;
...
...
@@ -330,8 +330,7 @@
padding
:
0
;
padding-bottom
:
100%
;
.text-metric-usage
,
.legend-metric-title
{
.text-metric-usage
{
fill
:
$black
;
font-weight
:
$gl-font-weight-normal
;
font-size
:
12px
;
...
...
@@ -374,10 +373,6 @@
}
}
.text-metric-title
{
font-size
:
12px
;
}
.y-label-text
,
.x-label-text
{
fill
:
$gray-darkest
;
...
...
changelogs/unreleased/jivl-summary-statistics-prometheus-dashboard.yml
0 → 100644
浏览文件 @
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
浏览文件 @
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
浏览文件 @
657fea86
import
Vue
from
'
vue
'
;
import
GraphLegend
from
'
~/monitoring/components/graph/legend.vue
'
;
import
measurements
from
'
~/monitoring/utils/measurements
'
;
import
createTimeSeries
from
'
~/monitoring/utils/multiple_time_series
'
;
import
{
singleRowMetricsMultipleSeries
,
convertDatesMultipleSeries
}
from
'
../mock_data
'
;
const
createComponent
=
(
propsData
)
=>
{
const
Component
=
Vue
.
extend
(
GraphLegend
);
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
);
import
mountComponent
from
'
spec/helpers/vue_mount_component_helper
'
;
import
{
singleRowMetricsMultipleSeries
,
convertDatesMultipleSeries
,
}
from
'
../mock_data
'
;
defaultValuesComponent
.
timeSeries
=
timeSeries
;
function
getTextFromNode
(
component
,
selector
)
{
return
component
.
$el
.
querySelector
(
selector
).
firstChild
.
nodeValue
.
trim
();
}
const
convertedMetrics
=
convertDatesMultipleSeries
(
singleRowMetricsMultipleSeries
,
);
describe
(
'
GraphLegend
'
,
()
=>
{
describe
(
'
Computed props
'
,
()
=>
{
it
(
'
textTransform
'
,
()
=>
{
const
component
=
createComponent
(
defaultValuesComponent
);
const
defaultValuesComponent
=
{};
expect
(
component
.
textTransform
).
toContain
(
'
translate(15, 120) rotate(-90)
'
);
});
const
timeSeries
=
createTimeSeries
(
convertedMetrics
[
0
].
queries
,
500
,
300
,
120
);
it
(
'
xPosition
'
,
()
=>
{
const
component
=
createComponent
(
defaultValuesComponent
);
defaultValuesComponent
.
timeSeries
=
timeSeries
;
expect
(
component
.
xPosition
).
toEqual
(
180
);
});
describe
(
'
Legend Component
'
,
()
=>
{
let
vm
;
let
Legend
;
it
(
'
yPosition
'
,
()
=>
{
const
component
=
createComponent
(
defaultValuesComponent
);
beforeEach
(()
=>
{
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
'
,
()
=>
{
const
component
=
createComponent
(
defaultValuesComponent
);
it
(
'
formatMetricUsage should contain the unit of display and the current value selected via "currentDataIndex"
'
,
()
=>
{
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
(
'
translateLegendGroup should only change Y direction
'
,
()
=>
{
const
component
=
createComponent
(
defaultValuesComponent
);
it
(
'
strokeDashArray
'
,
()
=>
{
const
dashedArray
=
vm
.
strokeDashArray
(
'
dashed
'
);
const
dottedArray
=
vm
.
strokeDashArray
(
'
dotted
'
);
const
translatedCoordinate
=
component
.
translateLegendGroup
(
1
);
expect
(
translatedCoordinate
.
indexOf
(
'
translate(0,
'
)).
not
.
toEqual
(
-
1
);
expect
(
dashedArray
).
toEqual
(
'
6, 3
'
);
expect
(
dottedArray
).
toEqual
(
'
3, 3
'
);
});
it
(
'
formatMetricUsage should contain the unit of display and the current value selected via "currentDataIndex"
'
,
()
=>
{
const
component
=
createComponent
(
defaultValuesComponent
);
it
(
'
summaryMetrics gets the average and max of a series
'
,
()
=>
{
const
summary
=
vm
.
summaryMetrics
(
timeSeries
[
0
]
);
const
formattedMetricUsage
=
component
.
formatMetricUsage
(
timeSeries
[
0
]);
const
valueFromSeries
=
timeSeries
[
0
].
values
[
component
.
currentDataIndex
].
value
;
expect
(
formattedMetricUsage
.
indexOf
(
component
.
unitOfDisplay
)).
not
.
toEqual
(
-
1
);
expect
(
formattedMetricUsage
.
indexOf
(
valueFromSeries
)).
not
.
toEqual
(
-
1
);
expect
(
summary
.
indexOf
(
'
Max
'
)).
not
.
toEqual
(
-
1
);
expect
(
summary
.
indexOf
(
'
Avg
'
)).
not
.
toEqual
(
-
1
);
});
});
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
);
const
titles
=
component
.
$el
.
querySelectorAll
(
'
.legend-metric-title
'
);
describe
(
'
View
'
,
()
=>
{
beforeEach
(()
=>
{
vm
=
mountComponent
(
Legend
,
{
legendTitle
:
'
legend
'
,
timeSeries
,
currentDataIndex
:
0
,
unitOfDisplay
:
'
Req/Sec
'
,
}
);
}
);
expect
(
titles
[
0
].
textContent
.
indexOf
(
'
1xx
'
)).
not
.
toEqual
(
-
1
);
expect
(
titles
[
1
].
textContent
.
indexOf
(
'
2xx
'
)).
not
.
toEqual
(
-
1
);
expect
(
getTextFromNode
(
component
,
'
.y-label-text
'
)).
toEqual
(
component
.
yAxisLabel
);
});
it
(
'
should render the usage, title and time with multiple time series
'
,
()
=>
{
const
titles
=
vm
.
$el
.
querySelectorAll
(
'
.legend-metric-title
'
);
it
(
'
should contain the same number of legend groups as the timeSeries length
'
,
()
=>
{
const
component
=
createComponent
(
defaultValuesComponent
);
expect
(
titles
[
0
].
textContent
.
indexOf
(
'
1xx
'
)).
not
.
toEqual
(
-
1
);
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
浏览文件 @
657fea86
...
...
@@ -2,11 +2,15 @@ import Vue from 'vue';
import
Graph
from
'
~/monitoring/components/graph.vue
'
;
import
MonitoringMixins
from
'
~/monitoring/mixins/monitoring_mixins
'
;
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
projectPath
=
'
http://test.host/frontend-fixtures/environments-project
'
;
const
createComponent
=
(
propsData
)
=>
{
const
createComponent
=
propsData
=>
{
const
Component
=
Vue
.
extend
(
Graph
);
return
new
Component
({
...
...
@@ -14,7 +18,9 @@ const createComponent = (propsData) => {
}).
$mount
();
};
const
convertedMetrics
=
convertDatesMultipleSeries
(
singleRowMetricsMultipleSeries
);
const
convertedMetrics
=
convertDatesMultipleSeries
(
singleRowMetricsMultipleSeries
,
);
describe
(
'
Graph
'
,
()
=>
{
beforeEach
(()
=>
{
...
...
@@ -31,7 +37,9 @@ describe('Graph', () => {
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
'
,
()
=>
{
...
...
@@ -46,8 +54,9 @@ describe('Graph', () => {
});
const
transformedHeight
=
`
${
component
.
graphHeight
-
100
}
`
;
expect
(
component
.
axisTransform
.
indexOf
(
transformedHeight
))
.
not
.
toEqual
(
-
1
);
expect
(
component
.
axisTransform
.
indexOf
(
transformedHeight
)).
not
.
toEqual
(
-
1
,
);
});
it
(
'
outerViewBox gets a width and height property based on the DOM size of the element
'
,
()
=>
{
...
...
@@ -63,11 +72,11 @@ describe('Graph', () => {
const
viewBoxArray
=
component
.
outerViewBox
.
split
(
'
'
);
expect
(
typeof
component
.
outerViewBox
).
toEqual
(
'
string
'
);
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
({
graphData
:
convertedMetrics
[
1
],
classType
:
'
col-md-6
'
,
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录