github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/query/remote/codecs.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 remote
    22  
    23  import (
    24  	"context"
    25  	"fmt"
    26  	"net/http"
    27  	"strings"
    28  	"time"
    29  
    30  	"github.com/m3db/m3/src/metrics/policy"
    31  	"github.com/m3db/m3/src/query/api/v1/handler/prometheus/handleroptions"
    32  	"github.com/m3db/m3/src/query/block"
    33  	"github.com/m3db/m3/src/query/generated/proto/rpcpb"
    34  	rpc "github.com/m3db/m3/src/query/generated/proto/rpcpb"
    35  	"github.com/m3db/m3/src/query/models"
    36  	"github.com/m3db/m3/src/query/storage"
    37  	"github.com/m3db/m3/src/query/storage/m3/storagemetadata"
    38  	"github.com/m3db/m3/src/query/util/logging"
    39  	"github.com/m3db/m3/src/x/instrument"
    40  	xtime "github.com/m3db/m3/src/x/time"
    41  
    42  	"google.golang.org/grpc/metadata"
    43  )
    44  
    45  const reqIDKey = "reqid"
    46  
    47  func fromTime(t time.Time) int64 {
    48  	return storage.TimeToPromTimestamp(xtime.ToUnixNano(t))
    49  }
    50  
    51  func toTime(t int64) time.Time {
    52  	return storage.PromTimestampToTime(t)
    53  }
    54  
    55  func encodeTags(tags models.Tags) []*rpc.Tag {
    56  	encodedTags := make([]*rpc.Tag, 0, tags.Len())
    57  	for _, t := range tags.Tags {
    58  		encodedTags = append(encodedTags, &rpc.Tag{
    59  			Name:  t.Name,
    60  			Value: t.Value,
    61  		})
    62  	}
    63  
    64  	return encodedTags
    65  }
    66  
    67  // encodeFetchResult  encodes fetch result to rpc response
    68  func encodeFetchResult(results *storage.FetchResult) *rpc.FetchResponse {
    69  	series := make([]*rpc.Series, len(results.SeriesList))
    70  	for i, result := range results.SeriesList {
    71  		vLen := result.Len()
    72  		datapoints := make([]*rpc.Datapoint, vLen)
    73  		for j := 0; j < vLen; j++ {
    74  			dp := result.Values().DatapointAt(j)
    75  			datapoints[j] = &rpc.Datapoint{
    76  				Timestamp: int64(dp.Timestamp),
    77  				Value:     dp.Value,
    78  			}
    79  		}
    80  
    81  		series[i] = &rpc.Series{
    82  			Meta: &rpc.SeriesMetadata{
    83  				Id: result.Name(),
    84  			},
    85  			Value: &rpc.Series_Decompressed{
    86  				Decompressed: &rpc.DecompressedSeries{
    87  					Datapoints: datapoints,
    88  					Tags:       encodeTags(result.Tags),
    89  				},
    90  			},
    91  		}
    92  	}
    93  
    94  	return &rpc.FetchResponse{
    95  		Series: series,
    96  		Meta:   encodeResultMetadata(results.Metadata),
    97  	}
    98  }
    99  
   100  // encodeFetchRequest encodes fetch request into rpc FetchRequest
   101  func encodeFetchRequest(
   102  	query *storage.FetchQuery,
   103  	options *storage.FetchOptions,
   104  ) (*rpc.FetchRequest, error) {
   105  	matchers, err := encodeTagMatchers(query.TagMatchers)
   106  	if err != nil {
   107  		return nil, err
   108  	}
   109  
   110  	opts, err := encodeFetchOptions(options)
   111  	if err != nil {
   112  		return nil, err
   113  	}
   114  
   115  	return &rpc.FetchRequest{
   116  		Start: fromTime(query.Start),
   117  		End:   fromTime(query.End),
   118  		Matchers: &rpc.FetchRequest_TagMatchers{
   119  			TagMatchers: matchers,
   120  		},
   121  		Options: opts,
   122  	}, nil
   123  }
   124  
   125  func encodeTagMatchers(modelMatchers models.Matchers) (*rpc.TagMatchers, error) {
   126  	if modelMatchers == nil {
   127  		return nil, nil
   128  	}
   129  
   130  	matchers := make([]*rpc.TagMatcher, len(modelMatchers))
   131  	for i, matcher := range modelMatchers {
   132  		t, err := encodeMatcherTypeToProto(matcher.Type)
   133  		if err != nil {
   134  			return nil, err
   135  		}
   136  
   137  		matchers[i] = &rpc.TagMatcher{
   138  			Name:  matcher.Name,
   139  			Value: matcher.Value,
   140  			Type:  t,
   141  		}
   142  	}
   143  
   144  	return &rpc.TagMatchers{
   145  		TagMatchers: matchers,
   146  	}, nil
   147  }
   148  
   149  func encodeFanoutOption(opt storage.FanoutOption) (rpc.FanoutOption, error) {
   150  	switch opt {
   151  	case storage.FanoutDefault:
   152  		return rpc.FanoutOption_DEFAULT_OPTION, nil
   153  	case storage.FanoutForceDisable:
   154  		return rpc.FanoutOption_FORCE_DISABLED, nil
   155  	case storage.FanoutForceEnable:
   156  		return rpc.FanoutOption_FORCE_ENABLED, nil
   157  	}
   158  
   159  	return 0, fmt.Errorf("unknown fanout option for proto encoding: %v", opt)
   160  }
   161  
   162  func encodeFetchOptions(options *storage.FetchOptions) (*rpc.FetchOptions, error) {
   163  	if options == nil {
   164  		return nil, nil
   165  	}
   166  
   167  	fanoutOpts := options.FanoutOptions
   168  	result := &rpc.FetchOptions{
   169  		Limit:  int64(options.SeriesLimit),
   170  		Source: options.Source,
   171  	}
   172  
   173  	unagg, err := encodeFanoutOption(fanoutOpts.FanoutUnaggregated)
   174  	if err != nil {
   175  		return nil, err
   176  	}
   177  
   178  	result.Unaggregated = unagg
   179  	agg, err := encodeFanoutOption(fanoutOpts.FanoutAggregated)
   180  	if err != nil {
   181  		return nil, err
   182  	}
   183  
   184  	result.Aggregated = agg
   185  	aggOpt, err := encodeFanoutOption(fanoutOpts.FanoutAggregatedOptimized)
   186  	if err != nil {
   187  		return nil, err
   188  	}
   189  
   190  	result.AggregatedOptimized = aggOpt
   191  	if v := options.RestrictQueryOptions; v != nil {
   192  		restrict, err := encodeRestrictQueryOptions(v)
   193  		if err != nil {
   194  			return nil, err
   195  		}
   196  
   197  		result.Restrict = restrict
   198  	}
   199  
   200  	if v := options.LookbackDuration; v != nil {
   201  		result.LookbackDuration = int64(*v)
   202  	}
   203  
   204  	return result, nil
   205  }
   206  
   207  func encodeRestrictQueryOptionsByType(
   208  	o *storage.RestrictByType,
   209  ) (*rpcpb.RestrictQueryType, error) {
   210  	if o == nil {
   211  		return nil, nil
   212  	}
   213  
   214  	if err := o.Validate(); err != nil {
   215  		return nil, err
   216  	}
   217  
   218  	result := &rpcpb.RestrictQueryType{}
   219  	switch o.MetricsType {
   220  	case storagemetadata.UnaggregatedMetricsType:
   221  		result.MetricsType = rpcpb.MetricsType_UNAGGREGATED_METRICS_TYPE
   222  	case storagemetadata.AggregatedMetricsType:
   223  		result.MetricsType = rpcpb.MetricsType_AGGREGATED_METRICS_TYPE
   224  
   225  		storagePolicyProto, err := o.StoragePolicy.Proto()
   226  		if err != nil {
   227  			return nil, err
   228  		}
   229  
   230  		result.MetricsStoragePolicy = storagePolicyProto
   231  	}
   232  
   233  	return result, nil
   234  }
   235  
   236  func encodeRestrictQueryOptionsByTag(
   237  	o *storage.RestrictByTag,
   238  ) (*rpcpb.RestrictQueryTags, error) {
   239  	if o == nil {
   240  		return nil, nil
   241  	}
   242  
   243  	matchers, err := encodeTagMatchers(o.GetMatchers())
   244  	if err != nil {
   245  		return nil, err
   246  	}
   247  
   248  	return &rpcpb.RestrictQueryTags{
   249  		Restrict: matchers,
   250  		Strip:    o.Strip,
   251  	}, nil
   252  }
   253  
   254  func encodeRestrictQueryOptions(
   255  	o *storage.RestrictQueryOptions,
   256  ) (*rpcpb.RestrictQueryOptions, error) {
   257  	if o == nil {
   258  		return nil, nil
   259  	}
   260  
   261  	byType, err := encodeRestrictQueryOptionsByType(o.GetRestrictByType())
   262  	if err != nil {
   263  		return nil, err
   264  	}
   265  
   266  	byTags, err := encodeRestrictQueryOptionsByTag(o.GetRestrictByTag())
   267  	if err != nil {
   268  		return nil, err
   269  	}
   270  
   271  	return &rpcpb.RestrictQueryOptions{
   272  		RestrictQueryType: byType,
   273  		RestrictQueryTags: byTags,
   274  	}, nil
   275  }
   276  
   277  func encodeMatcherTypeToProto(t models.MatchType) (rpc.MatcherType, error) {
   278  	switch t {
   279  	case models.MatchEqual:
   280  		return rpc.MatcherType_EQUAL, nil
   281  	case models.MatchNotEqual:
   282  		return rpc.MatcherType_NOTEQUAL, nil
   283  	case models.MatchRegexp:
   284  		return rpc.MatcherType_REGEXP, nil
   285  	case models.MatchNotRegexp:
   286  		return rpc.MatcherType_NOTREGEXP, nil
   287  	case models.MatchField:
   288  		return rpc.MatcherType_EXISTS, nil
   289  	case models.MatchNotField:
   290  		return rpc.MatcherType_NOTEXISTS, nil
   291  	case models.MatchAll:
   292  		return rpc.MatcherType_ALL, nil
   293  	default:
   294  		return 0, fmt.Errorf("unknown matcher type for proto encoding")
   295  	}
   296  }
   297  
   298  // encodeMetadata creates a context that propagates request metadata as well as requestID
   299  func encodeMetadata(ctx context.Context, requestID string) context.Context {
   300  	if ctx == nil {
   301  		return ctx
   302  	}
   303  
   304  	headerValues := ctx.Value(handleroptions.RequestHeaderKey)
   305  	headers, ok := headerValues.(http.Header)
   306  	if !ok {
   307  		return metadata.NewOutgoingContext(ctx, metadata.MD{reqIDKey: []string{requestID}})
   308  	}
   309  
   310  	return metadata.NewOutgoingContext(ctx, convertHeaderToMetaWithID(headers, requestID))
   311  }
   312  
   313  func convertHeaderToMetaWithID(headers http.Header, requestID string) metadata.MD {
   314  	meta := make(metadata.MD, len(headers)+1)
   315  	meta[reqIDKey] = []string{requestID}
   316  
   317  	// Metadata keys must be in lowe case
   318  	for k, v := range headers {
   319  		meta[strings.ToLower(k)] = v
   320  	}
   321  
   322  	return meta
   323  }
   324  
   325  // creates a context with propagated request metadata as well as requestID
   326  func retrieveMetadata(
   327  	streamCtx context.Context,
   328  	instrumentOpts instrument.Options,
   329  ) context.Context {
   330  	md, ok := metadata.FromIncomingContext(streamCtx)
   331  	id := "unknown"
   332  	if ok {
   333  		ids := md[reqIDKey]
   334  		if len(ids) == 1 {
   335  			id = ids[0]
   336  		}
   337  	}
   338  
   339  	return logging.NewContextWithID(streamCtx, id, instrumentOpts)
   340  }
   341  
   342  func decodeFetchRequest(
   343  	req *rpc.FetchRequest,
   344  ) (*storage.FetchQuery, error) {
   345  	tags, err := decodeTagMatchers(req.GetTagMatchers())
   346  	if err != nil {
   347  		return nil, err
   348  	}
   349  
   350  	return &storage.FetchQuery{
   351  		TagMatchers: tags,
   352  		Start:       toTime(req.Start),
   353  		End:         toTime(req.End),
   354  	}, nil
   355  }
   356  
   357  func decodeTagMatchers(rpcMatchers *rpc.TagMatchers) (models.Matchers, error) {
   358  	tagMatchers := rpcMatchers.GetTagMatchers()
   359  	matchers := make([]models.Matcher, len(tagMatchers))
   360  	for i, matcher := range tagMatchers {
   361  		matchType, name, value := models.MatchType(matcher.GetType()), matcher.GetName(), matcher.GetValue()
   362  		mMatcher, err := models.NewMatcher(matchType, name, value)
   363  		if err != nil {
   364  			return matchers, err
   365  		}
   366  
   367  		matchers[i] = mMatcher
   368  	}
   369  
   370  	return models.Matchers(matchers), nil
   371  }
   372  
   373  func decodeFanoutOption(opt rpc.FanoutOption) (storage.FanoutOption, error) {
   374  	switch opt {
   375  	case rpc.FanoutOption_DEFAULT_OPTION:
   376  		return storage.FanoutDefault, nil
   377  	case rpc.FanoutOption_FORCE_DISABLED:
   378  		return storage.FanoutForceDisable, nil
   379  	case rpc.FanoutOption_FORCE_ENABLED:
   380  		return storage.FanoutForceEnable, nil
   381  	}
   382  
   383  	return 0, fmt.Errorf("unknown fanout option for proto encoding: %v", opt)
   384  }
   385  
   386  func decodeRestrictQueryOptionsByType(
   387  	p *rpc.RestrictQueryType,
   388  ) (*storage.RestrictByType, error) {
   389  	if p == nil {
   390  		return nil, nil
   391  	}
   392  
   393  	result := &storage.RestrictByType{}
   394  	switch p.GetMetricsType() {
   395  	case rpcpb.MetricsType_UNAGGREGATED_METRICS_TYPE:
   396  		result.MetricsType = storagemetadata.UnaggregatedMetricsType
   397  	case rpcpb.MetricsType_AGGREGATED_METRICS_TYPE:
   398  		result.MetricsType = storagemetadata.AggregatedMetricsType
   399  	}
   400  
   401  	if p.GetMetricsStoragePolicy() != nil {
   402  		storagePolicy, err := policy.NewStoragePolicyFromProto(
   403  			p.MetricsStoragePolicy)
   404  		if err != nil {
   405  			return result, err
   406  		}
   407  
   408  		result.StoragePolicy = storagePolicy
   409  	}
   410  
   411  	if err := result.Validate(); err != nil {
   412  		return nil, err
   413  
   414  	}
   415  
   416  	return result, nil
   417  }
   418  
   419  func decodeRestrictQueryOptionsByTag(
   420  	p *rpc.RestrictQueryTags,
   421  ) (*storage.RestrictByTag, error) {
   422  	if p == nil {
   423  		return nil, nil
   424  	}
   425  
   426  	matchers, err := decodeTagMatchers(p.GetRestrict())
   427  	if err != nil {
   428  		return nil, err
   429  	}
   430  
   431  	return &storage.RestrictByTag{
   432  		Restrict: matchers,
   433  		Strip:    p.Strip,
   434  	}, nil
   435  }
   436  
   437  func decodeRestrictQueryOptions(
   438  	p *rpc.RestrictQueryOptions,
   439  ) (*storage.RestrictQueryOptions, error) {
   440  	if p == nil {
   441  		return nil, nil
   442  	}
   443  
   444  	byType, err := decodeRestrictQueryOptionsByType(p.GetRestrictQueryType())
   445  	if err != nil {
   446  		return nil, err
   447  	}
   448  
   449  	byTag, err := decodeRestrictQueryOptionsByTag(p.GetRestrictQueryTags())
   450  	if err != nil {
   451  		return nil, err
   452  	}
   453  
   454  	return &storage.RestrictQueryOptions{
   455  		RestrictByType: byType,
   456  		RestrictByTag:  byTag,
   457  	}, nil
   458  }
   459  
   460  func decodeFetchOptions(rpcFetchOptions *rpc.FetchOptions) (*storage.FetchOptions, error) {
   461  	result := storage.NewFetchOptions()
   462  	result.Remote = true
   463  	if rpcFetchOptions == nil {
   464  		return result, nil
   465  	}
   466  
   467  	result.SeriesLimit = int(rpcFetchOptions.Limit)
   468  	unagg, err := decodeFanoutOption(rpcFetchOptions.GetUnaggregated())
   469  	if err != nil {
   470  		return nil, err
   471  	}
   472  
   473  	agg, err := decodeFanoutOption(rpcFetchOptions.GetAggregated())
   474  	if err != nil {
   475  		return nil, err
   476  	}
   477  
   478  	aggOpt, err := decodeFanoutOption(rpcFetchOptions.GetAggregatedOptimized())
   479  	if err != nil {
   480  		return nil, err
   481  	}
   482  
   483  	result.FanoutOptions = &storage.FanoutOptions{
   484  		FanoutUnaggregated:        unagg,
   485  		FanoutAggregated:          agg,
   486  		FanoutAggregatedOptimized: aggOpt,
   487  	}
   488  
   489  	if v := rpcFetchOptions.Restrict; v != nil {
   490  		restrict, err := decodeRestrictQueryOptions(v)
   491  		if err != nil {
   492  			return nil, err
   493  		}
   494  
   495  		result.RestrictQueryOptions = restrict
   496  	}
   497  
   498  	if v := rpcFetchOptions.LookbackDuration; v > 0 {
   499  		duration := time.Duration(v)
   500  		result.LookbackDuration = &duration
   501  	}
   502  
   503  	result.Source = rpcFetchOptions.Source
   504  	return result, nil
   505  }
   506  
   507  func encodeResolutions(res []time.Duration) []int64 {
   508  	encoded := make([]int64, 0, len(res))
   509  	for _, r := range res {
   510  		encoded = append(encoded, int64(r))
   511  	}
   512  
   513  	return encoded
   514  }
   515  
   516  func encodeResultMetadata(meta block.ResultMetadata) *rpc.ResultMetadata {
   517  	warnings := make([]*rpc.Warning, 0, len(meta.Warnings))
   518  	for _, warn := range meta.Warnings {
   519  		warnings = append(warnings, &rpc.Warning{
   520  			Name:    []byte(warn.Name),
   521  			Message: []byte(warn.Message),
   522  		})
   523  	}
   524  
   525  	return &rpc.ResultMetadata{
   526  		Exhaustive:  meta.Exhaustive,
   527  		Warnings:    warnings,
   528  		Resolutions: encodeResolutions(meta.Resolutions),
   529  	}
   530  }
   531  
   532  func decodeResolutions(res []int64) []time.Duration {
   533  	decoded := make([]time.Duration, 0, len(res))
   534  	for _, d := range res {
   535  		decoded = append(decoded, time.Duration(d))
   536  	}
   537  
   538  	return decoded
   539  }
   540  
   541  func decodeResultMetadata(meta *rpc.ResultMetadata) block.ResultMetadata {
   542  	rpcWarnings := meta.GetWarnings()
   543  	warnings := make([]block.Warning, 0, len(rpcWarnings))
   544  	for _, warn := range rpcWarnings {
   545  		warnings = append(warnings, block.Warning{
   546  			Name:    string(warn.Name),
   547  			Message: string(warn.Message),
   548  		})
   549  	}
   550  
   551  	return block.ResultMetadata{
   552  		Exhaustive:  meta.Exhaustive,
   553  		Warnings:    warnings,
   554  		Resolutions: decodeResolutions(meta.GetResolutions()),
   555  	}
   556  }