|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593 |
- // Copyright 2017, OpenCensus Authors
- //
- // 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 prometheus
-
- import (
- "context"
- "fmt"
- "io/ioutil"
- "net/http"
- "net/http/httptest"
- "strings"
- "sync"
- "testing"
- "time"
-
- "go.opencensus.io/stats"
- "go.opencensus.io/stats/view"
- "go.opencensus.io/tag"
-
- "github.com/prometheus/client_golang/prometheus"
- )
-
- func newView(measureName string, agg *view.Aggregation) *view.View {
- m := stats.Int64(measureName, "bytes", stats.UnitBytes)
- return &view.View{
- Name: "foo",
- Description: "bar",
- Measure: m,
- Aggregation: agg,
- }
- }
-
- func TestOnlyCumulativeWindowSupported(t *testing.T) {
- // See Issue https://github.com/census-instrumentation/opencensus-go/issues/214.
- count1 := &view.CountData{Value: 1}
- lastValue1 := &view.LastValueData{Value: 56.7}
- tests := []struct {
- vds *view.Data
- want int
- }{
- 0: {
- vds: &view.Data{
- View: newView("TestOnlyCumulativeWindowSupported/m1", view.Count()),
- },
- want: 0, // no rows present
- },
- 1: {
- vds: &view.Data{
- View: newView("TestOnlyCumulativeWindowSupported/m2", view.Count()),
- Rows: []*view.Row{
- {Data: count1},
- },
- },
- want: 1,
- },
- 2: {
- vds: &view.Data{
- View: newView("TestOnlyCumulativeWindowSupported/m3", view.LastValue()),
- Rows: []*view.Row{
- {Data: lastValue1},
- },
- },
- want: 1,
- },
- }
-
- for i, tt := range tests {
- reg := prometheus.NewRegistry()
- collector := newCollector(Options{}, reg)
- collector.addViewData(tt.vds)
- mm, err := reg.Gather()
- if err != nil {
- t.Errorf("#%d: Gather err: %v", i, err)
- }
- reg.Unregister(collector)
- if got, want := len(mm), tt.want; got != want {
- t.Errorf("#%d: got nil %v want nil %v", i, got, want)
- }
- }
- }
-
- func TestCollectNonRacy(t *testing.T) {
- // Despite enforcing the singleton, for this case we
- // need an exporter hence won't be using NewExporter.
- exp, err := NewExporter(Options{})
- if err != nil {
- t.Fatalf("NewExporter: %v", err)
- }
- collector := exp.c
-
- // Synchronize and make sure every goroutine has terminated before we exit
- var waiter sync.WaitGroup
- waiter.Add(3)
- defer waiter.Wait()
-
- doneCh := make(chan bool)
- // 1. Viewdata write routine at 700ns
- go func() {
- defer waiter.Done()
- tick := time.NewTicker(700 * time.Nanosecond)
- defer tick.Stop()
-
- defer func() {
- close(doneCh)
- }()
-
- for i := 0; i < 1e3; i++ {
- count1 := &view.CountData{Value: 1}
- vds := []*view.Data{
- {View: newView(fmt.Sprintf("TestCollectNonRacy/m2-%d", i), view.Count()), Rows: []*view.Row{{Data: count1}}},
- }
- for _, v := range vds {
- exp.ExportView(v)
- }
- <-tick.C
- }
- }()
-
- inMetricsChan := make(chan prometheus.Metric, 1000)
- // 2. Simulating the Prometheus metrics consumption routine running at 900ns
- go func() {
- defer waiter.Done()
- tick := time.NewTicker(900 * time.Nanosecond)
- defer tick.Stop()
-
- for {
- select {
- case <-doneCh:
- return
- case <-inMetricsChan:
- }
- }
- }()
-
- // 3. Collect/Read routine at 800ns
- go func() {
- defer waiter.Done()
- tick := time.NewTicker(800 * time.Nanosecond)
- defer tick.Stop()
-
- for {
- select {
- case <-doneCh:
- return
- case <-tick.C:
- // Perform some collection here
- collector.Collect(inMetricsChan)
- }
- }
- }()
- }
-
- type mSlice []*stats.Int64Measure
-
- func (measures *mSlice) createAndAppend(name, desc, unit string) {
- m := stats.Int64(name, desc, unit)
- *measures = append(*measures, m)
- }
-
- type vCreator []*view.View
-
- func (vc *vCreator) createAndAppend(name, description string, keys []tag.Key, measure stats.Measure, agg *view.Aggregation) {
- v := &view.View{
- Name: name,
- Description: description,
- TagKeys: keys,
- Measure: measure,
- Aggregation: agg,
- }
- *vc = append(*vc, v)
- }
-
- func TestMetricsEndpointOutput(t *testing.T) {
- exporter, err := NewExporter(Options{})
- if err != nil {
- t.Fatalf("failed to create prometheus exporter: %v", err)
- }
- view.RegisterExporter(exporter)
-
- names := []string{"foo", "bar", "baz"}
-
- var measures mSlice
- for _, name := range names {
- measures.createAndAppend("tests/"+name, name, "")
- }
-
- var vc vCreator
- for _, m := range measures {
- vc.createAndAppend(m.Name(), m.Description(), nil, m, view.Count())
- }
-
- if err := view.Register(vc...); err != nil {
- t.Fatalf("failed to create views: %v", err)
- }
- defer view.Unregister(vc...)
-
- view.SetReportingPeriod(time.Millisecond)
-
- for _, m := range measures {
- stats.Record(context.Background(), m.M(1))
- }
-
- srv := httptest.NewServer(exporter)
- defer srv.Close()
-
- var i int
- var output string
- for {
- time.Sleep(10 * time.Millisecond)
- if i == 1000 {
- t.Fatal("no output at /metrics (10s wait)")
- }
- i++
-
- resp, err := http.Get(srv.URL)
- if err != nil {
- t.Fatalf("failed to get /metrics: %v", err)
- }
-
- body, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- t.Fatalf("failed to read body: %v", err)
- }
- resp.Body.Close()
-
- output = string(body)
- if output != "" {
- break
- }
- }
-
- if strings.Contains(output, "collected before with the same name and label values") {
- t.Fatal("metric name and labels being duplicated but must be unique")
- }
-
- if strings.Contains(output, "error(s) occurred") {
- t.Fatal("error reported by prometheus registry")
- }
-
- for _, name := range names {
- if !strings.Contains(output, "tests_"+name+" 1") {
- t.Fatalf("measurement missing in output: %v", name)
- }
- }
- }
-
- func TestCumulativenessFromHistograms(t *testing.T) {
- exporter, err := NewExporter(Options{})
- if err != nil {
- t.Fatalf("failed to create prometheus exporter: %v", err)
- }
- view.RegisterExporter(exporter)
- reportPeriod := time.Millisecond
- view.SetReportingPeriod(reportPeriod)
-
- m := stats.Float64("tests/bills", "payments by denomination", stats.UnitDimensionless)
- v := &view.View{
- Name: "cash/register",
- Description: "this is a test",
- Measure: m,
-
- // Intentionally used repeated elements in the ascending distribution.
- // to ensure duplicate distribution items are handles.
- Aggregation: view.Distribution(1, 5, 5, 5, 5, 10, 20, 50, 100, 250),
- }
-
- if err := view.Register(v); err != nil {
- t.Fatalf("Register error: %v", err)
- }
- defer view.Unregister(v)
-
- // Give the reporter ample time to process registration
- <-time.After(10 * reportPeriod)
-
- values := []float64{0.25, 245.67, 12, 1.45, 199.9, 7.69, 187.12}
- // We want the results that look like this:
- // 1: [0.25] | 1 + prev(i) = 1 + 0 = 1
- // 5: [1.45] | 1 + prev(i) = 1 + 1 = 2
- // 10: [7.69] | 1 + prev(i) = 1 + 2 = 3
- // 20: [12] | 1 + prev(i) = 1 + 3 = 4
- // 50: [] | 0 + prev(i) = 0 + 4 = 4
- // 100: [] | 0 + prev(i) = 0 + 4 = 4
- // 250: [187.12, 199.9, 245.67] | 3 + prev(i) = 3 + 4 = 7
- wantLines := []string{
- `cash_register_bucket{le="1"} 1`,
- `cash_register_bucket{le="5"} 2`,
- `cash_register_bucket{le="10"} 3`,
- `cash_register_bucket{le="20"} 4`,
- `cash_register_bucket{le="50"} 4`,
- `cash_register_bucket{le="100"} 4`,
- `cash_register_bucket{le="250"} 7`,
- `cash_register_bucket{le="+Inf"} 7`,
- `cash_register_sum 654.0799999999999`, // Summation of the input values
- `cash_register_count 7`,
- }
-
- ctx := context.Background()
- ms := make([]stats.Measurement, 0, len(values))
- for _, value := range values {
- mx := m.M(value)
- ms = append(ms, mx)
- }
- stats.Record(ctx, ms...)
-
- // Give the recorder ample time to process recording
- <-time.After(10 * reportPeriod)
-
- cst := httptest.NewServer(exporter)
- defer cst.Close()
- res, err := http.Get(cst.URL)
- if err != nil {
- t.Fatalf("http.Get error: %v", err)
- }
- blob, err := ioutil.ReadAll(res.Body)
- if err != nil {
- t.Fatalf("Read body error: %v", err)
- }
- str := strings.Trim(string(blob), "\n")
- lines := strings.Split(str, "\n")
- nonComments := make([]string, 0, len(lines))
- for _, line := range lines {
- if !strings.Contains(line, "#") {
- nonComments = append(nonComments, line)
- }
- }
-
- got := strings.Join(nonComments, "\n")
- want := strings.Join(wantLines, "\n")
- if got != want {
- t.Fatalf("\ngot:\n%s\n\nwant:\n%s\n", got, want)
- }
- }
-
- func TestHistogramUnorderedBucketBounds(t *testing.T) {
- exporter, err := NewExporter(Options{})
- if err != nil {
- t.Fatalf("failed to create prometheus exporter: %v", err)
- }
- view.RegisterExporter(exporter)
- reportPeriod := time.Millisecond
- view.SetReportingPeriod(reportPeriod)
-
- m := stats.Float64("tests/bills", "payments by denomination", stats.UnitDimensionless)
- v := &view.View{
- Name: "cash/register",
- Description: "this is a test",
- Measure: m,
-
- // Intentionally used unordered and duplicated elements in the distribution
- // to ensure unordered bucket bounds are handled.
- Aggregation: view.Distribution(10, 5, 1, 1, 50, 5, 20, 100, 250),
- }
-
- if err := view.Register(v); err != nil {
- t.Fatalf("Register error: %v", err)
- }
- defer view.Unregister(v)
-
- // Give the reporter ample time to process registration
- <-time.After(10 * reportPeriod)
-
- values := []float64{0.25, 245.67, 12, 1.45, 199.9, 7.69, 187.12}
- // We want the results that look like this:
- // 1: [0.25] | 1 + prev(i) = 1 + 0 = 1
- // 5: [1.45] | 1 + prev(i) = 1 + 1 = 2
- // 10: [7.69] | 1 + prev(i) = 1 + 2 = 3
- // 20: [12] | 1 + prev(i) = 1 + 3 = 4
- // 50: [] | 0 + prev(i) = 0 + 4 = 4
- // 100: [] | 0 + prev(i) = 0 + 4 = 4
- // 250: [187.12, 199.9, 245.67] | 3 + prev(i) = 3 + 4 = 7
- wantLines := []string{
- `cash_register_bucket{le="1"} 1`,
- `cash_register_bucket{le="5"} 2`,
- `cash_register_bucket{le="10"} 3`,
- `cash_register_bucket{le="20"} 4`,
- `cash_register_bucket{le="50"} 4`,
- `cash_register_bucket{le="100"} 4`,
- `cash_register_bucket{le="250"} 7`,
- `cash_register_bucket{le="+Inf"} 7`,
- `cash_register_sum 654.0799999999999`, // Summation of the input values
- `cash_register_count 7`,
- }
-
- ctx := context.Background()
- ms := make([]stats.Measurement, 0, len(values))
- for _, value := range values {
- mx := m.M(value)
- ms = append(ms, mx)
- }
- stats.Record(ctx, ms...)
-
- // Give the recorder ample time to process recording
- <-time.After(10 * reportPeriod)
-
- cst := httptest.NewServer(exporter)
- defer cst.Close()
- res, err := http.Get(cst.URL)
- if err != nil {
- t.Fatalf("http.Get error: %v", err)
- }
- blob, err := ioutil.ReadAll(res.Body)
- if err != nil {
- t.Fatalf("Read body error: %v", err)
- }
- str := strings.Trim(string(blob), "\n")
- lines := strings.Split(str, "\n")
- nonComments := make([]string, 0, len(lines))
- for _, line := range lines {
- if !strings.Contains(line, "#") {
- nonComments = append(nonComments, line)
- }
- }
-
- got := strings.Join(nonComments, "\n")
- want := strings.Join(wantLines, "\n")
- if got != want {
- t.Fatalf("\ngot:\n%s\n\nwant:\n%s\n", got, want)
- }
- }
-
- func TestConstLabelsIncluded(t *testing.T) {
- constLabels := prometheus.Labels{
- "service": "spanner",
- }
- measureLabel, _ := tag.NewKey("method")
-
- exporter, err := NewExporter(Options{
- ConstLabels: constLabels,
- })
- if err != nil {
- t.Fatalf("failed to create prometheus exporter: %v", err)
- }
- view.RegisterExporter(exporter)
- defer view.UnregisterExporter(exporter)
-
- names := []string{"foo", "bar", "baz"}
-
- var measures mSlice
- for _, name := range names {
- measures.createAndAppend("tests/"+name, name, "")
- }
-
- var vc vCreator
- for _, m := range measures {
- vc.createAndAppend(m.Name(), m.Description(), []tag.Key{measureLabel}, m, view.Count())
- }
-
- if err := view.Register(vc...); err != nil {
- t.Fatalf("failed to create views: %v", err)
- }
- defer view.Unregister(vc...)
-
- view.SetReportingPeriod(time.Millisecond)
-
- ctx, _ := tag.New(context.Background(), tag.Upsert(measureLabel, "issue961"))
- for _, m := range measures {
- stats.Record(ctx, m.M(1))
- }
-
- srv := httptest.NewServer(exporter)
- defer srv.Close()
-
- var i int
- var output string
- for {
- time.Sleep(10 * time.Millisecond)
- if i == 1000 {
- t.Fatal("no output at /metrics (10s wait)")
- }
- i++
-
- resp, err := http.Get(srv.URL)
- if err != nil {
- t.Fatalf("failed to get /metrics: %v", err)
- }
-
- body, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- t.Fatalf("failed to read body: %v", err)
- }
- resp.Body.Close()
-
- output = string(body)
- if output != "" {
- break
- }
- }
-
- if strings.Contains(output, "collected before with the same name and label values") {
- t.Fatal("metric name and labels being duplicated but must be unique")
- }
-
- if strings.Contains(output, "error(s) occurred") {
- t.Fatal("error reported by prometheus registry")
- }
-
- want := `# HELP tests_bar bar
- # TYPE tests_bar counter
- tests_bar{method="issue961",service="spanner"} 1
- # HELP tests_baz baz
- # TYPE tests_baz counter
- tests_baz{method="issue961",service="spanner"} 1
- # HELP tests_foo foo
- # TYPE tests_foo counter
- tests_foo{method="issue961",service="spanner"} 1
- `
- if output != want {
- t.Fatal("output differed from expected")
- }
- }
-
- func TestViewMeasureWithoutTag(t *testing.T) {
- exporter, err := NewExporter(Options{})
- if err != nil {
- t.Fatalf("failed to create prometheus exporter: %v", err)
- }
- view.RegisterExporter(exporter)
- defer view.UnregisterExporter(exporter)
- m := stats.Int64("tests/foo", "foo", stats.UnitDimensionless)
- k1, _ := tag.NewKey("key/1")
- k2, _ := tag.NewKey("key/2")
- k3, _ := tag.NewKey("key/3")
- k4, _ := tag.NewKey("key/4")
- k5, _ := tag.NewKey("key/5")
- randomKey, _ := tag.NewKey("issue659")
- v := &view.View{
- Name: m.Name(),
- Description: m.Description(),
- TagKeys: []tag.Key{k2, k5, k3, k1, k4}, // Ensure view has a tag
- Measure: m,
- Aggregation: view.Count(),
- }
- if err := view.Register(v); err != nil {
- t.Fatalf("failed to create views: %v", err)
- }
- defer view.Unregister(v)
- view.SetReportingPeriod(time.Millisecond)
- // Make a measure without some tags in the view.
- ctx1, _ := tag.New(context.Background(), tag.Upsert(k4, "issue659"), tag.Upsert(randomKey, "value"), tag.Upsert(k2, "issue659"))
- stats.Record(ctx1, m.M(1))
- ctx2, _ := tag.New(context.Background(), tag.Upsert(k5, "issue659"), tag.Upsert(k3, "issue659"), tag.Upsert(k1, "issue659"))
- stats.Record(ctx2, m.M(2))
- srv := httptest.NewServer(exporter)
- defer srv.Close()
- var i int
- var output string
- for {
- time.Sleep(10 * time.Millisecond)
- if i == 1000 {
- t.Fatal("no output at /metrics (10s wait)")
- }
- i++
- resp, err := http.Get(srv.URL)
- if err != nil {
- t.Fatalf("failed to get /metrics: %v", err)
- }
- body, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- t.Fatalf("failed to read body: %v", err)
- }
- resp.Body.Close()
- output = string(body)
- if output != "" {
- break
- }
- }
- if strings.Contains(output, "collected before with the same name and label values") {
- t.Fatal("metric name and labels being duplicated but must be unique")
- }
- if strings.Contains(output, "error(s) occurred") {
- t.Fatal("error reported by prometheus registry")
- }
- want := `# HELP tests_foo foo
- # TYPE tests_foo counter
- tests_foo{key_1="",key_2="issue659",key_3="",key_4="issue659",key_5=""} 1
- tests_foo{key_1="issue659",key_2="",key_3="issue659",key_4="",key_5="issue659"} 1
- `
- if output != want {
- t.Fatalf("output differed from expected output: %s want: %s", output, want)
- }
- }
|