github.com/google/cloudprober@v0.11.3/surfacers/prometheus/prometheus_test.go (about) 1 // Copyright 2017-2020 The Cloudprober 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 15 package prometheus 16 17 import ( 18 "bytes" 19 "context" 20 "fmt" 21 "math/rand" 22 "strings" 23 "testing" 24 "time" 25 26 "github.com/golang/protobuf/proto" 27 "github.com/google/cloudprober/logger" 28 "github.com/google/cloudprober/metrics" 29 configpb "github.com/google/cloudprober/surfacers/prometheus/proto" 30 ) 31 32 func newEventMetrics(sent, rcvd int64, respCodes map[string]int64, ptype, probe string) *metrics.EventMetrics { 33 respCodesVal := metrics.NewMap("code", metrics.NewInt(0)) 34 for k, v := range respCodes { 35 respCodesVal.IncKeyBy(k, metrics.NewInt(v)) 36 } 37 return metrics.NewEventMetrics(time.Now()). 38 AddMetric("sent", metrics.NewInt(sent)). 39 AddMetric("rcvd", metrics.NewInt(rcvd)). 40 AddMetric("resp-code", respCodesVal). 41 AddLabel("ptype", ptype). 42 AddLabel("probe", probe) 43 } 44 45 func verify(t *testing.T, ps *PromSurfacer, expectedMetrics map[string]testData) { 46 for k, td := range expectedMetrics { 47 pm := ps.metrics[td.metricName] 48 if pm == nil { 49 t.Errorf("Metric %s not found in the prometheus metrics: %v", k, ps.metrics) 50 continue 51 } 52 if pm.data[k] == nil { 53 t.Errorf("Data key %s not found in the prometheus metrics: %v", k, pm.data) 54 continue 55 } 56 if pm.data[k].value != td.value { 57 t.Errorf("Didn't get expected metrics. Got: %s, Expected: %s", pm.data[k].value, td.value) 58 } 59 } 60 var dataCount int 61 for _, pm := range ps.metrics { 62 dataCount += len(pm.data) 63 } 64 if dataCount != len(expectedMetrics) { 65 t.Errorf("Prometheus doesn't have expected number of data keys. Got: %d, Expected: %d", dataCount, len(expectedMetrics)) 66 } 67 } 68 69 // mergeMap is helper function to build expectedMetrics by merging newly 70 // added expectedMetrics with the existing ones. 71 func mergeMap(recv map[string]testData, newmap map[string]testData) { 72 for k, v := range newmap { 73 recv[k] = v 74 } 75 } 76 77 // testData encapsulates expected value for a metric key and metric name. 78 type testData struct { 79 metricName string // To access data row in a 2-level data structure. 80 value string 81 } 82 83 func newPromSurfacer(t *testing.T, writeTimestamp bool) *PromSurfacer { 84 c := &configpb.SurfacerConf{ 85 // Attach a random integer to metrics URL so that multiple 86 // tests can run in parallel without handlers clashing with 87 // each other. 88 MetricsUrl: proto.String(fmt.Sprintf("/metrics_%d", rand.Int())), 89 IncludeTimestamp: proto.Bool(writeTimestamp), 90 } 91 l, _ := logger.New(context.Background(), "promtheus_test") 92 ps, err := New(context.Background(), c, nil, l) 93 if err != nil { 94 t.Fatal("Error while initializing prometheus surfacer", err) 95 } 96 return ps 97 } 98 99 func TestRecord(t *testing.T) { 100 ps := newPromSurfacer(t, true) 101 102 // Record first EventMetrics 103 ps.record(newEventMetrics(32, 22, map[string]int64{ 104 "200": 22, 105 }, "http", "vm-to-google")) 106 expectedMetrics := map[string]testData{ 107 "sent{ptype=\"http\",probe=\"vm-to-google\"}": testData{"sent", "32"}, 108 "rcvd{ptype=\"http\",probe=\"vm-to-google\"}": testData{"rcvd", "22"}, 109 "resp_code{ptype=\"http\",probe=\"vm-to-google\",code=\"200\"}": testData{"resp_code", "22"}, 110 } 111 verify(t, ps, expectedMetrics) 112 113 // Record second EventMetrics, no overlap. 114 ps.record(newEventMetrics(500, 492, map[string]int64{}, "ping", "vm-to-vm")) 115 mergeMap(expectedMetrics, map[string]testData{ 116 "sent{ptype=\"ping\",probe=\"vm-to-vm\"}": testData{"sent", "500"}, 117 "rcvd{ptype=\"ping\",probe=\"vm-to-vm\"}": testData{"rcvd", "492"}, 118 }) 119 verify(t, ps, expectedMetrics) 120 121 // Record third EventMetrics, replaces first EventMetrics' metrics. 122 ps.record(newEventMetrics(62, 50, map[string]int64{ 123 "200": 42, 124 "204": 8, 125 }, "http", "vm-to-google")) 126 mergeMap(expectedMetrics, map[string]testData{ 127 "sent{ptype=\"http\",probe=\"vm-to-google\"}": testData{"sent", "62"}, 128 "rcvd{ptype=\"http\",probe=\"vm-to-google\"}": testData{"rcvd", "50"}, 129 "resp_code{ptype=\"http\",probe=\"vm-to-google\",code=\"200\"}": testData{"resp_code", "42"}, 130 "resp_code{ptype=\"http\",probe=\"vm-to-google\",code=\"204\"}": testData{"resp_code", "8"}, 131 }) 132 verify(t, ps, expectedMetrics) 133 134 // Test string metrics. 135 em := metrics.NewEventMetrics(time.Now()). 136 AddMetric("instance_id", metrics.NewString("23152113123131")). 137 AddMetric("version", metrics.NewString("cloudradar-20170606-RC00")). 138 AddLabel("module", "sysvars") 139 em.Kind = metrics.GAUGE 140 ps.record(em) 141 mergeMap(expectedMetrics, map[string]testData{ 142 "instance_id{module=\"sysvars\",val=\"23152113123131\"}": testData{"instance_id", "1"}, 143 "version{module=\"sysvars\",val=\"cloudradar-20170606-RC00\"}": testData{"version", "1"}, 144 }) 145 verify(t, ps, expectedMetrics) 146 } 147 148 func TestInvalidNames(t *testing.T) { 149 ps := newPromSurfacer(t, true) 150 respCodesVal := metrics.NewMap("resp-code", metrics.NewInt(0)) 151 respCodesVal.IncKeyBy("200", metrics.NewInt(19)) 152 ps.record(metrics.NewEventMetrics(time.Now()). 153 AddMetric("sent", metrics.NewInt(32)). 154 AddMetric("rcvd/sent", metrics.NewInt(22)). 155 AddMetric("resp", respCodesVal). 156 AddLabel("probe-type", "http"). 157 AddLabel("probe/name", "vm-to-google")) 158 159 // Metric rcvd/sent is dropped 160 // Label probe-type is converted to probe_type 161 // Label probe/name is dropped 162 // Map value key resp-code is converted to resp_code label name 163 expectedMetrics := map[string]testData{ 164 "sent{probe_type=\"http\"}": testData{"sent", "32"}, 165 "resp{probe_type=\"http\",resp_code=\"200\"}": testData{"resp", "19"}, 166 } 167 verify(t, ps, expectedMetrics) 168 } 169 170 func TestScrapeOutput(t *testing.T) { 171 ps := newPromSurfacer(t, true) 172 respCodesVal := metrics.NewMap("code", metrics.NewInt(0)) 173 respCodesVal.IncKeyBy("200", metrics.NewInt(19)) 174 latencyVal := metrics.NewDistribution([]float64{1, 4}) 175 latencyVal.AddSample(0.5) 176 latencyVal.AddSample(5) 177 ts := time.Now() 178 promTS := fmt.Sprintf("%d", ts.UnixNano()/(1000*1000)) 179 ps.record(metrics.NewEventMetrics(ts). 180 AddMetric("sent", metrics.NewInt(32)). 181 AddMetric("rcvd", metrics.NewInt(22)). 182 AddMetric("latency", latencyVal). 183 AddMetric("resp_code", respCodesVal). 184 AddLabel("ptype", "http")) 185 var b bytes.Buffer 186 ps.writeData(&b) 187 data := b.String() 188 for _, d := range []string{ 189 "#TYPE sent counter", 190 "#TYPE rcvd counter", 191 "#TYPE resp_code counter", 192 "#TYPE latency histogram", 193 "sent{ptype=\"http\"} 32 " + promTS, 194 "rcvd{ptype=\"http\"} 22 " + promTS, 195 "resp_code{ptype=\"http\",code=\"200\"} 19 " + promTS, 196 "latency_sum{ptype=\"http\"} 5.5 " + promTS, 197 "latency_count{ptype=\"http\"} 2 " + promTS, 198 "latency_bucket{ptype=\"http\",le=\"1\"} 1 " + promTS, 199 "latency_bucket{ptype=\"http\",le=\"4\"} 1 " + promTS, 200 "latency_bucket{ptype=\"http\",le=\"+Inf\"} 2 " + promTS, 201 } { 202 if strings.Index(data, d) == -1 { 203 t.Errorf("String \"%s\" not found in output data: %s", d, data) 204 } 205 } 206 } 207 208 func TestScrapeOutputNoTimestamp(t *testing.T) { 209 ps := newPromSurfacer(t, false) 210 respCodesVal := metrics.NewMap("code", metrics.NewInt(0)) 211 respCodesVal.IncKeyBy("200", metrics.NewInt(19)) 212 latencyVal := metrics.NewDistribution([]float64{1, 4}) 213 latencyVal.AddSample(0.5) 214 latencyVal.AddSample(5) 215 ps.record(metrics.NewEventMetrics(time.Now()). 216 AddMetric("sent", metrics.NewInt(32)). 217 AddMetric("rcvd", metrics.NewInt(22)). 218 AddMetric("latency", latencyVal). 219 AddMetric("resp_code", respCodesVal). 220 AddLabel("ptype", "http")) 221 var b bytes.Buffer 222 ps.writeData(&b) 223 data := b.String() 224 for _, d := range []string{ 225 "#TYPE sent counter", 226 "#TYPE rcvd counter", 227 "#TYPE resp_code counter", 228 "#TYPE latency histogram", 229 "sent{ptype=\"http\"} 32", 230 "rcvd{ptype=\"http\"} 22", 231 "resp_code{ptype=\"http\",code=\"200\"} 19", 232 "latency_sum{ptype=\"http\"} 5.5", 233 "latency_count{ptype=\"http\"} 2", 234 "latency_bucket{ptype=\"http\",le=\"1\"} 1", 235 "latency_bucket{ptype=\"http\",le=\"4\"} 1", 236 "latency_bucket{ptype=\"http\",le=\"+Inf\"} 2", 237 } { 238 if strings.Index(data, d) == -1 { 239 t.Errorf("String \"%s\" not found in output data: %s", d, data) 240 } 241 } 242 }