2022-06-16 09:35:13 +00:00
|
|
|
<!DOCTYPE html>
|
|
|
|
<html lang="en">
|
|
|
|
<head>
|
|
|
|
<meta charset="UTF-8">
|
|
|
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
|
<title>Trace Gantt</title>
|
|
|
|
<link rel="stylesheet" href="css/bootstrap.min.css">
|
|
|
|
<link rel="stylesheet" href="css/d3-gantt.css">
|
|
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<script language="javascript" type="text/javascript" src="js/jquery.min.js"></script>
|
|
|
|
<script language="javascript" type="text/javascript" src="js/bootstrap.min.js"></script>
|
|
|
|
<script language="javascript" type="text/javascript" src="js/d3.v4.min.js"></script>
|
|
|
|
<script language="javascript" type="text/javascript" src="js/d3-tip-0.8.0-alpha.1.js"></script>
|
|
|
|
<script language="javascript" type="text/javascript" src="js/d3-gantt.js"></script>
|
2022-06-17 16:02:03 +00:00
|
|
|
|
|
|
|
<nav class="navbar navbar-default">
|
|
|
|
<div class="container-fluid">
|
|
|
|
<div class="navbar-header">
|
|
|
|
<a class="navbar-brand" href="#">ClickHouse trace-visualizer</a>
|
|
|
|
</div>
|
|
|
|
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
|
|
|
|
<form class="navbar-form navbar-left">
|
|
|
|
<button type="button" class="btn btn-primary" id="toolbar-load" data-toggle="modal" data-target="#loadModal">Load</button>
|
|
|
|
</form>
|
|
|
|
</div>
|
2022-06-17 07:30:08 +00:00
|
|
|
</div>
|
2022-06-17 16:02:03 +00:00
|
|
|
</nav>
|
|
|
|
|
2022-06-16 09:35:13 +00:00
|
|
|
<div id="placeholder" class="chart-placeholder"></div>
|
2022-06-17 16:02:03 +00:00
|
|
|
|
2022-06-17 07:30:08 +00:00
|
|
|
<div class="modal fade" id="loadModal" tabindex="-1" role="dialog" aria-labelledby="loadModalLabel">
|
|
|
|
<div class="modal-dialog" role="document">
|
|
|
|
<div class="modal-content">
|
|
|
|
<div class="modal-header">
|
|
|
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
|
|
|
|
aria-hidden="true">×</span></button>
|
|
|
|
<h4 class="modal-title" id="loadModalLabel">Load Trace JSON</h4>
|
|
|
|
</div>
|
|
|
|
<div class="modal-body">
|
|
|
|
<input type="file" id="loadFiles" value="Load" /><br />
|
|
|
|
</div>
|
|
|
|
<div class="modal-footer">
|
|
|
|
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
|
|
|
<button type="button" class="btn btn-primary" id="btnDoLoad">Load</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
2022-06-16 09:35:13 +00:00
|
|
|
</body>
|
|
|
|
</html>
|
|
|
|
|
|
|
|
<script language="javascript">
|
2022-06-17 16:02:03 +00:00
|
|
|
var example_json = [];
|
|
|
|
var example_colors = ["#f88", "#8f8", "#88f", "#888", "#aaa"];
|
|
|
|
for (let i = 0; i < 1024; i++) {
|
|
|
|
example_json.push({
|
|
|
|
t1: i + 50 - Math.random() * 60,
|
|
|
|
t2: i + 50 + Math.random() * 60,
|
|
|
|
band: "band" + (i % 128),
|
|
|
|
color: example_colors[i % example_colors.length],
|
|
|
|
text: "it is span #" + i
|
|
|
|
});
|
|
|
|
}
|
|
|
|
example_json.bands = new Set(example_json.map(x => x.band));
|
|
|
|
example_json.max_time_ms = Math.max(...example_json.map(x => x.t2));
|
|
|
|
|
2022-06-16 09:35:13 +00:00
|
|
|
var data = null;
|
2022-06-17 07:30:08 +00:00
|
|
|
var chart = null;
|
|
|
|
|
|
|
|
function renderChart(parsed) {
|
|
|
|
data = parsed;
|
2022-06-17 16:02:03 +00:00
|
|
|
if (chart != null) {
|
|
|
|
chart.destroy();
|
|
|
|
}
|
|
|
|
let view_height = window.innerHeight - $("#placeholder")[0].getBoundingClientRect().y - 40;
|
|
|
|
let data_height = parsed.bands.size * 8;
|
|
|
|
chart = d3.gantt()
|
|
|
|
.height(Math.max(view_height, data_height))
|
|
|
|
.view_height(view_height)
|
|
|
|
.selector("#placeholder")
|
|
|
|
.timeDomain([0, parsed.max_time_ms])
|
|
|
|
;
|
2022-06-17 07:30:08 +00:00
|
|
|
chart(data);
|
|
|
|
}
|
2022-06-16 09:35:13 +00:00
|
|
|
|
|
|
|
$("<div id='errmsg'></div>").css({
|
|
|
|
position: "absolute",
|
|
|
|
display: "none",
|
|
|
|
border: "1px solid #faa",
|
|
|
|
padding: "2px",
|
|
|
|
"background-color": "#fcc",
|
|
|
|
opacity: 0.80
|
|
|
|
}).appendTo("body");
|
|
|
|
|
|
|
|
function fetchData(dataurl, parser = x => x) {
|
|
|
|
function onDataReceived(json, textStatus, xhr) {
|
|
|
|
$("#errmsg").hide();
|
2022-06-17 07:30:08 +00:00
|
|
|
renderChart(parser(json));
|
2022-06-16 09:35:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function onDataError(xhr, error) {
|
|
|
|
console.log(arguments);
|
|
|
|
$("#errmsg").text("Fetch data error: " + error + (xhr.status == 200? xhr.responseText: ""))
|
|
|
|
.css({bottom: "5px", left: "25%", width: "50%"})
|
|
|
|
.fadeIn(200);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dataurl) {
|
|
|
|
$.ajax({
|
|
|
|
url: dataurl,
|
|
|
|
type: "GET",
|
|
|
|
dataType: "json",
|
|
|
|
success: function (json, textStatus, xhr) { onDataReceived(json, textStatus, xhr); },
|
|
|
|
error: onDataError
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
onDataReceived(example_json, "textStatus", "xhr");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-17 07:30:08 +00:00
|
|
|
$("#btnDoLoad").click(function(){
|
|
|
|
let element = document.getElementById('loadFiles');
|
|
|
|
let files = element.files;
|
|
|
|
if (files.length <= 0) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
let fr = new FileReader();
|
|
|
|
fr.onload = function(e) {
|
|
|
|
$("#errmsg").hide();
|
|
|
|
renderChart(parseClickHouseTrace(JSON.parse(e.target.result)));
|
|
|
|
}
|
|
|
|
fr.readAsText(files.item(0));
|
|
|
|
element.value = '';
|
|
|
|
$('#loadModal').modal('hide');
|
|
|
|
});
|
|
|
|
|
2022-06-16 09:35:13 +00:00
|
|
|
function parseClickHouseTrace(json) {
|
|
|
|
let min_time_us = Number.MAX_VALUE;
|
|
|
|
for (let i = 0; i < json.data.length; i++) {
|
|
|
|
let span = json.data[i];
|
|
|
|
min_time_us = Math.min(min_time_us, +span.start_time_us);
|
|
|
|
}
|
|
|
|
|
|
|
|
let max_time_ms = 0;
|
|
|
|
function convertTime(us) {
|
|
|
|
let value = (us - min_time_us) / 1000;
|
|
|
|
max_time_ms = Math.max(max_time_ms, value);
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
|
|
|
function strHash(str) {
|
|
|
|
var hash = 0;
|
|
|
|
if (str.length === 0)
|
|
|
|
return hash;
|
|
|
|
for (let i = 0; i < str.length; i++) {
|
|
|
|
hash = ((hash << 5) - hash) + str.charCodeAt(i);
|
|
|
|
hash |= 0; // Convert to 32bit integer
|
|
|
|
}
|
|
|
|
if (hash < 0)
|
|
|
|
hash = -hash;
|
|
|
|
return hash;
|
|
|
|
}
|
|
|
|
|
|
|
|
let result = [];
|
2022-06-17 16:02:03 +00:00
|
|
|
let bands = new Set();
|
2022-06-16 09:35:13 +00:00
|
|
|
for (let i = 0; i < json.data.length; i++) {
|
|
|
|
let span = json.data[i];
|
2022-06-17 16:02:03 +00:00
|
|
|
let band = Object.values(span.group).join(' ');
|
|
|
|
bands.add(band);
|
2022-06-16 09:35:13 +00:00
|
|
|
result.push({
|
|
|
|
t1: convertTime(+span.start_time_us),
|
|
|
|
t2: convertTime(+span.finish_time_us),
|
2022-06-17 16:02:03 +00:00
|
|
|
band,
|
2022-06-16 09:35:13 +00:00
|
|
|
color: d3.interpolateRainbow((strHash(span.color) % 256) / 256),
|
|
|
|
text: span.operation_name
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-06-17 16:02:03 +00:00
|
|
|
result.bands = bands;
|
|
|
|
result.max_time_ms = max_time_ms;
|
2022-06-16 09:35:13 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
fetchData(); // do not fetch, just draw example_json w/o parsing
|
2022-06-17 07:30:08 +00:00
|
|
|
//fetchData("your-traces.json", parseClickHouseTrace);
|
2022-06-16 09:35:13 +00:00
|
|
|
</script>
|