Skip to content

Commit

Permalink
Completes refactor of UI to v2 native (#2304)
Browse files Browse the repository at this point in the history
This is the last step where the UI no longer uses any v1 types.

What it does now is a dual-pass where at first v2 spans are collected
into events such that spans that share an ID can be in the same row.

After that, it decorates them with positional details, such as where
to put annotations or how wide the span line should be.

fixes #2217
  • Loading branch information
adriancole authored Dec 2, 2018
1 parent deddd62 commit 788ec28
Show file tree
Hide file tree
Showing 17 changed files with 220 additions and 243 deletions.
2 changes: 1 addition & 1 deletion zipkin-ui/js/component_data/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {errToStr} from '../../js/component_ui/error';
import $ from 'jquery';
import queryString from 'query-string';
import {traceSummary, traceSummariesToMustache} from '../component_ui/traceSummary';
import {treeCorrectedForClockSkew} from '../skew';
import {treeCorrectedForClockSkew} from '../component_data/skew';

const debug = false;

Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
6 changes: 3 additions & 3 deletions zipkin-ui/js/component_data/trace.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {component} from 'flightjs';
import $ from 'jquery';
import {getError} from '../../js/component_ui/error';
import {traceToMustache} from '../../js/component_ui/traceToMustache';
import {treeCorrectedForClockSkew} from '../skew';
import {getError} from '../component_ui/error';
import {traceToMustache} from '../component_ui/traceToMustache';
import {treeCorrectedForClockSkew} from '../component_data/skew';

export function toContextualLogsUrl(logsUrl, traceId) {
if (logsUrl) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {ConstantNames} from './component_ui/traceConstants';
import {ConstantNames} from './traceConstants';

// returns 'critical' if one of the spans has an error tag or currentErrorType was already critical,
// returns 'transient' if one of the spans has an ERROR annotation, else
Expand Down Expand Up @@ -34,7 +34,7 @@ function toAnnotationRow(a, localFormatted, isDerived = false) {
const res = {
isDerived,
value: ConstantNames[a.value] || a.value,
timestamp: a.timestamp,
timestamp: a.timestamp
};
if (localFormatted) res.endpoint = localFormatted;
return res;
Expand Down Expand Up @@ -262,46 +262,35 @@ function getServiceName(endpoint) {
return endpoint ? endpoint.serviceName : undefined;
}

// assumes spans are already clean
function merge(spans) {
const first = spans.shift();
// Merges the data into a single span row, which is lacking presentation information
export function newSpanRow(spansToMerge) {
const first = spansToMerge[0];
const res = {
traceId: first.traceId,
id: first.id,
errorType: getErrorType(first, 'none')
spanId: first.id,
serviceNames: [],
annotations: [],
tags: [],
errorType: 'none'
};
if (first.parentId) res.parentId = first.parentId;

if (first.name) res.name = first.name;

if (!first.shared) {
if (first.timestamp) res.timestamp = first.timestamp;
if (first.duration) res.duration = first.duration;
}

const firstServiceName = getServiceName(first.localEndpoint);
if (firstServiceName) res.serviceName = firstServiceName;
res.serviceNames = firstServiceName ? [firstServiceName] : [];
maybePushServiceName(res.serviceNames, getServiceName(first.remoteEndpoint));

res.annotations = parseAnnotationRows(first);
res.tags = parseTagRows(first);
if (first.debug) res.debug = true;

spans.forEach(next => {
let sharedTimestamp;
let sharedDuration;
spansToMerge.forEach(next => {
if (next.parentId) res.parentId = next.parentId;
if (next.name && (!res.name || next.kind === 'SERVER')) {
res.name = next.name; // prefer the server's span name
if (next.name && (!res.spanName || next.kind === 'SERVER')) {
res.spanName = next.name; // prefer the server's span name
}

// If we have 2 different timestamps. Prefer the not shared one
if (!next.shared) {
if (!res.timestamp) res.timestamp = next.timestamp;
if (!res.duration) res.duration = next.duration;
if (next.shared) { // save off any shared timestamp, it is our second choice
if (!sharedTimestamp) sharedTimestamp = next.timestamp;
if (!sharedDuration) sharedDuration = next.duration;
} else {
if (!res.timestamp && next.timestamp) res.timestamp = next.timestamp;
if (!res.duration && next.duration) res.duration = next.duration;
}

const nextServiceName = getServiceName(next.localEndpoint);
if (next.kind === 'SERVER' && nextServiceName) {
if (nextServiceName && (!res.serviceName || next.kind === 'SERVER')) {
res.serviceName = nextServiceName; // prefer the server's service name
}

Expand All @@ -315,13 +304,13 @@ function merge(spans) {

if (next.debug) res.debug = true;
});
res.annotations.sort((a, b) => a.timestamp - b.timestamp);

// timestamp is used to derive positional data later
if (!res.timestamp && sharedTimestamp) res.timestamp = sharedTimestamp;
// duration is used for deriving data, and also for the zoom function
if (!res.duration && sharedDuration) res.duration = sharedDuration;

res.serviceNames.sort();
res.annotations.sort((a, b) => a.timestamp - b.timestamp);
return res;
}

module.exports.SPAN_V1 = {
merge(spans) {
return merge(spans);
}
};
9 changes: 6 additions & 3 deletions zipkin-ui/js/component_ui/traceSummary.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
// eslint-disable no-nested-ternary
import _ from 'lodash';
import moment from 'moment';
import {getErrorType} from '../spanConverter';
import {getErrorType} from './spanRow';

export function addStartEndTimestamps(span, timestamps) {
// To ensure data doesn't scroll off the screen, we need all timestamps, not just
// client/server ones.
export function addTimestamps(span, timestamps) {
span.annotations.forEach(a => timestamps.push(a.timestamp));
if (!span.timestamp) return;
timestamps.push(span.timestamp);
if (!span.duration) return;
Expand Down Expand Up @@ -56,7 +59,7 @@ export function traceSummary(root) {
spanCount++;
traceId = span.traceId;
errorType = getErrorType(span, errorType);
addStartEndTimestamps(span, timestamps);
addTimestamps(span, timestamps);
addServiceNameTimestampDuration(span, groupedTimestamps);
});

Expand Down
105 changes: 49 additions & 56 deletions zipkin-ui/js/component_ui/traceToMustache.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {addStartEndTimestamps, getMaxDuration, mkDurationStr} from './traceSummary';
import {SPAN_V1} from '../spanConverter';
import {addTimestamps, getMaxDuration, mkDurationStr} from './traceSummary';
import {newSpanRow} from './spanRow';

function incrementEntry(dict, key) {
if (dict[key]) {
Expand All @@ -13,13 +13,46 @@ function incrementEntry(dict, key) {
// as that is used for positioning spans later.
function getTraceTimestampAndDuration(root) {
const timestamps = [];
root.traverse(span => addStartEndTimestamps(span, timestamps));
root.traverse(span => addTimestamps(span, timestamps));
return {
traceTimestamp: timestamps[0] || 0,
traceDuration: getMaxDuration(timestamps)
timestamp: timestamps[0] || 0,
duration: getMaxDuration(timestamps)
};
}

function addLayoutDetails(
spanRow, traceTimestamp, traceDuration, depth, childIds
) { /* eslint-disable no-param-reassign */
spanRow.childIds = childIds;
spanRow.depth = (depth + 1) * 5;
spanRow.depthClass = (depth - 1) % 6;

// Add the correct width and duration string for the span
if (spanRow.duration) { // implies traceDuration, as trace duration is derived from spans
const width = traceDuration ? spanRow.duration / traceDuration * 100 : 0;
spanRow.width = width < 0.1 ? 0.1 : width;
spanRow.durationStr = mkDurationStr(spanRow.duration); // bubble over the span in trace view
} else {
spanRow.width = 0.1;
}

if (traceDuration) {
// position the span at the correct offset in the trace diagram.
spanRow.left = ((spanRow.timestamp - traceTimestamp) / traceDuration * 100);

// position each annotation at the offset in the trace diagram.
spanRow.annotations.forEach(a => { /* eslint-disable no-param-reassign */
// left offset here is from the span
a.left = spanRow.duration ? ((a.timestamp - spanRow.timestamp) / spanRow.duration * 100) : 0;
// relative time is for the trace itself
a.relativeTime = mkDurationStr(a.timestamp - traceTimestamp);
a.width = 8; // size of the dot
});
} else {
spanRow.left = 0;
}
}

export function traceToMustache(root, logsUrl) {
const serviceNameToCount = {};
const queue = root.queueRootMostSpans();
Expand All @@ -29,8 +62,8 @@ export function traceToMustache(root, logsUrl) {
spans: []
};

const {traceTimestamp, traceDuration} = getTraceTimestampAndDuration(root);
if (!traceTimestamp) throw new Error(`Trace ${modelview.traceId} is missing a timestamp`);
const {timestamp, duration} = getTraceTimestampAndDuration(root);
if (!timestamp) throw new Error(`Trace ${modelview.traceId} is missing a timestamp`);

while (queue.length > 0) {
let current = queue.shift();
Expand All @@ -54,63 +87,23 @@ export function traceToMustache(root, logsUrl) {

// The mustache template expects one row per span ID. To get the correct depth class, we need to
// count distinct span IDs above us.
let spanRowDepth = 1;
let depth = 1;
while (current.parent && current.parent.span) {
if (current.parent.span.id !== current.span.id) spanRowDepth++;
if (current.parent.span.id !== current.span.id) depth++;
current = current.parent;
}
// If we are the deepest span, mark the trace accordingly
if (spanRowDepth > modelview.depth) modelview.depth = spanRowDepth;

const span = SPAN_V1.merge(spansToMerge);
// TODO: merge the remaining "v1" type logic here so we don't have the confusion of a
// double transformation
const spanStartTs = span.timestamp || traceTimestamp;
const spanDuration = span.duration || 0;
const uiSpan = {
spanId: span.id,
depth: (spanRowDepth + 1) * 5,
depthClass: (spanRowDepth - 1) % 6,
annotations: span.annotations.map((a) => ({
...a,
left: spanDuration ? (a.timestamp - spanStartTs) / spanDuration * 100 : 0,
// TODO: do we really want relative time of annotations to be relative to the trace?
relativeTime: mkDurationStr(a.timestamp - traceTimestamp),
width: 8
})),
tags: span.tags,
serviceNames: span.serviceNames,
childIds,
errorType: span.errorType
};

// positions the span at the offset in the trace diagram.
if (traceDuration) {
uiSpan.left = parseFloat(spanStartTs - traceTimestamp) / parseFloat(traceDuration) * 100;
} else {
uiSpan.left = 0;
}

// Optionally add fields instead of defaulting to empty string
if (span.name) uiSpan.spanName = span.name;
if (spanDuration) {
const width = traceDuration ? spanDuration / traceDuration * 100 : 0;
uiSpan.width = width < 0.1 ? 0.1 : width;
uiSpan.duration = spanDuration; // used in zoom
uiSpan.durationStr = mkDurationStr(spanDuration); // bubble over the span in trace view
} else {
uiSpan.width = 0.1;
}
if (span.serviceName) uiSpan.serviceName = span.serviceName;
if (span.parentId) uiSpan.parentId = span.parentId;
if (depth > modelview.depth) modelview.depth = depth;

const spanRow = newSpanRow(spansToMerge);
addLayoutDetails(spanRow, timestamp, duration, depth, childIds);
// NOTE: This will increment both the local and remote service name
//
// TODO: We should only do this if it is a leaf span and a client or producer. If we are at the
// bottom of the tree, it can be helpful to count also against a remote uninstrumented service.
uiSpan.serviceNames.forEach(serviceName => incrementEntry(serviceNameToCount, serviceName));
spanRow.serviceNames.forEach(serviceName => incrementEntry(serviceNameToCount, serviceName));

modelview.spans.push(uiSpan);
modelview.spans.push(spanRow);
}

modelview.serviceNameAndSpanCounts = Object.keys(serviceNameToCount).sort().map(serviceName =>
Expand All @@ -120,10 +113,10 @@ export function traceToMustache(root, logsUrl) {
// the zoom feature needs backups and timeMarkers regardless of if there is a trace duration
modelview.spansBackup = modelview.spans;
modelview.timeMarkers = [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]
.map((p, index) => ({index, time: mkDurationStr(traceDuration * p)}));
.map((p, index) => ({index, time: mkDurationStr(duration * p)}));
modelview.timeMarkersBackup = modelview.timeMarkers;

if (traceDuration) modelview.durationStr = mkDurationStr(traceDuration);
if (duration) modelview.durationStr = mkDurationStr(duration);
if (logsUrl) modelview.logsUrl = logsUrl;

return modelview;
Expand Down
4 changes: 2 additions & 2 deletions zipkin-ui/js/component_ui/uploadTrace.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {component} from 'flightjs';
import FullPageSpinnerUI from '../component_ui/fullPageSpinner';
import {traceToMustache} from '../../js/component_ui/traceToMustache';
import {treeCorrectedForClockSkew} from '../skew';
import {traceToMustache} from '../component_ui/traceToMustache';
import {treeCorrectedForClockSkew} from '../component_data/skew';

export function ensureV2(trace) {
if (!Array.isArray(trace) || trace.length === 0) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const {ipsMatch, getClockSkew, treeCorrectedForClockSkew} = require('../js/skew');
const {SpanNode, SpanNodeBuilder} = require('../js/spanNode');
import {clean} from '../js/spanCleaner';
import {frontend, backend, skewedTrace} from './component_ui/traceTestHelpers';
const {ipsMatch, getClockSkew, treeCorrectedForClockSkew} = require('../../js/component_data/skew');
const {SpanNode, SpanNodeBuilder} = require('../../js/component_data/spanNode');
import {clean} from '../../js/component_data/spanCleaner';
import {frontend, backend, skewedTrace} from '../component_ui/traceTestHelpers';

const should = require('chai').should();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const {cleanupComparator, merge, mergeV2ById} = require('../js/spanCleaner');
const {cleanupComparator, merge, mergeV2ById} = require('../../js/component_data/spanCleaner');

// endpoints from zipkin2.TestObjects
const frontend = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const {SpanNode, SpanNodeBuilder} = require('../js/spanNode');
import {clean, mergeV2ById} from '../js/spanCleaner';
const {SpanNode, SpanNodeBuilder} = require('../../js/component_data/spanNode');
import {clean, mergeV2ById} from '../../js/component_data/spanCleaner';
const should = require('chai').should();

// originally zipkin2.internal.SpanNodeTest.java
Expand Down
Loading

0 comments on commit 788ec28

Please sign in to comment.