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:
Risha Mars 2018-02-07 09:50:01 -08:00 committed by GitHub
parent a2d537f5c4
commit ff15574a0d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 79 additions and 205 deletions

View File

@ -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>;
}

View File

@ -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>

View File

@ -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>

View File

@ -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>
);
}
}

View File

@ -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>