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

     1  // Copyright (c) 2020 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 prometheus
    22  
    23  import (
    24  	"encoding/json"
    25  	"fmt"
    26  	"math"
    27  	"sort"
    28  	"strconv"
    29  	"strings"
    30  )
    31  
    32  // Response represents Prometheus's query response.
    33  type Response struct {
    34  	// Status is the response status.
    35  	Status string `json:"status"`
    36  	// Data is the response data.
    37  	Data data `json:"data"`
    38  }
    39  
    40  type data struct {
    41  	// ResultType is the type of Result (matrix, vector, etc.).
    42  	ResultType string
    43  	// Result contains the query result (concrete type depends on ResultType).
    44  	Result     result
    45  }
    46  
    47  type result interface {
    48  	matches(other result) (MatchInformation, error)
    49  }
    50  
    51  // MatrixResult contains a list matrixRow.
    52  type MatrixResult struct {
    53  	Result []matrixRow `json:"result"`
    54  }
    55  
    56  // VectorResult contains a list of vectorItem.
    57  type VectorResult struct {
    58  	Result []vectorItem `json:"result"`
    59  }
    60  
    61  // ScalarResult is the scalar Value for the response.
    62  type ScalarResult struct {
    63  	Result Value `json:"result"`
    64  }
    65  
    66  // StringResult is the string Value for the response.
    67  type StringResult struct {
    68  	Result Value `json:"result"`
    69  }
    70  
    71  // UnmarshalJSON unmarshals the data struct of query response.
    72  func (d *data) UnmarshalJSON(bytes []byte) error {
    73  	var discriminator struct {
    74  		ResultType string `json:"resultType"`
    75  	}
    76  	if err := json.Unmarshal(bytes, &discriminator); err != nil {
    77  		return err
    78  	}
    79  	*d = data{ResultType: discriminator.ResultType}
    80  
    81  	switch discriminator.ResultType {
    82  
    83  	case "matrix":
    84  		d.Result = &MatrixResult{}
    85  
    86  	case "vector":
    87  		d.Result = &VectorResult{}
    88  
    89  	case "scalar":
    90  		d.Result = &ScalarResult{}
    91  
    92  	case "string":
    93  		d.Result = &StringResult{}
    94  
    95  	default:
    96  		return fmt.Errorf("unknown resultType: %s", discriminator.ResultType)
    97  	}
    98  
    99  	return json.Unmarshal(bytes, d.Result)
   100  }
   101  
   102  // Len is the number of elements in the collection.
   103  func (r MatrixResult) Len() int { return len(r.Result) }
   104  
   105  // Less reports whether the element with
   106  // index i should sort before the element with index j.
   107  func (r MatrixResult) Less(i, j int) bool {
   108  	return r.Result[i].id < r.Result[j].id
   109  }
   110  
   111  // Swap swaps the elements with indexes i and j.
   112  func (r MatrixResult) Swap(i, j int) { r.Result[i], r.Result[j] = r.Result[j], r.Result[i] }
   113  
   114  // Sort sorts the MatrixResult.
   115  func (r MatrixResult) Sort() {
   116  	for i, result := range r.Result {
   117  		r.Result[i].id = result.Metric.genID()
   118  	}
   119  
   120  	sort.Sort(r)
   121  }
   122  
   123  // Len is the number of elements in the vector.
   124  func (r VectorResult) Len() int { return len(r.Result) }
   125  
   126  // Less reports whether the element with
   127  // index i should sort before the element with index j.
   128  func (r VectorResult) Less(i, j int) bool {
   129  	return r.Result[i].id < r.Result[j].id
   130  }
   131  
   132  // Swap swaps the elements with indexes i and j.
   133  func (r VectorResult) Swap(i, j int) { r.Result[i], r.Result[j] = r.Result[j], r.Result[i] }
   134  
   135  // Sort sorts the VectorResult.
   136  func (r VectorResult) Sort() {
   137  	for i, result := range r.Result {
   138  		r.Result[i].id = result.Metric.genID()
   139  	}
   140  
   141  	sort.Sort(r)
   142  }
   143  
   144  // matrixRow is a single row of "matrix" Result.
   145  type matrixRow struct {
   146  	// Metric is the tags for the matrixRow.
   147  	Metric Tags `json:"metric"`
   148  	// Values is the set of values for the matrixRow.
   149  	Values Values `json:"values"`
   150  	id     string
   151  }
   152  
   153  // vectorItem is a single item of "vector" Result.
   154  type vectorItem struct {
   155  	// Metric is the tags for the vectorItem.
   156  	Metric Tags `json:"metric"`
   157  	// Value is the value for the vectorItem.
   158  	Value Value `json:"value"`
   159  	id    string
   160  }
   161  
   162  // Tags is a simple representation of Prometheus tags.
   163  type Tags map[string]string
   164  
   165  // Values is a list of values for the Prometheus Result.
   166  type Values []Value
   167  
   168  // Value is a single value for Prometheus Result.
   169  type Value []interface{}
   170  
   171  func (t *Tags) genID() string {
   172  	tags := make(sort.StringSlice, len(*t))
   173  	for k, v := range *t {
   174  		tags = append(tags, fmt.Sprintf("%s:%s,", k, v))
   175  	}
   176  
   177  	sort.Sort(tags)
   178  	var sb strings.Builder
   179  	// NB: this may clash but exact tag values are also checked, and this is a
   180  	// validation endpoint so there's less concern over correctness.
   181  	for _, t := range tags {
   182  		sb.WriteString(t)
   183  	}
   184  
   185  	return sb.String()
   186  }
   187  
   188  // MatchInformation describes how well two responses match.
   189  type MatchInformation struct {
   190  	// FullMatch indicates a full match.
   191  	FullMatch bool
   192  	// NoMatch indicates that the responses do not match sufficiently.
   193  	NoMatch bool
   194  }
   195  
   196  // Matches compares two responses and determines how closely they match.
   197  func (r Response) Matches(other Response) (MatchInformation, error) {
   198  	if r.Status != other.Status {
   199  		err := fmt.Errorf("status %s does not match other status %s",
   200  			r.Status, other.Status)
   201  		return MatchInformation{
   202  			NoMatch: true,
   203  		}, err
   204  	}
   205  
   206  	if r.Status == "error" {
   207  		return MatchInformation{
   208  			FullMatch: true,
   209  		}, nil
   210  	}
   211  
   212  	return r.Data.matches(other.Data)
   213  }
   214  
   215  func (d data) matches(other data) (MatchInformation, error) {
   216  	if d.ResultType != other.ResultType {
   217  		err := fmt.Errorf("result type %s does not match other result type %s",
   218  			d.ResultType, other.ResultType)
   219  		return MatchInformation{
   220  			NoMatch: true,
   221  		}, err
   222  	}
   223  
   224  	return d.Result.matches(other.Result)
   225  }
   226  
   227  func (r MatrixResult) matches(other result) (MatchInformation, error) {
   228  	otherMatrix, ok := other.(*MatrixResult)
   229  	if !ok {
   230  		err := fmt.Errorf("incorrect type for matching, expected MatrixResult, %v", other)
   231  		return MatchInformation{
   232  			NoMatch: true,
   233  		}, err
   234  	}
   235  
   236  	if len(r.Result) != len(otherMatrix.Result) {
   237  		err := fmt.Errorf("result length %d does not match other result length %d",
   238  			len(r.Result), len(otherMatrix.Result))
   239  		return MatchInformation{
   240  			NoMatch: true,
   241  		}, err
   242  	}
   243  
   244  	r.Sort()
   245  	otherMatrix.Sort()
   246  	for i, result := range r.Result {
   247  		if err := result.matches(otherMatrix.Result[i]); err != nil {
   248  			return MatchInformation{
   249  				NoMatch: true,
   250  			}, err
   251  		}
   252  	}
   253  
   254  	return MatchInformation{FullMatch: true}, nil
   255  }
   256  
   257  func (r VectorResult) matches(other result) (MatchInformation, error) {
   258  	otherVector, ok := other.(*VectorResult)
   259  	if !ok {
   260  		err := fmt.Errorf("incorrect type for matching, expected VectorResult")
   261  		return MatchInformation{
   262  			NoMatch: true,
   263  		}, err
   264  	}
   265  
   266  	if len(r.Result) != len(otherVector.Result) {
   267  		err := fmt.Errorf("result length %d does not match other result length %d",
   268  			len(r.Result), len(otherVector.Result))
   269  		return MatchInformation{
   270  			NoMatch: true,
   271  		}, err
   272  	}
   273  
   274  	r.Sort()
   275  	otherVector.Sort()
   276  	for i, result := range r.Result {
   277  		if err := result.matches(otherVector.Result[i]); err != nil {
   278  			return MatchInformation{
   279  				NoMatch: true,
   280  			}, err
   281  		}
   282  	}
   283  
   284  	return MatchInformation{FullMatch: true}, nil
   285  }
   286  
   287  func (r ScalarResult) matches(other result) (MatchInformation, error) {
   288  	otherScalar, ok := other.(*ScalarResult)
   289  	if !ok {
   290  		err := fmt.Errorf("incorrect type for matching, expected ScalarResult")
   291  		return MatchInformation{
   292  			NoMatch: true,
   293  		}, err
   294  	}
   295  
   296  	if err := r.Result.matches(otherScalar.Result); err != nil {
   297  		return MatchInformation{
   298  			NoMatch: true,
   299  		}, err
   300  	}
   301  
   302  	return MatchInformation{FullMatch: true}, nil
   303  }
   304  
   305  func (r StringResult) matches(other result) (MatchInformation, error) {
   306  	otherString, ok := other.(*StringResult)
   307  	if !ok {
   308  		err := fmt.Errorf("incorrect type for matching, expected StringResult")
   309  		return MatchInformation{
   310  			NoMatch: true,
   311  		}, err
   312  	}
   313  
   314  	if err := r.Result.matches(otherString.Result); err != nil {
   315  		return MatchInformation{
   316  			NoMatch: true,
   317  		}, err
   318  	}
   319  
   320  	return MatchInformation{FullMatch: true}, nil
   321  }
   322  
   323  func (r matrixRow) matches(other matrixRow) error {
   324  	// NB: tags should match by here so this is more of a sanity check.
   325  	if err := r.Metric.matches(other.Metric); err != nil {
   326  		return err
   327  	}
   328  
   329  	return r.Values.matches(other.Values)
   330  }
   331  
   332  func (r vectorItem) matches(other vectorItem) error {
   333  	// NB: tags should match by here so this is more of a sanity check.
   334  	if err := r.Metric.matches(other.Metric); err != nil {
   335  		return err
   336  	}
   337  
   338  	return r.Value.matches(other.Value)
   339  }
   340  
   341  func (t Tags) matches(other Tags) error {
   342  	if len(t) != len(other) {
   343  		return fmt.Errorf("tag length %d does not match other tag length %d",
   344  			len(t), len(other))
   345  	}
   346  
   347  	for k, v := range t {
   348  		if vv, ok := other[k]; ok {
   349  			if v != vv {
   350  				return fmt.Errorf("tag %s value %s does not match other tag value %s", k, v, vv)
   351  			}
   352  		} else {
   353  			return fmt.Errorf("tag %s not found in other tagset", v)
   354  		}
   355  	}
   356  
   357  	return nil
   358  }
   359  
   360  func (v Values) matches(other Values) error {
   361  	if len(v) != len(other) {
   362  		return fmt.Errorf("values length %d does not match other values length %d",
   363  			len(v), len(other))
   364  	}
   365  
   366  	for i, val := range v {
   367  		if err := val.matches(other[i]); err != nil {
   368  			return err
   369  		}
   370  	}
   371  
   372  	return nil
   373  }
   374  
   375  func (v Value) matches(other Value) error {
   376  	if len(v) != 2 {
   377  		return fmt.Errorf("value length %d must be 2", len(v))
   378  	}
   379  
   380  	if len(other) != 2 {
   381  		return fmt.Errorf("other value length %d must be 2", len(other))
   382  	}
   383  
   384  	tsV := fmt.Sprint(v[0])
   385  	tsOther := fmt.Sprint(other[0])
   386  	if tsV != tsOther {
   387  		return fmt.Errorf("ts %s does not match other ts %s", tsV, tsOther)
   388  	}
   389  
   390  	valV, err := strconv.ParseFloat(fmt.Sprint(v[1]), 64)
   391  	if err != nil {
   392  		return err
   393  	}
   394  
   395  	valOther, err := strconv.ParseFloat(fmt.Sprint(other[1]), 64)
   396  	if err != nil {
   397  		return err
   398  	}
   399  
   400  	if math.Abs(valV-valOther) > tolerance {
   401  		return fmt.Errorf("point %f does not match other point %f", valV, valOther)
   402  	}
   403  
   404  	return nil
   405  }