mirror of https://github.com/linkerd/linkerd2.git
Surface TCP stats in more places in the dashboard (#2464)
Show the TCP stats table in Namespace Overview page (the landing page) as well as the Namespace pages.
This commit is contained in:
parent
f258cf0d3a
commit
f2be6cb058
|
@ -6,7 +6,6 @@ import NetworkGraph from './NetworkGraph.jsx';
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Spinner from './util/Spinner.jsx';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import _filter from 'lodash/filter';
|
||||
import _get from 'lodash/get';
|
||||
import _isEmpty from 'lodash/isEmpty';
|
||||
|
@ -84,7 +83,7 @@ class Namespaces extends React.Component {
|
|||
}
|
||||
this.setState({ pendingRequests: true });
|
||||
|
||||
this.api.setCurrentRequests([this.api.fetchMetrics(this.api.urlsForResource("all", this.state.ns))]);
|
||||
this.api.setCurrentRequests([this.api.fetchMetrics(this.api.urlsForResource("all", this.state.ns, true))]);
|
||||
|
||||
Promise.all(this.api.getCurrentPromises())
|
||||
.then(([allRollup]) => {
|
||||
|
@ -117,8 +116,8 @@ class Namespaces extends React.Component {
|
|||
}
|
||||
return (
|
||||
<div className="page-section">
|
||||
<Typography variant="h5">{friendlyTitle(resource).plural}</Typography>
|
||||
<MetricsTable
|
||||
title={friendlyTitle(resource).plural}
|
||||
resource={resource}
|
||||
metrics={metrics}
|
||||
showNamespaceColumn={false} />
|
||||
|
@ -127,16 +126,16 @@ class Namespaces extends React.Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const {metrics} = this.state;
|
||||
const { metrics } = this.state;
|
||||
let noMetrics = _isEmpty(metrics.pod);
|
||||
let deploymentsWithMetrics = _filter(metrics.deployment, d => d.requestRate > 0);
|
||||
|
||||
return (
|
||||
<div className="page-content">
|
||||
{ !this.state.error ? null : <ErrorBanner message={this.state.error} /> }
|
||||
{ !this.state.loaded ? <Spinner /> : (
|
||||
{!this.state.error ? null : <ErrorBanner message={this.state.error} />}
|
||||
{!this.state.loaded ? <Spinner /> : (
|
||||
<div>
|
||||
{ noMetrics ? <div>No resources detected.</div> : null}
|
||||
{noMetrics ? <div>No resources detected.</div> : null}
|
||||
{
|
||||
_isEmpty(deploymentsWithMetrics) ? null :
|
||||
<NetworkGraph namespace={this.state.ns} deployments={metrics.deployment} />
|
||||
|
@ -148,6 +147,17 @@ class Namespaces extends React.Component {
|
|||
{this.renderResourceSection("statefulset", metrics.statefulset)}
|
||||
{this.renderResourceSection("job", metrics.job)}
|
||||
{this.renderResourceSection("authority", metrics.authority)}
|
||||
|
||||
{
|
||||
noMetrics ? null :
|
||||
<div className="page-section">
|
||||
<MetricsTable
|
||||
title="TCP"
|
||||
resource="pod"
|
||||
metrics={metrics.pod}
|
||||
isTcpTable={true} />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
</div>);
|
||||
|
|
|
@ -79,7 +79,9 @@ class NamespaceLanding extends React.Component {
|
|||
this.api.fetchMetrics(this.api.urlsForResource("namespace"))
|
||||
];
|
||||
if (!_isEmpty(this.state.selectedNs)) {
|
||||
apiRequests = apiRequests.concat([this.api.fetchMetrics(this.api.urlsForResource("all", this.state.selectedNs))]);
|
||||
apiRequests = apiRequests.concat([
|
||||
this.api.fetchMetrics(this.api.urlsForResource("all", this.state.selectedNs, true))
|
||||
]);
|
||||
}
|
||||
this.api.setCurrentRequests(apiRequests);
|
||||
|
||||
|
@ -149,7 +151,7 @@ class NamespaceLanding extends React.Component {
|
|||
<Grid container direction="column" spacing={16}>
|
||||
<Grid item><Typography variant="h4">Namespace: {namespace}</Typography></Grid>
|
||||
<Grid item><Divider /></Grid>
|
||||
<Grid item>{ noMetrics ? <div>No resources detected.</div> : null}</Grid>
|
||||
<Grid item>{noMetrics ? <div>No resources detected.</div> : null}</Grid>
|
||||
|
||||
{this.renderResourceSection("deployment", metrics.deployment)}
|
||||
{this.renderResourceSection("daemonset", metrics.daemonset)}
|
||||
|
@ -158,6 +160,19 @@ class NamespaceLanding extends React.Component {
|
|||
{this.renderResourceSection("statefulset", metrics.statefulset)}
|
||||
{this.renderResourceSection("job", metrics.job)}
|
||||
{this.renderResourceSection("authority", metrics.authority)}
|
||||
|
||||
{
|
||||
noMetrics ? null :
|
||||
<Grid container direction="column" justify="center">
|
||||
<Grid item>
|
||||
<MetricsTable
|
||||
title="TCP"
|
||||
resource="pod"
|
||||
metrics={metrics.pod}
|
||||
isTcpTable={true} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
}
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
@ -167,7 +182,7 @@ class NamespaceLanding extends React.Component {
|
|||
let hr = (
|
||||
<Grid container justify="space-between" alignItems="center">
|
||||
<Grid item><Typography variant="subtitle1">{ns.name}</Typography></Grid>
|
||||
{!ns.added ? null : <Grid item><SimpleChip label="meshed" type="good" /></Grid> }
|
||||
{!ns.added ? null : <Grid item><SimpleChip label="meshed" type="good" /></Grid>}
|
||||
</Grid>
|
||||
);
|
||||
return {
|
||||
|
@ -192,8 +207,8 @@ class NamespaceLanding extends React.Component {
|
|||
render() {
|
||||
return (
|
||||
<div className="page-content">
|
||||
{ !this.state.error ? null : <ErrorBanner message={this.state.error} /> }
|
||||
{ !this.state.loaded ? <Spinner /> : this.renderAccordion() }
|
||||
{!this.state.error ? null : <ErrorBanner message={this.state.error} />}
|
||||
{!this.state.loaded ? <Spinner /> : this.renderAccordion()}
|
||||
</div>);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -105,7 +105,7 @@ export class ResourceDetailBase extends React.Component {
|
|||
// rcs, deploys, replicasets, etc but not pods or authorities
|
||||
let shouldExclude = this.state.resourceType === "pod" ?
|
||||
r => r !== "pod" :
|
||||
r => r === "pod" || r === "authority" || r === "service";
|
||||
r => r === "pod" || r === "authority" || r === "service";
|
||||
return _reduce(metricsByResource, (mem, resourceMetrics, resource) => {
|
||||
if (shouldExclude(resource)) {
|
||||
return mem;
|
||||
|
@ -131,7 +131,7 @@ export class ResourceDetailBase extends React.Component {
|
|||
this.api.fetchPods(resource.namespace),
|
||||
// metrics for all pods in this namespace (hack, continued)
|
||||
this.api.fetchMetrics(
|
||||
`${this.api.urlsForResource("pod", resource.namespace)}&tcp_stats=true`
|
||||
`${this.api.urlsForResource("pod", resource.namespace, true)}`
|
||||
),
|
||||
// upstream resources of this resource (meshed traffic only)
|
||||
this.api.fetchMetrics(
|
||||
|
@ -269,9 +269,9 @@ export class ResourceDetailBase extends React.Component {
|
|||
<Grid item><Typography variant="h5">{resourceType}/{resourceName}</Typography></Grid>
|
||||
<Grid item>
|
||||
<Grid container spacing={8}>
|
||||
{ showNoTrafficMsg ? <Grid item><SimpleChip label="no traffic" type="warning" /></Grid> : null }
|
||||
{showNoTrafficMsg ? <Grid item><SimpleChip label="no traffic" type="warning" /></Grid> : null}
|
||||
<Grid item>
|
||||
{ resourceIsMeshed ?
|
||||
{resourceIsMeshed ?
|
||||
<SimpleChip label="meshed" type="good" /> :
|
||||
<SimpleChip label="unmeshed" type="bad" />
|
||||
}
|
||||
|
@ -301,24 +301,24 @@ export class ResourceDetailBase extends React.Component {
|
|||
updateUnmeshedSources={this.updateUnmeshedSources}
|
||||
disableTop={!resourceIsMeshed} />
|
||||
|
||||
{ _isEmpty(upstreams) ? null : (
|
||||
{_isEmpty(upstreams) ? null : (
|
||||
<React.Fragment>
|
||||
<MetricsTable
|
||||
resource="multi_resource"
|
||||
title="Inbound"
|
||||
metrics={upstreamMetrics} />
|
||||
</React.Fragment>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
{ _isEmpty(this.state.downstreamMetrics) ? null : (
|
||||
{_isEmpty(this.state.downstreamMetrics) ? null : (
|
||||
<React.Fragment>
|
||||
<MetricsTable
|
||||
resource="multi_resource"
|
||||
title="Outbound"
|
||||
metrics={downstreamMetrics} />
|
||||
</React.Fragment>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
|
|
|
@ -17,7 +17,7 @@ const checkFetchOk = resp => {
|
|||
throw {
|
||||
status: resp.status,
|
||||
url: resp.url,
|
||||
statusText:resp.statusText,
|
||||
statusText: resp.statusText,
|
||||
error: error.error
|
||||
};
|
||||
});
|
||||
|
@ -118,10 +118,20 @@ const ApiHelpers = (pathPrefix, defaultMetricsWindow = '1m') => {
|
|||
metricsWindow = window;
|
||||
};
|
||||
|
||||
const urlsForResource = (type, namespace) => {
|
||||
const urlsForResource = (type, namespace, includeTcp) => {
|
||||
// Traffic Performance Summary. This retrieves stats for the given resource.
|
||||
let baseUrl = '/api/tps-reports?resource_type=' + type;
|
||||
return !namespace ? baseUrl + '&all_namespaces=true' : baseUrl + '&namespace=' + namespace;
|
||||
let resourceUrl = '/api/tps-reports?resource_type=' + type;
|
||||
|
||||
if (_isEmpty(namespace)) {
|
||||
resourceUrl += '&all_namespaces=true';
|
||||
} else {
|
||||
resourceUrl += '&namespace=' + namespace;
|
||||
}
|
||||
if (includeTcp) {
|
||||
resourceUrl += '&tcp_stats=true';
|
||||
}
|
||||
|
||||
return resourceUrl;
|
||||
};
|
||||
|
||||
// maintain a list of a component's requests,
|
||||
|
@ -157,7 +167,7 @@ const ApiHelpers = (pathPrefix, defaultMetricsWindow = '1m') => {
|
|||
return (
|
||||
<Link
|
||||
to={url}
|
||||
{...(this.props.targetBlank ? {target:'_blank'} : {})}>
|
||||
{...(this.props.targetBlank ? { target: '_blank' } : {})}>
|
||||
{this.props.children}
|
||||
</Link>
|
||||
);
|
||||
|
@ -175,10 +185,10 @@ const ApiHelpers = (pathPrefix, defaultMetricsWindow = '1m') => {
|
|||
};
|
||||
|
||||
// a prefixed link to a Resource Detail page
|
||||
const ResourceLink = ({resource, linkText}) => {
|
||||
const ResourceLink = ({ resource, linkText }) => {
|
||||
return (
|
||||
<PrefixedLink to={generateResourceURL(resource)}>
|
||||
{ linkText || resource.type + "/" + resource.name}
|
||||
{linkText || resource.type + "/" + resource.name}
|
||||
</PrefixedLink>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -286,5 +286,11 @@ describe('ApiHelpers', () => {
|
|||
let deploymentUrls = api.urlsForResource("pod", "my-ns");
|
||||
expect(deploymentUrls).toEqual('/api/tps-reports?resource_type=pod&namespace=my-ns');
|
||||
});
|
||||
|
||||
it('queries for TCP stats when specified', () => {
|
||||
api = ApiHelpers();
|
||||
let url = api.urlsForResource('sts', '', true);
|
||||
expect(url).toEqual('/api/tps-reports?resource_type=sts&all_namespaces=true&tcp_stats=true');
|
||||
})
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue