/
QueryServiceSpec.scala
471 lines (394 loc) · 17.7 KB
/
QueryServiceSpec.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
/*
* Copyright 2012 Twitter 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.
*
*/
package com.twitter.zipkin.query
import adjusters.{TimeSkewAdjuster, NullAdjuster}
import org.specs.Specification
import org.specs.mock.{ClassMocker, JMocker}
import scala.collection.immutable.Set
import com.twitter.zipkin.gen
import com.twitter.zipkin.common._
import java.nio.ByteBuffer
import com.twitter.util.Future
import com.twitter.scrooge.BinaryThriftStructSerializer
import com.twitter.zipkin.storage.{Aggregates, TraceIdDuration, Storage, Index}
import com.twitter.zipkin.adapter.{ThriftQueryAdapter, ThriftAdapter}
class QueryServiceSpec extends Specification with JMocker with ClassMocker {
val ep1 = Endpoint(123, 123, "service1")
val ep2 = Endpoint(234, 234, "service2")
val ep3 = Endpoint(345, 345, "service3")
val ann1 = Annotation(100, gen.Constants.CLIENT_SEND, Some(ep1))
val ann2 = Annotation(150, gen.Constants.CLIENT_RECV, Some(ep1))
val trace1 = Trace(List(Span(1, "methodcall", 666, None, List(ann1, ann2), Nil)))
// duration 50
val ann3 = Annotation(101, gen.Constants.CLIENT_SEND, Some(ep2))
val ann4 = Annotation(501, gen.Constants.CLIENT_RECV, Some(ep2))
val trace2 = Trace(List(Span(2, "methodcall", 667, None, List(ann3, ann4), Nil)))
// duration 400
val ann5 = Annotation(99, gen.Constants.CLIENT_SEND, Some(ep3))
val ann6 = Annotation(199, gen.Constants.CLIENT_RECV, Some(ep3))
val trace3 = Trace(List(Span(3, "methodcall", 668, None, List(ann5, ann6), Nil)))
// duration 100
// get some server action going on
val ann7 = Annotation(110, gen.Constants.SERVER_RECV, Some(ep2))
val ann8 = Annotation(140, gen.Constants.SERVER_SEND, Some(ep2))
val trace4 = Trace(List(Span(1, "methodcall", 666, None, List(ann1, ann2), Nil),
Span(1, "methodcall", 666, None, List(ann7, ann8), Nil)))
// no spans
val trace5 = Trace(List())
"QueryService" should {
"generate exception in getTraceIdsByName if service name is null" in {
val qs = new QueryService(null, null, null, null)
qs.start
qs.getTraceIdsBySpanName(null, "span", 101, 100, gen.Order.DurationDesc)() must throwA[gen.QueryException]
}
"throw exception in getTraceIdsByServiceName if service name is null" in {
val qs = new QueryService(null, null, null, null)
qs.start
qs.getTraceIdsByServiceName(null, 101, 100, gen.Order.DurationDesc)() must throwA[gen.QueryException]
}
"throw exception in getTraceIdsByAnnotation if annotation is null" in {
val qs = new QueryService(null, null, null, null)
qs.start
qs.getTraceIdsByAnnotation(null, null, null, 101, 100, gen.Order.DurationDesc)() must throwA[gen.QueryException]
}
class MockIndex extends Index {
def ids: Seq[Long] = Seq(1, 2, 3)
def mockSpanName: Option[String] = Some("methodcall")
def mockValue: Option[ByteBuffer] = None
def close() = null
def getTraceIdsByName(serviceName: String, spanName: Option[String],
endTs: Long, limit: Int): Future[Seq[Long]] = {
serviceName mustEqual "service"
spanName mustEqual mockSpanName
endTs mustEqual 100L
Future(ids)
}
def getTraceIdsByAnnotation(service: String, annotation: String, value: Option[ByteBuffer], endTs: Long,
limit: Int): Future[Seq[Long]] = {
service mustEqual "service"
annotation mustEqual "annotation"
value mustEqual mockValue
endTs mustEqual 100L
Future(ids)
}
def getTracesDuration(traceIds: Seq[Long]): Future[Seq[TraceIdDuration]] = {
traceIds mustEqual Seq(1, 2, 3)
Future(Seq(TraceIdDuration(1, 50, 100), TraceIdDuration(2, 401, 101),
TraceIdDuration(3, 100, 99)))
}
def getServiceNames = null
def getSpanNames(service: String) = null
def indexTraceIdByServiceAndName(span: Span) = null
def indexSpanByAnnotations(span: Span) = null
def indexServiceName(span: Span) = null
def indexSpanNameByService(span: Span) = null
def indexSpanDuration(span: Span): Future[Void] = null
}
"find traces in service span name index, fetch from storage" in {
val storage = mock[Storage]
val index = new MockIndex {}
val qs = new QueryService(storage, index, null, Map())
qs.start()
val expected = List(2, 3, 1)
val actual = qs.getTraceIdsBySpanName("service", "methodcall", 100, 50,
gen.Order.DurationDesc)()
actual mustEqual expected
}
"find traces in service span name index, order by duration desc" in {
val storage = mock[Storage]
val index = new MockIndex {}
val qs = new QueryService(storage, index, null, Map())
qs.start()
val expected = List(2, 3, 1)
val actual = qs.getTraceIdsBySpanName("service", "methodcall", 100, 50, gen.Order.DurationDesc)()
actual mustEqual expected
}
"find traces in service span name index, order by nothing" in {
val storage = mock[Storage]
val index = new MockIndex {}
val qs = new QueryService(storage, index, null, Map())
qs.start()
val expected = List(1, 2, 3)
val actual = qs.getTraceIdsBySpanName("service", "methodcall", 100, 50, gen.Order.None)()
actual mustEqual expected
}
"successfully return the trace summary for a trace id" in {
val storage = mock[Storage]
val index = mock[Index]
val qs = new QueryService(storage, index, null, Map())
qs.start()
val traceId = 123L
expect {
one(storage).getTracesByIds(List(traceId)) willReturn Future(List(trace1))
}
val ts = List(ThriftAdapter(TraceSummary(1, 100, 150, 50, Map("service1" -> 1), List(ep1))))
ts mustEqual qs.getTraceSummariesByIds(List(traceId), List())()
}
"successfully return the trace combo for a trace id" in {
val storage = mock[Storage]
val index = mock[Index]
val qs = new QueryService(storage, index, null, Map())
qs.start()
val traceId = 123L
expect {
one(storage).getTracesByIds(List(traceId)) willReturn Future(List(trace1))
}
val trace = trace1.toThrift
val summary = ThriftAdapter(TraceSummary(1, 100, 150, 50, Map("service1" -> 1), List(ep1)))
val timeline = trace1.toTimeline.map(ThriftQueryAdapter(_))
val combo = gen.TraceCombo(trace, Some(summary), timeline, Some(Map(666L -> 1)))
Seq(combo) mustEqual qs.getTraceCombosByIds(List(traceId), List())()
}
"find traces in service name index, fetch from storage" in {
val storage = mock[Storage]
val index = new MockIndex {
override def mockSpanName = None
}
val qs = new QueryService(storage, index, null, Map())
qs.start()
val expected = List(2, 3, 1)
val actual = qs.getTraceIdsByServiceName("service", 100, 50, gen.Order.DurationDesc)()
expected mustEqual actual
}
"find traces in annotation index by timestamp annotation, fetch from storage" in {
val storage = mock[Storage]
val index = new MockIndex {}
val qs = new QueryService(storage, index, null, Map())
qs.start()
val expected = List(2, 3, 1)
expected mustEqual qs.getTraceIdsByAnnotation("service", "annotation", null, 100, 50,
gen.Order.DurationDesc)()
}
"find traces in annotation index by kv annotation, fetch from storage" in {
val storage = mock[Storage]
val index = new MockIndex {
override def mockValue = Some(ByteBuffer.wrap("value".getBytes))
}
val qs = new QueryService(storage, index, null, Map())
qs.start()
val expected = List(2, 3, 1)
val actual = qs.getTraceIdsByAnnotation("service", "annotation", ByteBuffer.wrap("value".getBytes),
100, 50, gen.Order.DurationDesc)()
expected mustEqual actual
}
"fetch traces from storage" in {
val storage = mock[Storage]
val index = mock[Index]
val qs = new QueryService(storage, index, null, Map())
qs.start()
expect {
1.of(storage).getTracesByIds(List(1L)) willReturn Future(List(trace1))
}
val expected = List(trace1.toThrift)
val actual = qs.getTracesByIds(List(1L), List())()
expected mustEqual actual
}
"fetch timeline from storage" in {
val storage = mock[Storage]
val index = mock[Index]
val qs = new QueryService(storage, index, null,
Map(gen.Adjust.Nothing -> NullAdjuster, gen.Adjust.TimeSkew -> new TimeSkewAdjuster()))
qs.start()
expect {
1.of(storage).getTracesByIds(List(1L)) willReturn Future(List(trace4.mergeSpans))
}
val ann1 = gen.TimelineAnnotation(100, gen.Constants.CLIENT_SEND,
ThriftAdapter(ep1), 666, None, "service1", "methodcall")
val ann2 = gen.TimelineAnnotation(150, gen.Constants.CLIENT_RECV,
ThriftAdapter(ep1), 666, None, "service1", "methodcall")
val ann3 = gen.TimelineAnnotation(110, gen.Constants.SERVER_RECV,
ThriftAdapter(ep2), 666, None, "service2", "methodcall")
val ann4 = gen.TimelineAnnotation(140, gen.Constants.SERVER_SEND,
ThriftAdapter(ep2), 666, None, "service2", "methodcall")
val expected = List(gen.TraceTimeline(1L, 666, List(ann1, ann3, ann4, ann2), List()))
val actual = qs.getTraceTimelinesByIds(List(1L), List(gen.Adjust.Nothing, gen.Adjust.TimeSkew))()
expected mustEqual actual
}
"fetch timeline with clock skew from storage, fix skew" in {
val storage = mock[Storage]
val index = mock[Index]
val qs = new QueryService(storage, index, null, Map(gen.Adjust.TimeSkew -> new TimeSkewAdjuster()))
qs.start()
// these are real traces, except for timestap that has been chopped down to make easier to spot
// differences
val epKoalabird = Some(Endpoint(170024040, -26945, "koalabird-cuckoo"))
val epKoalabirdT = ThriftAdapter(epKoalabird.get)
val epCuckoo = Some(Endpoint(170061954, 9149, "cuckoo.thrift"))
val epCuckooT = ThriftAdapter(epCuckoo.get)
val epCuckooCassie = Some(Endpoint(170061954, -24936, "client"))
val epCuckooCassieT = ThriftAdapter(epCuckooCassie.get)
val rs1 = Span(4488677265848750007L, "ValuesFromSource", 4488677265848750007L, None, List(
Annotation(6712580L, "cs", epKoalabird),
Annotation(6873376L, "cr", epKoalabird),
Annotation(6711797L, "sr", epCuckoo),
Annotation(6872073L, "ss", epCuckoo)
), Nil)
val rs2 = Span(4488677265848750007L, "multiget_slice", 2603914853951996887L,
Some(4488677265848750007L), List(
Annotation(6712539L, "cs", epCuckooCassie),
Annotation(6871825L, "cr", epCuckooCassie)
), Nil)
val realTrace = Trace(List(rs1, rs2))
expect {
1.of(storage).getTracesByIds(List(4488677265848750007L)) willReturn Future(List(realTrace.mergeSpans))
}
val actual = qs.getTraceTimelinesByIds(List(4488677265848750007L), List(gen.Adjust.TimeSkew))()
actual.size mustEqual 1
val tla = actual(0).`annotations`
/*
we expect the following order of annotations back
this is the order of the evens as they happened
Annotation(6712580L, "cs", epKoalabird),
Annotation(6711797L, "sr", epCuckoo),
Annotation(6712539L, "cs", epCuckooCassie),
Annotation(6871825L, "cr", epCuckooCassie)
Annotation(6872073L, "ss", epCuckoo)
Annotation(6873376L, "cr", epKoalabird),
*/
// we ignore the timestamps for now, order is what we care about
tla(0).`value` mustEqual "cs"
tla(0).`host` mustEqual epKoalabirdT
tla(1).`value` mustEqual "sr"
tla(1).`host` mustEqual epCuckooT
tla(2).`value` mustEqual "cs"
tla(2).`host` mustEqual epCuckooCassieT
tla(3).`value` mustEqual "cr"
tla(3).`host` mustEqual epCuckooCassieT
tla(4).`value` mustEqual "ss"
tla(4).`host` mustEqual epCuckooT
tla(5).`value` mustEqual "cr"
tla(5).`host` mustEqual epKoalabirdT
}
"fetch timeline with clock skew from storage, fix skew - the sequel" in {
val storage = mock[Storage]
val index = mock[Index]
val qs = new QueryService(storage, index, null, Map(gen.Adjust.TimeSkew -> new TimeSkewAdjuster()))
qs.start()
// these are real traces, except for timestap that has been chopped down to make easier to spot
// differences
val epKoalabird = Some(Endpoint(170021254, -24672, "koalabird-cuckoo"))
val epKoalabirdT = ThriftAdapter(epKoalabird.get)
val epCuckoo = Some(Endpoint(170062191, 9149, "cuckoo.thrift"))
val epCuckooT = ThriftAdapter(epCuckoo.get)
val epCuckooCassie = Some(Endpoint(170062191, -8985, "client"))
val epCuckooCassieT = ThriftAdapter(epCuckooCassie.get)
val rs1 = Span(-6120267009876080004L, "ValuesFromSource", -6120267009876080004L, None, List(
Annotation(630715L, "cs", epKoalabird),
Annotation(832298L, "cr", epKoalabird),
Annotation(632011L, "sr", epCuckoo),
Annotation(833041L, "ss", epCuckoo)
), Nil)
val rs2 = Span(-6120267009876080004L, "multiget_slice", 3496046180771443122L,
Some(-6120267009876080004L), List(
Annotation(632711L, "cs", epCuckooCassie),
Annotation(832872L, "cr", epCuckooCassie)
), Nil)
val realTrace = Trace(List(rs1, rs2))
expect {
1.of(storage).getTracesByIds(List(-6120267009876080004L)) willReturn Future(List(realTrace.mergeSpans))
}
val actual = qs.getTraceTimelinesByIds(List(-6120267009876080004L), List(gen.Adjust.TimeSkew))()
actual.size mustEqual 1
val tla = actual(0).`annotations`
/*
we expect the following order of annotations back
this is the order of the evens as they happened
Annotation(timestamp=630715L, 'koalabird-cuckoo', value='cs'),
Annotation(timestamp=632011L, 'cuckoo.thrift', value='sr'),
Annotation(timestamp=632711L, 'client', value='cs'),
Annotation(timestamp=832872L, 'client', value='cr')
Annotation(timestamp=833041L, 'cuckoo.thrift', value='ss'),
Annotation(timestamp=832298L, 'koalabird-cuckoo', value='cr')
diff between cr - cs: 201583
diff between ss - sr: 201030
latency: (201583 - 201030) / 2 = 276
skew: 1020
Annotation(timestamp=630715L, 'koalabird-cuckoo', value='cs'),
Annotation(timestamp=630991, 'cuckoo.thrift', value='sr'),
Annotation(timestamp=631691, 'client', value='cs'),
Annotation(timestamp=831852, 'client', value='cr')
Annotation(timestamp=832021, 'cuckoo.thrift', value='ss'),
Annotation(timestamp=832298L, 'koalabird-cuckoo', value='cr')
*/
// we ignore the timestamps for now, order is what we care about
tla(0).`value` mustEqual "cs"
tla(0).`host` mustEqual epKoalabirdT
tla(1).`value` mustEqual "sr"
tla(1).`host` mustEqual epCuckooT
tla(2).`value` mustEqual "cs"
tla(2).`host` mustEqual epCuckooCassieT
tla(3).`value` mustEqual "cr"
tla(3).`host` mustEqual epCuckooCassieT
tla(4).`value` mustEqual "ss"
tla(4).`host` mustEqual epCuckooT
tla(5).`value` mustEqual "cr"
tla(5).`host` mustEqual epKoalabirdT
}
"fail to find traces by name in index, return empty" in {
val storage = mock[Storage]
val index = new MockIndex {
override def ids: Seq[Long] = Seq()
override def getTracesDuration(traceIds: Seq[Long]): Future[Seq[TraceIdDuration]] = Future(Seq())
}
val qs = new QueryService(storage, index, null, Map())
qs.start()
List() mustEqual qs.getTraceIdsBySpanName("service", "methodcall", 100, 50, gen.Order.DurationDesc)()
}
"fail to find traces by annotation in index, return empty" in {
val storage = mock[Storage]
val index = new MockIndex {
override def ids: Seq[Long] = Seq()
override def getTracesDuration(traceIds: Seq[Long]): Future[Seq[TraceIdDuration]] = Future(Seq())
}
val qs = new QueryService(storage, index, null, Map())
qs.start()
List() mustEqual qs.getTraceIdsByAnnotation("service", "annotation", null, 100, 50, gen.Order.DurationDesc)()
}
"return the correct ttl" in {
val storage = mock[Storage]
val index = mock[Index]
val qs = new QueryService(storage, index, null, Map())
qs.start()
val ttl = 123
expect {
one(storage).getDataTimeToLive willReturn ttl
}
ttl mustEqual qs.getDataTimeToLive()()
}
"retrieve aggregates" in {
val aggregates = mock[Aggregates]
val serviceName = "mockingbird"
val annotations = Seq("a", "b", "c")
"retrieve top annotations" in {
val qs = new QueryService(null, null, aggregates, Map())
qs.start()
expect {
one(aggregates).getTopAnnotations(serviceName) willReturn Future.value(annotations)
}
qs.getTopAnnotations(serviceName)() mustEqual annotations
}
"retrieve top key value annotations" in {
val qs = new QueryService(null, null, aggregates, Map())
qs.start()
expect {
one(aggregates).getTopKeyValueAnnotations(serviceName) willReturn Future.value(annotations)
}
qs.getTopKeyValueAnnotations(serviceName)() mustEqual annotations
}
}
}
}