bosun.org@v0.0.0-20210513094433-e25bc3e69a1f/opentsdb/tsdb_test.go (about) 1 package opentsdb 2 3 import ( 4 "bytes" 5 "io/ioutil" 6 "net/http" 7 "net/url" 8 "testing" 9 10 "github.com/stretchr/testify/assert" 11 ) 12 13 func TestClean(t *testing.T) { 14 clean := "aoeSNVT152-./_" 15 if c, err := Clean(clean); c != clean { 16 t.Error("was clean", clean) 17 } else if err != nil { 18 t.Fatal(err) 19 } 20 notclean := "euon@#$sar:.03 n ]e/" 21 notcleaned := "euonsar.03ne/" 22 if c, err := Clean(notclean); c != notcleaned { 23 t.Error("wasn't cleaned", notclean, "into:", c) 24 } else if err != nil { 25 t.Fatal(err) 26 } 27 } 28 29 func TestEmptyPoint(t *testing.T) { 30 d := DataPoint{} 31 if d.Clean() == nil { 32 t.Fatal("empty datapoint should not be cleanable") 33 } 34 } 35 36 func TestParseQueryV2_1(t *testing.T) { 37 tests := []struct { 38 query string 39 error bool 40 }{ 41 {"sum:10m-avg:proc.stat.cpu{t=v,o=k}", false}, 42 {"sum:10m-avg:rate:proc.stat.cpu", false}, 43 {"sum:10m-avg:rate{counter,1,2}:proc.stat.cpu{t=v,o=k}", false}, 44 {"sum:proc.stat.cpu", false}, 45 {"sum:rate:proc.stat.cpu{t=v,o=k}", false}, 46 47 {"", true}, 48 {"sum:cpu+", true}, 49 {"sum:cpu{}", true}, 50 {"sum:stat{a=b=c}", true}, 51 } 52 for _, q := range tests { 53 _, err := ParseQuery(q.query, Version2_1) 54 if err != nil && !q.error { 55 t.Errorf("got error: %s: %s", q.query, err) 56 } else if err == nil && q.error { 57 t.Errorf("expected error: %s", q.query) 58 } 59 } 60 } 61 62 func TestParseQueryV2_2(t *testing.T) { 63 tests := []struct { 64 query string 65 error bool 66 }{ 67 {"sum:10m-avg:proc.stat.cpu{t=v,o=k}", false}, 68 {"sum:10m-avg:rate:proc.stat.cpu", false}, 69 {"sum:10m-avg:rate{counter,1,2}:proc.stat.cpu{t=v,o=k}", false}, 70 {"sum:10m-avg:rate{counter,1,2}:proc.stat.cpu{t=v,o=k}{t=wildcard(v*)}", false}, 71 {"sum:10m-avg:rate{counter,1,2}:proc.stat.cpu{}{t=wildcard(v*)}", false}, 72 {"sum:proc.stat.cpu", false}, 73 {"sum:rate:proc.stat.cpu{t=v,o=k}", false}, 74 75 {"", true}, 76 {"sum:cpu+", true}, 77 // This is supported in 2.2 78 {"sum:cpu{}", false}, 79 // This wouldn't be valid, but we are more permissive and will let opentsdb 80 // return errors. This is because there can be a regex filter now. Might 81 // be issues with escaping there however 82 {"sum:stat{a=b=c}", false}, 83 84 //test fill policies 85 {"sum:10m-avg-zero:proc.stat.cpu{t=v,o=k}", false}, 86 {"sum:10m-avg-:proc.stat.cpu{t=v,o=k}", true}, 87 {"sum:10m-avg-none:rate:proc.stat.cpu", false}, 88 {"sum:10m-avg-:rate{counter,1,2}:proc.stat.cpu{t=v,o=k}", true}, 89 } 90 for _, q := range tests { 91 _, err := ParseQuery(q.query, Version2_2) 92 if err != nil && !q.error { 93 t.Errorf("got error: %s: %s", q.query, err) 94 } else if err == nil && q.error { 95 t.Errorf("expected error: %s", q.query) 96 } 97 } 98 } 99 100 func TestParseRequestV2_1(t *testing.T) { 101 tests := []struct { 102 query string 103 error bool 104 }{ 105 {"start=1&m=sum:c", false}, 106 {"start=1&m=sum:c&end=2", false}, 107 {"start=1&m=sum:10m-avg:rate:proc.stat.cpu{t=v,o=k}", false}, 108 109 {"start=&m=", true}, 110 {"m=sum:c", true}, 111 {"start=1", true}, 112 } 113 for _, q := range tests { 114 _, err := ParseRequest(q.query, Version2_1) 115 if err != nil && !q.error { 116 t.Errorf("got error: %s: %s", q.query, err) 117 } else if err == nil && q.error { 118 t.Errorf("expected error: %s", q.query) 119 } 120 } 121 } 122 123 func TestParseRequestV2_2(t *testing.T) { 124 tests := []struct { 125 query string 126 error bool 127 }{ 128 {"start=1&m=sum:c", false}, 129 {"start=1&m=sum:c&end=2", false}, 130 {"start=1&m=sum:10m-avg:rate:proc.stat.cpu{t=v,o=k}", false}, 131 {"start=1&m=sum:10m-avg:rate:proc.stat.cpu{}{t=v,o=k}", false}, 132 {"start=1&m=sum:10m-avg:rate:proc.stat.cpu{z=iwildcard(foo*)}{t=v,o=k}", false}, 133 134 {"start=&m=", true}, 135 {"m=sum:c", true}, 136 {"start=1", true}, 137 } 138 for _, q := range tests { 139 _, err := ParseRequest(q.query, Version2_2) 140 if err != nil && !q.error { 141 t.Errorf("got error: %s: %s", q.query, err) 142 } else if err == nil && q.error { 143 t.Errorf("expected error: %s", q.query) 144 } 145 } 146 } 147 func TestTagGroupParsing(t *testing.T) { 148 tests := []struct { 149 query string 150 groups string 151 }{ 152 {"sum:10m-avg:proc.stat.cpu{}{t=v,o=k}", "{}"}, 153 {"sum:10m-avg:proc.stat.cpu{dc=uk}{t=v,o=k}", "{dc=}"}, 154 } 155 for _, q := range tests { 156 r, err := ParseQuery(q.query, Version2_2) 157 if err == nil { 158 if r.GroupByTags.String() != q.groups { 159 t.Errorf("expected group tags %s got %s", q.groups, r.GroupByTags) 160 } 161 162 } 163 } 164 } 165 166 func TestParseRateOptions(t *testing.T) { 167 tests := []struct { 168 query string 169 rate RateOptions 170 }{ 171 {"sum:10m-avg:rate{counter,1,2}:test.metric", 172 RateOptions{ 173 Counter: true, 174 CounterMax: 1, 175 ResetValue: 2, 176 }, 177 }, 178 {"sum:10m-avg:rate{dropcounter}:test.metric", 179 RateOptions{ 180 Counter: true, 181 DropResets: true, 182 }, 183 }, 184 {"sum:10m-avg:rate{counter,,2}:test.metric", 185 RateOptions{ 186 Counter: true, 187 ResetValue: 2, 188 }, 189 }, 190 {"sum:10m-avg:rate{counter,1}:test.metric", 191 RateOptions{ 192 Counter: true, 193 CounterMax: 1, 194 }, 195 }, 196 } 197 for _, q := range tests { 198 parsedQuery, err := ParseQuery(q.query, Version2_2) 199 if err != nil { 200 t.Errorf("error parsing query %s: %s", q.query, err) 201 continue 202 } 203 if q.rate != parsedQuery.RateOptions { 204 t.Errorf("for query %s expected parsed rate options %+v, got: %+v", q.query, q.rate, parsedQuery.RateOptions) 205 } 206 } 207 } 208 209 func TestParseFilters(t *testing.T) { 210 tests := []struct { 211 query string 212 filters Filters 213 }{ 214 {"sum:10m-avg:rate{counter,1,2}:proc.stat.cpu{t=v,o=k}", 215 Filters{ 216 Filter{ 217 Type: "literal_or", 218 TagK: "t", 219 Filter: "v", 220 GroupBy: true, 221 }, 222 Filter{ 223 Type: "literal_or", 224 TagK: "o", 225 Filter: "k", 226 GroupBy: true, 227 }, 228 }, 229 }, 230 {"sum:10m-avg:rate:proc.stat.cpu{}{t=v,o=k}", 231 Filters{ 232 Filter{ 233 Type: "literal_or", 234 TagK: "t", 235 Filter: "v", 236 GroupBy: false, 237 }, 238 Filter{ 239 Type: "literal_or", 240 TagK: "o", 241 Filter: "k", 242 GroupBy: false, 243 }, 244 }, 245 }, 246 {"sum:10m-avg:rate:proc.stat.cpu{t=v}{o=k}", 247 Filters{ 248 Filter{ 249 Type: "literal_or", 250 TagK: "t", 251 Filter: "v", 252 GroupBy: true, 253 }, 254 Filter{ 255 Type: "literal_or", 256 TagK: "o", 257 Filter: "k", 258 GroupBy: false, 259 }, 260 }, 261 }, 262 {"sum:10m-avg:rate:proc.stat.cpu{t=v}{o=iwildcard(foo*)}", 263 Filters{ 264 Filter{ 265 Type: "literal_or", 266 TagK: "t", 267 Filter: "v", 268 GroupBy: true, 269 }, 270 Filter{ 271 Type: "iwildcard", 272 TagK: "o", 273 Filter: "foo*", 274 GroupBy: false, 275 }, 276 }, 277 }, 278 } 279 for _, q := range tests { 280 parsedQuery, err := ParseQuery(q.query, Version2_2) 281 if err != nil { 282 t.Errorf("error parsing query %s: %s", q.query, err) 283 continue 284 } 285 if len(q.filters) != len(parsedQuery.Filters) { 286 t.Errorf("expected %d filters, got: %d", len(q.filters), len(parsedQuery.Filters)) 287 } else { 288 for i, f := range parsedQuery.Filters { 289 if q.filters[i] != f { 290 t.Errorf("expected parsed filter %+v, got: %+v", q.filters[i], f) 291 } 292 } 293 } 294 } 295 } 296 297 func TestQueryString(t *testing.T) { 298 tests := []struct { 299 in Query 300 out string 301 }{ 302 { 303 Query{ 304 Aggregator: "avg", 305 Metric: "test.metric", 306 Rate: true, 307 RateOptions: RateOptions{ 308 Counter: true, 309 CounterMax: 1, 310 ResetValue: 2, 311 }, 312 }, 313 "avg:rate{counter,1,2}:test.metric", 314 }, 315 { 316 Query{ 317 Aggregator: "avg", 318 Metric: "test.metric", 319 Rate: true, 320 RateOptions: RateOptions{ 321 Counter: true, 322 CounterMax: 1, 323 }, 324 }, 325 "avg:rate{counter,1}:test.metric", 326 }, 327 { 328 Query{ 329 Aggregator: "avg", 330 Metric: "test.metric", 331 Rate: true, 332 RateOptions: RateOptions{ 333 Counter: true, 334 }, 335 }, 336 "avg:rate{counter}:test.metric", 337 }, 338 { 339 Query{ 340 Aggregator: "avg", 341 Metric: "test.metric", 342 Rate: true, 343 RateOptions: RateOptions{ 344 CounterMax: 1, 345 ResetValue: 2, 346 }, 347 }, 348 "avg:rate:test.metric", 349 }, 350 { 351 Query{ 352 Aggregator: "avg", 353 Metric: "test.metric", 354 RateOptions: RateOptions{ 355 Counter: true, 356 CounterMax: 1, 357 ResetValue: 2, 358 }, 359 }, 360 "avg:test.metric", 361 }, 362 { 363 Query{ 364 Aggregator: "avg", 365 Metric: "test.metric", 366 Rate: true, 367 RateOptions: RateOptions{ 368 Counter: true, 369 DropResets: true, 370 }, 371 }, 372 "avg:rate{dropcounter}:test.metric", 373 }, 374 } 375 for _, q := range tests { 376 s := q.in.String() 377 if s != q.out { 378 t.Errorf(`got "%s", expected "%s"`, s, q.out) 379 } 380 } 381 } 382 383 func TestValidTSDBString(t *testing.T) { 384 tests := map[string]bool{ 385 "abcXYZ012_./-": true, 386 387 "": false, 388 "a|c": false, 389 "a=b": false, 390 } 391 for s, v := range tests { 392 r := ValidTSDBString(s) 393 if v != r { 394 t.Errorf("%v: got %v, expected %v", s, r, v) 395 } 396 } 397 } 398 399 func TestValidTags(t *testing.T) { 400 tests := map[string]bool{ 401 "a=b|c,d=*": true, 402 403 "": false, 404 "a=b,a=c": false, 405 "a=b=c": false, 406 } 407 for s, v := range tests { 408 _, err := ParseTags(s) 409 r := err == nil 410 if v != r { 411 t.Errorf("%v: got %v, expected %v", s, r, v) 412 } 413 } 414 } 415 416 func TestAllSubsets(t *testing.T) { 417 ts, _ := ParseTags("a=1,b=2,c=3,d=4") 418 subsets := ts.AllSubsets() 419 if len(subsets) != 15 { 420 t.Fatal("Expect 15 subsets") 421 } 422 } 423 424 func TestReplace(t *testing.T) { 425 tests := []struct{ in, out string }{ 426 {"abc", "abc"}, 427 {"ny-web01", "ny-web01"}, 428 {"_./", "_./"}, 429 {"%%%a", ".a"}, 430 } 431 for i, test := range tests { 432 out, err := Replace(test.in, ".") 433 if err != nil { 434 t.Errorf("Test %d: %s", i, err) 435 } 436 if out != test.out { 437 t.Errorf("Test %d: %s != %s", i, out, test.out) 438 } 439 } 440 } 441 442 // RoundTripFunc . 443 type RoundTripFunc func(req *http.Request) *http.Response 444 445 // RoundTrip . 446 func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { 447 return f(req), nil 448 } 449 450 //NewTestClient returns *http.Client with Transport replaced to avoid making real calls 451 func NewTestClient(fn RoundTripFunc) *http.Client { 452 return &http.Client{ 453 Transport: RoundTripFunc(fn), 454 } 455 } 456 457 func TestQueryResponseUrlParsing(t *testing.T) { 458 459 tests := []struct { 460 raw string 461 URL url.URL 462 }{ 463 {"localhost:4242", url.URL{Scheme: "http", Host: "localhost:4242", Path: "/api/query"}}, 464 {"127.0.0.1:4444", url.URL{Scheme: "http", Host: "127.0.0.1:4444", Path: "/api/query"}}, 465 {"https://tsdb.skyscanner.net:4242", url.URL{Scheme: "https", Host: "tsdb.skyscanner.net:4242", Path: "/api/query"}}, 466 } 467 468 r := Request{} 469 stockResponse := http.Response{ 470 StatusCode: 200, 471 Body: ioutil.NopCloser(bytes.NewBufferString(`OK`)), 472 Header: make(http.Header), 473 } 474 475 for _, test := range tests { 476 client := NewTestClient(func(req *http.Request) *http.Response { 477 assert.Equal(t, test.URL, *req.URL) 478 return &stockResponse 479 }) 480 _, _ = r.QueryResponse(test.raw, client) 481 482 } 483 484 } 485 486 func BenchmarkReplace_Noop(b *testing.B) { 487 for i := 0; i < b.N; i++ { 488 Replace("abcdefghijklmnopqrstuvwxyz", "") 489 } 490 } 491 492 func BenchmarkReplace_Something(b *testing.B) { 493 for i := 0; i < b.N; i++ { 494 Replace("abcdef&hij@@$$opq#stuvw*yz", "") 495 } 496 }