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 }