1#! /usr/bin/perl
2#
3# Copyright © 2017 Intel Corporation
4#
5# Permission is hereby granted, free of charge, to any person obtaining a
6# copy of this software and associated documentation files (the "Software"),
7# to deal in the Software without restriction, including without limitation
8# the rights to use, copy, modify, merge, publish, distribute, sublicense,
9# and/or sell copies of the Software, and to permit persons to whom the
10# Software is furnished to do so, subject to the following conditions:
11#
12# The above copyright notice and this permission notice (including the next
13# paragraph) shall be included in all copies or substantial portions of the
14# Software.
15#
16# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
19# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22# IN THE SOFTWARE.
23#
24
25use strict;
26use warnings;
27use 5.010;
28
29my $gid = 0;
30my (%db, %vdb, %queue, %submit, %notify, %rings, %ctxdb, %ringmap, %reqwait,
31    %ctxtimelines);
32my (%cids, %ctxmap);
33my $cid = 0;
34my %queues;
35my @freqs;
36
37use constant VENG => '255:254';
38
39my $max_requests = 1000;
40my $width_us = 32000;
41my $correct_durations = 0;
42my %ignore_ring;
43my %skip_box;
44my $html = 0;
45my $trace = 0;
46my $avg_delay_stats = 0;
47my $gpu_timeline = 0;
48my $colour_contexts = 0;
49
50my @args;
51
52sub arg_help
53{
54	return unless scalar(@_);
55
56	if ($_[0] eq '--help' or $_[0] eq '-h') {
57		shift @_;
58print <<ENDHELP;
59Notes:
60
61   The tool parse the output generated by the 'perf script' command after the
62   correct set of i915 tracepoints have been collected via perf record.
63
64   To collect the data:
65
66	./trace.pl --trace [command-to-be-profiled]
67
68   The above will invoke perf record, or alternatively it can be done directly:
69
70	perf record -a -c 1 -e i915:intel_gpu_freq_change, \
71			       i915:i915_request_add, \
72			       i915:i915_request_submit, \
73			       i915:i915_request_in, \
74			       i915:i915_request_out, \
75			       dma_fence:dma_fence_signaled, \
76			       i915:i915_request_wait_begin, \
77			       i915:i915_request_wait_end \
78			       [command-to-be-profiled]
79
80   Then create the log file with:
81
82	perf script >trace.log
83
84   This file in turn should be piped into this tool which will generate some
85   statistics out of it, or if --html was given HTML output.
86
87   HTML can be viewed from a directory containing the 'vis' JavaScript module.
88   On Ubuntu this can be installed like this:
89
90	apt-get install npm
91	npm install vis
92
93Usage:
94   trace.pl <options> <input-file >output-file
95
96      --help / -h			This help text
97      --max-items=num / -m num		Maximum number of boxes to put on the
98					timeline. More boxes means more work for
99					the JavaScript engine in the browser.
100      --zoom-width-ms=ms / -z ms	Width of the initial timeline zoom
101      --split-requests / -s		Try to split out request which were
102					submitted together due coalescing in the
103					driver. May not be 100% accurate and may
104					influence the per-engine statistics so
105					use with care.
106      --ignore-ring=id / -i id		Ignore ring with the numerical id when
107					parsing the log (enum intel_engine_id).
108					Can be given multiple times.
109      --skip-box=name / -x name		Do not put a certain type of a box on
110					the timeline. One of: queue, ready,
111					execute and ctxsave.
112					Can be given multiple times.
113      --html				Generate HTML output.
114      --trace cmd			Trace the following command.
115      --avg-delay-stats			Print average delay stats.
116      --gpu-timeline			Draw overall GPU busy timeline.
117      --colour-contexts / -c		Use different colours for different
118					context execution boxes.
119ENDHELP
120
121		exit 0;
122	}
123
124	return @_;
125}
126
127sub arg_html
128{
129	return unless scalar(@_);
130
131	if ($_[0] eq '--html') {
132		shift @_;
133		$html = 1;
134	}
135
136	return @_;
137}
138
139sub arg_avg_delay_stats
140{
141	return unless scalar(@_);
142
143	if ($_[0] eq '--avg-delay-stats') {
144		shift @_;
145		$avg_delay_stats = 1;
146	}
147
148	return @_;
149}
150
151sub arg_gpu_timeline
152{
153	return unless scalar(@_);
154
155	if ($_[0] eq '--gpu-timeline') {
156		shift @_;
157		$gpu_timeline = 1;
158	}
159
160	return @_;
161}
162
163sub arg_trace
164{
165	my @events = ( 'i915:intel_gpu_freq_change',
166		       'i915:i915_request_add',
167		       'i915:i915_request_submit',
168		       'i915:i915_request_in',
169		       'i915:i915_request_out',
170		       'dma_fence:dma_fence_signaled',
171		       'i915:i915_request_wait_begin',
172		       'i915:i915_request_wait_end' );
173
174	return unless scalar(@_);
175
176	if ($_[0] eq '--trace') {
177		shift @_;
178
179		unshift @_, '--';
180		unshift @_, join(',', @events);
181		unshift @_, ('perf', 'record', '-a', '-c', '1', '-q', '-o', 'perf.data', '-e');
182
183		exec @_;
184	}
185
186	return @_;
187}
188
189sub arg_max_requests
190{
191	my $val;
192
193	return unless scalar(@_);
194
195	if ($_[0] eq '--max-requests' or $_[0] eq '-m') {
196		shift @_;
197		$val = shift @_;
198	} elsif ($_[0] =~ /--max-requests=(\d+)/) {
199		shift @_;
200		$val = $1;
201	}
202
203	$max_requests = int($val) if defined $val;
204
205	return @_;
206}
207
208sub arg_zoom_width
209{
210	my $val;
211
212	return unless scalar(@_);
213
214	if ($_[0] eq '--zoom-width-ms' or $_[0] eq '-z') {
215		shift @_;
216		$val = shift @_;
217	} elsif ($_[0] =~ /--zoom-width-ms=(\d+)/) {
218		shift @_;
219		$val = $1;
220	}
221
222	$width_us = int($val) * 1000 if defined $val;
223
224	return @_;
225}
226
227sub arg_split_requests
228{
229	return unless scalar(@_);
230
231	if ($_[0] eq '--split-requests' or $_[0] eq '-s') {
232		shift @_;
233		$correct_durations = 1;
234	}
235
236	return @_;
237}
238
239sub arg_ignore_ring
240{
241	my $val;
242
243	return unless scalar(@_);
244
245	if ($_[0] eq '--ignore-ring' or $_[0] eq '-i') {
246		shift @_;
247		$val = shift @_;
248	} elsif ($_[0] =~ /--ignore-ring=(\d+)/) {
249		shift @_;
250		$val = $1;
251	}
252
253	$ignore_ring{$val} = 1 if defined $val;
254
255	return @_;
256}
257
258sub arg_skip_box
259{
260	my $val;
261
262	return unless scalar(@_);
263
264	if ($_[0] eq '--skip-box' or $_[0] eq '-x') {
265		shift @_;
266		$val = shift @_;
267	} elsif ($_[0] =~ /--skip-box=(\d+)/) {
268		shift @_;
269		$val = $1;
270	}
271
272	$skip_box{$val} = 1 if defined $val;
273
274	return @_;
275}
276
277sub arg_colour_contexts
278{
279	return unless scalar(@_);
280
281	if ($_[0] eq '--colour-contexts' or
282	    $_[0] eq '--color-contexts' or
283	    $_[0] eq '-c') {
284		shift @_;
285		$colour_contexts = 1;
286	}
287
288	return @_;
289}
290
291@args = @ARGV;
292while (@args) {
293	my $left = scalar(@args);
294
295	@args = arg_help(@args);
296	@args = arg_html(@args);
297	@args = arg_avg_delay_stats(@args);
298	@args = arg_gpu_timeline(@args);
299	@args = arg_trace(@args);
300	@args = arg_max_requests(@args);
301	@args = arg_zoom_width(@args);
302	@args = arg_split_requests(@args);
303	@args = arg_ignore_ring(@args);
304	@args = arg_skip_box(@args);
305	@args = arg_colour_contexts(@args);
306
307	last if $left == scalar(@args);
308}
309
310die if scalar(@args);
311
312@ARGV = @args;
313
314sub db_key
315{
316	my ($ring, $ctx, $seqno) = @_;
317
318	return $ring . '/' . $ctx . '/' . $seqno;
319}
320
321sub notify_key
322{
323	my ($ctx, $seqno) = @_;
324
325	return $ctx . '/' . $seqno;
326}
327
328sub sanitize_ctx
329{
330	my ($ctx, $ring) = @_;
331
332	if (exists $ctxdb{$ctx} and $ctxdb{$ctx} > 1) {
333		return $ctx . '.' . $ctxdb{$ctx};
334	} else {
335		return $ctx;
336	}
337}
338
339sub is_veng
340{
341	my ($class, $instance) = split ':', shift;
342
343	return $instance eq '254';
344}
345
346# Main input loop - parse lines and build the internal representation of the
347# trace using a hash of requests and some auxilliary data structures.
348my $prev_freq = 0;
349my $prev_freq_ts = 0;
350while (<>) {
351	my @fields;
352	my $tp_name;
353	my %tp;
354	my ($time, $ctx, $ring, $seqno, $orig_ctx, $key);
355
356	chomp;
357	@fields = split ' ';
358
359	chop $fields[3];
360	$time = int($fields[3] * 1000000.0 + 0.5);
361
362	$tp_name = $fields[4];
363
364	splice @fields, 0, 5;
365
366	foreach my $f (@fields) {
367		my ($k, $v);
368
369		next unless $f =~ m/=/;
370		($k, $v) = ($`, $');
371		$k = 'global' if $k eq 'global_seqno';
372		chop $v if substr($v, -1, 1) eq ',';
373		$tp{$k} = $v;
374
375		$tp{'ring'} = $tp{'engine'} if $k eq 'engine';
376	}
377
378	next if exists $tp{'ring'} and exists $ignore_ring{$tp{'ring'}};
379
380	if (exists $tp{'ring'} and exists $tp{'seqno'}) {
381		$ring = $tp{'ring'};
382		$seqno = $tp{'seqno'};
383
384		if (exists $tp{'ctx'}) {
385			$ctx = $tp{'ctx'};
386			$orig_ctx = $ctx;
387			$ctx = sanitize_ctx($ctx, $ring);
388			$ring = VENG if is_veng($ring);
389			$key = db_key($ring, $ctx, $seqno);
390		}
391	}
392
393	if ($tp_name eq 'i915:i915_request_wait_begin:') {
394		my %rw;
395
396		next if exists $reqwait{$key};
397		die if $ring eq VENG and not exists $queues{$ctx};
398
399		$rw{'key'} = $key;
400		$rw{'ring'} = $ring;
401		$rw{'seqno'} = $seqno;
402		$rw{'ctx'} = $ctx;
403		$rw{'start'} = $time;
404		$reqwait{$key} = \%rw;
405	} elsif ($tp_name eq 'i915:i915_request_wait_end:') {
406		die if $ring eq VENG and not exists $queues{$ctx};
407
408		if (exists $reqwait{$key}) {
409			$reqwait{$key}->{'end'} = $time;
410		} else { # Virtual engine
411			my $vkey = db_key(VENG, $ctx, $seqno);
412
413			die unless exists $reqwait{$vkey};
414
415			# If the wait started on the virtual engine, attribute
416			# it to it completely.
417			$reqwait{$vkey}->{'end'} = $time;
418		}
419	} elsif ($tp_name eq 'i915:i915_request_add:') {
420		if (exists $queue{$key}) {
421			$ctxdb{$orig_ctx}++;
422			$ctx = sanitize_ctx($orig_ctx, $ring);
423			$key = db_key($ring, $ctx, $seqno);
424		} else {
425			$ctxdb{$orig_ctx} = 1;
426		}
427
428		$queue{$key} = $time;
429		if ($ring eq VENG and not exists $queues{$ctx}) {
430			$queues{$ctx} = 1 ;
431			$cids{$ctx} = $cid++;
432			$ctxmap{$cids{$ctx}} = $ctx;
433		}
434	} elsif ($tp_name eq 'i915:i915_request_submit:') {
435		die if exists $submit{$key};
436		die unless exists $queue{$key};
437		die if $ring eq VENG and not exists $queues{$ctx};
438
439		$submit{$key} = $time;
440	} elsif ($tp_name eq 'i915:i915_request_in:') {
441		my ($q, $s);
442		my %req;
443
444		# preemption
445		delete $db{$key} if exists $db{$key};
446
447		unless (exists $queue{$key}) {
448			# Virtual engine
449			my $vkey = db_key(VENG, $ctx, $seqno);
450			my %req;
451
452			die unless exists $queues{$ctx};
453			die unless exists $queue{$vkey};
454			die unless exists $submit{$vkey};
455
456			# Create separate request record on the queue timeline
457			$q = $queue{$vkey};
458			$s = $submit{$vkey};
459			$req{'queue'} = $q;
460			$req{'submit'} = $s;
461			$req{'start'} = $time;
462			$req{'end'} = $time;
463			$req{'ring'} = VENG;
464			$req{'seqno'} = $seqno;
465			$req{'ctx'} = $ctx;
466			$req{'name'} = $ctx . '/' . $seqno;
467			$req{'global'} = $tp{'global'};
468			$req{'port'} = $tp{'port'};
469
470			$vdb{$vkey} = \%req;
471		} else {
472			$q = $queue{$key};
473			$s = $submit{$key};
474		}
475
476		$req{'start'} = $time;
477		$req{'ring'} = $ring;
478		$req{'seqno'} = $seqno;
479		$req{'ctx'} = $ctx;
480		$ctxtimelines{$ctx . '/' . $ring} = 1;
481		$req{'name'} = $ctx . '/' . $seqno;
482		$req{'global'} = $tp{'global'};
483		$req{'port'} = $tp{'port'};
484		$req{'queue'} = $q;
485		$req{'submit'} = $s;
486		$req{'virtual'} = 1 if exists $queues{$ctx};
487		$rings{$ring} = $gid++ unless exists $rings{$ring};
488		$ringmap{$rings{$ring}} = $ring;
489		$db{$key} = \%req;
490	} elsif ($tp_name eq 'i915:i915_request_out:') {
491		if ($tp{'completed?'}) {
492			my $nkey;
493
494			die unless exists $db{$key};
495			die unless exists $db{$key}->{'start'};
496			die if exists $db{$key}->{'end'};
497
498			$nkey = notify_key($ctx, $seqno);
499
500			$db{$key}->{'end'} = $time;
501			$db{$key}->{'notify'} = $notify{$nkey}
502						if exists $notify{$nkey};
503		} else {
504			delete $db{$key};
505		}
506	} elsif ($tp_name eq 'dma_fence:dma_fence_signaled:') {
507		my $nkey;
508
509		next unless $tp{'driver'} eq 'i915' and
510			    $tp{'timeline'} eq 'signaled';
511
512		$nkey = notify_key($tp{'context'}, $tp{'seqno'});
513
514		die if exists $notify{$nkey};
515		$notify{$nkey} = $time unless exists $notify{$nkey};
516	} elsif ($tp_name eq 'i915:intel_gpu_freq_change:') {
517		push @freqs, [$prev_freq_ts, $time, $prev_freq] if $prev_freq;
518		$prev_freq_ts = $time;
519		$prev_freq = $tp{'new_freq'};
520	}
521}
522
523# Sanitation pass to fixup up out of order notify and context complete, and to
524# find the largest seqno to be used for timeline sorting purposes.
525my $max_seqno = 0;
526foreach my $key (keys %db) {
527	my $nkey = notify_key($db{$key}->{'ctx'}, $db{$key}->{'seqno'});
528
529	die unless exists $db{$key}->{'start'};
530
531	$max_seqno = $db{$key}->{'seqno'} if $db{$key}->{'seqno'} > $max_seqno;
532
533	# Notify arrived after context complete?
534	$db{$key}->{'notify'} = $notify{$nkey} if not exists $db{$key}->{'notify'}
535						  and exists $notify{$nkey};
536
537	# No notify but we have end?
538	$db{$key}->{'notify'} = $db{$key}->{'end'} if exists $db{$key}->{'end'} and
539						      not exists $db{$key}->{'notify'};
540
541	# If user interrupt arrived out of order push it back to be no later
542	# than request out.
543	if (exists $db{$key}->{'end'} and exists $db{$key}->{'notify'} and
544	    $db{$key}->{'notify'} > $db{$key}->{'end'}) {
545		$db{$key}->{'notify'} = $db{$key}->{'end'};
546	}
547}
548
549my $key_count = scalar(keys %db);
550
551my %engine_timelines;
552
553sub sortStart {
554	my $as = $db{$a}->{'start'};
555	my $bs = $db{$b}->{'start'};
556	my $val;
557
558	$val = $as <=> $bs;
559	$val = $a cmp $b if $val == 0;
560
561	return $val;
562}
563
564sub get_engine_timeline {
565	my ($ring) = @_;
566	my @timeline;
567
568	return $engine_timelines{$ring} if exists $engine_timelines{$ring};
569
570	@timeline = grep { $db{$_}->{'ring'} eq $ring } keys %db;
571	@timeline = sort sortStart @timeline;
572	$engine_timelines{$ring} = \@timeline;
573
574	return \@timeline;
575}
576
577# Fix up coalesced requests by ending them either when the following same
578# context request with known end ends, or when a different context starts.
579foreach my $gid (sort keys %rings) {
580	my $ring = $ringmap{$rings{$gid}};
581	my $timeline = get_engine_timeline($ring);
582	my $last_complete = -1;
583	my $last_ctx = -1;
584	my $complete;
585
586	foreach my $pos (0..$#{$timeline}) {
587		my $key = @{$timeline}[$pos];
588		my ($ctx, $end);
589
590		next if exists $db{$key}->{'end'};
591
592		$db{$key}->{'no-end'} = 1;
593		$ctx = $db{$key}->{'ctx'};
594
595		if ($pos > $last_complete or $ctx != $last_ctx) {
596			my $next = $pos;
597
598			undef $complete;
599
600			while ($next < $#{$timeline}) {
601				my $next_key = ${$timeline}[++$next];
602				if ($ctx == $db{$next_key}->{'ctx'} and
603				    exists $db{$next_key}->{'end'}) {
604					$last_ctx = $db{$next_key}->{'ctx'};
605					$last_complete = $next;
606					$complete = $next_key;
607					last;
608				}
609			}
610		}
611
612		if (defined $complete) {
613			if ($ctx == $db{$complete}->{'ctx'}) {
614				$end = $db{$complete}->{'end'};
615			} else {
616				$end = $db{$complete}->{'start'};
617			}
618		} else {
619			# No next submission. Use notify if available or give up.
620			if (exists $db{$key}->{'notify'}) {
621				$end = $db{$key}->{'notify'};
622			} else {
623				$end = $db{$key}->{'start'};
624				$db{$key}->{'incomplete'} = 1;
625			}
626		}
627
628		unless (exists $db{$key}->{'notify'}) {
629			$db{$key}->{'notify'} = $end;
630			$db{$key}->{'no-notify'} = 1;
631		}
632		$db{$key}->{'end'} = $end;
633		$db{$key}->{'notify'} = $end if $db{$key}->{'notify'} > $end;
634	}
635}
636
637my $re_sort = 1;
638my @sorted_keys;
639
640sub maybe_sort_keys
641{
642	if ($re_sort) {
643		@sorted_keys = sort sortStart keys %db;
644		$re_sort = 0;
645		die "Database changed size?!" unless scalar(@sorted_keys) ==
646						     $key_count;
647	}
648}
649
650maybe_sort_keys();
651
652my %ctx_timelines;
653
654sub sortContext {
655	my $as = $db{$a}->{'seqno'};
656	my $bs = $db{$b}->{'seqno'};
657	my $val;
658
659	$val = $as <=> $bs;
660
661	die if $val == 0;
662
663	return $val;
664}
665
666sub get_ctx_timeline {
667	my ($ctx, $ring, $key) = @_;
668	my @timeline;
669
670	return $ctx_timelines{$key} if exists $ctx_timelines{$key};
671
672	@timeline = grep { $db{$_}->{'ring'} eq $ring and
673			   $db{$_}->{'ctx'} == $ctx } @sorted_keys;
674	# FIXME seqno restart
675	@timeline = sort sortContext @timeline;
676
677	$ctx_timelines{$key} = \@timeline;
678
679	return \@timeline;
680}
681
682# Split out merged batches if requested.
683if ($correct_durations) {
684	# Shift !port0 requests start time to after the previous context on the
685	# same timeline has finished.
686	foreach my $gid (sort keys %rings) {
687		my $ring = $ringmap{$rings{$gid}};
688		my $timeline = get_engine_timeline($ring);
689		my $complete;
690
691		foreach my $pos (0..$#{$timeline}) {
692			my $key = @{$timeline}[$pos];
693			my $prev = $complete;
694			my $pkey;
695
696			$complete = $key unless exists $db{$key}->{'no-end'};
697			$pkey = $complete;
698
699			next if $db{$key}->{'port'} == 0;
700
701			$pkey = $prev if $complete eq $key;
702
703			die unless defined $pkey;
704
705			$db{$key}->{'start'} = $db{$pkey}->{'end'};
706			$db{$key}->{'start'} = $db{$pkey}->{'notify'} if $db{$key}->{'start'} > $db{$key}->{'end'};
707
708			die if $db{$key}->{'start'} > $db{$key}->{'end'};
709
710			$re_sort = 1;
711		}
712	}
713
714	maybe_sort_keys();
715
716	# Batch with no-end (no request_out) means it was submitted as part of
717	# coalesced context. This means it's start time should be set to the end
718	# time of a following request on this context timeline.
719	foreach my $tkey (sort keys %ctxtimelines) {
720		my ($ctx, $ring) = split '/', $tkey;
721		my $timeline = get_ctx_timeline($ctx, $ring, $tkey);
722		my $last_complete = -1;
723		my $complete;
724
725		foreach my $pos (0..$#{$timeline}) {
726			my $key = @{$timeline}[$pos];
727			my $next_key;
728
729			next unless exists $db{$key}->{'no-end'};
730			last if $pos == $#{$timeline};
731
732			# Shift following request to start after the current
733			# one, but only if that wouldn't make it zero duration,
734			# which would indicate notify arrived after context
735			# complete.
736			$next_key = ${$timeline}[$pos + 1];
737			if (exists $db{$key}->{'notify'} and
738			    $db{$key}->{'notify'} < $db{$key}->{'end'}) {
739				$db{$next_key}->{'engine-start'} = $db{$next_key}->{'start'};
740				$db{$next_key}->{'start'} = $db{$key}->{'notify'};
741				$re_sort = 1;
742			}
743		}
744	}
745}
746
747maybe_sort_keys();
748
749# GPU time accounting
750my (%running, %runnable, %queued, %batch_avg, %batch_total_avg, %batch_count);
751my (%submit_avg, %execute_avg, %ctxsave_avg);
752
753my $last_ts = 0;
754my $first_ts;
755my $min_ctx;
756
757foreach my $key (@sorted_keys) {
758	my $ring = $db{$key}->{'ring'};
759	my $end = $db{$key}->{'end'};
760	my $start = $db{$key}->{'start'};
761	my $engine_start = $db{$key}->{'engine_start'};
762	my $notify = $db{$key}->{'notify'};
763
764	$first_ts = $db{$key}->{'queue'} if not defined $first_ts or $db{$key}->{'queue'} < $first_ts;
765	$last_ts = $end if $end > $last_ts;
766	$min_ctx = $db{$key}->{'ctx'} if not defined $min_ctx or
767					 $db{$key}->{'ctx'} < $min_ctx;
768
769	unless (exists $db{$key}->{'no-end'}) {
770		$db{$key}->{'context-complete-delay'} = $end - $notify;
771	} else {
772		$db{$key}->{'context-complete-delay'} = 0;
773	}
774
775	$engine_start = $db{$key}->{'start'} unless defined $engine_start;
776	$db{$key}->{'execute-delay'} = $engine_start - $db{$key}->{'submit'};
777	$db{$key}->{'submit-delay'} = $db{$key}->{'submit'} - $db{$key}->{'queue'};
778	unless (exists $db{$key}->{'no-notify'}) {
779		$db{$key}->{'duration'} = $notify - $start;
780	} else {
781		$db{$key}->{'duration'} = 0;
782	}
783
784	$running{$ring} += $end - $start if $correct_durations or
785					    not exists $db{$key}->{'no-end'};
786	unless (exists $db{$key}->{'virtual'}) {
787		$runnable{$ring} += $db{$key}->{'execute-delay'};
788		$queued{$ring} += $start - $db{$key}->{'execute-delay'} - $db{$key}->{'queue'};
789	}
790
791	$batch_count{$ring}++;
792
793	$batch_avg{$ring} += $db{$key}->{'duration'};
794	$batch_total_avg{$ring} += $end - $start;
795
796	$submit_avg{$ring} += $db{$key}->{'submit-delay'};
797	$execute_avg{$ring} += $db{$key}->{'execute-delay'};
798	$ctxsave_avg{$ring} += $db{$key}->{'context-complete-delay'};
799}
800
801foreach my $ring (sort keys %batch_avg) {
802	$batch_avg{$ring} /= $batch_count{$ring};
803	$batch_total_avg{$ring} /= $batch_count{$ring};
804	$submit_avg{$ring} /= $batch_count{$ring};
805	$execute_avg{$ring} /= $batch_count{$ring};
806	$ctxsave_avg{$ring} /= $batch_count{$ring};
807}
808
809# Calculate engine idle time
810my %flat_busy;
811foreach my $gid (sort keys %rings) {
812	my $ring = $ringmap{$rings{$gid}};
813	my (@s_, @e_);
814
815	# Extract all GPU busy intervals and sort them.
816	foreach my $key (@sorted_keys) {
817		next unless $db{$key}->{'ring'} eq $ring;
818		die if $db{$key}->{'start'} > $db{$key}->{'end'};
819		push @s_, $db{$key}->{'start'};
820		push @e_, $db{$key}->{'end'};
821	}
822
823	die unless $#s_ == $#e_;
824
825	# Flatten the intervals.
826	for my $i (1..$#s_) {
827		last if $i >= @s_; # End of array.
828		die if $e_[$i] < $s_[$i];
829		if ($s_[$i] <= $e_[$i - 1]) {
830			# Current entry overlaps with the previous one. We need
831			# to merge end of the previous interval from the list
832			# with the start of the current one.
833			if ($e_[$i] >= $e_[$i - 1]) {
834				splice @e_, $i - 1, 1;
835			} else {
836				splice @e_, $i, 1;
837			}
838			splice @s_, $i, 1;
839			# Continue with the same element when list got squashed.
840			redo;
841		}
842	}
843
844	# Add up all busy times.
845	my $total = 0;
846	for my $i (0..$#s_) {
847		die if $e_[$i] < $s_[$i];
848
849		$total = $total + ($e_[$i] - $s_[$i]);
850	}
851
852	$flat_busy{$ring} = $total;
853}
854
855# Calculate overall GPU idle time
856my @gpu_intervals;
857my (@s_, @e_);
858
859# Extract all GPU busy intervals and sort them.
860foreach my $key (@sorted_keys) {
861	push @s_, $db{$key}->{'start'};
862	push @e_, $db{$key}->{'end'};
863	die if $db{$key}->{'start'} > $db{$key}->{'end'};
864}
865
866die unless $#s_ == $#e_;
867
868# Flatten the intervals (copy & paste of the flattening loop above)
869for my $i (1..$#s_) {
870	last if $i >= @s_;
871	die if $e_[$i] < $s_[$i];
872	die if $s_[$i] < $s_[$i - 1];
873	if ($s_[$i] <= $e_[$i - 1]) {
874		if ($e_[$i] >= $e_[$i - 1]) {
875			splice @e_, $i - 1, 1;
876		} else {
877			splice @e_, $i, 1;
878		}
879		splice @s_, $i, 1;
880		redo;
881	}
882}
883
884# Add up all busy times.
885my $total = 0;
886for my $i (0..$#s_) {
887	die if $e_[$i] < $s_[$i];
888
889	$total = $total + ($e_[$i] - $s_[$i]);
890}
891
892# Generate data for the GPU timeline if requested
893if ($gpu_timeline) {
894	for my $i (0..$#s_) {
895		push @gpu_intervals, [ $s_[$i], $e_[$i] ];
896	}
897}
898
899$flat_busy{'gpu-busy'} = $total / ($last_ts - $first_ts) * 100.0;
900$flat_busy{'gpu-idle'} = (1.0 - $total / ($last_ts - $first_ts)) * 100.0;
901
902# Add up all request waits per engine
903my %reqw;
904foreach my $key (keys %reqwait) {
905	$reqw{$reqwait{$key}->{'ring'}} += $reqwait{$key}->{'end'} - $reqwait{$key}->{'start'};
906}
907
908# Add up all request waits per virtual engine
909my %vreqw;
910foreach my $key (keys %reqwait) {
911	$vreqw{$reqwait{$key}->{'ctx'}} += $reqwait{$key}->{'end'} - $reqwait{$key}->{'start'};
912}
913
914say sprintf('GPU: %.2f%% idle, %.2f%% busy',
915	     $flat_busy{'gpu-idle'}, $flat_busy{'gpu-busy'}) unless $html;
916
917my $timeline_text = $colour_contexts ?
918		    'per context coloured shading like' : 'box shading like';
919
920my %ctx_colours;
921my $ctx_table;
922
923sub generate_ctx_table
924{
925	my @states = ('queue', 'ready', 'execute', 'ctxsave', 'incomplete');
926	my $max_show = 6;
927	my (@ctxts, @disp_ctxts);
928	my $step;
929
930	if( $colour_contexts ) {
931		@ctxts = sort keys %ctxdb;
932	} else {
933		@ctxts = ($min_ctx);
934	}
935
936	# Limit number of shown context examples
937	$step = int(scalar(@ctxts) / $max_show);
938	if ($step) {
939		foreach my $i (0..$#ctxts) {
940			push @disp_ctxts, $ctxts[$i] unless $i % $step;
941			last if scalar(@disp_ctxts) == $max_show;
942		}
943	} else {
944		@disp_ctxts = @ctxts;
945	}
946
947	$ctx_table .= '<table>';
948
949	foreach my $ctx (@disp_ctxts) {
950		$ctx_table .= "<tr>\n";
951		$ctx_table .= "  <td>Context $ctx</td>\n" if $colour_contexts;
952		foreach my $state (@states) {
953			$ctx_table .= "  <td align='center' valign='middle'><div style='" . box_style($ctx, $state) . " padding-top: 6px; padding-bottom: 6px; padding-left: 6x; padding-right: 6px;'>" . uc($state) . "</div></td>\n";
954		}
955		$ctx_table .= "</tr>\n";
956	}
957
958	$ctx_table .= '</table>';
959}
960
961sub generate_ctx_colours
962{
963	my $num_ctx = keys %ctxdb;
964	my $i = 0;
965
966	foreach my $ctx (sort keys %ctxdb) {
967		$ctx_colours{$ctx} = int(360 / $num_ctx * $i++);
968	}
969}
970
971
972generate_ctx_colours() if $html and $colour_contexts;
973generate_ctx_table() if $html;
974
975print <<ENDHTML if $html;
976<!DOCTYPE HTML>
977<html>
978<head>
979  <title>i915 GT timeline</title>
980
981  <style type="text/css">
982    body, html {
983      font-family: sans-serif;
984    }
985  </style>
986
987  <script src="node_modules/vis/dist/vis.js"></script>
988  <link href="node_modules/vis//dist/vis.css" rel="stylesheet" type="text/css" />
989</head>
990<body>
991<p>
992<b>Timeline request view is $timeline_text:</b>
993<table>
994<tr>
995<td>
996$ctx_table
997</td>
998<td>
999QUEUE = requests executing on the GPU<br>
1000READY = runnable requests waiting for a slot on GPU<br>
1001EXECUTE = requests waiting on fences and dependencies before they are runnable<br>
1002CTXSAVE = GPU saving the context image<br>
1003INCOMPLETE = request of unknown completion time
1004<p>
1005Boxes are in format 'ctx-id/seqno'.
1006</p>
1007<p>
1008Use Ctrl+scroll-action to zoom-in/out and scroll-action or dragging to move around the timeline.
1009</p>
1010<button onclick="toggleStacking()">Toggle overlap stacking</button>
1011</td>
1012</tr>
1013</table>
1014<p>
1015<b>GPU idle: $flat_busy{'gpu-idle'}%</b>
1016<br>
1017<b>GPU busy: $flat_busy{'gpu-busy'}%</b>
1018</p>
1019<div id="visualization"></div>
1020
1021<script type="text/javascript">
1022  var container = document.getElementById('visualization');
1023
1024  var groups = new vis.DataSet([
1025ENDHTML
1026
1027#   var groups = new vis.DataSet([
1028# 	{id: 1, content: 'g0'},
1029# 	{id: 2, content: 'g1'}
1030#   ]);
1031
1032sub html_stats
1033{
1034	my ($stats, $group, $id) = @_;
1035	my $veng = exists $stats->{'virtual'} ? 1 : 0;
1036	my $name;
1037
1038	$name = $veng ? 'Virtual' : 'Ring';
1039	$name .= $group;
1040	$name .= '<br><small><br>';
1041	unless ($veng) {
1042		$name .= sprintf('%.2f', $stats->{'idle'}) . '% idle<br><br>';
1043		$name .= sprintf('%.2f', $stats->{'busy'}) . '% busy<br>';
1044	}
1045	$name .= sprintf('%.2f', $stats->{'runnable'}) . '% runnable<br>';
1046	$name .= sprintf('%.2f', $stats->{'queued'}) . '% queued<br><br>';
1047	$name .= sprintf('%.2f', $stats->{'wait'}) . '% wait<br><br>';
1048	$name .= $stats->{'count'} . ' batches<br>';
1049	unless ($veng) {
1050		$name .= sprintf('%.2f', $stats->{'avg'}) . 'us avg batch<br>';
1051		$name .= sprintf('%.2f', $stats->{'total-avg'}) . 'us avg engine batch<br>';
1052	}
1053	$name .= '</small>';
1054
1055	print "\t{id: $id, content: '$name'},\n";
1056}
1057
1058sub stdio_stats
1059{
1060	my ($stats, $group, $id) = @_;
1061	my $veng = exists $stats->{'virtual'} ? 1 : 0;
1062	my $str;
1063
1064	$str = $veng ? 'Virtual' : 'Ring';
1065	$str .= $group . ': ';
1066	$str .= $stats->{'count'} . ' batches, ';
1067	unless ($veng) {
1068		$str .= sprintf('%.2f (%.2f) avg batch us, ',
1069				$stats->{'avg'}, $stats->{'total-avg'});
1070		$str .= sprintf('%.2f', $stats->{'idle'}) . '% idle, ';
1071		$str .= sprintf('%.2f', $stats->{'busy'}) . '% busy, ';
1072	}
1073
1074	$str .= sprintf('%.2f', $stats->{'runnable'}) . '% runnable, ';
1075	$str .= sprintf('%.2f', $stats->{'queued'}) . '% queued, ';
1076	$str .= sprintf('%.2f', $stats->{'wait'}) . '% wait';
1077
1078	if ($avg_delay_stats and not $veng) {
1079		$str .= ', submit/execute/save-avg=(';
1080		$str .= sprintf('%.2f/%.2f/%.2f)', $stats->{'submit'}, $stats->{'execute'}, $stats->{'save'});
1081	}
1082
1083	say $str;
1084}
1085
1086print "\t{id: 0, content: 'Freq'},\n" if $html;
1087print "\t{id: 1, content: 'GPU'},\n" if $gpu_timeline;
1088
1089my $engine_start_id = $gpu_timeline ? 2 : 1;
1090
1091foreach my $group (sort keys %rings) {
1092	my $name;
1093	my $ring = $ringmap{$rings{$group}};
1094	my $id = $engine_start_id + $rings{$group};
1095	my $elapsed = $last_ts - $first_ts;
1096	my %stats;
1097
1098	$stats{'idle'} = (1.0 - $flat_busy{$ring} / $elapsed) * 100.0;
1099	$stats{'busy'} = $running{$ring} / $elapsed * 100.0;
1100	if (exists $runnable{$ring}) {
1101		$stats{'runnable'} = $runnable{$ring} / $elapsed * 100.0;
1102	} else {
1103		$stats{'runnable'} = 0;
1104	}
1105	if (exists $queued{$ring}) {
1106		$stats{'queued'} = $queued{$ring} / $elapsed * 100.0;
1107	} else {
1108		$stats{'queued'} = 0;
1109	}
1110	$reqw{$ring} = 0 unless exists $reqw{$ring};
1111	$stats{'wait'} = $reqw{$ring} / $elapsed * 100.0;
1112	$stats{'count'} = $batch_count{$ring};
1113	$stats{'avg'} = $batch_avg{$ring};
1114	$stats{'total-avg'} = $batch_total_avg{$ring};
1115	$stats{'submit'} = $submit_avg{$ring};
1116	$stats{'execute'} = $execute_avg{$ring};
1117	$stats{'save'} = $ctxsave_avg{$ring};
1118
1119	if ($html) {
1120		html_stats(\%stats, $group, $id);
1121	} else {
1122		stdio_stats(\%stats, $group, $id);
1123	}
1124}
1125
1126sub sortVQueue {
1127	my $as = $vdb{$a}->{'queue'};
1128	my $bs = $vdb{$b}->{'queue'};
1129	my $val;
1130
1131	$val = $as <=> $bs;
1132	$val = $a cmp $b if $val == 0;
1133
1134	return $val;
1135}
1136
1137my @sorted_vkeys = sort sortVQueue keys %vdb;
1138my (%vqueued, %vrunnable);
1139
1140foreach my $key (@sorted_vkeys) {
1141	my $ctx = $vdb{$key}->{'ctx'};
1142
1143	$vdb{$key}->{'submit-delay'} = $vdb{$key}->{'submit'} - $vdb{$key}->{'queue'};
1144	$vdb{$key}->{'execute-delay'} = $vdb{$key}->{'start'} - $vdb{$key}->{'submit'};
1145
1146	$vqueued{$ctx} += $vdb{$key}->{'submit-delay'};
1147	$vrunnable{$ctx} += $vdb{$key}->{'execute-delay'};
1148}
1149
1150my $veng_id = $engine_start_id + scalar(keys %rings);
1151
1152foreach my $cid (sort keys %ctxmap) {
1153	my $ctx = $ctxmap{$cid};
1154	my $elapsed = $last_ts - $first_ts;
1155	my %stats;
1156
1157	$stats{'virtual'} = 1;
1158	if (exists $vrunnable{$ctx}) {
1159		$stats{'runnable'} = $vrunnable{$ctx} / $elapsed * 100.0;
1160	} else {
1161		$stats{'runnable'} = 0;
1162	}
1163	if (exists $vqueued{$ctx}) {
1164		$stats{'queued'} = $vqueued{$ctx} / $elapsed * 100.0;
1165	} else {
1166		$stats{'queued'} = 0;
1167	}
1168	$vreqw{$ctx} = 0 unless exists $vreqw{$ctx};
1169	$stats{'wait'} = $vreqw{$ctx} / $elapsed * 100.0;
1170	$stats{'count'} = scalar(grep {$ctx == $vdb{$_}->{'ctx'}} keys %vdb);
1171
1172	if ($html) {
1173		html_stats(\%stats, $cid, $veng_id++);
1174	} else {
1175		stdio_stats(\%stats, $cid, $veng_id++);
1176	}
1177}
1178
1179exit 0 unless $html;
1180
1181print <<ENDHTML;
1182  ]);
1183
1184  var items = new vis.DataSet([
1185ENDHTML
1186
1187sub sortQueue {
1188	my $as = $db{$a}->{'queue'};
1189	my $bs = $db{$b}->{'queue'};
1190	my $val;
1191
1192	$val = $as <=> $bs;
1193	$val = $a cmp $b if $val == 0;
1194
1195	return $val;
1196}
1197
1198sub ctx_colour
1199{
1200	my ($ctx, $stage, $lfac) = (@_);
1201	my ($s, $l);
1202	my $val;
1203
1204	unless ($colour_contexts) {
1205		if ($stage eq 'queue') {
1206			$val = 210;
1207			$s = 65;
1208			$l = 52;
1209		} elsif ($stage eq 'ready') {
1210			$val = 0;
1211			$s = 0;
1212			$l = 47;
1213		} elsif ($stage eq 'execute') {
1214			$val = 346;
1215			$s = 68;
1216			$l = 65;
1217		} elsif ($stage eq 'ctxsave') {
1218			$val = 26;
1219			$s = 90;
1220			$l = 52;
1221		} elsif ($stage eq 'incomplete') {
1222			$val = 0;
1223			$s = 85;
1224			$l = 50;
1225		}
1226	} else {
1227		if ($stage eq 'queue') {
1228			$s = 35;
1229			$l = 85;
1230		} elsif ($stage eq 'ready') {
1231			$s = 35;
1232			$l = 45;
1233		} elsif ($stage eq 'execute') {
1234			$s = 80;
1235			$l = 65;
1236		} elsif ($stage eq 'ctxsave') {
1237			$s = 75;
1238			$l = 70;
1239		} elsif ($stage eq 'incomplete') {
1240			$s = 80;
1241			$l = 25;
1242		}
1243
1244		$val = $ctx_colours{$ctx};
1245	}
1246
1247	$l = int($l * $lfac);
1248
1249	return "hsl($val, $s%, $l%)";
1250}
1251
1252sub box_style
1253{
1254	my ($ctx, $stage) = @_;
1255	my $deg;
1256	my $text_col = 'white';
1257
1258	if ($stage eq 'queue') {
1259		$deg = 90;
1260		$text_col = 'black' if $colour_contexts;
1261	} elsif ($stage eq 'ready') {
1262		$deg = 45;
1263	} elsif ($stage eq 'execute') {
1264		$deg = 0;
1265		$text_col = 'black' if $colour_contexts;
1266	} elsif ($stage eq 'ctxsave') {
1267		$deg = 105;
1268		$text_col = 'black' if $colour_contexts;
1269	} elsif ($stage eq 'incomplete') {
1270		$deg = 0;
1271	}
1272
1273	return "color: $text_col; background: repeating-linear-gradient(" .
1274		$deg . 'deg, ' .
1275		ctx_colour($ctx, $stage, 1.0) . ', ' .
1276		ctx_colour($ctx, $stage, 1.0) . ' 10px, ' .
1277		ctx_colour($ctx, $stage, 0.90) . ' 10px, ' .
1278		ctx_colour($ctx, $stage, 0.90) . ' 20px);';
1279}
1280
1281my $i = 0;
1282my $req = 0;
1283foreach my $key (sort sortQueue keys %db) {
1284	my ($name, $ctx, $seqno) = ($db{$key}->{'name'}, $db{$key}->{'ctx'}, $db{$key}->{'seqno'});
1285	my ($queue, $start, $notify, $end) = ($db{$key}->{'queue'}, $db{$key}->{'start'}, $db{$key}->{'notify'}, $db{$key}->{'end'});
1286	my $engine_start = $db{$key}->{'engine-start'};
1287	my $submit = $queue + $db{$key}->{'submit-delay'};
1288	my ($content, $style);
1289	my $group = $engine_start_id + $rings{$db{$key}->{'ring'}};
1290	my $subgroup = $ctx - $min_ctx;
1291	my $type = ' type: \'range\',';
1292	my $startend;
1293	my $skey;
1294
1295	# submit to execute
1296	unless (exists $skip_box{'queue'} or exists $db{$key}->{'virtual'}) {
1297		$skey = 2 * $max_seqno * $ctx + 2 * $seqno;
1298		$style = box_style($ctx, 'queue');
1299		$content = "$name<br>$db{$key}->{'submit-delay'}us <small>($db{$key}->{'execute-delay'}us)</small>";
1300		$startend = 'start: ' . $queue . ', end: ' . $submit;
1301		print "\t{id: $i, key: $skey, $type group: $group, subgroup: $subgroup, subgroupOrder: $subgroup, content: '$content', $startend, style: \'$style\'},\n";
1302		$i++;
1303	}
1304
1305	# execute to start
1306	$engine_start = $db{$key}->{'start'} unless defined $engine_start;
1307	unless (exists $skip_box{'ready'} or exists $db{$key}->{'virtual'}) {
1308		$skey = 2 * $max_seqno * $ctx + 2 * $seqno + 1;
1309		$style = box_style($ctx, 'ready');
1310		$content = "<small>$name<br>$db{$key}->{'execute-delay'}us</small>";
1311		$startend = 'start: ' . $submit . ', end: ' . $engine_start;
1312		print "\t{id: $i, key: $skey, $type group: $group, subgroup: $subgroup, subgroupOrder: $subgroup, content: '$content', $startend, style: \'$style\'},\n";
1313		$i++;
1314	}
1315
1316	# start to user interrupt
1317	unless (exists $skip_box{'execute'}) {
1318		$skey = -2 * $max_seqno * $ctx - 2 * $seqno - 1;
1319		$style = box_style($ctx,
1320				   exists $db{$key}->{'incomplete'} ?
1321				   'incomplete' : 'execute');
1322		$content = "$name <small>$db{$key}->{'port'}</small>";
1323		$content .= ' <small><i>???</i></small> ' if exists $db{$key}->{'incomplete'};
1324		$content .= ' <small><i>++</i></small> ' if exists $db{$key}->{'no-end'};
1325		$content .= ' <small><i>+</i></small> ' if exists $db{$key}->{'no-notify'};
1326		$content .= "<br>$db{$key}->{'duration'}us <small>($db{$key}->{'context-complete-delay'}us)</small>";
1327		$startend = 'start: ' . $start . ', end: ' . $notify;
1328		print "\t{id: $i, key: $skey, $type group: $group, subgroup: $subgroup, subgroupOrder: $subgroup, content: '$content', $startend, style: \'$style\'},\n";
1329		$i++;
1330	}
1331
1332	# user interrupt to context complete
1333	unless (exists $skip_box{'ctxsave'} or exists $db{$key}->{'no-end'}) {
1334		$skey = -2 * $max_seqno * $ctx - 2 * $seqno;
1335		$style = box_style($ctx, 'ctxsave');
1336		my $ctxsave = $db{$key}->{'end'} - $db{$key}->{'notify'};
1337		$content = "<small>$name<br>${ctxsave}us</small>";
1338		$content .= ' <small><i>???</i></small> ' if exists $db{$key}->{'incomplete'};
1339		$content .= ' <small><i>++</i></small> ' if exists $db{$key}->{'no-end'};
1340		$content .= ' <small><i>+</i></small> ' if exists $db{$key}->{'no-notify'};
1341		$startend = 'start: ' . $notify . ', end: ' . $end;
1342		print "\t{id: $i, key: $skey, $type group: $group, subgroup: $subgroup, subgroupOrder: $subgroup, content: '$content', $startend, style: \'$style\'},\n";
1343		$i++;
1344	}
1345
1346	$last_ts = $end;
1347
1348	last if ++$req > $max_requests;
1349}
1350
1351push @freqs, [$prev_freq_ts, $last_ts, $prev_freq] if $prev_freq;
1352
1353foreach my $item (@freqs) {
1354	my ($start, $end, $freq) = @$item;
1355	my $startend;
1356
1357	next if $start > $last_ts;
1358
1359	$start = $first_ts if $start < $first_ts;
1360	$end = $last_ts if $end > $last_ts;
1361	$startend = 'start: ' . $start . ', end: ' . $end;
1362	print "\t{id: $i, type: 'range', group: 0, content: '$freq', $startend},\n";
1363	$i++;
1364}
1365
1366if ($gpu_timeline) {
1367	foreach my $item (@gpu_intervals) {
1368		my ($start, $end) = @$item;
1369		my $startend;
1370
1371		next if $start > $last_ts;
1372
1373		$start = $first_ts if $start < $first_ts;
1374		$end = $last_ts if $end > $last_ts;
1375		$startend = 'start: ' . $start . ', end: ' . $end;
1376		print "\t{id: $i, type: 'range', group: 1, $startend},\n";
1377		$i++;
1378	}
1379}
1380
1381$req = 0;
1382$veng_id = $engine_start_id + scalar(keys %rings);
1383foreach my $key (@sorted_vkeys) {
1384	my ($name, $ctx, $seqno) = ($vdb{$key}->{'name'}, $vdb{$key}->{'ctx'}, $vdb{$key}->{'seqno'});
1385	my $queue = $vdb{$key}->{'queue'};
1386	my $submit = $vdb{$key}->{'submit'};
1387	my $engine_start = $db{$key}->{'engine-start'};
1388	my ($content, $style, $startend, $skey);
1389	my $group = $veng_id + $cids{$ctx};
1390	my $subgroup = $ctx - $min_ctx;
1391	my $type = ' type: \'range\',';
1392	my $duration;
1393
1394	# submit to execute
1395	unless (exists $skip_box{'queue'}) {
1396		$skey = 2 * $max_seqno * $ctx + 2 * $seqno;
1397		$style = box_style($ctx, 'queue');
1398		$content = "$name<br>$vdb{$key}->{'submit-delay'}us <small>($vdb{$key}->{'execute-delay'}us)</small>";
1399		$startend = 'start: ' . $queue . ', end: ' . $submit;
1400		print "\t{id: $i, key: $skey, $type group: $group, subgroup: $subgroup, subgroupOrder: $subgroup, content: '$content', $startend, style: \'$style\'},\n";
1401		$i++;
1402	}
1403
1404	# execute to start
1405	$engine_start = $vdb{$key}->{'start'} unless defined $engine_start;
1406	unless (exists $skip_box{'ready'}) {
1407		$skey = 2 * $max_seqno * $ctx + 2 * $seqno + 1;
1408		$style = box_style($ctx, 'ready');
1409		$content = "<small>$name<br>$vdb{$key}->{'execute-delay'}us</small>";
1410		$startend = 'start: ' . $submit . ', end: ' . $engine_start;
1411		print "\t{id: $i, key: $skey, $type group: $group, subgroup: $subgroup, subgroupOrder: $subgroup, content: '$content', $startend, style: \'$style\'},\n";
1412		$i++;
1413	}
1414
1415	last if ++$req > $max_requests;
1416}
1417
1418my $end_ts = $first_ts + $width_us;
1419$first_ts = $first_ts;
1420
1421print <<ENDHTML;
1422  ]);
1423
1424  function majorAxis(date, scale, step) {
1425	var s = date / 1000000;
1426	var precision;
1427
1428	if (scale == 'millisecond')
1429		precision = 6;
1430	else if (scale == 'second')
1431		precision = 3;
1432	else
1433		precision = 0;
1434
1435	return s.toFixed(precision) + "s";
1436  }
1437
1438  function minorAxis(date, scale, step) {
1439	var t = date;
1440	var precision;
1441	var unit;
1442
1443	if (scale == 'millisecond') {
1444		t %= 1000;
1445		precision = 0;
1446		unit = 'us';
1447	} else if (scale == 'second') {
1448		t /= 1000;
1449		t %= 1000;
1450		precision = 0;
1451		unit = 'ms';
1452	} else {
1453		t /= 1000000;
1454		precision = 1;
1455		unit = 's';
1456	}
1457
1458	return t.toFixed(precision) + unit;
1459  }
1460
1461  // Configuration for the Timeline
1462  var options = { groupOrder: 'content',
1463		  horizontalScroll: true,
1464		  stack: false,
1465		  stackSubgroups: false,
1466		  zoomKey: 'ctrlKey',
1467		  orientation: 'top',
1468		  format: { majorLabels: majorAxis, minorLabels: minorAxis },
1469		  start: $first_ts,
1470		  end: $end_ts};
1471
1472  // Create a Timeline
1473  var timeline = new vis.Timeline(container, items, groups, options);
1474
1475  function toggleStacking() {
1476	options.stack = !options.stack;
1477	options.stackSubgroups = !options.stackSubgroups;
1478	timeline.setOptions(options);
1479  }
1480ENDHTML
1481
1482print <<ENDHTML;
1483</script>
1484</body>
1485</html>
1486ENDHTML
1487