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 }