1// Copyright 2014 The Prometheus Authors
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14package prometheus
15
16import (
17	"math"
18	"math/rand"
19	"sync"
20	"testing"
21	"testing/quick"
22	"time"
23
24	dto "github.com/prometheus/client_model/go"
25)
26
27func listenGaugeStream(vals, result chan float64, done chan struct{}) {
28	var sum float64
29outer:
30	for {
31		select {
32		case <-done:
33			close(vals)
34			for v := range vals {
35				sum += v
36			}
37			break outer
38		case v := <-vals:
39			sum += v
40		}
41	}
42	result <- sum
43	close(result)
44}
45
46func TestGaugeConcurrency(t *testing.T) {
47	it := func(n uint32) bool {
48		mutations := int(n % 10000)
49		concLevel := int(n%15 + 1)
50
51		var start, end sync.WaitGroup
52		start.Add(1)
53		end.Add(concLevel)
54
55		sStream := make(chan float64, mutations*concLevel)
56		result := make(chan float64)
57		done := make(chan struct{})
58
59		go listenGaugeStream(sStream, result, done)
60		go func() {
61			end.Wait()
62			close(done)
63		}()
64
65		gge := NewGauge(GaugeOpts{
66			Name: "test_gauge",
67			Help: "no help can be found here",
68		})
69		for i := 0; i < concLevel; i++ {
70			vals := make([]float64, mutations)
71			for j := 0; j < mutations; j++ {
72				vals[j] = rand.Float64() - 0.5
73			}
74
75			go func(vals []float64) {
76				start.Wait()
77				for _, v := range vals {
78					sStream <- v
79					gge.Add(v)
80				}
81				end.Done()
82			}(vals)
83		}
84		start.Done()
85
86		if expected, got := <-result, math.Float64frombits(gge.(*gauge).valBits); math.Abs(expected-got) > 0.000001 {
87			t.Fatalf("expected approx. %f, got %f", expected, got)
88			return false
89		}
90		return true
91	}
92
93	if err := quick.Check(it, nil); err != nil {
94		t.Fatal(err)
95	}
96}
97
98func TestGaugeVecConcurrency(t *testing.T) {
99	it := func(n uint32) bool {
100		mutations := int(n % 10000)
101		concLevel := int(n%15 + 1)
102		vecLength := int(n%5 + 1)
103
104		var start, end sync.WaitGroup
105		start.Add(1)
106		end.Add(concLevel)
107
108		sStreams := make([]chan float64, vecLength)
109		results := make([]chan float64, vecLength)
110		done := make(chan struct{})
111
112		for i := 0; i < vecLength; i++ {
113			sStreams[i] = make(chan float64, mutations*concLevel)
114			results[i] = make(chan float64)
115			go listenGaugeStream(sStreams[i], results[i], done)
116		}
117
118		go func() {
119			end.Wait()
120			close(done)
121		}()
122
123		gge := NewGaugeVec(
124			GaugeOpts{
125				Name: "test_gauge",
126				Help: "no help can be found here",
127			},
128			[]string{"label"},
129		)
130		for i := 0; i < concLevel; i++ {
131			vals := make([]float64, mutations)
132			pick := make([]int, mutations)
133			for j := 0; j < mutations; j++ {
134				vals[j] = rand.Float64() - 0.5
135				pick[j] = rand.Intn(vecLength)
136			}
137
138			go func(vals []float64) {
139				start.Wait()
140				for i, v := range vals {
141					sStreams[pick[i]] <- v
142					gge.WithLabelValues(string('A' + pick[i])).Add(v)
143				}
144				end.Done()
145			}(vals)
146		}
147		start.Done()
148
149		for i := range sStreams {
150			if expected, got := <-results[i], math.Float64frombits(gge.WithLabelValues(string('A'+i)).(*gauge).valBits); math.Abs(expected-got) > 0.000001 {
151				t.Fatalf("expected approx. %f, got %f", expected, got)
152				return false
153			}
154		}
155		return true
156	}
157
158	if err := quick.Check(it, nil); err != nil {
159		t.Fatal(err)
160	}
161}
162
163func TestGaugeFunc(t *testing.T) {
164	gf := NewGaugeFunc(
165		GaugeOpts{
166			Name:        "test_name",
167			Help:        "test help",
168			ConstLabels: Labels{"a": "1", "b": "2"},
169		},
170		func() float64 { return 3.1415 },
171	)
172
173	if expected, got := `Desc{fqName: "test_name", help: "test help", constLabels: {a="1",b="2"}, variableLabels: []}`, gf.Desc().String(); expected != got {
174		t.Errorf("expected %q, got %q", expected, got)
175	}
176
177	m := &dto.Metric{}
178	gf.Write(m)
179
180	if expected, got := `label:<name:"a" value:"1" > label:<name:"b" value:"2" > gauge:<value:3.1415 > `, m.String(); expected != got {
181		t.Errorf("expected %q, got %q", expected, got)
182	}
183}
184
185func TestGaugeSetCurrentTime(t *testing.T) {
186	g := NewGauge(GaugeOpts{
187		Name: "test_name",
188		Help: "test help",
189	})
190	g.SetToCurrentTime()
191	unixTime := float64(time.Now().Unix())
192
193	m := &dto.Metric{}
194	g.Write(m)
195
196	delta := unixTime - m.GetGauge().GetValue()
197	// This is just a smoke test to make sure SetToCurrentTime is not
198	// totally off. Tests with current time involved are hard...
199	if math.Abs(delta) > 5 {
200		t.Errorf("Gauge set to current time deviates from current time by more than 5s, delta is %f seconds", delta)
201	}
202}
203