1// Copyright (c) 2019 The Jaeger Authors. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package jaeger 16 17import ( 18 "fmt" 19 "testing" 20 "time" 21 22 "github.com/uber/jaeger-client-go/thrift-gen/sampling" 23 24 "github.com/opentracing/opentracing-go" 25) 26 27type benchSampler struct { 28 name string 29 sampler Sampler 30} 31 32func (b benchSampler) String() string { 33 return b.name 34} 35 36func benchAdaptiveSampler(lateBinding bool, prob float64) Sampler { 37 ops := makeOps(5) 38 samplingRates := make([]*sampling.OperationSamplingStrategy, 5) 39 for i, op := range ops { 40 samplingRates[i] = &sampling.OperationSamplingStrategy{ 41 Operation: op, 42 ProbabilisticSampling: &sampling.ProbabilisticSamplingStrategy{SamplingRate: prob}, 43 } 44 } 45 strategies := &sampling.PerOperationSamplingStrategies{ 46 DefaultSamplingProbability: prob, 47 DefaultLowerBoundTracesPerSecond: 0, 48 PerOperationStrategies: samplingRates, 49 } 50 return NewPerOperationSampler(PerOperationSamplerParams{ 51 MaxOperations: 7, 52 OperationNameLateBinding: lateBinding, 53 Strategies: strategies, 54 }) 55 // Change to below when running on <=2.19 56 //return newAdaptiveSampler(strategies, 7) 57} 58 59func BenchmarkTracer(b *testing.B) { 60 axes := []axis{ 61 { 62 name: "sampler", 63 values: []interface{}{ 64 benchSampler{name: "NeverSample", sampler: NewConstSampler(false)}, 65 benchSampler{name: "AlwaysSample", sampler: NewConstSampler(true)}, 66 benchSampler{name: "AdaptiveNeverSampleNoLateBinding", sampler: benchAdaptiveSampler(false, 0)}, 67 benchSampler{name: "AdaptiveAlwaysSampleNoLateBinding", sampler: benchAdaptiveSampler(false, 1)}, 68 benchSampler{name: "AdaptiveNeverSampleWithLateBinding", sampler: benchAdaptiveSampler(true, 0)}, 69 benchSampler{name: "AdaptiveAlwaysSampleWithLateBinding", sampler: benchAdaptiveSampler(true, 1)}, 70 }, 71 }, 72 // adding remote sampler on top of others did not show significant impact, keeping it off for now. 73 {name: "remoteSampler", values: []interface{}{false}}, 74 {name: "children", values: []interface{}{false, true}}, 75 // tags and ops dimensions did not show significant impact, so keeping to one value only for now. 76 {name: "tags", values: []interface{}{5}}, 77 {name: "ops", values: []interface{}{10}}, 78 } 79 for _, entry := range combinations(axes) { 80 b.Run(entry.encode(axes)+"cpus", func(b *testing.B) { 81 sampler := entry["sampler"].(benchSampler).sampler 82 if entry["remoteSampler"].(bool) { 83 sampler = NewRemotelyControlledSampler("service", 84 SamplerOptions.InitialSampler(sampler), 85 SamplerOptions.SamplingRefreshInterval(time.Minute), 86 ) 87 } 88 options := benchOptions{ 89 tags: entry["tags"].(int), 90 ops: entry["ops"].(int), 91 sampler: sampler, 92 children: entry["children"].(bool), 93 } 94 benchmarkTracer(b, options) 95 }) 96 } 97} 98 99type benchOptions struct { 100 tags int 101 ops int 102 sampler Sampler 103 children bool 104} 105 106func benchmarkTracer(b *testing.B, options benchOptions) { 107 tags := makeStrings("tag", options.tags) 108 ops := makeOps(options.ops) 109 110 sampler := options.sampler 111 tracer, closer := NewTracer("service", sampler, NewNullReporter()) 112 defer closer.Close() 113 114 b.ResetTimer() 115 116 b.RunParallel(func(pb *testing.PB) { 117 var parent opentracing.SpanContext 118 var op int 119 for pb.Next() { 120 span := tracer.StartSpan(ops[op], opentracing.ChildOf(parent)) 121 for i := range tags { 122 span.SetTag(tags[i], tags[i]) 123 } 124 if options.children { 125 parent = span.Context() 126 } 127 span.Finish() 128 op = (op + 1) % len(ops) 129 } 130 }) 131} 132 133func makeOps(num int) []string { 134 return makeStrings("span", num) 135} 136 137func makeStrings(prefix string, num int) []string { 138 values := make([]string, num) 139 for i := 0; i < num; i++ { 140 values[i] = fmt.Sprintf("%s%02d", prefix, i) 141 } 142 return values 143} 144 145// combinations takes a list axes and their values and 146// returns a collection of entries which contain all combinations of each axis 147// value with every other axis' values. 148func combinations(axes []axis) []permutation { 149 if len(axes) == 0 { 150 return nil 151 } 152 153 if len(axes) == 1 { 154 return axes[0].entries() 155 } 156 157 var entries []permutation 158 // combos := combinations(axes[1:]) 159 last := len(axes) - 1 160 combos := combinations(axes[:last]) 161 for _, remaining := range combos { 162 for _, entry := range axes[last].entries() { 163 for k, v := range remaining { 164 entry[k] = v 165 } 166 entries = append(entries, entry) 167 } 168 } 169 170 return entries 171} 172 173type axis struct { 174 name string 175 values []interface{} 176} 177 178func (x axis) entries() []permutation { 179 items := make([]permutation, len(x.values)) 180 for i, value := range x.values { 181 items[i] = permutation{x.name: value} 182 } 183 return items 184} 185 186type permutation map[string]interface{} 187 188// encode converts a permutation into a string "k=v/k=v/...". 189// The axes argument is used to provide a definitive order of keys. 190func (p permutation) encode(axes []axis) string { 191 name := "" 192 for _, axis := range axes { 193 k := axis.name 194 v := p[k] 195 name = name + fmt.Sprintf("%s=%v", k, v) + "/" 196 } 197 return name 198} 199