TBR: update oncall dashboard to better support replicated services.
PresubmitTest: none
Change-Id: I4d77db1b8304578cf181326cf554c873c024763e
diff --git a/oncall/client/browser/components/metric-actions-panel/index.js b/oncall/client/browser/components/metric-actions-panel/index.js
index 4cf02a6..3271289 100644
--- a/oncall/client/browser/components/metric-actions-panel/index.js
+++ b/oncall/client/browser/components/metric-actions-panel/index.js
@@ -2,94 +2,254 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+var dateformat = require('dateformat');
var hg = require('mercury');
var h = require('mercury').h;
+var Consts = require('../../constants');
+var MouseMoveHandler = require('../../mousemove-handler.js');
var Util = require('../../util');
-module.exports = {
- render: render
-};
+module.exports = create;
+module.exports.render = render;
var VANADIUM_PRODUCTION_NAMESPACE_ID = '4b20e0dc-cacf-11e5-87ec-42010af0020b';
var AUTH_PRODUCTION_NAMESPACE_ID = '2b6d405a-b4a6-11e5-9776-42010af000a6';
+/** Constructor. */
+function create(data) {
+ if (!data) {
+ return null;
+ }
+
+ return hg.state({
+ selectedMetric: hg.struct(data.selectedMetric),
+ selectedMetricIndex: hg.value(data.selectedMetricIndex),
+ visible: hg.value(data.visible),
+
+ mouseOffsetFactor: hg.value(-1),
+
+ channels: {
+ mouseClickOnMetric: mouseClickOnMetric,
+ closeMetricActionsPanel: closeMetricActionsPanel,
+ mouseMoveOnSparkline: mouseMoveOnSparkline,
+ mouseOutOfSparkline: mouseOutOfSparkline
+ }
+ });
+}
+
+/** Callback for moving mouse on sparkline. */
+function mouseClickOnMetric(state, data) {
+ state.selectedMetricIndex.set(data.index);
+}
+
+function closeMetricActionsPanel(state) {
+ state.visible.set(false);
+}
+
+/** Callback for moving mouse on sparkline. */
+function mouseMoveOnSparkline(state, data) {
+ state.mouseOffsetFactor.set(data.f);
+}
+
+/** Callback for moving mouse out of sparkline. */
+function mouseOutOfSparkline(state) {
+ state.mouseOffsetFactor.set(-1);
+}
+
/** The main render function. */
-function render(state, selectedMetric, data) {
+function render(state, curData) {
+ var colData = state.selectedMetric.colData;
+ var metricsData = curData[colData.dataKey][colData.metricName];
+
+ var list = renderReplicaList(state, metricsData, curData);
+ var panel = renderMetric(
+ state, metricsData[state.selectedMetricIndex],
+ state.selectedMetric.serviceName, curData);
+ return h('div.metric-actions-container',
+ h('div.inner-container', [list, panel])
+ );
+}
+
+function renderReplicaList(state, metricsData, curData) {
+ var colData = state.selectedMetric.colData;
+ var list = metricsData.map(function(metricData, index) {
+ var curIndex = index;
+
+ var points = '0,100 100,100';
+ var timestamps = metricData.HistoryTimestamps;
+ var values = metricData.HistoryValues;
+ if (timestamps.length > 0 && values.length > 0) {
+ points = Util.genPolylinePoints(
+ timestamps, values,
+ curData.MinTime, curData.MaxTime,
+ metricData.MinValue, metricData.MaxValue);
+ }
+ var curValue = Util.formatValue(metricData.CurrentValue);
+ var extraCurValueClass = '';
+ var mouseOffset = 100 * state.mouseOffsetFactor;
+ if (mouseOffset >= 0) {
+ curValue = Util.formatValue(Util.interpolateValue(
+ metricData.CurrentValue, state.mouseOffsetFactor,
+ timestamps, values));
+ extraCurValueClass = '.history';
+ }
+
+ // Handle error when getting time series.
+ var hasErrors = metricData.ErrMsg !== '';
+ var extraColMetricClass = '';
+ if (hasErrors) {
+ curValue = '?';
+ extraColMetricClass = '.warning';
+ }
+ // Handle current value over threshold.
+ var overThreshold = (
+ colData.threshold && metricData.CurrentValue >= colData.threshold);
+ var thresholdValue = -100;
+ if (overThreshold) {
+ extraColMetricClass = '.fatal';
+ thresholdValue = (colData.threshold-metricData.MinValue)/
+ (metricData.MaxValue-metricData.MinValue)*100.0;
+ }
+ // Handle stale data.
+ var tsLen = timestamps.length;
+ if (tsLen > 0) {
+ var lastTimestamp = timestamps[tsLen - 1];
+ if (curData.MaxTime - lastTimestamp >
+ Consts.stableDataThresholdInSeconds) {
+ extraColMetricClass = '.warning';
+ }
+ } else {
+ extraColMetricClass = '.warning';
+ }
+
+ if (index === state.selectedMetricIndex) {
+ extraColMetricClass += '.selected';
+ }
+
+ var sparkline = h('div.col-metric' + extraColMetricClass, {
+ 'ev-click': hg.send(state.channels.mouseClickOnMetric, {
+ index: curIndex
+ })
+ }, [
+ h('div.highlight-overlay'),
+ Util.renderMouseLine(mouseOffset),
+ h('div.sparkline', {
+ 'ev-mousemove': new MouseMoveHandler(
+ state.channels.mouseMoveOnSparkline),
+ 'ev-mouseout': hg.send(state.channels.mouseOutOfSparkline)
+ }, [
+ //renderThreshold(thresholdValue),
+ Util.renderSparkline(points)
+ ]),
+ h('div.cur-value' + extraCurValueClass, [curValue])
+ ]);
+ return sparkline;
+ });
+ return h('div.metric-actions-list', list);
+}
+
+function renderMetric(state, metricData, serviceName, curData) {
var namespaceId = VANADIUM_PRODUCTION_NAMESPACE_ID;
- if (selectedMetric.metricData.Project === 'vanadium-auth-production') {
+ if (metricData.Project === 'vanadium-auth-production') {
namespaceId = AUTH_PRODUCTION_NAMESPACE_ID;
}
- var panel = h('div.metric-actions-content', [
+
+ // Calculate current timestamp.
+ var curTimestamp = curData.MaxTime;
+ var extraCurTimestampClass = '';
+ if (metricData.HistoryTimestamps && metricData.HistoryTimestamps.length > 0) {
+ curTimestamp = metricData.HistoryTimestamps[
+ metricData.HistoryTimestamps.length - 1];
+ }
+ if (state.mouseOffsetFactor >= 0) {
+ curTimestamp =
+ (curData.MaxTime - curData.MinTime) * state.mouseOffsetFactor +
+ curData.MinTime;
+ extraCurTimestampClass = '.history';
+ }
+
+ // Calculate current value.
+ var curValue = Util.formatValue(metricData.CurrentValue);
+ var extraCurValueClass = '';
+ var mouseOffset = 100 * state.mouseOffsetFactor;
+ if (mouseOffset >= 0) {
+ curValue = Util.formatValue(Util.interpolateValue(
+ metricData.CurrentValue, state.mouseOffsetFactor,
+ metricData.HistoryTimestamps, metricData.HistoryValues));
+ extraCurValueClass = '.history';
+ }
+
+ return h('div.metric-actions-content', [
h('div.row', [
h('div.item-label', 'Service Name'),
- h('div.item-value', selectedMetric.serviceName)
+ h('div.item-value', serviceName)
]),
h('div.row', [
h('div.item-label', 'Service Version'),
- h('div.item-value', selectedMetric.metricData.ServiceVersion)
+ h('div.item-value', metricData.ServiceVersion)
]),
h('div.row', [
h('div.item-label', 'Metric Type'),
h('div.item-value',
- selectedMetric.metricData.ResultType.replace('resultType', ''))
+ metricData.ResultType.replace('resultType', ''))
]),
h('div.row', [
h('div.item-label', 'Metric Name'),
- h('div.item-value', selectedMetric.metricData.MetricName)
+ h('div.item-value', metricData.MetricName)
]),
h('div.row', [
h('div.item-label', 'Current Value'),
- h('div.item-value',
- Util.formatValue(selectedMetric.metricData.CurrentValue))
+ h('div.item-value' + extraCurValueClass, curValue)
+ ]),
+ h('div.row', [
+ h('div.item-label', 'Current Time'),
+ h('div.item-value' + extraCurTimestampClass,
+ curTimestamp ===
+ '?' ? '?' : dateformat(new Date(curTimestamp * 1000)))
]),
h('div.row', [
h('div.item-label', 'Logs'),
h('div.item-value', h('a', {
- href: 'logs?p=' + selectedMetric.metricData.Project +
- '&z=' + selectedMetric.metricData.Zone +
- '&d=' + selectedMetric.metricData.PodName +
- '&c=' + selectedMetric.metricData.MainContainer,
+ href: 'logs?p=' + metricData.Project +
+ '&z=' + metricData.Zone +
+ '&d=' + metricData.PodName +
+ '&c=' + metricData.MainContainer,
target: '_blank'
- }, selectedMetric.metricData.MainContainer)),
+ }, metricData.MainContainer)),
]),
h('div.space'),
h('div.row', [
h('div.item-label', 'Pod Name'),
h('div.item-value', h('a', {
href: 'https://app.google.stackdriver.com/gke/pod/1009941:vanadium:' +
- namespaceId + ':' + selectedMetric.metricData.PodUID,
+ namespaceId + ':' + metricData.PodUID,
target: '_blank'
- }, selectedMetric.metricData.Instance)),
+ }, metricData.Instance)),
]),
h('div.row', [
h('div.item-label', 'Pod Node'),
h('div.item-value', h('a', {
href: 'https://app.google.stackdriver.com/instances/' +
- data.Instances[selectedMetric.metricData.PodNode],
+ curData.Instances[metricData.PodNode],
target: '_blank'
- }, selectedMetric.metricData.PodNode)),
- ]),
- h('div.row', [
- h('div.item-label', 'Pod Config'),
- h('div.item-value', h('a', {
- href: 'cfg?p=' + selectedMetric.metricData.Project +
- '&z=' + selectedMetric.metricData.Zone +
- '&d=' + selectedMetric.metricData.PodName,
- target: '_blank'
- }, 'cfg')),
+ }, metricData.PodNode)),
]),
h('div.row', [
h('div.item-label', 'Pod Status'),
- h('div.item-value', selectedMetric.metricData.PodStatus)
+ h('div.item-value', h('a', {
+ href: 'cfg?p=' + metricData.Project +
+ '&z=' + metricData.Zone +
+ '&d=' + metricData.PodName,
+ target: '_blank'
+ }, 'status')),
]),
h('div.row', [
h('div.item-label', 'Zone'),
- h('div.item-value', selectedMetric.metricData.Zone)
+ h('div.item-value', metricData.Zone)
]),
h('div.btn-close', {
'ev-click': hg.send(state.channels.closeMetricActionsPanel)
}, 'Close')
]);
- return h('div.metric-actions-container', panel);
}
diff --git a/oncall/client/browser/components/metric-item/index.js b/oncall/client/browser/components/metric-item/index.js
deleted file mode 100644
index 2bfb29f..0000000
--- a/oncall/client/browser/components/metric-item/index.js
+++ /dev/null
@@ -1,192 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-/**
- * A metric item is a compact graph in the instance view. It only shows the
- * title, current/pass value, and a sparkline for the metric's data points.
- */
-
-var hg = require('mercury');
-var h = require('mercury').h;
-var svg = require('virtual-dom/virtual-hyperscript/svg');
-var uuid = require('uuid');
-
-var Consts = require('../../constants');
-var MouseMoveEvent = require('../../mousemove-handler');
-var Util = require('../../util');
-
-module.exports = create;
-module.exports.render = render;
-
-function create(data) {
- var state = hg.state({
- // Graph title.
- label: data.metric.Name,
-
- // Current value.
- value: data.metric.CurrentValue,
-
- // Value range.
- minValue: data.metric.MinValue,
- maxValue: data.metric.MaxValue,
-
- // Data points
- historyTimestamps: data.metric.HistoryTimestamps,
- historyValues: data.metric.HistoryValues,
-
- // Threshold value.
- threshold: data.metric.Threshold,
-
- // Current health.
- healthy: data.metric.Healthy,
-
- // The id of a mask for masking the mouse line within the graph area.
- svgMaskId: uuid.v1(),
-
- // See comments in instance-view/index.js.
- mouseOffsetFactor: data.mouseOffsetFactor,
- hoveredMetric: data.hoveredMetric,
-
- channels: {
- mouseMove: mouseMove,
- mouseOut: mouseOut,
- mouseOver: mouseOver
- }
- });
-
- return state;
-}
-
-/** Callback when mouse is moving on the graph. */
-function mouseMove(state, data) {
- if (state.mouseOffsetFactor() !== data.f) {
- state.mouseOffsetFactor.set(data.f);
- }
-}
-
-/** Callback when mouse is out of the graph. */
-function mouseOut(state) {
- state.mouseOffsetFactor.set(-1);
- state.hoveredMetric.set({});
-}
-
-/** Callback when mouse is over the graph. */
-function mouseOver(state) {
- state.hoveredMetric.set({
- label: state.label,
- value: state.value,
- minValue: state.minValue,
- maxValue: state.maxValue,
- historyTimestamps: state.historyTimestamps,
- historyValues: state.historyValues
- });
-}
-
-/** The main render function. */
-function render(state) {
- var curValue = Util.formatValue(Util.interpolateValue(
- state.value, state.mouseOffsetFactor,
- state.historyTimestamps, state.historyValues));
- var valueClassNames = [];
- if (state.mouseOffsetFactor >= 0) {
- valueClassNames.push('historyValue');
- }
- if (!state.healthy) {
- valueClassNames.push('unhealthy');
- }
- return h('div.metric-item', {
- className: state.healthy ? '' : 'unhealthy',
- 'ev-mouseout': hg.send(state.channels.mouseOut),
- 'ev-mouseover': hg.send(state.channels.mouseOver)
- }, [
- h('div.metric-item-title', Consts.getDisplayName(state.label)),
- h('div.metric-item-value', {
- className: valueClassNames.join(' ')
- }, curValue),
- renderGraph(state)
- ]);
-}
-
-/** Renders the main graph. */
-function renderGraph(state) {
- var mouseOffset = 100 * state.mouseOffsetFactor;
- var minTime = state.historyTimestamps[0];
- var maxTime = state.historyTimestamps[state.historyTimestamps.length - 1];
- var points = Util.genPolylinePoints(
- state.historyTimestamps, state.historyValues,
- minTime, maxTime, state.minValue, state.maxValue);
-
- var items = [
- renderSparkline(points),
- renderMouseLine(points, mouseOffset, state)
- ];
- if (state.threshold >= 0) {
- items.push(renderThresholdLine(state));
- }
-
- return h('div.sparkline', {
- 'ev-mousemove': new MouseMoveEvent(state.channels.mouseMove),
- }, items);
-}
-
-/** Renders the sparkline for the metric's data points. */
-function renderSparkline(points) {
- return svg('svg', {
- 'class': 'content',
- 'viewBox': '0 0 100 100',
- 'preserveAspectRatio': 'none'
- }, [
- svg('polyline', {'points': points}),
- svg('polygon', {'points': '0,100 ' + points + ' 100,100 0,100'})
- ]);
-}
-
-/**
- * Renders the mouse line at the given offset.
- *
- * For better appearance, we mask the mouse line within the graph area.
- */
-function renderMouseLine(points, mouseOffset, state) {
- var maskId = 'mask-' + state.svgMaskId;
- return svg('svg', {
- 'class': 'mouse-line',
- 'viewBox': '0 0 100 100',
- 'preserveAspectRatio': 'none'
- }, [
- svg('defs', [
- svg('mask', {
- 'id': maskId,
- 'x': 0,
- 'y': 0,
- 'width': 100,
- 'height': 100
- }, [
- svg('polygon', {
- 'points': '0,100 ' + points + ' 100,100 0,100',
- 'style': {'fill': '#ffffff'}
- })
- ])
- ]),
- svg('polyline', {
- 'points': mouseOffset + ',0 ' + mouseOffset + ',100',
- 'mask': 'url(#' + maskId + ')'
- })
- ]);
-}
-
-/** Renders the threshold line. */
-function renderThresholdLine(state) {
- var thresholdOffset = Util.getOffsetForValue(
- state.threshold, state.minValue, state.maxValue);
- return svg('svg', {
- 'class': 'threshold-line',
- 'viewBox': '0 0 100 100',
- 'preserveAspectRatio': 'none',
- }, [
- svg('path', {
- 'd': 'M0 ' + thresholdOffset + ' L 100 ' + thresholdOffset,
- 'stroke-dasharray': '2,2'
- })
- ]);
-}
diff --git a/oncall/client/browser/components/metrics-group/index.js b/oncall/client/browser/components/metrics-group/index.js
deleted file mode 100644
index 09a0ad4..0000000
--- a/oncall/client/browser/components/metrics-group/index.js
+++ /dev/null
@@ -1,90 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-/**
- * A metric group shows a set of metric items in multiple rows where each row
- * has "numColumns" items. It can also show a set of links (optionally) after
- * the group title.
- */
-
-var hg = require('mercury');
-var h = require('mercury').h;
-
-var metricItemComponent = require('../metric-item');
-
-module.exports = create;
-module.exports.render = render;
-
-var numColumns = 5;
-
-/** Constructor. */
-function create(data) {
- // Transform data.metrics to an array of metricItemComponents.
- var metrics = data.metrics.map(function(metric) {
- return metricItemComponent({
- metric: metric,
- mouseOffsetFactor: data.mouseOffsetFactor,
- hoveredMetric: data.hoveredMetric
- });
- });
-
- // Calculate overall health.
- var healthy = true;
- metrics.forEach(function(metric) {
- healthy &= metric.healthy;
- });
-
- var state = hg.state({
- title: data.title,
- links: data.links,
- healthy: healthy,
- metricItems: hg.array(metrics)
- });
-
- return state;
-}
-
-/** The main render function. */
-function render(state) {
- // Organize metric items in rows and fill the empty space with fillers.
- var itemSize = state.metricItems.length;
- var paddedItemSize = (Math.ceil(itemSize / numColumns)) * numColumns;
- var rows = [];
- var curRow = null;
- for (var i = 0; i < paddedItemSize; i++) {
- if (i % numColumns === 0) {
- if (curRow !== null) {
- rows.push(h('div.metrics-group-row', curRow));
- }
- curRow = [];
- }
- if (i > itemSize - 1) {
- curRow.push(h('div.metric-item-filler'));
- } else {
- curRow.push(metricItemComponent.render(state.metricItems[i]));
- }
- }
- rows.push(h('div.metrics-group-row', curRow));
-
- // Group title and links.
- var titleItems = [
- h('div.metrics-group-title', h('span', state.title))
- ];
- if (state.links) {
- var links = state.links.map(function(link) {
- return h('a', {
- href: link.link,
- target: '_blank'
- }, link.name);
- });
- titleItems.push(h('div.link-container', links));
- }
-
- return h('div.metrics-group', [
- h('div.metrics-group-title-container', titleItems),
- h('div.metrics-group-items-container', [
- h('div.metrics-group-items', rows)
- ])
- ]);
-}
diff --git a/oncall/client/browser/components/status-table/index.js b/oncall/client/browser/components/status-table/index.js
index be3e7d6..b3640d0 100644
--- a/oncall/client/browser/components/status-table/index.js
+++ b/oncall/client/browser/components/status-table/index.js
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+var dateformat = require('dateformat');
var hg = require('mercury');
var h = require('mercury').h;
var svg = require('virtual-dom/virtual-hyperscript/svg');
@@ -46,28 +47,6 @@
},
]
},
- // Roled.
- {
- rowHeader: Consts.metricNames.MN_ROLE,
- columns: [
- {
- dataKey: Consts.dataKeys.DK_SERVICE_LATENCY,
- label: 'LATENCY',
- metricName: Consts.metricNames.MN_ROLE,
- threshold: 2000
- },
- {
- dataKey: Consts.dataKeys.DK_SERVICE_QPS,
- label: 'QPS',
- metricName: Consts.metricNames.MN_ROLE
- },
- {
- dataKey: Consts.dataKeys.DK_SERVICE_METADATA,
- label: 'BUILD AGE (h)',
- metricName: Consts.metricNames.MN_ROLE
- }
- ]
- },
// Proxy.
{
rowHeader: Consts.metricNames.MN_PROXY,
@@ -90,6 +69,28 @@
}
]
},
+ // Roled.
+ {
+ rowHeader: Consts.metricNames.MN_ROLE,
+ columns: [
+ {
+ dataKey: Consts.dataKeys.DK_SERVICE_LATENCY,
+ label: 'LATENCY',
+ metricName: Consts.metricNames.MN_ROLE,
+ threshold: 2000
+ },
+ {
+ dataKey: Consts.dataKeys.DK_SERVICE_QPS,
+ label: 'QPS',
+ metricName: Consts.metricNames.MN_ROLE
+ },
+ {
+ dataKey: Consts.dataKeys.DK_SERVICE_METADATA,
+ label: 'BUILD AGE (h)',
+ metricName: Consts.metricNames.MN_ROLE
+ }
+ ]
+ },
// Identityd.
{
rowHeader: Consts.metricNames.MN_IDENTITY,
@@ -139,6 +140,28 @@
metricName: Consts.metricNames.MN_BENCHMARKS
}
]
+ },
+ // Syncbase allocator.
+ {
+ rowHeader: Consts.metricNames.MN_SB_ALLOCATOR,
+ columns: [
+ {
+ dataKey: Consts.dataKeys.DK_SERVICE_LATENCY,
+ label: 'LATENCY',
+ metricName: Consts.metricNames.MN_SB_ALLOCATOR,
+ threshold: 2000
+ },
+ {
+ dataKey: Consts.dataKeys.DK_SERVICE_QPS,
+ label: 'QPS',
+ metricName: Consts.metricNames.MN_SB_ALLOCATOR
+ },
+ {
+ dataKey: Consts.dataKeys.DK_SERVICE_METADATA,
+ label: 'BUILD AGE (h)',
+ metricName: Consts.metricNames.MN_SB_ALLOCATOR
+ }
+ ]
}
];
@@ -153,10 +176,13 @@
data: hg.struct(data.data),
mouseOffsetFactor: hg.value(-1),
- showMetricActionsPanel: hg.value(false),
+
+ mouseOverDataKey: hg.value(''),
+ mouseOverMetricName: hg.value(''),
channels: {
mouseMoveOnSparkline: mouseMoveOnSparkline,
+ mouseOverSparkline: mouseOverSparkline,
mouseOutOfSparkline: mouseOutOfSparkline,
}
});
@@ -167,93 +193,186 @@
state.mouseOffsetFactor.set(data.f);
}
+/** Callback for moving mouse over a sparkline. */
+function mouseOverSparkline(state, colData) {
+ state.mouseOverDataKey.set(colData.dataKey);
+ state.mouseOverMetricName.set(colData.metricName);
+}
+
/** Callback for moving mouse out of sparkline. */
function mouseOutOfSparkline(state) {
state.mouseOffsetFactor.set(-1);
+ state.mouseOverDataKey.set('');
+ state.mouseOverMetricName.set('');
}
/** The main render function. */
function render(globalState, state) {
var data = state.data;
- var rows = tableRows.map(function(rowData) {
- var cols = rowData.columns.map(function(colData) {
- // Create a column for a metric.
- var colHeader = h('div.col-header', colData.label);
+ var rows = tableRows.map(function(rowData, rowIndex) {
+ var numWarnings = 0;
+ var numReplicas = 0;
+ var hasFatalErrors = false;
+ var cols = rowData.columns.map(function(colData, colIndex) {
+ // Create a column for each metric.
+ //
+ // Use the first column (usually latency) to determine number of replicas.
var metricsData = data[colData.dataKey][colData.metricName];
- var sparkLines = metricsData.map(function(metricData) {
- // 100 is the default logical width of any svg graphs.
- var points = '0,100 100,100';
+ if (numReplicas === 0) {
+ numReplicas = metricsData.length;
+ }
+
+ // Calculate average from all replicas.
+ var avg = {}; // timestamps -> [values from replicas]
+ var numErrors = 0;
+ metricsData.forEach(function(metricData) {
if (metricData && metricData.HistoryTimestamps) {
- points = Util.genPolylinePoints(
- metricData.HistoryTimestamps, metricData.HistoryValues,
- data.MinTime, data.MaxTime,
- metricData.MinValue, metricData.MaxValue);
+ for (var i = 0; i < metricData.HistoryTimestamps.length; i++) {
+ var t = metricData.HistoryTimestamps[i];
+ var v = metricData.HistoryValues[i];
+ if (!avg[t]) {
+ avg[t] = [];
+ }
+ avg[t].push(v);
+ }
}
- var curValue = Util.formatValue(metricData.CurrentValue);
+
+ var hasError = false;
+
// Handle error when getting time series.
- var hasErrors = metricData.ErrMsg !== '';
- var extraColMetricClass = '';
- if (hasErrors) {
- curValue = '?';
- extraColMetricClass = '.err';
+ if (metricData.ErrMsg !== '') {
+ hasError = true;
}
- // Handle current value over threshold.
- var overThreshold = (
- colData.threshold && metricData.CurrentValue >= colData.threshold);
- var thresholdValue = -100;
- if (overThreshold) {
- extraColMetricClass = '.unhealthy';
- thresholdValue = (colData.threshold-metricData.MinValue)/
- (metricData.MaxValue-metricData.MinValue)*100.0;
- }
+
// Handle stale data.
var tsLen = metricData.HistoryTimestamps.length;
if (tsLen > 0) {
var lastTimestamp = metricData.HistoryTimestamps[tsLen - 1];
- if (data.MaxTime - lastTimestamp > 600) {
- extraColMetricClass = '.stale';
+ if (data.MaxTime - lastTimestamp >
+ Consts.stableDataThresholdInSeconds) {
+ hasError = true;
}
} else {
- extraColMetricClass = '.unhealthy';
+ hasError = true;
}
- // Mouse line.
- var extraCurValueClass = '';
- var mouseOffset = 100 * state.mouseOffsetFactor;
- if (!hasErrors && mouseOffset >= 0) {
- curValue = Util.formatValue(Util.interpolateValue(
- metricData.CurrentValue, state.mouseOffsetFactor,
- metricData.HistoryTimestamps, metricData.HistoryValues));
- extraCurValueClass = '.history';
+ // Value over threshold.
+ var overThreshold = (
+ colData.threshold && metricData.CurrentValue >= colData.threshold);
+ if (overThreshold) {
+ hasError = true;
}
- return h('div.col-metric' + extraColMetricClass, {
- 'title': hasErrors ?
- metricData.ErrMsg : metricData.Instance + ', ' + metricData.Zone,
- 'ev-click': hg.send(globalState.channels.mouseClickOnMetric, {
- metricData: metricData,
- serviceName: rowData.rowHeader
- })
- }, [
- h('div.highlight-overlay'),
- renderMouseLine(mouseOffset),
- h('div.sparkline', {
- 'ev-mousemove': new MouseMoveHandler(
- state.channels.mouseMoveOnSparkline),
- 'ev-mouseout': hg.send(state.channels.mouseOutOfSparkline)
- }, [
- renderThreshold(thresholdValue),
- renderSparkline(points)
- ]),
- h('div.cur-value' + extraCurValueClass, [curValue])
- ]);
+ if (hasError) {
+ numErrors++;
+ }
});
- var items = [colHeader];
- items = items.concat(sparkLines);
+ var extraColMetricClass = '';
+ if (numErrors > 0) {
+ extraColMetricClass = '.warning';
+ // Use the latency column to estimate how many replicas are in warning
+ // state.
+ if (colData.label === 'LATENCY') {
+ numWarnings = numErrors;
+ }
+ }
+
+ // Prepare data for average timeseries.
+ var avgTimestamps = [];
+ var avgValues = [];
+ var avgMinValue = Number.MAX_VALUE;
+ var avgMaxValue = 0;
+ Object.keys(avg).sort().forEach(function(t) {
+ avgTimestamps.push(parseInt(t));
+ var values = avg[t];
+ var sum = 0;
+ values.forEach(function(v) {
+ sum += v;
+ });
+ var avgValue = sum / values.length;
+ avgValues.push(avgValue);
+ avgMinValue = Math.min(avgMinValue, avgValue);
+ avgMaxValue = Math.max(avgMaxValue, avgValue);
+ });
+
+ // Render sparkline for average values.
+ //
+ // 100 is the default logical width of any svg graphs.
+ var points = '0,100 100,100';
+ if (avgTimestamps.length > 0 && avgValues.length > 0) {
+ points = Util.genPolylinePoints(
+ avgTimestamps, avgValues,
+ data.MinTime, data.MaxTime,
+ avgMinValue, avgMaxValue);
+ }
+ var avgCurValue = 0;
+ if (avgValues.length > 0) {
+ avgCurValue = avgValues[avgValues.length - 1];
+ }
+ var curValue = Util.formatValue(avgCurValue);
+ var extraCurValueClass = '';
+ var mouseOffset = 100 * state.mouseOffsetFactor;
+ if (mouseOffset >= 0) {
+ curValue = Util.formatValue(Util.interpolateValue(
+ avgCurValue, state.mouseOffsetFactor,
+ avgTimestamps, avgValues));
+ extraCurValueClass = '.history';
+ }
+
+ // Handle avg value over threshold.
+ var overThreshold = (
+ colData.threshold && avgCurValue >= colData.threshold);
+ var thresholdValue = -100;
+ if (overThreshold) {
+ hasFatalErrors = true;
+ extraColMetricClass = '.fatal';
+ thresholdValue = (colData.threshold-avgMinValue)/
+ (avgMaxValue - avgMinValue)*100.0;
+ }
+
+ var sparklineItems = [
+ h('div.highlight-overlay'),
+ Util.renderMouseLine(mouseOffset),
+ h('div.sparkline', {
+ 'ev-mousemove': new MouseMoveHandler(
+ state.channels.mouseMoveOnSparkline),
+ 'ev-mouseover': hg.send(state.channels.mouseOverSparkline, colData),
+ 'ev-mouseout': hg.send(state.channels.mouseOutOfSparkline),
+ }, [
+ renderThreshold(thresholdValue),
+ Util.renderSparkline(points)
+ ]),
+ h('div.cur-value' + extraCurValueClass, [curValue])
+ ];
+ if (state.mouseOffsetFactor >= 0 &&
+ state.mouseOverDataKey === colData.dataKey &&
+ state.mouseOverMetricName === colData.metricName) {
+ var curTimestamp =
+ (data.MaxTime - data.MinTime) * state.mouseOffsetFactor +
+ data.MinTime;
+ sparklineItems.push(
+ h('div.mouseover-time', dateformat(new Date(curTimestamp * 1000))));
+ }
+ var sparkline = h('div.col-metric' + extraColMetricClass, {
+ 'ev-click': hg.send(globalState.channels.mouseClickOnMetric, {
+ serviceName: rowData.rowHeader,
+ colData: colData
+ })
+ }, sparklineItems);
+
+ var items = [h('div.col-header', colData.label), sparkline];
return h('div.col', items);
});
- cols.unshift(h('div.row-header', Consts.getDisplayName(rowData.rowHeader)));
+ var headerExtraClass = (numWarnings !== 0) ? '.warning' : '';
+ if (hasFatalErrors) {
+ headerExtraClass = '.fatal';
+ }
+ cols.unshift(h('div.row-header' + headerExtraClass, [
+ h('div.header-label', Consts.getDisplayName(rowData.rowHeader)),
+ h('div.header-numhealthy',
+ ((numReplicas - numWarnings) + '/' + numReplicas))
+ ]));
return h('div.row', cols);
});
@@ -261,20 +380,6 @@
}
/**
- * Renders sparkline for the given points.
- * @param {string} points - A string in the form of "x1,y1 x2,y2 ...".
- */
-function renderSparkline(points) {
- return svg('svg', {
- 'class': 'content',
- 'viewBox': '0 0 100 100',
- 'preserveAspectRatio': 'none'
- }, [
- svg('polyline', {'points': points}),
- ]);
-}
-
-/**
* Renders threshold line.
*/
function renderThreshold(value) {
@@ -289,19 +394,3 @@
}),
]);
}
-
-/**
- * Renders mouse line at the given offset.
- * @param {Number} mouseOffset - The logical offset for the mouse line.
- */
-function renderMouseLine(mouseOffset) {
- return svg('svg', {
- 'class': 'mouse-line',
- 'viewBox': '0 0 100 100',
- 'preserveAspectRatio': 'none'
- }, [
- svg('polyline', {
- 'points': mouseOffset + ',0 ' + mouseOffset + ',100'
- })
- ]);
-}
diff --git a/oncall/client/browser/constants.js b/oncall/client/browser/constants.js
index 1ea8d25..cef9e91 100644
--- a/oncall/client/browser/constants.js
+++ b/oncall/client/browser/constants.js
@@ -25,6 +25,7 @@
MN_MOUNTTABLE: 'mounttable',
MN_PROXY: 'proxy service',
MN_ROLE: 'role service',
+ MN_SB_ALLOCATOR: 'syncbase allocator',
MN_MT_MOUNTED_SERVERS: 'mounttable mounted servers',
MN_MT_NODES: 'mounttable nodes'
});
@@ -74,6 +75,7 @@
'role service': 'ROLES',
'role service latency': 'ROLES',
'roled': 'ROLED',
+ 'syncbase allocator': 'SB ALLOCATOR',
'tcpconn': 'TCP CONN',
'waiting-connections': 'WAITING CONN',
'writing-connections': 'WRITING CONN'
@@ -175,6 +177,8 @@
/** All available metric objects. */
var mainMetrics = cloudServiceMetrics.concat(nginxMetrics);
+var stableDataThresholdInSeconds = 600;
+
/**
* Creates a metric.
*
@@ -238,5 +242,6 @@
cloudServiceGCEMetrics: cloudServiceGCEMetrics,
nginxMetrics: nginxMetrics,
nginxGCEMetrics: nginxGCEMetrics,
- mainMetrics: mainMetrics
+ mainMetrics: mainMetrics,
+ stableDataThresholdInSeconds: stableDataThresholdInSeconds
};
diff --git a/oncall/client/browser/index.js b/oncall/client/browser/index.js
index 8151bc9..f07a6e4 100644
--- a/oncall/client/browser/index.js
+++ b/oncall/client/browser/index.js
@@ -19,12 +19,6 @@
*/
var curData;
-/**
- * A variable to keep track of current metric data shown in the
- * metric actions panel.
- */
-var selectedMetricData;
-
// Ask mercury to listen to mousemove/mouseout/mouseover events.
hg.Delegator().listenTo('mousemove');
hg.Delegator().listenTo('mouseout');
@@ -48,15 +42,13 @@
// The status table showing service status.
statusTable: statusTableComponent(null),
- // The view showing data on the "instance" level.
- // instanceView: instanceViewComponent(null),
+ // The metric actions panel.
+ metricActionsPanel: metricActionsPanelComponent(null)
}),
// Whether to show settings panel.
showSettingsPanel: hg.value(false),
- showMetricActionsPanel: hg.value(false),
-
// Settings stored in cookies.
settings: hg.varhash({
darkTheme: hg.value(cookies.darkTheme === 'true')
@@ -67,18 +59,17 @@
changeTheme: changeTheme,
clickOnSettingsGear: clickOnSettingsGear,
closeSettingsPanel: closeSettingsPanel,
- closeMetricActionsPanel: closeMetricActionsPanel
}
});
/** Callback for clicking on a metric. */
function mouseClickOnMetric(state, data) {
- selectedMetricData = data;
- state.showMetricActionsPanel.set(true);
-}
-
-function closeMetricActionsPanel(state) {
- state.showMetricActionsPanel.set(false);
+ var metricActionPanelData = metricActionsPanelComponent({
+ selectedMetric: data,
+ selectedMetricIndex: 0,
+ visible: true
+ });
+ state.components.put('metricActionsPanel', metricActionPanelData);
}
/** Callback when user clicks on the settings gear. */
@@ -119,9 +110,11 @@
mainContent.push(hg.partial(settingsPanelComponent.render, state));
}
- if (state.showMetricActionsPanel) {
- mainContent.push(hg.partial(metricActionsPanelComponent.render, state,
- selectedMetricData, curData));
+ if (state.components.metricActionsPanel &&
+ state.components.metricActionsPanel.visible) {
+ mainContent.push(
+ metricActionsPanelComponent.render(state.components.metricActionsPanel,
+ curData));
}
var className = state.settings.darkTheme ? 'main.darkTheme' : 'main';
@@ -183,8 +176,8 @@
if (state.showSettingsPanel()) {
state.showSettingsPanel.set(false);
}
- if (state.showMetricActionsPanel()) {
- state.showMetricActionsPanel.set(false);
+ if (state.components.metricActionsPanel) {
+ state.components.metricActionsPanel.visible.set(false);
}
}
};
diff --git a/oncall/client/browser/util.js b/oncall/client/browser/util.js
index e93d321..b7cc3be 100644
--- a/oncall/client/browser/util.js
+++ b/oncall/client/browser/util.js
@@ -14,7 +14,9 @@
genPolylinePoints: genPolylinePoints,
getOffsetForValue: getOffsetForValue,
formatValue: formatValue,
- isEmptyObj: isEmptyObj
+ isEmptyObj: isEmptyObj,
+ renderSparkline: renderSparkline,
+ renderMouseLine: renderMouseLine
};
/**
@@ -165,6 +167,9 @@
* @return {string}
*/
function formatValue(value) {
+ if (isNaN(value)) {
+ return '?';
+ }
if (value < 1) {
value = value.toFixed(2);
} else if (value < 10) {
@@ -186,3 +191,33 @@
function isEmptyObj(obj) {
return Object.keys(obj).length === 0;
}
+
+/**
+ * Renders sparkline for the given points.
+ * @param {string} points - A string in the form of "x1,y1 x2,y2 ...".
+ */
+function renderSparkline(points) {
+ return svg('svg', {
+ 'class': 'content',
+ 'viewBox': '0 0 100 100',
+ 'preserveAspectRatio': 'none'
+ }, [
+ svg('polyline', {'points': points}),
+ ]);
+}
+
+/**
+ * Renders mouse line at the given offset.
+ * @param {Number} mouseOffset - The logical offset for the mouse line.
+ */
+function renderMouseLine(mouseOffset) {
+ return svg('svg', {
+ 'class': 'mouse-line',
+ 'viewBox': '0 0 100 100',
+ 'preserveAspectRatio': 'none'
+ }, [
+ svg('polyline', {
+ 'points': mouseOffset + ',0 ' + mouseOffset + ',100'
+ })
+ ]);
+}
diff --git a/oncall/client/stylesheets/components/metric-actions-panel.css b/oncall/client/stylesheets/components/metric-actions-panel.css
index 5507a61..e284207 100644
--- a/oncall/client/stylesheets/components/metric-actions-panel.css
+++ b/oncall/client/stylesheets/components/metric-actions-panel.css
@@ -7,53 +7,79 @@
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.2);
+ position: relative;
+}
+
+div.metric-actions-container div.inner-container {
+ position: absolute;
display: flex;
- justify-content: center;
- align-items: center;
+ top: 20%;
+ left: 50%;
+ width: 700px;
+ height: 440px;
+ margin-left: -350px;
+ background-color: white;
+ padding: 10px;
+}
+
+div.metric-actions-container div.metric-actions-list {
+ overflow-y: auto;
+ overflow-x: hidden;
+ padding-right: 12px;
}
div.metric-actions-container div.metric-actions-content {
- background-color: white;
- border: 1px black solid;
width: 440px;
- padding: 12px;
- padding-top: 16px;
+ padding-left: 12px;
+ border-left: 1px #999 dashed;
+ display: flex;
+ flex-direction: column;
}
-div.metric-actions-container div.title {
+div.metric-actions-container div.metric-actions-content div.title {
font-weight: var(--font-weight-medium);
padding: 4px 0px 16px 0px;
}
div.metric-actions-container div.btn-close {
+ position: absolute;
padding-top: 10px;
display: flex;
align-items: center;
cursor: pointer;
color: #999;
+ bottom: 10px;
+ right: 10px;
}
div.metric-actions-container div.btn-close:hover {
color: #444;
}
-div.metric-actions-container div.row {
+div.metric-actions-container div.metric-actions-content div.row {
display: flex;
flex-direction: row;
}
-div.metric-actions-container div.space {
+div.metric-actions-container div.metric-actions-content div.space {
height: 14px;
width: 100%;
}
-div.metric-actions-container div.item-label {
+div.metric-actions-container div.metric-actions-content div.item-label {
font-weight: var(--font-weight-medium);
width: 140px;
padding-right: 10px;
}
-div.metric-actions-container div.item-link {
+div.metric-actions-container div.metric-actions-content div.item-value.history {
+ opacity: 0.5;
+}
+
+div.metric-actions-container div.metric-actions-content div.item-link {
margin-left: 10px;
}
+div.metric-actions-list div.col-metric {
+ border-bottom: 1px dotted #888;
+}
diff --git a/oncall/client/stylesheets/components/metric-item.css b/oncall/client/stylesheets/components/metric-item.css
index 054f980..034efcc 100644
--- a/oncall/client/stylesheets/components/metric-item.css
+++ b/oncall/client/stylesheets/components/metric-item.css
@@ -1,139 +1,117 @@
/* Copyright 2015 The Vanadium Authors. All rights reserved. */
/* Use of this source code is governed by a BSD-style */
/* license that can be found in the LICENSE file. */
-
-div.metric-item {
- width: var(--metric-item-width);
- color: var(--dark-green-grey);
- cursor: pointer;
- margin-right: 4px;
+.col-metric {
position: relative;
-}
-
-.darkTheme div.metric-item {
- color: var(--light-green-grey);
-}
-
-div.metric-item.unhealthy {
- background: var(--red-stripes-background-dimmed);
-}
-
-div.metric-item:nth-child(1):before {
- width: 0px !important;
-}
-
-div.metric-item:hover .metric-item-title {
- background-color: var(--light-grey);
-}
-
-.darkTheme div.metric-item:hover .metric-item-title {
- background-color: var(--dark-highlight);
-}
-
-div.metric-item div.metric-item-title {
- padding-top: 4px;
- padding-bottom: 2px;
- font-size: 12px;
- line-height: var(--metric-item-title-height);
- white-space: nowrap;
- overflow: hidden;
-}
-
-div.metric-item div.metric-item-graph-container {
+ padding: 6px 4px 6px 4px;
display: flex;
- justify-content: space-between;
- padding-left: 4px;
- padding-right: 2px;
-}
-
-div.metric-item div.metric-item-value {
- font-size: 16px;
- font-weight: var(--font-weight-medium);
- color: var(--cyan-800);
- height: var(--metric-item-value-height);
- line-height: var(--metric-item-value-height);
- margin-bottom: 4px;
-}
-
-.darkTheme div.metric-item div.metric-item-value {
- color: var(--light-green);
-}
-
-div.metric-item div.metric-item-value.unhealthy {
- color: var(--dark-red);
-}
-
-.darkTheme div.metric-item div.metric-item-value.unhealthy {
- color: var(--light-red);
-}
-
-div.metric-item div.metric-item-graph {
-}
-
-div.metric-item-filler {
- flex-grow: 1;
- height: var(--metric-item-height);
- width: var(--metric-item-width);
-}
-
-div.metric-item div.sparkline {
- position: relative;
+ width: var(--col-metric-width);
+ height: var(--col-metric-height);
+ background: var(--cyan-800);
+ flex-direction: row;
cursor: pointer;
- height: var(--sparkline-height);
}
-div.metric-item div.sparkline svg {
- width: 100%;
- height: 100%;
+.col-metric.warning {
+ background-color: var(--warning) !important;
+}
+
+.col-metric.fatal {
+ background-color: var(--fatal) !important;
+}
+
+.darkTheme .col-metric {
+ background: DarkGreen;
+}
+
+.col-metric .cur-value {
+ position: absolute;
+ left: 166px;
+ top: 6px;
+ color: white;
+ font-size: 14px;
+ height: var(--col-metric-content-height);
+ line-height: var(--col-metric-content-height);
+}
+
+.col-metric div.cur-value.history {
+ color: #B2DADD !important;
+}
+
+.darkTheme .col-metric div.cur-value.history {
+ color: #AAA !important;
+}
+
+.col-metric.err .cur-value {
+ color: red !important;
+}
+
+.col-metric div.sparkline {
+ position: absolute;
+ top: 4px;
+ left: 6px;
+ width: 156px;
+ height: var(--col-metric-content-height);
+}
+
+.col-metric svg {
position: absolute;
top: 0px;
left: 0px;
+ width: 156px;
+ height: var(--col-metric-content-height);
}
-div.metric-item div.sparkline svg.content polyline {
+.col-metric svg.mouse-line {
+ position: absolute;
+ top: 0px;
+ left: 6px;
+ width: 156px;
+ height: var(--col-metric-height);
+}
+
+.col-metric svg.content {
+}
+
+.col-metric svg.content polyline {
vector-effect: non-scaling-stroke;
fill: none;
- stroke: var(--dark-green-grey);
- stroke-width: 2;
+ stroke: white;
+ stroke-width: 0.8;
}
-.darkTheme div.metric-item div.sparkline svg.content polyline {
- stroke: var(--light-green-grey);
+.col-metric svg.threshold path {
+ vector-effect: non-scaling-stroke;
+ fill: none;
+ stroke: white;
+ stroke-width: 0.8;
}
-div.metric-item div.sparkline svg.content polygon {
- fill: WhiteSmoke;
- stroke: none;
-}
-
-.darkTheme div.metric-item div.sparkline svg.content polygon {
- fill: #333;
-}
-
-div.metric-item svg.mouse-line {
+.col-metric svg.mouse-line {
shape-rendering: crispedges;
}
-div.metric-item div.sparkline svg.mouse-line polyline {
+.col-metric svg.mouse-line polyline {
vector-effect: non-scaling-stroke;
fill: none;
- stroke: #BBB;
+ stroke: rgba(255, 255, 255, 0.2);
stroke-width: 1;
}
-div.metric-item div.sparkline svg.mouse-line polygon {
- vector-effect: non-scaling-stroke;
- fill: var(--dark-red);
- stroke: none;
+.col-metric div.highlight-overlay {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ top: 0px;
+ left: 0px;
+ background-color: white;
+ opacity: 0;
}
-div.metric-item svg.threshold-line {
- shape-rendering: crispedges;
+.col-metric:hover div.highlight-overlay {
+ opacity: 0.1;
}
-div.metric-item div.sparkline svg.threshold-line path {
- vector-effect: non-scaling-stroke;
- fill: none;
- stroke: var(--dark-red);
- stroke-width: 1;
+.col-metric.selected div.highlight-overlay {
+ opacity: 0.2;
}
diff --git a/oncall/client/stylesheets/components/status-table.css b/oncall/client/stylesheets/components/status-table.css
index e4ae455..7109308 100644
--- a/oncall/client/stylesheets/components/status-table.css
+++ b/oncall/client/stylesheets/components/status-table.css
@@ -21,8 +21,31 @@
div.status-table .row-header {
width: 120px;
text-align: right;
- padding-top: 15px;
+ padding-top: 19px;
padding-right: 10px;
+ line-height: 18px;
+}
+
+div.status-table .row-header.warning div {
+ color: var(--warning) !important;
+}
+
+div.status-table .row-header.fatal div {
+ color: var(--fatal) !important;
+}
+
+div.status-table .header-label {
+ font-weight: 500;
+}
+
+div.status-table .header-numhealthy {
+ margin-top: 2px;
+ font-size: 18px;
+ color: var(--cyan-800);
+}
+
+.darkTheme div.status-table .header-numhealthy {
+ color: LightGreen;
}
div.status-table .col {
@@ -34,121 +57,10 @@
height: 21px;
}
-div.status-table .col-metric {
- position: relative;
- padding: 6px 4px 6px 4px;
- display: flex;
- width: var(--col-metric-width);
- height: var(--col-metric-height);
- background: var(--cyan-800);
- flex-direction: row;
- cursor: pointer;
-}
-
-div.status-table .col-metric.err {
- background-color: #ffbb55 !important;
-}
-
-div.status-table .col-metric.unhealthy {
- background-color: #AA0000 !important;
-}
-
-div.status-table .col-metric.stale {
- background-color: #ffbb55 !important;
-}
-
-.darkTheme div.status-table .col-metric {
- background: DarkGreen;
-}
-
-div.status-table .col-metric .cur-value {
+div.status-table div.mouseover-time {
position: absolute;
- left: 166px;
- top: 6px;
- color: white;
- font-size: 14px;
- height: var(--col-metric-content-height);
- line-height: var(--col-metric-content-height);
-}
-
-div.status-table .col-metric div.cur-value.history {
- color: #B2DADD !important;
-}
-
-.darkTheme div.status-table .col-metric div.cur-value.history {
- color: #AAA !important;
-}
-
-div.status-table .col-metric.err .cur-value {
- color: red !important;
-}
-
-div.status-table .col-metric div.sparkline {
- position: absolute;
- top: 4px;
- left: 6px;
- width: 156px;
- height: var(--col-metric-content-height);
-}
-
-div.status-table .col-metric svg {
- position: absolute;
- top: 0px;
+ top: var(--col-metric-height);
left: 0px;
- width: 156px;
- height: var(--col-metric-content-height);
-}
-
-div.status-table .col-metric svg.mouse-line {
- position: absolute;
- top: 0px;
- left: 6px;
- width: 156px;
- height: var(--col-metric-height);
-}
-
-div.status-table .col-metric svg.content {
-}
-
-div.status-table .col-metric svg.content polyline {
- vector-effect: non-scaling-stroke;
- fill: none;
- stroke: white;
- stroke-width: 0.8;
-}
-
-div.status-table .col-metric svg.threshold path {
- vector-effect: non-scaling-stroke;
- fill: none;
- stroke: white;
- stroke-width: 0.8;
-}
-
-div.status-table .col-metric svg.mouse-line {
- shape-rendering: crispedges;
-}
-
-div.status-table .col-metric svg.mouse-line polyline {
- vector-effect: non-scaling-stroke;
- fill: none;
- stroke: #339CA5;
- stroke-width: 1;
-}
-
-.darkTheme div.status-table .col-metric svg.mouse-line polyline {
- stroke: #338333;
-}
-
-div.status-table .col-metric div.highlight-overlay {
- position: absolute;
- width: 100%;
- height: 100%;
- top: 0px;
- left: 0px;
- background-color: white;
- opacity: 0;
-}
-
-div.status-table .col-metric:hover div.highlight-overlay {
- opacity: 0.1;
+ color: #999;
+ font-size: 11px;
}
diff --git a/oncall/client/stylesheets/variables.css b/oncall/client/stylesheets/variables.css
index c44e5cc..3a06e16 100644
--- a/oncall/client/stylesheets/variables.css
+++ b/oncall/client/stylesheets/variables.css
@@ -51,6 +51,8 @@
--white-transparent: rgba(255, 255, 255, 0.4);
--dark-background: #222;
--dark-highlight: #333;
+ --warning: #C36900;
+ --fatal: #AA0000;
/* Fonts */
--primary-font: Roboto;
@@ -63,8 +65,8 @@
--section-shadow: rgba(0, 0, 0, 0.30) 0px 0px 4px 0px;
/* Heights */
- --col-metric-height: 36px;
- --col-metric-content-height: 28px;
+ --col-metric-height: 48px;
+ --col-metric-content-height: 40px;
--header-height: 60px;
--zone-title-height: 40px;
--full-graph-height: 240px;
diff --git a/oncall/serve.go b/oncall/serve.go
index 553b43c..6ee3ad2 100644
--- a/oncall/serve.go
+++ b/oncall/serve.go
@@ -541,6 +541,8 @@
retPods[monitoring.SNProxy] = append(retPods[monitoring.SNProxy], pod)
case "role":
retPods[monitoring.SNRole] = append(retPods[monitoring.SNRole], pod)
+ case "sb-allocator":
+ retPods[monitoring.SNAllocator] = append(retPods[monitoring.SNAllocator], pod)
}
}
// Index nodes names by ids.