github.com/siglens/siglens@v0.0.0-20240328180423-f7ce9ae441ed/pkg/integrations/otsdb/query/queryparser.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  	"fmt"
    21  	"strconv"
    22  	"strings"
    23  	"time"
    24  
    25  	"github.com/cespare/xxhash"
    26  	dtu "github.com/siglens/siglens/pkg/common/dtypeutils"
    27  	rutils "github.com/siglens/siglens/pkg/readerUtils"
    28  	"github.com/siglens/siglens/pkg/segment"
    29  	"github.com/siglens/siglens/pkg/segment/structs"
    30  	segutils "github.com/siglens/siglens/pkg/segment/utils"
    31  	toputils "github.com/siglens/siglens/pkg/utils"
    32  	log "github.com/sirupsen/logrus"
    33  	"github.com/valyala/fasthttp"
    34  )
    35  
    36  func MetricsQueryParser(ctx *fasthttp.RequestCtx, myid uint64) {
    37  	var httpResp toputils.HttpServerResponse
    38  	queryReq := ctx.QueryArgs()
    39  	startStr := string(queryReq.Peek("start"))
    40  	endStr := string(queryReq.Peek("end"))
    41  	m := string(queryReq.Peek("m"))
    42  	mQRequest, err := ParseRequest(startStr, endStr, m, myid)
    43  	if err != nil {
    44  		ctx.SetStatusCode(fasthttp.StatusBadRequest)
    45  		httpResp.Message = err.Error()
    46  		httpResp.StatusCode = fasthttp.StatusBadRequest
    47  		toputils.WriteResponse(ctx, httpResp)
    48  		return
    49  	}
    50  	qid := rutils.GetNextQid()
    51  	segment.LogMetricsQuery("metrics query parser", mQRequest, qid)
    52  	mQRequest.MetricsQuery.OrgId = myid
    53  	res := segment.ExecuteMetricsQuery(&mQRequest.MetricsQuery, &mQRequest.TimeRange, qid)
    54  	mQResponse, err := res.GetOTSDBResults(&mQRequest.MetricsQuery)
    55  	if err != nil {
    56  		ctx.SetStatusCode(fasthttp.StatusBadRequest)
    57  		httpResp.Message = err.Error()
    58  		httpResp.StatusCode = fasthttp.StatusBadRequest
    59  		toputils.WriteResponse(ctx, httpResp)
    60  		return
    61  	}
    62  	toputils.WriteJsonResponse(ctx, &mQResponse)
    63  	ctx.SetContentType(toputils.ContentJson)
    64  	ctx.SetStatusCode(fasthttp.StatusOK)
    65  }
    66  
    67  func ParseRequest(startStr string, endStr string, m string, myid uint64) (*structs.MetricsQueryRequest, error) {
    68  	if startStr == "" || m == "" {
    69  		return nil, fmt.Errorf("Invalid query - missing 'start' or 'm' parameter")
    70  	}
    71  	start, err := parseTime(startStr)
    72  	if err != nil {
    73  		log.Errorf("MetricsQueryParser: Unable to parse Start time: %v. Error: %+v", startStr, err)
    74  		return nil, fmt.Errorf("Unable to parse Start time. Error: %+v", err)
    75  	}
    76  	var end uint32
    77  	if endStr == "" {
    78  		end = uint32(time.Now().Unix())
    79  	} else {
    80  		end, err = parseTime(endStr)
    81  		if err != nil {
    82  			log.Errorf("MetricsQueryParser: Unable to parse End time: %v. Error: %+v", endStr, err)
    83  			return nil, fmt.Errorf("Unable to parse End time. Error: %+v", err)
    84  		}
    85  	}
    86  
    87  	metricName, hashedMName, tags, err := parseMetricTag(m)
    88  	if err != nil {
    89  		log.Errorf("MetricsQueryParser: Unable to parse Metrics Tag: %v. Error: %+v", m, err)
    90  		return nil, fmt.Errorf("Unable to parse Metrics Tag. Error: %+v", err)
    91  	}
    92  	aggregator, downsampler, err := parseAggregatorDownsampler(m)
    93  	if err != nil {
    94  		return nil, fmt.Errorf("Unable to parse Aggregator and/or Downsampler. Error: %+v", err)
    95  	}
    96  
    97  	metricQueryRequest := &structs.MetricsQueryRequest{
    98  		MetricsQuery: structs.MetricsQuery{
    99  			MetricName:  metricName,
   100  			HashedMName: hashedMName,
   101  			TagsFilters: tags,
   102  			Aggregator:  structs.Aggreation{AggregatorFunction: aggregator},
   103  			Downsampler: downsampler,
   104  			OrgId:       myid,
   105  		},
   106  		TimeRange: dtu.MetricsTimeRange{
   107  			StartEpochSec: start,
   108  			EndEpochSec:   end,
   109  		},
   110  	}
   111  	return metricQueryRequest, nil
   112  }
   113  
   114  func parseTime(timeStr string) (uint32, error) {
   115  	if strings.HasSuffix(timeStr, "-ago") {
   116  		var duration time.Duration
   117  		durationStr := strings.TrimSuffix(timeStr, "-ago")
   118  		unit := durationStr[len(durationStr)-1]
   119  		durationNum, err := strconv.Atoi(strings.TrimSuffix(durationStr, string(unit)))
   120  		if err != nil {
   121  			log.Errorf("parseTime: invalid time format: %s. Error: %v", timeStr, err)
   122  			return 0, err
   123  		}
   124  		switch unit {
   125  		case 's':
   126  			duration = time.Duration(durationNum) * time.Second
   127  		case 'm':
   128  			duration = time.Duration(durationNum) * time.Minute
   129  		case 'h':
   130  			duration = time.Duration(durationNum) * time.Hour
   131  		case 'd':
   132  			duration = time.Duration(durationNum) * 24 * time.Hour
   133  		case 'w':
   134  			duration = time.Duration(durationNum) * 7 * 24 * time.Hour
   135  		case 'n':
   136  			duration = time.Duration(durationNum) * 30 * 24 * time.Hour
   137  		case 'y':
   138  			duration = time.Duration(durationNum) * 365 * 24 * time.Hour
   139  		default:
   140  			log.Errorf("parseTime: invalid time format: %s", timeStr)
   141  			return 0, fmt.Errorf("invalid time format: %s", timeStr)
   142  		}
   143  		return uint32(time.Now().Add(-duration).Unix()), nil
   144  	}
   145  	// if it is not a relative time, parse as absolute time
   146  	var t time.Time
   147  	var err error
   148  	// unixtime
   149  	if unixTime, err := strconv.ParseInt(timeStr, 10, 64); err == nil {
   150  		if toputils.IsTimeInMilli(uint64(unixTime)) {
   151  			return uint32(unixTime / 1e3), nil
   152  		}
   153  		return uint32(unixTime), nil
   154  	}
   155  	//absolute time formats
   156  	timeStr = absoluteTimeFormat(timeStr)
   157  	formats := []string{
   158  		"2006-01-02 15:04:05",
   159  		"2006-01-02",
   160  	}
   161  	for _, format := range formats {
   162  		t, err = time.Parse(format, timeStr)
   163  		if err == nil {
   164  			break
   165  		}
   166  	}
   167  	if err != nil {
   168  		log.Errorf("parseTime: invalid time format: %s. Error: %v", timeStr, err)
   169  		return 0, err
   170  	}
   171  	return uint32(t.Unix()), nil
   172  }
   173  
   174  func absoluteTimeFormat(timeStr string) string {
   175  	if strings.Contains(timeStr, "-") && strings.Count(timeStr, "-") == 1 {
   176  		timeStr = strings.Replace(timeStr, "-", " ", 1)
   177  	}
   178  	timeStr = strings.Replace(timeStr, "/", "-", 2)
   179  	if strings.Contains(timeStr, ":") {
   180  		if strings.Count(timeStr, ":") < 2 {
   181  			timeStr += ":00"
   182  		}
   183  	}
   184  	return timeStr
   185  }
   186  
   187  func parseMetricTag(m string) (string, uint64, []*structs.TagsFilter, error) {
   188  	logicalOperator := segutils.And
   189  	metricStart := strings.LastIndex(m, ":") + 1
   190  	metricEnd := strings.Index(m, "{")
   191  	if metricEnd == -1 {
   192  		metricEnd = len(m)
   193  	}
   194  	metric := m[metricStart:metricEnd]
   195  	hashedMName := xxhash.Sum64String(metric)
   196  
   197  	tagsStart := strings.Index(m, "{")
   198  	tagsEnd := strings.Index(m, "}")
   199  	if tagsStart == -1 || tagsEnd == -1 {
   200  		log.Errorf("parseMetricTag: invalid query format, either tasgStart or tagsEnd was -1 for m: %s", m)
   201  		return metric, hashedMName, nil, fmt.Errorf("invalid query format, either tasgStart or tagsEnd was -1 for m: %s", m)
   202  	}
   203  	tagsStr := m[tagsStart+1 : tagsEnd]
   204  	listOfTags := strings.Split(tagsStr, ",")
   205  	tags := make([]*structs.TagsFilter, 0)
   206  	var val interface{}
   207  	var key, multipleTagValues string
   208  	var hashedTagVal uint64
   209  	for _, tag := range listOfTags {
   210  		pair := strings.Split(tag, "=")
   211  		if len(pair) != 2 {
   212  			log.Errorf("parseMetricTag: invalid tag format, len(pair)=%v was not 2 for tag: %s", len(pair), tag)
   213  			return metric, hashedMName, nil, fmt.Errorf("invalid tag format, len(pair)=%v was not 2 for tag: %s", len(pair), tag)
   214  		}
   215  		key, multipleTagValues = strings.TrimSpace(pair[0]), strings.TrimSpace(pair[1])
   216  		valuesList := strings.Split(multipleTagValues, "|")
   217  		if len(valuesList) > 1 {
   218  			logicalOperator = segutils.Or
   219  		}
   220  		for _, val = range valuesList {
   221  			switch v := val.(type) {
   222  			case string:
   223  				if (strings.HasPrefix(v, `"`) && strings.HasSuffix(v, `"`) && len(v) > 1) ||
   224  					(strings.HasPrefix(v, `'`) && strings.HasSuffix(v, `'`) && len(v) > 1) {
   225  					v = v[1 : len(v)-1]
   226  					val = v
   227  				}
   228  				hashedTagVal = xxhash.Sum64String(v)
   229  			case int64:
   230  				hashedTagVal = uint64(v)
   231  			case float64:
   232  				hashedTagVal = uint64(v)
   233  			case uint64:
   234  				hashedTagVal = v
   235  			default:
   236  				log.Errorf("parseMetricTag: invalid tag value type %T for value %v", val, val)
   237  				return metric, hashedMName, nil, fmt.Errorf("parseMetricTag: invalid tag value type %v", val)
   238  			}
   239  			tags = append(tags, &structs.TagsFilter{
   240  				TagKey:          key,
   241  				RawTagValue:     val,
   242  				HashTagValue:    hashedTagVal,
   243  				LogicalOperator: logicalOperator,
   244  			})
   245  		}
   246  	}
   247  	return metric, hashedMName, tags, nil
   248  }
   249  
   250  func parseAggregatorDownsampler(m string) (segutils.AggregateFunctions, structs.Downsampler, error) {
   251  	aggregaterDownsamplerList := strings.Split(m, ":")
   252  	aggregator := segutils.Sum
   253  	downsamplerStr := ""
   254  	var ok bool
   255  	var aggregatorMapping = map[string]segutils.AggregateFunctions{
   256  		"count":       segutils.Count,
   257  		"avg":         segutils.Avg,
   258  		"min":         segutils.Min,
   259  		"max":         segutils.Max,
   260  		"sum":         segutils.Sum,
   261  		"cardinality": segutils.Cardinality,
   262  		"quantile":    segutils.Quantile,
   263  	}
   264  	agg := structs.Aggreation{AggregatorFunction: aggregatorMapping["avg"]}
   265  	downsampler := structs.Downsampler{Interval: 1, Unit: "m", Aggregator: agg}
   266  
   267  	if len(aggregaterDownsamplerList) >= 2 {
   268  		aggregator, ok = aggregatorMapping[aggregaterDownsamplerList[0]]
   269  		if !ok {
   270  			log.Errorf("parseAggregatorDownsampler: unsupported aggregator function: %s", aggregaterDownsamplerList[0])
   271  			return segutils.Sum, downsampler, fmt.Errorf("unsupported aggregator function: %s", aggregaterDownsamplerList[0])
   272  		}
   273  		if len(aggregaterDownsamplerList) > 2 {
   274  			downsamplerStr = aggregaterDownsamplerList[1]
   275  		}
   276  	}
   277  
   278  	if downsamplerStr != "" {
   279  		downsampleComp := strings.Split(downsamplerStr, "-")
   280  		if len(downsampleComp) == 1 {
   281  			log.Errorf("parseAggregatorDownsampler: invalid downsampler format for %s", downsamplerStr)
   282  			return aggregator, downsampler, fmt.Errorf("invalid downsampler format for %s", downsamplerStr)
   283  		}
   284  		downsampler.Aggregator = structs.Aggreation{AggregatorFunction: aggregatorMapping[downsampleComp[1]]}
   285  		downsamplerIntervalUnit := downsampleComp[0]
   286  		var intervalStr string
   287  		var unit string
   288  		for i, c := range downsamplerIntervalUnit {
   289  			if c >= '0' && c <= '9' {
   290  				intervalStr += string(c)
   291  			} else if c == 'c' && i == len(downsamplerIntervalUnit)-1 {
   292  				downsampler.CFlag = true
   293  				break
   294  			} else {
   295  				unit += string(c)
   296  			}
   297  		}
   298  		if len(intervalStr) == 0 || len(unit) == 0 {
   299  			log.Errorf("parseAggregatorDownsampler: invalid downsampler(no interval or unit) format for %s", downsamplerStr)
   300  			return aggregator, downsampler, fmt.Errorf("invalid downsampler format for %s", downsamplerStr)
   301  		}
   302  		interval, err := strconv.Atoi(intervalStr)
   303  		if err != nil {
   304  			log.Errorf("parseAggregatorDownsampler: invalid interval in downsampler %s. Error: %v", downsamplerStr, err)
   305  			return aggregator, downsampler, fmt.Errorf("invalid interval in downsampler")
   306  		}
   307  		downsampler.Interval = interval
   308  		downsampler.Unit = unit
   309  	}
   310  	return aggregator, downsampler, nil
   311  }