Skip to content

Commit 520ebc1

Browse files
zeagordllinder
authored andcommitted
Adds UI for Links with trace ids (openzipkin#2128)
* Initial pass for adding multiple trace lookup * Polishes in-memory impl and adds integration tests * Adds getByTraceIds to mysql * Adds getByTraceIds to elasticsearch * Adds getByTraceIds to cassandra * Adds getByTraceIds to legacy cassandra * Fixes compile break on traced storage component * Adds trace IDs to dependency links TODO: make this conditional, as existing dependency linker jobs won't need IDs and keeping them might make spark jobs move more data than they currently need. * Adds Parent-Child Traces view * Adds Traces view for the dependency link * UI-Integration for the link dependency * Adds UI integration and v2 endpoint for filtering links
1 parent 2789049 commit 520ebc1

File tree

9 files changed

+252
-31
lines changed

9 files changed

+252
-31
lines changed

zipkin-server/src/main/java/zipkin/server/internal/ZipkinQueryApiV2.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,10 @@ public String getTraces(
102102
@Nullable @RequestParam(value = "maxDuration", required = false) Long maxDuration,
103103
@Nullable @RequestParam(value = "endTs", required = false) Long endTs,
104104
@Nullable @RequestParam(value = "lookback", required = false) Long lookback,
105-
@RequestParam(value = "limit", defaultValue = "10") int limit)
105+
@RequestParam(value = "limit", defaultValue = "10") int limit,
106+
@Nullable @RequestParam(value = "parentService", required = false) String parentServiceName,
107+
@Nullable @RequestParam(value = "childService", required = false) String childServiceName,
108+
@RequestParam(value = "fetchErrors", required = false) boolean fetchErrors)
106109
throws IOException {
107110
QueryRequest queryRequest =
108111
QueryRequest.newBuilder()
@@ -114,6 +117,9 @@ public String getTraces(
114117
.endTs(endTs != null ? endTs : System.currentTimeMillis())
115118
.lookback(lookback != null ? lookback : defaultLookback)
116119
.limit(limit)
120+
.parentServiceName(parentServiceName)
121+
.childServiceName(childServiceName)
122+
.fetchErrors(fetchErrors)
117123
.build();
118124

119125
List<List<Span>> traces = storage.spanStore().getTraces(queryRequest).execute();

zipkin-ui/js/component_data/dependency.js

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {component} from 'flightjs';
22
import moment from 'moment';
33
import $ from 'jquery';
4+
import {traceSummary, traceSummariesToMustache} from '../component_ui/traceSummary';
45

56
export default component(function dependency() {
67
let services = {};
@@ -28,18 +29,34 @@ export default component(function dependency() {
2829
});
2930
};
3031

32+
this.filterDependency = function(parent, child, endTs, lookback, limit, error, serviceName) {
33+
const apiURL = `api/v2/traces?parentService=${parent}&childService=${child}&lookback=
34+
${lookback}&endTs=${endTs}&limit=${limit}&error=${error}`;
35+
$.ajax(apiURL, {
36+
type: 'GET',
37+
dataType: 'json'
38+
}).done(traces => {
39+
const traceView = {
40+
traces: traceSummariesToMustache('all', traces.map(traceSummary)),
41+
apiURL,
42+
rawResponse: traces,
43+
serviceName
44+
};
45+
this.trigger('filterLinkDataRecieved', traceView);
46+
}).fail(e => {
47+
this.trigger('defaultPageModelView', {traces: 'No traces to show', error: e});
48+
});
49+
};
50+
3151
this.buildServiceData = function(links) {
3252
services = {};
3353
dependencies = {};
3454
links.forEach(link => {
3555
const {parent, child} = link;
36-
3756
dependencies[parent] = dependencies[parent] || {};
3857
dependencies[parent][child] = link;
39-
4058
services[parent] = services[parent] || {serviceName: parent, uses: [], usedBy: []};
4159
services[child] = services[child] || {serviceName: child, uses: [], usedBy: []};
42-
4360
services[parent].uses.push(child);
4461
services[child].usedBy.push(parent);
4562
});
@@ -61,14 +78,18 @@ export default component(function dependency() {
6178
this.trigger(document, 'parentChildDataReceived', data);
6279
});
6380
});
64-
6581
const endTs = document.getElementById('endTs').value || moment().valueOf();
6682
const startTs = document.getElementById('startTs').value;
6783
let lookback;
6884
if (startTs && endTs > startTs) {
6985
lookback = endTs - startTs;
7086
}
7187
this.getDependency(endTs, lookback);
88+
this.on(document, 'filterLinkDataRequested',
89+
function(event, {parentService, childService, limit, error, serviceName}) {
90+
this.filterDependency(parentService, childService, endTs,
91+
lookback, limit, error, serviceName);
92+
});
7293
});
7394

7495
this.getServiceData = function(serviceName, callback) {

zipkin-ui/js/component_ui/serviceDataModal.js

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import {component} from 'flightjs';
22
import $ from 'jquery';
33
import bootstrap // eslint-disable-line no-unused-vars
4-
from 'bootstrap-sass/assets/javascripts/bootstrap.js';
4+
from 'bootstrap-sass/assets/javascripts/bootstrap.js';
5+
import TracesUI from './traces';
6+
import {tracesTemplate} from '../templates';
57

68
function renderDependencyModal(event, data) {
79
const $modal = $('#dependencyModal');
@@ -20,7 +22,6 @@ function renderDependencyModal(event, data) {
2022
serviceName: data.child
2123
});
2224
});
23-
2425
$modal.find('#dependencyModalParent').html($parentElement);
2526
$modal.find('#dependencyModalChild').html($childElement);
2627
$modal.find('#dependencyCallCount').text(data.callCount);
@@ -72,24 +73,60 @@ function renderServiceDataModal(event, data) {
7273
}
7374

7475
export default component(function serviceDataModal() {
76+
this.attributes({
77+
filterForm: '#filterForm',
78+
selectlimit: ':input[name=limit]',
79+
selecterorr: '#errorradio :selected'
80+
});
81+
7582
this.showServiceDataModal = function(event, data) {
7683
this.trigger(document, 'serviceDataRequested', {
7784
serviceName: data.serviceName
7885
});
7986
};
8087

88+
this.showTracesForLinkedServices = function(event, data) {
89+
this.trigger(document, 'filterLinkDataRequested', {
90+
serviceName: data.serviceName
91+
});
92+
};
93+
8194
this.showDependencyModal = function(event, data) {
8295
this.trigger(document, 'parentChildDataRequested', {
8396
parent: data.parent,
8497
child: data.child,
8598
callCount: data.callCount
8699
});
87100
};
101+
this.renderItems = function(event, modelView) {
102+
event.preventDefault();
103+
event.stopPropagation();
104+
$('#traces').html(tracesTemplate({
105+
...modelView
106+
}));
107+
TracesUI.attachTo('#traces');
108+
};
88109

110+
this.filterLinkDependency = function(event) {
111+
event.preventDefault();
112+
event.stopPropagation();
113+
// TODO: Find a better way to get the filter values
114+
this.trigger(document, 'filterLinkDataRequested', {
115+
limit: document.getElementById('limit').value,
116+
error: document.getElementById('erroronly').checked || false,
117+
parentService: document.getElementById('dependencyModalParent').children[0].text,
118+
childService: document.getElementById('dependencyModalChild').children[0].text,
119+
serviceName: document.getElementById('serviceModalTitle').textContent
120+
});
121+
};
89122
this.after('initialize', function() {
90123
this.on(document, 'showServiceDataModal', this.showServiceDataModal);
91124
this.on(document, 'showDependencyModal', this.showDependencyModal);
92125
this.on(document, 'serviceDataReceived', renderServiceDataModal);
93126
this.on(document, 'parentChildDataReceived', renderDependencyModal);
127+
this.on('#filterTraceBtn', 'click', {
128+
filterForm: this.filterLinkDependency,
129+
});
130+
this.on(document, 'filterLinkDataRecieved', this.renderItems);
94131
});
95132
});

zipkin-ui/js/page/dependency.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const DependencyPageComponent = component(function DependencyPage() {
2525
DependencyData.attachTo('#dependency-container');
2626
DependencyGraphUI.attachTo('#dependency-container', {config: this.attr.config});
2727
ServiceDataModal.attachTo('#service-data-modal-container');
28+
ServiceDataModal.attachTo('#filterForm');
2829
TimeStampUI.attachTo('#end-ts');
2930
TimeStampUI.attachTo('#start-ts');
3031
GoToDependencyUI.attachTo('#dependency-query-form');

zipkin-ui/js/templates.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@ export const layoutTemplate = require('../templates/layout.mustache');
44
export const dependenciesTemplate = require('../templates/dependency.mustache');
55
export const traceTemplate = require('../templates/trace.mustache');
66
export const traceViewerTemplate = require('../templates/traceViewer.mustache');
7+
export const tracesTemplate = require('../templates/traces.mustache');
8+

zipkin-ui/templates/dependency.mustache

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,12 @@
2323
<div class="modal-dialog">
2424
<div class="modal-content">
2525
<div class="modal-header">
26-
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
26+
<button type="button" id="dependencyclose" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
2727
<h3 class="modal-title">
2828
<span id="serviceModalTitle">[title]</span>
2929
</h3>
3030
</div>
3131
<div class="modal-body">
32-
3332
<div class="container">
3433
<div class="row">
3534
<div class="col-sm-6">
@@ -48,15 +47,14 @@
4847
</div>
4948

5049
<div class="modal" id="dependencyModal" tabindex="-1">
51-
<div class="modal-dialog">
50+
<div class="modal-dialog" style="min-width: 80%">
5251
<div class="modal-content">
5352
<div class="modal-header">
5453
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
5554
<h3 class="modal-title text-center">
5655
<div id="dependencyModalParent">[parent]</div>
5756
<div class=""><span class="glyphicon glyphicon-arrow-down"></span></div>
5857
<div id="dependencyModalChild">[child]</div>
59-
6058
</h3>
6159
</div>
6260
<div class="modal-body">
@@ -78,6 +76,35 @@
7876
</tr>
7977
</tbody>
8078
</table>
79+
<div class="panel panel-default">
80+
<div class="panel-heading">
81+
<h4 text-center> Filter Traces </h4>
82+
</div>
83+
<div class="panel-body">
84+
<form role='form' id="filterForm">
85+
<div class="form-group">
86+
<label for="limit" data-i18n="traces.limit">Limit</label>
87+
<input class="form-control input-sm" id="limit" name="limit" type="text" value="{{limit}}">
88+
</div>
89+
<div class="form-group">
90+
{{! <label class="radio-inline">
91+
<input id="alltrace" type="radio" name="alltrace">All Traces
92+
</label>
93+
<label class="radio-inline">
94+
<input id="erroronly" type="radio" name="erroronly">Errors only
95+
</label> }}
96+
<div class="custom-control custom-checkbox">
97+
<input id="erroronly" type="checkbox" class="custom-control-input" id="customCheck1">
98+
<label class="custom-control-label" for="customCheck1">Fetch only errors</label>
99+
</div>
100+
</div>
101+
<div class="form-group">
102+
<button type="button" id="filterTraceBtn" class="btn btn-primary btn-lg" data-i18n="traces.findBtn">Filter Traces</button>
103+
</div>
104+
</form>
105+
<ul id="traces" />
106+
</div>
107+
</div>
81108
</div>
82109
</div>
83110
</div>

zipkin-ui/templates/traces.mustache

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<hr><h6> Filter Results </h6><hr>
2+
{{#traces}}
3+
<li class="trace {{infoClass}}" data-traceId="{{traceId}}" data-duration="{{duration}}" data-timestamp="{{timestamp}}" data-service-percentage="{{servicePercentage}}">
4+
<a href="{{contextRoot}}traces/{{traceId}}">
5+
<div class="bar-block">
6+
<span class="bar-graphic" style="width:{{width}}%;"></span>
7+
<span class="bar-label">{{durationStr}}</span>
8+
<span class="bar-label">{{totalSpans}} spans</span>
9+
</div>
10+
<div class="bar-block">
11+
<span class="bar-graphic" style="width:{{servicePercentage}}%;"></span>
12+
<span class="bar-label">{{serviceName}} {{servicePercentage}}%</span>
13+
</div>
14+
</a>
15+
<div class="trace-details services">
16+
{{#serviceDurations}}
17+
<span class="label label-default service-filter-label" data-service-name="{{name}}">{{name}}
18+
x{{count}} {{max}}ms</span> {{/serviceDurations}}
19+
</div>
20+
<div class="trace-details timestamp pull-right">
21+
<time class="label timeago" datetime="{{startTs}}">{{startTs}}</time>
22+
</div>
23+
</li>
24+
{{/traces}}

zipkin/src/main/java/zipkin/storage/QueryRequest.java

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,16 @@ public final class QueryRequest {
103103
/** Maximum number of traces to return. Defaults to 10 */
104104
public final int limit;
105105

106+
107+
@Nullable
108+
public final String parentServiceName;
109+
110+
@Nullable
111+
public final String childServiceName;
112+
113+
public final boolean fetchErrors;
114+
115+
106116
/**
107117
* Corresponds to query parameter "annotationQuery". Ex. "http.method=GET and error"
108118
*
@@ -136,11 +146,17 @@ public String toAnnotationQuery() {
136146
Long maxDuration,
137147
long endTs,
138148
long lookback,
139-
int limit) {
149+
int limit,
150+
String parentServiceName,
151+
String childServiceName,
152+
boolean fetchErrors) {
140153
checkArgument(serviceName == null || !serviceName.isEmpty(), "serviceName was empty");
141154
checkArgument(spanName == null || !spanName.isEmpty(), "spanName was empty");
142155
checkArgument(endTs > 0, "endTs should be positive, in epoch microseconds: was %d", endTs);
143156
checkArgument(limit > 0, "limit should be positive: was %d", limit);
157+
this.parentServiceName = parentServiceName != null ? parentServiceName.toLowerCase() : null;
158+
this.childServiceName = childServiceName !=null ? childServiceName.toLowerCase() : null;
159+
this.fetchErrors = fetchErrors;
144160
this.serviceName = serviceName != null? serviceName.toLowerCase() : null;
145161
this.spanName = spanName != null ? spanName.toLowerCase() : null;
146162
this.annotations = annotations;
@@ -182,6 +198,9 @@ public static Builder builder() {
182198
}
183199

184200
public static final class Builder {
201+
private String parentServiceName;
202+
private String childServiceName;
203+
private boolean fetchErrors;
185204
private String serviceName;
186205
private String spanName;
187206
private List<String> annotations = new ArrayList<>();
@@ -196,6 +215,9 @@ public static final class Builder {
196215
}
197216

198217
Builder(QueryRequest source) {
218+
this.parentServiceName = source.parentServiceName;
219+
this.childServiceName = source.childServiceName;
220+
this.fetchErrors = source.fetchErrors;
199221
this.serviceName = source.serviceName;
200222
this.spanName = source.spanName;
201223
this.annotations = source.annotations;
@@ -286,6 +308,23 @@ public Builder limit(Integer limit) {
286308
return this;
287309
}
288310

311+
/** @see QueryRequest#parentServiceName */
312+
public Builder parent(String parentServiceName){
313+
this.parentServiceName = parentServiceName;
314+
return this;
315+
}
316+
317+
/** @see QueryRequest#childServiceName */
318+
public Builder child(String childServiceName){
319+
this.childServiceName = childServiceName;
320+
return this;
321+
}
322+
/** @see QueryRequest#fetchErrors */
323+
public Builder error(boolean fetchErrors){
324+
this.fetchErrors = fetchErrors;
325+
return this;
326+
}
327+
289328
public QueryRequest build() {
290329
long selectedEndTs = endTs == null ? System.currentTimeMillis() : endTs;
291330
return new QueryRequest(
@@ -297,7 +336,10 @@ public QueryRequest build() {
297336
maxDuration,
298337
selectedEndTs,
299338
Math.min(lookback == null ? selectedEndTs : lookback, selectedEndTs),
300-
limit == null ? 10 : limit);
339+
limit == null ? 10 : limit,
340+
parentServiceName,
341+
childServiceName,
342+
fetchErrors);
301343
}
302344
}
303345

@@ -323,7 +365,14 @@ public boolean equals(Object o) {
323365
}
324366
if (o instanceof QueryRequest) {
325367
QueryRequest that = (QueryRequest) o;
326-
return ((this.serviceName == null) ? (that.serviceName == null) : this.serviceName.equals(that.serviceName))
368+
return ((this.parentServiceName == null) ? (that.parentServiceName == null) : this.parentServiceName.equals
369+
(that.parentServiceName))
370+
&& ((this.childServiceName == null) ? (that.childServiceName == null) : this
371+
.childServiceName.equals
372+
(that.childServiceName))
373+
&& (this.fetchErrors == that.fetchErrors)
374+
&& ((this.serviceName == null) ? (that.serviceName == null) : this.serviceName.equals
375+
(that.serviceName))
327376
&& ((this.spanName == null) ? (that.spanName == null) : this.spanName.equals(that.spanName))
328377
&& ((this.annotations == null) ? (that.annotations == null) : this.annotations.equals(that.annotations))
329378
&& ((this.binaryAnnotations == null) ? (that.binaryAnnotations == null) : this.binaryAnnotations.equals(that.binaryAnnotations))

0 commit comments

Comments
 (0)