github.com/siglens/siglens@v0.0.0-20240328180423-f7ce9ae441ed/pkg/integrations/otsdb/query/expqueryparser.go (about)

     1  /*
     2  Copyright 2023.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package otsdbquery
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  	"strconv"
    23  	"sync"
    24  	"time"
    25  
    26  	"github.com/cespare/xxhash"
    27  	dtu "github.com/siglens/siglens/pkg/common/dtypeutils"
    28  	rutils "github.com/siglens/siglens/pkg/readerUtils"
    29  	"github.com/siglens/siglens/pkg/segment"
    30  	"github.com/siglens/siglens/pkg/segment/structs"
    31  	utils "github.com/siglens/siglens/pkg/segment/utils"
    32  	. "github.com/siglens/siglens/pkg/utils"
    33  	log "github.com/sirupsen/logrus"
    34  	"github.com/valyala/fasthttp"
    35  )
    36  
    37  var aggregatorMapping = map[string]utils.AggregateFunctions{
    38  	"count":       utils.Count,
    39  	"avg":         utils.Avg,
    40  	"min":         utils.Min,
    41  	"max":         utils.Max,
    42  	"sum":         utils.Sum,
    43  	"cardinality": utils.Cardinality,
    44  }
    45  
    46  func MetricsQueryExpressionsParser(ctx *fasthttp.RequestCtx) {
    47  	var httpResp HttpServerResponse
    48  	rawJSON := ctx.PostBody()
    49  	if rawJSON == nil {
    50  		ctx.SetStatusCode(fasthttp.StatusBadRequest)
    51  		httpResp.Message = "Invalid raw JSON request"
    52  		httpResp.StatusCode = fasthttp.StatusBadRequest
    53  		WriteResponse(ctx, httpResp)
    54  		return
    55  	}
    56  	var readJSON *structs.OTSDBMetricsQueryExpRequest
    57  	if err := json.Unmarshal(rawJSON, &readJSON); err != nil {
    58  		var badJsonKey string
    59  		if e, ok := err.(*json.UnmarshalTypeError); ok {
    60  			badJsonKey = e.Field
    61  		}
    62  		log.Errorf("MetricsQueryExpressionsParser: Error unmarshalling JSON. Failed to parse key: %s. Error: %v", badJsonKey, err)
    63  		ctx.SetStatusCode(fasthttp.StatusBadRequest)
    64  		httpResp.Message = err.Error()
    65  		httpResp.StatusCode = fasthttp.StatusBadRequest
    66  		WriteResponse(ctx, httpResp)
    67  		return
    68  	}
    69  	metricsQueryRequest, err := MetricsQueryExpressionsParseRequest(readJSON)
    70  	if err != nil {
    71  		ctx.SetStatusCode(fasthttp.StatusBadRequest)
    72  		httpResp.Message = err.Error()
    73  		httpResp.StatusCode = fasthttp.StatusBadRequest
    74  		WriteResponse(ctx, httpResp)
    75  		return
    76  	}
    77  	var wg sync.WaitGroup
    78  	expMetricsQueryResult := make(map[string][]*structs.MetricsQueryResponse)
    79  	var expMetricsQueryResultLock sync.Mutex
    80  	for alias, req := range metricsQueryRequest {
    81  		wg.Add(1)
    82  		go asyncMetricsQueryRequest(&wg, alias, req, &expMetricsQueryResult, &expMetricsQueryResultLock)
    83  	}
    84  	wg.Wait()
    85  	WriteJsonResponse(ctx, &expMetricsQueryResult)
    86  	ctx.SetContentType(ContentJson)
    87  	ctx.SetStatusCode(fasthttp.StatusOK)
    88  }
    89  
    90  func asyncMetricsQueryRequest(wg *sync.WaitGroup, alias string, req *structs.MetricsQueryRequest, expMetricsQueryResult *map[string][]*structs.MetricsQueryResponse, expMetricsQueryResultLock *sync.Mutex) {
    91  	defer wg.Done()
    92  	qid := rutils.GetNextQid()
    93  	segment.LogMetricsQuery("metrics query parser", req, qid)
    94  	res := segment.ExecuteMetricsQuery(&req.MetricsQuery, &req.TimeRange, qid)
    95  	mQResponse, err := res.GetOTSDBResults(&req.MetricsQuery)
    96  	if err != nil {
    97  		return
    98  	}
    99  	expMetricsQueryResultLock.Lock()
   100  	(*expMetricsQueryResult)[alias] = mQResponse
   101  	expMetricsQueryResultLock.Unlock()
   102  }
   103  
   104  func MetricsQueryExpressionsParseRequest(queryRequest *structs.OTSDBMetricsQueryExpRequest) (map[string]*structs.MetricsQueryRequest, error) {
   105  	var metricsQueryRequest map[string]*structs.MetricsQueryRequest = make(map[string]*structs.MetricsQueryRequest)
   106  	var timeRange dtu.MetricsTimeRange
   107  
   108  	var startStr string
   109  	if queryRequest.Time.Start == nil || queryRequest.Time.Start == "" {
   110  		return nil, fmt.Errorf("Invalid query - missing 'Start Time'")
   111  	}
   112  	switch v := queryRequest.Time.Start.(type) {
   113  	case int:
   114  		startStr = fmt.Sprintf("%d", v)
   115  	case float64:
   116  		startStr = fmt.Sprintf("%d", int64(v))
   117  	case string:
   118  		startStr = v
   119  	}
   120  	start, err := parseTime(startStr)
   121  	if err != nil {
   122  		log.Errorf("MetricsQueryExpressionsParseRequest: Unable to parse Start time: %v. Error: %+v", queryRequest.Time.Start, err)
   123  		return nil, fmt.Errorf("Unable to parse Start time. Error: %+v", err)
   124  	}
   125  
   126  	var endStr string
   127  	var end uint32
   128  	if queryRequest.Time.End == nil || queryRequest.Time.End == "" {
   129  		end = uint32(time.Now().Unix())
   130  	} else {
   131  		switch v := queryRequest.Time.End.(type) {
   132  		case int:
   133  			endStr = fmt.Sprintf("%d", v)
   134  		case float64:
   135  			endStr = fmt.Sprintf("%d", int64(v))
   136  		case string:
   137  			endStr = v
   138  		}
   139  		end, err = parseTime(endStr)
   140  		if err != nil {
   141  			log.Errorf("MetricsQueryExpressionsParseRequest: Unable to parse End time: %v. Error: %+v", queryRequest.Time.End, err)
   142  			return nil, fmt.Errorf("Unable to parse End time. Error: %+v", err)
   143  		}
   144  	}
   145  
   146  	timeRange.StartEpochSec = start
   147  	timeRange.EndEpochSec = end
   148  
   149  	aggregator, downsampler, err := metricsQueryExpressionsParseAggregatorDownsampler(queryRequest)
   150  	if err != nil {
   151  		log.Errorf("MetricsQueryExpressionsParseRequest: Unable to parse Aggregator and/or Downsampler. Error: %+v", err)
   152  		return nil, fmt.Errorf("Unable to parse Aggregator and/or Downsampler. Error: %+v", err)
   153  	}
   154  	metrics := metricsQueryExpressionsParseMetrics(queryRequest)
   155  	tags := metricsQueryExpressionsParseTags(queryRequest)
   156  
   157  	for _, output := range queryRequest.Outputs {
   158  		id := output.Id
   159  		metricInfo, ok := metrics[id]
   160  		if !ok {
   161  			log.Errorf("MetricsQueryExpressionsParseRequest: the output id %v does not match any metric id", id)
   162  			continue
   163  		}
   164  		if metricInfo["aggregator"] != -1 {
   165  			aggregator = metricInfo["aggregator"].(utils.AggregateFunctions)
   166  		}
   167  		tagsFilter, ok := tags[metricInfo["filter"].(string)]
   168  		if !ok {
   169  			log.Errorf("MetricsQueryExpressionsParseRequest: tags does not contain filter %s", metricInfo["filter"])
   170  			continue
   171  		}
   172  
   173  		mReq := &structs.MetricsQueryRequest{
   174  			MetricsQuery: structs.MetricsQuery{
   175  				MetricName:  metricInfo["metric-name"].(string),
   176  				HashedMName: metricInfo["hashed-mname"].(uint64),
   177  				TagsFilters: tagsFilter,
   178  				Aggregator:  structs.Aggreation{AggregatorFunction: aggregator},
   179  				Downsampler: downsampler,
   180  			},
   181  			TimeRange: timeRange,
   182  		}
   183  		metricsQueryRequest[output.Alias] = mReq
   184  	}
   185  	return metricsQueryRequest, nil
   186  }
   187  
   188  func metricsQueryExpressionsParseAggregatorDownsampler(queryRequest *structs.OTSDBMetricsQueryExpRequest) (utils.AggregateFunctions, structs.Downsampler, error) {
   189  	downsampler := structs.Downsampler{Interval: 1, Unit: "m", Aggregator: structs.Aggreation{AggregatorFunction: aggregatorMapping["avg"]}}
   190  	aggregator, ok := aggregatorMapping[queryRequest.Time.Aggregator]
   191  	if !ok {
   192  		log.Errorf("metricsQueryExpressionsParseAggregatorDownsampler: unsupported aggregator function: %s", queryRequest.Time.Aggregator)
   193  		return -1, downsampler, fmt.Errorf("unsupported aggregator function: %s", queryRequest.Time.Aggregator)
   194  	}
   195  	if queryRequest.Time.Downsampler.Interval == "" && queryRequest.Time.Downsampler.Aggregator == "" {
   196  		return aggregator, downsampler, nil
   197  	}
   198  	if queryRequest.Time.Downsampler.Interval == "" || queryRequest.Time.Downsampler.Aggregator == "" {
   199  		log.Errorf("metricsQueryExpressionsParseAggregatorDownsampler: unsupported aggregator function: %s", queryRequest.Time.Aggregator)
   200  		return aggregator, downsampler, fmt.Errorf("incomplete downsampler function: %v", queryRequest.Time.Downsampler)
   201  	}
   202  	downsampler.Aggregator = structs.Aggreation{AggregatorFunction: aggregatorMapping[queryRequest.Time.Downsampler.Aggregator]}
   203  	var intervalStr, unit string
   204  	for _, c := range queryRequest.Time.Downsampler.Interval {
   205  		if c >= '0' && c <= '9' {
   206  			intervalStr += string(c)
   207  		} else {
   208  			unit += string(c)
   209  		}
   210  	}
   211  	if len(intervalStr) == 0 || len(unit) == 0 {
   212  		log.Errorf("metricsQueryExpressionsParseAggregatorDownsampler: invalid downsampler(no interval or unit) format for %s", queryRequest.Time.Downsampler.Interval)
   213  		return aggregator, downsampler, fmt.Errorf("invalid interval format for downsampler %s", queryRequest.Time.Downsampler.Interval)
   214  	}
   215  	interval, err := strconv.Atoi(intervalStr)
   216  	if err != nil {
   217  		log.Errorf("metricsQueryExpressionsParseAggregatorDownsampler: invalid interval in downsampler. Error: %v", err)
   218  		return aggregator, downsampler, err
   219  	}
   220  	downsampler.Interval = interval
   221  	downsampler.Unit = unit
   222  	return aggregator, downsampler, nil
   223  }
   224  
   225  func metricsQueryExpressionsParseMetrics(queryRequest *structs.OTSDBMetricsQueryExpRequest) map[string]map[string]interface{} {
   226  	var metricInfo map[string]map[string]interface{} = make(map[string]map[string]interface{})
   227  	for _, metric := range queryRequest.Metrics {
   228  		aggregator, ok := aggregatorMapping[queryRequest.Time.Aggregator]
   229  		if !ok {
   230  			aggregator = -1
   231  		}
   232  		metricInfo[metric.Id] = map[string]interface{}{"metric-name": metric.MetricName, "hashed-mname": xxhash.Sum64String(metric.MetricName), "filter": metric.Filter, "aggregator": aggregator}
   233  	}
   234  	return metricInfo
   235  }
   236  
   237  func metricsQueryExpressionsParseTags(queryRequest *structs.OTSDBMetricsQueryExpRequest) map[string][]*structs.TagsFilter {
   238  	var tagInfo map[string][]*structs.TagsFilter = make(map[string][]*structs.TagsFilter)
   239  
   240  	for _, tags := range queryRequest.Filters {
   241  		id := tags.Id
   242  		tagsList := make([]*structs.TagsFilter, 0)
   243  		for _, filter := range tags.Tags {
   244  			tagsList = append(tagsList, &structs.TagsFilter{
   245  				TagKey:       filter.Tagk,
   246  				RawTagValue:  filter.Filter,
   247  				HashTagValue: xxhash.Sum64String(filter.Filter),
   248  			})
   249  		}
   250  		tagInfo[id] = tagsList
   251  	}
   252  	return tagInfo
   253  }