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 }