Skip to content

Commit

Permalink
Add ability to compare traces on span durations
Browse files Browse the repository at this point in the history
Add a feature to deemphasize structural differences and instead base
the comparison on the average duration of spans for a node. Feature is
hidden by default and enabled by adding "duration" as a query param.

Signed-off-by: Joe Farro <joef@uber.com>
  • Loading branch information
tiffon committed Jul 30, 2018
1 parent b145843 commit 1d28725
Show file tree
Hide file tree
Showing 21 changed files with 904 additions and 95 deletions.
4 changes: 3 additions & 1 deletion packages/jaeger-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@
"combokeys": "^3.0.0",
"cytoscape": "^3.2.1",
"cytoscape-dagre": "^2.0.0",
"d3-scale": "^1.0.6",
"d3-color": "^1.2.0",
"d3-interpolate": "^1.2.0",
"d3-scale": "^2.1.0",
"dagre": "^0.7.4",
"deep-freeze": "^0.0.1",
"fuzzy": "^0.1.3",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import type { FetchedTrace } from '../../../types';
import './DiffSelection.css';

type Props = {
metric: ?string,
toggleComparison: (string, boolean) => void,
traces: FetchedTrace[],
};
Expand All @@ -37,9 +38,9 @@ export default class DiffSelection extends React.PureComponent<Props> {
props: Props;

render() {
const { toggleComparison, traces } = this.props;
const { metric, toggleComparison, traces } = this.props;
const cohort = traces.filter(ft => ft.state !== fetchedState.ERROR).map(ft => ft.id);
const compareHref = cohort.length > 1 ? getUrl({ cohort }) : null;
const compareHref = cohort.length > 1 ? getUrl({ cohort, metric }) : null;
const compareBtn = (
<Button className="ub-right" disabled={cohort.length < 2} type="primary">
Compare Traces
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ type SearchResultsProps = {
cohortAddTrace: string => void,
cohortRemoveTrace: string => void,
diffCohort: FetchedTrace[],
diffMetric: ?string,
goToTrace: string => void,
loading: boolean,
maxTraceDuration: number,
Expand Down Expand Up @@ -85,8 +86,10 @@ export default class SearchResults extends React.PureComponent<SearchResultsProp
};

render() {
const { loading, diffCohort, skipMessage, traces } = this.props;
const diffSelection = <DiffSelection toggleComparison={this.toggleComparison} traces={diffCohort} />;
const { loading, diffCohort, diffMetric, skipMessage, traces } = this.props;
const diffSelection = (
<DiffSelection toggleComparison={this.toggleComparison} traces={diffCohort} metric={diffMetric} />
);
if (loading) {
return (
<React.Fragment>
Expand Down
13 changes: 10 additions & 3 deletions packages/jaeger-ui/src/components/SearchTracePage/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export class SearchTracePageImpl extends Component {
cohortAddTrace,
cohortRemoveTrace,
diffCohort,
diffMetric,
errors,
isHomepage,
loadingServices,
Expand Down Expand Up @@ -103,6 +104,7 @@ export class SearchTracePageImpl extends Component {
maxTraceDuration={maxTraceDuration}
cohortRemoveTrace={cohortRemoveTrace}
diffCohort={diffCohort}
diffMetric={diffMetric}
skipMessage={isHomepage}
traces={traceResults}
/>
Expand All @@ -127,6 +129,7 @@ SearchTracePageImpl.propTypes = {
// eslint-disable-next-line react/forbid-prop-types
traceResults: PropTypes.array,
diffCohort: PropTypes.array,
diffMetric: PropTypes.string,
cohortAddTrace: PropTypes.func,
cohortRemoveTrace: PropTypes.func,
maxTraceDuration: PropTypes.number,
Expand Down Expand Up @@ -167,8 +170,11 @@ const stateTraceXformer = getLastXformCacher(stateTrace => {

const stateTraceDiffXformer = getLastXformCacher((stateTrace, stateTraceDiff) => {
const { traces } = stateTrace;
const { cohort } = stateTraceDiff;
return cohort.map(id => traces[id] || { id });
const { cohort, metric } = stateTraceDiff;
return {
metric,
cohort: cohort.map(id => traces[id] || { id }),
};
});

const sortedTracesXformer = getLastXformCacher((traces, sortBy) => {
Expand Down Expand Up @@ -198,7 +204,7 @@ export function mapStateToProps(state) {
const query = queryString.parse(state.router.location.search);
const isHomepage = !Object.keys(query).length;
const { traces, maxDuration, traceError, loadingTraces } = stateTraceXformer(state.trace);
const diffCohort = stateTraceDiffXformer(state.trace, state.traceDiff);
const { cohort: diffCohort, metric: diffMetric } = stateTraceDiffXformer(state.trace, state.traceDiff);
const { loadingServices, services, serviceError } = stateServicesXformer(state.services);
const errors = [];
if (traceError) {
Expand All @@ -211,6 +217,7 @@ export function mapStateToProps(state) {
const traceResults = sortedTracesXformer(traces, sortBy);
return {
diffCohort,
diffMetric,
isHomepage,
loadingServices,
loadingTraces,
Expand Down
62 changes: 53 additions & 9 deletions packages/jaeger-ui/src/components/TraceDiff/TraceDiff.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { actions as diffActions } from './duck';
import { getUrl } from './url';
import TraceDiffGraph from './TraceDiffGraph';
import TraceDiffHeader from './TraceDiffHeader';
import TraceDiffSettings from './TraceDiffSettings';
import * as jaegerApiActions from '../../actions/jaeger-api';
import { TOP_NAV_HEIGHT } from '../../constants';

Expand All @@ -40,6 +41,10 @@ type Props = {
fetchMultipleTraces: (string[]) => void,
forceState: TraceDiffState => void,
history: RouterHistory,
metric: ?string,
scale: ?string,
scaleOn: ?string,
showSettings: boolean,
tracesData: Map<string, ?FetchedTrace>,
traceDiffState: TraceDiffState,
};
Expand All @@ -49,8 +54,8 @@ type State = {
};

function syncStates(urlSt, reduxSt, forceState) {
const { a: urlA, b: urlB } = urlSt;
const { a: reduxA, b: reduxB } = reduxSt;
const { a: urlA, b: urlB, metric: metricA, scale: scaleA, scaleOn: scaleOnA } = urlSt;
const { a: reduxA, b: reduxB, metric: metricB, scale: scaleB, scaleOn: scaleOnB } = reduxSt;
if (urlA !== reduxA || urlB !== reduxB) {
forceState(urlSt);
return;
Expand All @@ -64,6 +69,17 @@ function syncStates(urlSt, reduxSt, forceState) {
const needSync = Array.from(urlCohort).some(id => !reduxCohort.has(id));
if (needSync) {
forceState(urlSt);
return;
}
if (metricA !== metricB) {
forceState(urlSt);
return;
}
if (scaleA !== scaleB) {
forceState(urlSt);
}
if (scaleOnA !== scaleOnB) {
forceState(urlSt);
}
}

Expand Down Expand Up @@ -105,8 +121,19 @@ export class TraceDiffImpl extends React.PureComponent<Props, State> {
}

processProps() {
const { a, b, cohort, fetchMultipleTraces, forceState, tracesData, traceDiffState } = this.props;
syncStates({ a, b, cohort }, traceDiffState, forceState);
const {
a,
b,
cohort,
metric,
fetchMultipleTraces,
forceState,
scale,
scaleOn,
tracesData,
traceDiffState,
} = this.props;
syncStates({ a, b, cohort, metric, scale, scaleOn }, traceDiffState, forceState);
const cohortData = cohort.map(id => tracesData.get(id) || { id, state: null });
const needForDiffs = cohortData.filter(ft => ft.state == null).map(ft => ft.id);
if (needForDiffs.length) {
Expand All @@ -116,8 +143,8 @@ export class TraceDiffImpl extends React.PureComponent<Props, State> {

diffSetUrl(change: { newA?: ?string, newB?: ?string }) {
const { newA, newB } = change;
const { a, b, cohort, history } = this.props;
const url = getUrl({ a: newA || a, b: newB || b, cohort });
const { a, b, cohort, metric, history, scale, scaleOn } = this.props;
const url = getUrl({ a: newA || a, b: newB || b, cohort, metric, scale, scaleOn });
history.push(url);
}

Expand All @@ -132,7 +159,7 @@ export class TraceDiffImpl extends React.PureComponent<Props, State> {
};

render() {
const { a, b, cohort, tracesData } = this.props;
const { a, b, cohort, history, metric, scale, scaleOn, showSettings, tracesData } = this.props;
const { graphTopOffset } = this.state;
const traceA = a ? tracesData.get(a) || { id: a } : null;
const traceB = b ? tracesData.get(b) || { id: b } : null;
Expand All @@ -150,8 +177,19 @@ export class TraceDiffImpl extends React.PureComponent<Props, State> {
/>
</div>
<div key="graph" className="TraceDiff--graphWrapper" style={{ top: graphTopOffset }}>
<TraceDiffGraph a={traceA} b={traceB} />
<TraceDiffGraph a={traceA} b={traceB} metric={metric} scale={scale} scaleOn={scaleOn} />
</div>
{showSettings && (
<TraceDiffSettings
a={a}
b={b}
cohort={cohort}
metric={metric}
scale={scale}
scaleOn={scaleOn}
pushUrl={history.push}
/>
)}
</React.Fragment>
);
}
Expand All @@ -160,7 +198,9 @@ export class TraceDiffImpl extends React.PureComponent<Props, State> {
// TODO(joe): simplify but do not invalidate the URL
export function mapStateToProps(state: ReduxState, ownProps: { match: Match }) {
const { a, b } = ownProps.match.params;
const { cohort: origCohort = [] } = queryString.parse(state.router.location.search);
const { duration, cohort: origCohort = [], metric, scale, scaleOn } = queryString.parse(
state.router.location.search
);
const fullCohortSet: Set<string> = new Set([].concat(a, b, origCohort).filter(Boolean));
const cohort: string[] = Array.from(fullCohortSet);
const { traces } = state.trace;
Expand All @@ -170,7 +210,11 @@ export function mapStateToProps(state: ReduxState, ownProps: { match: Match }) {
a,
b,
cohort,
metric,
scale,
scaleOn,
tracesData,
showSettings: duration !== undefined || metric !== undefined,
traceDiffState: state.traceDiff,
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,28 @@
import * as React from 'react';
import { DirectedGraph, LayoutManager } from '@jaegertracing/plexus';

import drawNode from './drawNode';
// import drawNode from './drawNode';
import { getNodeDrawer } from './drawNode';
import getColorInterpolator from './getColorInterpolator';
import ErrorMessage from '../../common/ErrorMessage';
import LoadingIndicator from '../../common/LoadingIndicator';
import { fetchedState } from '../../../constants';
import convPlexus from '../../../model/trace-dag/convPlexus';
import TraceDag from '../../../model/trace-dag/TraceDag';

// import type { DiffMembers } from '../../../model/trace-dag/DagNode';
// import type { PVertex } from '../../../model/trace-dag/types';
// import type { TraceDiffState } from '../../../types/trace-diff';
import type { FetchedTrace } from '../../../types';

import './TraceDiffGraph.css';

type Props = {
a: ?FetchedTrace,
b: ?FetchedTrace,
metric: ?string,
scale: ?string,
scaleOn: ?string,
};

const { classNameIsSmall } = DirectedGraph.propsFactories;
Expand All @@ -49,18 +57,24 @@ export default class TraceDiffGraph extends React.PureComponent<Props> {
props: Props;

layoutManager: LayoutManager;
// nodeFactory: (vertex: PVertex<DiffMembers>) => React.Node;

constructor(props: Props) {
super(props);
this.layoutManager = new LayoutManager({ useDotEdges: true, splines: 'polyline' });
}

// componentWillMount() {
// const { metric } = this.props;
// // this.nodeFactory = getNodeDrawer(metric);
// }

componentWillUnmount() {
this.layoutManager.stopAndRelease();
}

render() {
const { a, b } = this.props;
const { a, b, metric, scale, scaleOn } = this.props;
if (!a || !b) {
return <h1 className="u-mt-vast u-tx-muted ub-tx-center">At least two Traces are needed</h1>;
}
Expand Down Expand Up @@ -95,8 +109,9 @@ export default class TraceDiffGraph extends React.PureComponent<Props> {
const aTraceDag = TraceDag.newFromTrace(aData);
const bTraceDag = TraceDag.newFromTrace(bData);
const diffDag = TraceDag.diff(aTraceDag, bTraceDag);
const getColor = getColorInterpolator(metric, scale, scaleOn, diffDag.nodesMap);
const { edges, vertices } = convPlexus(diffDag.nodesMap);

const nodeFactory = getNodeDrawer(metric, scaleOn, getColor);
return (
<div className="TraceDiffGraph--graphWrapper">
<DirectedGraph
Expand All @@ -106,7 +121,7 @@ export default class TraceDiffGraph extends React.PureComponent<Props> {
className="TraceDiffGraph--dag"
minimapClassName="TraceDiffGraph--miniMap"
layoutManager={this.layoutManager}
getNodeLabel={drawNode}
getNodeLabel={nodeFactory}
setOnRoot={classNameIsSmall}
setOnEdgesContainer={setOnEdgesContainer}
edges={edges}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// @flow

// Copyright (c) 2018 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import DagNode from '../../../model/trace-dag/DagNode';

import type { DiffMembers } from '../../../model/trace-dag/DagNode';
import type { DenseSpan, NodeID } from '../../../model/trace-dag/types';

type AvgDurations = {
a: ?number,
b: ?number,
};

const avgDurationsCache: WeakMap<DiffMembers, AvgDurations> = new WeakMap();

// export function getAvgDuration(denseSpans: ?DenseSpan[]) {
function getAvgDuration(denseSpans: ?(DenseSpan[])) {
if (!denseSpans) {
return null;
}
let sum = 0;
for (let i = 0; i < denseSpans.length; i++) {
sum += denseSpans[i].span.duration;
}
return sum / denseSpans.length;
}

export function getAvgDurations(diff: DiffMembers) {
const cached = avgDurationsCache.get(diff);
if (cached) {
return cached;
}
const a = getAvgDuration(diff.a && diff.a.members);
const b = getAvgDuration(diff.b && diff.b.members);
const rv = { a, b };
avgDurationsCache.set(diff, rv);
return rv;
}

export function getRelativeDifference(a: number, b: number) {
return (a - b) / Math.max(a, b) * 100;
}

function getDifference(a: number, b: number) {
return a - b;
}

export function getAvgDurationDiffDomain(scaleOn: ?string, nodesMap: Map<NodeID, DagNode>) {
let maxRelDiff = 0;
const getDiff = scaleOn === 'relative' ? getRelativeDifference : getDifference;
const keys = [...nodesMap.keys()];
for (let i = 0; i < keys.length; i++) {
const id = keys[i];
const dagNode = nodesMap.get(id);
if (!dagNode) {
continue;
}
const { a, b } = getAvgDurations(dagNode.data);
if (a == null || b == null) {
continue;
}
const relDiff = Math.abs(getDiff(a, b));
if (relDiff > maxRelDiff) {
maxRelDiff = relDiff;
}
}
return [-maxRelDiff, 0, maxRelDiff];
}
Loading

0 comments on commit 1d28725

Please sign in to comment.