|
| 1 | +# frozen_string_literal: true |
| 2 | + |
| 3 | +require 'benchmark' |
| 4 | +require 'benchmark/ips' |
| 5 | +require 'vernier' |
| 6 | + |
| 7 | +require 'prometheus/client' |
| 8 | +require 'prometheus/client/data_stores/single_threaded' |
| 9 | +require 'prometheus/client/histogram' |
| 10 | +require 'prometheus/client/histogram_fixed' |
| 11 | + |
| 12 | +# NoopStore doesn't work here, because it doesn't implement `get` and `all_values` methods |
| 13 | +Prometheus::Client.config.data_store = Prometheus::Client::DataStores::SingleThreaded.new # Simple data storage |
| 14 | + |
| 15 | +BUCKETS = [ |
| 16 | + 0.00001, 0.000015, 0.00002, 0.000025, 0.00003, 0.000035, 0.00004, 0.000045, 0.00005, 0.000055, 0.00006, 0.000065, 0.00007, 0.000075, 0.00008, 0.000085, |
| 17 | + 0.00009, 0.000095, 0.0001, 0.000101, 0.000102, 0.000103, 0.000104, 0.000105, 0.000106, 0.000107, 0.000108, 0.000109, 0.00011, 0.000111, 0.000112, 0.000113, |
| 18 | + 0.000114, 0.000115, 0.000116, 0.000117, 0.000118, 0.000119, 0.00012, 0.000121, 0.000122, 0.000123, 0.000124, 0.000125, 0.000126, 0.000127, 0.000128, |
| 19 | + 0.000129, 0.00013, 0.000131, 0.000132, 0.000133, 0.000134, 0.000135, 0.000136, 0.000137, 0.000138, 0.000139, 0.00014, 0.000141, 0.000142, 0.000143, 0.000144, |
| 20 | + 0.000145, 0.000146, 0.000147, 0.000148, 0.000149, 0.00015, 0.000151, 0.000152, 0.000153, 0.000154, 0.000155, 0.000156, 0.000157, 0.000158, 0.000159, 0.00016, |
| 21 | + 0.000161, 0.000162, 0.000163, 0.000164, 0.000165, 0.000166, 0.000167, 0.000168, 0.000169, 0.00017, 0.000171, 0.000172, 0.000173, 0.000174, 0.000175, |
| 22 | + 0.000176, 0.000177, 0.000178, 0.000179, 0.00018, 0.000181, 0.000182, 0.000183, 0.000184, 0.000185, 0.000186, 0.000187, 0.000188, 0.000189, 0.00019, 0.000191, |
| 23 | + 0.000192, 0.000193, 0.000194, 0.000195, 0.000196, 0.000197, 0.000198, 0.000199, 0.0002, 0.00021, 0.00022, 0.00023, 0.00024, 0.00025, 0.00026, |
| 24 | + 0.00027, 0.00028, 0.00029, 0.0003, 0.00031, 0.00032, 0.00033, 0.00034, 0.00035, 0.00036, 0.00037, 0.00038, 0.00039, 0.0004, 0.00041, 0.00042, |
| 25 | + 0.00043, 0.00044, 0.00045, 0.00046, 0.00047, 0.00048, 0.00049, 0.0005, 0.00051, 0.00052, 0.00053, 0.00054, 0.00055, 0.00056, 0.00057, 0.00058, |
| 26 | + 0.00059, 0.0006, 0.00061, 0.00062, 0.00063, 0.00064, 0.00065, 0.00066, 0.00067, 0.00068, 0.00069, 0.0007, 0.00071, 0.00072, 0.00073, 0.00074, |
| 27 | + 0.00075, 0.00076, 0.00077, 0.00078, 0.00079, 0.0008, 0.00081, 0.00082, 0.00083, 0.00084, 0.00085, 0.00086, 0.00087, 0.00088, 0.00089, 0.0009, |
| 28 | + 0.00091, 0.00092, 0.00093, 0.00094, 0.00095, 0.00096, 0.00097, 0.00098, 0.00099, 0.001, 0.0015, 0.002, 0.0025, 0.003, 0.0035, 0.004, 0.0045, 0.005, |
| 29 | + 0.0055, 0.006, 0.0065, 0.007, 0.0075, 0.008, 0.0085, 0.009, 0.0095, 0.01, 0.015, 0.02, 0.025, 0.03, 0.035, 0.04, 0.045, 0.05, 0.055, 0.06, 0.065, 0.07, |
| 30 | + 0.075, 0.08, 0.085, 0.09, 0.095, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95, 1.0, 1.5, 2.0, 2.5, |
| 31 | + 3.0, 3.5, 4.0, 4.5, 5.0, 5.5, 6.0, 6.5, 7.0, 7.5, 8.0, 8.5, 9.0, 9.5, 10.0, 10.5, 11.0, 11.5, 12.0, 12.5, 13.0, 13.5, 14.0, 14.5, 15.0, 15.5, 16.0, 16.5, 17.0, 17.5 |
| 32 | +].freeze |
| 33 | + |
| 34 | +def allocations |
| 35 | + x = GC.stat(:total_allocated_objects) |
| 36 | + yield |
| 37 | + GC.stat(:total_allocated_objects) - x |
| 38 | +end |
| 39 | + |
| 40 | +# Setup 4 test cases |
| 41 | +# 1. Default buckets + no labels |
| 42 | +# 2. Large buckets + no labels |
| 43 | +# 3. Default buckets + 2 labels |
| 44 | +# 4. Large buckets + 2 labels |
| 45 | +TEST_CASES = { |
| 46 | + 'default buckets + no lables' => [ |
| 47 | + Prometheus::Client::Histogram.new( |
| 48 | + :default_buckets_old, |
| 49 | + docstring: 'default buckets + no lables' |
| 50 | + ), |
| 51 | + Prometheus::Client::HistogramFixed.new( |
| 52 | + :default_buckets_new, |
| 53 | + docstring: 'default buckets + no lables' |
| 54 | + ) |
| 55 | + ], |
| 56 | + 'large buckets + no lables' => [ |
| 57 | + Prometheus::Client::Histogram.new( |
| 58 | + :large_buckets_old, |
| 59 | + docstring: 'large buckets + no lables', |
| 60 | + buckets: BUCKETS |
| 61 | + ), |
| 62 | + Prometheus::Client::HistogramFixed.new( |
| 63 | + :large_buckets_new, |
| 64 | + docstring: 'large buckets + no lables', |
| 65 | + buckets: BUCKETS |
| 66 | + ) |
| 67 | + ], |
| 68 | + 'default buckets + labels' => [ |
| 69 | + Prometheus::Client::Histogram.new( |
| 70 | + :default_buckets_with_labels_old, |
| 71 | + docstring: 'default buckets + labels', |
| 72 | + labels: [:label1, :label2] |
| 73 | + ), |
| 74 | + Prometheus::Client::HistogramFixed.new( |
| 75 | + :default_buckets_with_labels_new, |
| 76 | + docstring: 'default buckets + labels', |
| 77 | + labels: [:label1, :label2] |
| 78 | + ) |
| 79 | + ], |
| 80 | + 'large buckets + labels' => [ |
| 81 | + Prometheus::Client::Histogram.new( |
| 82 | + :large_buckets_with_labels_old, |
| 83 | + docstring: 'large buckets + labels', |
| 84 | + buckets: BUCKETS, |
| 85 | + labels: [:label1, :label2] |
| 86 | + ), |
| 87 | + Prometheus::Client::HistogramFixed.new( |
| 88 | + :large_buckets_with_labels_new, |
| 89 | + docstring: 'large buckets + labels', |
| 90 | + buckets: BUCKETS, |
| 91 | + labels: [:label1, :label2] |
| 92 | + ) |
| 93 | + ], |
| 94 | +}.freeze |
| 95 | + |
| 96 | +labels = [1,2,3,4,5,6,7,8,9] |
| 97 | + |
| 98 | +TEST_CASES.each do |name, (old, new)| |
| 99 | + with_labels = name.include?('+ labels') |
| 100 | + Benchmark.ips do |bm| |
| 101 | + bm.report("#{name} -> Observe old") do |
| 102 | + if with_labels |
| 103 | + old.observe(rand(BUCKETS.last + 10), labels: { label1: labels.sample, label2: labels.sample }) |
| 104 | + else |
| 105 | + old.observe(rand(BUCKETS.last + 10)) |
| 106 | + end |
| 107 | + end |
| 108 | + bm.report("#{name} -> Observe new") do |
| 109 | + if with_labels |
| 110 | + new.observe(rand(BUCKETS.last + 10), labels: { label1: labels.sample, label2: labels.sample }) |
| 111 | + else |
| 112 | + new.observe(rand(BUCKETS.last + 10)) |
| 113 | + end |
| 114 | + end |
| 115 | + |
| 116 | + bm.compare! |
| 117 | + end |
| 118 | + |
| 119 | + Benchmark.ips do |bm| |
| 120 | + bm.report("#{name} -> Values old") { old.values } |
| 121 | + bm.report("#{name} -> Values new") { new.values } |
| 122 | + |
| 123 | + bm.compare! |
| 124 | + end |
| 125 | +end |
| 126 | + |
| 127 | +# Sample usage of profiler |
| 128 | +val = rand(BUCKETS.last) |
| 129 | +l = { label1: 1, label2: 2 } |
| 130 | + |
| 131 | +# Vernier.profile(mode: :wall, out: 'x.json', interval: 1, allocation_interval: 1) do |
| 132 | +# 100000.times { large_buckets_new.observe(val, labels: l) } |
| 133 | +# end |
| 134 | + |
| 135 | +old, new = TEST_CASES['large buckets + labels'] |
| 136 | + |
| 137 | +puts "Old#observe allocations -> #{allocations { 1000.times { old.observe(val, labels: l) }}}" |
| 138 | +puts "New#observe allocations -> #{allocations { 1000.times { new.observe(val, labels: l) }}}" |
| 139 | + |
| 140 | +puts "Old#values allocations -> #{allocations { 1000.times { old.values }}}" |
| 141 | +puts "New#values allocations -> #{allocations { 1000.times { new.values }}}" |
| 142 | + |
| 143 | + |
| 144 | +# Results: |
| 145 | +# 1. Default buckets + no labels |
| 146 | +# #observe is almost the same, but #values is 2.15x faster |
| 147 | +# |
| 148 | +# default buckets + no lables -> Observe new: 492718.9 i/s |
| 149 | +# default buckets + no lables -> Observe old: 475856.7 i/s - same-ish: difference falls within error |
| 150 | +# default buckets + no lables -> Values new: 98723.1 i/s |
| 151 | +# default buckets + no lables -> Values old: 45867.1 i/s - 2.15x slower |
| 152 | +# |
| 153 | +# 2. Large buckets + no labels |
| 154 | +# #observe is almost the same, but #values is 2.93x faster |
| 155 | +# |
| 156 | +# large buckets + no lables -> Observe new: 441325.9 i/s |
| 157 | +# large buckets + no lables -> Observe old: 437752.4 i/s - same-ish: difference falls within error |
| 158 | +# large buckets + no lables -> Values new: 4792.0 i/s |
| 159 | +# large buckets + no lables -> Values old: 1636.8 i/s - 2.93x slower |
| 160 | +# |
| 161 | +# 3. Default buckets + 2 labels |
| 162 | +# #observe is 1.44x faster, #values is 2.70x faster |
| 163 | +# |
| 164 | +# default buckets + labels -> Observe new: 450357.3 i/s |
| 165 | +# default buckets + labels -> Observe old: 311747.3 i/s - 1.44x slower |
| 166 | +# default buckets + labels -> Values new: 1633.8 i/s |
| 167 | +# default buckets + labels -> Values old: 604.2 i/s - 2.70x slower |
| 168 | +# |
| 169 | +# 4. Large buckets + 2 labels |
| 170 | +# #observe is 1.41x faster, #values is 9.57x faster |
| 171 | +# |
| 172 | +# large buckets + labels -> Observe new: 392597.2 i/s |
| 173 | +# large buckets + labels -> Observe old: 277499.9 i/s - 1.41x slower |
| 174 | +# large buckets + labels -> Values new: 247.6 i/s |
| 175 | +# large buckets + labels -> Values old: 25.9 i/s - 9.57x slower |
| 176 | +# |
| 177 | +# 5. Allocations for 1000 observations for #observe method |
| 178 | +# Old allocations -> 11001 - 11 allocations per observation |
| 179 | +# New allocations -> 1000 - 1 allocation per observation |
| 180 | +# Last place left `bucket_label_set = base_label_set.dup` in #observe method |
| 181 | +# |
| 182 | +# 6. Allocations for 1000 observations for #values method |
| 183 | +# Old#values allocations -> 96150000 |
| 184 | +# New#values allocations -> 3325000 |
| 185 | +# almost 30x less allocations |
0 commit comments