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  }