github.com/siglens/siglens@v0.0.0-20240328180423-f7ce9ae441ed/pkg/health/clusterStatsHandler.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 health
    18  
    19  import (
    20  	"bytes"
    21  	"encoding/json"
    22  	"fmt"
    23  	"sort"
    24  	"strconv"
    25  	"strings"
    26  
    27  	"github.com/dustin/go-humanize"
    28  	jsoniter "github.com/json-iterator/go"
    29  	"github.com/siglens/siglens/pkg/hooks"
    30  	segwriter "github.com/siglens/siglens/pkg/segment/writer"
    31  	"github.com/siglens/siglens/pkg/segment/writer/metrics"
    32  	mmeta "github.com/siglens/siglens/pkg/segment/writer/metrics/meta"
    33  	"github.com/siglens/siglens/pkg/usageStats"
    34  	"github.com/siglens/siglens/pkg/utils"
    35  	vtable "github.com/siglens/siglens/pkg/virtualtable"
    36  	log "github.com/sirupsen/logrus"
    37  	"github.com/valyala/fasthttp"
    38  )
    39  
    40  func ProcessClusterStatsHandler(ctx *fasthttp.RequestCtx, myid uint64) {
    41  
    42  	var httpResp utils.ClusterStatsResponseInfo
    43  	var err error
    44  	if hook := hooks.GlobalHooks.MiddlewareExtractOrgIdHook; hook != nil {
    45  		myid, err = hook(ctx)
    46  		if err != nil {
    47  			log.Errorf("ProcessClusterStatsHandler: failed to extract orgId from context. Err=%+v", err)
    48  			utils.SetBadMsg(ctx, "")
    49  			return
    50  		}
    51  	}
    52  	indexData, logsEventCount, logsIncomingBytes, logsOnDiskBytes := getIngestionStats(myid)
    53  	queryCount, totalResponseTime, querieSinceInstall := usageStats.GetQueryStats(myid)
    54  
    55  	metricsIncomingBytes, metricsDatapointsCount, metricsOnDiskBytes := getMetricsStats(myid)
    56  	metricsImMemBytes := metrics.GetTotalEncodedSize()
    57  
    58  	if hook := hooks.GlobalHooks.AddMultinodeStatsHook; hook != nil {
    59  		hook(indexData, myid, &logsIncomingBytes, &logsOnDiskBytes, &logsEventCount,
    60  			&metricsIncomingBytes, &metricsOnDiskBytes, &metricsDatapointsCount,
    61  			&queryCount, &totalResponseTime)
    62  	}
    63  
    64  	httpResp.IngestionStats = make(map[string]interface{})
    65  	httpResp.QueryStats = make(map[string]interface{})
    66  	httpResp.MetricsStats = make(map[string]interface{})
    67  
    68  	httpResp.IngestionStats["Log Incoming Volume"] = convertBytesToGB(logsIncomingBytes)
    69  	httpResp.IngestionStats["Incoming Volume"] = convertBytesToGB(logsIncomingBytes + float64(metricsIncomingBytes))
    70  
    71  	httpResp.IngestionStats["Metrics Incoming Volume"] = convertBytesToGB(float64(metricsIncomingBytes))
    72  
    73  	httpResp.IngestionStats["Event Count"] = humanize.Comma(int64(logsEventCount))
    74  
    75  	httpResp.IngestionStats["Log Storage Used"] = convertBytesToGB(logsOnDiskBytes)
    76  	httpResp.IngestionStats["Metrics Storage Used"] = convertBytesToGB(float64(metricsOnDiskBytes + metricsImMemBytes))
    77  	totalOnDiskBytes := logsOnDiskBytes + float64(metricsOnDiskBytes) + float64(metricsImMemBytes)
    78  	httpResp.IngestionStats["Storage Saved"] = (1 - (totalOnDiskBytes / (logsIncomingBytes + float64(metricsIncomingBytes)))) * 100
    79  
    80  	if hook := hooks.GlobalHooks.SetExtraIngestionStatsHook; hook != nil {
    81  		hook(httpResp.IngestionStats)
    82  	}
    83  
    84  	httpResp.MetricsStats["Incoming Volume"] = convertBytesToGB(float64(metricsIncomingBytes))
    85  	httpResp.MetricsStats["Datapoints Count"] = humanize.Comma(int64(metricsDatapointsCount))
    86  
    87  	httpResp.QueryStats["Query Count"] = queryCount
    88  	httpResp.QueryStats["Queries Since Install"] = querieSinceInstall
    89  
    90  	if queryCount > 1 {
    91  		httpResp.QueryStats["Average Latency"] = fmt.Sprintf("%v", utils.ToFixed(totalResponseTime/float64(queryCount), 3)) + " ms"
    92  	} else {
    93  		httpResp.QueryStats["Average Latency"] = fmt.Sprintf("%v", utils.ToFixed(totalResponseTime, 3)) + " ms"
    94  	}
    95  
    96  	httpResp.IndexStats = convertIndexDataToSlice(indexData)
    97  	utils.WriteJsonResponse(ctx, httpResp)
    98  
    99  }
   100  
   101  func convertIndexDataToSlice(indexData map[string]utils.ResultPerIndex) []utils.ResultPerIndex {
   102  	retVal := make([]utils.ResultPerIndex, 0, len(indexData))
   103  	i := 0
   104  	for idx, v := range indexData {
   105  		nextVal := make(utils.ResultPerIndex)
   106  		nextVal[idx] = make(map[string]interface{})
   107  		nextVal[idx]["ingestVolume"] = convertBytesToGB(v[idx]["ingestVolume"].(float64))
   108  		nextVal[idx]["eventCount"] = humanize.Comma(int64(v[idx]["eventCount"].(uint64)))
   109  		retVal = append(retVal, nextVal)
   110  		i++
   111  	}
   112  	return retVal[:i]
   113  }
   114  
   115  func ProcessClusterIngestStatsHandler(ctx *fasthttp.RequestCtx, orgId uint64) {
   116  	var err error
   117  	if hook := hooks.GlobalHooks.MiddlewareExtractOrgIdHook; hook != nil {
   118  		orgId, err = hook(ctx)
   119  		if err != nil {
   120  			log.Errorf("ProcessClusterIngestStatsHandler: failed to extract orgId from context. Err=%+v", err)
   121  			utils.SetBadMsg(ctx, "")
   122  			return
   123  		}
   124  	}
   125  
   126  	var httpResp utils.ClusterStatsResponseInfo
   127  	rawJSON := ctx.PostBody()
   128  	if rawJSON == nil {
   129  		log.Errorf(" ClusterIngestStatsHandler: received empty search request body ")
   130  		utils.SetBadMsg(ctx, "")
   131  		return
   132  	}
   133  
   134  	readJSON := make(map[string]interface{})
   135  	var jsonc = jsoniter.ConfigCompatibleWithStandardLibrary
   136  	decoder := jsonc.NewDecoder(bytes.NewReader(rawJSON))
   137  	decoder.UseNumber()
   138  	err = decoder.Decode(&readJSON)
   139  	if err != nil {
   140  		ctx.SetStatusCode(fasthttp.StatusBadRequest)
   141  		_, err = ctx.WriteString(err.Error())
   142  		if err != nil {
   143  			log.Errorf(" ClusterIngestStatsHandler: could not write error message err=%v", err)
   144  		}
   145  		log.Errorf(" ClusterIngestStatsHandler: failed to decode search request body! Err=%+v", err)
   146  		return
   147  	}
   148  
   149  	pastXhours, granularity := parseIngestionStatsRequest(readJSON)
   150  	rStats, _ := usageStats.GetUsageStats(pastXhours, granularity, orgId)
   151  	httpResp.ChartStats = make(map[string]map[string]interface{})
   152  
   153  	for k, entry := range rStats {
   154  		httpResp.ChartStats[k] = make(map[string]interface{}, 2)
   155  		httpResp.ChartStats[k]["EventCount"] = entry.EventCount
   156  		httpResp.ChartStats[k]["MetricsCount"] = entry.MetricsDatapointsCount
   157  		httpResp.ChartStats[k]["GBCount"] = float64(entry.BytesCount) / 1_000_000_000
   158  	}
   159  	utils.WriteJsonResponse(ctx, httpResp)
   160  }
   161  
   162  func parseAlphaNumTime(inp string, defValue uint64) (uint64, usageStats.UsageStatsGranularity) {
   163  	granularity := usageStats.Daily
   164  	sanTime := strings.ReplaceAll(inp, " ", "")
   165  	retVal := defValue
   166  
   167  	strln := len(sanTime)
   168  	if strln < 6 {
   169  		return retVal, usageStats.Daily
   170  	}
   171  
   172  	unit := sanTime[strln-1]
   173  	num, err := strconv.ParseInt(sanTime[4:strln-1], 0, 64)
   174  	if err != nil {
   175  		return defValue, usageStats.Daily
   176  	}
   177  
   178  	switch unit {
   179  	case 'h':
   180  		retVal = uint64(num)
   181  		granularity = usageStats.Hourly
   182  	case 'd':
   183  		retVal = 24 * uint64(num)
   184  		granularity = usageStats.Daily
   185  	}
   186  	//for past 2 days , set granularity to Hourly
   187  	if num <= 2 {
   188  		granularity = usageStats.Hourly
   189  	}
   190  	return retVal, granularity
   191  }
   192  
   193  func parseIngestionStatsRequest(jsonSource map[string]interface{}) (uint64, usageStats.UsageStatsGranularity) {
   194  	var pastXhours uint64
   195  	granularity := usageStats.Daily
   196  	startE, ok := jsonSource["startEpoch"]
   197  	if !ok || startE == nil {
   198  		pastXhours = uint64(7 * 24)
   199  	} else {
   200  		switch val := startE.(type) {
   201  		case json.Number:
   202  			temp, _ := val.Int64()
   203  			pastXhours = uint64(temp)
   204  		case float64:
   205  			pastXhours = uint64(val)
   206  		case int64:
   207  			pastXhours = uint64(val)
   208  		case uint64:
   209  			pastXhours = uint64(val)
   210  		case string:
   211  			defValue := uint64(7 * 24)
   212  			pastXhours, granularity = parseAlphaNumTime(string(val), defValue)
   213  		default:
   214  			pastXhours = uint64(7 * 24)
   215  		}
   216  	}
   217  	return pastXhours, granularity
   218  }
   219  
   220  func getIngestionStats(myid uint64) (map[string]utils.ResultPerIndex, int64, float64, float64) {
   221  
   222  	totalIncomingBytes := float64(0)
   223  	totalEventCount := int64(0)
   224  	totalOnDiskBytes := float64(0)
   225  
   226  	ingestionStats := make(map[string]utils.ResultPerIndex)
   227  	allVirtualTableNames, err := vtable.GetVirtualTableNames(myid)
   228  	sortedIndices := make([]string, 0, len(allVirtualTableNames))
   229  
   230  	for k := range allVirtualTableNames {
   231  		sortedIndices = append(sortedIndices, k)
   232  	}
   233  	sort.Strings(sortedIndices)
   234  
   235  	if err != nil {
   236  		log.Errorf("getIngestionStats: Error in getting virtual table names, err:%v", err)
   237  	}
   238  
   239  	allvtableCnts := segwriter.GetVTableCountsForAll(myid)
   240  
   241  	for _, indexName := range sortedIndices {
   242  		if indexName == "" {
   243  			log.Errorf("getIngestionStats: skipping an empty index name indexName=%v", indexName)
   244  			continue
   245  		}
   246  
   247  		cnts, ok := allvtableCnts[indexName]
   248  		if !ok {
   249  			continue
   250  		}
   251  
   252  		unrotatedByteCount, unrotatedEventCount, unrotatedOnDiskBytesCount := segwriter.GetUnrotatedVTableCounts(indexName, myid)
   253  
   254  		totalEventsForIndex := uint64(cnts.RecordCount) + uint64(unrotatedEventCount)
   255  		totalEventCount += int64(totalEventsForIndex)
   256  
   257  		totalBytesReceivedForIndex := float64(cnts.BytesCount + unrotatedByteCount)
   258  		totalIncomingBytes += totalBytesReceivedForIndex
   259  
   260  		totalOnDiskBytesCountForIndex := uint64(cnts.OnDiskBytesCount + unrotatedOnDiskBytesCount)
   261  		totalOnDiskBytes += float64(totalOnDiskBytesCountForIndex)
   262  
   263  		perIndexStat := make(map[string]map[string]interface{})
   264  
   265  		perIndexStat[indexName] = make(map[string]interface{})
   266  
   267  		perIndexStat[indexName]["ingestVolume"] = totalBytesReceivedForIndex
   268  		perIndexStat[indexName]["eventCount"] = totalEventsForIndex
   269  
   270  		ingestionStats[indexName] = perIndexStat
   271  	}
   272  	return ingestionStats, totalEventCount, totalIncomingBytes, totalOnDiskBytes
   273  }
   274  
   275  func convertBytesToGB(bytes float64) string {
   276  	convertedGB := bytes / 1_000_000_000
   277  	finalStr := fmt.Sprintf("%.3f", convertedGB) + " GB"
   278  	return finalStr
   279  }
   280  
   281  func getMetricsStats(myid uint64) (uint64, uint64, uint64) {
   282  	bytesCount := uint64(0)
   283  	onDiskBytesCount := uint64(0)
   284  	recCount := uint64(0)
   285  	allMetricsMetas, err := mmeta.GetAllMetricsMetaEntries(myid)
   286  	if err != nil {
   287  		log.Errorf("populateMetricsMetadata: unable to get all the metrics meta entries. Error: %v", err)
   288  		return bytesCount, recCount, onDiskBytesCount
   289  	}
   290  	for _, mMetaInfo := range allMetricsMetas {
   291  		if mMetaInfo.OrgId == myid {
   292  			onDiskBytesCount += mMetaInfo.OnDiskBytes
   293  			bytesCount += mMetaInfo.BytesReceivedCount
   294  			recCount += mMetaInfo.DatapointCount
   295  		}
   296  	}
   297  	unrotatedIncoming, unrotatedOnDisk, unrotatedRecs := metrics.GetUnrotatedMetricStats(myid)
   298  	bytesCount += unrotatedIncoming
   299  	onDiskBytesCount += unrotatedOnDisk
   300  	recCount += unrotatedRecs
   301  	return bytesCount, recCount, onDiskBytesCount
   302  }