mirror of https://github.com/linkerd/linkerd2.git
MetricsTable: Consolidate latency, success, request metrics into one tab (#276)
* Consolidate latency, success, request metrics into one tab on the SortableMetricsTable - removes sparklines from the table - makes tables sortable by default - move pod table in DeploymentDetail to its own row * remove request distribution column, reorder columns
This commit is contained in:
parent
a2d537f5c4
commit
ff15574a0d
|
@ -16,6 +16,8 @@ import { emptyMetric, getPodsByDeployment, processRollupMetrics, processTimeseri
|
|||
import './../../css/deployment.css';
|
||||
import 'whatwg-fetch';
|
||||
|
||||
const Fragment = React.Fragment;
|
||||
|
||||
export default class DeploymentDetail extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
@ -164,45 +166,50 @@ export default class DeploymentDetail extends React.Component {
|
|||
}
|
||||
|
||||
return (
|
||||
<Row gutter={rowGutter} key="deployment-midsection">
|
||||
<Col span={16}>
|
||||
<div className="pod-summary">
|
||||
<div className="border-container border-neutral subsection-header">
|
||||
<div className="border-container-content subsection-header">Pod summary</div>
|
||||
</div>
|
||||
{
|
||||
_.isEmpty(this.state.metrics) ? null :
|
||||
<div className="pod-distribution-chart">
|
||||
<div className="bar-chart-title">
|
||||
<div>Request load by pod</div>
|
||||
<div className="bar-chart-tooltip" />
|
||||
<Fragment key="deployment-pod-summary">
|
||||
<Row gutter={rowGutter}>
|
||||
<Col span={16}>
|
||||
<div className="pod-summary">
|
||||
<div className="border-container border-neutral subsection-header">
|
||||
<div className="border-container-content subsection-header">Pod summary</div>
|
||||
</div>
|
||||
{
|
||||
_.isEmpty(this.state.metrics) ? null :
|
||||
<div className="pod-distribution-chart">
|
||||
<div className="bar-chart-title">
|
||||
<div>Request load by pod</div>
|
||||
<div className="bar-chart-tooltip" />
|
||||
</div>
|
||||
<BarChart
|
||||
data={this.state.metrics}
|
||||
lastUpdated={this.state.lastUpdated}
|
||||
containerClassName="pod-distribution-chart" />
|
||||
</div>
|
||||
<BarChart
|
||||
data={this.state.metrics}
|
||||
lastUpdated={this.state.lastUpdated}
|
||||
containerClassName="pod-distribution-chart" />
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<div className="border-container border-neutral deployment-details">
|
||||
<div className="border-container-content">
|
||||
<div className=" subsection-header">Deployment details</div>
|
||||
<Metric title="Pods" value={_.size(podTableData)} />
|
||||
<Metric title="Upstream deployments" value={this.numUpstreams()} />
|
||||
<Metric title="Downstream deployments" value={this.numDownstreams()} />
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row>
|
||||
<Col span={24}>
|
||||
<TabbedMetricsTable
|
||||
resource="pod"
|
||||
resourceName={this.state.deploy}
|
||||
metrics={podTableData}
|
||||
lastUpdated={this.state.lastUpdated}
|
||||
api={this.api} />
|
||||
</div>
|
||||
</Col>
|
||||
|
||||
<Col span={8}>
|
||||
<div className="border-container border-neutral deployment-details">
|
||||
<div className="border-container-content">
|
||||
<div className=" subsection-header">Deployment details</div>
|
||||
<Metric title="Pods" value={_.size(podTableData)} />
|
||||
<Metric title="Upstream deployments" value={this.numUpstreams()} />
|
||||
<Metric title="Downstream deployments" value={this.numDownstreams()} />
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -217,8 +224,6 @@ export default class DeploymentDetail extends React.Component {
|
|||
<TabbedMetricsTable
|
||||
resource="path"
|
||||
metrics={this.state.pathMetrics}
|
||||
hideSparklines={true}
|
||||
lastUpdated={this.props.lastUpdated}
|
||||
api={this.api} />
|
||||
</div>;
|
||||
}
|
||||
|
|
|
@ -158,9 +158,7 @@ export default class DeploymentsList extends React.Component {
|
|||
<div className="deployments-list">
|
||||
<TabbedMetricsTable
|
||||
resource="deployment"
|
||||
lastUpdated={this.state.lastUpdated}
|
||||
metrics={this.state.metrics}
|
||||
hideSparklines={this.state.limitSparklineData}
|
||||
api={this.api} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -74,10 +74,8 @@ export default class Paths extends React.Component {
|
|||
<TabbedMetricsTable
|
||||
resource="path"
|
||||
metrics={this.state.metrics}
|
||||
lastUpdated={this.state.lastUpdated}
|
||||
api={this.api}
|
||||
sortable={true}
|
||||
hideSparklines={true} />
|
||||
sortable={true} />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
import _ from 'lodash';
|
||||
import LineGraph from './LineGraph.jsx';
|
||||
import Percentage from './util/Percentage.js';
|
||||
import { processTimeseriesMetrics } from './util/MetricUtils.js';
|
||||
import { metricToFormatter } from './util/Utils.js';
|
||||
import React from 'react';
|
||||
import { metricToFormatter, toClassName } from './util/Utils.js';
|
||||
import { Table, Tabs } from 'antd';
|
||||
import { Table } from 'antd';
|
||||
|
||||
/*
|
||||
Table to display Success Rate, Requests and Latency in tabs.
|
||||
|
@ -12,8 +9,8 @@ import { Table, Tabs } from 'antd';
|
|||
*/
|
||||
|
||||
const resourceInfo = {
|
||||
"upstream_deployment": { title: "upstream deployment", url: "/deployment?deploy=" },
|
||||
"downstream_deployment": { title: "downstream deployment", url: "/deployment?deploy=" },
|
||||
"upstream_deployment": { title: "deployment", url: "/deployment?deploy=" },
|
||||
"downstream_deployment": { title: "deployment", url: "/deployment?deploy=" },
|
||||
"upstream_pod": { title: "upstream pod", url: "/pod?pod=" },
|
||||
"downstream_pod": { title: "downstream pod", url: "/pod?pod=" },
|
||||
"deployment": { title: "deployment", url: "/deployment?deploy=" },
|
||||
|
@ -21,19 +18,27 @@ const resourceInfo = {
|
|||
"path": { title: "path", url: null }
|
||||
};
|
||||
|
||||
const generateColumns = (sortable, ConduitLink) => {
|
||||
return {
|
||||
resourceName: resource => {
|
||||
return {
|
||||
title: resource.title,
|
||||
dataIndex: "name",
|
||||
key: "name",
|
||||
sorter: sortable ? (a, b) => (a.name || "").localeCompare(b.name) : false,
|
||||
render: name => !resource.url ? name :
|
||||
<ConduitLink to={`${resource.url}${name}`}>{name}</ConduitLink>
|
||||
};
|
||||
const columnDefinitions = (sortable = true, resource, ConduitLink) => {
|
||||
return [
|
||||
{
|
||||
title: resource.title,
|
||||
dataIndex: "name",
|
||||
key: "name",
|
||||
width: 150,
|
||||
sorter: sortable ? (a, b) => (a.name || "").localeCompare(b.name) : false,
|
||||
render: name => !resource.url ? name :
|
||||
<ConduitLink to={`${resource.url}${name}`}>{name}</ConduitLink>
|
||||
},
|
||||
successRate: {
|
||||
{
|
||||
title: "Request Rate",
|
||||
dataIndex: "requestRate",
|
||||
key: "requestRateRollup",
|
||||
defaultSortOrder: 'descend',
|
||||
className: "numeric",
|
||||
sorter: sortable ? (a, b) => numericSort(a.requestRate, b.requestRate) : false,
|
||||
render: d => metricToFormatter["REQUEST_RATE"](d)
|
||||
},
|
||||
{
|
||||
title: "Success Rate",
|
||||
dataIndex: "successRate",
|
||||
key: "successRateRollup",
|
||||
|
@ -41,32 +46,15 @@ const generateColumns = (sortable, ConduitLink) => {
|
|||
sorter: sortable ? (a, b) => numericSort(a.successRate, b.successRate) : false,
|
||||
render: d => metricToFormatter["SUCCESS_RATE"](d)
|
||||
},
|
||||
requests: {
|
||||
title: "Request Rate",
|
||||
dataIndex: "requestRate",
|
||||
key: "requestRateRollup",
|
||||
{
|
||||
title: "P50 Latency",
|
||||
dataIndex: "P50",
|
||||
key: "p50LatencyRollup",
|
||||
className: "numeric",
|
||||
sorter: sortable ? (a, b) => numericSort(a.requestRate, b.requestRate) : false,
|
||||
render: d => metricToFormatter["REQUEST_RATE"](d)
|
||||
},
|
||||
requestDistribution: {
|
||||
title: "Request distribution",
|
||||
dataIndex: "requestDistribution",
|
||||
key: "distribution",
|
||||
className: "numeric",
|
||||
sorter: sortable ? (a, b) =>
|
||||
numericSort(a.requestDistribution.get(), b.requestDistribution.get()) : false,
|
||||
render: d => d.prettyRate()
|
||||
},
|
||||
latencyP99: {
|
||||
title: "P99 Latency",
|
||||
dataIndex: "P99",
|
||||
key: "p99LatencyRollup",
|
||||
className: "numeric",
|
||||
sorter: sortable ? (a, b) => numericSort(a.P99, b.P99) : false,
|
||||
sorter: sortable ? (a, b) => numericSort(a.P50, b.P50) : false,
|
||||
render: metricToFormatter["LATENCY"]
|
||||
},
|
||||
latencyP95: {
|
||||
{
|
||||
title: "P95 Latency",
|
||||
dataIndex: "P95",
|
||||
key: "p95LatencyRollup",
|
||||
|
@ -74,82 +62,37 @@ const generateColumns = (sortable, ConduitLink) => {
|
|||
sorter: sortable ? (a, b) => numericSort(a.P95, b.P95) : false,
|
||||
render: metricToFormatter["LATENCY"]
|
||||
},
|
||||
latencyP50: {
|
||||
title: "P50 Latency",
|
||||
dataIndex: "P50",
|
||||
key: "p50LatencyRollup",
|
||||
{
|
||||
title: "P99 Latency",
|
||||
dataIndex: "P99",
|
||||
key: "p99LatencyRollup",
|
||||
className: "numeric",
|
||||
sorter: sortable ? (a, b) => numericSort(a.P50, b.P50) : false,
|
||||
sorter: sortable ? (a, b) => numericSort(a.P99, b.P99) : false,
|
||||
render: metricToFormatter["LATENCY"]
|
||||
}
|
||||
};
|
||||
];
|
||||
};
|
||||
|
||||
const numericSort = (a, b) => (_.isNil(a) ? -1 : a) - (_.isNil(b) ? -1 : b);
|
||||
|
||||
const metricToColumns = baseCols => {
|
||||
return {
|
||||
requestRate: resource => [
|
||||
baseCols.resourceName(resource),
|
||||
baseCols.requests,
|
||||
resource.title === "deployment" ? null : baseCols.requestDistribution
|
||||
],
|
||||
successRate: resource => [baseCols.resourceName(resource), baseCols.successRate],
|
||||
latency: resource => [
|
||||
baseCols.resourceName(resource),
|
||||
baseCols.latencyP50,
|
||||
baseCols.latencyP95,
|
||||
baseCols.latencyP99
|
||||
]
|
||||
};
|
||||
};
|
||||
|
||||
const nameToDataKey = {
|
||||
requestRate: "REQUEST_RATE",
|
||||
successRate: "SUCCESS_RATE",
|
||||
latency: "LATENCY"
|
||||
};
|
||||
|
||||
export default class TabbedMetricsTable extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.api = this.props.api;
|
||||
this.handleApiError = this.handleApiError.bind(this);
|
||||
this.loadFromServer = this.loadFromServer.bind(this);
|
||||
|
||||
let tsHelper = this.api.urlsForResource[this.props.resource];
|
||||
|
||||
this.state = {
|
||||
timeseries: {},
|
||||
rollup: this.preprocessMetrics(),
|
||||
groupBy: tsHelper.groupBy,
|
||||
metricsUrl: tsHelper.url(this.props.resourceName),
|
||||
error: '',
|
||||
lastUpdated: this.props.lastUpdated,
|
||||
pollingInterval: 10000,
|
||||
pendingRequests: false
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (!this.props.hideSparklines) {
|
||||
this.loadFromServer();
|
||||
this.timerId = window.setInterval(this.loadFromServer, this.state.pollingInterval);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.clearInterval(this.timerId);
|
||||
}
|
||||
|
||||
preprocessMetrics() {
|
||||
let tableData = _.cloneDeep(this.props.metrics);
|
||||
let totalRequestRate = _.sumBy(this.props.metrics, "requestRate") || 0;
|
||||
|
||||
_.each(tableData, datum => {
|
||||
datum.totalRequests = totalRequestRate;
|
||||
datum.requestDistribution = new Percentage(datum.requestRate, datum.totalRequests);
|
||||
|
||||
_.each(datum.latency, (value, quantile) => {
|
||||
datum[quantile] = value;
|
||||
});
|
||||
|
@ -158,62 +101,9 @@ export default class TabbedMetricsTable extends React.Component {
|
|||
return tableData;
|
||||
}
|
||||
|
||||
loadFromServer() {
|
||||
if (this.state.pendingRequests) {
|
||||
return; // don't make more requests if the ones we sent haven't completed
|
||||
}
|
||||
this.setState({ pendingRequests: true });
|
||||
|
||||
this.api.fetchMetrics(this.state.metricsUrl.ts)
|
||||
.then(tsResp => {
|
||||
let tsByEntity = processTimeseriesMetrics(tsResp.metrics, this.state.groupBy);
|
||||
this.setState({
|
||||
timeseries: tsByEntity,
|
||||
pendingRequests: false,
|
||||
error: ''
|
||||
});
|
||||
})
|
||||
.catch(this.handleApiError);
|
||||
}
|
||||
|
||||
handleApiError(e) {
|
||||
this.setState({
|
||||
pendingRequests: false,
|
||||
error: `Error getting data from server: ${e.message}`
|
||||
});
|
||||
}
|
||||
|
||||
getSparklineColumn(metricName) {
|
||||
return {
|
||||
title: `History (last ${this.api.getMetricsWindow()})`,
|
||||
key: metricName,
|
||||
className: "numeric",
|
||||
render: d => {
|
||||
let tsData;
|
||||
if (metricName === "latency") {
|
||||
tsData = _.get(this.state.timeseries, [d.name, "LATENCY", "P99"], []);
|
||||
} else {
|
||||
tsData = _.get(this.state.timeseries, [d.name, nameToDataKey[metricName]], []);
|
||||
}
|
||||
|
||||
return (<LineGraph
|
||||
data={tsData}
|
||||
lastUpdated={this.props.lastUpdated}
|
||||
containerClassName={`spark-${toClassName(metricName)}-${toClassName(d.name)}-${toClassName(this.props.resource)}`}
|
||||
height={17}
|
||||
width={170}
|
||||
flashLastDatapoint={false} />);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
renderTable(metric) {
|
||||
render() {
|
||||
let resource = resourceInfo[this.props.resource];
|
||||
let columnDefinitions = metricToColumns(generateColumns(this.props.sortable, this.props.api.ConduitLink));
|
||||
let columns = _.compact(columnDefinitions[metric](resource));
|
||||
if (!this.props.hideSparklines) {
|
||||
columns.push(this.getSparklineColumn(metric));
|
||||
}
|
||||
let columns = _.compact(columnDefinitions(this.props.sortable, resource, this.api.ConduitLink));
|
||||
|
||||
return (<Table
|
||||
dataSource={this.state.rollup}
|
||||
|
@ -222,16 +112,4 @@ export default class TabbedMetricsTable extends React.Component {
|
|||
className="conduit-table"
|
||||
rowKey={r => r.name} />);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Tabs defaultActiveKey="tab-1">
|
||||
<Tabs.TabPane tab="Requests" key="tab-1">{this.renderTable("requestRate")}</Tabs.TabPane>
|
||||
<Tabs.TabPane tab="Success Rate" key="tab-2">{this.renderTable("successRate")}</Tabs.TabPane>
|
||||
<Tabs.TabPane tab="Latency" key="tab-3">{this.renderTable("latency")}</Tabs.TabPane>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import { rowGutter } from './util/Utils.js';
|
|||
import TabbedMetricsTable from './TabbedMetricsTable.jsx';
|
||||
import { Col, Row } from 'antd';
|
||||
|
||||
const maxTsToFetch = 15;
|
||||
export default class UpstreamDownstreamTables extends React.Component {
|
||||
render() {
|
||||
let numUpstreams = _.size(this.props.upstreamMetrics);
|
||||
|
@ -23,8 +22,6 @@ export default class UpstreamDownstreamTables extends React.Component {
|
|||
<TabbedMetricsTable
|
||||
resource={`upstream_${this.props.resourceType}`}
|
||||
resourceName={this.props.resourceName}
|
||||
hideSparklines={numUpstreams > maxTsToFetch}
|
||||
lastUpdated={this.props.lastUpdated}
|
||||
metrics={this.props.upstreamMetrics}
|
||||
api={this.props.api} />
|
||||
</div>
|
||||
|
@ -40,8 +37,6 @@ export default class UpstreamDownstreamTables extends React.Component {
|
|||
<TabbedMetricsTable
|
||||
resource={`downstream_${this.props.resourceType}`}
|
||||
resourceName={this.props.resourceName}
|
||||
hideSparklines={numDownstreams > maxTsToFetch}
|
||||
lastUpdated={this.props.lastUpdated}
|
||||
metrics={this.props.downstreamMetrics}
|
||||
api={this.props.api} />
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue