github.com/siglens/siglens@v0.0.0-20240328180423-f7ce9ae441ed/pkg/segment/tracing/handler/tracehandler.go (about)

     1  package handler
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"regexp"
     9  	"strconv"
    10  	"strings"
    11  	"time"
    12  
    13  	jsoniter "github.com/json-iterator/go"
    14  	pipesearch "github.com/siglens/siglens/pkg/ast/pipesearch"
    15  	"github.com/siglens/siglens/pkg/es/writer"
    16  	"github.com/siglens/siglens/pkg/segment/tracing/structs"
    17  	"github.com/siglens/siglens/pkg/segment/tracing/utils"
    18  	putils "github.com/siglens/siglens/pkg/utils"
    19  	log "github.com/sirupsen/logrus"
    20  	"github.com/valyala/fasthttp"
    21  )
    22  
    23  const OneHourInMs = 60 * 60 * 1000
    24  
    25  func ProcessSearchTracesRequest(ctx *fasthttp.RequestCtx, myid uint64) {
    26  	searchRequestBody, readJSON, err := ParseAndValidateRequestBody(ctx)
    27  	if err != nil {
    28  		writeErrMsg(ctx, "ProcessSearchTracesRequest", "could not parse and validate request body", err)
    29  		return
    30  	}
    31  
    32  	nowTs := putils.GetCurrentTimeInMs()
    33  	searchText, startEpoch, endEpoch, _, _, _ := pipesearch.ParseSearchBody(readJSON, nowTs)
    34  
    35  	page := 1
    36  	pageVal, ok := readJSON["page"]
    37  	if !ok || pageVal == 0 {
    38  		page = 1
    39  	} else {
    40  		switch val := pageVal.(type) {
    41  		case json.Number:
    42  			pageInt, err := val.Int64()
    43  			if err != nil {
    44  				log.Errorf("ProcessSearchTracesRequest: error converting page to int: %v", err)
    45  			}
    46  			page = int(pageInt)
    47  		default:
    48  			log.Errorf("ProcessSearchTracesRequest: page is not a int Val %+v", val)
    49  		}
    50  	}
    51  
    52  	isOnlyTraceID, traceId := ExtractTraceID(searchText)
    53  	traceIds := make([]string, 0)
    54  	if isOnlyTraceID {
    55  		traceIds = append(traceIds, traceId)
    56  	} else {
    57  		// In order to get unique trace_id,  append group by block to the "searchText" field
    58  		if len(searchRequestBody.SearchText) > 0 {
    59  			searchRequestBody.SearchText = searchRequestBody.SearchText + " | stats count BY trace_id"
    60  		} else {
    61  			writeErrMsg(ctx, "ProcessSearchTracesRequest", "request does not contain required parameter: searchText", nil)
    62  			return
    63  		}
    64  
    65  		pipeSearchResponseOuter, err := processSearchRequest(searchRequestBody, myid)
    66  		if err != nil {
    67  			writeErrMsg(ctx, "ProcessSearchTracesRequest", err.Error(), nil)
    68  			return
    69  		}
    70  		traceIds = GetUniqueTraceIds(pipeSearchResponseOuter, startEpoch, endEpoch, page)
    71  	}
    72  
    73  	traces := make([]*structs.Trace, 0)
    74  	// Get status code count for each trace
    75  	for _, traceId := range traceIds {
    76  		// Get the start time and end time for this trace
    77  		searchRequestBody.SearchText = "trace_id=" + traceId + " AND parent_span_id=\"\" | fields start_time, end_time, name, service"
    78  		pipeSearchResponseOuter, err := processSearchRequest(searchRequestBody, myid)
    79  		if err != nil {
    80  			log.Errorf("ProcessSearchTracesRequest: traceId:%v, %v", traceId, err)
    81  			continue
    82  		}
    83  
    84  		if pipeSearchResponseOuter.Hits.Hits == nil || len(pipeSearchResponseOuter.Hits.Hits) == 0 {
    85  			continue
    86  		}
    87  
    88  		startTime, exists := pipeSearchResponseOuter.Hits.Hits[0]["start_time"]
    89  		if !exists {
    90  			continue
    91  		}
    92  		endTime, exists := pipeSearchResponseOuter.Hits.Hits[0]["end_time"]
    93  		if !exists {
    94  			continue
    95  		}
    96  
    97  		serviceName, exists := pipeSearchResponseOuter.Hits.Hits[0]["service"]
    98  		if !exists {
    99  			continue
   100  		}
   101  
   102  		operationName, exists := pipeSearchResponseOuter.Hits.Hits[0]["name"]
   103  		if !exists {
   104  			continue
   105  		}
   106  
   107  		traceStartTime := uint64(startTime.(float64))
   108  		traceEndTime := uint64(endTime.(float64))
   109  
   110  		// Only process traces which start and end in this period [startEpoch, endEpoch]
   111  		if (startEpoch*1e6 > traceStartTime) || (endEpoch*1e6 < traceEndTime) {
   112  			continue
   113  		}
   114  
   115  		searchRequestBody.SearchText = "trace_id=" + traceId + " | stats count BY status"
   116  		pipeSearchResponseOuter, err = processSearchRequest(searchRequestBody, myid)
   117  		if err != nil {
   118  			log.Errorf("ProcessSearchTracesRequest: traceId:%v, %v", traceId, err)
   119  			continue
   120  		}
   121  
   122  		AddTrace(pipeSearchResponseOuter, &traces, traceId, traceStartTime, traceEndTime, serviceName.(string), operationName.(string))
   123  	}
   124  
   125  	traceResult := &structs.TraceResult{
   126  		Traces: traces,
   127  	}
   128  
   129  	putils.WriteJsonResponse(ctx, traceResult)
   130  	ctx.SetStatusCode(fasthttp.StatusOK)
   131  }
   132  
   133  func ProcessTotalTracesRequest(ctx *fasthttp.RequestCtx, myid uint64) {
   134  	searchRequestBody, _, err := ParseAndValidateRequestBody(ctx)
   135  	if err != nil {
   136  		writeErrMsg(ctx, "ProcessTotalTracesRequest", "could not parse and validate request body", err)
   137  		return
   138  	}
   139  
   140  	// In order to get unique trace_id,  append group by block to the "searchText" field
   141  	if len(searchRequestBody.SearchText) > 0 {
   142  		searchRequestBody.SearchText = searchRequestBody.SearchText + " | stats count BY trace_id"
   143  	} else {
   144  		writeErrMsg(ctx, "ProcessTotalTracesRequest", "request does not contain required parameter: searchText", nil)
   145  		return
   146  	}
   147  
   148  	pipeSearchResponseOuter, err := processSearchRequest(searchRequestBody, myid)
   149  	if err != nil {
   150  		writeErrMsg(ctx, "ProcessTotalTracesRequest", err.Error(), nil)
   151  		return
   152  	}
   153  
   154  	totalTraces := GetTotalUniqueTraceIds(pipeSearchResponseOuter)
   155  	ctx.SetStatusCode(fasthttp.StatusOK)
   156  	ctx.SetBodyString(strconv.Itoa(totalTraces))
   157  }
   158  
   159  func ParseAndValidateRequestBody(ctx *fasthttp.RequestCtx) (*structs.SearchRequestBody, map[string]interface{}, error) {
   160  	rawJSON := ctx.PostBody()
   161  	if rawJSON == nil {
   162  		log.Errorf("Received empty search request body")
   163  		putils.SetBadMsg(ctx, "")
   164  		return nil, nil, errors.New("Received empty search request body")
   165  	}
   166  
   167  	readJSON := make(map[string]interface{})
   168  	var jsonc = jsoniter.ConfigCompatibleWithStandardLibrary
   169  	decoder := jsonc.NewDecoder(bytes.NewReader(rawJSON))
   170  	decoder.UseNumber()
   171  	err := decoder.Decode(&readJSON)
   172  	if err != nil {
   173  		return nil, nil, err
   174  	}
   175  
   176  	searchRequestBody := &structs.SearchRequestBody{}
   177  	if err := json.Unmarshal(ctx.PostBody(), &searchRequestBody); err != nil {
   178  		return nil, nil, err
   179  	}
   180  
   181  	searchRequestBody.QueryLanguage = "Splunk QL"
   182  	searchRequestBody.IndexName = "traces"
   183  
   184  	return searchRequestBody, readJSON, nil
   185  }
   186  
   187  func GetTotalUniqueTraceIds(pipeSearchResponseOuter *pipesearch.PipeSearchResponseOuter) int {
   188  	return len(pipeSearchResponseOuter.Aggs[""].Buckets)
   189  }
   190  func GetUniqueTraceIds(pipeSearchResponseOuter *pipesearch.PipeSearchResponseOuter, startEpoch uint64, endEpoch uint64, page int) []string {
   191  	if len(pipeSearchResponseOuter.Aggs[""].Buckets) < (page-1)*50 {
   192  		return []string{}
   193  	}
   194  
   195  	endIndex := page * 50
   196  	if endIndex > len(pipeSearchResponseOuter.Aggs[""].Buckets) {
   197  		endIndex = len(pipeSearchResponseOuter.Aggs[""].Buckets)
   198  	}
   199  
   200  	traceIds := make([]string, 0)
   201  	// Only Process up to 50 traces per page
   202  	for _, bucket := range pipeSearchResponseOuter.Aggs[""].Buckets[(page-1)*50 : endIndex] {
   203  		traceId, exists := bucket["key"]
   204  		if !exists {
   205  			continue
   206  		}
   207  		traceIds = append(traceIds, traceId.(string))
   208  	}
   209  	return traceIds
   210  }
   211  
   212  // Check if searchText only contains traceId as query condition
   213  func ExtractTraceID(searchText string) (bool, string) {
   214  	pattern := `^trace_id=([a-zA-Z0-9]+)$`
   215  
   216  	regex, err := regexp.Compile(pattern)
   217  	if err != nil {
   218  		return false, ""
   219  	}
   220  
   221  	matches := regex.FindStringSubmatch(searchText)
   222  	if len(matches) != 2 {
   223  		return false, ""
   224  	}
   225  
   226  	return true, matches[1]
   227  }
   228  
   229  func AddTrace(pipeSearchResponseOuter *pipesearch.PipeSearchResponseOuter, traces *[]*structs.Trace, traceId string, traceStartTime uint64,
   230  	traceEndTime uint64, serviceName string, operationName string) {
   231  	spanCnt := 0
   232  	errorCnt := 0
   233  	for _, bucket := range pipeSearchResponseOuter.Aggs[""].Buckets {
   234  		statusCode, exists := bucket["key"].(string)
   235  		if !exists {
   236  			log.Error("AddTrace: Unable to extract 'key' from bucket")
   237  			return
   238  		}
   239  		countMap, exists := bucket["count(*)"].(map[string]interface{})
   240  		if !exists {
   241  			log.Error("AddTrace: Unable to extract 'count(*)' from bucket")
   242  			return
   243  		}
   244  		countFloat64, exists := countMap["value"].(float64)
   245  		if !exists {
   246  			log.Error("AddTrace: Unable to extract 'value' from bucket")
   247  			return
   248  		}
   249  
   250  		count := int(countFloat64)
   251  		spanCnt += count
   252  		if statusCode == string(structs.Status_STATUS_CODE_ERROR) {
   253  			errorCnt += count
   254  		}
   255  	}
   256  
   257  	trace := &structs.Trace{
   258  		TraceId:         traceId,
   259  		StartTime:       traceStartTime,
   260  		EndTime:         traceEndTime,
   261  		SpanCount:       spanCnt,
   262  		SpanErrorsCount: errorCnt,
   263  		ServiceName:     serviceName,
   264  		OperationName:   operationName,
   265  	}
   266  
   267  	*traces = append(*traces, trace)
   268  
   269  }
   270  
   271  // Call /api/search endpoint
   272  func processSearchRequest(searchRequestBody *structs.SearchRequestBody, myid uint64) (*pipesearch.PipeSearchResponseOuter, error) {
   273  
   274  	modifiedData, err := json.Marshal(searchRequestBody)
   275  	if err != nil {
   276  		return nil, fmt.Errorf("processSearchRequest: could not marshal to json body, err=%v", err)
   277  	}
   278  
   279  	// Get initial data
   280  	rawTraceCtx := &fasthttp.RequestCtx{}
   281  	rawTraceCtx.Request.Header.SetMethod("POST")
   282  	rawTraceCtx.Request.SetBody(modifiedData)
   283  	pipesearch.ProcessPipeSearchRequest(rawTraceCtx, myid)
   284  	pipeSearchResponseOuter := pipesearch.PipeSearchResponseOuter{}
   285  
   286  	// Parse initial data
   287  	if err := json.Unmarshal(rawTraceCtx.Response.Body(), &pipeSearchResponseOuter); err != nil {
   288  		return nil, fmt.Errorf("processSearchRequest: could not unmarshal json body, err=%v", err)
   289  	}
   290  	return &pipeSearchResponseOuter, nil
   291  }
   292  
   293  // Monitor spans health in the last 5 mins
   294  func MonitorSpansHealth() {
   295  	time.Sleep(1 * time.Minute) // Wait for initial traces ingest first
   296  	for {
   297  		ProcessRedTracesIngest()
   298  		time.Sleep(5 * time.Minute)
   299  	}
   300  }
   301  
   302  func ProcessRedTracesIngest() {
   303  	// Initial request
   304  	searchRequestBody := structs.SearchRequestBody{
   305  		IndexName:     "traces",
   306  		SearchText:    "*",
   307  		QueryLanguage: "Splunk QL",
   308  		StartEpoch:    "now-5m",
   309  		EndEpoch:      "now",
   310  		From:          0,
   311  		Size:          1000,
   312  	}
   313  
   314  	// We can only determine whether a span is an entry span or not after retrieving all the spans,
   315  	// E.g.: Perhaps there is no parent span for span:12345 in this request, and its parent span exists in the next
   316  	//request. Therefore, we cannot determine if one span has a parent span in a single request.
   317  	// We should use this array to record all the spans
   318  	spans := make([]*structs.Span, 0)
   319  
   320  	for {
   321  		ctx := &fasthttp.RequestCtx{}
   322  		requestData, err := json.Marshal(searchRequestBody)
   323  		if err != nil {
   324  			log.Errorf("ProcessRedTracesIngest: could not marshal to json body, err=%v", err)
   325  			return
   326  		}
   327  
   328  		ctx.Request.Header.SetMethod("POST")
   329  		ctx.Request.SetBody(requestData)
   330  
   331  		// Get initial data
   332  		pipesearch.ProcessPipeSearchRequest(ctx, 0)
   333  
   334  		// Parse initial data
   335  		rawSpanData := structs.RawSpanData{}
   336  		if err := json.Unmarshal(ctx.Response.Body(), &rawSpanData); err != nil {
   337  			writeErrMsg(ctx, "ProcessRedTracesIngest", "could not unmarshal json body", err)
   338  			return
   339  		}
   340  
   341  		if rawSpanData.Hits.Spans == nil || len(rawSpanData.Hits.Spans) == 0 {
   342  			break
   343  		}
   344  
   345  		spans = append(spans, rawSpanData.Hits.Spans...)
   346  		searchRequestBody.From += 1000
   347  	}
   348  
   349  	if len(spans) == 0 {
   350  		return
   351  	}
   352  
   353  	spanIDtoService := make(map[string]string)
   354  	entrySpans := make([]*structs.Span, 0)
   355  	serviceToSpanCnt := make(map[string]int)
   356  	serviceToErrSpanCnt := make(map[string]int)
   357  	serviceToSpanDuration := make(map[string][]uint64)
   358  
   359  	// Map from the service name to the RED metrics
   360  	serviceToMetrics := make(map[string]structs.RedMetrics)
   361  
   362  	for _, span := range spans {
   363  		spanIDtoService[span.SpanID] = span.Service
   364  	}
   365  
   366  	// Get entry spans
   367  	for _, span := range spans {
   368  
   369  		// A span is an entry point if it has no parent or its parent is a different service
   370  		if len(span.ParentSpanID) != 0 {
   371  			parentServiceName, exists := spanIDtoService[span.ParentSpanID]
   372  			if exists && parentServiceName == span.Service {
   373  				continue
   374  			}
   375  		}
   376  
   377  		entrySpans = append(entrySpans, span)
   378  	}
   379  
   380  	// Map the service name to: the number of entry spans, erroring entry spans, duration list of span
   381  	for _, entrySpan := range entrySpans {
   382  		spanCnt, exists := serviceToSpanCnt[entrySpan.Service]
   383  		if exists {
   384  			serviceToSpanCnt[entrySpan.Service] = spanCnt + 1
   385  		} else {
   386  			serviceToSpanCnt[entrySpan.Service] = 1
   387  		}
   388  
   389  		if string(structs.Status_STATUS_CODE_ERROR) == string(entrySpan.Status) {
   390  			spanErrorCnt, exists := serviceToErrSpanCnt[entrySpan.Service]
   391  			if exists {
   392  				serviceToErrSpanCnt[entrySpan.Service] = spanErrorCnt + 1
   393  			} else {
   394  				serviceToErrSpanCnt[entrySpan.Service] = 1
   395  			}
   396  		}
   397  
   398  		durationList, exists := serviceToSpanDuration[entrySpan.Service]
   399  		if exists {
   400  			durationList = append(durationList, entrySpan.Duration)
   401  		} else {
   402  			durationList = []uint64{entrySpan.Duration}
   403  		}
   404  
   405  		serviceToSpanDuration[entrySpan.Service] = durationList
   406  	}
   407  
   408  	// Map from the service name to the RED metrics
   409  	for service, spanCnt := range serviceToSpanCnt {
   410  
   411  		errSpanCnt := 0
   412  		val, exists := serviceToErrSpanCnt[service]
   413  		if exists {
   414  			errSpanCnt = val
   415  		}
   416  
   417  		redMetrics := structs.RedMetrics{
   418  			Rate:      float64(spanCnt) / float64(60),
   419  			ErrorRate: (float64(errSpanCnt) / float64(spanCnt)) * 100,
   420  		}
   421  
   422  		durations, exists := serviceToSpanDuration[service]
   423  		for i, duration := range durations {
   424  			durations[i] = duration / 1000000 // convert duration from nanoseconds to milliseconds
   425  		}
   426  		if exists {
   427  			redMetrics.P50 = utils.FindPercentileData(durations, 50)
   428  			redMetrics.P90 = utils.FindPercentileData(durations, 90)
   429  			redMetrics.P95 = utils.FindPercentileData(durations, 95)
   430  			redMetrics.P99 = utils.FindPercentileData(durations, 99)
   431  		}
   432  
   433  		serviceToMetrics[service] = redMetrics
   434  
   435  		jsonData, err := redMetricsToJson(redMetrics, service)
   436  		if err != nil {
   437  			log.Errorf("ProcessRedTracesIngest: failed to marshal redMetrics %v: %v", redMetrics, err)
   438  			continue
   439  		}
   440  
   441  		// Setup ingestion parameters
   442  		now := putils.GetCurrentTimeInMs()
   443  		indexName := "red-traces"
   444  		shouldFlush := false
   445  		lenJsonData := uint64(len(jsonData))
   446  		localIndexMap := make(map[string]string)
   447  		orgId := uint64(0)
   448  
   449  		// Ingest red metrics
   450  		err = writer.ProcessIndexRequest(jsonData, now, indexName, lenJsonData, shouldFlush, localIndexMap, orgId)
   451  		if err != nil {
   452  			log.Errorf("ProcessRedTracesIngest: failed to process ingest request: %v", err)
   453  			continue
   454  		}
   455  
   456  	}
   457  }
   458  
   459  func redMetricsToJson(redMetrics structs.RedMetrics, service string) ([]byte, error) {
   460  	result := make(map[string]interface{})
   461  	result["service"] = service
   462  	result["rate"] = redMetrics.Rate
   463  	result["error_rate"] = redMetrics.ErrorRate
   464  	result["p50"] = redMetrics.P50
   465  	result["p90"] = redMetrics.P90
   466  	result["p95"] = redMetrics.P95
   467  	result["p99"] = redMetrics.P99
   468  	return json.Marshal(result)
   469  }
   470  
   471  func DependencyGraphThread() {
   472  	time.Sleep(1 * time.Minute) // Initial one-minute wait
   473  	depMatrix := MakeTracesDependancyGraph()
   474  	writeDependencyMatrix(depMatrix)
   475  
   476  	for {
   477  		now := time.Now()
   478  		nextHour := now.Truncate(time.Hour).Add(time.Hour)
   479  		sleepDuration := time.Until(nextHour)
   480  
   481  		time.Sleep(sleepDuration)
   482  		depMatrix = MakeTracesDependancyGraph()
   483  		writeDependencyMatrix(depMatrix)
   484  	}
   485  }
   486  
   487  func MakeTracesDependancyGraph() map[string]map[string]int {
   488  	nowTs := putils.GetCurrentTimeInMs()
   489  	startEpoch := nowTs - OneHourInMs
   490  	endEpoch := nowTs
   491  
   492  	requestBody := map[string]interface{}{
   493  		"indexName":     "traces",
   494  		"startEpoch":    startEpoch,
   495  		"endEpoch":      endEpoch,
   496  		"searchText":    "*",
   497  		"queryLanguage": "Splunk QL",
   498  	}
   499  	requestBodyJSON, err := json.Marshal(requestBody)
   500  	if err != nil {
   501  		fmt.Println("Error marshaling request body:", err)
   502  		return nil
   503  	}
   504  	ctx := &fasthttp.RequestCtx{}
   505  	ctx.Request.SetBody(requestBodyJSON)
   506  
   507  	ctx.Request.Header.SetMethod("POST")
   508  	pipesearch.ProcessPipeSearchRequest(ctx, 0)
   509  
   510  	rawSpanData := structs.RawSpanData{}
   511  	if err := json.Unmarshal(ctx.Response.Body(), &rawSpanData); err != nil {
   512  		log.Errorf("MakeTracesDependancyGraph: could not unmarshal json body, err=%v", err)
   513  		return nil
   514  	}
   515  	spanIdToServiceName := make(map[string]string)
   516  	dependencyMatrix := make(map[string]map[string]int)
   517  
   518  	for _, span := range rawSpanData.Hits.Spans {
   519  		spanIdToServiceName[span.SpanID] = span.Service
   520  	}
   521  	for _, span := range rawSpanData.Hits.Spans {
   522  		if span.ParentSpanID == "" {
   523  			continue
   524  		}
   525  		parentService, parentExists := spanIdToServiceName[span.ParentSpanID]
   526  		if !parentExists {
   527  			continue
   528  		}
   529  		if parentService == span.Service {
   530  			continue
   531  		}
   532  		if dependencyMatrix[parentService] == nil {
   533  			dependencyMatrix[parentService] = make(map[string]int)
   534  		}
   535  		dependencyMatrix[parentService][span.Service]++
   536  	}
   537  	return dependencyMatrix
   538  }
   539  
   540  func writeDependencyMatrix(dependencyMatrix map[string]map[string]int) {
   541  	dependencyMatrixJSON, err := json.Marshal(dependencyMatrix)
   542  	if err != nil {
   543  		log.Errorf("Error marshaling dependency matrix:err=%v", err)
   544  		return
   545  	}
   546  
   547  	// Setup ingestion parameters
   548  	now := putils.GetCurrentTimeInMs()
   549  	indexName := "service-dependency"
   550  	shouldFlush := false
   551  	lenJsonData := uint64(len((dependencyMatrixJSON)))
   552  	localIndexMap := make(map[string]string)
   553  	orgId := uint64(0)
   554  
   555  	// Ingest
   556  	err = writer.ProcessIndexRequest(dependencyMatrixJSON, now, indexName, lenJsonData, shouldFlush, localIndexMap, orgId)
   557  	if err != nil {
   558  		log.Errorf("MakeTracesDependancyGraph: failed to process ingest request: %v", err)
   559  
   560  	}
   561  }
   562  
   563  func ProcessDependencyRequest(ctx *fasthttp.RequestCtx, myid uint64) {
   564  	searchRequestBody := &structs.SearchRequestBody{}
   565  	searchRequestBody.QueryLanguage = "Splunk QL"
   566  	searchRequestBody.IndexName = "service-dependency"
   567  	searchRequestBody.SearchText = "*"
   568  
   569  	dependencyResponseOuter, err := processSearchRequest(searchRequestBody, myid)
   570  	if err != nil {
   571  		log.Errorf("ProcessSearchRequest: %v", err)
   572  		return
   573  	}
   574  	processedData := make(map[string]interface{})
   575  	if dependencyResponseOuter.Hits.Hits == nil || len(dependencyResponseOuter.Hits.Hits) == 0 {
   576  		depMatrix := MakeTracesDependancyGraph()
   577  		if len(depMatrix) == 0 {
   578  			log.Errorf("pipeSearchResponseOuter: received empty response")
   579  			ctx.SetStatusCode(fasthttp.StatusOK)
   580  			return
   581  		}
   582  		writeDependencyMatrix(depMatrix)
   583  		for key, value := range depMatrix {
   584  			for k, v := range value {
   585  				if processedData[key] == nil {
   586  					processedData[key] = make(map[string]int)
   587  				}
   588  				serviceMap := processedData[key].(map[string]int)
   589  				serviceMap[k] = v
   590  			}
   591  		}
   592  	} else {
   593  		for key, value := range dependencyResponseOuter.Hits.Hits[0] {
   594  			if key == "_index" || key == "timestamp" {
   595  				processedData[key] = value
   596  				continue
   597  			}
   598  			keys := strings.Split(key, ".")
   599  			if len(keys) != 2 {
   600  				fmt.Printf("Unexpected key format: %s\n", key)
   601  				continue
   602  			}
   603  			service, dependentService := keys[0], keys[1]
   604  			if processedData[service] == nil {
   605  				processedData[service] = make(map[string]int)
   606  			}
   607  
   608  			serviceMap := processedData[service].(map[string]int)
   609  			serviceMap[dependentService] = int(value.(float64))
   610  		}
   611  	}
   612  
   613  	ctx.SetContentType("application/json; charset=utf-8")
   614  	err = json.NewEncoder(ctx).Encode(processedData)
   615  	if err != nil {
   616  		ctx.SetStatusCode(fasthttp.StatusServiceUnavailable)
   617  		_, writeErr := ctx.WriteString(fmt.Sprintf("Error encoding JSON: %s", err.Error()))
   618  		if writeErr != nil {
   619  			log.Errorf("Error writing to context: %v", writeErr)
   620  		}
   621  		return
   622  	}
   623  	ctx.SetStatusCode(fasthttp.StatusOK)
   624  
   625  }
   626  
   627  func ProcessGanttChartRequest(ctx *fasthttp.RequestCtx, myid uint64) {
   628  
   629  	rawJSON := ctx.PostBody()
   630  	if rawJSON == nil {
   631  		log.Errorf("ProcessGanttChartRequest: received empty search request body ")
   632  		putils.SetBadMsg(ctx, "")
   633  		return
   634  	}
   635  
   636  	readJSON := make(map[string]interface{})
   637  	var jsonc = jsoniter.ConfigCompatibleWithStandardLibrary
   638  	decoder := jsonc.NewDecoder(bytes.NewReader(rawJSON))
   639  	decoder.UseNumber()
   640  	err := decoder.Decode(&readJSON)
   641  	if err != nil {
   642  		writeErrMsg(ctx, "ProcessGanttChartRequest", "could not decode json", err)
   643  		return
   644  	}
   645  
   646  	// Parse the JSON data from ctx.PostBody
   647  	searchRequestBody := &structs.SearchRequestBody{}
   648  	if err := json.Unmarshal(ctx.PostBody(), &searchRequestBody); err != nil {
   649  		writeErrMsg(ctx, "ProcessGanttChartRequest", "could not unmarshal json body", err)
   650  		return
   651  	}
   652  
   653  	searchRequestBody.QueryLanguage = "Splunk QL"
   654  	searchRequestBody.IndexName = "traces"
   655  	searchRequestBody.From = 0
   656  	searchRequestBody.Size = 1000
   657  
   658  	// Used to find out which attributes belong to tags
   659  	fieldsNotInTag := []string{"trace_id", "span_id", "parent_span_id", "service", "trace_state", "name", "kind", "start_time", "end_time",
   660  		"duration", "dropped_attributes_count", "dropped_events_count", "dropped_links_count", "status", "events", "links", "_index", "timestamp"}
   661  
   662  	idToSpanMap := make(map[string]*structs.GanttChartSpan, 0)
   663  	idToParentId := make(map[string]string, 0)
   664  
   665  	for {
   666  		modifiedData, err := json.Marshal(searchRequestBody)
   667  		if err != nil {
   668  			writeErrMsg(ctx, "ProcessGanttChartRequest", "could not marshal to json body", err)
   669  		}
   670  
   671  		// Get initial data
   672  		rawTraceCtx := &fasthttp.RequestCtx{}
   673  		rawTraceCtx.Request.Header.SetMethod("POST")
   674  		rawTraceCtx.Request.SetBody(modifiedData)
   675  		pipesearch.ProcessPipeSearchRequest(rawTraceCtx, myid)
   676  
   677  		resultMap := make(map[string]interface{}, 0)
   678  		decoder := jsonc.NewDecoder(bytes.NewReader(rawTraceCtx.Response.Body()))
   679  		decoder.UseNumber()
   680  		err = decoder.Decode(&resultMap)
   681  		if err != nil {
   682  			writeErrMsg(ctx, "ProcessGanttChartRequest", "could not decode response body", err)
   683  			return
   684  		}
   685  
   686  		hits, exists := resultMap["hits"]
   687  		if !exists {
   688  			writeErrMsg(ctx, "ProcessGanttChartRequest", "Key 'hits' not found in response", nil)
   689  			return
   690  		}
   691  
   692  		hitsMap, ok := hits.(map[string]interface{})
   693  		if !ok {
   694  			writeErrMsg(ctx, "ProcessGanttChartRequest", "Error asserting type for 'hits'", nil)
   695  			return
   696  		}
   697  
   698  		records, exists := hitsMap["records"]
   699  		if !exists {
   700  			writeErrMsg(ctx, "ProcessGanttChartRequest", "Key 'records' not found in response", nil)
   701  			return
   702  		}
   703  
   704  		rawSpans, ok := records.([]interface{})
   705  		if !ok {
   706  			writeErrMsg(ctx, "ProcessGanttChartRequest", "Error asserting type for 'records'", nil)
   707  			return
   708  		}
   709  
   710  		if len(rawSpans) == 0 {
   711  			break
   712  		}
   713  
   714  		for _, rawSpan := range rawSpans {
   715  			spanMap := rawSpan.(map[string]interface{})
   716  
   717  			span := &structs.GanttChartSpan{}
   718  
   719  			jsonData, err := json.Marshal(spanMap)
   720  			if err != nil {
   721  				log.Errorf("ProcessGanttChartRequest: could not marshal to json body, err=%v", err)
   722  				continue
   723  			}
   724  			if err := json.Unmarshal(jsonData, &span); err != nil {
   725  				log.Errorf("ProcessGanttChartRequest: could not unmarshal to json body, err=%v", err)
   726  				continue
   727  			}
   728  
   729  			serviceName, exists := spanMap["service"]
   730  			if !exists {
   731  				log.Errorf("ProcessGanttChartRequest: span:%v does not contain the required field: service", span.SpanID)
   732  				continue
   733  			}
   734  
   735  			operationName, exists := spanMap["name"]
   736  			if !exists {
   737  				log.Errorf("ProcessGanttChartRequest: span:%v does not contain the required field: name", span.SpanID)
   738  				continue
   739  			}
   740  
   741  			parentSpanId, exists := spanMap["parent_span_id"]
   742  			if !exists {
   743  				log.Errorf("ProcessGanttChartRequest: span:%v does not contain the required field: parent_span_id", span.SpanID)
   744  				continue
   745  			}
   746  
   747  			idToParentId[span.SpanID] = parentSpanId.(string)
   748  
   749  			// Remove all non-tag fields
   750  			for _, strToRemove := range fieldsNotInTag {
   751  				delete(spanMap, strToRemove)
   752  			}
   753  
   754  			for key, val := range spanMap {
   755  				if val == nil {
   756  					delete(spanMap, key)
   757  				}
   758  			}
   759  			span.Tags = spanMap
   760  			span.ServiceName = serviceName.(string)
   761  			span.OperationName = operationName.(string)
   762  			idToSpanMap[span.SpanID] = span
   763  		}
   764  		searchRequestBody.From += 1000
   765  	}
   766  
   767  	res, err := utils.BuildSpanTree(idToSpanMap, idToParentId)
   768  	if err != nil {
   769  		writeErrMsg(ctx, "ProcessGanttChartRequest", err.Error(), nil)
   770  		return
   771  	}
   772  
   773  	putils.WriteJsonResponse(ctx, res)
   774  	ctx.SetStatusCode(fasthttp.StatusOK)
   775  }
   776  
   777  func writeErrMsg(ctx *fasthttp.RequestCtx, functionName string, errorMsg string, err error) {
   778  
   779  	errContent := functionName + ": " + errorMsg
   780  	if err != nil {
   781  		errContent += fmt.Sprintf(", err=%v", err)
   782  	}
   783  
   784  	ctx.SetStatusCode(fasthttp.StatusBadRequest)
   785  	_, err = ctx.WriteString(errContent)
   786  	if err != nil {
   787  		log.Errorf(functionName, ": could not write error message err=%v", err)
   788  	}
   789  	log.Errorf(functionName, ": failed to decode search request body! Err=%v", err)
   790  }