Skip to content

Commit

Permalink
ui: Add per-node option to Custom Graphs
Browse files Browse the repository at this point in the history
Adds "Per Node" option to custom graphs, which will graph a separate
line for every node in the cluster for a given metric.

Also fixes a bug where you could not graph multiple lines of the same
named metric.

Release note: None
  • Loading branch information
Matt Tracy committed Mar 15, 2018
1 parent 4cf81f9 commit b96ab02
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 23 deletions.
38 changes: 38 additions & 0 deletions pkg/ui/src/redux/nodes.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import * as protos from "src/js/protos";
import {
nodeDisplayNameByIDSelector,
selectCommissionedNodeStatuses,
selectStoreIDsByNodeID,
LivenessStatus,
sumNodeStats,
} from "./nodes";
Expand Down Expand Up @@ -110,6 +111,43 @@ describe("node data selectors", function() {
assert.deepEqual(nodeDisplayNameByIDSelector(store.getState()), {});
});
});

describe("store IDs by node ID", function() {
it("correctly creates storeID map", function() {
const data = [
{
desc: { node_id: 1 },
store_statuses: [
{ desc: { store_id: 1 }},
{ desc: { store_id: 2 }},
{ desc: { store_id: 3 }},
],
},
{
desc: { node_id: 2 },
store_statuses: [
{ desc: { store_id: 4 }},
],
},
{
desc: { node_id: 3 },
store_statuses: [
{ desc: { store_id: 5 }},
{ desc: { store_id: 6 }},
],
},
];
const store = createAdminUIStore();
store.dispatch(nodesReducerObj.receiveData(data));
const state = store.getState();

assert.deepEqual(selectStoreIDsByNodeID(state), {
1: ["1", "2", "3"],
2: ["4"],
3: ["5", "6"],
});
});
});
});

describe("selectCommissionedNodeStatuses", function() {
Expand Down
17 changes: 16 additions & 1 deletion pkg/ui/src/redux/nodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,19 @@ export const nodeDisplayNameByIDSelector = createSelector(
},
);

// selectStoreIDsByNodeID returns a map from node ID to a list of store IDs for
// that node. Like nodeIDsSelector, the store ids are converted to strings.
export const selectStoreIDsByNodeID = createSelector(
nodeStatusesSelector,
(nodeStatuses) => {
const result: {[key: string]: string[]} = {};
_.each(nodeStatuses, ns =>
result[ns.desc.node_id] = _.map(ns.store_statuses, ss => ss.desc.store_id.toString()),
);
return result;
},
);

/**
* nodesSummarySelector returns a directory object containing a variety of
* computed information based on the current nodes. This object is easy to
Expand All @@ -249,7 +262,8 @@ export const nodesSummarySelector = createSelector(
nodeDisplayNameByIDSelector,
livenessStatusByNodeIDSelector,
livenessByNodeIDSelector,
(nodeStatuses, nodeIDs, nodeStatusByID, nodeSums, nodeDisplayNameByID, livenessStatusByNodeID, livenessByNodeID) => {
selectStoreIDsByNodeID,
(nodeStatuses, nodeIDs, nodeStatusByID, nodeSums, nodeDisplayNameByID, livenessStatusByNodeID, livenessByNodeID, storeIDsByNodeID) => {
return {
nodeStatuses,
nodeIDs,
Expand All @@ -258,6 +272,7 @@ export const nodesSummarySelector = createSelector(
nodeDisplayNameByID,
livenessStatusByNodeID,
livenessByNodeID,
storeIDsByNodeID,
};
},
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import _ from "lodash";

import { NodesSummary } from "src/redux/nodes";

/**
Expand Down Expand Up @@ -45,9 +43,5 @@ export function nodeDisplayName(nodesSummary: NodesSummary, nid: string) {
}

export function storeIDsForNode(nodesSummary: NodesSummary, nid: string): string[] {
const ns = nodesSummary.nodeStatusByID[nid];
if (!ns) {
return [];
}
return _.map(ns.store_statuses, (ss) => ss.desc.store_id.toString());
return nodesSummary.storeIDsByNodeID[nid] || [];
}
18 changes: 15 additions & 3 deletions pkg/ui/src/views/reports/containers/customgraph/customMetric.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import { DropdownOption } from "src/views/shared/components/dropdown";
import TimeSeriesQueryAggregator = protos.cockroach.ts.tspb.TimeSeriesQueryAggregator;
import TimeSeriesQueryDerivative = protos.cockroach.ts.tspb.TimeSeriesQueryDerivative;

const aggregatorOptions: DropdownOption[] = [
const downsamplerOptions: DropdownOption[] = [
TimeSeriesQueryAggregator.AVG,
TimeSeriesQueryAggregator.MAX,
TimeSeriesQueryAggregator.MIN,
TimeSeriesQueryAggregator.SUM,
].map(agg => ({ label: TimeSeriesQueryAggregator[agg], value: agg.toString() }));

const aggregatorOptions = downsamplerOptions;

const derivativeOptions: DropdownOption[] = [
{ label: "Normal", value: TimeSeriesQueryDerivative.NONE.toString() },
{ label: "Rate", value: TimeSeriesQueryDerivative.DERIVATIVE.toString() },
Expand All @@ -26,6 +28,7 @@ export class CustomMetricState {
downsampler = TimeSeriesQueryAggregator.AVG;
aggregator = TimeSeriesQueryAggregator.SUM;
derivative = TimeSeriesQueryDerivative.NONE;
perNode = false;
source = "";
}

Expand Down Expand Up @@ -73,6 +76,12 @@ export class CustomMetricRow extends React.Component<CustomMetricRowProps> {
});
}

changePerNode = (selection: React.FormEvent<HTMLInputElement>) => {
this.changeState({
perNode: selection.currentTarget.checked,
});
}

deleteOption = () => {
this.props.onDelete(this.props.index);
}
Expand All @@ -81,7 +90,7 @@ export class CustomMetricRow extends React.Component<CustomMetricRowProps> {
const {
metricOptions,
nodeOptions,
rowState: { metric, downsampler, aggregator, derivative, source },
rowState: { metric, downsampler, aggregator, derivative, source, perNode },
} = this.props;

return (
Expand All @@ -107,7 +116,7 @@ export class CustomMetricRow extends React.Component<CustomMetricRowProps> {
clearable={false}
searchable={false}
value={downsampler.toString()}
options={aggregatorOptions}
options={downsamplerOptions}
onChange={this.changeDownsampler}
/>
</div>
Expand Down Expand Up @@ -148,6 +157,9 @@ export class CustomMetricRow extends React.Component<CustomMetricRowProps> {
/>
</div>
</td>
<td className="metric-table__cell">
<input type="checkbox" checked={perNode} onChange={this.changePerNode} />
</td>
<td>
<button className="metric-edit-button" onClick={this.deleteOption}>Remove</button>
</td>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,6 @@
background inherit
padding 0

&__cell
text-align center

50 changes: 38 additions & 12 deletions pkg/ui/src/views/reports/containers/customgraph/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import { PageConfig, PageConfigItem } from "src/views/shared/components/pageconf
import { CustomMetricState, CustomMetricRow } from "./customMetric";
import "./customgraph.styl";

import { NodeStatus$Properties } from "../../../../util/proto";

const axisUnitsOptions: DropdownOption[] = [
AxisUnits.Count,
AxisUnits.Bytes,
Expand Down Expand Up @@ -66,7 +68,7 @@ class CustomGraph extends React.Component<CustomGraphProps & WithRouterProps> {

return _.keys(nodeStatuses[0].metrics).map(k => {
const fullMetricName =
_.has(nodeStatuses[0].store_statuses[0].metrics, k)
isStoreMetric(nodeStatuses[0], k)
? "cr.store." + k
: "cr.node." + k;

Expand Down Expand Up @@ -148,6 +150,7 @@ class CustomGraph extends React.Component<CustomGraphProps & WithRouterProps> {
renderGraph() {
const metrics = this.currentMetrics();
const units = this.currentAxisUnits();
const { nodesSummary } = this.props;
if (_.isEmpty(metrics)) {
return (
<section className="section">
Expand All @@ -164,17 +167,35 @@ class CustomGraph extends React.Component<CustomGraphProps & WithRouterProps> {
{
metrics.map((m, i) => {
if (m.metric !== "") {
return (
<Metric
key={i}
title={m.metric}
name={m.metric}
aggregator={m.aggregator}
downsampler={m.downsampler}
derivative={m.derivative}
sources={m.source === "" ? [] : [m.source]}
/>
);
if (m.perNode) {
return _.map(nodesSummary.nodeIDs, (nodeID) => (
<Metric
key={"${i}${nodeID}"}
title={`${nodeID}: ${m.metric} (${i})`}
name={m.metric}
aggregator={m.aggregator}
downsampler={m.downsampler}
derivative={m.derivative}
sources={
isStoreMetric(nodesSummary.nodeStatuses[0], m.metric)
? _.map(nodesSummary.storeIDsByNodeID[nodeID] || [], n => n.toString())
: [nodeID]
}
/>
));
} else {
return (
<Metric
key={i}
title={`${m.metric} (${i}) `}
name={m.metric}
aggregator={m.aggregator}
downsampler={m.downsampler}
derivative={m.derivative}
sources={m.source === "" ? [] : [m.source]}
/>
);
}
}
return "";
})
Expand Down Expand Up @@ -202,6 +223,7 @@ class CustomGraph extends React.Component<CustomGraphProps & WithRouterProps> {
<td className="metric-table__header">Aggregator</td>
<td className="metric-table__header">Rate</td>
<td className="metric-table__header">Source</td>
<td className="metric-table__header">Per Node</td>
<td className="metric-table__header metric-table__header--no-title"></td>
</tr>
</thead>
Expand Down Expand Up @@ -266,3 +288,7 @@ const mapDispatchToProps = {
};

export default connect(mapStateToProps, mapDispatchToProps)(withRouter(CustomGraph));

function isStoreMetric(nodeStatus: NodeStatus$Properties, metricName: string) {
return _.has(nodeStatus.store_statuses[0].metrics, metricName);
}

0 comments on commit b96ab02

Please sign in to comment.