Add small success rate chart to table, misc web tweaks (#1628)

A bunch of web UI tweaks: 
- Add a small success rate chart to the metrics tables
- Improve latency formatting for seconds latencies
- Rename upstream/downstream to inbound/outbound
- Make Top table look consistent with rest of tables on page
- Fix widths of metrics column columns so that tables align
This commit is contained in:
Risha Mars 2018-09-12 13:47:46 -07:00 committed by GitHub
parent 01be78e455
commit b49ccce5f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 95 additions and 37 deletions

View File

@ -36,15 +36,6 @@
& .ant-progress-text {
color: black;
}
& .status-good .ant-progress-circle-path {
stroke: var(--green);
}
& .status-ok .ant-progress-circle-path {
stroke: var(--orange)
}
& .status-poor .ant-progress-circle-path {
stroke: var(--red);
}
}
& .octopus-metric {
@ -57,7 +48,7 @@
color: var(--red);
}
&.status-neutral {
color: #E0E0E0;
color: var(--graphgrey);
}
&.status-ok {
color: var(--orange);

View File

@ -11,6 +11,7 @@
--warmgrey: #f6f6f6;
--coldgrey: #c9c9c9;
--neutralgrey: #828282;
--graphgrey: #E0E0E0;
--silver: #BDBDBD;
--green: #26E99D;
--red: #FF4D2B;
@ -243,6 +244,30 @@ a.button.primary:active {
& tr .numeric {
text-align: right;
}
& .metric-table-sr {
margin-right: 6px;
}
& .metric-table-sr-chart.ant-progress-circle {
/* override ant progress circle height */
& div.ant-progress-inner {
max-height: 16px;
}
}
}
/* ant Progress bar color orverrides */
.success-rate-arc {
&.status-good .ant-progress-circle-path {
stroke: var(--green);
}
&.status-ok .ant-progress-circle-path {
stroke: var(--orange)
}
&.status-poor .ant-progress-circle-path {
stroke: var(--red);
}
}
/* Colored dot for indicating statuses */
@ -262,7 +287,7 @@ div.status-dot {
background-color: #E0E0E0;
}
&.status-dot-ok {
background-color: #ffd54f;
background-color: var(--orange);
}
}

View File

@ -2,7 +2,6 @@ import _ from 'lodash';
import BaseTable from './BaseTable.jsx';
import ErrorModal from './ErrorModal.jsx';
import GrafanaLink from './GrafanaLink.jsx';
import { processedMetricsPropType } from './util/MetricUtils.js';
import PropTypes from 'prop-types';
import React from 'react';
import { Tooltip } from 'antd';
@ -12,11 +11,13 @@ import {
metricToFormatter,
numericSort
} from './util/Utils.js';
import { processedMetricsPropType, successRateWithMiniChart } from './util/MetricUtils.jsx';
/*
Table to display Success Rate, Requests and Latency in tabs.
Expects rollup and timeseries data.
*/
const smMetricColWidth = "70px";
const withTooltip = (d, metricName) => {
return (
@ -75,6 +76,7 @@ const columnDefinitions = (resource, namespaces, onFilterClick, showNamespaceCol
title: formatTitle("Dash", "Grafana Dashboard"),
key: "grafanaDashboard",
className: "numeric",
width: smMetricColWidth,
render: row => !row.added || _.get(row, "pods.totalPods") === "0" ? null : (
<GrafanaLink
name={row.name}
@ -117,14 +119,16 @@ const columnDefinitions = (resource, namespaces, onFilterClick, showNamespaceCol
dataIndex: "successRate",
key: "successRateRollup",
className: "numeric",
width: "120px",
sorter: (a, b) => numericSort(a.successRate, b.successRate),
render: d => metricToFormatter["SUCCESS_RATE"](d)
render: successRateWithMiniChart
},
{
title: formatTitle("RPS", "Request Rate"),
dataIndex: "requestRate",
key: "requestRateRollup",
className: "numeric",
width: smMetricColWidth,
sorter: (a, b) => numericSort(a.requestRate, b.requestRate),
render: d => withTooltip(d, "REQUEST_RATE")
},
@ -133,6 +137,7 @@ const columnDefinitions = (resource, namespaces, onFilterClick, showNamespaceCol
dataIndex: "P50",
key: "p50LatencyRollup",
className: "numeric",
width: smMetricColWidth,
sorter: (a, b) => numericSort(a.P50, b.P50),
render: metricToFormatter["LATENCY"]
},
@ -141,6 +146,7 @@ const columnDefinitions = (resource, namespaces, onFilterClick, showNamespaceCol
dataIndex: "P95",
key: "p95LatencyRollup",
className: "numeric",
width: smMetricColWidth,
sorter: (a, b) => numericSort(a.P95, b.P95),
render: metricToFormatter["LATENCY"]
},
@ -149,6 +155,7 @@ const columnDefinitions = (resource, namespaces, onFilterClick, showNamespaceCol
dataIndex: "P99",
key: "p99LatencyRollup",
className: "numeric",
width: smMetricColWidth,
sorter: (a, b) => numericSort(a.P99, b.P99),
render: metricToFormatter["LATENCY"]
},
@ -157,6 +164,7 @@ const columnDefinitions = (resource, namespaces, onFilterClick, showNamespaceCol
key: "tlsTraffic",
dataIndex: "tlsRequestPercent",
className: "numeric",
width: smMetricColWidth,
sorter: (a, b) => numericSort(a.tlsRequestPercent.get(), b.tlsRequestPercent.get()),
render: d => _.isNil(d) || d.get() === -1 ? "---" : d.prettyRate()
}

View File

@ -3,7 +3,7 @@ import ErrorBanner from './ErrorBanner.jsx';
import { friendlyTitle } from './util/Utils.js';
import MetricsTable from './MetricsTable.jsx';
import NetworkGraph from './NetworkGraph.jsx';
import { processMultiResourceRollup } from './util/MetricUtils.js';
import { processMultiResourceRollup } from './util/MetricUtils.jsx';
import PropTypes from 'prop-types';
import React from 'react';
import { Spin } from 'antd';

View File

@ -7,7 +7,7 @@ import PropTypes from 'prop-types';
import React from 'react';
import { withContext } from './util/AppContext.jsx';
import { Collapse, Icon, Spin, Tooltip } from 'antd';
import { processMultiResourceRollup, processSingleResourceRollup } from './util/MetricUtils.js';
import { processMultiResourceRollup, processSingleResourceRollup } from './util/MetricUtils.jsx';
import 'whatwg-fetch';
const isMeshedTooltip = (

View File

@ -1,5 +1,5 @@
import _ from 'lodash';
import { metricsPropType } from './util/MetricUtils.js';
import { metricsPropType } from './util/MetricUtils.jsx';
import PropTypes from 'prop-types';
import React from 'react';
import { withContext } from './util/AppContext.jsx';

View File

@ -2,19 +2,12 @@ import _ from 'lodash';
import PropTypes from 'prop-types';
import React from 'react';
import { Col, Icon, Progress, Row } from 'antd';
import { getSuccessRateClassification, srArcClassLabels } from './util/MetricUtils.jsx' ;
import { metricToFormatter, toShortResourceName } from './util/Utils.js';
import './../../css/octopus.css';
const displayName = resource => `${toShortResourceName(resource.type)}/${resource.name}`;
const getSrClassification = sr => {
if (sr < 0.9) {
return "status-poor";
} else if (sr < 0.95) {
return "status-ok";
} else {return "status-good";}
};
const Metric = ({title, value, className}) => {
return (
<Row type="flex" justify="center" className={`octopus-metric ${className}`}>
@ -60,7 +53,7 @@ export default class Octopus extends React.Component {
<div>
<div className="octopus-sr-gauge">
<Progress
className={getSrClassification(resource.successRate)}
className={`success-rate-arc ${getSuccessRateClassification(resource.successRate, srArcClassLabels)}`}
type="dashboard"
format={() => metricToFormatter["SUCCESS_RATE"](resource.successRate)}
width={type === "main" ? 132 : 64}

View File

@ -4,7 +4,7 @@ import ErrorBanner from './ErrorBanner.jsx';
import MetricsTable from './MetricsTable.jsx';
import Octopus from './Octopus.jsx';
import { processNeighborData } from './util/TapUtils.jsx';
import { processSingleResourceRollup } from './util/MetricUtils.js';
import { processSingleResourceRollup } from './util/MetricUtils.jsx';
import PropTypes from 'prop-types';
import React from 'react';
import { Spin } from 'antd';
@ -226,7 +226,7 @@ export class ResourceDetailBase extends React.Component {
{ _.isEmpty(this.state.neighborMetrics.upstream) ? null : (
<div className="page-section">
<h2 className="subsection-header">Upstreams</h2>
<h2 className="subsection-header">Inbound</h2>
<MetricsTable
resource={this.state.resource.type}
metrics={this.state.neighborMetrics.upstream} />
@ -236,7 +236,7 @@ export class ResourceDetailBase extends React.Component {
{ _.isEmpty(this.state.neighborMetrics.downstream) ? null : (
<div className="page-section">
<h2 className="subsection-header">Downstreams</h2>
<h2 className="subsection-header">Outbound</h2>
<MetricsTable
resource={this.state.resource.type}
metrics={this.state.neighborMetrics.downstream} />

View File

@ -6,7 +6,7 @@ import PropTypes from 'prop-types';
import React from 'react';
import { Spin } from 'antd';
import withREST from './util/withREST.jsx';
import { metricsPropType, processSingleResourceRollup } from './util/MetricUtils.js';
import { metricsPropType, processSingleResourceRollup } from './util/MetricUtils.jsx';
import 'whatwg-fetch';
export class ResourceListBase extends React.Component {

View File

@ -12,7 +12,7 @@ import {
getSuccessRateClassification,
processMultiResourceRollup,
processSingleResourceRollup
} from './util/MetricUtils.js';
} from './util/MetricUtils.jsx';
import {linkerdLogoOnly, linkerdWordLogo} from './util/SvgWrappers.jsx';
import './../../css/sidebar.css';
import 'whatwg-fetch';

View File

@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React from 'react';
import { successRateWithMiniChart } from './util/MetricUtils.jsx';
import { Table } from 'antd';
import { withContext } from './util/AppContext.jsx';
import { directionColumn, srcDstColumn } from './util/TapUtils.jsx';
@ -26,32 +27,38 @@ const topColumns = (resourceType, ResourceLink) => [
{
title: "Count",
dataIndex: "count",
className: "numeric",
defaultSortOrder: "descend",
sorter: (a, b) => numericSort(a.count, b.count),
},
{
title: "Best",
dataIndex: "best",
className: "numeric",
sorter: (a, b) => numericSort(a.best, b.best),
render: formatLatencySec
},
{
title: "Worst",
dataIndex: "worst",
className: "numeric",
sorter: (a, b) => numericSort(a.worst, b.worst),
render: formatLatencySec
},
{
title: "Last",
dataIndex: "last",
className: "numeric",
sorter: (a, b) => numericSort(a.last, b.last),
render: formatLatencySec
},
{
title: "Success Rate",
dataIndex: "successRate",
className: "numeric",
width: "128px",
sorter: (a, b) => numericSort(a.successRate.get(), b.successRate.get()),
render: d => !d ? "---" : d.prettyRate()
render: d => successRateWithMiniChart(d.get())
}
];
@ -75,7 +82,7 @@ class TopEventTable extends React.Component {
columns={topColumns(this.props.resourceType, this.props.api.ResourceLink)}
rowKey="key"
pagination={false}
className="top-event-table"
className="top-event-table metric-table"
size="middle" />
);
}

View File

@ -1,6 +1,9 @@
import _ from 'lodash';
import { metricToFormatter } from './Utils.js';
import Percentage from './Percentage.js';
import { Progress } from 'antd';
import PropTypes from 'prop-types';
import React from 'react';
const getPodCategorization = pod => {
if (pod.added && pod.status === "Running") {
@ -27,6 +30,27 @@ export const getSuccessRateClassification = (rate, successRateLabels) => {
}
};
export const srArcClassLabels = {
good: "status-good",
neutral: "status-ok",
bad: "status-poor",
default: "status-ok"
};
export const successRateWithMiniChart = sr => (
<div>
<span className="metric-table-sr">{metricToFormatter["SUCCESS_RATE"](sr)}</span>
<Progress
className={`success-rate-arc ${getSuccessRateClassification(sr, srArcClassLabels)} metric-table-sr-chart`}
type="dashboard"
showInfo={false}
width={32}
strokeWidth={12}
percent={sr * 100}
gapDegree={180} />
</div>
);
const getTotalRequests = row => {
let success = parseInt(_.get(row, ["stats", "successCount"], 0), 10);
let failure = parseInt(_.get(row, ["stats", "failureCount"], 0), 10);

View File

@ -12,6 +12,7 @@ export const rowGutter = 3 * baseWidth;
*/
const successRateFormatter = d3.format(".2%");
const commaFormatter = d3.format(",");
const secondsFormatter = d3.format(",.3s");
export const formatWithComma = m => {
if (_.isNil(m)) {
@ -42,7 +43,7 @@ export const formatLatencySec = latency => {
} else if (s < 1.0) {
return `${niceLatency(s * 1000)} ms`;
} else {
return `${niceLatency(s)} s`;
return `${secondsFormatter(s)} s`;
}
};
@ -196,3 +197,11 @@ export const publicAddressToString = (ipv4, port) => {
let octets = decodeIPToOctets(ipv4);
return octets.join(".") + ":" + port;
};
export const getSrClassification = sr => {
if (sr < 0.9) {
return "status-poor";
} else if (sr < 0.95) {
return "status-ok";
} else {return "status-good";}
};

View File

@ -7,7 +7,7 @@ import Percentage from '../js/components/util/Percentage';
import {
processMultiResourceRollup,
processSingleResourceRollup
} from '../js/components/util/MetricUtils.js';
} from '../js/components/util/MetricUtils.jsx';
describe('MetricUtils', () => {
describe('processSingleResourceRollup', () => {

View File

@ -79,8 +79,8 @@ describe('Utils', () => {
});
it('formats latency greater than 1s as s', () => {
expect(metricToFormatter["LATENCY"](1000)).to.equal('1 s');
expect(metricToFormatter["LATENCY"](9999)).to.equal('10 s');
expect(metricToFormatter["LATENCY"](1000)).to.equal('1.00 s');
expect(metricToFormatter["LATENCY"](9999)).to.equal('10.0 s');
expect(metricToFormatter["LATENCY"](99999)).to.equal('100 s');
});
@ -99,7 +99,8 @@ describe('Utils', () => {
expect(formatLatencySec("0.000231910")).to.equal("232 µs");
expect(formatLatencySec("0.000988600")).to.equal("989 µs");
expect(formatLatencySec("0.005598200")).to.equal("6 ms");
expect(formatLatencySec("3.029409200")).to.equal("3 s");
expect(formatLatencySec("3.029409200")).to.equal("3.03 s");
expect(formatLatencySec("34.395600")).to.equal("34.4 s");
});
});