提交 3ec562be 编写于 作者: P Piotr Dabkowski 提交者: Piotr Bryk

Added cumulative metrics frontend to pod list. (#1142)

* Added cumulative metrics frontend to pod list.

* Added tests + i18n and other improvements.

* Improved look of graph + organized code and made it more generic.

* Added graph rendering tests.
上级 9208f888
......@@ -3,6 +3,7 @@
"version": "1.4.0-beta1",
"dependencies": {
"angular": "~1.5.7",
"angular-animate": "~1.5.7",
"angular-aria": "~1.5.7",
"angular-material": "~1.0.9",
......@@ -11,6 +12,8 @@
"angular-resource": "~1.5.7",
"angular-sanitize": "~1.5.7",
"ansi-up": "drudru/ansi_up#~v1.3.0",
"d3": "^3.4.4",
"nvd3": "1.8.4",
"material-design-icons": "~2.2.3",
"roboto-fontface": "0.5.0",
"ng-jsoneditor": "angular-tools/ng-jsoneditor#~1.0.0",
......@@ -31,6 +34,7 @@
}
},
"devDependencies": {
"cljsjs-packages-externs": "https://github.com/cljsjs/packages.git#0c44b38658ad789a45d342bff4f13706276f293a",
"angular-mocks": "~1.5.7",
"google-closure-library": "*"
}
......
......@@ -123,6 +123,12 @@ function createCompileTask(translation) {
path.join(
conf.paths.nodeModules,
'google-closure-compiler/contrib/externs/angular-1.4-resource.js'),
path.join(
conf.paths.bowerComponents,
'cljsjs-packages-externs/d3/resources/cljsjs/d3/common/d3.ext.js'),
path.join(
conf.paths.bowerComponents,
'cljsjs-packages-externs/nvd3/resources/cljsjs/nvd3/common/nvd3.ext.js'),
path.join(conf.paths.externs, '**/*.js'),
],
generate_exports: null,
......
......@@ -579,4 +579,13 @@
<translation id="9213553703311647006" key="MSG_BREADCRUMBS_DEPLOY_APP_LABEL" source="/Users/bryk/src/github.com/kubernetes/dashboard/.tmp/serve/app-dev.js" desc="Breadcrum label for the deploy containerized app form view.">Create an app</translation>
<translation id="3098067826555563293" key="MSG_BREADCRUMBS_DEPLOY_FILE_LABEL" source="/Users/bryk/src/github.com/kubernetes/dashboard/.tmp/serve/app-dev.js" desc="Breadcrum label for the YAML upload form">Upload</translation>
<translation id="1050029015813261838" key="MSG_BREADCRUMBS_LOGS_LABEL" source="/Users/bryk/src/github.com/kubernetes/dashboard/.tmp/serve/app-dev.js" desc="Breadcrum label for the logs view.">Logs</translation>
<translation id="6172878964647554384" key="MSG_GRAPH_CPU_USAGE_LEGEND_LABEL" source="/usr/local/google/home/pdabkowski/WebstormProjects/src/github.com/kubernetes/dashboard/.tmp/serve/app-dev.js" desc="Name of the CPU usage metric as displayed in the legend.">CPU Usage</translation>
<translation id="6525531670828842343" key="MSG_GRAPH_MEMORY_USAGE_LEGEND_LABEL" source="/usr/local/google/home/pdabkowski/WebstormProjects/src/github.com/kubernetes/dashboard/.tmp/serve/app-dev.js" desc="Name of the memory usage metric as displayed in the legend.">Memory Usage</translation>
<translation id="3211276367494735750" key="MSG_GRAPH_CPU_USAGE_AXIS_LABEL" source="/usr/local/google/home/pdabkowski/WebstormProjects/src/github.com/kubernetes/dashboard/.tmp/serve/app-dev.js" desc="Name of Y axis showing CPU usage.">CPU Usage (cores)</translation>
<translation id="7757477254654009885" key="MSG_GRAPH_MEMORY_USAGE_AXIS_LABEL" source="/usr/local/google/home/pdabkowski/WebstormProjects/src/github.com/kubernetes/dashboard/.tmp/serve/app-dev.js" desc="Name of Y axis showing memory usage.">Memory Usage (bytes)</translation>
<translation id="185015236199417198" key="MSG_GRAPH_TIME_AXIS_LABEL" source="/usr/local/google/home/pdabkowski/WebstormProjects/src/github.com/kubernetes/dashboard/.tmp/serve/app-dev.js" desc="Name of time axis.">Time</translation>
<translation id="339987725992094148" key="MSG_GRAPH_CPU_AXIS_LABEL" source="/usr/local/google/home/pdabkowski/WebstormProjects/src/github.com/kubernetes/dashboard/.tmp/serve/app-dev.js" desc="Name of Y axis showing CPU usage.">CPU (cores)</translation>
<translation id="2029236165141474072" key="MSG_GRAPH_MEMORY_AXIS_LABEL" source="/usr/local/google/home/pdabkowski/WebstormProjects/src/github.com/kubernetes/dashboard/.tmp/serve/app-dev.js" desc="Name of Y axis showing memory usage.">Memory (bytes)</translation>
<translation id="4576303150557908117" key="MSG_GRAPH_CPU_LIMIT_LEGEND_LABEL" source="/usr/local/google/home/pdabkowski/WebstormProjects/src/github.com/kubernetes/dashboard/.tmp/serve/app-dev.js" desc="Name of the CPU usage metric as displayed in the legend.">CPU Limit</translation>
<translation id="1378300715036317101" key="MSG_GRAPH_DATA_POINT_NOT_AVAILABLE" source="/usr/local/google/home/pdabkowski/WebstormProjects/src/github.com/kubernetes/dashboard/.tmp/serve/app-dev.js" desc="String to display when data point is not available.">N/A</translation>
</translationbundle>
\ No newline at end of file
......@@ -768,4 +768,13 @@
<translation id="9213553703311647006" key="MSG_BREADCRUMBS_DEPLOY_APP_LABEL" source="/Users/bryk/src/github.com/kubernetes/dashboard/.tmp/serve/app-dev.js" desc="Breadcrum label for the deploy containerized app form view.">Create an app</translation>
<translation id="3098067826555563293" key="MSG_BREADCRUMBS_DEPLOY_FILE_LABEL" source="/Users/bryk/src/github.com/kubernetes/dashboard/.tmp/serve/app-dev.js" desc="Breadcrum label for the YAML upload form">Upload</translation>
<translation id="1050029015813261838" key="MSG_BREADCRUMBS_LOGS_LABEL" source="/Users/bryk/src/github.com/kubernetes/dashboard/.tmp/serve/app-dev.js" desc="Breadcrum label for the logs view.">Logs</translation>
<translation id="6172878964647554384" key="MSG_GRAPH_CPU_USAGE_LEGEND_LABEL" source="/usr/local/google/home/pdabkowski/WebstormProjects/src/github.com/kubernetes/dashboard/.tmp/serve/app-dev.js" desc="Name of the CPU usage metric as displayed in the legend.">CPU Usage</translation>
<translation id="6525531670828842343" key="MSG_GRAPH_MEMORY_USAGE_LEGEND_LABEL" source="/usr/local/google/home/pdabkowski/WebstormProjects/src/github.com/kubernetes/dashboard/.tmp/serve/app-dev.js" desc="Name of the memory usage metric as displayed in the legend.">Memory Usage</translation>
<translation id="3211276367494735750" key="MSG_GRAPH_CPU_USAGE_AXIS_LABEL" source="/usr/local/google/home/pdabkowski/WebstormProjects/src/github.com/kubernetes/dashboard/.tmp/serve/app-dev.js" desc="Name of Y axis showing CPU usage.">CPU Usage (cores)</translation>
<translation id="7757477254654009885" key="MSG_GRAPH_MEMORY_USAGE_AXIS_LABEL" source="/usr/local/google/home/pdabkowski/WebstormProjects/src/github.com/kubernetes/dashboard/.tmp/serve/app-dev.js" desc="Name of Y axis showing memory usage.">Memory Usage (bytes)</translation>
<translation id="185015236199417198" key="MSG_GRAPH_TIME_AXIS_LABEL" source="/usr/local/google/home/pdabkowski/WebstormProjects/src/github.com/kubernetes/dashboard/.tmp/serve/app-dev.js" desc="Name of time axis.">Time</translation>
<translation id="339987725992094148" key="MSG_GRAPH_CPU_AXIS_LABEL" source="/usr/local/google/home/pdabkowski/WebstormProjects/src/github.com/kubernetes/dashboard/.tmp/serve/app-dev.js" desc="Name of Y axis showing CPU usage.">CPU (cores)</translation>
<translation id="2029236165141474072" key="MSG_GRAPH_MEMORY_AXIS_LABEL" source="/usr/local/google/home/pdabkowski/WebstormProjects/src/github.com/kubernetes/dashboard/.tmp/serve/app-dev.js" desc="Name of Y axis showing memory usage.">Memory (bytes)</translation>
<translation id="4576303150557908117" key="MSG_GRAPH_CPU_LIMIT_LEGEND_LABEL" source="/usr/local/google/home/pdabkowski/WebstormProjects/src/github.com/kubernetes/dashboard/.tmp/serve/app-dev.js" desc="Name of the CPU usage metric as displayed in the legend.">CPU Limit</translation>
<translation id="1378300715036317101" key="MSG_GRAPH_DATA_POINT_NOT_AVAILABLE" source="/usr/local/google/home/pdabkowski/WebstormProjects/src/github.com/kubernetes/dashboard/.tmp/serve/app-dev.js" desc="String to display when data point is not available.">N/A</translation>
</translationbundle>
\ No newline at end of file
......@@ -733,6 +733,7 @@ func (apiHandler *APIHandler) handleGetPods(
namespace := parseNamespacePathParameter(request)
dataSelect := parseDataSelectPathParameter(request)
dataSelect.MetricQuery = dataselect.StandardMetrics // download standard metrics - cpu, and memory - by default
result, err := pod.GetPodList(apiHandler.client, apiHandler.heapsterClient, namespace, dataSelect)
if err != nil {
handleInternalError(response, err)
......
......@@ -30,6 +30,10 @@ type DataSelectQuery struct {
var NoMetrics = NewMetricQuery(nil, nil)
// StandardMetrics query results in a standard metrics being returned.
// standard metrics are: cpu usage, memory usage and aggregation = sum.
var StandardMetrics = NewMetricQuery([]string{"cpu/usage_rate", "memory/usage"}, metric.OnlySumAggregation)
// MetricQuery holds parameters for metric extraction process.
// It accepts list of metrics to be downloaded and a list of aggregations that should be performed for each metric.
// Query has this format metrics=metric1,metric2,...&aggregations=aggregation1,aggregation2,...
......
......@@ -32,7 +32,7 @@ type PodList struct {
// Unordered list of Pods.
Pods []Pod `json:"pods"`
CumulativeMetrics []metric.Metric `json:"cumulativeMetrics,omitempty"`
CumulativeMetrics []metric.Metric `json:"cumulativeMetrics"`
}
// Pod is a presentation layer view of Kubernetes Pod resource. This means
......
......@@ -361,11 +361,29 @@ backendApi.DeploymentDetail;
/**
* @typedef {{
* pods: !Array<!backendApi.Pod>,
* listMeta: !backendApi.ListMeta
* listMeta: !backendApi.ListMeta,
* cumulativeMetrics: (!Array<!backendApi.Metric>|null),
* }}
*/
backendApi.PodList;
/**
* @typedef {{
* dataPoints: !Array<!backendApi.DataPoint>,
* metricName: string,
* aggregation: string,
* }}
*/
backendApi.Metric;
/**
* @typedef {{
* x: number,
* y: number,
* }}
*/
backendApi.DataPoint;
/**
* @typedef {{
* name: string,
......
......@@ -41,6 +41,7 @@ $body: #eee;
$emphasis: #000;
$content-background: #fff;
$chart-1: #00c752;
$chart-2: #326de6;
$success-color: #008000;
$error-color: #ff5722;
......
......@@ -26,6 +26,7 @@ import paginationModule from './../pagination/pagination_module';
import namespaceModule from './../namespace/namespace_module';
import sparklineDirective from './sparkline/sparkline_directive';
import warnThresholdDirective from './warnthreshold/warnthreshold_directive';
import {graphComponent} from './graph/graph_component';
/**
* Module containing common components for the application.
......@@ -50,4 +51,5 @@ export default angular
.directive('kdLabels', labelsDirective)
.directive('kdMiddleEllipsis', middleEllipsisDirective)
.directive('kdSparkline', sparklineDirective)
.directive('kdWarnThreshold', warnThresholdDirective);
.directive('kdWarnThreshold', warnThresholdDirective)
.component('kdGraph', graphComponent);
<!--
Copyright 2015 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<div></div>
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {axisSettings, metricDisplaySettings, TimeAxisType} from './graph_settings';
export class GraphController {
/**
* @ngInject
* @param {!angular.Scope} $scope
* @param {!angular.JQLite} $element
*/
constructor($scope, $element) {
/** @private {!angular.Scope} */
this.scope_ = $scope;
/** @private {!angular.JQLite} */
this.element_ = $element;
/**
* List of pods. Initialized from the scope.
* @export {!Array<!backendApi.Metric>}
*/
this.metrics;
}
$onInit() {
// draw graph if data is available
if (this.metrics !== null && this.metrics.length !== 0) {
this.generateGraph();
}
}
/**
* Generates graph using this.metrics provided.
* @private
*/
generateGraph() {
let chart;
nv.addGraph(() => {
// basic chart options - multiChart with interactive tooltip
chart = nv.models.multiChart().margin({top: 30, right: 90, bottom: 60, left: 75}).options({
duration: 300,
tooltips: true,
useInteractiveGuideline: true,
});
let data = [];
let yAxis1Type;
let yAxis2Type;
let y1max = 1;
let y2max = 1;
// iterate over metrics and add them to graph display
for (let metric of this.metrics) {
// don't display metric if the number of its data points is smaller than 2
if (metric.dataPoints.length < 2) {
continue;
}
// check whether it's possible to display this metric
if (metric.metricName in metricDisplaySettings) {
let metricSettings = metricDisplaySettings[metric.metricName];
if (metricSettings.yAxis === 1) {
if (typeof yAxis1Type === 'undefined') {
yAxis1Type = metricSettings.yAxisType;
} else if (yAxis1Type !== metricSettings.yAxisType) {
throw new Error(
'Can\'t display requested data - metrics have inconsistent types of y1 axis!');
}
y1max = Math.max(y1max, Math.max(...metric.dataPoints.map((e) => e.y)));
} else { // yAxis is 2
if (typeof yAxis2Type === 'undefined') {
yAxis2Type = metricSettings.yAxisType;
} else if (yAxis2Type !== metricSettings.yAxisType) {
throw new Error(
'Can\'t display requested data - metrics have inconsistent types of y2 axis!');
}
y2max = Math.max(y2max, Math.max(...metric.dataPoints.map((e) => e.y)));
}
data.push({
'area': metricSettings.area,
'values': metric.dataPoints,
'key': metricSettings.key,
'color': metricSettings.color,
'fillOpacity': metricSettings.fillOpacity,
'strokeWidth': metricSettings.strokeWidth,
'type': metricSettings.type,
'yAxis': metricSettings.yAxis,
});
}
}
// customise X axis (hardcoded time).
let xAxisSettings = axisSettings[TimeAxisType];
chart.xAxis.axisLabel(xAxisSettings.label)
.tickFormat(xAxisSettings.formatter)
.staggerLabels(true);
// customise Y axes
if (typeof yAxis1Type !== 'undefined') {
let yAxis1Settings = axisSettings[yAxis1Type];
chart.yAxis1.axisLabel(yAxis1Settings.label).tickFormat(yAxis1Settings.formatter);
chart.yDomain1([0, y1max]);
}
if (typeof yAxis2Type !== 'undefined') {
let yAxis2Settings = axisSettings[yAxis2Type];
chart.yAxis2.axisLabel(yAxis2Settings.label).tickFormat(yAxis2Settings.formatter);
chart.yDomain2([0, y2max]);
}
// hack to fix tooltip to use appropriate formatters instead of raw numbers.
// d is the value to be formatted, tooltip_row_index is a index of a row in tooltip that is
// being formatted.
chart.interactiveLayer.tooltip.valueFormatter(function(d, tooltip_row_index) {
let notDisabledMetrics = data.filter((e) => !e.disabled);
if (tooltip_row_index < notDisabledMetrics.length) {
return notDisabledMetrics[tooltip_row_index].yAxis === 1 ? chart.yAxis1.tickFormat()(d) :
chart.yAxis2.tickFormat()(d);
}
// sometimes disabled property on data is updated slightly before tooltip is notified so we
// may have wrong tooltip_row_index
// in this case return raw value. Note - the period of time when unformatted value is
// displayed is very brief -
// too short to notice.
return d;
});
// generate graph
let graphArea = d3.select(this.element_[0]);
let svg = graphArea.append('svg');
svg.attr('height', '300px').datum(data).call(chart);
// add grey line to the bottom to separate from the rest of the page.
svg.style({
'background-color': 'white',
'border-bottom-style': 'solid',
'border-bottom-width': '1px',
'border-bottom-color': 'rgba(0, 0, 0, 0.117647)',
});
// update the graph in case of graph area resize
nv.utils.windowResize(chart.update);
this.scope_.$watch(
() => graphArea.node().getBoundingClientRect().width, // variable to watch
() => setTimeout(chart.update, 500), // TODO - this should be changed to just
// chart.update after we implement different method
// of left hand side nav animation (instant DOM
// change).
false // not a deep watch
);
return chart;
});
}
}
/**
* Definition object for the component that displays graph with CPU and Memory usage metrics.
*
* @type {!angular.Component}
*/
export const graphComponent = {
bindings: {
'metrics': '<',
},
controller: GraphController,
templateUrl: 'common/components/graph/graph.html',
};
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {formatCpuUsage, formatMemoryUsage, formatTime} from './graph_tick_formatters';
const i18n = {
/** @export {string} @desc Name of the CPU usage metric as displayed in the legend. */
MSG_GRAPH_CPU_USAGE_LEGEND_LABEL: goog.getMsg('CPU Usage'),
/** @export {string} @desc Name of the memory usage metric as displayed in the legend. */
MSG_GRAPH_MEMORY_USAGE_LEGEND_LABEL: goog.getMsg('Memory Usage'),
/** @export {string} @desc Name of the CPU limit metric as displayed in the legend. */
MSG_GRAPH_CPU_LIMIT_LEGEND_LABEL: goog.getMsg('CPU Limit'),
/** @export {string} @desc Name of Y axis showing CPU usage. */
MSG_GRAPH_CPU_AXIS_LABEL: goog.getMsg('CPU (cores)'),
/** @export {string} @desc Name of Y axis showing memory usage. */
MSG_GRAPH_MEMORY_AXIS_LABEL: goog.getMsg('Memory (bytes)'),
/** @export {string} @desc Name of time axis. */
MSG_GRAPH_TIME_AXIS_LABEL: goog.getMsg('Time'),
};
export const CPUAxisType = 'CPUAxisType';
export const MemoryAxisType = 'MemoryAxisType';
export const TimeAxisType = 'TimeAxisType';
/**
* Settings used by GraphController to display different metrics.
*
* @type {!Object<string, !Object<string, !metricDisplaySettings.metricSetting>>}
*/
export const metricDisplaySettings = {
'cpu/usage_rate': {
yAxisType: CPUAxisType,
area: true,
key: i18n.MSG_GRAPH_CPU_USAGE_LEGEND_LABEL,
color: '#00c752', // $chart-1
fillOpacity: 0.2,
strokeWidth: 3,
type: 'line',
yAxis: 1,
},
'cpu/limit': {
yAxisType: CPUAxisType,
area: true,
key: i18n.MSG_GRAPH_CPU_LIMIT_LEGEND_LABEL,
color: '#f51200',
fillOpacity: 0.2,
strokeWidth: 3,
type: 'line',
yAxis: 1,
},
'memory/usage': {
yAxisType: MemoryAxisType,
area: true,
key: i18n.MSG_GRAPH_MEMORY_USAGE_LEGEND_LABEL,
color: '#326de6', // $chart-2
fillOpacity: 0.2,
strokeWidth: 3,
type: 'line',
yAxis: 2,
},
};
/**
* Settings object that should be provided for every metric supported by graph frontend.
*
* @typedef {{
* yAxisType: !backendApi.ObjectMeta,
* area: boolean,
* key: string,
* color: string,
* fillOpacity: number,
* strokeWidth: number,
* type: string,
* yAxis: number,
* }}
*/
metricDisplaySettings.metricSetting;
/**
* Settings used by GraphController to display different axes.
*
* @type {!Object<string, !Object<string, !axisSettings.axisSetting>>}
*/
export const axisSettings = {
'CPUAxisType': {
formatter: formatCpuUsage,
label: i18n.MSG_GRAPH_CPU_AXIS_LABEL,
},
'MemoryAxisType': {
formatter: formatMemoryUsage,
label: i18n.MSG_GRAPH_MEMORY_AXIS_LABEL,
},
'TimeAxisType': {
formatter: formatTime,
label: i18n.MSG_GRAPH_TIME_AXIS_LABEL,
},
};
/**
* Settings object that should be provided for every axis type supported by graph frontend.
*
* @typedef {{
* formatter: function(number):string,
* label: string,
* }}
*/
axisSettings.axisSetting;
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import coresFilter from 'common/filters/cores_filter';
import memoryFilter from 'common/filters/memory_filter';
/**
* Formats the number to contain ideally 3 significant figures, but reduces the number of significant figures for
* small numbers in order to keep the number of decimal places down to 3. So for numbers below 0.01 number of
* significant figures will be 1.
* @param {number} d
* @return {string}
* @private
*/
function precisionFilter(d) {
if (d >= 1000) {
return d3.format(',')(d.toPrecision(3));
}
if (d < 0.01) {
return d.toPrecision(1);
} else if (d < 0.1) {
return d.toPrecision(2);
}
return d.toPrecision(3);
}
/**
* Returns formatted memory usage value.
* @param {number} d
* @return {string}
*/
export function formatMemoryUsage(d) {
return d === null ? i18n.MSG_GRAPH_DATA_POINT_NOT_AVAILABLE : memoryFilter(precisionFilter)(d);
}
/**
* Returns formatted CPU usage value.
* @param {number} d
* @return {string}
*/
export function formatCpuUsage(d) {
return d === null ? i18n.MSG_GRAPH_DATA_POINT_NOT_AVAILABLE : coresFilter(precisionFilter)(d);
}
/**
* Returns formatted time.
* @param {number} d
* @return {string}
*/
export function formatTime(d) {
return d3.time.format('%H:%M')(new Date(1000 * d));
}
const i18n = {
/** @export {string} @desc String to display when data point is not available. */
MSG_GRAPH_DATA_POINT_NOT_AVAILABLE: goog.getMsg('N/A'),
};
......@@ -17,7 +17,5 @@ limitations under the License.
<svg viewBox="0,0 1,1"
preserveAspectRatio="none"
class="kd-sparkline">
<polygon
ng-attr-points="0,1 {{::sparklineCtrl.polygonPoints()}} 1,1"
class="kd-sparkline-series"/>
<polygon ng-attr-points="0,1 {{::sparklineCtrl.polygonPoints()}} 1,1"/>
</svg>
......@@ -24,3 +24,11 @@
.kd-sparkline-series {
fill: $chart-1;
}
.kd-sparkline-cpu-series {
fill: $chart-1;
}
.kd-sparkline-memory-series {
fill: $chart-2;
}
......@@ -90,14 +90,14 @@ limitations under the License.
</kd-resource-card-column>
<kd-resource-card-column ng-if="::$ctrl.showMetrics()">
<div ng-if="::$ctrl.hasCpuUsage(pod)">
<kd-sparkline timeseries="::pod.metrics.cpuUsageHistory"></kd-sparkline>
<kd-sparkline timeseries="::pod.metrics.cpuUsageHistory" class="kd-sparkline-cpu-series"></kd-sparkline>
{{::(pod.metrics.cpuUsage | kdCores)}}
</div>
<div ng-if="::!$ctrl.hasCpuUsage(pod)">-</div>
</kd-resource-card-column>
<kd-resource-card-column ng-if="::$ctrl.showMetrics()">
<div ng-if="::$ctrl.hasMemoryUsage(pod)">
<kd-sparkline timeseries="::pod.metrics.memoryUsageHistory"></kd-sparkline>
<kd-sparkline timeseries="::pod.metrics.memoryUsageHistory" class="kd-sparkline-memory-series"></kd-sparkline>
{{::(pod.metrics.memoryUsage | kdMemory)}}
</div>
<div ng-if="::!$ctrl.hasMemoryUsage(pod)">-</div>
......@@ -162,14 +162,14 @@ limitations under the License.
</kd-resource-card-column>
<kd-resource-card-column ng-if="::$ctrl.showMetrics()">
<div ng-if="::$ctrl.hasCpuUsage(pod)">
<kd-sparkline timeseries="::pod.metrics.cpuUsageHistory"></kd-sparkline>
<kd-sparkline timeseries="::pod.metrics.cpuUsageHistory" class="kd-sparkline-cpu-series"></kd-sparkline>
{{::(pod.metrics.cpuUsage | kdCores)}}
</div>
<div ng-if="::!$ctrl.hasCpuUsage(pod)">-</div>
</kd-resource-card-column>
<kd-resource-card-column ng-if="::$ctrl.showMetrics()">
<div ng-if="::$ctrl.hasMemoryUsage(pod)">
<kd-sparkline timeseries="::pod.metrics.memoryUsageHistory"></kd-sparkline>
<kd-sparkline timeseries="::pod.metrics.memoryUsageHistory" class="kd-sparkline-memory-series"></kd-sparkline>
{{::(pod.metrics.memoryUsage | kdMemory)}}
</div>
<div ng-if="::!$ctrl.hasMemoryUsage(pod)">-</div>
......
......@@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<kd-graph metrics="$ctrl.podList.cumulativeMetrics"></kd-graph>
<kd-content-card ng-if="!$ctrl.shouldShowZeroState()">
<kd-content>
......
......@@ -6,5 +6,6 @@
// Define missing global Angular testing environment variables.
"globals": {
"angular": true,
"nv": true,
},
}
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import componentsModule from 'common/components/components_module';
/**
* @type {!Array<!backendApi.Metric>}
*/
let stdMetrics = [
{
'dataPoints': [
{'x': 1472219880, 'y': 50},
{'x': 1472219940, 'y': 40},
{'x': 1472220000, 'y': 48},
],
'metricName': 'cpu/usage_rate',
'aggregation': 'sum',
},
{
'dataPoints': [
{'x': 1472219880, 'y': 976666624},
{'x': 1472219940, 'y': 976728064},
],
'metricName': 'memory/usage',
'aggregation': 'sum',
},
];
/**
* @type {!Array<!backendApi.Metric>}
*/
let metricsWithTooFewDataPoints = [
{
'dataPoints': [
{'x': 1472219880, 'y': 50},
],
'metricName': 'cpu/usage_rate',
'aggregation': 'sum',
},
{
'dataPoints': [
{'x': 1472219880, 'y': 976666624},
{'x': 1472219940, 'y': 976728064},
],
'metricName': 'memory/usage',
'aggregation': 'sum',
},
];
describe('Graph component controller', () => {
/**
* @type {!common/components/graph/graph_component.GraphController}
*/
let ctrl;
/** @type {!angular.JQLite} */
let element;
beforeEach(() => {
angular.mock.module(componentsModule.name);
angular.mock.inject(($componentController) => {
element = angular.element(document.createElement('div'));
element.appendTo(document.body);
ctrl = $componentController('kdGraph', {$element: element}, {metrics: stdMetrics});
});
});
it('should instantiate the controller properly', () => { expect(ctrl).not.toBeUndefined(); });
it('should render svg graph inside parent element', () => {
ctrl.$onInit();
nv.render.queue.pop().generate();
expect(element.children(':first').is('svg')).toBeTruthy();
});
it('should not render graph if metrics are null', () => {
ctrl.metrics = null;
ctrl.$onInit();
if (nv.render.queue.length > 0) {
nv.render.queue.pop().generate();
}
expect(element.children(':first').is('svg')).toBeFalsy();
});
it('should not render graph if metrics were empty', () => {
ctrl.metrics = [];
ctrl.$onInit();
if (nv.render.queue.length > 0) {
nv.render.queue.pop().generate();
}
expect(element.children(':first').is('svg')).toBeFalsy();
});
it('should render a graph with correct number of data points', () => {
ctrl.$onInit();
nv.render.queue.pop().generate();
expect(element.find('.lines1Wrap path.nv-line').attr('d').split(',').length).toEqual(4);
expect(element.find('.lines2Wrap path.nv-line').attr('d').split(',').length).toEqual(3);
});
it('should only render metrics with at least 2 data points', () => {
ctrl.metrics = metricsWithTooFewDataPoints;
ctrl.$onInit();
nv.render.queue.pop().generate();
expect(element.find('.lines1Wrap path.nv-line').attr('d')).toBeUndefined();
expect(element.find('.lines2Wrap path.nv-line').attr('d').split(',').length).toEqual(3);
});
it('- Y axes should be from 0 to max', () => {
ctrl.$onInit();
nv.render.queue.pop().generate();
// y1
expect(element.find('.nv-y1 .nv-axisMin-y > text').text()).toEqual('0');
expect(element.find('.nv-y1 .nv-axisMax-y > text').text()).toEqual('0.050');
// y2
expect(element.find('.nv-y2 .nv-axisMin-y > text').text()).toEqual('0');
expect(element.find('.nv-y2 .nv-axisMax-y > text').text()).toEqual('931 Mi');
});
it('- X axis should have correct tick format', () => {
ctrl.$onInit();
nv.render.queue.pop().generate();
expect(element.find('.nv-x .nv-axisMin-x > text').text()).toMatch(/\d?\d:58/);
expect(element.find('.nv-x .nv-axisMax-x > text').text()).toMatch(/\d?\d:00/);
});
});
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {formatCpuUsage, formatMemoryUsage} from 'common/components/graph/graph_tick_formatters';
describe('Memory usage value formatter', () => {
it('should format memory', () => {
expect(formatMemoryUsage(null)).toEqual('N/A');
expect(formatMemoryUsage(0)).toEqual('0');
expect(formatMemoryUsage(1)).toEqual('1.00');
expect(formatMemoryUsage(2)).toEqual('2.00');
expect(formatMemoryUsage(1000)).toEqual('1,000');
expect(formatMemoryUsage(1024)).toEqual('1,020');
expect(formatMemoryUsage(1025)).toEqual('1.00 Ki');
expect(formatMemoryUsage(7896)).toEqual('7.71 Ki');
expect(formatMemoryUsage(109809)).toEqual('107 Ki');
expect(formatMemoryUsage(768689899)).toEqual('733 Mi');
expect(formatMemoryUsage(768689899789)).toEqual('716 Gi');
expect(formatMemoryUsage(76868989978978)).toEqual('69.9 Ti');
expect(formatMemoryUsage(7686898997897878)).toEqual('6.83 Pi');
expect(formatMemoryUsage(768689899789787867898766)).toEqual('683,000,000 Pi');
});
});
describe('CPU usage value formatter', () => {
it('should format memory', () => {
expect(formatMemoryUsage(null)).toEqual('N/A');
expect(formatCpuUsage(0)).toEqual('0');
expect(formatCpuUsage(1)).toEqual('0.001');
expect(formatCpuUsage(2)).toEqual('0.002');
expect(formatCpuUsage(11)).toEqual('0.011');
expect(formatCpuUsage(100)).toEqual('0.100');
expect(formatCpuUsage(140)).toEqual('0.140');
expect(formatCpuUsage(1000)).toEqual('1.00');
expect(formatCpuUsage(1024)).toEqual('1.02');
expect(formatCpuUsage(1025)).toEqual('1.02');
expect(formatCpuUsage(7896)).toEqual('7.90');
expect(formatCpuUsage(109809)).toEqual('110');
expect(formatCpuUsage(768689899)).toEqual('769 k');
expect(formatCpuUsage(768689899789)).toEqual('769 M');
expect(formatCpuUsage(76868989978978)).toEqual('76.9 G');
expect(formatCpuUsage(7686898997897878)).toEqual('7.69 T');
expect(formatCpuUsage(76868989978978876876876468543578)).toEqual('76,900,000,000,000,000 T');
});
});
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册