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 }