From: Tvrtko Ursulin <tvrtko.ursulin@xxxxxxxxx> Given a log file created via perf with some interesting trace events enabled, this tool can generate the timeline graph of requests getting queued, their dependencies resolved, sent to the GPU for executing and finally completed. This can be useful when analyzing certain classes of performance issues. More help is available in the tool itself. The tool will also calculate some overall per engine statistics, like total time engine was idle and similar. Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@xxxxxxxxx> --- A bit rough around the edges but works for the majority of traces I was feeding into it. Wheter or not we want to add this to IGT I am not sure. --- scripts/Makefile.am | 2 +- scripts/trace.pl | 806 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 807 insertions(+), 1 deletion(-) create mode 100755 scripts/trace.pl diff --git a/scripts/Makefile.am b/scripts/Makefile.am index 85d4a5cf4e5c..641715294936 100644 --- a/scripts/Makefile.am +++ b/scripts/Makefile.am @@ -1,2 +1,2 @@ -dist_noinst_SCRIPTS = intel-gfx-trybot who.sh run-tests.sh +dist_noinst_SCRIPTS = intel-gfx-trybot who.sh run-tests.sh trace.pl noinst_PYTHON = throttle.py diff --git a/scripts/trace.pl b/scripts/trace.pl new file mode 100755 index 000000000000..057c1291c2b8 --- /dev/null +++ b/scripts/trace.pl @@ -0,0 +1,806 @@ +#! /usr/bin/perl +# +# Copyright © 2017 Intel Corporation +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice (including the next +# paragraph) shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. +# + +use strict; +use warnings; +use 5.010; + +my $gid = 0; +my (%db, %queue, %submit, %notify, %rings, %ctxdb, %ringmap, %reqwait); +my @freqs; + +my $max_items = 3000; +my $width_us = 32000; +my $correct_durations = 0; +my %ignore_ring; +my %skip_box; + +my @args = @ARGV; + +sub arg_help +{ + if ($_[0] eq '--help' or $_[0] eq '-h') { + shift @_; +print <<ENDHELP; +Notes: + + The tool parse the output generated by the 'perf script' command after the + correct set of i915 tracepoints have been collected via perf record. + + To collect the data: + + perf record -a -c 1 -e i915:intel_gpu_freq_change, \ + i915:i915_gem_request_add, \ + i915:i915_gem_request_submit, \ + i915:i915_gem_request_in, \ + i915:i915_gem_request_out, \ + i915:intel_engine_notify, \ + i915:i915_gem_request_wait_begin, \ + i915:i915_gem_request_wait_end \ + [command-to-be-profiled] + + Then create the log file with: + + perf script >trace.log + + This file in turn should be piped into this tool which will generate some + HTML output out of it. + + HTML can be viewed from a directory containing the 'vis' JavaScript module. + On Ubuntu this can be installed like this: + + apt-get install npm + npm install vis + +Usage: + trace.pl <options> <input-file >output-file + + --help / -h This help text + --max-items=num / -m num Maximum number of boxes to put on the + timeline. More boxes means more work for + the JavaScript engine in the browser. + --zoom-width-ms=ms / -z ms Width of the initial timeline zoom + --split-requests / -s Try to split out request which were + submitted together due coalescing in the + driver. May not be 100% accurate and may + influence the per-engine statistics so + use with care. + --ignore-ring=id / -i id Ignore ring with the numerical id when + parsing the log (enum intel_engine_id). + Can be given multiple times. + --skip-box=name / -x name Do not put a certain type of a box on + the timeline. One of: queue, ready, + execute and ctxsave. + Can be given multiple times. +ENDHELP + } + + return @_; +} + +sub arg_max_items +{ + my $val; + + return unless scalar(@_); + + if ($_[0] eq '--max-items' or $_[0] eq '-m') { + shift @_; + $val = shift @_; + } elsif ($_[0] =~ /--max-items=(\d+)/) { + shift @_; + $val = $1; + } + + $max_items = int($val) if defined $val; + + return @_; +} + +sub arg_zoom_width +{ + my $val; + + return unless scalar(@_); + + if ($_[0] eq '--zoom-width-ms' or $_[0] eq '-z') { + shift @_; + $val = shift @_; + } elsif ($_[0] =~ /--zoom-width-ms=(\d+)/) { + shift @_; + $val = $1; + } + + $width_us = int($val) * 1000 if defined $val; + + return @_; +} + +sub arg_split_requests +{ + return unless scalar(@_); + + if ($_[0] eq '--split-requests' or $_[0] eq '-s') { + shift @_; + $correct_durations = 1; + } + + return @_; +} + +sub arg_ignore_ring +{ + my $val; + + return unless scalar(@_); + + if ($_[0] eq '--ignore-ring' or $_[0] eq '-i') { + shift @_; + $val = shift @_; + } elsif ($_[0] =~ /--ignore-ring=(\d+)/) { + shift @_; + $val = $1; + } + + $ignore_ring{$val} = 1 if defined $val; + + return @_; +} + +sub arg_skip_box +{ + my $val; + + return unless scalar(@_); + + if ($_[0] eq '--skip-box' or $_[0] eq '-x') { + shift @_; + $val = shift @_; + } elsif ($_[0] =~ /--skip-box=(\d+)/) { + shift @_; + $val = $1; + } + + $skip_box{$val} = 1 if defined $val; + + return @_; +} + +while (@args) { + my $left = scalar(@args); + + @args = arg_help(@args); + @args = arg_max_items(@args); + @args = arg_zoom_width(@args); + @args = arg_split_requests(@args); + @args = arg_ignore_ring(@args); + @args = arg_skip_box(@args); + + last if $left == scalar(@args); +} + +die if scalar(@args); + +@ARGV = @args; + +sub parse_req +{ + my ($line, $tp) = @_; + state %cache; + + $cache{$tp} = qr/(\d+)\.(\d+):.*$tp.*ring=(\d+), ctx=(\d+), seqno=(\d+), global=(\d+)/ unless exists $cache{$tp}; + + if ($line =~ $cache{$tp}) { + return ($1, $2, $3, $4, $5, $6); + } else { + return undef; + } +} + +sub parse_req_hw +{ + my ($line, $tp) = @_; + state %cache; + + $cache{$tp} = qr/(\d+)\.(\d+):.*$tp.*ring=(\d+), ctx=(\d+), seqno=(\d+), global=(\d+), port=(\d+)/ unless exists $cache{$tp}; + + if ($line =~ $cache{$tp}) { + return ($1, $2, $3, $4, $5, $6, $7); + } else { + return undef; + } +} + +sub parse_req_wait_begin +{ + my ($line, $tp) = @_; + + if ($line =~ /(\d+)\.(\d+):.*i915_gem_request_wait_begin.*ring=(\d+), ctx=(\d+), seqno=(\d+)/) { + return ($1, $2, $3, $4, $5); + } else { + return undef; + } +} + +sub parse_notify +{ + my ($line) = @_; + + if ($line =~ /(\d+)\.(\d+):.*i915_gem_request_notify.*ring=(\d+), seqno=(\d+)/) { + return ($1, $2, $3, $4); + } else { + return undef; + } +} + +sub parse_freq +{ + my ($line) = @_; + + if ($line =~ /(\d+)\.(\d+):.*intel_gpu_freq_change.*new_freq=(\d+)/) { + return ($1, $2, $3); + } else { + return undef; + } +} + +sub us +{ + my ($s, $us) = @_; + + return $s * 1000000 + $us; +} + +sub db_key +{ + my ($ring, $ctx, $seqno) = @_; + + return $ring . '/' . $ctx . '/' . $seqno; +} + +sub global_key +{ + my ($ring, $seqno) = @_; + + return $ring . '/' . $seqno; +} + +sub sanitize_ctx +{ + my ($ctx) = @_; + + if (exists $ctxdb{$ctx}) { + return $ctx . '.' . $ctxdb{$ctx}; + } else { + return $ctx; + } +} + +sub ts +{ + my ($us) = @_; + my ($h, $m, $s); + + $s = int($us / 1000000); + $us = $us % 1000000; + + $m = int($s / 60); + $s = $s % 60; + + $h = int($m / 60); + $m = $m % 60; + + return sprintf('2017-01-01 %02u:%02u:%02u.%06u', int($h), int($m), int($s), int($us)); +} + +# Main input loop - parse lines and build the internal representation of the +# trace using a hash of requests and some auxilliary data structures. +my $prev_freq = 0; +my $prev_freq_ts = 0; +while (<>) { + my ($s, $us, $ring, $ctx, $seqno, $global_seqno, $port); + my $freq; + my $key; + + chomp; + + ($s, $us, $ring, $ctx, $seqno) = parse_req_wait_begin($_); + if (defined $s) { + my %rw; + + next if exists $ignore_ring{$ring}; + + $ctx = sanitize_ctx($ctx); + $key = db_key($ring, $ctx, $seqno); + + next if exists $reqwait{$key}; + + $rw{'key'} = $key; + $rw{'ring'} = $ring; + $rw{'seqno'} = $seqno; + $rw{'ctx'} = $ctx; + $rw{'start'} = us($s, $us); + $reqwait{$key} = \%rw; + next; + } + + ($s, $us, $ring, $ctx, $seqno, $global_seqno) = parse_req($_, 'i915:i915_gem_request_wait_end'); + if (defined $s) { + next if exists $ignore_ring{$ring}; + + $ctx = sanitize_ctx($ctx); + $key = db_key($ring, $ctx, $seqno); + + next unless exists $reqwait{$key}; + + $reqwait{$key}->{'end'} = us($s, $us); + next; + } + + ($s, $us, $ring, $ctx, $seqno, $global_seqno) = parse_req($_, 'i915:i915_gem_request_add'); + if (defined $s) { + my $orig_ctx = $ctx; + + next if exists $ignore_ring{$ring}; + + $ctx = sanitize_ctx($ctx); + $key = db_key($ring, $ctx, $seqno); + + if (exists $queue{$key}) { + $ctxdb{$orig_ctx}++; + $ctx = sanitize_ctx($orig_ctx); + $key = db_key($ring, $ctx, $seqno); + } + + $queue{$key} = us($s, $us); + next; + } + + ($s, $us, $ring, $ctx, $seqno, $global_seqno) = parse_req($_, 'i915:i915_gem_request_submit'); + if (defined $s) { + next if exists $ignore_ring{$ring}; + + $ctx = sanitize_ctx($ctx); + $key = db_key($ring, $ctx, $seqno); + + die if exists $submit{$key}; + die unless exists $queue{$key}; + + $submit{$key} = us($s, $us); + next; + } + + ($s, $us, $ring, $ctx, $seqno, $global_seqno, $port) = parse_req_hw($_, 'i915:i915_gem_request_in'); + if (defined $s) { + my %req; + + next if exists $ignore_ring{$ring}; + + $ctx = sanitize_ctx($ctx); + $key = db_key($ring, $ctx, $seqno); + + die if exists $db{$key}; + next unless exists $queue{$key}; ### FIXME die + die unless exists $submit{$key}; + + $req{'start'} = us($s, $us); + $req{'ring'} = $ring; + $req{'seqno'} = $seqno; + $req{'ctx'} = $ctx; + $req{'name'} = $ctx . '/' . $seqno; + $req{'global'} = $global_seqno; + $req{'port'} = $port; + $req{'queue'} = $queue{$key}; + $req{'submit-delay'} = $submit{$key} - $queue{$key}; + $req{'execute-delay'} = $req{'start'} - $submit{$key}; + $rings{$ring} = $gid++ unless exists $rings{$ring}; + $ringmap{$rings{$ring}} = $ring; + $db{$key} = \%req; + next; + } + + ($s, $us, $ring, $ctx, $seqno, $global_seqno, $port) = parse_req($_, 'i915:i915_gem_request_out'); + if (defined $s) { + my $gkey = global_key($ring, $global_seqno); + + next if exists $ignore_ring{$ring}; + + $ctx = sanitize_ctx($ctx); + $key = db_key($ring, $ctx, $seqno); + + next unless exists $db{$key}; ### FIXME die + die unless exists $db{$key}->{'start'}; + die if exists $db{$key}->{'end'}; + + $db{$key}->{'end'} = us($s, $us); + if (exists $notify{$gkey}) { + $db{$key}->{'notify'} = $notify{$gkey}; + } else { + # No notify so far. Maybe it will arrive later which + # will be handled in the sanitation loop below. + $db{$key}->{'notify'} = $db{$key}->{'end'}; + $db{$key}->{'no-notify'} = 1; + } + $db{$key}->{'duration'} = $db{$key}->{'notify'} - $db{$key}->{'start'}; + $db{$key}->{'context-complete-delay'} = $db{$key}->{'end'} - $db{$key}->{'notify'}; + next; + } + + ($s, $us, $ring, $seqno) = parse_notify($_); + if (defined $s) { + next if exists $ignore_ring{$ring}; + $notify{global_key($ring, $seqno)} = us($s, $us); + next; + } + + ($s, $us, $freq) = parse_freq($_); + if (defined $s) { + my $cur = us($s, $us); + + push @freqs, [$prev_freq_ts, $cur, $prev_freq] if $prev_freq; + $prev_freq_ts = $cur; + $prev_freq = $freq; + next; + } +} + +# Sanitation pass to fixup up out of order notify and context complete, and to +# fine the largest seqno to be used for timeline sorting purposes. +my $max_seqno = 0; +foreach my $key (keys %db) { + my $gkey = global_key($db{$key}->{'ring'}, $db{$key}->{'global'}); + + die unless exists $db{$key}->{'start'}; + + $max_seqno = $db{$key}->{'seqno'} if $db{$key}->{'seqno'} > $max_seqno; + + unless (exists $db{$key}->{'end'}) { + # Context complete not received. + if (exists $notify{$gkey}) { + # No context complete due req merging - use notify. + $db{$key}->{'notify'} = $notify{$gkey}; + $db{$key}->{'end'} = $db{$key}->{'notify'}; + $db{$key}->{'no-end'} = 1; + } else { + # No notify and no context complete - mark it. + $db{$key}->{'end'} = $db{$key}->{'start'} + 999; + $db{$key}->{'notify'} = $db{$key}->{'end'}; + $db{$key}->{'incomplete'} = 1; + } + + $db{$key}->{'duration'} = $db{$key}->{'notify'} - $db{$key}->{'start'}; + $db{$key}->{'context-complete-delay'} = $db{$key}->{'end'} - $db{$key}->{'notify'}; + } else { + # Notify arrived after context complete. + if (exists $db{$key}->{'no-notify'} and exists $notify{$gkey}) { + delete $db{$key}->{'no-notify'}; + $db{$key}->{'notify'} = $notify{$gkey}; + $db{$key}->{'duration'} = $db{$key}->{'notify'} - $db{$key}->{'start'}; + $db{$key}->{'context-complete-delay'} = $db{$key}->{'end'} - $db{$key}->{'notify'}; + } + } +} + +# GPU time accounting +my (%running, %runnable, %queued, %batch_avg, %batch_total_avg, %batch_count); +my $last_ts = 0; +my $first_ts; + +foreach my $key (sort {$db{$a}->{'start'} <=> $db{$b}->{'start'}} keys %db) { + my $ring = $db{$key}->{'ring'}; + my $end = $db{$key}->{'end'}; + + $first_ts = $db{$key}->{'queue'} if not defined $first_ts or $db{$key}->{'queue'} < $first_ts; + $last_ts = $end if $end > $last_ts; + + $running{$ring} += $end - $db{$key}->{'start'} unless exists $db{$key}->{'no-end'}; + $runnable{$ring} += $db{$key}->{'execute-delay'}; + $queued{$ring} += $db{$key}->{'start'} - $db{$key}->{'execute-delay'} - $db{$key}->{'queue'}; + + $batch_count{$ring}++; + + # correct duration of merged batches + if ($correct_durations and exists $db{$key}->{'no-end'}) { + my $start = $db{$key}->{'start'}; + my $ctx = $db{$key}->{'ctx'}; + my $seqno = $db{$key}->{'seqno'}; + my $next_key; + my $i = 1; + + do { + $next_key = db_key($ring, $ctx, $seqno + $i); + $i++; + } until (exists $db{$next_key} or $i > scalar(keys(%db))); # ugly stop hack + + # 20us tolerance + if (exists $db{$next_key} and $db{$next_key}->{'start'} < $start + 20) { + $db{$next_key}->{'start'} = $start + $db{$key}->{'duration'}; + $db{$next_key}->{'duration'} = $db{$next_key}->{'notify'} - $db{$next_key}->{'start'}; + $end = $db{$key}->{'notify'}; + } + } + $batch_avg{$ring} += $db{$key}->{'duration'}; + $batch_total_avg{$ring} += $end - $db{$key}->{'start'}; +} + +foreach my $ring (keys %batch_avg) { + $batch_avg{$ring} /= $batch_count{$ring}; + $batch_total_avg{$ring} /= $batch_count{$ring}; +} + +# Calculate engine idle time +my %flat_busy; +foreach my $gid (sort keys %rings) { + my $ring = $ringmap{$rings{$gid}}; + my (@s_, @e_); + + # Extract all GPU busy intervals and sort them. + foreach my $key (sort {$db{$a}->{'start'} <=> $db{$b}->{'start'}} keys %db) { + next unless $db{$key}->{'ring'} == $ring; + push @s_, $db{$key}->{'start'}; + push @e_, $db{$key}->{'end'}; + } + + die unless $#s_ == $#e_; + + # Flatten the intervals. + for my $i (1..$#s_) { + last if $i >= @s_; # End of array. + if ($s_[$i] <= $e_[$i - 1]) { + # Current entry overlaps with the previous one. We need + # to merge end of the previous interval from the list + # with the start of the current one. + splice @e_, $i - 1, 1; + splice @s_, $i, 1; + # Continue with the same element when list got squashed. + redo; + } + } + + # Add up all busy times. + my $total = 0; + for my $i (0..$#s_) { + die if $e_[$i] < $s_[$i]; + + $total = $total + ($e_[$i] - $s_[$i]); + } + + $flat_busy{$ring} = $total; +} + +my %reqw; +foreach my $key (keys %reqwait) { + $reqw{$reqwait{$key}->{'ring'}} += $reqwait{$key}->{'end'} - $reqwait{$key}->{'start'}; +} + +print <<ENDHTML; +<!DOCTYPE HTML> +<html> +<head> + <title>i915 GT timeline</title> + + <style type="text/css"> + body, html { + font-family: sans-serif; + } + </style> + + <script src="node_modules/vis/dist/vis.js"></script> + <link href="node_modules/vis//dist/vis.css" rel="stylesheet" type="text/css" /> +</head> +<body> + +<button onclick="toggleStackSubgroups()">Toggle stacking</button> + +<p> +pink = requests executing on the GPU<br> +grey = runnable requests waiting for a slot on GPU<br> +blue = requests waiting on fences and dependencies before they are runnable<br> +</p> +<p> +Boxes are in format 'ctx-id/seqno'. +</p> +<p> +Use Ctrl+scroll-action to zoom-in/out and scroll-action or dragging to move around the timeline. +</p> + +<div id="visualization"></div> + +<script type="text/javascript"> + var container = document.getElementById('visualization'); + + var groups = new vis.DataSet([ +ENDHTML + +# var groups = new vis.DataSet([ +# {id: 1, content: 'g0'}, +# {id: 2, content: 'g1'} +# ]); + +print "\t{id: 0, content: 'Freq'},\n"; +foreach my $group (sort keys %rings) { + my $name; + my $ring = $ringmap{$rings{$group}}; + my $id = 1 + $rings{$group}; + my $elapsed = $last_ts - $first_ts; + my $p; + + $name = 'Ring' . $group; + $name .= '<br><small><br>'; + $p = (1.0 - $flat_busy{$ring} / $elapsed) * 100.0; + $name .= sprintf('%2.2f', $p) . '% idle<br><br>'; + $p = $running{$ring} / $elapsed * 100.0; + $name .= sprintf('%2.2f', $p) . '% busy<br>'; + $p = $runnable{$ring} / $elapsed * 100.0; + $name .= sprintf('%2.2f', $p) . '% runnable<br>'; + $p = $queued{$ring} / $elapsed * 100.0; + $name .= sprintf('%2.2f', $p) . '% queued<br><br>'; + $p = $reqw{$ring} / $elapsed * 100.0; + $name .= sprintf('%2.2f', $p) . '% wait<br><br>'; + $name .= $batch_count{$ring} . ' batches<br>'; + $name .= sprintf('%2.2f', $batch_avg{$ring}) . 'us avg batch<br>'; + $name .= sprintf('%2.2f', $batch_total_avg{$ring}) . 'us avg engine batch<br>'; + $name .= '</small>'; + + print "\t{id: $id, content: '$name'},\n"; +} + +print <<ENDHTML; + ]); + + var items = new vis.DataSet([ +ENDHTML + +# var items = new vis.DataSet([ +# {id: 1, group: 1, content: 'item 1', start: '2013-04-20', type: 'box'}, +# {id: 2, group: 2, content: 'item 2', start: '2013-04-14'}, +# {id: 3, group: 1, content: 'item 3', start: '2013-04-18'}, +# {id: 4, group: 2, content: 'item 4', start: '2013-04-16', end: '2013-04-19'}, +# {id: 5, group: 1, content: 'item 5', start: '2013-04-25'}, +# {id: 6, group: 2, content: 'item 6', start: '2013-04-27'} +# ]); + +my $i = 0; +foreach my $key (sort {$db{$a}->{'queue'} <=> $db{$b}->{'queue'}} keys %db) { + my ($name, $ctx, $seqno) = ($db{$key}->{'name'}, $db{$key}->{'ctx'}, $db{$key}->{'seqno'}); + my ($queue, $start, $notify, $end) = ($db{$key}->{'queue'}, $db{$key}->{'start'}, $db{$key}->{'notify'}, $db{$key}->{'end'}); + my $submit = $queue + $db{$key}->{'submit-delay'}; + my ($content, $style); + my $group = 1 + $rings{$db{$key}->{'ring'}}; + my $type = ' type: \'range\','; + my $startend; + my $skey; + + # submit to execute + unless (exists $skip_box{'queue'}) { + $skey = 2 * $max_seqno * $ctx + 2 * $seqno; + $style = 'color: black; background-color: lightblue;'; + $content = "$name<br>$db{$key}->{'submit-delay'}us <small>($db{$key}->{'execute-delay'}us)</small>"; + $startend = 'start: \'' . ts($queue) . '\', end: \'' . ts($submit) . '\''; + print "\t{id: $i, key: $skey, $type group: $group, subgroup: 1, subgroupOrder: 1, content: '$content', $startend, style: \'$style\'},\n"; + $i++; + } + + # execute to start + unless (exists $skip_box{'ready'}) { + $skey = 2 * $max_seqno * $ctx + 2 * $seqno + 1; + $style = 'color: black; background-color: lightgrey;'; + $content = "<small>$name<br>$db{$key}->{'execute-delay'}us</small>"; + $startend = 'start: \'' . ts($submit) . '\', end: \'' . ts($start) . '\''; + print "\t{id: $i, key: $skey, $type group: $group, subgroup: 1, subgroupOrder: 2, content: '$content', $startend, style: \'$style\'},\n"; + $i++; + } + + # start to user interrupt + unless (exists $skip_box{'execute'}) { + $skey = -2 * $max_seqno * $ctx - 2 * $seqno - 1; + if (exists $db{$key}->{'incomplete'}) { + $style = 'color: white; background-color: red;'; + } else { + $style = 'color: black; background-color: pink;'; + } + $content = "$name <small>$db{$key}->{'port'}</small>"; + $content .= ' <small><i>???</i></small> ' if exists $db{$key}->{'incomplete'}; + $content .= ' <small><i>++</i></small> ' if exists $db{$key}->{'no-end'}; + $content .= ' <small><i>+</i></small> ' if exists $db{$key}->{'no-notify'}; + $content .= "<br>$db{$key}->{'duration'}us <small>($db{$key}->{'context-complete-delay'}us)</small>"; + $startend = 'start: \'' . ts($start) . '\', end: \'' . ts($notify) . '\''; + print "\t{id: $i, key: $skey, $type group: $group, subgroup: 2, subgroupOrder: 3, content: '$content', $startend, style: \'$style\'},\n"; + $i++; + } + + # user interrupt to context complete + unless (exists $skip_box{'ctxsave'}) { + $skey = -2 * $max_seqno * $ctx - 2 * $seqno; + $style = 'color: black; background-color: orange;'; + my $ctxsave = $db{$key}->{'end'} - $db{$key}->{'notify'}; + $content = "<small>$name<br>${ctxsave}us</small>"; + $content .= ' <small><i>???</i></small> ' if exists $db{$key}->{'incomplete'}; + $content .= ' <small><i>++</i></small> ' if exists $db{$key}->{'no-end'}; + $content .= ' <small><i>+</i></small> ' if exists $db{$key}->{'no-notify'}; + $startend = 'start: \'' . ts($notify) . '\', end: \'' . ts($end) . '\''; + print "\t{id: $i, key: $skey, $type group: $group, subgroup: 2, subgroupOrder: 4, content: '$content', $startend, style: \'$style\'},\n"; + $i++; + } + + $last_ts = $end; + + last if $i > $max_items; +} + +foreach my $item (@freqs) { + my ($start, $end, $freq) = @$item; + my $startend; + + next if $start > $last_ts; + + $start = $first_ts if $start < $first_ts; + $end = $last_ts if $end > $last_ts; + $startend = 'start: \'' . ts($start) . '\', end: \'' . ts($end) . '\''; + print "\t{id: $i, type: 'range', group: 0, content: '$freq', $startend},\n"; + $i++; +} + +my $end_ts = ts($first_ts + $width_us); +$first_ts = ts($first_ts); + +print <<ENDHTML; + ]); + + function customOrder (a, b) { + // order by id + return a.subgroupOrder - b.subgroupOrder; + } + + // Configuration for the Timeline + var options = { groupOrder: 'content', + horizontalScroll: true, + stack: true, + stackSubgroups: false, + zoomKey: 'ctrlKey', + orientation: 'top', + order: customOrder, + start: '$first_ts', + end: '$end_ts'}; + + // Create a Timeline + var timeline = new vis.Timeline(container, items, groups, options); + + function toggleStackSubgroups() { + options.stackSubgroups = !options.stackSubgroups; + timeline.setOptions(options); + } +ENDHTML + +# foreach my $ts (values %notify) { +# my $time = ts($ts); +# print "\tdate = new Date('$time');\n"; +# print "\ttimeline.addCustomTime(date, undefined);\n"; +# } + +print <<ENDHTML; +</script> +</body> +</html> +ENDHTML -- 2.9.3 _______________________________________________ Intel-gfx mailing list Intel-gfx@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/intel-gfx