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 }