linkerd2/web/app/js/components/MetricsTable.jsx

174 lines
4.6 KiB
JavaScript

import { friendlyTitle, metricToFormatter } from './util/Utils.js';
import BaseTable from './BaseTable.jsx';
import ErrorModal from './ErrorModal.jsx';
import GrafanaLink from './GrafanaLink.jsx';
import Grid from '@material-ui/core/Grid';
import PropTypes from 'prop-types';
import React from 'react';
import SuccessRateMiniChart from './util/SuccessRateMiniChart.jsx';
import _ from 'lodash';
import { processedMetricsPropType } from './util/MetricUtils.jsx';
import { withContext } from './util/AppContext.jsx';
const columnDefinitions = (resource, showNamespaceColumn, PrefixedLink) => {
let isAuthorityTable = resource === "authority";
let nsColumn = [
{
title: "Namespace",
key: "namespace",
isNumeric: false,
render: d => !d.namespace ? "---" : <PrefixedLink to={"/namespaces/" + d.namespace}>{d.namespace}</PrefixedLink>
}
];
let meshedColumn = {
title: "Meshed",
key: "meshed",
isNumeric: true,
render: d => !d.pods ? null : d.pods.meshedPods + "/" + d.pods.totalPods
};
let columns = [
{
title: friendlyTitle(resource).singular,
key: "resource-title",
isNumeric: false,
render: d => {
let nameContents;
if (resource === "namespace") {
nameContents = <PrefixedLink to={"/namespaces/" + d.name}>{d.name}</PrefixedLink>;
} else if (!d.added || isAuthorityTable) {
nameContents = d.name;
} else {
nameContents = (
<PrefixedLink to={"/namespaces/" + d.namespace + "/" + resource + "s/" + d.name}>
{d.name}
</PrefixedLink>
);
}
return (
<Grid container alignItems="center" spacing={8}>
<Grid item>{nameContents}</Grid>
{ _.isEmpty(d.errors) ? null :
<Grid item><ErrorModal errors={d.errors} resourceName={d.name} resourceType={resource} /></Grid>}
</Grid>
);
}
},
{
title: "Success Rate",
key: "success-rate",
isNumeric: true,
render: d => <SuccessRateMiniChart sr={d.successRate} />
},
{
title: "Request Rate",
key: "request-rate",
isNumeric: true,
render: d => metricToFormatter["NO_UNIT"](d.requestRate)
},
{
title: "P50 Latency",
key: "p50_latency",
isNumeric: true,
render: d => metricToFormatter["LATENCY"](d.P50)
},
{
title: "P95 Latency",
key: "p95_latency",
isNumeric: true,
render: d => metricToFormatter["LATENCY"](d.P95)
},
{
title: "P99 Latency",
key: "p99_latency",
isNumeric: true,
render: d => metricToFormatter["LATENCY"](d.P99)
},
{
title: "TLS",
key: "has_tls",
isNumeric: true,
render: d => _.isNil(d.tlsRequestPercent) || d.tlsRequestPercent.get() === -1 ? "---" : d.tlsRequestPercent.prettyRate()
},
{
title: "Grafana Dashboard",
key: "grafanaDashboard",
isNumeric: true,
render: row => {
if (!isAuthorityTable && (!row.added || _.get(row, "pods.totalPods") === "0") ) {
return null;
}
return (
<GrafanaLink
name={row.name}
namespace={row.namespace}
resource={resource}
PrefixedLink={PrefixedLink} />
);
}
}
];
// don't add the meshed column on a Authority MetricsTable
if (!isAuthorityTable) {
columns.splice(1, 0, meshedColumn);
}
if (!showNamespaceColumn) {
return columns;
} else {
return _.concat(nsColumn, columns);
}
};
const preprocessMetrics = metrics => {
let tableData = _.cloneDeep(metrics);
_.each(tableData, datum => {
_.each(datum.latency, (value, quantile) => {
datum[quantile] = value;
});
});
return tableData;
};
class MetricsTable extends React.Component {
static propTypes = {
api: PropTypes.shape({
PrefixedLink: PropTypes.func.isRequired,
}).isRequired,
metrics: PropTypes.arrayOf(processedMetricsPropType),
resource: PropTypes.string.isRequired,
showNamespaceColumn: PropTypes.bool
};
static defaultProps = {
showNamespaceColumn: true,
metrics: []
};
render() {
const { metrics, resource, showNamespaceColumn, api } = this.props;
let showNsColumn = resource === "namespace" ? false : showNamespaceColumn;
let columns = columnDefinitions(resource, showNsColumn, api.PrefixedLink);
let rows = preprocessMetrics(metrics);
return (
<BaseTable
tableRows={rows}
tableColumns={columns}
tableClassName="metric-table"
padding="dense" />
);
}
}
export default withContext(MetricsTable);