github.com/siglens/siglens@v0.0.0-20240328180423-f7ce9ae441ed/pkg/segment/query/metadata/metricsmetadata.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 metadata
    18  
    19  import (
    20  	"fmt"
    21  	"sort"
    22  	"sync"
    23  	"time"
    24  
    25  	"github.com/siglens/siglens/pkg/config"
    26  	"github.com/siglens/siglens/pkg/segment/reader/microreader"
    27  	"github.com/siglens/siglens/pkg/segment/structs"
    28  	"github.com/siglens/siglens/pkg/segment/utils"
    29  	log "github.com/sirupsen/logrus"
    30  )
    31  
    32  /*
    33  	This file defines holder structs and rebalancing methods for metrics semgments
    34  
    35  	Note, the memory allocation of metricssegment is idependent of metadata.go
    36  */
    37  
    38  /*
    39  Holder
    40  */
    41  type allMetricsSegmentMetadata struct {
    42  
    43  	// sortedMetricsSegmentMeta in sorted order of descending latest time, used for global memory limiting
    44  	sortedMetricsSegmentMeta []*MetricsSegmentMetadata
    45  
    46  	// metricsSegmentMetaMap maps a metrics base dir to its metadata
    47  	metricsSegmentMetaMap map[string]*MetricsSegmentMetadata
    48  
    49  	// metadata update lock
    50  	updateLock *sync.RWMutex
    51  }
    52  
    53  var globalMetricsMetadata *allMetricsSegmentMetadata = &allMetricsSegmentMetadata{
    54  	sortedMetricsSegmentMeta: make([]*MetricsSegmentMetadata, 0),
    55  	metricsSegmentMetaMap:    make(map[string]*MetricsSegmentMetadata),
    56  	updateLock:               &sync.RWMutex{},
    57  }
    58  
    59  type MetricsSegmentMetadata struct {
    60  	structs.MetricsMeta // original read metrics meta
    61  	MetricsSegmentSearchMetadata
    62  }
    63  
    64  type MetricsSegmentSearchMetadata struct {
    65  	mBlockSummary        []*structs.MBlockSummary
    66  	mBlockSize           uint64
    67  	loadedSearchMetadata bool
    68  }
    69  
    70  func BulkAddMetricsSegment(allMMetadata []*MetricsSegmentMetadata) {
    71  	globalMetricsMetadata.bulkAddMetricsMicroIndex(allMMetadata)
    72  }
    73  
    74  func InitMetricsMicroIndex(mMeta *structs.MetricsMeta) *MetricsSegmentMetadata {
    75  
    76  	mm := &MetricsSegmentMetadata{
    77  		MetricsMeta: *mMeta,
    78  	}
    79  	mm.loadedSearchMetadata = false
    80  	mm.initMetadataSize()
    81  	return mm
    82  }
    83  
    84  func (mm *MetricsSegmentMetadata) initMetadataSize() {
    85  	searchMetadataSize := uint64(0)
    86  	searchMetadataSize += uint64(mm.NumBlocks * structs.SIZE_OF_MBSUM) // block summaries
    87  	mm.mBlockSize = searchMetadataSize
    88  }
    89  
    90  func (mm *allMetricsSegmentMetadata) bulkAddMetricsMicroIndex(allMMetadata []*MetricsSegmentMetadata) {
    91  	mm.updateLock.Lock()
    92  	defer mm.updateLock.Unlock()
    93  
    94  	for _, newMMeta := range allMMetadata {
    95  		if _, ok := mm.metricsSegmentMetaMap[newMMeta.MSegmentDir]; ok {
    96  			continue
    97  		}
    98  		mm.sortedMetricsSegmentMeta = append(mm.sortedMetricsSegmentMeta, newMMeta)
    99  		mm.metricsSegmentMetaMap[newMMeta.MSegmentDir] = newMMeta
   100  		GlobalSegStoreSummary.IncrementTotalMetricsSegmentCount()
   101  	}
   102  	sort.Slice(mm.sortedMetricsSegmentMeta, func(i, j int) bool {
   103  		return mm.sortedMetricsSegmentMeta[i].LatestEpochSec > mm.sortedMetricsSegmentMeta[j].LatestEpochSec
   104  	})
   105  }
   106  
   107  func (mm *allMetricsSegmentMetadata) rebalanceMetricsSsm(ssmSizeBytes uint64) {
   108  
   109  	sTime := time.Now()
   110  
   111  	mm.updateLock.RLock()
   112  	defer mm.updateLock.RUnlock()
   113  	searchIndex := mm.getSsmMaxIndicesToLoad(ssmSizeBytes)
   114  	evicted := mm.evictSsmPastIndices(searchIndex)
   115  
   116  	inMemSize, inMemSearchMetaCount, newloaded := mm.loadSsmUntilIndex(searchIndex)
   117  
   118  	log.Infof("rebalanceMetricsSsm SSM, inMem: %+v SSM, allocated: %+v MB, evicted: %v, newloaded: %v, took: %vms",
   119  		inMemSearchMetaCount, utils.ConvertUintBytesToMB(inMemSize),
   120  		evicted, newloaded, int(time.Since(sTime).Milliseconds()))
   121  
   122  	GlobalSegStoreSummary.SetInMemoryMetricsSearchmetadataCount(uint64(inMemSearchMetaCount))
   123  	GlobalSegStoreSummary.SetInMemoryMetricsSsmSizeMB(utils.ConvertUintBytesToMB(inMemSize))
   124  }
   125  
   126  /*
   127  Returns the max indices that should have SSM(segmentsearchmeta) loaded
   128  */
   129  func (mm *allMetricsSegmentMetadata) getSsmMaxIndicesToLoad(totalMem uint64) int {
   130  
   131  	numBlocks := len(mm.sortedMetricsSegmentMeta)
   132  	totalMBAllocated := uint64(0)
   133  	maxSearchIndex := 0
   134  	for ; maxSearchIndex < numBlocks; maxSearchIndex++ {
   135  		searchMetadataSize := mm.sortedMetricsSegmentMeta[maxSearchIndex].mBlockSize
   136  		if searchMetadataSize+totalMBAllocated > totalMem {
   137  			break
   138  		}
   139  		totalMBAllocated += searchMetadataSize
   140  	}
   141  
   142  	return maxSearchIndex
   143  }
   144  
   145  func (mm *allMetricsSegmentMetadata) evictSsmPastIndices(searchIndex int) int {
   146  
   147  	idxToClear := make([]int, 0)
   148  	for i := searchIndex; i < len(mm.sortedMetricsSegmentMeta); i++ {
   149  		if mm.sortedMetricsSegmentMeta[i].loadedSearchMetadata {
   150  			idxToClear = append(idxToClear, i)
   151  		}
   152  	}
   153  
   154  	if len(idxToClear) > 0 {
   155  		for _, idx := range idxToClear {
   156  			mm.sortedMetricsSegmentMeta[idx].clearSearchMetadata()
   157  		}
   158  	}
   159  	return len(idxToClear)
   160  }
   161  
   162  // Returns total in memory size in bytes, total search metadata in memory
   163  func (mm *allMetricsSegmentMetadata) loadSsmUntilIndex(searchMetaIdx int) (uint64, int, int) {
   164  	totalSize := uint64(0)
   165  	totalSearchMetaCount := int(0)
   166  
   167  	idxToLoad := make([]int, 0)
   168  	for i := 0; i < searchMetaIdx; i++ {
   169  		if !mm.sortedMetricsSegmentMeta[i].loadedSearchMetadata {
   170  			idxToLoad = append(idxToLoad, i)
   171  		} else {
   172  			totalSize += mm.sortedMetricsSegmentMeta[i].mBlockSize
   173  			totalSearchMetaCount += 1
   174  		}
   175  	}
   176  
   177  	if len(idxToLoad) > 0 {
   178  		a, b := mm.loadParallel(idxToLoad)
   179  		totalSize += a
   180  		totalSearchMetaCount += b
   181  	}
   182  
   183  	return totalSize, totalSearchMetaCount, len(idxToLoad)
   184  }
   185  
   186  func (mm *MetricsSegmentMetadata) LoadSearchMetadata() error {
   187  	if mm.loadedSearchMetadata {
   188  		return nil
   189  	}
   190  	bSumFname := fmt.Sprintf("%s.mbsu", mm.MSegmentDir)
   191  	blockSum, err := microreader.ReadMetricsBlockSummaries(bSumFname)
   192  	if err != nil {
   193  		mm.clearSearchMetadata()
   194  		log.Errorf("LoadSearchMetadata: unable to read the metrics block summaries. Error: %v", err)
   195  		return err
   196  	}
   197  	mm.loadedSearchMetadata = true
   198  	mm.mBlockSummary = blockSum
   199  	return nil
   200  }
   201  
   202  func (mm *MetricsSegmentMetadata) clearSearchMetadata() {
   203  	mm.loadedSearchMetadata = false
   204  	mm.mBlockSummary = nil
   205  }
   206  
   207  /*
   208  Caller is responsible for acquiring the right read locks
   209  
   210  Parameters:
   211  
   212  	idxToLoad: indices in the sortedMetricsSegmentMeta to load metrics block summary for
   213  
   214  Returns:
   215  
   216  	totalSize:
   217  	totalEntities:
   218  */
   219  func (mm *allMetricsSegmentMetadata) loadParallel(idxToLoad []int) (uint64, int) {
   220  	totalSize := uint64(0)
   221  	totalEntities := int(0)
   222  
   223  	wg := &sync.WaitGroup{}
   224  	var err error
   225  	parallelism := int(config.GetParallelism())
   226  
   227  	for i, idx := range idxToLoad {
   228  		wg.Add(1)
   229  		go func(myIdx int, rbufIdx int) {
   230  			err = mm.sortedMetricsSegmentMeta[myIdx].LoadSearchMetadata()
   231  			if err != nil {
   232  				log.Errorf("loadParallel: failed to load SSM at index %d. Error: %v", myIdx, err)
   233  			}
   234  			wg.Done()
   235  		}(idx, i%parallelism)
   236  		if i%parallelism == 0 {
   237  			wg.Wait()
   238  		}
   239  	}
   240  	wg.Wait()
   241  
   242  	for _, idx := range idxToLoad {
   243  		if mm.sortedMetricsSegmentMeta[idx].loadedSearchMetadata {
   244  			totalSize += mm.sortedMetricsSegmentMeta[idx].mBlockSize
   245  			totalEntities += 1
   246  		}
   247  	}
   248  	return totalSize, totalEntities
   249  }
   250  
   251  func DeleteMetricsSegmentKey(dirPath string) error {
   252  	err := globalMetricsMetadata.deleteMetricsSegmentKey(dirPath)
   253  	if err != nil {
   254  		log.Errorf("DeleteMetricsSegmentKey: err deleting the metrics segment %v. Error:%v", dirPath, err)
   255  		return err
   256  	}
   257  	return nil
   258  }
   259  
   260  func (hm *allMetricsSegmentMetadata) deleteMetricsSegmentKey(dirPath string) error {
   261  	hm.updateLock.Lock()
   262  	defer hm.updateLock.Unlock()
   263  
   264  	segment, ok := globalMetricsMetadata.metricsSegmentMetaMap[dirPath]
   265  	if !ok {
   266  		return fmt.Errorf("deleteSegmentKey: metrics segment directory %s not found", dirPath)
   267  	}
   268  
   269  	for i, m := range globalMetricsMetadata.sortedMetricsSegmentMeta {
   270  		if m == segment {
   271  			globalMetricsMetadata.sortedMetricsSegmentMeta = append(globalMetricsMetadata.sortedMetricsSegmentMeta[:i], globalMetricsMetadata.sortedMetricsSegmentMeta[i+1:]...)
   272  			break
   273  		}
   274  	}
   275  	delete(globalMetricsMetadata.metricsSegmentMetaMap, dirPath)
   276  
   277  	GlobalSegStoreSummary.DecrementTotalMetricsSegmentCount()
   278  
   279  	return nil
   280  }