github.com/facebookincubator/go-belt@v0.0.0-20230703220935-39cd348f1a38/tool/experimental/metrics/implementation/prometheus/metrics_test.go (about)

     1  // Copyright 2022 Meta Platforms, Inc. and affiliates.
     2  //
     3  // Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
     4  //
     5  // 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
     6  //
     7  // 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
     8  //
     9  // 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
    10  //
    11  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    12  
    13  // Copyright (c) Facebook, Inc. and its affiliates.
    14  //
    15  // This source code is licensed under the MIT license found in the
    16  // LICENSE file in the root directory of this source tree.
    17  
    18  package prometheus
    19  
    20  import (
    21  	"fmt"
    22  	"sort"
    23  	"strings"
    24  	"testing"
    25  
    26  	"github.com/facebookincubator/go-belt/pkg/field"
    27  	"github.com/facebookincubator/go-belt/tool/experimental/metrics"
    28  	metricstester "github.com/facebookincubator/go-belt/tool/experimental/metrics/tests"
    29  	"github.com/facebookincubator/go-belt/tool/experimental/metrics/types"
    30  	"github.com/prometheus/client_golang/prometheus"
    31  	"github.com/stretchr/testify/assert"
    32  	"github.com/stretchr/testify/require"
    33  )
    34  
    35  func contextFields(fields map[string]interface{}) *field.FieldsChain {
    36  	var cFields *field.FieldsChain
    37  	for k, v := range fields {
    38  		cFields = cFields.WithField(k, v, metrics.AllowInMetrics)
    39  	}
    40  	return cFields
    41  }
    42  
    43  func TestMetrics(t *testing.T) {
    44  	for name, registerer := range map[string]*prometheus.Registry{
    45  		"WithRegisterer":    prometheus.NewRegistry(),
    46  		"WithoutRegisterer": nil,
    47  	} {
    48  		t.Run(name, func(t *testing.T) {
    49  			metricstester.TestMetrics(t, func() types.Metrics {
    50  				cFields := contextFields(map[string]interface{}{"testField": "", "anotherField": ""})
    51  
    52  				// Current implementation resets metrics if new label appears,
    53  				// thus some unit-tests fails (and the should). Specifically
    54  				// for prometheus we decided to tolerate this problem, therefore
    55  				// adding hacks to prevent a wrong values: pre-register metrics
    56  				// with all the labels beforehand.
    57  				m := New(registerer)
    58  				m.WithContextFields(cFields, -1).(types.Metrics).Count("WithResetFields")
    59  				m.WithContextFields(cFields, -1).(types.Metrics).Gauge("WithResetFields")
    60  				m.WithContextFields(cFields, -1).(types.Metrics).IntGauge("WithResetFields")
    61  
    62  				return m
    63  			})
    64  		})
    65  	}
    66  }
    67  
    68  func TestMetricsList(t *testing.T) {
    69  	m := New(nil)
    70  	c0 := m.WithContextFields(contextFields(map[string]interface{}{"testField": "", "anotherField": ""}), -1).(types.Metrics).Count("test")
    71  	g0 := m.WithContextFields(contextFields(map[string]interface{}{"testField": "", "anotherField": ""}), -1).(types.Metrics).Gauge("test")
    72  	i0 := m.WithContextFields(contextFields(map[string]interface{}{"testField": "", "anotherField": ""}), -1).(types.Metrics).IntGauge("test")
    73  	c1 := m.WithContextFields(contextFields(map[string]interface{}{"testField": "a", "anotherField": ""}), -1).(types.Metrics).Count("test")
    74  	g1 := m.WithContextFields(contextFields(map[string]interface{}{"testField": "b", "anotherField": ""}), -1).(types.Metrics).Gauge("test")
    75  	i1 := m.WithContextFields(contextFields(map[string]interface{}{"testField": "c", "anotherField": ""}), -1).(types.Metrics).IntGauge("test")
    76  
    77  	list := m.List()
    78  	require.Len(t, list, 3)
    79  	for _, c := range list {
    80  		ch := make(chan prometheus.Metric)
    81  		go func() {
    82  			c.Collect(ch)
    83  			close(ch)
    84  		}()
    85  
    86  		count := 0
    87  		for range ch {
    88  			count++
    89  		}
    90  		assert.Equal(t, 2, count, fmt.Sprintf("%#+v", c))
    91  	}
    92  	if t.Failed() {
    93  		t.Errorf("c0: %#+v\ng0: %#+v\ni0: %#+v\nc1: %#+v\ng1: %#+v\ni1: %#+v\n",
    94  			c0, g0, i0, c1, g1, i1)
    95  	}
    96  }
    97  
    98  func TestMetricsRegistererDoubleUse(t *testing.T) {
    99  	registry := prometheus.NewRegistry()
   100  	metrics0 := New(registry)
   101  	metrics1 := New(registry)
   102  
   103  	// these test cases should panic:
   104  
   105  	t.Run("Count", func(t *testing.T) {
   106  		defer func() {
   107  			require.NotNil(t, recover())
   108  		}()
   109  
   110  		metrics0.Count("test")
   111  		metrics1.Count("test")
   112  	})
   113  
   114  	t.Run("Gauge", func(t *testing.T) {
   115  		defer func() {
   116  			require.NotNil(t, recover())
   117  		}()
   118  
   119  		metrics0.Gauge("test")
   120  		metrics1.Gauge("test")
   121  	})
   122  
   123  	t.Run("IntGauge", func(t *testing.T) {
   124  		defer func() {
   125  			require.NotNil(t, recover())
   126  		}()
   127  
   128  		metrics0.IntGauge("test")
   129  		metrics1.IntGauge("test")
   130  	})
   131  }
   132  
   133  func TestMergeSortedStrings(t *testing.T) {
   134  	slices := [][]string{
   135  		{"a", "b", "c"},
   136  		{"r", "a", "n", "d", "o", "m"},
   137  		{"a", "rb", "", "it", "r", "ary"},
   138  	}
   139  	for idx := range slices {
   140  		sort.Strings(slices[idx])
   141  	}
   142  	for _, a := range slices {
   143  		for _, b := range slices {
   144  			t.Run(strings.Join(a, "-")+"_"+strings.Join(b, "-"), func(t *testing.T) {
   145  				m := map[string]struct{}{}
   146  				for _, aItem := range a {
   147  					m[aItem] = struct{}{}
   148  				}
   149  				for _, bItem := range b {
   150  					m[bItem] = struct{}{}
   151  				}
   152  				expected := make([]string, 0, len(m))
   153  				for k := range m {
   154  					expected = append(expected, k)
   155  				}
   156  				sort.Strings(expected)
   157  
   158  				require.Equal(t, expected, mergeSortedStrings(a, b...))
   159  			})
   160  		}
   161  	}
   162  }
   163  
   164  func TestDisabledLabels(t *testing.T) {
   165  	registry := prometheus.NewRegistry()
   166  	var m metrics.Metrics = New(registry, OptionDisableLabels(true))
   167  	m = m.WithContextFields(((*field.FieldsChain)(nil)).WithField("1", 2), 1).(metrics.Metrics)
   168  	c := m.Count("someCount")
   169  	require.Len(t, c.(*Count).labelNames, 0)
   170  	c = c.WithResetFields(field.Fields{{Key: "3", Value: 4}})
   171  	require.Len(t, c.(*Count).labelNames, 0)
   172  }