Skip to content

Commit

Permalink
Adds "View Saved Trace" screen (openzipkin#1884)
Browse files Browse the repository at this point in the history
This is an alternative to saving on the backend or via a proxy

Closes openzipkin#1747
  • Loading branch information
Logic-32 authored and abesto committed Sep 10, 2019
1 parent e69d489 commit 9f7976a
Show file tree
Hide file tree
Showing 11 changed files with 792 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ public ResponseEntity<?> serveIndex() throws IOException {
// If the path is a a file w/an extension, treat normally.
// Otherwise instead of returning 404, forward to the index.
// See https://github.com/twitter/finatra/blob/458c6b639c3afb4e29873d123125eeeb2b02e2cd/http/src/main/scala/com/twitter/finatra/http/response/ResponseBuilder.scala#L321
@RequestMapping(value = {"/zipkin/", "/zipkin/traces/{id}", "/zipkin/dependency"}, method = GET)
@RequestMapping(value = {"/zipkin/", "/zipkin/traces/{id}", "/zipkin/dependency", "/zipkin/traceViewer"}, method = GET)
public ModelAndView forwardUiEndpoints() {
return new ModelAndView("forward:/zipkin/index.html");
}
Expand Down
73 changes: 73 additions & 0 deletions zipkin-ui/js/component_ui/uploadTrace.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import {component} from 'flightjs';
import FullPageSpinnerUI from '../component_ui/fullPageSpinner';
import traceToMustache from '../../js/component_ui/traceToMustache';
import {SPAN_V1} from '../spanConverter';

function rootToFrontComparator(span1/* , span2*/) {
return span1.parentId === undefined ? -1 : 0;
}

function sort(trace) {
if (trace != null) {
trace.sort(rootToFrontComparator);
}
}

function ensureV1(trace) {
if (trace == null || trace.length === 0
|| (trace[0].localEndpoint === undefined && trace[0].remoteEndpoint === undefined)) {
return trace;
}

const newTrace = [];
for (let i = 0; i < trace.length; i++) {
newTrace.push(SPAN_V1.convert(trace[i]));
}

return newTrace;
}

export default component(function uploadTrace() {
this.doUpload = function() {
const files = this.node.files;
if (files.length === 0) {
return;
}

const reader = new FileReader();
reader.onload = evt => {
let model;
try {
let trace = JSON.parse(evt.target.result);
trace = ensureV1(trace);
sort(trace);

const modelview = traceToMustache(trace);
model = {modelview, trace};
} catch (e) {
this.trigger('uiServerError',
{desc: 'Cannot parse file', message: e});
throw e;
} finally {
this.trigger(document, 'uiHideFullPageSpinner');
}

this.trigger(document, 'traceViewerPageModelView', model);
};

reader.onerror = evt => {
this.trigger(document, 'uiHideFullPageSpinner');
this.trigger('uiServerError',
{desc: 'Cannot load file', message: `${evt.target.error.name}`});
};

this.trigger(document, 'uiShowFullPageSpinner');
setTimeout(() => reader.readAsText(files[0]), 0);
};

this.after('initialize', function() {
this.on('change', this.doUpload);
FullPageSpinnerUI.teardownAll();
FullPageSpinnerUI.attachTo('#fullPageSpinner');
});
});
2 changes: 2 additions & 0 deletions zipkin-ui/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {compose, registry, advice, debug} from 'flightjs';
import crossroads from 'crossroads';
import initializeDefault from './page/default';
import initializeTrace from './page/trace';
import initializeTraceViewer from './page/traceViewer';
import initializeDependency from './page/dependency';
import CommonUI from './page/common';
import loadConfig from './config';
Expand All @@ -21,6 +22,7 @@ loadConfig().then(config => {

crossroads.addRoute(contextRoot, () => initializeDefault(config));
crossroads.addRoute(`${contextRoot}traces/{id}`, traceId => initializeTrace(traceId, config));
crossroads.addRoute(`${contextRoot}traceViewer`, () => initializeTraceViewer(config));
crossroads.addRoute(`${contextRoot}dependency`, () => initializeDependency(config));
crossroads.parse(window.location.pathname);
}, e => {
Expand Down
75 changes: 75 additions & 0 deletions zipkin-ui/js/page/traceViewer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import {component} from 'flightjs';
import $ from 'jquery';
import FilterAllServicesUI from '../component_ui/filterAllServices';
import JsonPanelUI from '../component_ui/jsonPanel';
import ServiceFilterSearchUI from '../component_ui/serviceFilterSearch';
import SpanPanelUI from '../component_ui/spanPanel';
import TraceUI from '../component_ui/trace';
import FilterLabelUI from '../component_ui/filterLabel';
import ZoomOut from '../component_ui/zoomOutSpans';
import UploadTraceUI from '../component_ui/uploadTrace';
import {traceViewerTemplate} from '../templates';
import {contextRoot} from '../publicPath';

const TraceViewerPageComponent = component(function TraceViewerPage() {
this.render = function(model) {
try {
this.$node.html(traceViewerTemplate({
contextRoot,
...model
}));
} catch (e) {
this.trigger('uiServerError',
{desc: 'Failed to render template', message: e.message});
}

UploadTraceUI.attachTo('#traceFile');
};

this.attach = function() {
FilterAllServicesUI.attachTo('#filterAllServices', {
totalServices: $('.trace-details.services span').length
});
JsonPanelUI.attachTo('#jsonPanel');
ServiceFilterSearchUI.attachTo('#serviceFilterSearch');
SpanPanelUI.attachTo('#spanPanel');
TraceUI.attachTo('#trace-container');
FilterLabelUI.attachTo('.service-filter-label');
ZoomOut.attachTo('#zoomOutSpans');
};

this.teardown = function() {
ZoomOut.teardownAll();
FilterLabelUI.teardownAll();
TraceUI.teardownAll();
SpanPanelUI.teardownAll();
ServiceFilterSearchUI.teardownAll();
JsonPanelUI.teardownAll();
FilterAllServicesUI.teardownAll();
};

this.after('initialize', function() {
window.document.title = 'Zipkin - Trace Viewer';

this.render({});

this.on(document, 'traceViewerPageModelView', function(ev, data) {
this.teardown();
this.render(data.modelview);
this.attach();

this.$node.find('#traceJsonLink').click(e => {
e.preventDefault();
this.trigger('uiRequestJsonPanel', {title: `Trace ${data.modelview.traceId}`,
obj: data.trace,
link: `${contextRoot}traceViewer`});
});

$('.annotation:not(.core)').tooltip({placement: 'left'});
});
});
});

export default function initializeTrace(config) {
TraceViewerPageComponent.attachTo('.content', {config});
}
114 changes: 114 additions & 0 deletions zipkin-ui/js/spanConverter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
function toV1Endpoint(endpoint) {
if (endpoint === undefined) {
return undefined;
}
const res = {
serviceName: endpoint.serviceName || '', // undefined is not allowed in v1
};
if (endpoint.ipv4) {
res.ipv4 = endpoint.ipv4;
}
if (endpoint.port) {
res.port = endpoint.port;
}
return res;
}

function toV1Annotation(ann, endpoint) {
return {
value: ann.value,
timestamp: ann.timestamp,
endpoint
};
}

// Copied from https://github.com/openzipkin/zipkin-js/blob/8018e441d01804b02d0d217f10cd82759e71e02a/packages/zipkin/src/jsonEncoder.js#L25
// Modified to correct assumption that 'annotations' always exist and ensure
// that 'beginAnnotation' comes first timestamp/duration should always be copied over
function convertV1(span) {
const res = {
traceId: span.traceId
};
if (span.parentId) { // instead of writing "parentId": NULL
res.parentId = span.parentId;
}
res.id = span.id;
res.name = span.name || ''; // undefined is not allowed in v1
res.timestamp = span.timestamp;
res.duration = span.duration;

const jsonEndpoint = toV1Endpoint(span.localEndpoint);

let beginAnnotation;
let endAnnotation;
let addressKey;
switch (span.kind) {
case 'CLIENT':
beginAnnotation = span.timestamp ? 'cs' : undefined;
endAnnotation = 'cr';
addressKey = 'sa';
break;
case 'SERVER':
beginAnnotation = span.timestamp ? 'sr' : undefined;
endAnnotation = 'ss';
addressKey = 'ca';
break;
default:
}

if (span.annotations !== undefined && span.annotations.length > 0
|| beginAnnotation) { // don't write empty array
res.annotations = [];
}

if (beginAnnotation) {
res.annotations.push({
value: beginAnnotation,
timestamp: span.timestamp,
endpoint: jsonEndpoint
});
}

if (span.annotations !== undefined && span.annotations.length > 0) {
span.annotations.forEach((ann) =>
res.annotations.push(toV1Annotation(ann, jsonEndpoint))
);
}

if (beginAnnotation && span.duration) {
res.annotations.push({
value: endAnnotation,
timestamp: span.timestamp + span.duration,
endpoint: jsonEndpoint
});
}

const keys = Object.keys(span.tags || {});
if (keys.length > 0 || span.remoteEndpoint) { // don't write empty array
res.binaryAnnotations = keys.map(key => ({
key,
value: span.tags[key],
endpoint: jsonEndpoint
}));
}

if (span.remoteEndpoint) {
const address = {
key: addressKey,
value: true,
endpoint: toV1Endpoint(span.remoteEndpoint)
};
res.binaryAnnotations.push(address);
}

if (span.debug) { // instead of writing "debug": false
res.debug = true;
}
return res;
}

module.exports.SPAN_V1 = {
convert(span) {
return convertV1(span);
}
};
1 change: 1 addition & 0 deletions zipkin-ui/js/templates.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export const searchDisabled = require('../templates/searchDisabled.mustache');
export const layoutTemplate = require('../templates/layout.mustache');
export const dependenciesTemplate = require('../templates/dependency.mustache');
export const traceTemplate = require('../templates/trace.mustache');
export const traceViewerTemplate = require('../templates/traceViewer.mustache');
3 changes: 2 additions & 1 deletion zipkin-ui/static/nav.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
nav.inves = Investigate system behavior
nav.find = Find a trace
nav.dep = Dependencies
nav.search = Go to trace
nav.search = Go to trace
nav.viewSaved = View Saved Trace
1 change: 1 addition & 0 deletions zipkin-ui/static/trace.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
trace.duration = Duration:
trace.file = Trace JSON:
trace.service = Services:
trace.depth = Depth:
trace.spans = Total Spans:
Expand Down
7 changes: 4 additions & 3 deletions zipkin-ui/templates/layout.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,18 @@
<div class="navbar-left">
<ul class="nav navbar-nav">
<li data-route="index"><a href="{{contextRoot}}" data-i18n="nav.find">Find a trace</a></li>
<li data-route="traceViewer"><a href="{{contextRoot}}traceViewer" data-i18n="nav.viewSaved">View Saved Trace</a></li>
<li data-route="dependency"><a href="{{contextRoot}}dependency/" data-i18n="nav.dep">Dependencies</a></li>
</ul>
</div>
<div class="navbar-right" style="width: 50%">
<div class="navbar-right" style="width: 30%">
<div class="navbar-right" style='padding-left: 15px'>
<p class="navbar-text muted" id="environment"></p>
</div>
<form id="traceIdQueryForm" class="form-inline" role="form">
<div class="navbar-right" style="width: 25%; float: right">
<div class="navbar-right" style="float: right">
<div class="row well-sm clearfix">
<input type="text" class="form-control input-sm" id="traceIdQuery" name="traceIdQuery" style="width: 100%" placeholder='Go to trace' data-i18n='nav.search'>
<input type="text" class="form-control input-sm" id="traceIdQuery" name="traceIdQuery" placeholder='Go to trace' data-i18n='nav.search'>
</div>
</div>
</form>
Expand Down
Loading

0 comments on commit 9f7976a

Please sign in to comment.