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

     1  // Copyright (c) 2018 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  	"encoding/json"
    25  	"fmt"
    26  	"net/http"
    27  	"strings"
    28  
    29  	"github.com/m3db/m3/src/query/block"
    30  	"github.com/m3db/m3/src/query/storage"
    31  	"github.com/m3db/m3/src/x/headers"
    32  )
    33  
    34  // ReturnedDataLimited is info about whether data was limited by a query.
    35  type ReturnedDataLimited struct {
    36  	Series     int
    37  	Datapoints int
    38  
    39  	// Total series is the total number of series which maybe be >= Series.
    40  	// Truncation happens at the series-level to avoid presenting partial series
    41  	// and so this value is useful for indicating how many series would have
    42  	// been rendered without limiting either series or datapoints.
    43  	TotalSeries int
    44  
    45  	// Limited signals that the results returned were
    46  	// limited by either series or datapoint limits.
    47  	Limited bool
    48  }
    49  
    50  // ReturnedMetadataLimited is info about whether data was limited by a query.
    51  type ReturnedMetadataLimited struct {
    52  	// Results is the total amount of rendered results.
    53  	Results int
    54  	// TotalResults is the total amount of metadata results.
    55  	TotalResults int
    56  	// Limited signals that the results returned were limited by a limit.
    57  	Limited bool
    58  }
    59  
    60  // Waiting is info about an operation waiting for permits.
    61  type Waiting struct {
    62  	// WaitedIndex counts how many times index querying had to wait for permits.
    63  	WaitedIndex int `json:"waitedIndex"`
    64  	// WaitedSeriesRead counts how many times series being read had to wait for permits.
    65  	WaitedSeriesRead int `json:"waitedSeriesRead"`
    66  }
    67  
    68  // WaitedAny returns whether any waiting occurred.
    69  func (w Waiting) WaitedAny() bool {
    70  	return w.WaitedIndex > 0 || w.WaitedSeriesRead > 0
    71  }
    72  
    73  // AddDBResultResponseHeaders adds response headers based on metadata
    74  // and fetch options related to the database result.
    75  func AddDBResultResponseHeaders(
    76  	w http.ResponseWriter,
    77  	meta block.ResultMetadata,
    78  	fetchOpts *storage.FetchOptions,
    79  ) error {
    80  	if fetchOpts != nil {
    81  		w.Header().Set(headers.TimeoutHeader, fetchOpts.Timeout.String())
    82  	}
    83  
    84  	waiting := Waiting{
    85  		WaitedIndex:      meta.WaitedIndex,
    86  		WaitedSeriesRead: meta.WaitedSeriesRead,
    87  	}
    88  
    89  	// NB: only add count headers if present.
    90  	if meta.FetchedSeriesCount > 0 {
    91  		w.Header().Add(headers.FetchedSeriesCount, fmt.Sprint(meta.FetchedSeriesCount))
    92  	}
    93  
    94  	// Merge all of the metadata by name results into `merged` and report on that.
    95  	merged := meta.MetadataByNameMerged()
    96  
    97  	if merged.Aggregated > 0 {
    98  		w.Header().Add(headers.FetchedAggregatedSeriesCount, fmt.Sprint(merged.Aggregated))
    99  	}
   100  
   101  	if merged.Unaggregated > 0 {
   102  		w.Header().Add(headers.FetchedUnaggregatedSeriesCount, fmt.Sprint(merged.Unaggregated))
   103  	}
   104  
   105  	if merged.NoSamples > 0 {
   106  		w.Header().Add(headers.FetchedSeriesNoSamplesCount, fmt.Sprint(merged.NoSamples))
   107  	}
   108  
   109  	if merged.WithSamples > 0 {
   110  		w.Header().Add(headers.FetchedSeriesWithSamplesCount, fmt.Sprint(merged.WithSamples))
   111  	}
   112  
   113  	if namespaces := meta.GetNamespaces(); len(namespaces) > 0 {
   114  		w.Header().Add(headers.NamespacesHeader, strings.Join(namespaces, ","))
   115  	}
   116  
   117  	if meta.FetchedResponses > 0 {
   118  		w.Header().Add(headers.FetchedResponsesHeader, fmt.Sprint(meta.FetchedResponses))
   119  	}
   120  
   121  	if meta.FetchedBytesEstimate > 0 {
   122  		w.Header().Add(headers.FetchedBytesEstimateHeader, fmt.Sprint(meta.FetchedBytesEstimate))
   123  	}
   124  
   125  	// Also report the top metadata by name, in JSON.
   126  	if fetchOpts != nil && fetchOpts.MaxMetricMetadataStats > 0 {
   127  		if stats := meta.TopMetadataByName(fetchOpts.MaxMetricMetadataStats); len(stats) > 0 {
   128  			js, err := json.Marshal(stats)
   129  			if err != nil {
   130  				return err
   131  			}
   132  			w.Header().Add(headers.MetricStats, string(js))
   133  		}
   134  	}
   135  
   136  	if meta.FetchedMetadataCount > 0 {
   137  		w.Header().Add(headers.FetchedMetadataCount, fmt.Sprint(meta.FetchedMetadataCount))
   138  	}
   139  
   140  	if waiting.WaitedAny() {
   141  		s, err := json.Marshal(waiting)
   142  		if err != nil {
   143  			return err
   144  		}
   145  		w.Header().Add(headers.WaitedHeader, string(s))
   146  	}
   147  
   148  	ex := meta.Exhaustive
   149  	warns := len(meta.Warnings)
   150  	if !ex {
   151  		warns++
   152  	}
   153  
   154  	if warns == 0 {
   155  		return nil
   156  	}
   157  
   158  	warnings := make([]string, 0, warns)
   159  	if !ex {
   160  		warnings = append(warnings, headers.LimitHeaderSeriesLimitApplied)
   161  	}
   162  
   163  	for _, warn := range meta.Warnings {
   164  		warnings = append(warnings, warn.Header())
   165  	}
   166  
   167  	w.Header().Set(headers.LimitHeader, strings.Join(warnings, ","))
   168  
   169  	return nil
   170  }
   171  
   172  // AddReturnedLimitResponseHeaders adds headers related to hitting
   173  // limits on the allowed amount of data that can be returned to the client.
   174  func AddReturnedLimitResponseHeaders(
   175  	w http.ResponseWriter,
   176  	returnedDataLimited *ReturnedDataLimited,
   177  	returnedMetadataLimited *ReturnedMetadataLimited,
   178  ) error {
   179  	if limited := returnedDataLimited; limited != nil {
   180  		s, err := json.Marshal(limited)
   181  		if err != nil {
   182  			return err
   183  		}
   184  		w.Header().Add(headers.ReturnedDataLimitedHeader, string(s))
   185  	}
   186  	if limited := returnedMetadataLimited; limited != nil {
   187  		s, err := json.Marshal(limited)
   188  		if err != nil {
   189  			return err
   190  		}
   191  		w.Header().Add(headers.ReturnedMetadataLimitedHeader, string(s))
   192  	}
   193  
   194  	return nil
   195  }