github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/query/graphite/storage/m3_wrapper.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 storage
    22  
    23  import (
    24  	"context"
    25  	"errors"
    26  	"fmt"
    27  	"math"
    28  	"strings"
    29  	"sync"
    30  	"time"
    31  
    32  	"github.com/m3db/m3/src/m3ninx/doc"
    33  	"github.com/m3db/m3/src/query/block"
    34  	xctx "github.com/m3db/m3/src/query/graphite/context"
    35  	"github.com/m3db/m3/src/query/graphite/graphite"
    36  	"github.com/m3db/m3/src/query/graphite/ts"
    37  	"github.com/m3db/m3/src/query/models"
    38  	"github.com/m3db/m3/src/query/storage"
    39  	querystorage "github.com/m3db/m3/src/query/storage"
    40  	"github.com/m3db/m3/src/query/storage/m3"
    41  	"github.com/m3db/m3/src/query/storage/m3/consolidators"
    42  	"github.com/m3db/m3/src/query/util/logging"
    43  	"github.com/m3db/m3/src/x/instrument"
    44  	xtime "github.com/m3db/m3/src/x/time"
    45  
    46  	"go.uber.org/zap"
    47  )
    48  
    49  var errSeriesNoResolution = errors.New("series has no resolution set")
    50  
    51  type m3WrappedStore struct {
    52  	m3             storage.Storage
    53  	m3dbOpts       m3.Options
    54  	instrumentOpts instrument.Options
    55  	opts           M3WrappedStorageOptions
    56  }
    57  
    58  // M3WrappedStorageOptions is the graphite storage options.
    59  type M3WrappedStorageOptions struct {
    60  	AggregateNamespacesAllData                 bool
    61  	ShiftTimeStart                             time.Duration
    62  	ShiftTimeEnd                               time.Duration
    63  	ShiftStepsStart                            int
    64  	ShiftStepsEnd                              int
    65  	ShiftStepsStartWhenAtResolutionBoundary    *int
    66  	ShiftStepsEndWhenAtResolutionBoundary      *int
    67  	ShiftStepsStartWhenEndAtResolutionBoundary *int
    68  	ShiftStepsEndWhenStartAtResolutionBoundary *int
    69  	RenderPartialStart                         bool
    70  	RenderPartialEnd                           bool
    71  	RenderSeriesAllNaNs                        bool
    72  	CompileEscapeAllNotOnlyQuotes              bool
    73  	FindResultsIncludeBothExpandableAndLeaf    bool
    74  }
    75  
    76  type seriesMetadata struct {
    77  	Resolution time.Duration
    78  }
    79  
    80  // NewM3WrappedStorage creates a graphite storage wrapper around an m3query
    81  // storage instance.
    82  func NewM3WrappedStorage(
    83  	m3storage storage.Storage,
    84  	m3dbOpts m3.Options,
    85  	instrumentOpts instrument.Options,
    86  	opts M3WrappedStorageOptions,
    87  ) Storage {
    88  	return &m3WrappedStore{
    89  		m3:             m3storage,
    90  		m3dbOpts:       m3dbOpts,
    91  		instrumentOpts: instrumentOpts,
    92  		opts:           opts,
    93  	}
    94  }
    95  
    96  // TranslatedQueryType describes a translated query type.
    97  type TranslatedQueryType uint
    98  
    99  const (
   100  	// TerminatedTranslatedQuery is a query that is terminated at an explicit
   101  	// leaf node (i.e. specific graphite path index number).
   102  	TerminatedTranslatedQuery TranslatedQueryType = iota
   103  	// StarStarUnterminatedTranslatedQuery is a query that is not terminated by
   104  	// an explicit leaf node since it matches indefinite child nodes due to
   105  	// a "**" in the query which matches indefinited child nodes.
   106  	StarStarUnterminatedTranslatedQuery
   107  )
   108  
   109  // TranslateQueryToMatchersWithTerminator converts a graphite query to tag
   110  // matcher pairs, and adds a terminator matcher to the end.
   111  func TranslateQueryToMatchersWithTerminator(
   112  	query string,
   113  ) (models.Matchers, TranslatedQueryType, error) {
   114  	if strings.Contains(query, "**") {
   115  		// First add matcher to ensure it's a graphite metric with __g0__ tag.
   116  		hasFirstPathMatcher, err := convertMetricPartToMatcher(0, wildcard)
   117  		if err != nil {
   118  			return nil, 0, err
   119  		}
   120  		// Need to regexp on the entire ID since ** matches over different
   121  		// graphite path dimensions.
   122  		globOpts := graphite.GlobOptions{
   123  			AllowMatchAll: true,
   124  		}
   125  		idRegexp, _, err := graphite.ExtendedGlobToRegexPattern(query, globOpts)
   126  		if err != nil {
   127  			return nil, 0, err
   128  		}
   129  		return models.Matchers{
   130  			hasFirstPathMatcher,
   131  			models.Matcher{
   132  				Type:  models.MatchRegexp,
   133  				Name:  doc.IDReservedFieldName,
   134  				Value: idRegexp,
   135  			},
   136  		}, StarStarUnterminatedTranslatedQuery, nil
   137  	}
   138  
   139  	metricLength := graphite.CountMetricParts(query)
   140  	// Add space for a terminator character.
   141  	matchersLength := metricLength + 1
   142  	matchers := make(models.Matchers, matchersLength)
   143  	for i := 0; i < metricLength; i++ {
   144  		metric := graphite.ExtractNthMetricPart(query, i)
   145  		if len(metric) > 0 {
   146  			m, err := convertMetricPartToMatcher(i, metric)
   147  			if err != nil {
   148  				return nil, 0, err
   149  			}
   150  
   151  			matchers[i] = m
   152  		} else {
   153  			err := fmt.Errorf("invalid matcher format: %s", query)
   154  			return nil, 0, err
   155  		}
   156  	}
   157  
   158  	// Add a terminator matcher at the end to ensure expansion is terminated at
   159  	// the last given metric part.
   160  	matchers[metricLength] = matcherTerminator(metricLength)
   161  	return matchers, TerminatedTranslatedQuery, nil
   162  }
   163  
   164  // GetQueryTerminatorTagName will return the name for the terminator matcher in
   165  // the given pattern. This is useful for filtering out any additional results.
   166  func GetQueryTerminatorTagName(query string) []byte {
   167  	metricLength := graphite.CountMetricParts(query)
   168  	return graphite.TagName(metricLength)
   169  }
   170  
   171  func translateQuery(
   172  	query string,
   173  	fetchOpts FetchOptions,
   174  	opts M3WrappedStorageOptions,
   175  ) (*storage.FetchQuery, error) {
   176  	matchers, _, err := TranslateQueryToMatchersWithTerminator(query)
   177  	if err != nil {
   178  		return nil, err
   179  	}
   180  
   181  	// Apply any shifts.
   182  	fetchOpts.StartTime = fetchOpts.StartTime.Add(opts.ShiftTimeStart)
   183  	fetchOpts.EndTime = fetchOpts.EndTime.Add(opts.ShiftTimeEnd)
   184  
   185  	return &storage.FetchQuery{
   186  		Raw:         query,
   187  		TagMatchers: matchers,
   188  		Start:       fetchOpts.StartTime,
   189  		End:         fetchOpts.EndTime,
   190  		// NB: interval is not used for initial consolidation step from the storage
   191  		// so it's fine to use default here.
   192  		Interval: time.Duration(0),
   193  	}, nil
   194  }
   195  
   196  type truncateBoundsToResolutionOptions struct {
   197  	shiftStepsStart                            int
   198  	shiftStepsEnd                              int
   199  	shiftStepsStartWhenAtResolutionBoundary    *int
   200  	shiftStepsEndWhenAtResolutionBoundary      *int
   201  	shiftStepsStartWhenEndAtResolutionBoundary *int
   202  	shiftStepsEndWhenStartAtResolutionBoundary *int
   203  	renderPartialStart                         bool
   204  	renderPartialEnd                           bool
   205  }
   206  
   207  func truncateBoundsToResolution(
   208  	startTime time.Time,
   209  	endTime time.Time,
   210  	resolution time.Duration,
   211  	opts truncateBoundsToResolutionOptions,
   212  ) (xtime.UnixNano, xtime.UnixNano) {
   213  	var (
   214  		start = xtime.ToUnixNano(startTime)
   215  		end   = xtime.ToUnixNano(endTime)
   216  
   217  		truncatedStart            = start.Truncate(resolution)
   218  		truncatedEnd              = end.Truncate(resolution)
   219  		startAtResolutionBoundary = start.Equal(truncatedStart)
   220  		endAtResolutionBoundary   = end.Equal(truncatedEnd)
   221  	)
   222  
   223  	// First calculate number of datapoints requested.
   224  	round := math.Floor
   225  	if opts.renderPartialEnd {
   226  		round = math.Ceil
   227  	}
   228  	// If not matched to resolution then return a partial datapoint, unless
   229  	// render partial end is requested in which case return the extra datapoint.
   230  	length := round(float64(end.Sub(start)) / float64(resolution))
   231  
   232  	// Now determine start time depending on if in the middle of a step or not.
   233  	// NB: if truncated start matches start, it's already valid.
   234  	if !start.Equal(truncatedStart) {
   235  		if opts.renderPartialStart {
   236  			// Otherwise if we include partial start then set to truncated.
   237  			start = truncatedStart
   238  		} else {
   239  			// Else we snap to the next step.
   240  			start = truncatedStart.Add(resolution)
   241  		}
   242  	}
   243  
   244  	// Finally calculate end.
   245  	end = start.Add(time.Duration(length) * resolution)
   246  
   247  	// Apply shifts.
   248  	var (
   249  		shiftStartAtBoundary        = opts.shiftStepsStartWhenAtResolutionBoundary
   250  		shiftEndAtBoundary          = opts.shiftStepsEndWhenAtResolutionBoundary
   251  		shiftStartWhenEndAtBoundary = opts.shiftStepsStartWhenEndAtResolutionBoundary
   252  		shiftEndWhenStartAtBoundary = opts.shiftStepsEndWhenStartAtResolutionBoundary
   253  		shiftStartOverride          bool
   254  		shiftEndOverride            bool
   255  	)
   256  	if startAtResolutionBoundary {
   257  		if n := shiftStartAtBoundary; n != nil {
   258  			// Apply start boundary shifts which override constant shifts if at boundary.
   259  			start = start.Add(time.Duration(*n) * resolution)
   260  			shiftStartOverride = true
   261  		}
   262  		if n := shiftEndWhenStartAtBoundary; n != nil && !endAtResolutionBoundary {
   263  			// Apply end boundary shifts which override constant shifts if at boundary.
   264  			end = end.Add(time.Duration(*n) * resolution)
   265  			shiftEndOverride = true
   266  		}
   267  	}
   268  	if endAtResolutionBoundary {
   269  		if n := shiftEndAtBoundary; n != nil {
   270  			// Apply end boundary shifts which override constant shifts if at boundary.
   271  			end = end.Add(time.Duration(*n) * resolution)
   272  			shiftEndOverride = true
   273  		}
   274  		if n := shiftStartWhenEndAtBoundary; n != nil && !startAtResolutionBoundary {
   275  			// Apply start boundary shifts which override constant shifts if at boundary.
   276  			start = start.Add(time.Duration(*n) * resolution)
   277  			shiftStartOverride = true
   278  		}
   279  	}
   280  
   281  	if !shiftStartOverride {
   282  		// Apply constant shift if no override shift effective.
   283  		start = start.Add(time.Duration(opts.shiftStepsStart) * resolution)
   284  	}
   285  	if !shiftEndOverride {
   286  		// Apply constant shift if no override shift effective.
   287  		end = end.Add(time.Duration(opts.shiftStepsEnd) * resolution)
   288  	}
   289  
   290  	return start, end
   291  }
   292  
   293  func translateTimeseries(
   294  	ctx xctx.Context,
   295  	result block.Result,
   296  	start, end time.Time,
   297  	m3dbOpts m3.Options,
   298  	truncateOpts truncateBoundsToResolutionOptions,
   299  ) ([]*ts.Series, error) {
   300  	if len(result.Blocks) == 0 {
   301  		return []*ts.Series{}, nil
   302  	}
   303  
   304  	bl := result.Blocks[0]
   305  	defer bl.Close()
   306  
   307  	iter, err := bl.SeriesIter()
   308  	if err != nil {
   309  		return nil, err
   310  	}
   311  
   312  	resolutions := result.Metadata.Resolutions
   313  	seriesMetas := iter.SeriesMeta()
   314  	if len(seriesMetas) != len(resolutions) {
   315  		return nil, fmt.Errorf("number of timeseries %d does not match number of "+
   316  			"resolutions %d", len(seriesMetas), len(resolutions))
   317  	}
   318  
   319  	seriesMetadataMap := newSeriesMetadataMap(seriesMetadataMapOptions{
   320  		InitialSize: iter.SeriesCount(),
   321  	})
   322  
   323  	for i, meta := range seriesMetas {
   324  		seriesMetadataMap.SetUnsafe(meta.Name, seriesMetadata{
   325  			Resolution: resolutions[i],
   326  		}, seriesMetadataMapSetUnsafeOptions{
   327  			NoCopyKey:     true,
   328  			NoFinalizeKey: true,
   329  		})
   330  	}
   331  
   332  	var (
   333  		results     []*ts.Series
   334  		resultsLock sync.Mutex
   335  	)
   336  	processor := m3dbOpts.BlockSeriesProcessor()
   337  	err = processor.Process(bl, m3dbOpts, m3.BlockSeriesProcessorFn(func(
   338  		iter block.SeriesIter,
   339  	) error {
   340  		series, err := translateTimeseriesFromIter(ctx, iter,
   341  			start, end, seriesMetadataMap, truncateOpts)
   342  		if err != nil {
   343  			return err
   344  		}
   345  
   346  		resultsLock.Lock()
   347  		defer resultsLock.Unlock()
   348  
   349  		if len(results) == 0 {
   350  			// Don't grow slice, can just take ref.
   351  			results = series
   352  		} else {
   353  			results = append(results, series...)
   354  		}
   355  
   356  		return nil
   357  	}))
   358  	if err != nil {
   359  		return nil, err
   360  	}
   361  	return results, nil
   362  }
   363  
   364  func translateTimeseriesFromIter(
   365  	ctx xctx.Context,
   366  	iter block.SeriesIter,
   367  	queryStart, queryEnd time.Time,
   368  	seriesMetadataMap *seriesMetadataMap,
   369  	opts truncateBoundsToResolutionOptions,
   370  ) ([]*ts.Series, error) {
   371  	seriesMetas := iter.SeriesMeta()
   372  	series := make([]*ts.Series, 0, len(seriesMetas))
   373  	for idx := 0; iter.Next(); idx++ {
   374  		meta, ok := seriesMetadataMap.Get(seriesMetas[idx].Name)
   375  		if !ok {
   376  			return nil, fmt.Errorf("series meta for series missing: %s", seriesMetas[idx].Name)
   377  		}
   378  
   379  		resolution := time.Duration(meta.Resolution)
   380  		if resolution <= 0 {
   381  			return nil, errSeriesNoResolution
   382  		}
   383  
   384  		start, end := truncateBoundsToResolution(queryStart, queryEnd, resolution, opts)
   385  		length := int(end.Sub(start) / resolution)
   386  		millisPerStep := int(resolution / time.Millisecond)
   387  		values := ts.NewValues(ctx, millisPerStep, length)
   388  
   389  		m3series := iter.Current()
   390  		dps := m3series.Datapoints()
   391  		for _, datapoint := range dps.Datapoints() {
   392  			ts := datapoint.Timestamp
   393  			if ts.Before(start) {
   394  				// Outside of range requested.
   395  				continue
   396  			}
   397  
   398  			if !ts.Before(end) {
   399  				// No more valid datapoints.
   400  				break
   401  			}
   402  
   403  			index := int(datapoint.Timestamp.Sub(start) / resolution)
   404  			values.SetValueAt(index, datapoint.Value)
   405  		}
   406  
   407  		name := string(seriesMetas[idx].Name)
   408  		series = append(series, ts.NewSeries(ctx, name, start.ToTime(), values))
   409  	}
   410  
   411  	if err := iter.Err(); err != nil {
   412  		return nil, err
   413  	}
   414  
   415  	return series, nil
   416  }
   417  
   418  func (s *m3WrappedStore) fanoutOptions() *storage.FanoutOptions {
   419  	fanoutOpts := &storage.FanoutOptions{
   420  		FanoutUnaggregated:        storage.FanoutForceDisable,
   421  		FanoutAggregated:          storage.FanoutDefault,
   422  		FanoutAggregatedOptimized: storage.FanoutForceDisable,
   423  	}
   424  	if s.opts.AggregateNamespacesAllData {
   425  		// NB(r): If aggregate namespaces house all the data, we can do a
   426  		// default optimized fanout where we only query the namespaces
   427  		// that contain the data for the ranges we are querying for.
   428  		fanoutOpts.FanoutAggregatedOptimized = storage.FanoutDefault
   429  	}
   430  	return fanoutOpts
   431  }
   432  
   433  func (s *m3WrappedStore) FetchByQuery(
   434  	ctx xctx.Context, query string, fetchOpts FetchOptions,
   435  ) (*FetchResult, error) {
   436  	m3query, err := translateQuery(query, fetchOpts, s.opts)
   437  	if err != nil {
   438  		// NB: error here implies the query cannot be translated; empty set expected
   439  		// rather than propagating an error.
   440  		logger := logging.WithContext(ctx.RequestContext(), s.instrumentOpts)
   441  		logger.Info("could not translate query, returning empty results",
   442  			zap.String("query", query))
   443  		return &FetchResult{
   444  			SeriesList: []*ts.Series{},
   445  			Metadata:   block.NewResultMetadata(),
   446  		}, nil
   447  	}
   448  
   449  	m3ctx := ctx.RequestContext()
   450  	if _, ok := m3ctx.Deadline(); !ok {
   451  		var cancel context.CancelFunc
   452  		m3ctx, cancel = context.WithTimeout(m3ctx, fetchOpts.Timeout)
   453  		defer cancel()
   454  	}
   455  
   456  	var fetchOptions *storage.FetchOptions
   457  	if fetchOpts.QueryFetchOpts != nil {
   458  		fetchOptions = fetchOpts.QueryFetchOpts.Clone()
   459  	} else {
   460  		fetchOptions = storage.NewFetchOptions()
   461  	}
   462  
   463  	// NB: ensure single block return.
   464  	fetchOptions.BlockType = models.TypeSingleBlock
   465  	fetchOptions.FanoutOptions = s.fanoutOptions()
   466  	res, err := s.m3.FetchBlocks(m3ctx, m3query, fetchOptions)
   467  	if err != nil {
   468  		return nil, err
   469  	}
   470  
   471  	if blockCount := len(res.Blocks); blockCount > 1 {
   472  		return nil, fmt.Errorf("expected at most one block, received %d", blockCount)
   473  	}
   474  
   475  	truncateOpts := truncateBoundsToResolutionOptions{
   476  		shiftStepsStart:                            s.opts.ShiftStepsStart,
   477  		shiftStepsEnd:                              s.opts.ShiftStepsEnd,
   478  		shiftStepsStartWhenAtResolutionBoundary:    s.opts.ShiftStepsStartWhenAtResolutionBoundary,
   479  		shiftStepsEndWhenAtResolutionBoundary:      s.opts.ShiftStepsEndWhenAtResolutionBoundary,
   480  		shiftStepsStartWhenEndAtResolutionBoundary: s.opts.ShiftStepsStartWhenEndAtResolutionBoundary,
   481  		shiftStepsEndWhenStartAtResolutionBoundary: s.opts.ShiftStepsEndWhenStartAtResolutionBoundary,
   482  		renderPartialStart:                         s.opts.RenderPartialStart,
   483  		renderPartialEnd:                           s.opts.RenderPartialEnd,
   484  	}
   485  
   486  	series, err := translateTimeseries(ctx, res,
   487  		fetchOpts.StartTime, fetchOpts.EndTime, s.m3dbOpts, truncateOpts)
   488  	if err != nil {
   489  		return nil, err
   490  	}
   491  
   492  	return NewFetchResult(ctx, series, res.Metadata), nil
   493  }
   494  
   495  func (s *m3WrappedStore) CompleteTags(
   496  	ctx context.Context,
   497  	query *querystorage.CompleteTagsQuery,
   498  	opts *querystorage.FetchOptions,
   499  ) (*consolidators.CompleteTagsResult, error) {
   500  	// NB(r): Make sure to apply consistent fanout options to both
   501  	// queries and aggregate queries for Graphite.
   502  	opts = opts.Clone() // Clone to avoid mutating input and cause data races.
   503  	opts.FanoutOptions = s.fanoutOptions()
   504  	return s.m3.CompleteTags(ctx, query, opts)
   505  }