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 }