77 < title > Trace Gantt</ title >
88 < link rel ="stylesheet " href ="css/bootstrap.min.css ">
99 < link rel ="stylesheet " href ="css/d3-gantt.css ">
10+ < style >
11+ # hourglass
12+ {
13+ display : none;
14+ font-size : 110% ;
15+ color : # 888 ;
16+ animation : hourglass-animation 2s linear infinite;
17+ }
18+
19+ @keyframes hourglass-animation {
20+ 0% { transform : none; }
21+ 25% { transform : rotate (180deg ); }
22+ 50% { transform : rotate (180deg ); }
23+ 75% { transform : rotate (360deg ); }
24+ 100% { transform : rotate (360deg ); }
25+ }
26+ </ style >
1027</ head >
1128< body >
1229 < script language ="javascript " type ="text/javascript " src ="js/jquery.min.js "> </ script >
1330 < script language ="javascript " type ="text/javascript " src ="js/bootstrap.min.js "> </ script >
1431 < script language ="javascript " type ="text/javascript " src ="js/d3.v4.min.js "> </ script >
1532 < script language ="javascript " type ="text/javascript " src ="js/d3-tip-0.8.0-alpha.1.js "> </ script >
1633 < script language ="javascript " type ="text/javascript " src ="js/d3-gantt.js "> </ script >
34+ < script language ="javascript " type ="text/javascript " src ="js/query-clickhouse.js "> </ script >
1735
1836 < nav class ="navbar navbar-default ">
1937 < div class ="container-fluid ">
3654 < div class ="modal-dialog " role ="document ">
3755 < div class ="modal-content ">
3856 < div class ="modal-header ">
39- < button type ="button " class ="close " data-dismiss ="modal " aria-label ="Close "> < span
40- aria-hidden ="true "> ×</ span > </ button >
41- < h4 class ="modal-title " id ="loadModalLabel "> Load Trace JSON</ h4 >
57+ < button type ="button " class ="close " data-dismiss ="modal " aria-label ="Close ">
58+ < span aria-hidden ="true "> ×</ span >
59+ </ button >
60+ < h4 class ="modal-title " id ="loadModalLabel "> Load Data</ h4 >
4261 </ div >
4362 < div class ="modal-body ">
44- < input type ="file " id ="loadFiles " value ="Load " /> < br />
63+ <!-- Nav tabs -->
64+ < ul class ="nav nav-tabs " role ="tablist ">
65+ < li class ="active ">
66+ < a href ="#tabFileUpload " role ="tab " data-toggle ="tab "> Upload JSON</ a >
67+ </ li >
68+ < li >
69+ < a href ="#tabClickhouse " role ="tab " data-toggle ="tab "> From ClickHouse</ a >
70+ </ li >
71+ </ ul >
72+
73+ <!-- Tab panes -->
74+ < div class ="tab-content " style ="margin-top: 15px; ">
75+ <!-- File Upload Tab -->
76+ < div class ="tab-pane active " id ="tabFileUpload ">
77+ < input type ="file " id ="loadFiles " value ="Load " />
78+ </ div >
79+
80+ <!-- ClickHouse Query Tab -->
81+ < div class ="tab-pane " id ="tabClickhouse ">
82+ < form id ="params ">
83+ < div id ="connection-params " style ="margin-bottom: 10px; ">
84+ < input spellcheck ="false " id ="url " type ="text " value ="http://localhost:8123 " placeholder ="URL " class ="form-control " style ="margin-bottom: 5px; " />
85+ < input spellcheck ="false " id ="user " type ="text " value ="" placeholder ="user " class ="form-control " style ="margin-bottom: 5px; " />
86+ < input spellcheck ="false " id ="password " type ="password " placeholder ="password " value ="" class ="form-control " style ="margin-bottom: 5px; " />
87+ </ div >
88+ < textarea spellcheck ="false " id ="query " rows ="26 " class ="form-control " placeholder ="SQL Query "> WITH '__PUT_YOUR_QUERY_ID_HERE__' AS my_query_id
89+ SELECT
90+ ('thread #' || leftPad(attribute['clickhouse.thread_id'], 6, '0')) AS group,
91+ replaceRegexpOne(operation_name, '(.*)_.*', '\\1') AS operation_name,
92+ start_time_us,
93+ finish_time_us,
94+ sipHash64(operation_name) AS color,
95+ attribute
96+ FROM system.opentelemetry_span_log
97+ WHERE 1
98+ AND trace_id IN (
99+ SELECT trace_id
100+ FROM system.opentelemetry_span_log
101+ WHERE (attribute['clickhouse.query_id']) IN (SELECT query_id FROM system.query_log WHERE initial_query_id = my_query_id)
102+ )
103+ AND operation_name !='query'
104+ AND operation_name NOT LIKE '%Pipeline%'
105+ AND operation_name NOT LIKE 'TCPHandler%'
106+ AND operation_name NOT LIKE 'Query%'
107+ ORDER BY
108+ group ASC,
109+ parent_span_id ASC,
110+ start_time_us ASC
111+ SETTINGS output_format_json_named_tuples_as_objects = 1, skip_unavailable_shards = 1</ textarea >
112+ < div class ="checkbox " style ="margin-top: 10px; ">
113+ < label >
114+ < input type ="checkbox " id ="flushLogsCheckbox " checked > Run SYSTEM FLUSH LOGS
115+ </ label >
116+ </ div >
117+ < div id ="query-error " style ="color: red; margin-top: 10px; display: none; "> </ div >
118+ </ form >
119+ </ div >
120+ </ div >
45121 </ div >
122+
46123 < div class ="modal-footer ">
47124 < button type ="button " class ="btn btn-default " data-dismiss ="modal "> Cancel</ button >
48125 < button type ="button " class ="btn btn-primary " id ="btnDoLoad "> Load</ button >
126+ < button type ="button " class ="btn btn-success " id ="btnQueryClickhouse " style ="display: none; "> Query</ button >
127+ < div id ="hourglass "> ⧗</ div >
49128 </ div >
50129 </ div >
51130 </ div >
@@ -163,6 +242,71 @@ <h4 class="modal-title" id="findModalLabel">Span Filter</h4>
163242 }
164243 }
165244
245+ // Show correct button depending on tab
246+ $ ( '#loadModal' ) . on ( 'shown.bs.modal' , function ( ) {
247+ $ ( 'a[data-toggle="tab"]' ) . on ( 'shown.bs.tab' , function ( e ) {
248+ if ( $ ( e . target ) . attr ( 'href' ) === '#tabClickhouse' ) {
249+ $ ( '#btnDoLoad' ) . hide ( ) ;
250+ $ ( '#btnQueryClickhouse' ) . show ( ) ;
251+ } else {
252+ $ ( '#btnDoLoad' ) . show ( ) ;
253+ $ ( '#btnQueryClickhouse' ) . hide ( ) ;
254+ }
255+ } ) ;
256+ } ) ;
257+
258+ // Handle ClickHouse query
259+ let query_controller = null ;
260+ $ ( '#btnQueryClickhouse' ) . on ( 'click' , async function ( ) {
261+ if ( query_controller == null ) {
262+ let rows = [ ] ;
263+ let error = '' ;
264+ const errorDiv = document . getElementById ( 'query-error' ) ;
265+ errorDiv . style . display = 'none' ;
266+ errorDiv . textContent = '' ;
267+ document . getElementById ( 'hourglass' ) . style . display = 'inline-block' ;
268+ $ ( '#btnQueryClickhouse' ) . text ( "Stop" ) ;
269+ if ( $ ( '#flushLogsCheckbox' ) . is ( ':checked' ) ) {
270+ query_controller = new AbortController ( ) ;
271+ await queryClickHouse ( {
272+ host : $ ( '#url' ) . val ( ) ,
273+ user : $ ( '#user' ) . val ( ) ,
274+ password : $ ( '#password' ) . val ( ) ,
275+ query : "SYSTEM FLUSH LOGS" ,
276+ for_each_row : ( data ) => { } ,
277+ on_error : ( errorMsg ) => error = errorMsg ,
278+ controller : query_controller
279+ } ) ;
280+ }
281+ if ( error == '' ) {
282+ query_controller = new AbortController ( ) ;
283+ await queryClickHouse ( {
284+ host : $ ( '#url' ) . val ( ) ,
285+ user : $ ( '#user' ) . val ( ) ,
286+ password : $ ( '#password' ) . val ( ) ,
287+ query : $ ( '#query' ) . val ( ) ,
288+ for_each_row : ( data ) => rows . push ( data ) ,
289+ on_error : ( errorMsg ) => error = errorMsg ,
290+ controller : query_controller
291+ } ) ;
292+ }
293+ query_controller = null ;
294+ $ ( '#btnQueryClickhouse' ) . text ( "Query" ) ;
295+ document . getElementById ( 'hourglass' ) . style . display = 'none' ;
296+
297+ if ( error != '' ) {
298+ errorDiv . textContent = error ;
299+ errorDiv . style . display = 'block' ;
300+ } else {
301+ renderChart ( parseClickHouseTrace ( rows ) ) ;
302+ $ ( '#loadModal' ) . modal ( 'hide' ) ;
303+ }
304+ } else { // Cancel query
305+ query_controller . abort ( ) ;
306+ }
307+ } ) ;
308+
309+ // Handle uploaded JSON
166310 $ ( "#btnDoLoad" ) . click ( function ( ) {
167311 let element = document . getElementById ( 'loadFiles' ) ;
168312 let files = element . files ;
@@ -172,7 +316,7 @@ <h4 class="modal-title" id="findModalLabel">Span Filter</h4>
172316 let fr = new FileReader ( ) ;
173317 fr . onload = function ( e ) {
174318 $ ( "#errmsg" ) . hide ( ) ;
175- renderChart ( parseClickHouseTrace ( JSON . parse ( e . target . result ) ) ) ;
319+ renderChart ( parseClickHouseTrace ( JSON . parse ( e . target . result ) . data ) ) ;
176320 }
177321 fr . readAsText ( files . item ( 0 ) ) ;
178322 element . value = '' ;
@@ -197,10 +341,10 @@ <h4 class="modal-title" id="findModalLabel">Span Filter</h4>
197341 doFindPattern ( ) ;
198342 } ) ;
199343
200- function parseClickHouseTrace ( json ) {
344+ function parseClickHouseTrace ( rows ) {
201345 let min_time_us = Number . MAX_VALUE ;
202- for ( let i = 0 ; i < json . data . length ; i ++ ) {
203- let span = json . data [ i ] ;
346+ for ( let i = 0 ; i < rows . length ; i ++ ) {
347+ let span = rows [ i ] ;
204348 min_time_us = Math . min ( min_time_us , + span . start_time_us ) ;
205349 }
206350
@@ -226,8 +370,8 @@ <h4 class="modal-title" id="findModalLabel">Span Filter</h4>
226370
227371 let result = [ ] ;
228372 let bands = new Set ( ) ;
229- for ( let i = 0 ; i < json . data . length ; i ++ ) {
230- let span = json . data [ i ] ;
373+ for ( let i = 0 ; i < rows . length ; i ++ ) {
374+ let span = rows [ i ] ;
231375 let band = Object . values ( span . group ) . join ( ' ' ) ;
232376 bands . add ( band ) ;
233377 result . push ( {
0 commit comments