// Execute returns the list of tags satisfying the provided predicate. func (cmd *DescribeCommand) Execute(context ExecutionContext) (interface{}, error) { tagsets, _ := context.API.GetAllTags(cmd.metricName) // Splitting each tag key into its own set of values is helpful for discovering actual metrics. keyValueSets := map[string]map[string]bool{} // a map of tag_key => Set{tag_value}. for _, tagset := range tagsets { if cmd.predicate.Apply(tagset) { // Add each key as needed for key, value := range tagset { if keyValueSets[key] == nil { keyValueSets[key] = map[string]bool{} } keyValueSets[key][value] = true // add `value` to the set for `key` } } } keyValueLists := map[string][]string{} // a map of tag_key => list[tag_value] for key, set := range keyValueSets { list := make([]string, 0, len(set)) for value := range set { list = append(list, value) } // sort the result natural_sort.Sort(list) keyValueLists[key] = list } return keyValueLists, nil }
// Execute returns the list of tags satisfying the provided predicate. func (cmd *DescribeCommand) Execute(context ExecutionContext) (interface{}, error) { // We generate a simple update function that closes around the profiler // so if we do have a cache miss it's correctly reported on this request. updateFunction := func() ([]api.TagSet, error) { tagsets, err := context.MetricMetadataAPI.GetAllTags(cmd.metricName, api.MetricMetadataAPIContext{ Profiler: context.Profiler, }) return tagsets, err } tagsets, _ := context.OptimizationConfiguration.AllTagsCacheHitOrExecute(cmd.metricName, updateFunction) // Splitting each tag key into its own set of values is helpful for discovering actual metrics. keyValueSets := map[string]map[string]bool{} // a map of tag_key => Set{tag_value}. for _, tagset := range tagsets { if cmd.predicate.Apply(tagset) { // Add each key as needed for key, value := range tagset { if keyValueSets[key] == nil { keyValueSets[key] = map[string]bool{} } keyValueSets[key][value] = true // add `value` to the set for `key` } } } keyValueLists := map[string][]string{} // a map of tag_key => list[tag_value] for key, set := range keyValueSets { list := make([]string, 0, len(set)) for value := range set { list = append(list, value) } // sort the result natural_sort.Sort(list) keyValueLists[key] = list } return keyValueLists, nil }
// Execute performs the query represented by the given query string, and returs the result. func (cmd *SelectCommand) Execute(context ExecutionContext) (CommandResult, error) { userTimerange, err := api.NewSnappedTimerange(cmd.context.Start, cmd.context.End, cmd.context.Resolution) if err != nil { return CommandResult{}, err } slotLimit := context.SlotLimit defaultLimit := 1000 if slotLimit == 0 { slotLimit = defaultLimit // the default limit } smallestResolution := userTimerange.Duration() / time.Duration(slotLimit-2) // ((end + res/2) - (start - res/2)) / res + 1 <= slots // make adjustments for a snap that moves the endpoints // (do some algebra) // (end - start + res) + res <= slots * res // end - start <= res * (slots - 2) // so // res >= (end - start) / (slots - 2) // Update the timerange by applying the insights of the storage API: chosenResolution := context.TimeseriesStorageAPI.ChooseResolution(userTimerange, smallestResolution) chosenTimerange, err := api.NewSnappedTimerange(userTimerange.Start(), userTimerange.End(), int64(chosenResolution/time.Millisecond)) if err != nil { return CommandResult{}, err } if chosenTimerange.Slots() > slotLimit { return CommandResult{}, function.NewLimitError( "Requested number of data points exceeds the configured limit", chosenTimerange.Slots(), slotLimit) } hasTimeout := context.Timeout != 0 var cancellable api.Cancellable if hasTimeout { cancellable = api.NewTimeoutCancellable(time.Now().Add(context.Timeout)) } else { cancellable = api.NewCancellable() } r := context.Registry if r == nil { r = registry.Default() } defer close(cancellable.Done()) // broadcast the finish - this ensures that the future work is cancelled. evaluationContext := function.EvaluationContext{ MetricMetadataAPI: context.MetricMetadataAPI, FetchLimit: function.NewFetchCounter(context.FetchLimit), TimeseriesStorageAPI: context.TimeseriesStorageAPI, Predicate: cmd.predicate, SampleMethod: cmd.context.SampleMethod, Timerange: chosenTimerange, Cancellable: cancellable, Registry: r, Profiler: context.Profiler, OptimizationConfiguration: context.OptimizationConfiguration, EvaluationNotes: []string{}, UserSpecifiableConfig: context.UserSpecifiableConfig, } timeout := (<-chan time.Time)(nil) if hasTimeout { // A nil channel will just block forever timeout = time.After(context.Timeout) } results := make(chan []function.Value, 1) errors := make(chan error, 1) // Goroutines are never garbage collected, so we need to provide capacity so that the send always succeeds. go func() { // Evaluate the result, and send it along the goroutines. result, err := function.EvaluateMany(&evaluationContext, cmd.expressions) if err != nil { errors <- err return } results <- result }() select { case <-timeout: return CommandResult{}, function.NewLimitError("Timeout while executing the query.", context.Timeout, context.Timeout) case err := <-errors: return CommandResult{}, err case result := <-results: lists := make([]api.SeriesList, len(result)) for i := range result { lists[i], err = result[i].ToSeriesList(evaluationContext.Timerange) if err != nil { return CommandResult{}, err } } description := map[string][]string{} for _, list := range lists { for _, series := range list.Series { for key, value := range series.TagSet { description[key] = append(description[key], value) } } } for key, values := range description { natural_sort.Sort(values) filtered := []string{} for i := range values { if i == 0 || values[i-1] != values[i] { filtered = append(filtered, values[i]) } } description[key] = filtered } return CommandResult{ Body: lists, Metadata: map[string]interface{}{ "description": description, "notes": evaluationContext.EvaluationNotes, }, }, nil } }