github.com/m3db/m3@v1.5.0/src/query/block/meta.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 block
    22  
    23  import (
    24  	"fmt"
    25  	"sort"
    26  	"strings"
    27  	"time"
    28  
    29  	"github.com/m3db/m3/src/query/models"
    30  )
    31  
    32  // Metadata is metadata for a block, describing size and common tags across
    33  // constituent series.
    34  type Metadata struct {
    35  	// Bounds represents the time bounds for all series in the block.
    36  	Bounds models.Bounds
    37  	// Tags contains any tags common across all series in the block.
    38  	Tags models.Tags
    39  	// ResultMetadata contains metadata from any database access operations during
    40  	// fetching block details.
    41  	ResultMetadata ResultMetadata
    42  }
    43  
    44  // Equals returns a boolean reporting whether the compared metadata has equal
    45  // fields.
    46  func (m Metadata) Equals(other Metadata) bool {
    47  	return m.Tags.Equals(other.Tags) && m.Bounds.Equals(other.Bounds)
    48  }
    49  
    50  // String returns a string representation of metadata.
    51  func (m Metadata) String() string {
    52  	return fmt.Sprintf("Bounds: %v, Tags: %v", m.Bounds, m.Tags)
    53  }
    54  
    55  // Warnings is a slice of warnings.
    56  type Warnings []Warning
    57  
    58  // ResultMetricMetadata describes metadata on a per metric-name basis.
    59  type ResultMetricMetadata struct {
    60  	// NoSamples is the total number of series that were fetched to compute
    61  	// this result but had no samples.
    62  	NoSamples int
    63  	// WithSamples is the total number of series that were fetched to compute
    64  	// this result and had samples.
    65  	WithSamples int
    66  	// Aggregated is the total number of aggregated series that were fetched to
    67  	// compute this result.
    68  	Aggregated int
    69  	// Unaggregated is the total number of unaggregated series that were fetched to
    70  	// compute this result.
    71  	Unaggregated int
    72  }
    73  
    74  // Equals determines if two result metric metadatas are equal.
    75  func (m ResultMetricMetadata) Equals(other ResultMetricMetadata) bool {
    76  	if m.NoSamples != other.NoSamples {
    77  		return false
    78  	}
    79  	if m.WithSamples != other.WithSamples {
    80  		return false
    81  	}
    82  	if m.Aggregated != other.Aggregated {
    83  		return false
    84  	}
    85  	if m.Unaggregated != other.Unaggregated {
    86  		return false
    87  	}
    88  	return true
    89  }
    90  
    91  // Merge takes another ResultMetricMetadata and merges it into this one.
    92  func (m *ResultMetricMetadata) Merge(other ResultMetricMetadata) {
    93  	m.NoSamples += other.NoSamples
    94  	m.WithSamples += other.WithSamples
    95  	m.Aggregated += other.Aggregated
    96  	m.Unaggregated += other.Unaggregated
    97  }
    98  
    99  func mergeMetricMetadataMaps(dst, src map[string]*ResultMetricMetadata) {
   100  	for name, other := range src {
   101  		m, ok := dst[name]
   102  		if !ok {
   103  			dst[name] = other
   104  		} else {
   105  			m.Merge(*other)
   106  		}
   107  	}
   108  }
   109  
   110  // ResultMetadata describes metadata common to each type of query results,
   111  // indicating any additional information about the result.
   112  type ResultMetadata struct {
   113  	// Namespaces are the set of namespaces queried.
   114  	// External users must access via `AddNamespace`
   115  	namespaces map[string]struct{}
   116  	// FetchedResponses is the number of M3 RPC fetch responses received.
   117  	FetchedResponses int
   118  	// FetchedBytesEstimate is the estimated number of bytes fetched.
   119  	FetchedBytesEstimate int
   120  	// LocalOnly indicates that this query was executed only on the local store.
   121  	LocalOnly bool
   122  	// Exhaustive indicates whether the underlying data set presents a full
   123  	// collection of retrieved data.
   124  	Exhaustive bool
   125  	// Warnings is a list of warnings that indicate potentially partial or
   126  	// incomplete results.
   127  	Warnings Warnings
   128  	// Resolutions is a list of resolutions for series obtained by this query.
   129  	Resolutions []time.Duration
   130  	// KeepNaNs indicates if NaNs should be kept when returning results.
   131  	KeepNaNs bool
   132  	// WaitedIndex counts how many times index querying had to wait for permits.
   133  	WaitedIndex int
   134  	// WaitedSeriesRead counts how many times series being read had to wait for permits.
   135  	WaitedSeriesRead int
   136  	// FetchedSeriesCount is the total number of series that were fetched to compute
   137  	// this result.
   138  	FetchedSeriesCount int
   139  	// FetchedMetadataCount is the total amount of metadata that was fetched to compute
   140  	// this result.
   141  	FetchedMetadataCount int
   142  	// MetricNames is the set of unique metric tag name values across all series in this result.
   143  	// External users must access via `ByName(name)`.
   144  	metadataByName map[string]*ResultMetricMetadata
   145  }
   146  
   147  // AddNamespace adds a namespace to the namespace set, initializing the underlying map if necessary.
   148  func (m *ResultMetadata) AddNamespace(namespace string) {
   149  	if m.namespaces == nil {
   150  		m.namespaces = make(map[string]struct{})
   151  	}
   152  	m.namespaces[namespace] = struct{}{}
   153  }
   154  
   155  // GetNamespaces returns an array representing the set of namespaces added via AddNamespace.
   156  func (m ResultMetadata) GetNamespaces() []string {
   157  	if m.namespaces == nil {
   158  		return []string{}
   159  	}
   160  	namespaces := []string{}
   161  	for n := range m.namespaces {
   162  		namespaces = append(namespaces, n)
   163  	}
   164  	sort.Strings(namespaces)
   165  	return namespaces
   166  }
   167  
   168  // ByName returns the ResultMetricMetadata for a given metric name.
   169  func (m *ResultMetadata) ByName(nameTag []byte) *ResultMetricMetadata {
   170  	if m.metadataByName == nil {
   171  		m.metadataByName = make(map[string]*ResultMetricMetadata)
   172  	}
   173  
   174  	r, ok := m.metadataByName[string(nameTag)]
   175  	if ok {
   176  		return r
   177  	}
   178  
   179  	r = &ResultMetricMetadata{}
   180  	m.metadataByName[string(nameTag)] = r
   181  	return r
   182  }
   183  
   184  // MetadataByNameMerged returns the metadataByName map values merged into one.
   185  func (m ResultMetadata) MetadataByNameMerged() ResultMetricMetadata {
   186  	r := ResultMetricMetadata{}
   187  	for _, m := range m.metadataByName {
   188  		r.Merge(*m)
   189  	}
   190  	return r
   191  }
   192  
   193  // TopMetadataByName returns the top `max` ResultMetricMetadatas by the sum of their
   194  // contained counters.
   195  func (m ResultMetadata) TopMetadataByName(max int) map[string]*ResultMetricMetadata {
   196  	if len(m.metadataByName) <= max {
   197  		return m.metadataByName
   198  	}
   199  
   200  	keys := []string{}
   201  	for k := range m.metadataByName {
   202  		keys = append(keys, k)
   203  	}
   204  	sort.SliceStable(keys, func(i, j int) bool {
   205  		a := m.metadataByName[keys[i]]
   206  		b := m.metadataByName[keys[j]]
   207  		n := a.Aggregated + a.Unaggregated + a.NoSamples + a.WithSamples
   208  		m := b.Aggregated + b.Unaggregated + b.NoSamples + b.WithSamples
   209  		// Sort in descending order
   210  		return n > m
   211  	})
   212  	top := make(map[string]*ResultMetricMetadata, max)
   213  	for i := 0; i < max; i++ {
   214  		k := keys[i]
   215  		top[k] = m.metadataByName[k]
   216  	}
   217  	return top
   218  }
   219  
   220  // NewResultMetadata creates a new result metadata.
   221  func NewResultMetadata() ResultMetadata {
   222  	return ResultMetadata{
   223  		LocalOnly:  true,
   224  		Exhaustive: true,
   225  	}
   226  }
   227  
   228  func combineResolutions(a, b []time.Duration) []time.Duration {
   229  	if len(a) == 0 {
   230  		if len(b) != 0 {
   231  			return b
   232  		}
   233  	} else {
   234  		if len(b) == 0 {
   235  			return a
   236  		}
   237  
   238  		combined := make([]time.Duration, 0, len(a)+len(b))
   239  		combined = append(combined, a...)
   240  		combined = append(combined, b...)
   241  		return combined
   242  	}
   243  
   244  	return nil
   245  }
   246  
   247  func combineWarnings(a, b Warnings) Warnings {
   248  	if len(a) == 0 {
   249  		if len(b) != 0 {
   250  			return b
   251  		}
   252  	} else {
   253  		if len(b) == 0 {
   254  			return a
   255  		}
   256  
   257  		combinedWarnings := make(Warnings, 0, len(a)+len(b))
   258  		combinedWarnings = append(combinedWarnings, a...)
   259  		return combinedWarnings.addWarnings(b...)
   260  	}
   261  
   262  	return nil
   263  }
   264  
   265  func combineNamespaces(a, b map[string]struct{}) map[string]struct{} {
   266  	if a == nil {
   267  		return b
   268  	}
   269  	if b == nil {
   270  		return a
   271  	}
   272  	merged := make(map[string]struct{})
   273  	for n := range a {
   274  		merged[n] = struct{}{}
   275  	}
   276  	for n := range b {
   277  		merged[n] = struct{}{}
   278  	}
   279  	return merged
   280  }
   281  
   282  func combineMetricMetadata(a, b map[string]*ResultMetricMetadata) map[string]*ResultMetricMetadata {
   283  	if a == nil && b == nil {
   284  		return nil
   285  	}
   286  
   287  	merged := make(map[string]*ResultMetricMetadata)
   288  	mergeMetricMetadataMaps(merged, a)
   289  	mergeMetricMetadataMaps(merged, b)
   290  
   291  	return merged
   292  }
   293  
   294  // Equals determines if two result metadatas are equal.
   295  func (m ResultMetadata) Equals(n ResultMetadata) bool {
   296  	if m.Exhaustive && !n.Exhaustive || !m.Exhaustive && n.Exhaustive {
   297  		return false
   298  	}
   299  
   300  	if m.LocalOnly && !n.LocalOnly || !m.LocalOnly && n.LocalOnly {
   301  		return false
   302  	}
   303  
   304  	if len(m.Resolutions) != len(n.Resolutions) {
   305  		return false
   306  	}
   307  
   308  	for i, mRes := range m.Resolutions {
   309  		if n.Resolutions[i] != mRes {
   310  			return false
   311  		}
   312  	}
   313  
   314  	for i, mWarn := range m.Warnings {
   315  		if !n.Warnings[i].equals(mWarn) {
   316  			return false
   317  		}
   318  	}
   319  
   320  	if m.WaitedIndex != n.WaitedIndex {
   321  		return false
   322  	}
   323  
   324  	if m.WaitedSeriesRead != n.WaitedSeriesRead {
   325  		return false
   326  	}
   327  
   328  	if m.FetchedSeriesCount != n.FetchedSeriesCount {
   329  		return false
   330  	}
   331  
   332  	if !m.MetadataByNameMerged().Equals(n.MetadataByNameMerged()) {
   333  		return false
   334  	}
   335  
   336  	return m.FetchedMetadataCount == n.FetchedMetadataCount
   337  }
   338  
   339  // CombineMetadata combines two result metadatas.
   340  func (m ResultMetadata) CombineMetadata(other ResultMetadata) ResultMetadata {
   341  	return ResultMetadata{
   342  		namespaces:           combineNamespaces(m.namespaces, other.namespaces),
   343  		FetchedResponses:     m.FetchedResponses + other.FetchedResponses,
   344  		FetchedBytesEstimate: m.FetchedBytesEstimate + other.FetchedBytesEstimate,
   345  		LocalOnly:            m.LocalOnly && other.LocalOnly,
   346  		Exhaustive:           m.Exhaustive && other.Exhaustive,
   347  		Warnings:             combineWarnings(m.Warnings, other.Warnings),
   348  		Resolutions:          combineResolutions(m.Resolutions, other.Resolutions),
   349  		WaitedIndex:          m.WaitedIndex + other.WaitedIndex,
   350  		WaitedSeriesRead:     m.WaitedSeriesRead + other.WaitedSeriesRead,
   351  		FetchedSeriesCount:   m.FetchedSeriesCount + other.FetchedSeriesCount,
   352  		metadataByName:       combineMetricMetadata(m.metadataByName, other.metadataByName),
   353  		FetchedMetadataCount: m.FetchedMetadataCount + other.FetchedMetadataCount,
   354  	}
   355  }
   356  
   357  // IsDefault returns true if this result metadata matches the unchanged default.
   358  func (m ResultMetadata) IsDefault() bool {
   359  	return m.Exhaustive && m.LocalOnly && len(m.Warnings) == 0
   360  }
   361  
   362  // VerifyTemporalRange will verify that each resolution seen is below the
   363  // given step size, adding warning headers if it is not.
   364  func (m *ResultMetadata) VerifyTemporalRange(step time.Duration) {
   365  	// NB: this map is unlikely to have more than 2 elements in real execution,
   366  	// since these correspond to namespace count.
   367  	invalidResolutions := make(map[time.Duration]struct{}, 10)
   368  	for _, res := range m.Resolutions {
   369  		if res > step {
   370  			invalidResolutions[res] = struct{}{}
   371  		}
   372  	}
   373  
   374  	if len(invalidResolutions) > 0 {
   375  		warnings := make([]string, 0, len(invalidResolutions))
   376  		for k := range invalidResolutions {
   377  			warnings = append(warnings, fmt.Sprintf("%v", k))
   378  		}
   379  
   380  		sort.Strings(warnings)
   381  		warning := fmt.Sprintf("range: %v, resolutions: %s",
   382  			step, strings.Join(warnings, ", "))
   383  		m.AddWarning("resolution larger than query range", warning)
   384  	}
   385  }
   386  
   387  // AddWarning adds a warning to the result metadata.
   388  // NB: warnings are expected to be small in general, so it's better to iterate
   389  // over the array rather than introduce a map.
   390  func (m *ResultMetadata) AddWarning(name string, message string) {
   391  	m.Warnings = m.Warnings.addWarnings(Warning{
   392  		Name:    name,
   393  		Message: message,
   394  	})
   395  }
   396  
   397  // AddWarnings adds several warnings to the result metadata.
   398  func (m *ResultMetadata) AddWarnings(warnings ...Warning) {
   399  	m.Warnings = m.Warnings.addWarnings(warnings...)
   400  }
   401  
   402  // NB: this is not a very efficient merge but this is extremely unlikely to be
   403  // merging more than 5 or 6 total warnings.
   404  func (w Warnings) addWarnings(warnings ...Warning) Warnings {
   405  	for _, newWarning := range warnings {
   406  		found := false
   407  		for _, warning := range w {
   408  			if warning.equals(newWarning) {
   409  				found = true
   410  				break
   411  			}
   412  		}
   413  
   414  		if !found {
   415  			w = append(w, newWarning)
   416  		}
   417  	}
   418  
   419  	return w
   420  }
   421  
   422  // WarningStrings converts warnings to a slice of strings for presentation.
   423  func (m ResultMetadata) WarningStrings() []string {
   424  	size := len(m.Warnings)
   425  	if !m.Exhaustive {
   426  		size++
   427  	}
   428  
   429  	strs := make([]string, 0, size)
   430  	for _, warn := range m.Warnings {
   431  		strs = append(strs, warn.Header())
   432  	}
   433  
   434  	if !m.Exhaustive {
   435  		strs = append(strs, "m3db exceeded query limit: results not exhaustive")
   436  	}
   437  
   438  	return strs
   439  }
   440  
   441  // Warning is a message that indicates potential partial or incomplete results.
   442  type Warning struct {
   443  	// Name is the name of the store originating the warning.
   444  	Name string
   445  	// Message is the content of the warning message.
   446  	Message string
   447  }
   448  
   449  // Header formats the warning into a format to send in a response header.
   450  func (w Warning) Header() string {
   451  	return fmt.Sprintf("%s_%s", w.Name, w.Message)
   452  }
   453  
   454  func (w Warning) equals(warning Warning) bool {
   455  	return w.Name == warning.Name && w.Message == warning.Message
   456  }