github.com/siglens/siglens@v0.0.0-20240328180423-f7ce9ae441ed/pkg/querytracker/querytracker.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 querytracker 18 19 import ( 20 "bytes" 21 "errors" 22 "fmt" 23 "os" 24 "sort" 25 "strings" 26 "sync" 27 "time" 28 29 "encoding/json" 30 31 "github.com/imdario/mergo" 32 jsoniter "github.com/json-iterator/go" 33 "github.com/siglens/siglens/pkg/blob" 34 "github.com/siglens/siglens/pkg/config" 35 "github.com/siglens/siglens/pkg/segment/structs" 36 "github.com/siglens/siglens/pkg/utils" 37 38 vtable "github.com/siglens/siglens/pkg/virtualtable" 39 log "github.com/sirupsen/logrus" 40 "github.com/valyala/fasthttp" 41 ) 42 43 const MAX_QUERIES_TO_TRACK = 100 // this limits how many PQS searches we are doing 44 const MAX_CANDIDATE_QUERIES = 10_000 // this limits how many unique queries we use in our stats calculations 45 46 const STALE_QUERIES_EXPIRY_SECS = 21600 // queries will get booted out if they have not been seen in 6 hours 47 const STALE_SLEEP_SECS = 1800 48 49 const FLUSH_SLEEP_SECS = 120 50 51 const MAX_NUM_GROUPBY_COLS = 10 52 53 var localPersistentQueries = map[string]*PersistentSearchNode{} // map[pqid] ==> *PersistentQuery 54 var allNodesPQsSorted = []*PersistentSearchNode{} 55 var persistentInfoLock = sync.RWMutex{} 56 var groupByOverrideLock = sync.RWMutex{} 57 var localPersistentAggs = map[string]*PersistentAggregation{} // map[pqid] ==> *PersistentAggregation 58 var allPersistentAggsSorted = []*PersistentAggregation{} 59 var localGroupByOverride = map[string]*PersistentGroupBy{} 60 61 type PersistentSearchNode struct { 62 SearchNode *structs.SearchNode 63 PersistentInfo 64 } 65 66 type PersistentAggregation struct { 67 QueryAggs *structs.QueryAggregators 68 PersistentInfo 69 } 70 71 type PersistentGroupBy struct { 72 GroupByCols map[string]bool 73 MeasureCols map[string]bool 74 } 75 76 type PersistentInfo struct { 77 AllTables map[string]bool 78 LocalUsage uint32 79 TotalUsage uint32 `json:"-"` 80 LastUsedEpoch uint64 81 Pqid string 82 } 83 84 func InitQT() { 85 readSavedQueryInfo() 86 go removeStaleEntries() 87 go runFlushLoop() 88 } 89 90 func runFlushLoop() { 91 for { 92 time.Sleep(FLUSH_SLEEP_SECS * time.Second) 93 persistentInfoLock.Lock() 94 flushPQueriesToDisk() 95 persistentInfoLock.Unlock() 96 err := blob.UploadQueryNodeDir() 97 if err != nil { 98 log.Errorf("runFlushLoop: Error in uploading the query nodes dir, err: %v", err) 99 continue 100 } 101 } 102 } 103 104 func removeStaleEntries() { 105 for { 106 time.Sleep(STALE_SLEEP_SECS * time.Second) 107 removeOldEntries() 108 } 109 } 110 111 func removeOldEntries() { 112 persistentInfoLock.Lock() 113 defer persistentInfoLock.Unlock() 114 now := uint64(time.Now().Unix()) 115 totalQueries := len(allNodesPQsSorted) 116 removed := uint32(0) 117 for i := totalQueries - 1; i >= 0; i-- { 118 if now-allNodesPQsSorted[i].LastUsedEpoch > STALE_QUERIES_EXPIRY_SECS { 119 removed++ 120 delete(localPersistentQueries, allNodesPQsSorted[i].Pqid) 121 allNodesPQsSorted = append(allNodesPQsSorted[:i], allNodesPQsSorted[i+1:]...) 122 } 123 } 124 125 totalAggs := len(allPersistentAggsSorted) 126 for i := totalAggs - 1; i >= 0; i-- { 127 if now-allPersistentAggsSorted[i].LastUsedEpoch > STALE_QUERIES_EXPIRY_SECS { 128 removed++ 129 delete(localPersistentQueries, allPersistentAggsSorted[i].Pqid) 130 allPersistentAggsSorted = append(allPersistentAggsSorted[:i], allPersistentAggsSorted[i+1:]...) 131 } 132 } 133 if removed > 0 { 134 log.Infof("RemoveStaleEntries: removed %v stale entries, query len=%v, aggs len=%v", removed, len(allNodesPQsSorted), 135 len(allPersistentAggsSorted)) 136 137 sort.Slice(allNodesPQsSorted, func(i, j int) bool { 138 return allNodesPQsSorted[i].TotalUsage > allNodesPQsSorted[j].TotalUsage 139 }) 140 sort.Slice(allPersistentAggsSorted, func(i, j int) bool { 141 return allPersistentAggsSorted[i].TotalUsage > allPersistentAggsSorted[j].TotalUsage 142 }) 143 } else { 144 log.Infof("RemoveStaleEntries: removed criteria not met, query len=%v, aggs len=%+v", len(allNodesPQsSorted), 145 len(allPersistentAggsSorted)) 146 } 147 148 } 149 150 func GetTopNPersistentSearches(intable string, orgid uint64) (map[string]*structs.SearchNode, error) { 151 152 res := make(map[string]*structs.SearchNode) 153 if !config.IsPQSEnabled() { 154 return res, nil 155 } 156 157 persistentInfoLock.Lock() 158 defer persistentInfoLock.Unlock() 159 160 for pqNum, pqinfo := range allNodesPQsSorted { 161 if pqNum > MAX_QUERIES_TO_TRACK { 162 break 163 } 164 if _, ok := pqinfo.AllTables[intable]; ok { 165 res[pqinfo.Pqid] = pqinfo.SearchNode 166 } else { 167 // if during qtupdate insertion the indexnames contained wildcard, and there was no index created 168 // at the time, then that would have not expanded to real indexnames, we do it now 169 found := false 170 for idxname := range pqinfo.AllTables { 171 indexNamesRetrieved := vtable.ExpandAndReturnIndexNames(idxname, orgid, false) 172 for _, t := range indexNamesRetrieved { 173 pqinfo.AllTables[t] = true // for future so that we don't enter this idxname expansion block 174 if t == intable { 175 res[pqinfo.Pqid] = pqinfo.SearchNode 176 found = true 177 break // inner for loop exit 178 } 179 } 180 if found { 181 break // outer for loop exit 182 } 183 } 184 } 185 } 186 187 return res, nil 188 } 189 190 func GetPersistentColumns(intable string, orgid uint64) (map[string]bool, error) { 191 persistentQueries, err := GetTopNPersistentSearches(intable, orgid) 192 193 if err != nil { 194 log.Errorf("GetPersistentColumns: error getting persistent queries: %v", err) 195 return map[string]bool{}, err 196 } 197 198 pqsCols := make(map[string]bool) 199 for _, searchNode := range persistentQueries { 200 allColumns, _ := searchNode.GetAllColumnsToSearch() 201 for col := range allColumns { 202 pqsCols[col] = true 203 } 204 } 205 206 return pqsCols, nil 207 } 208 209 type colUsage struct { 210 col string 211 usage int 212 } 213 214 // returns a sorted slice of most used group by columns, and all measure columns. 215 func GetTopPersistentAggs(table string) ([]string, map[string]bool) { 216 groupByColsUsage := make(map[string]int) 217 measureInfoUsage := make(map[string]bool) 218 219 if !config.IsPQSEnabled() { 220 return []string{}, measureInfoUsage 221 } 222 overrideGroupByCols := make([]string, 0) 223 persistentInfoLock.Lock() 224 defer persistentInfoLock.Unlock() 225 226 if strings.HasPrefix(table, "jaeger-") { 227 overrideGroupByCols = append(overrideGroupByCols, "traceID", "serviceName", "operationName") 228 measureInfoUsage["startTime"] = true 229 } 230 231 if _, ok := localGroupByOverride[table]; ok { 232 if localGroupByOverride[table].GroupByCols != nil { 233 cols := localGroupByOverride[table].GroupByCols 234 for col := range cols { 235 overrideGroupByCols = append(overrideGroupByCols, col) 236 } 237 } 238 if localGroupByOverride[table].MeasureCols != nil { 239 mcols := localGroupByOverride[table].MeasureCols 240 for m := range mcols { 241 measureInfoUsage[m] = true 242 } 243 } 244 } 245 246 for idx, agginfo := range allPersistentAggsSorted { 247 if idx > MAX_QUERIES_TO_TRACK { 248 break 249 } 250 if _, ok := agginfo.AllTables[table]; !ok { 251 continue 252 } 253 queryAggs := agginfo.QueryAggs 254 if queryAggs == nil || queryAggs.GroupByRequest == nil { 255 continue 256 } 257 cols := queryAggs.GroupByRequest.GroupByColumns 258 for _, col := range cols { 259 // groupby columns from more popular queries should get more preference, so use usage count 260 groupByColsUsage[col] += int(agginfo.TotalUsage) 261 } 262 measureInfo := queryAggs.GroupByRequest.MeasureOperations 263 for _, m := range measureInfo { 264 measureInfoUsage[m.MeasureCol] = true 265 } 266 } 267 var ss []colUsage 268 for k, v := range groupByColsUsage { 269 ss = append(ss, colUsage{k, v}) 270 } 271 sort.Slice(ss, func(i, j int) bool { 272 return ss[i].usage > ss[j].usage 273 }) 274 var finalCols []string 275 if len(overrideGroupByCols) >= MAX_NUM_GROUPBY_COLS { 276 finalCols = make([]string, MAX_NUM_GROUPBY_COLS) 277 finalCols = append(finalCols, overrideGroupByCols[:MAX_NUM_GROUPBY_COLS]...) 278 } else { 279 finalCols = append(finalCols, overrideGroupByCols[:]...) 280 for _, s := range ss { 281 if len(finalCols) <= MAX_NUM_GROUPBY_COLS { 282 finalCols = append(finalCols, s.col) 283 } else { 284 break 285 } 286 } 287 } 288 return finalCols, measureInfoUsage 289 } 290 291 func UpdateQTUsage(tableName []string, sn *structs.SearchNode, aggs *structs.QueryAggregators) { 292 293 if len(tableName) == 0 { 294 return 295 } 296 297 persistentInfoLock.Lock() 298 defer persistentInfoLock.Unlock() 299 updateSearchNodeUsage(tableName, sn) 300 updateAggsUsage(tableName, aggs) 301 } 302 303 func updateSearchNodeUsage(tableName []string, sn *structs.SearchNode) { 304 305 if sn == nil { 306 return 307 } 308 if sn.NodeType == structs.MatchAllQuery { 309 return 310 } 311 312 pqid := GetHashForQuery(sn) 313 314 var pqinfo *PersistentSearchNode 315 var ok bool 316 pqinfo, ok = localPersistentQueries[pqid] 317 if !ok { 318 if len(localPersistentQueries) >= MAX_CANDIDATE_QUERIES { 319 log.Infof("updateSearchNodeUsage: reached limit %v for candidate queries, booting last one", MAX_CANDIDATE_QUERIES) 320 delete(localPersistentQueries, allNodesPQsSorted[len(allNodesPQsSorted)-1].Pqid) 321 allNodesPQsSorted = allNodesPQsSorted[:len(allNodesPQsSorted)-1] 322 } 323 pInfo := PersistentInfo{AllTables: make(map[string]bool), Pqid: pqid} 324 pqinfo = &PersistentSearchNode{SearchNode: sn} 325 pqinfo.PersistentInfo = pInfo 326 localPersistentQueries[pqid] = pqinfo 327 allNodesPQsSorted = append(allNodesPQsSorted, pqinfo) 328 log.Infof("updateSearchNodeUsage: added pqid %v, total=%v, tableName=%v", 329 pqid, len(localPersistentQueries), tableName) 330 331 } 332 333 pqinfo.LastUsedEpoch = uint64(time.Now().Unix()) 334 pqinfo.TotalUsage++ 335 pqinfo.LocalUsage++ 336 for _, tName := range tableName { 337 pqinfo.AllTables[tName] = true 338 } 339 340 sort.Slice(allNodesPQsSorted, func(i, j int) bool { 341 return allNodesPQsSorted[i].TotalUsage > allNodesPQsSorted[j].TotalUsage 342 }) 343 } 344 345 func updateAggsUsage(tableName []string, aggs *structs.QueryAggregators) { 346 347 if aggs == nil || aggs.IsAggsEmpty() { 348 return 349 } 350 351 pqid := GetHashForAggs(aggs) 352 353 var pqinfo *PersistentAggregation 354 var ok bool 355 pqinfo, ok = localPersistentAggs[pqid] 356 if !ok { 357 if len(localPersistentAggs) >= MAX_CANDIDATE_QUERIES { 358 log.Infof("updateAggsUsage: reached limit %v for candidate queries, booting last one", MAX_CANDIDATE_QUERIES) 359 delete(localPersistentAggs, allPersistentAggsSorted[len(allPersistentAggsSorted)-1].Pqid) 360 allPersistentAggsSorted = allPersistentAggsSorted[:len(allPersistentAggsSorted)-1] 361 } 362 pInfo := PersistentInfo{AllTables: make(map[string]bool), Pqid: pqid} 363 pqinfo = &PersistentAggregation{QueryAggs: aggs} 364 pqinfo.PersistentInfo = pInfo 365 localPersistentAggs[pqid] = pqinfo 366 allPersistentAggsSorted = append(allPersistentAggsSorted, pqinfo) 367 log.Infof("updateAggsUsage: added pqid %v, total=%v, tableName=%v", 368 pqid, len(localPersistentAggs), tableName) 369 370 } 371 372 pqinfo.LastUsedEpoch = uint64(time.Now().Unix()) 373 pqinfo.TotalUsage++ 374 pqinfo.LocalUsage++ 375 for _, tName := range tableName { 376 pqinfo.AllTables[tName] = true 377 } 378 379 sort.Slice(allPersistentAggsSorted, func(i, j int) bool { 380 return allPersistentAggsSorted[i].TotalUsage > allPersistentAggsSorted[j].TotalUsage 381 }) 382 } 383 384 func GetQTUsageInfo(tableName []string, sn *structs.SearchNode) (*PersistentSearchNode, error) { 385 386 if sn == nil { 387 return nil, errors.New("sn was nil") 388 } 389 390 pqid := GetHashForQuery(sn) 391 392 persistentInfoLock.RLock() 393 defer persistentInfoLock.RUnlock() 394 395 pqinfo, ok := localPersistentQueries[pqid] 396 if ok { 397 return pqinfo, nil 398 } else { 399 for _, pqinfo := range allNodesPQsSorted { 400 if pqinfo.Pqid == pqid { 401 return pqinfo, nil 402 } 403 } 404 } 405 406 return nil, errors.New("pqid not found") 407 } 408 409 func IsQueryPersistent(tableName []string, sn *structs.SearchNode) (bool, error) { 410 411 if sn == nil { 412 return false, errors.New("sn was nil") 413 } 414 415 pqid := GetHashForQuery(sn) 416 417 persistentInfoLock.RLock() 418 defer persistentInfoLock.RUnlock() 419 pqInfo, ok := localPersistentQueries[pqid] 420 421 if !ok { 422 for _, pqinfo := range allNodesPQsSorted { 423 if pqinfo.Pqid == pqid { 424 return true, nil 425 } 426 } 427 return false, nil 428 } 429 430 found := false 431 for _, idx := range tableName { 432 if _, ok := pqInfo.AllTables[idx]; ok { 433 found = true 434 break 435 } 436 } 437 438 if found { 439 // we found it but make sure it is in top 100 queries 440 totallen := len(allNodesPQsSorted) 441 for i := 0; i < MAX_QUERIES_TO_TRACK && i < totallen; i++ { 442 if allNodesPQsSorted[i].Pqid == pqid { 443 return true, nil 444 } 445 } 446 } 447 448 return false, nil 449 } 450 451 func flushPQueriesToDisk() { 452 var sb strings.Builder 453 sb.WriteString(config.GetDataPath() + "querynodes/" + config.GetHostID() + "/pqueries/") 454 baseDir := sb.String() 455 456 err := os.MkdirAll(baseDir, 0764) 457 if err != nil { 458 log.Errorf("flushPQueriesToDisk: failed to create basedir=%v, err=%v", baseDir, err) 459 return 460 } 461 462 queryfName := baseDir + "pqinfo.bin" 463 queryFD, err := os.OpenFile(queryfName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) 464 if err != nil { 465 log.Errorf("flushPQueriesToDisk: Failed to open pqinfo file=%v, err=%v", queryfName, err) 466 return 467 } 468 defer queryFD.Close() 469 jdata, err := json.Marshal(&localPersistentQueries) 470 if err != nil { 471 log.Errorf("flushPQueriesToDisk: json marshalling failed fname=%v, err=%v", queryfName, err) 472 return 473 } 474 // todo encode in binary form before writing 475 if _, err = queryFD.Write(jdata); err != nil { 476 log.Errorf("flushPQueriesToDisk: write failed fname=%v, err=%v", queryfName, err) 477 return 478 } 479 480 aggsfName := baseDir + "aggsinfo.bin" 481 aggsFD, err := os.OpenFile(aggsfName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) 482 if err != nil { 483 log.Errorf("flushPQueriesToDisk: Failed to open pqinfo file=%v, err=%v", aggsfName, err) 484 return 485 } 486 defer aggsFD.Close() 487 adata, err := json.Marshal(localPersistentAggs) 488 if err != nil { 489 log.Errorf("flushPQueriesToDisk: json marshalling failed fname=%v, err=%v", aggsfName, err) 490 return 491 } 492 // todo encode in binary form before writing 493 if _, err = aggsFD.Write(adata); err != nil { 494 log.Errorf("flushPQueriesToDisk: write failed fname=%v, err=%v", aggsfName, err) 495 return 496 } 497 498 groupbyAggsFName := baseDir + "groupinfo.bin" 499 fd, err := os.OpenFile(groupbyAggsFName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) 500 if err != nil { 501 log.Errorf("flushPQueriesToDisk: Failed to open file=%v, err=%v", groupbyAggsFName, err) 502 return 503 } 504 defer fd.Close() 505 gdata, err := json.Marshal(localGroupByOverride) 506 if err != nil { 507 log.Errorf("flushPQueriesToDisk: json marshalling failed fname=%v, err=%v", groupbyAggsFName, err) 508 return 509 } 510 // todo encode in binary form before writing 511 if _, err = fd.Write(gdata); err != nil { 512 log.Errorf("flushPQueriesToDisk: write failed fname=%v, err=%v", groupbyAggsFName, err) 513 return 514 } 515 } 516 517 func readSavedQueryInfo() { 518 519 var sb strings.Builder 520 sb.WriteString(config.GetDataPath() + "querynodes/" + config.GetHostID() + "/pqueries/") 521 baseDir := sb.String() 522 523 persistentInfoLock.Lock() 524 defer persistentInfoLock.Unlock() 525 526 queryfName := baseDir + "pqinfo.bin" 527 content, err := os.ReadFile(queryfName) 528 if err != nil { 529 return 530 } 531 err = json.Unmarshal(content, &localPersistentQueries) 532 if err != nil { 533 log.Errorf("readSavedPQueries: json unmarshall failed fname=%v, err=%v", queryfName, err) 534 localPersistentQueries = make(map[string]*PersistentSearchNode) 535 return 536 } 537 538 allNodesPQsSorted = make([]*PersistentSearchNode, 0) 539 for _, pqinfo := range localPersistentQueries { 540 allNodesPQsSorted = append(allNodesPQsSorted, pqinfo) 541 } 542 543 for _, pqinfo := range allNodesPQsSorted { 544 pqinfo.SearchNode.AddQueryInfoForNode() 545 localPersistentQueries[pqinfo.Pqid] = pqinfo 546 } 547 548 sort.Slice(allNodesPQsSorted, func(i, j int) bool { 549 return allNodesPQsSorted[i].TotalUsage > allNodesPQsSorted[j].TotalUsage 550 }) 551 552 log.Infof("readSavedPQueries: read %v queries into pqinfo", len(allNodesPQsSorted)) 553 554 aggsfName := baseDir + "aggsinfo.bin" 555 content, err = os.ReadFile(aggsfName) 556 if err != nil { 557 return 558 } 559 err = json.Unmarshal(content, &localPersistentAggs) 560 if err != nil { 561 log.Errorf("readSavedPQueries: json unmarshall failed fname=%v, err=%v", aggsfName, err) 562 localPersistentAggs = make(map[string]*PersistentAggregation) 563 return 564 } 565 566 allPersistentAggsSorted = make([]*PersistentAggregation, 0) 567 for _, pqinfo := range localPersistentAggs { 568 allPersistentAggsSorted = append(allPersistentAggsSorted, pqinfo) 569 } 570 571 for _, pqinfo := range allPersistentAggsSorted { 572 localPersistentAggs[pqinfo.Pqid] = pqinfo 573 } 574 575 sort.Slice(allPersistentAggsSorted, func(i, j int) bool { 576 return allPersistentAggsSorted[i].TotalUsage > allPersistentAggsSorted[j].TotalUsage 577 }) 578 579 log.Infof("readSavedPQueries: read %v aggs into pqinfo", len(allPersistentAggsSorted)) 580 581 groupByfName := baseDir + "groupinfo.bin" 582 content, err = os.ReadFile(groupByfName) 583 if err != nil { 584 return 585 } 586 err = json.Unmarshal(content, &localGroupByOverride) 587 if err != nil { 588 log.Errorf("readSavedPQueries: json unmarshall failed fname=%v, err=%v", groupByfName, err) 589 localGroupByOverride = make(map[string]*PersistentGroupBy) 590 return 591 } 592 log.Infof("readSavedPQueries: read %v groupby aggs", len(localGroupByOverride)) 593 } 594 595 func GetPQSSummary(ctx *fasthttp.RequestCtx) { 596 response := getPQSSummary() 597 utils.WriteJsonResponse(ctx, response) 598 ctx.Response.Header.Set("Content-Type", "application/json") 599 ctx.SetStatusCode(fasthttp.StatusOK) 600 } 601 602 func getPQSSummary() map[string]interface{} { 603 persistentInfoLock.RLock() 604 defer persistentInfoLock.RUnlock() 605 606 response := make(map[string]interface{}) 607 numQueriesInPQS := len(allNodesPQsSorted) 608 response["total_tracked_queries"] = numQueriesInPQS 609 pqidUsageCount := make(map[string]int) 610 for idx, pqinfo := range allNodesPQsSorted { 611 if idx > MAX_QUERIES_TO_TRACK { 612 continue 613 } 614 pqidUsageCount[pqinfo.Pqid] = int(pqinfo.TotalUsage) 615 } 616 response["promoted_searches"] = pqidUsageCount 617 aggsUsageCount := make(map[string]int) 618 for idx, pqinfo := range allPersistentAggsSorted { 619 if idx > MAX_QUERIES_TO_TRACK { 620 continue 621 } 622 aggsUsageCount[pqinfo.Pqid] = int(pqinfo.TotalUsage) 623 } 624 response["promoted_aggregations"] = aggsUsageCount 625 return response 626 } 627 628 // writes the json converted search node 629 func GetPQSById(ctx *fasthttp.RequestCtx) { 630 pqid := utils.ExtractParamAsString(ctx.UserValue("pqid")) 631 finalResult := getPqsById(pqid) 632 if finalResult == nil { 633 err := getAggPQSById(ctx, pqid) 634 if err != nil { 635 var httpResp utils.HttpServerResponse 636 ctx.SetStatusCode(fasthttp.StatusBadRequest) 637 httpResp.Message = fmt.Sprintf("pqid %+v does not exist", pqid) 638 httpResp.StatusCode = fasthttp.StatusBadRequest 639 utils.WriteResponse(ctx, httpResp) 640 } 641 return 642 } 643 644 utils.WriteJsonResponse(ctx, &finalResult) 645 ctx.Response.Header.Set("Content-Type", "application/json") 646 ctx.SetStatusCode(fasthttp.StatusOK) 647 } 648 649 func getPqsById(pqid string) map[string]interface{} { 650 persistentInfoLock.RLock() 651 defer persistentInfoLock.RUnlock() 652 // TODO: aggs support 653 pqinfo, exists := localPersistentQueries[pqid] 654 if !exists { 655 for _, info := range allNodesPQsSorted { 656 if info.Pqid == pqid { 657 pqinfo = info 658 } 659 } 660 } 661 662 var finalResult map[string]interface{} 663 if pqinfo != nil { 664 sNode := pqinfo.SearchNode 665 var convertedSNode map[string]interface{} 666 converted, _ := json.Marshal(sNode) 667 _ = json.Unmarshal(converted, &convertedSNode) 668 669 finalResult = make(map[string]interface{}) 670 finalResult["pqid"] = pqinfo.Pqid 671 finalResult["last_used_epoch"] = pqinfo.LastUsedEpoch 672 finalResult["total_usage"] = pqinfo.TotalUsage 673 finalResult["virtual_tables"] = pqinfo.AllTables 674 finalResult["search_node"] = convertedSNode 675 } 676 return finalResult 677 } 678 679 func getAggPQSById(ctx *fasthttp.RequestCtx, pqid string) error { 680 pqinfo, exists := localPersistentAggs[pqid] 681 if !exists { 682 for _, info := range allPersistentAggsSorted { 683 if info.Pqid == pqid { 684 pqinfo = info 685 } 686 } 687 } 688 689 if pqinfo == nil { 690 return fmt.Errorf("pqid %+s does not exist in aggs", pqid) 691 } 692 sNode := pqinfo.QueryAggs 693 var convertedAggs map[string]interface{} 694 converted, _ := json.Marshal(sNode) 695 _ = json.Unmarshal(converted, &convertedAggs) 696 697 finalResult := make(map[string]interface{}) 698 finalResult["pqid"] = pqinfo.Pqid 699 finalResult["last_used_epoch"] = pqinfo.LastUsedEpoch 700 finalResult["total_usage"] = pqinfo.TotalUsage 701 finalResult["virtual_tables"] = pqinfo.AllTables 702 finalResult["search_aggs"] = convertedAggs 703 704 utils.WriteJsonResponse(ctx, &finalResult) 705 ctx.Response.Header.Set("Content-Type", "application/json") 706 ctx.SetStatusCode(fasthttp.StatusOK) 707 return nil 708 } 709 710 func RefreshExternalPQInfo(fNames []string) error { 711 712 allNodesPQs := make(map[string]*PersistentSearchNode) 713 persistentInfoLock.Lock() 714 defer persistentInfoLock.Unlock() 715 716 for _, file := range fNames { 717 var tempPersistentQueries = map[string]*PersistentSearchNode{} 718 content, err := os.ReadFile(file) 719 if err != nil { 720 if os.IsNotExist(err) { 721 return nil 722 } 723 log.Errorf("RefreshExternalPQInfo: error in reading fname=%v, err=%v", file, err) 724 return err 725 } 726 727 err = json.Unmarshal(content, &tempPersistentQueries) 728 if err != nil { 729 log.Errorf("RefreshExternalPQInfo: json unmarshall failed fname=%v, err=%v", file, err) 730 return err 731 } 732 733 for pqid, pqinfo := range tempPersistentQueries { 734 val, present := allNodesPQs[pqid] 735 736 if !present { 737 pqinfo.TotalUsage = pqinfo.LocalUsage 738 allNodesPQs[pqid] = pqinfo 739 } else { 740 val.TotalUsage = val.TotalUsage + pqinfo.LocalUsage 741 742 // merge Alltables 743 err = mergo.Merge(&val.AllTables, pqinfo.AllTables) 744 if err != nil { 745 log.Errorf("RefreshExternalPQInfo: error in merging Alltables, err=%v", err) 746 return err 747 } 748 } 749 } 750 } 751 allNodesPQsSorted = make([]*PersistentSearchNode, 0) 752 for _, pqinfo := range localPersistentQueries { 753 allNodesPQsSorted = append(allNodesPQsSorted, pqinfo) 754 } 755 756 for pqid, pqinfo := range allNodesPQs { 757 val, present := localPersistentQueries[pqid] 758 if present { 759 val.TotalUsage = val.LocalUsage + pqinfo.TotalUsage 760 } else { 761 allNodesPQsSorted = append(allNodesPQsSorted, pqinfo) 762 } 763 } 764 765 //Sort the slice in descending order of TotalUsage 766 sort.Slice(allNodesPQsSorted, func(i, j int) bool { 767 return allNodesPQsSorted[i].TotalUsage > allNodesPQsSorted[j].TotalUsage 768 }) 769 return nil 770 } 771 772 func RefreshExternalAggsInfo(fNames []string) error { 773 allNodesAggs := make(map[string]*PersistentAggregation) 774 persistentInfoLock.Lock() 775 defer persistentInfoLock.Unlock() 776 777 for _, file := range fNames { 778 var tempAggs = map[string]*PersistentAggregation{} 779 content, err := os.ReadFile(file) 780 if err != nil { 781 if os.IsNotExist(err) { 782 return nil 783 } 784 log.Errorf("RefreshExternalAggsInfo: error in reading fname=%v, err=%v", file, err) 785 return err 786 } 787 788 err = json.Unmarshal(content, &tempAggs) 789 if err != nil { 790 log.Errorf("RefreshExternalAggsInfo: json unmarshall failed fname=%v, err=%v", file, err) 791 return err 792 } 793 794 for pqid, pqinfo := range tempAggs { 795 val, present := allNodesAggs[pqid] 796 797 if !present { 798 pqinfo.TotalUsage = pqinfo.LocalUsage 799 allNodesAggs[pqid] = pqinfo 800 } else { 801 val.TotalUsage = val.TotalUsage + pqinfo.LocalUsage 802 803 // merge Alltables 804 err = mergo.Merge(&val.AllTables, pqinfo.AllTables) 805 if err != nil { 806 log.Errorf("RefreshExternalAggsInfo: error in merging Alltables, err=%v", err) 807 return err 808 } 809 } 810 } 811 } 812 allPersistentAggsSorted = make([]*PersistentAggregation, 0) 813 for _, pqinfo := range localPersistentAggs { 814 allPersistentAggsSorted = append(allPersistentAggsSorted, pqinfo) 815 } 816 817 for pqid, aggsInfo := range allNodesAggs { 818 val, present := localPersistentAggs[pqid] 819 if present { 820 val.TotalUsage = val.LocalUsage + aggsInfo.TotalUsage 821 } else { 822 allPersistentAggsSorted = append(allPersistentAggsSorted, aggsInfo) 823 } 824 } 825 826 //Sort the slice in descending order of TotalUsage 827 sort.Slice(allNodesPQsSorted, func(i, j int) bool { 828 return allNodesPQsSorted[i].TotalUsage > allNodesPQsSorted[j].TotalUsage 829 }) 830 return nil 831 } 832 833 func PostPqsClear(ctx *fasthttp.RequestCtx) { 834 ClearPqs() 835 ctx.SetStatusCode(fasthttp.StatusOK) 836 } 837 838 func ClearPqs() { 839 persistentInfoLock.Lock() 840 localPersistentQueries = make(map[string]*PersistentSearchNode) 841 allNodesPQsSorted = make([]*PersistentSearchNode, 0) 842 843 localPersistentAggs = make(map[string]*PersistentAggregation) 844 allPersistentAggsSorted = make([]*PersistentAggregation, 0) 845 persistentInfoLock.Unlock() 846 847 groupByOverrideLock.Lock() 848 localGroupByOverride = make(map[string]*PersistentGroupBy) 849 groupByOverrideLock.Unlock() 850 851 flushPQueriesToDisk() 852 } 853 854 func PostPqsAggCols(ctx *fasthttp.RequestCtx) { 855 var httpResp utils.HttpServerResponse 856 rawJSON := ctx.PostBody() 857 if rawJSON == nil { 858 log.Errorf("PostPqsAggCols: received empty request") 859 utils.SetBadMsg(ctx, "Empty post body") 860 return 861 } 862 863 readJSON := make(map[string]interface{}) 864 var jsonc = jsoniter.ConfigCompatibleWithStandardLibrary 865 decoder := jsonc.NewDecoder(bytes.NewReader(rawJSON)) 866 decoder.UseNumber() 867 err := decoder.Decode(&readJSON) 868 if err != nil { 869 ctx.SetStatusCode(fasthttp.StatusBadRequest) 870 _, err = ctx.WriteString(err.Error()) 871 if err != nil { 872 log.Errorf("PostPqsAggCols: could not write error message err=%v", err) 873 } 874 log.Errorf("PostPqsAggCols: failed to decode request body! Err=%+v", err) 875 } 876 877 err = parsePostPqsAggBody(readJSON) 878 879 if err != nil { 880 utils.SetBadMsg(ctx, err.Error()) 881 } else { 882 ctx.SetStatusCode(fasthttp.StatusOK) 883 httpResp.Message = "All OK" 884 httpResp.StatusCode = fasthttp.StatusOK 885 } 886 utils.WriteResponse(ctx, httpResp) 887 } 888 889 func parsePostPqsAggBody(jsonSource map[string]interface{}) error { 890 var tableName string 891 var err error 892 groupByColsMap := make(map[string]bool) 893 measureColsMaps := make(map[string]bool) 894 groupByOverrideLock.Lock() 895 defer groupByOverrideLock.Unlock() 896 for key, value := range jsonSource { 897 switch valtype := value.(type) { 898 case string: 899 if key == "tableName" { 900 tableName = valtype 901 if tableName == "*" { 902 err := errors.New("PostPqsAggCols: tableName can not be *") 903 log.Errorf("%+v", err) 904 return err 905 } 906 } 907 case []interface{}: 908 switch key { 909 case "groupByColumns": 910 { 911 groupByColsMap, err = processPostAggs(valtype) 912 if err != nil { 913 log.Errorf("PostPqsAggCols:processPostAggs error %v", err) 914 return err 915 } 916 } 917 case "measureColumns": 918 { 919 measureColsMaps, err = processPostAggs(valtype) 920 if err != nil { 921 log.Errorf("PostPqsAggCols:processPostAggs error %v", err) 922 return err 923 } 924 } 925 } 926 default: 927 log.Errorf("PostPqsAggCols: Invalid key=[%v]", key) 928 err := fmt.Sprintf("PostPqsAggCols: Invalid key=[%v]", key) 929 return errors.New(err) 930 } 931 } 932 if _, ok := localGroupByOverride[tableName]; ok { 933 entry := localGroupByOverride[tableName] 934 for cname := range entry.GroupByCols { 935 groupByColsMap[cname] = true 936 } 937 for mcname := range entry.MeasureCols { 938 measureColsMaps[mcname] = true 939 } 940 941 } 942 pqsAggs := &PersistentGroupBy{GroupByCols: groupByColsMap, MeasureCols: measureColsMaps} 943 localGroupByOverride[tableName] = pqsAggs 944 return nil 945 } 946 func processPostAggs(inputValueParam interface{}) (map[string]bool, error) { 947 switch inputValueParam.(type) { 948 case []interface{}: 949 break 950 default: 951 err := fmt.Errorf("processPostAggs type = %T not accepted", inputValueParam) 952 return nil, err 953 } 954 evMap := make(map[string]bool) 955 for _, element := range inputValueParam.([]interface{}) { 956 switch element := element.(type) { 957 case string: 958 evMap[element] = true 959 default: 960 err := fmt.Errorf("processPostAggs type = %T not accepted", element) 961 return nil, err 962 } 963 } 964 return evMap, nil 965 }