github.com/siglens/siglens@v0.0.0-20240328180423-f7ce9ae441ed/static/js/trace.js (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  'use strict';
    18  
    19  let svgWidth;
    20  let traceId;
    21  
    22  $(document).ready(() => {
    23      $(".theme-btn").on("click", themePickerHandler);
    24      if (Cookies.get("theme")) {
    25          theme = Cookies.get("theme");
    26          $("body").attr("data-theme", theme);
    27      }
    28      svgWidth = $("#timeline-container").width();
    29  
    30      traceId = getParameterFromUrl('trace_id');
    31      getTraceInformation(traceId);
    32  });
    33  
    34  function getParameterFromUrl(param) {
    35      const urlParams = new URLSearchParams(window.location.search);
    36      return urlParams.get(param);
    37  }
    38  
    39  function getTraceInformation(traceId) {
    40      console.log("traceId: " + traceId);
    41      $.ajax({
    42          method: "POST",
    43          url: "api/traces/ganttchart",
    44          headers: {
    45              'Content-Type': 'application/json; charset=utf-8',
    46              'Accept': '*/*'
    47          },
    48          data: JSON.stringify({
    49              "searchText": `trace_id=${traceId}`,
    50              "startEpoch": "now-3h",
    51              "endEpoch": "now"
    52          }),
    53          dataType: 'json',
    54          crossDomain: true,
    55      }).then(function (res) {
    56          traceDetails(res);
    57          displayTimeline(res)
    58      })
    59  }
    60  
    61  $(".back-to-search-traces").on("click", function(){
    62      window.location.href = "search-traces.html";
    63  });
    64  
    65  function traceDetails(res){
    66      $('#trace-overview').append(
    67          ` <div class="trace-name">
    68          <span class="service">${res.service_name}</span>
    69          <span class="operation">: ${res.operation_name}</span>
    70          <span class="trace-id">${traceId.substring(0, 7)}</span>
    71      </div>
    72      <div class="d-flex trace-details">
    73          <div>Trace Start: <span>${ convertNanosecondsToDateTime(res.actual_start_time)}</span></div>
    74          <div>Duration:<span>${ nsToMs(res.duration)}ms</span></div>
    75      </div>`
    76      )
    77  }
    78  
    79  function nsToMs(ns) {
    80      return ns / 1e6;
    81  }
    82  
    83  function convertNanosecondsToDateTime(timestamp) {
    84      const milliseconds = timestamp / 1e6;
    85      const date = new Date(milliseconds);
    86  
    87      const formattedDate = date.toLocaleString('en-US', {
    88          month: 'long',
    89          day: 'numeric',
    90          year: 'numeric',
    91          hour: 'numeric',
    92          minute: 'numeric',
    93          second: 'numeric',
    94          millisecond: 'numeric',
    95          timeZoneName: 'short'
    96      });
    97  
    98      return formattedDate;
    99  }
   100  
   101  function displayTimeline(data) {
   102  
   103      const totalHeight = calculateTotalHeight(data);
   104      const padding = { top: 20, right: 20, bottom: 20, left: 20 };
   105  
   106      const svg = d3
   107          .select("#timeline-container")
   108          .append("svg")
   109          .attr("width", svgWidth + padding.left + padding.right - 50)
   110          .attr("height", totalHeight + padding.top + padding.bottom)
   111          .append("g")
   112          .attr(
   113              "transform",
   114              "translate(" + padding.left + "," + padding.top + ")",
   115          );
   116  
   117      const xScale = d3
   118          .scaleLinear()
   119          .domain([nsToMs(data.start_time), nsToMs(data.end_time)])
   120          .range([400, svgWidth - 100]);
   121  
   122      // Add a time grid
   123      const timeTicks = xScale.ticks(4); // number of ticks
   124      svg.selectAll(".time-tick")
   125          .data(timeTicks)
   126          .enter()
   127          .append("line")
   128          .attr("class", "time-tick")
   129          .attr("x1", (d) => xScale(d))
   130          .attr("x2", (d) => xScale(d))
   131          .attr("y1", 50)
   132          .attr("y2", 50 + totalHeight);
   133  
   134      // Add time labels
   135      svg.selectAll(".time-label")
   136          .data(timeTicks)
   137          .enter()
   138          .append("text")
   139          .attr("class", "time-label")
   140          .attr("x", (d) => xScale(d))
   141          .attr("y", 40)
   142          .attr("text-anchor", "middle")
   143          .text((d) => `${d}ms`)
   144  
   145      // recursively render the timeline
   146      let y = 100; 
   147  
   148      function renderTimeline(node, level = 0) {
   149          if (node.children) {
   150              node.children.sort((a, b) => a.start_time - b.start_time); // sort by start time
   151          }
   152      // Add node labels
   153      const label = svg
   154          .append("text")
   155          .attr("x", 10 * level)
   156          .attr("y", y + 12)
   157          .text(`${node.service_name}:${node.operation_name}`)
   158          .attr("class", "node-label")
   159          .classed("anomalous-node", node.is_anomalous)
   160          .classed("normal-node", !node.is_anomalous);
   161  
   162  
   163          if (!node.is_anomalous){
   164          const rect = svg
   165              .append("rect")
   166              .attr("x",xScale(nsToMs(node.start_time)))
   167              .attr("y", y)
   168              .attr("width", xScale(nsToMs(node.end_time)) - xScale(nsToMs(node.start_time)))
   169              .attr("height", 20)
   170              .attr("fill", "#6449D6")
   171              .on("mouseover", () => {
   172                  rect.style("cursor", "pointer");
   173                  tooltip
   174                      .style("display", "block")
   175                      .html(
   176                          `
   177                          <strong>SpanId</strong>: ${node.span_id} <br>
   178                          <strong>Name</strong>: ${node.service_name} : ${node.operation_name}<br>
   179                          <strong>Start Time</strong>: ${nsToMs(node.start_time)}ms<br>
   180                          <strong>End Time</strong>: ${nsToMs(node.end_time)}ms<br>
   181                          <strong>Duration</strong>: ${nsToMs(node.duration)}ms <br>
   182                          <strong>Tags</strong>: ${Object.entries(node.tags).map(([key, value]) => `<em>${key}</em> <strong>:</strong> <em>${value}</em><br>`).join('')}
   183                        `,
   184                      );
   185              })
   186              .on("mousemove", (event) => {
   187                  tooltip
   188                      .style("left", event.pageX + 10 + "px")
   189                      .style("top", event.pageY - 28 + "px");
   190              })
   191              .on("mouseout", () => {
   192                  rect.style("cursor", "default");
   193                  tooltip.style("display", "none");
   194              });
   195          }
   196  
   197          // Increment y for the next node
   198          y += 50;
   199  
   200      if (node.children && node.children.length > 0) {
   201          node.children.forEach((child) => {
   202              renderTimeline(child, level + 1);
   203          });
   204      }
   205  
   206      }
   207  
   208      const tooltip = d3
   209          .select("body")
   210          .append("div")
   211          .attr("class", "tooltip-gantt");
   212  
   213      renderTimeline(data);
   214  }
   215  
   216  function calculateTotalHeight(node) {
   217      let totalHeight = 0;
   218      function calculateHeight(node) {
   219          totalHeight += 50;
   220          if (node.children !== null) {
   221              node.children.forEach(calculateHeight);
   222          }
   223      }
   224      calculateHeight(node);
   225      return totalHeight + 200;
   226  }