github.com/m3db/m3@v1.5.0/src/query/api/v1/handler/prometheus/handleroptions/header_test.go (about)

     1  // Copyright (c) 2019 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package handleroptions
    22  
    23  import (
    24  	"context"
    25  	"encoding/json"
    26  	"fmt"
    27  	"net/http/httptest"
    28  	"testing"
    29  	"time"
    30  
    31  	"github.com/m3db/m3/src/query/block"
    32  	"github.com/m3db/m3/src/query/storage"
    33  	"github.com/m3db/m3/src/x/headers"
    34  
    35  	"github.com/stretchr/testify/assert"
    36  	"github.com/stretchr/testify/require"
    37  )
    38  
    39  func TestAddDBResultResponseHeaders(t *testing.T) {
    40  	recorder := httptest.NewRecorder()
    41  	meta := block.NewResultMetadata()
    42  	require.NoError(t, AddDBResultResponseHeaders(recorder, meta, nil))
    43  	assert.Equal(t, 0, len(recorder.Header()))
    44  
    45  	recorder = httptest.NewRecorder()
    46  	meta.Exhaustive = false
    47  	ex := headers.LimitHeaderSeriesLimitApplied
    48  	require.NoError(t, AddDBResultResponseHeaders(recorder, meta, nil))
    49  	assert.Equal(t, 1, len(recorder.Header()))
    50  	assert.Equal(t, ex, recorder.Header().Get(headers.LimitHeader))
    51  
    52  	recorder = httptest.NewRecorder()
    53  	meta.AddWarning("foo", "bar")
    54  	ex = fmt.Sprintf("%s,%s_%s", headers.LimitHeaderSeriesLimitApplied, "foo", "bar")
    55  	require.NoError(t, AddDBResultResponseHeaders(recorder, meta, nil))
    56  	assert.Equal(t, 1, len(recorder.Header()))
    57  	assert.Equal(t, ex, recorder.Header().Get(headers.LimitHeader))
    58  
    59  	recorder = httptest.NewRecorder()
    60  	meta.Exhaustive = true
    61  	ex = "foo_bar"
    62  	require.NoError(t, AddDBResultResponseHeaders(recorder, meta, nil))
    63  	assert.Equal(t, 1, len(recorder.Header()))
    64  	assert.Equal(t, ex, recorder.Header().Get(headers.LimitHeader))
    65  
    66  	recorder = httptest.NewRecorder()
    67  	meta = block.NewResultMetadata()
    68  	require.NoError(t, AddDBResultResponseHeaders(recorder, meta, &storage.FetchOptions{
    69  		Timeout: 5 * time.Second,
    70  	}))
    71  	assert.Equal(t, 1, len(recorder.Header()))
    72  	assert.Equal(t, "5s", recorder.Header().Get(headers.TimeoutHeader))
    73  
    74  	recorder = httptest.NewRecorder()
    75  	meta = block.NewResultMetadata()
    76  	meta.WaitedIndex = 3
    77  	meta.WaitedSeriesRead = 42
    78  	require.NoError(t, AddDBResultResponseHeaders(recorder, meta, nil))
    79  	assert.Equal(t, 1, len(recorder.Header()))
    80  	assert.Equal(t, "{\"waitedIndex\":3,\"waitedSeriesRead\":42}",
    81  		recorder.Header().Get(headers.WaitedHeader))
    82  }
    83  
    84  func TestAddDBResultResponseHeadersFetched(t *testing.T) {
    85  	recorder := httptest.NewRecorder()
    86  	meta := block.NewResultMetadata()
    87  	meta.FetchedSeriesCount = 42
    88  	meta.FetchedMetadataCount = 142
    89  	meta.FetchedResponses = 99
    90  	meta.FetchedBytesEstimate = 1072
    91  	require.NoError(t, AddDBResultResponseHeaders(recorder, meta, nil))
    92  	assert.Equal(t, 4, len(recorder.Header()))
    93  	assert.Equal(t, "99", recorder.Header().Get(headers.FetchedResponsesHeader))
    94  	assert.Equal(t, "1072", recorder.Header().Get(headers.FetchedBytesEstimateHeader))
    95  	assert.Equal(t, "42", recorder.Header().Get(headers.FetchedSeriesCount))
    96  	assert.Equal(t, "142", recorder.Header().Get(headers.FetchedMetadataCount))
    97  }
    98  
    99  func TestAddDBResultResponseHeadersNamespaces(t *testing.T) {
   100  	recorder := httptest.NewRecorder()
   101  	meta := block.NewResultMetadata()
   102  	require.NoError(t, AddDBResultResponseHeaders(recorder, meta, nil))
   103  	assert.Equal(t, 0, len(recorder.Header()))
   104  
   105  	recorder = httptest.NewRecorder()
   106  	meta = block.NewResultMetadata()
   107  	meta.AddNamespace("default")
   108  	require.NoError(t, AddDBResultResponseHeaders(recorder, meta, nil))
   109  	assert.Equal(t, 1, len(recorder.Header()))
   110  	assert.Equal(t, "default", recorder.Header().Get(headers.NamespacesHeader))
   111  
   112  	recorder = httptest.NewRecorder()
   113  	meta = block.NewResultMetadata()
   114  	meta.AddNamespace("default")
   115  	meta.AddNamespace("myfavoritens")
   116  	meta.AddNamespace("myfavoritens")
   117  	require.NoError(t, AddDBResultResponseHeaders(recorder, meta, nil))
   118  	assert.Equal(t, 1, len(recorder.Header()))
   119  	assert.Equal(t, "default,myfavoritens", recorder.Header().Get(headers.NamespacesHeader))
   120  }
   121  
   122  func TestAddDBResultResponseHeadersMetadataByName(t *testing.T) {
   123  	recorder := httptest.NewRecorder()
   124  	meta := block.NewResultMetadata()
   125  	*(meta.ByName([]byte("mymetric"))) = block.ResultMetricMetadata{
   126  		NoSamples:    1,
   127  		WithSamples:  2,
   128  		Aggregated:   3,
   129  		Unaggregated: 4,
   130  	}
   131  	fetchOpts := storage.NewFetchOptions()
   132  	fetchOpts.MaxMetricMetadataStats = 10
   133  	require.NoError(t, AddDBResultResponseHeaders(recorder, meta, fetchOpts))
   134  	assert.Equal(t, 6, len(recorder.Header()))
   135  	assert.Equal(t, "0s", recorder.Header().Get(headers.TimeoutHeader))
   136  	assert.Equal(t, "1", recorder.Header().Get(headers.FetchedSeriesNoSamplesCount))
   137  	assert.Equal(t, "2", recorder.Header().Get(headers.FetchedSeriesWithSamplesCount))
   138  	assert.Equal(t, "3", recorder.Header().Get(headers.FetchedAggregatedSeriesCount))
   139  	assert.Equal(t, "4", recorder.Header().Get(headers.FetchedUnaggregatedSeriesCount))
   140  	assert.Equal(t,
   141  		"{\"mymetric\":{\"NoSamples\":1,\"WithSamples\":2,\"Aggregated\":3,\"Unaggregated\":4}}",
   142  		recorder.Header().Get(headers.MetricStats))
   143  
   144  	recorder = httptest.NewRecorder()
   145  	meta = block.NewResultMetadata()
   146  	*(meta.ByName([]byte("metric_a"))) = block.ResultMetricMetadata{
   147  		NoSamples:    1,
   148  		WithSamples:  2,
   149  		Aggregated:   3,
   150  		Unaggregated: 4,
   151  	}
   152  	*(meta.ByName([]byte("metric_b"))) = block.ResultMetricMetadata{
   153  		NoSamples:    10,
   154  		WithSamples:  20,
   155  		Aggregated:   30,
   156  		Unaggregated: 40,
   157  	}
   158  	require.NoError(t, AddDBResultResponseHeaders(recorder, meta, fetchOpts))
   159  	assert.Equal(t, 6, len(recorder.Header()))
   160  	assert.Equal(t, "0s", recorder.Header().Get(headers.TimeoutHeader))
   161  	assert.Equal(t, "11", recorder.Header().Get(headers.FetchedSeriesNoSamplesCount))
   162  	assert.Equal(t, "22", recorder.Header().Get(headers.FetchedSeriesWithSamplesCount))
   163  	assert.Equal(t, "33", recorder.Header().Get(headers.FetchedAggregatedSeriesCount))
   164  	assert.Equal(t, "44", recorder.Header().Get(headers.FetchedUnaggregatedSeriesCount))
   165  	assert.Equal(t,
   166  		"{\"metric_a\":{\"NoSamples\":1,\"WithSamples\":2,\"Aggregated\":3,\"Unaggregated\":4},"+
   167  			"\"metric_b\":{\"NoSamples\":10,\"WithSamples\":20,\"Aggregated\":30,\"Unaggregated\":40}}",
   168  		recorder.Header().Get(headers.MetricStats))
   169  
   170  	recorder = httptest.NewRecorder()
   171  	meta = block.NewResultMetadata()
   172  	numStats := fetchOpts.MaxMetricMetadataStats + 2
   173  	totalCount := 0
   174  	for i := 0; i < numStats; i++ {
   175  		count := i + 1
   176  		meta.ByName([]byte(fmt.Sprintf("metric_%v", i))).Unaggregated = count
   177  		totalCount += count
   178  	}
   179  	require.NoError(t, AddDBResultResponseHeaders(recorder, meta, fetchOpts))
   180  	assert.Equal(t, 3, len(recorder.Header()))
   181  	assert.Equal(t, "0s", recorder.Header().Get(headers.TimeoutHeader))
   182  	assert.Equal(t, fmt.Sprint(totalCount), recorder.Header().Get(headers.FetchedUnaggregatedSeriesCount))
   183  
   184  	parsed := make(map[string]*block.ResultMetricMetadata)
   185  	metricStatsHeader := recorder.Header().Get(headers.MetricStats)
   186  	assert.NotEmpty(t, metricStatsHeader)
   187  	err := json.Unmarshal([]byte(metricStatsHeader), &parsed)
   188  	assert.NoError(t, err)
   189  	assert.Equal(t, fetchOpts.MaxMetricMetadataStats, len(parsed))
   190  	observedCount := 0
   191  	for _, stat := range parsed {
   192  		observedCount += stat.Unaggregated
   193  	}
   194  	// The total count includes `max+2` counters. The bottom two values of those 12 are 1 and 2.
   195  	// So we want to see the total minus (1 + 2), which means the count contains only the
   196  	// top `max` counts.
   197  	wantCount := totalCount - (1 + 2)
   198  	assert.Equal(t, observedCount, wantCount)
   199  }
   200  
   201  func TestAddDBResultResponseHeadersMetadataByNameMaxConfig(t *testing.T) {
   202  	recorder := httptest.NewRecorder()
   203  	meta := block.NewResultMetadata()
   204  	*(meta.ByName([]byte("mymetric"))) = block.ResultMetricMetadata{
   205  		NoSamples:    1,
   206  		WithSamples:  2,
   207  		Aggregated:   3,
   208  		Unaggregated: 4,
   209  	}
   210  
   211  	// Disable metric metadata stats using a header
   212  	req := httptest.NewRequest("GET", "/api/v1/query", nil)
   213  	req.Header.Add(headers.LimitMaxMetricMetadataStatsHeader, "0")
   214  	fetchOptsBuilder, err := NewFetchOptionsBuilder(
   215  		FetchOptionsBuilderOptions{
   216  			Timeout: 5 * time.Second,
   217  		},
   218  	)
   219  	require.NoError(t, err)
   220  	_, fetchOpts, err := fetchOptsBuilder.NewFetchOptions(context.Background(), req)
   221  	require.NoError(t, err)
   222  	require.NoError(t, AddDBResultResponseHeaders(recorder, meta, fetchOpts))
   223  	assert.Equal(t, 5, len(recorder.Header()))
   224  	assert.Equal(t, "5s", recorder.Header().Get(headers.TimeoutHeader))
   225  	assert.Equal(t, "1", recorder.Header().Get(headers.FetchedSeriesNoSamplesCount))
   226  	assert.Equal(t, "2", recorder.Header().Get(headers.FetchedSeriesWithSamplesCount))
   227  	assert.Equal(t, "3", recorder.Header().Get(headers.FetchedAggregatedSeriesCount))
   228  	assert.Equal(t, "4", recorder.Header().Get(headers.FetchedUnaggregatedSeriesCount))
   229  	assert.Empty(t, recorder.Header().Get(headers.MetricStats))
   230  
   231  	// Disable metric metadata stats using config
   232  	recorder = httptest.NewRecorder()
   233  	fetchOpts.MaxMetricMetadataStats = 0
   234  	require.NoError(t, AddDBResultResponseHeaders(recorder, meta, fetchOpts))
   235  	assert.Equal(t, 5, len(recorder.Header()))
   236  	assert.Equal(t, "5s", recorder.Header().Get(headers.TimeoutHeader))
   237  	assert.Equal(t, "1", recorder.Header().Get(headers.FetchedSeriesNoSamplesCount))
   238  	assert.Equal(t, "2", recorder.Header().Get(headers.FetchedSeriesWithSamplesCount))
   239  	assert.Equal(t, "3", recorder.Header().Get(headers.FetchedAggregatedSeriesCount))
   240  	assert.Equal(t, "4", recorder.Header().Get(headers.FetchedUnaggregatedSeriesCount))
   241  	assert.Empty(t, recorder.Header().Get(headers.MetricStats))
   242  }
   243  
   244  func TestAddReturnedLimitResponseHeaders(t *testing.T) {
   245  	recorder := httptest.NewRecorder()
   246  	require.NoError(t, AddReturnedLimitResponseHeaders(recorder, &ReturnedDataLimited{
   247  		Series:      3,
   248  		Datapoints:  6,
   249  		TotalSeries: 3,
   250  		Limited:     false,
   251  	}, nil))
   252  	assert.Equal(t, 1, len(recorder.Header()))
   253  	assert.Equal(t, "{\"Series\":3,\"Datapoints\":6,\"TotalSeries\":3,\"Limited\":false}",
   254  		recorder.Header().Get(headers.ReturnedDataLimitedHeader))
   255  
   256  	recorder = httptest.NewRecorder()
   257  	require.NoError(t, AddReturnedLimitResponseHeaders(recorder, nil, &ReturnedMetadataLimited{
   258  		Results:      3,
   259  		TotalResults: 3,
   260  		Limited:      false,
   261  	}))
   262  	assert.Equal(t, 1, len(recorder.Header()))
   263  	assert.Equal(t, "{\"Results\":3,\"TotalResults\":3,\"Limited\":false}",
   264  		recorder.Header().Get(headers.ReturnedMetadataLimitedHeader))
   265  }