diff -r 000000000000 -r dfb7c4ff071f commsfwtools/commstools/svg/parseseq.pl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/commsfwtools/commstools/svg/parseseq.pl Thu Dec 17 09:22:25 2009 +0200 @@ -0,0 +1,2560 @@ +# Copyright (c) 2005-2009 Nokia Corporation and/or its subsidiary(-ies). +# All rights reserved. +# This component and the accompanying materials are made available +# under the terms of "Eclipse Public License v1.0" +# which accompanies this distribution, and is available +# at the URL "http://www.eclipse.org/legal/epl-v10.html". +# +# Initial Contributors: +# Nokia Corporation - initial contribution. +# +# Contributors: +# +# Description: +# parseseq.pl [] +# Arguments: +# -p Include popups (when clicking on messages etc). +# -r , Display rows in the range specified. Row numbers are zero based and inclusive. +# Negative row numbers count backwards from the end row. and/or +# may be omitted, in which case the beginning and ending rows respectively +# are assumed. If "r" argument is not specified, a default of the last 400 rows +# is assumed (equivalent to an argument of "-r -400,"). +# -R The maximum number of rows in each "view" (page). The overall sequence is generated as a number +# of individual HTML/SVG files, each showing a fragment of the sequence. Each of these is +# termed a "view". This option indicates the maximum number of rows per view. Note +# that because the sequence is split into equal sized views, the actual size of each +# view can vary from half values of this parameter to its full value. This parameter +# only limits the maximum size of each view. +# -H On "trimmed" displays (i.e. only displaying last lines), don't +# hide object timelines that don't have any messages within the area +# being displayed (but do have messages outside of that area). +# -x File containing "[DontHideShortName]" section which contains truncated names +# of classes (minus the instance numbering at the end) that should *not* be +# hidden from "trimmed" displays (i.e. displays where only the last lines +# are displayed). +# -M Exclude messages from the display. Used for getting an overview of node and activity creation. +# -v Print on standard output a list of objects which have not been displayed because +# they exchanged no messages or were outside of the displayed view. +# -V Print on standard output some progress messages (useful when parsing long logs that +# take several minutes). +# -f Do not display page links in a fixed position window. +# -n Print node names vertically as opposed to horizontally. +# -N Node to filter available list by +# -A Activity to filter available list by +# -h This help message +# todo: +# - use findObject() within findOrCreateobject() +# - hide objects whose lifetime extends entirely across the displayed area, but exchange no messages in that area +# +# + +#require 'getopts.pl'; +use Getopt::Std; +use HTML::Entities; +use strict; +use Cwd; + +my $version = "2.2 (27/02/08)"; + +getopts("MHpx:N:A:vVr:R:hnf"); +our($opt_M, $opt_H, $opt_p, $opt_x, $opt_N, $opt_A, $opt_v, $opt_V, $opt_r, $opt_R, $opt_h, $opt_n, $opt_f); + +if ($opt_h) +{ + print <] + + Arguments: + + -p Include popups (when clicking on messages etc). + + -r , Display rows in the range specified. Row numbers are zero based and inclusive. + Negative row numbers count backwards from the end row. and/or + may be omitted, in which case the beginning and ending rows respectively + are assumed. If "r" argument is not specified, a default of the last 400 rows + is assumed (equivalent to an argument of "-r -400,"). + + -R The maximum number of rows in each "view" (page). The overall sequence is generated + as a number of individual HTML/SVG files, each showing a fragment of the sequence. + Each of these is termed a "view". This option indicates the maximum number of rows + per view. Note that because the sequence is split into equal sized views, the actual + size of each view can vary from half values of this parameter to its full value. + This parameter only limits the maximum size of each view. + + -H On "trimmed" displays (i.e. only displaying last lines), don't + hide object timelines that don't have any messages within the area + being displayed (but do have messages outside of that area). + + -x File containing "[DontHideShortName]" section which contains truncated names + of classes (minus the instance numbering at the end) that should *not* be + hidden from "trimmed" displays (i.e. displays where only the last lines + are displayed). + + -M Exclude messages from the display. Used for getting an overview of node and activity + creation. + + -v Print on standard output a list of objects which have not been displayed because + they exchanged no messages or were outside of the displayed view. + + -V Print on standard output some progress messages (useful when parsing long logs that + take several minutes). + + -f Do not display page links in a fixed position window. + + -n Print node names vertically as opposed to horizontally. + + -N Node to filter available list by. If set, you will be given a list of node activities + which contain the string you passed in, in the node. Select one of these and it will + generate the diagram only for the lifetime of that activity. + + -A Activity to filter available list by. If set, you will be given a list of node + activities which match the string you passed in, in the activity. + + e.g. + >parseseq -A start \log.seq + Reading sequence file + [1] Conn1 :: ConnectionStart + [2] IPCPr :: IpCprStart + [3] IPProtoCPr :: IPProtoCprStart + [4] AgentCPr :: PRStart + [5] AgentCPr :: CprDataClientStart + [6] AgentSCPr :: AgentSCprStart + Select activity to draw: + ... + + -h This help message +HELP + exit (0); +} + +# +# Flags +# + +# remove objects that are created but have no messages +my $trimSilentFlag = 1; + +# sort objects left-to-right by type +my $sortObjectsFlag = 1; + +my $maxRowsToDisplay = 300; +if ($opt_R) { + die "Parameter to -R option must be between 100 and 1000\n" if $opt_R < 100 || $opt_R > 1000; + $maxRowsToDisplay = $opt_R; +} + +# whether to output timestamps in the form "hh:mm:ss,dd" where the ",dd" represents the number of seconds since the last timestamp (where possible) +my $outputTimeDeltas = 0; + +# constants controlling progress messages +my $sequenceProgressIncrement = 10000; +my $trimProgressTrigger = 1000; +my $trimProgressPercentIncrement = 10; + +# +# constants controlling layout +# + +my $incrementY = 19; +my $fontWidth = 5; +my $fontHeight = 9; +my $leftMargin = 125; +my $leftSeperatorMargin = 55; +my $screenWidth = 50; +my $rightMargin = 30; +my $topMargin = 20; +my $bottomMargin = 22; +my $objectSpacing = 90; +my $arrowWidth = 10; +my $textArrowSpacing = 1; +my $messageSpacingAboveLine = 1; +my $objectVerticalLineSpacing = 2; +my $objectUnderlineToLifelineGap = 2; +my $objectNameRepeatCount = 35; +my $lifelineBottomOverrun = 5; +my $activitySpacing = 6; +my $textMattSize = $activitySpacing * 2 + 2; # text is on top of node lifeline and first two activity lines +my $lifelineTextLeftMargin = 8; +my $rotatedTextOffset = 3; +# +# derived constants +# + +my $topOffset = $topMargin + $objectVerticalLineSpacing; + +# +# colour constants +# + +my $objectNameColour1 = "red"; +my $objectNameColour2 = "blue"; +my $objectNameLightColour1 = "salmon"; +my $objectNameLightColour2 = "rgb(50,50,200)"; +my $aliveColour = "black"; +my $deadColour = "white"; +my $seperatorColour = "rgb(245,245,245)"; +my $receiveColour = "black"; +my $postColour = "green"; + +my $activityColour = "skyblue"; +my $activityEndUnknownColour = "salmon"; + +# magic value for Creation/Deletion unknown + +my $objectUnknown = -2; + +# +# changeable global variables that need to be re-initialised per view drawn +# (see initialiseGlobals()) +# + +my $currentY; + +# +# changeable global variables that need to be initialised once before views are drawn +# + +my $minOrder = 100; +my $maxOrder = 0; + +my $limitedRowText; + +# hash for quick lookup of objects +my %objhash = (); +my @silentObjectsTrimmed = (); +my @messages = (); +my @sequences = (); +my @objects = (); +my %dontHideShortName = (); + +# +# Data Structures: +# +# "objects" is array of hashes: +# { Name => , X =>, Y =>, MsgCount =>, Creation =>, Deletion =>, Order => } +# +# "sequences" is an array of hashes: +# { Action => , Object => Text =>, SrcObj =>, DestObj =>, Msg => } + + +# Input file format: +# +# ... +# +# specifically: +# +# [p|r] +# t +# oc +# od +# l +# +# actions: +# +# p Post +# r Receive +# t Text +# oc Object Create +# od Object Destroyed +# l Label +# +# All fields are space separated text. For example: +# +# P StartFlow SCPR Flow +# + +readIniFile(); +initialiseGlobals(); + +print "Reading sequence file\n" if ($opt_V); +readSequences(); + +if ($opt_N || $opt_A) + { + trimDownToActivity(); + } + +if ($trimSilentFlag) + { + print "Trimming silent objects\n" if ($opt_V); + trimSilentObjects(); + } + +my $beginRow = 0; +my $endRow = 99999; + +# Count the number of rows that would be displayed +# (doesn't actually draw them because of "0" argument) + +print "Generating sequence pages\n" if ($opt_V); + +my $totalRows = drawSequences($beginRow, $endRow, 0); +fixObjectsWithUnknownCreationDeletion(); + +my $rowsToDisplay = $totalRows; + +if ($opt_r) { + $rowsToDisplay = processRowArgument($totalRows); + } + +createSubDir(); + +if ($totalRows <= $maxRowsToDisplay) { + drawView(0, $totalRows - 1, 0, 0); +} else { + my $viewsToDisplay = int($rowsToDisplay / $maxRowsToDisplay) + 1; + my $rowsPerView = int(($rowsToDisplay + $viewsToDisplay - 1) / $viewsToDisplay); + my $viewNumber = 0; + createViewMap(); + while ($beginRow < $totalRows) { + $endRow = $beginRow + $rowsPerView - 1; + if ($endRow >= $totalRows) { + drawView($beginRow, $totalRows - 1, $viewNumber, $viewNumber); # last view + } else { + drawView($beginRow, $endRow, $viewNumber, $viewsToDisplay - 1); + } + ++$viewNumber; + $beginRow = $endRow + 1; + } + closeViewMap($viewNumber); +} + +print "\n" if ($opt_V); + +if ($opt_v) + { + printArray("\nObjects without any message exchange", \@silentObjectsTrimmed); + } + + +# +# Generate the SVG and HTML files for a particular "view" (range of rows) +# + +sub drawView($$$$) { + my ($beginRow, $endRow, $viewNumber, $lastViewNumber) = @_; + print "Generating page $viewNumber of $lastViewNumber\r" if ($opt_V); + if ($opt_H != 1) + { + hideObjectsOutsideOfView($beginRow, $endRow); + } + + my $screenWidth = calculateObjectColumnPositions(); + my $screenHeight = (($endRow - $beginRow + 1) * $incrementY) + $topMargin + $bottomMargin; + + # + # Filename convention for views: log0.svg, log1.svg, ..., log.svg, log.svg - i.e. log.svg is the last view + # (considered the one most interesting and hence gets undecorated filename). + # + + my $fileName = "log"; + if ($viewNumber != $lastViewNumber) { + $fileName .= $viewNumber; + } + + open SVG, ">html/${fileName}.svg" || die "Cannot open html/${fileName}.svg for writing\n"; + #open RTTTL, ">html/${fileName}.rtttlpre" || die "Cannot open html/${fileName}.rttlpre for writing\n"; + + outputDocHeader($screenWidth, $screenHeight); + drawObjectNames(0); + updateObjectViewList($viewNumber); + drawObjectLifelines($beginRow, $endRow, $screenHeight); + drawActivities($beginRow, $endRow, $screenHeight, $viewNumber); + my @labelsOnPage = (); + my @objectsDestroyed = (); + my @objectsCreated = (); + drawSequences($beginRow, $endRow, 1, \@labelsOnPage, \@objectsCreated, \@objectsDestroyed); + outputViewMap($viewNumber, $lastViewNumber, \@labelsOnPage, \@objectsCreated, \@objectsDestroyed); + outputDocFooter(); + + close SVG; + #close RTTTL; + + outputHTMLEmbedder($screenWidth, $screenHeight, $viewNumber, $lastViewNumber, $fileName); + if ($opt_v) { + printHiddenObjects("\nObjects outside of displayed view $viewNumber"); + } + + resetForNextView(); +} + +sub createSubDir() { + if (! -d html) { + mkdir "html" || die "Cannot create 'html' subdirectory\n"; + } +} +sub resetForNextView() { + # objects to hide must be calculated afresh per view + unhideAllObjects(); + initialiseGlobals(); +} + +# +# (re)initialise globals required per view drawn +# +sub initialiseGlobals() { + $currentY = $topMargin; + } + + +#################### +# Message routines +#################### + +sub findOrCreateMessage($) + { + my $msgName = $_[0]; + my $msg; + foreach $msg (@messages) { + if ($msg eq $msgName) { + return \$msg; + } + } + push @messages, $msgName; + return \$messages[$#messages]; + } + +################### +# Object routines +################### + +sub readSequences() +{ + my $line = 0; + while (<>) { + die unless s/^(\w+)\s+//; + my $action = $1; + if ($action eq "t") + { + # Text + # "t " + # Place text item on the timeline of a particular object + my $pos; + if (s/^([^\s]*)\s+//) { $pos = $1; } + my $objRef = findOrCreateObject($pos); + chomp; + my $text = $_; + push @sequences, { Action => $action, Object => $objRef, Text => $text }; + } + elsif ($action eq "oc") + { + # Object Create + # oc + split; + my $objName = shift @_; + my $order = shift @_; + + my $objRef = findOrCreateObject($objName); + $objRef->{Order} = $order; + if ($order < $minOrder) + { + $minOrder = $order; + } + if ($order > $maxOrder) + { + $maxOrder = $order; + } + chomp; + push @sequences, { Action => $action, Object => $objRef }; + } + elsif ($action eq "od") + { + # Object Destroyed + # od + split; + my $objName = shift @_; + my $objRef = findOrCreateObject($objName); + chomp; + push @sequences, { Action => $action, Object => $objRef }; + } + elsif ($action eq "ac") + { + # Activity Created + # ac + split; + my $objRef = findObjectByName(shift @_); + if ($objRef != 0) + { + my $addr = shift @_; + my $name = shift @_; + push @sequences, { Action => $action, Object => $objRef, Addr => $addr, Name => $name }; + } + chomp; + } + elsif ($action eq "ad") + { + # Activity Destroyed + # ad + split; + my $addr = shift @_; + chomp; + push @sequences, { Action => $action, Addr => $addr }; + } + elsif ($action eq "p" || $action eq "r" || $action eq "sc") + { + # Post/Receive/SynchronousCall + # [P|R|SC] + my $msg; + if (/^"/) + { + s/^"([^"]+)"\s+//; + $msg = $1; + } + else + { + s/^(\S+)\s+//; + $msg = $1; + } + my $msgRef = findOrCreateMessage($msg); + split; + my $srcRef = findOrCreateObject(shift @_); + my $destRef = findOrCreateObject(shift @_); + $srcRef->{MsgCount}++; + $destRef->{MsgCount}++; + if (!$opt_M) { + push @sequences, { Action => $action, Msg => $msgRef, SrcObj => $srcRef, DestObj => $destRef }; + } + } + elsif ($action eq "l" || $action eq "pn" || $action eq "ts") { + # Label (on lefthand side - for time stamp) + # l + # + # Popup - associated with [n]ext message + # pn + chomp; + push @sequences, { Action => $action, Text => $_ }; + } + ++$line; + if ($opt_V) { + if ($line >= $sequenceProgressIncrement && ($line % $sequenceProgressIncrement == 0)) { + print "line $line\r"; + } + } + } + if ($opt_V && $line >= $sequenceProgressIncrement) { + print "line $line (complete)\n"; + } +} + +sub findObjectByName() + { + my $objName = $_[0]; + if (exists($objhash{$objName})) { + return $objhash{$objName} + } + return 0; + } + +sub findOrCreateObject() + { + my $objName = $_[0]; + my $obj = &findObjectByName($objName); + if ($obj != 0) { + return $obj; + } + + push @objects, { Name => $objName, X => 0, Y => 0, MsgCount => 0, Order => 0, Creation => $objectUnknown, Deletion => $objectUnknown, Hide => 0 }; + + $obj = $objects[$#objects]; + $objhash{$objName} = $obj; + + return $obj; + } + +sub printArray($$) + { + my ($title, $arrayRef) = @_; + print $title, ":\n"; + foreach my $i (@$arrayRef) + { + print "$i\n"; + } + } + + +sub printHiddenObjects($) + { + print $_[0], ":\n"; + foreach my $i (@objects) + { + if ($i->{Hide} == 1) + { + print $i->{Name}, "\n"; + } + } + } + +sub processRowArgument($) +# +# Process the "-r ," argument. If either and/or are missing, +# then assume that the beginning and ending row are the first and last row respectively. +# +# Input argument: Total number of rows in sequence +# Outputs: Beginning and ending row to display (in $beginRow, $endRow) +# Return value: Total number of rows to display + { + my $totalRows = $_[0]; + die unless $totalRows; + + if (! $opt_r) { + $opt_r = "$maxRowsToDisplay,"; + } + + $opt_r =~ m/(-?[0-9]*),(-?[0-9]*)/; + + if (! $1) + { + $beginRow = 0; + } + else + { + $beginRow = $1; + if ($beginRow < 0) { + $beginRow += $totalRows; + if ($beginRow < 0) { + $beginRow = 0; + } + } + } + if (! $2) + { + $endRow = $totalRows - 1; + } + else + { + $endRow = $2; + if ($endRow < 0) { + $endRow += $totalRows; + if ($endRow < 0) { + $endRow = 0; + } + } + } + + die "Invalid row range specification ($beginRow, $endRow)" if ($beginRow > $endRow); + + print "Total rows = $totalRows\n"; + + my $newTotalRows = $endRow - $beginRow + 1; + if ($newTotalRows < $totalRows) { + my $endRowText = ($endRow == $totalRows - 1) ? "the end (row $endRow)" : $endRow; + SetLimitedRowText("Only displaying $newTotalRows rows - row $beginRow to $endRowText"); + print limitedRowText(), "\n"; + return $newTotalRows; + } + else { + return $totalRows; + } + } + +sub SetLimitedRowText($) + { + $limitedRowText = $_[0]; + } + +sub limitedRowText() + { + return $limitedRowText; + } + +# +# Set X position for each object. Return final screen width. +# + +sub calculateObjectColumnPositions() + { + # loop through each order + my $x = $leftMargin; + my $colno = 1; + my $order; + + # + # Objects can either be placed by sorting left-to-right by type, or else + # placing objects as they are created. + # + + if ($sortObjectsFlag == 1) + { + for ($order = 0 ; $order <= $maxOrder ; $order++) + { + foreach my $obj (@objects) + { + if ($obj->{Hide} == 1) + { + next; + } + if ($obj->{Order} == $order) + { + $obj->{X} = $x; + $x += $objectSpacing; + + $obj->{colno} = $colno; + $colno++; + +# print "$obj->{Name}, order $obj->{Order}, X $obj->{X}\n"; + } + } + } + } + else + { + foreach my $obj (@objects) + { + if ($obj->{Hide} == 1) + { + next; + } +# print "$obj->{Name}, order $obj->{Order}, X $obj->{X}\n"; + $obj->{X} = $x; + $x += $objectSpacing; + + $obj->{colno} = $colno; + $colno++; + } + } + return $x; + } + +sub hideObjectsOutsideOfView($$) + { + my ($beginRow, $endRow) = @_; + + my $obj; + foreach my $obj (@objects) { + # Don't need this as we now track process creation/destruction properly + #if ($obj->{Name} =~ /^!/) + # { + # next; + # } + + if (IsCrossingRange($beginRow, $endRow, $obj->{Creation}, $obj->{Deletion}) != 1) + { + $obj->{Hide} = 1; + } + } + } + +sub unhideAllObjects() + { + foreach my $obj (@objects) { + $obj->{Hide} = 0; + } + } + +sub trimDownToActivity() +{ + my @activities = undef; + my $i = 0; + + for ($i = 0; $i < $#sequences; $i++) + { + my $s = $sequences[$i]; + + if ($s->{Action} eq "ac" && ($opt_N eq "" || $s->{Object}{Name} =~ /$opt_N/i) && ($opt_A eq "" || $s->{Name} =~ /$opt_A/i)) + { + push @activities, { Index => $i, Addr => $s->{Addr} }; + my $ai = $#activities; + print "[$ai] ".$s->{Object}{Name}." :: ".$s->{Name}."\n"; + } + } + print "Select activity to draw: "; + my $selected = ; + chomp($selected); + $selected += 0; + + + my $adfound = 0; + + my $selectedact = $activities[$selected]; + my $selectedseq = $sequences[$selectedact->{Index}]; + + my %usednodes = (); + $usednodes{$selectedseq->{Object}} = 1; + $selectedseq->{Object}{MsgCount}++; + for ($i = 0; $i < $#sequences ; $i++) + { + if ($i >= $selectedact->{Index} && $adfound == 0) + { + my $s = $sequences[$i]; + my $action = $s->{Action}; + if ($action eq "p" || $action eq "r" || $action eq "sc") + { + if ($usednodes{$s->{SrcObj}} == 1) + { + $usednodes{$s->{DestObj}} = 1; + } + else + { + $s->{KillMe} = 1; + } + } + if ($action eq "ad" && $s->{Addr} eq $selectedact->{Addr}) + { + $adfound = 1; + } + } + else + { + # remove message posts + my $action = $sequences[$i]->{Action}; + if ($action eq "p" || $action eq "r" || $action eq "sc" || $action eq "t" || $action eq "ac" || $action eq "ad") + { + $sequences[$i]->{KillMe} = 1; + } + } + } + + for ($i = $#sequences; $i >= 0; $i--) + { + if ($sequences[$i]->{KillMe} == 1) + { + my $action = $sequences[$i]->{Action}; + if ($action eq "p" || $action eq "r" || $action eq "sc") + { + $sequences[$i]->{SrcObj}{MsgCount}--; + $sequences[$i]->{DestObj}{MsgCount}--; + } + splice @sequences, $i, 1; + } + } + +} + +sub trimSilentObjects() + { + # get rid of objects that didn't end up with any messages sent to/from them. + # Exceptions are objects beginning with "!", which are forced to be displayed (for example, + # exe timelines displaying socket/host resolver operations). + my $i; + my %trimObjects = (); + # NOTE: in the following "for" loop, "i" is incremented in the body. Beware if using "next" etc. + # + + my $objectCount = scalar(@objects); + my $percentIncrement = 0; + + if ($opt_V && $objectCount > $trimProgressTrigger) { + $percentIncrement = $objectCount/$trimProgressPercentIncrement; + } + + for ($i = 0 ; $i < $objectCount ; ) { # we are splicing @objects in the loop, so do we need to recalculate scalar() every time? + my $objectName = $objects[$i]->{Name}; + if ($objectName =~ /^!/) + { + ++$i; + next; + } + + $objectName =~ s/[0-9]+$//; + if ($dontHideShortName{$objectName}) + { + ++$i; + next; + } + + if ($objects[$i]->{MsgCount} == 0) + { + push @silentObjectsTrimmed, $objects[$i]->{Name} if ($opt_v); + + $trimObjects{$objects[$i]} = 1; + $objects[$i]->{KillMe} = 1; +# deleteObject($i); + $objectCount = scalar(@objects); # is this needed given we aren't incrementing i? + $percentIncrement = $objectCount/$trimProgressPercentIncrement if ($percentIncrement); + # Don't increment "i" here + ++$i; + } + else + { + ++$i; + } + if ($percentIncrement && $i > $percentIncrement && ($i % $percentIncrement) == 0) { + print int($i * 100 / $objectCount), "% complete\r"; + } + } + + for (my $i = 0; $i < scalar(@sequences); $i++) { + my $action = $sequences[$i]->{Action}; + my $obj = $sequences[$i]->{Object}; + if ( ($action eq "t" || $action eq "oc") && exists($trimObjects{$obj})) { + $sequences[$i]->{KillMe} = 1; + } + } + + for (my $i = $#sequences; $i >= 0; $i--) { + if ($sequences[$i]->{KillMe} == 1) { + splice @sequences, $i, 1; + } + } + + for (my $i = $#objects; $i >= 0; $i--) { + if ($objects[$i]->{KillMe} == 1) { + splice @objects, $i, 1; + } + } + + print "100% complete\n" if ($percentIncrement); # last percentage printed in loop may be, say, "88%" so make it look 100% complete + } + +sub deleteObject($) + { + my ($i) = @_; # object index to delete + my $j; + + for ($j = 0 ; $j < scalar(@sequences) ; ) + { + my $action = $sequences[$j]->{Action}; + if ( (($action eq "t") || ($action eq "oc")) && + ($sequences[$j]->{Object} == $objects[$i]) ) + { + splice @sequences, $j, 1; + } + else + { + ++$j; + } + } + splice @objects, $i, 1; + } + +sub drawObjectNames($) + { + my ($repeatedNameFlag) = @_; + my $i; + my $colour; + foreach $i (@objects) { + if ($i->{Hide} == 1) + { + next; + } + my $colour; + if ($repeatedNameFlag == 1) + { + if ($i->{Order} == 0) + { + $colour = "!silver"; + } + else + { + $colour = (($i->{Order} % 2) == 0) ? $objectNameLightColour1 : $objectNameLightColour2; + $colour = "!" . $colour; + } + + } + else + { + if ($i->{Order} == 0) + { + $colour = ""; + } + else + { + $colour = (($i->{Order} % 2) == 0) ? $objectNameColour1 : $objectNameColour2; + } + } + my $name = $i->{Name}; + $name =~ s/^!//; + # **** REMOVE THIS HACK for debugEvent() **** + if ($opt_n) + { + my $x = $i->{X} + $rotatedTextOffset; + my $transform="rotate(90, $x, $currentY)"; + # If node names are displayed vertically, then the text matt which creates a break in the + # node lifeline for the node name text is not required (a "!" in front of the colour name). + $colour =~ s/^!//; + outputText($x + $rotatedTextOffset, $name, $currentY, "begin", $colour, qq{id="$name" onclick="debugEvent(evt)" transform="$transform" font-size="12" font-family="sans-serif"}); + } + else + { + outputText($i->{X}, $name, $currentY, "middle", $colour, qq{id="$name" onclick="debugEvent(evt)"}); + } + } + incrementY(); + } + +# +# Test whether a value is within a certain (start,end] range inclusive. +# +# Arguments: value, start, end +# +# If start and/or end are -1, then this is a "wildcard" match +# + +sub IsInRangeInclusive($$$) + { + my ($value, $start, $end) = @_; + return ($start == -1 || $value >= $start) && ($end == -1 || $value <= $end); + } + +# +# Test whether one range crosses another. +# +# if $start and/or $end are $objectUnknown, this means 1 and infinity respectively. +# + +sub IsCrossingRange($$$$) + { + my ($startRange, $endRange, $start, $end) = @_; + if ($start == $objectUnknown) + { + return ($end == $objectUnknown || $end >= $startRange); + } + elsif ($end == $objectUnknown) + { + return ($start == $objectUnknown || $start <= $endRange); + } + else + { + return ($start <= $endRange && $end >= $startRange); + } + } + +# +# Activity related routines +# + +# Create an activity hash associated with the object in question. Parameters: +# Reference to the object +# Unique activity address +# Returns: reference to the new activity hash. +# +sub createObjectActivity($$) + { + my ($objRef, $addr) = @_; + my $actRef; + if (($actRef = findObjectActivity($objRef, $addr)) != 0) + { + print "Duplicate activity creation - $addr\n"; + return 0; + } + else + { + my $newActRef = { Addr => $addr, End => -1 }; + push @{$objRef->{Activities}}, $newActRef; + return $newActRef; + } + } + +# Find an existing activity hash associated with the object in question. Parameters: +# Reference to the object +# Unique activity address (string) +# Returns: reference to the new activity hash. +# + +sub findObjectActivity($$) + { + my ($objRef, $addr) = @_; + + if (defined($objRef->{Activities})) + { + my $actRef; + foreach $actRef (@{$objRef->{Activities}}) + { + if ($actRef->{Addr} eq $addr) + { + return $actRef; + } + } + } + return 0; + } + +# Find the activity and object associated with an activity address. Parameters: +# Unique activity address (string) +# Reference to a variable that will receive the object reference (out) +# Returns: reference to the activity. + +sub findActivity($$) + { + my ($addr, $returnObj) = @_; + my $objRef; + my $actRef; + foreach $objRef (@objects) + { + if (defined($objRef->{Activities})) + { + $actRef = findObjectActivity($objRef, $addr); + if ($actRef != 0) + { + ${$returnObj} = $objRef; + return $actRef; + } + } + } + return 0; + } + +# Allocate a free vertical activity line associated with the object. +# Returns: vertical activity line (number starting from 1). +sub allocateObjectActivityLine($) + { + my ($objRef) = @_; + if (! defined ($objRef->{LineMap})) + { + # LineMap is actually a bitmap. Allocate first line. + $objRef->{LineMap} = 1; + return 1; + } + else { + my $lineMap = $objRef->{LineMap}; + my $freeBit = ~($lineMap) & ($lineMap + 1); + die "Zero allocated bit - $lineMap\n" if ($freeBit == 0); + $objRef->{LineMap} |= $freeBit; + my $bitNumber = 1; + while ($freeBit >>= 1) + { + ++$bitNumber; + } + return $bitNumber; + } + } + +sub freeObjectActivityLine($$) + { + my ($objRef,$line) = @_; + die "Freeing undefined/empty LineMap\n" if (!defined($objRef->{LineMap}) || $objRef->{LineMap} == 0); + $objRef->{LineMap} &= ~(1 << ($line-1)); + } + +sub closeActivity($$$) + { + my ($actRef, $objRef, $endRow) = @_; + $actRef->{End} = $endRow; + $actRef->{Addr} = -1; # address can be re-used, so have to clear it + freeObjectActivityLine($objRef, $actRef->{Line}); + } + +# +# Draw message sequences +# +# Args: drawFlag 1 = do draw, 0 = don't draw +# Return: number of rows in diagram for sequences +# +# ToDo: +# Partitioning: how do popups work ? +# + +sub drawSequences($$$$$$) + { + my ($startRow, $endRow, $drawFlag, $labelsOnPageRef, $objectsCreatedRef, $objectsDestroyedRef) = @_; + my $nextLabel = ""; + + # startRow/endRow = start/end row of displayed area (inclusive) + # absoluteRow = increments through all rows that have an object to display, whether in displayed area or not. + # rows = Increments for each row that contains an object to display, but only when $absoluteRow moves + # into the displayed area. + + my $rows = 0; + my $absoluteRow = 0; + my $drawDone = 0; + my $inRange = 0; + my $lastPopupText = ""; # text of last "pn" action + + foreach my $ref (@sequences) { + my $action = $ref->{Action}; + + $inRange = IsInRangeInclusive($absoluteRow, $startRow, $endRow); + if ($action eq "t") { + # text + if ($inRange) { + ++$rows; + if ($drawFlag == 1) { + my $objX = $ref->{Object}->{X}; + my $text = $ref->{Text}; + # Text matt no longer required because "t" primitives are not drawn centred on the node lifeline anymore. + # my $colour = "!"; + my $colour = ""; + if ($text =~ /\s*\{(\d+,\d+,\d+)\}\s*(.+)/) { + $colour .= "rgb($1)"; + $text = $2; + } + my $attrs = generatePopupAttrs(\$lastPopupText, qq{id="$ref->{Object}->{Name}"}); + outputText($objX + $lifelineTextLeftMargin, $text, $currentY, "begin", $colour, $attrs); + $drawDone = 1; + # support for putting threads into the view map + $text = $ref->{Text}; + if ($text =~ m/^\(Thread "(.*)" (\w+)\)$/) { + if ($2 eq "Created") { + push @{$objectsCreatedRef}, $1; + } elsif ($2 eq "Destroyed") { + push @{$objectsDestroyedRef}, $1; + } + } + } else { + fakeUpCreationDeletionIfRequired($ref->{Object}, $absoluteRow); + } + } + # displayable row, so increment absoluteRow. + ++$absoluteRow; + } elsif ($action eq "ts") { + # text + if ($inRange) { + ++$rows; + if ($drawFlag == 1) { + my $text = $ref->{Text}; + outputTextSeperator($text, $currentY); + $drawDone = 1; + } + } + # displayable row, so increment absoluteRow. + ++$absoluteRow; + } + elsif ($action eq "oc") { + # object created + + # ensure that $objectCreation contains the very first creation point and + # is not subsequently overwritten if we happen to come across another "oc". + # This would only happen if an object was deleted and another object of + # the same type was created with the same address, so it looks to us like the + # same object being created/destroyed in several cycles. + + if ($drawFlag == 0) { + if ($ref->{Object}->{Creation} == $objectUnknown) { + $ref->{Object}->{Creation} = $absoluteRow; + } + } + else { + if ($inRange) { + push @{$objectsCreatedRef}, $ref->{Object}; + } + } + } + elsif ($action eq "od") + { + # object destroyed + # + # Note: okay for objectDeletion to be overwritten by subsequent "od" actions such + # that it is left containing the last "od" in the sequence for that object. See comment + # in "oc" about this circumstance. + + if ($drawFlag == 0) + { + $ref->{Object}->{Deletion} = $absoluteRow; + } + else { + if ($inRange) { + push @{$objectsDestroyedRef}, $ref->{Object}; + } + } + } + elsif ($action eq "l") + { + # left label + if ($outputTimeDeltas && $nextLabel =~ m/^(\d\d):(\d\d):(\d\d)(,(\d+))?/) # hh:mm:ss(dur) + { + use integer my $dur; + my ($h1,$m1,$s1,$dur) = ($1,$2,$3,$5); + if ($ref->{Text} =~ m/^(\d\d):(\d\d):(\d\d)/) + { + my ($h2,$m2,$s2) = ($1,$2,$3); + $dur += ($h2 - $h1) * 3600 + ($m2 - $m1) * 60 + ($s2 - $s1); + $nextLabel = $h2 . ":" . $m2 . ":" . $s2 . "," . $dur; + } + } + else + { + $nextLabel = $ref->{Text}; + } + if ($drawFlag == 1 && $inRange) { + push @{$labelsOnPageRef}, $nextLabel; + } + } + elsif ($action eq "pn") + { + # popup + if ($drawFlag == 1 && $opt_p && $inRange) + { + $lastPopupText = $ref->{Text}; + # not setting $drawDone=1 because popups do not occupy a row + } + } + elsif ($action eq "ac") + { + # Activity Created + # (does not occupy a row, hence no need to set $drawDone) + # - add activity to list of activities in object and set start row attribute + # - determine free vertical position to draw activity line + if ($drawFlag == 0) # just do it once on first invocation of drawSequences() + { + my $actRef = findObjectActivity($ref->{Object}, $ref->{Addr}); + if ($actRef != 0) + { + # We have encountered a duplicate "ac" without an intervening "ad". + # In other words, we've come across two "activity create" tags with the + # same memory address without an intervening "activity delete" tag + # closing off the first memory address. The same memory address + # has been re-used. Mark the first of the activities as having ended here + # (at the point where the second activity starts) and mark its activity line + # in red - it's the best we can do. + $actRef->{EndUnknown} = 1; + closeActivity($actRef, $ref->{Object}, $absoluteRow); + } + + my $newActRef = createObjectActivity($ref->{Object}, $ref->{Addr}); + $newActRef->{Start} = $absoluteRow; + $newActRef->{Line} = allocateObjectActivityLine($ref->{Object}); + $newActRef->{Name} = $ref->{Name}; + } + } + elsif ($action eq "ad") + { + # Activity Destroyed + # (does not occupy a row, hence no need to set $drawDone) + # - find corresponding activity and set end row attribute + # - update our list of free vertical activity lines associated with the object + if ($drawFlag == 0) # just do it once on first invocation of drawSequences() + { + my $objRef; + my $actRef = findActivity($ref->{Addr}, \$objRef); + if ($actRef != 0) + { + closeActivity($actRef, $objRef, $absoluteRow); + } + else + { +# print "Destroyed activity not found - $ref->{Addr}\n"; + } + } + } + else + { + # post or receive + if ($inRange) + { + ++$rows; + if ($drawFlag == 1) + { + my $msg = ${$ref->{Msg}}; + my $srcX = $ref->{SrcObj}->{X}; + my $destX = $ref->{DestObj}->{X}; + my $align; + my $colour; + my $endType; + if ($action eq "p") { + $align = "tail"; + $colour = $postColour; + $endType = "postArr"; + } + elsif ($action eq "r") { + $align = "head"; + $colour = $receiveColour; + $endType = "recvArr"; + } + elsif ($action eq "sc") { + $align = "head"; + $colour = $receiveColour; + $endType = "callArr"; + } + else { + die("Unknown action"); + } + my $lineName = $ref->{SrcObj}->{Name} . "_" . $ref->{DestObj}->{Name}; + my $attrs = generatePopupAttrs(\$lastPopupText, qq{id="$lineName"}); + outputLabelledLine($srcX, $destX, $msg, $colour, $endType, $align, $attrs); + + #print RTTTL ($ref->{SrcObj}->{colno}.",".$ref->{DestObj}->{colno}."\n"); + $drawDone = 1; + } + else + { + # If the object has no creation point specified, just take the first message as the + # creation point. This is to ensure that such objects (generally with hex numbers as object names + # due to fragmental logs) do not appear on every single SVG page. + fakeUpCreationDeletionIfRequired($ref->{SrcObj}, $absoluteRow); + fakeUpCreationDeletionIfRequired($ref->{DestObj}, $absoluteRow); + } + } + ++$absoluteRow; + } + + if ($drawDone == 1) { + if ($nextLabel) + { + outputLeftLabel($nextLabel); + $nextLabel = ""; + } + incrementY(); + $drawDone = 0; + } + + if ($absoluteRow != 0 && (($absoluteRow) % $objectNameRepeatCount) == 0) + { + # Note: have (possibly) incremented absoluteRow above, so re-calculate $inRange + $inRange = IsInRangeInclusive($absoluteRow, $startRow, $endRow); + if ($inRange) + { + # drawObjectNames() increments Y once, and we increment Y once for + # spacing, so we occupy two rows here. + $rows += 2; + if ($drawFlag == 1) + { + drawObjectNames(1); + incrementY(); + } + } + $absoluteRow += 2; + } + } + return $rows; + } + +sub fakeUpCreationDeletionIfRequired($$) + { + my ($ref,$row) = @_; + if ($ref->{Name} =~ /[0-9a-f]{8}/) + { + if ($ref->{Creation} == $objectUnknown) + { + $ref->{Creation} = $row; + #print "Object $srcObjRef->{Name} fake creation $absoluteRow\n"; + } + if ($ref->{Deletion} == $objectUnknown) + { + $ref->{FakeDeletion} = $row; + #print "Object $ref->{SrcObj}->{Name} fake deletion $absoluteRow\n"; + } + } + } + +sub fixObjectsWithUnknownCreationDeletion() + { + foreach my $ref (@objects) { + if ($ref->{Hide} == 1) { + next; + } + if ($ref->{Deletion} == $objectUnknown && $ref->{FakeDeletion}) { + $ref->{Deletion} = $ref->{FakeDeletion}; + #print "Object $ref->{Name} fixing deletion to $ref->{FakeDeletion}\n"; + } + } + } + +# +# Generate SVG attributes suitable for an element with a popup. +# +# inputs: +# textRef Reference to a string variable which, when non-null, indicates +# that the attributes should additionally contain an onclick popup. +# attrs Initial attribute values. +# + +sub generatePopupAttrs($$) + { + my ($textRef,$attrs) = @_; + if ($opt_p && ${$textRef} ne "") + { + # non-null popup text, so append popup attribute + $attrs .= qq{ onclick="popup(evt,'${$textRef}')"}; + # nullify the text - important not to leave behind stray popup text + # that can inadvertantly be utilised in a later seq primitive that does + # not specify a "pn" seq primitive. + ${$textRef} = ""; + } + return $attrs; + } + +sub relativeRowToY($) + { + return $topOffset + ($_[0] + 1) * $incrementY; + } + +# +# Draw the vertical object lifelines between the object creation and destruction points +# +# main inputs: global $screenHeight + +# Partitioning: need to modify this to take into account the beginning and ending row of the partition being +# displayed. + +sub drawObjectLifelines($$$) + { + my ($startRow, $endRow, $screenHeight) = @_; + my $x; + my $lifelineBottomOfScreenY = $screenHeight - $topOffset + $lifelineBottomOverrun; + my $afterLastRow = $endRow + 1; + my $beforeFirstRow = $startRow - 1; + +#print "\ndrawObjectLifelines(startRow $startRow, endRow $endRow, screenHeight $screenHeight)\n"; + + foreach my $i (@objects) + { + if ($i->{Hide} == 1) + { + next; + } + + $x = $i->{X}; + + my $creationRow = $i->{Creation}; + my $deletionRow = $i->{Deletion}; + +# print "Before: $i->{Name}, creationRow $creationRow, deletionRow $deletionRow\n"; + + if ($creationRow == $objectUnknown) + { + $creationRow = $beforeFirstRow; + } + + if ($deletionRow == $objectUnknown) + { + $deletionRow = $afterLastRow; + } + + my $deletionY; + my $creationY; + +# print "After: $i->{Name}, creationRow $creationRow, deletionRow $deletionRow\n"; + + my $idAttr = qq{id="$i->{Name}"}; + if ($deletionRow < $startRow || $creationRow > $endRow) + { + outputVerticalLine($x, $topOffset, $lifelineBottomOfScreenY, $deadColour, ""); + } + elsif ($creationRow < $startRow && $deletionRow > $endRow) + { + outputVerticalLine($x, $topOffset, $lifelineBottomOfScreenY, $aliveColour, $idAttr); + } + elsif ($creationRow < $startRow && IsInRangeInclusive($deletionRow, $startRow, $endRow)) + { +# $deletionY = $topOffset + ($deletionRow - $startRow + 1) * $incrementY; + $deletionY = relativeRowToY($deletionRow - $startRow); + outputVerticalLine($x, $topOffset, $deletionY, $aliveColour, $idAttr); + outputVerticalLine($x, $deletionY, $lifelineBottomOfScreenY, $deadColour, ""); + } + elsif ($deletionRow > $endRow && IsInRangeInclusive($creationRow, $startRow, $endRow)) + { + $creationY = relativeRowToY($creationRow - $startRow); +# $creationY = $topOffset + ($creationRow - $startRow + 1) * $incrementY; + outputVerticalLine($x, $topOffset, $creationY, $deadColour, ""); + outputVerticalLine($x, $creationY, $lifelineBottomOfScreenY, $aliveColour, $idAttr); + } + else + { +# $creationY = $topOffset + ($creationRow - $startRow + 1) * $incrementY; +# $deletionY = $topOffset + ($deletionRow - $startRow + 1) * $incrementY; + $creationY = relativeRowToY($creationRow - $startRow); + $deletionY = relativeRowToY($deletionRow - $startRow); + outputVerticalLine($x, $topOffset, $creationY, $deadColour, ""); + outputVerticalLine($x, $creationY, $deletionY, $aliveColour, $idAttr); + outputVerticalLine($x, $deletionY, $lifelineBottomOfScreenY, $deadColour, ""); + } + } + } + +sub drawActivities($$$$) + { + my ($startRow, $endRow, $screenHeight, $viewNumber) = @_; +# print "drawActivities(startRow $startRow, endRow $endRow, screenHeight $screenHeight)\n"; + my $x; + my $beforeFirstRow = $startRow - 1; + my $afterLastRow = $endRow + 1; + my $bottomOfScreenY = $screenHeight - $topOffset + $lifelineBottomOverrun; + foreach my $obj (@objects) + { + if ($obj->{Hide} == 1) + { + next; + } + + if (defined ($obj->{Activities})) + { + foreach my $act (@{$obj->{Activities}}) + { +# print "\t$obj->{Name} : ", $act->{Start}, " -> ", $act->{End}, ", $act->{Line}, $act->{Addr}\n"; + my $startAct = $act->{Start}; + my $endAct = $act->{End}; + my $startY; + my $endY; + my $startContinuation; + my $endContinuation; + + # "EndUnknown" indicates that the activity endpoint is not known, so mark + # it in a different colour to highlight this. Note that {End} may still + # be set in this case - it just means that is a hint as to the last possible place + # where it could have been terminated, not necessarily that it actually terminated + # at that point. Also, {End} may be -1, which means we don't even have a hint + # as to where it terminated, so just assume the node termination point. + my $colour = $act->{EndUnknown} == 1 ? $activityEndUnknownColour : $activityColour; + if ($endAct == -1) # unknown end point + { + # don't know activity end (logging missing). Trim it to the object destruction + # point (if known). + if ($obj->{Deletion} != $objectUnknown) + { + $endAct = $obj->{Deletion}; + } + else + { + $endAct = $afterLastRow; + } + $colour = $activityEndUnknownColour; + } + if ($startAct > $endRow || $endAct < $startRow) + { + # not visible at all in this page + next; + } + if ($startAct < $startRow) + { + $startContinuation = 1; + $startY = $topOffset; + } + else + { + $startContinuation = 0; + $startY = relativeRowToY($startAct - $startRow); + } + + if ($endAct > $endRow) { + $endContinuation = 1; + $endY = $bottomOfScreenY; + } + else + { + $endContinuation = 0; + $endY = relativeRowToY($endAct - $startRow); + } +# print "\t\tstartY = $startY, endY = $endY\n"; + $x = $obj->{X}; + my $activityX = $x + ($act->{Line} * $activitySpacing); + my $topCornerSize = 5; + my $endUpwardsOffset = 10; + + # For activities that span multiple pages, ensure the name of the activity pop up box (i.e. when you + # click on the activity line) includes the page where the activity started (e.g. "PRStart (p11)"). + # Just do this for pages after the one where the activity starts. + my $pages = ""; + if ($startContinuation == 0) # note page where activity started + { + $act->{StartPage} = $viewNumber; + } + else + { + $pages = " (p$act->{StartPage})"; + } + + my $event = qq{ onclick="popup(evt, '$act->{Name}$pages')" }; + if ($startContinuation == 1 && $endContinuation == 1) + { + outputVerticalLine($activityX, $startY, $endY, $colour, $event); + } + elsif ($startContinuation == 1 && $endContinuation == 0) + { + $endY -= $endUpwardsOffset; + outputVerticalLine($activityX, $startY, $endY - $topCornerSize, $colour, $event); + outputLine($activityX, $endY - $topCornerSize, $x, $endY, $colour, ""); + } + elsif ($startContinuation == 0 && $endContinuation == 1) + { + outputLine($x, $startY, $activityX, $startY + $topCornerSize, $colour, ""); + outputVerticalLine($activityX, $startY + $topCornerSize, $endY, $colour, $event); + } + else + { + # $startContinuation == 0 && $endContinuation == 0 + $endY -= $endUpwardsOffset; + outputLine($x, $startY, $activityX, $startY + $topCornerSize, $colour, ""); + outputVerticalLine($activityX, $startY + $topCornerSize, $endY - $topCornerSize, $colour, $event); + outputLine($activityX, $endY - $topCornerSize, $x, $endY, $colour, ""); + } + } + } + } + } + +sub readIniFile() + { + if ($opt_x) { + my $sectionName; + open (INI, $opt_x) || die "Cannot open ini file $opt_x\n"; + while () { + chomp; + # remove leading and trailing blanks and ignore blank lines + s/^\s+//; + s/\s+$//; + if (! $_) + { next; }; + + if (/^\[(.+)\]/) + { + # parse section name from "[
]" lines + $sectionName = $1; + } + else + { + if ($sectionName eq "DontHideShortName") + { + $dontHideShortName{$_} = 1; + } + } + } + close INI; + } + } + +########################## +# HTML Embedder routines # +########################## + +sub createViewMap() +{ + open MAP, ">logmap.html" || die "Cannot open logmap.html for writing\n"; + print MAP "\n\n"; + + # Begin page label/time table + print MAP qq { \n }; + print MAP qq { \n }; +} + +sub closeViewMap($) +{ + # End page label/time table + print MAP "
PageLinesTimesObjects CreatedObjects Destroyed
\n

\n"; + + # begin object page lifetime table + my $pageCount = $_[0]; + print MAP qq{ \n }; + print MAP qq{ \n }; + foreach my $obj (@objects) { + print MAP qq { \n }; + my $last; + my $name = $obj->{Name}; + my $style = ""; + if ($name =~ s/^!//) { + $style = "font-weight: bold"; + } + if ($style) { + print MAP qq {}; + } else { + print MAP ""; + } + if (!defined($obj->{LastPage})) { + print MAP qq {}; + $last = $obj->{FirstPage}; + } else { + print MAP qq {}; + $last = $obj->{LastPage}; + } + print MAP qq{ \n"; + } + print MAP "
Object Lifetimes per Page
ObjectStart/EndLifetime
$name$name$obj->{FirstPage}$obj->{FirstPage}$obj->{LastPage}\n }; + for (my $i = 0 ;$i < $pageCount ; ++$i) { + my $anchor = ($i != $pageCount - 1) ? "html/log$i.html" : "log.html"; + if ($i < $obj->{FirstPage} || $i > $last) { + print MAP ""; + } else { + print MAP qq{ }; + } + } + print MAP "
  
\n"; + print MAP "\n<\html>\n"; + close MAP; +} + +sub outputViewMap($$$$$) +{ + my ($viewNumber, $lastViewNumber, $labelsOnPageRef, $objectsCreatedRef, $objectsDestroyedRef) = @_; + my $firstLine; + my $lastLine; + my $firstTime; + my $lastTime; + foreach my $i (@{$labelsOnPageRef}) { + if ($i =~ s/^#//) { + if (! $firstLine) { + $firstLine = $i; + } else { + $lastLine = $i; + } + } elsif ($i =~ m/^\d\d:\d\d:\d\d\.\d/) { + if (! $firstTime) { + $firstTime = $i; + } else { + $lastTime = $i; + } + } + } + $firstTime = " " if (!$firstTime); + $firstLine = " " if (!$firstLine); + $lastLine = " " if (!$lastLine); + $lastTime = " " if (!$lastTime); + my $anchor = $viewNumber != $lastViewNumber ? "html/log$viewNumber.html" : "log.html"; + print MAP qq { $viewNumber$firstLine$lastLine$firstTime$lastTime\n }; + print MAP qq { \n }; + my $name = ""; + outputViewObjects($objectsCreatedRef); + print MAP qq { \n }; + outputViewObjects($objectsDestroyedRef); + print MAP "\n"; +} + +sub outputViewObjects($) +{ + my $objectsRef = $_[0]; + my $name = ""; + foreach my $obj (@{$objectsRef}) { + my $style = ""; + if (ref($obj) eq "HASH") { + $name = $obj->{Name}; # obj = reference to (SVG) object + if ($name =~ s/^!//) { + $style = "font-weight: bold"; + } + } else { + $name = $obj; # obj = thread name + $style = "font-style: italic; font-weight: bold"; + } + if ($style) { # highlight processes/threads + print MAP "$name "; + } else { + print MAP "$name "; + } + } + if (!$name) { + print MAP " "; + } +} + +sub updateObjectViewList($) +{ + my ($viewNumber) = @_; + foreach my $obj (@objects) { + if ($obj->{Hide} == 1) { + next; + } + if (! defined($obj->{FirstPage})) { + $obj->{FirstPage} = $viewNumber; + } + elsif (! defined($obj->{LastPage}) || $viewNumber > $obj->{LastPage}) { + $obj->{LastPage} = $viewNumber; + } + } +} + + +# +# Output the "0 1 2 3 4 ..." anchors representing the different views +# +sub outputHTMLPageLinks($$$$) { + my ($viewNumber, $lastViewNumber, $htmlPathNotLastView, $htmlPathLastView) = @_; + my $i; + for ($i = 0 ; $i <= $lastViewNumber ; ++$i) { + if ($i == $viewNumber) { + print HTML qq{  $i\n}; + } else { + if ($i != $lastViewNumber) { + print HTML qq{ $i\n}; + } else { + print HTML qq{ $i\n}; + } + } + } +} + +sub outputHTMLEmbedder($$$$$) +{ + my ($width, $height, $viewNumber, $lastViewNumber, $fileName) = @_; + # + # leave log.html in the current directory, everything else goes into "html/" subdirectory + # + my $htmlPath; + my $svgPathInHtmlFile; + my $htmlLinkPathLastView; + my $htmlLinkPathNotLastView; + my $mainPath; + if ($viewNumber != $lastViewNumber) { + $htmlPath = "html/" . $fileName; + $htmlLinkPathLastView = "../"; + $htmlLinkPathNotLastView = ""; + $svgPathInHtmlFile = $fileName; + $mainPath = "../"; + } else { + $htmlPath = $fileName; + $htmlLinkPathLastView = "html/"; + $htmlLinkPathNotLastView = "html/"; + $svgPathInHtmlFile = "html/" . $fileName; + $mainPath = ""; + } + open HTML, ">${htmlPath}.html" || die "Cannot open ${htmlPath}.html for writing\n"; + + if (! $opt_f) { + # DOCTYPE needed for "position: fixed" to work in IE + print HTML "\n"; + print HTML qq{\n\n\n\n\n}; + # Output a duplicate hidden set of pagelinks in the same position as the fixed ones. The hidden pagelinks serve to + # reserve vertical space at the top of the display for the fixed links to sit on top of and prevent them obscuring + # the top of the SVG image. Also, if the main window width is made smaller than the width of the fixed page links, + # the latter will resize downward and start obscuring the top of the SVG image. The hidden pagelinks prevent this + # by resizing downward at the same time and so, not being fixed, will automatically reserve the right amount of space + # at the top of the display for the fixed pagelinks to reside in. A better way would be nice, but I don't know it. + print HTML qq{

}; + outputHTMLPageLinks($viewNumber, $lastViewNumber, $htmlLinkPathNotLastView, $htmlLinkPathLastView); + print HTML qq{
\n"; + # wmode="transparent": In IE the "position: fixed" pagelinks will otherwise be hidden underneath the SVG image. + $embedExtraArgs = qq{ wmode="transparent"}; + } + print HTML qq{\n}; + my $limitedText = limitedRowText(); + if ($limitedText) + { + print HTML "$limitedText \n"; + } + + print HTML qq{\n}; + print HTML qq{\n}; + print HTML qq{\n}; + print HTML qq{ Zoom%\n\n}; + + if ($opt_f) { + # don't need the bottom page links if the fixed position ones are at the top + outputHTMLPageLinks($viewNumber, $lastViewNumber, $htmlLinkPathNotLastView, $htmlLinkPathLastView); + } + + print HTML qq{ + + +EOT +; +############################### +# End interpolated JavaScript # +############################### + close HTML; +} + + + +####################### +# SVG output routines +####################### + +sub outputDocHeader() + { + my ($width,$height) = @_; + print SVG '',"\n"; + print SVG '',"\n"; + print SVG "\n"; + outputDefs(); + } + +sub outputDefs() + { + print SVG "\n"; + print SVG qq{\n}; + print SVG qq{\t\n}; + print SVG "\n"; + if ($opt_p) { + print SVG qq{\n}; + print SVG qq{\t\n}; + print SVG "\n"; + } + + print SVG qq{\n}; + print SVG qq{\t\n}; + print SVG "\n"; + + print SVG "\n"; + } + +# +# Output label at lefthand side - used for timestamp +# + +sub outputLeftLabel($) + { + my ($text) = @_; + outputText(0, $text, $currentY, "begin","black", ""); + } + +sub outputTextSeperator($$) + { + my ($text, $y) = @_; + outputRect($leftSeperatorMargin, $y - $fontHeight, $screenWidth, $fontHeight, $seperatorColour, $seperatorColour); + outputText($leftSeperatorMargin, $text, $y, "begin", "black", ""); + } + + +sub outputLabelledLine() + { + my ($x1,$x2,$text,$colour,$endType,$alignment, $otherAttrs) = @_; + outputHorizontalLine($x1,$x2,$colour,$endType,$otherAttrs); + my $textx; + my $anchor; + + if (! $colour) { + $colour = "black"; + } + + if (!$alignment || $alignment eq "mid") { + $textx = (($x1 + $x2) / 2); + $anchor = "middle"; + } + else { + if ($alignment eq "head") { + if ($x1 < $x2) { + $anchor = "end"; + $textx = $x2 - $arrowWidth - $textArrowSpacing; + } + else { + $anchor = "start"; + $textx = $x2 + $arrowWidth + $textArrowSpacing; + } + } else { # "tail" + $textx = $x1; + if ($x1 < $x2) { + $anchor = "start"; + } else { + $anchor = "end"; + } + } + } + + outputText($textx,$text,$currentY - $messageSpacingAboveLine, $anchor, $colour, $otherAttrs); + } + +sub outputText() + { + my ($x,$text,$y,$anchor,$colour, $otherAttrs) = @_; + $text = encode_entities($text); + + my $attrs = qq{ x="$x" y="$y" $otherAttrs }; + + if ($anchor) + { + $attrs .= qq { text-anchor="$anchor" }; + } + + if ($colour) + { + if ($colour =~ s/^!//) + { + outputRect($x - 1, $y - $fontHeight, $textMattSize, $fontHeight, "white", "white"); + } + if ($colour) + { + $attrs .= qq { fill="$colour" }; + } + } + + print SVG "$text\n"; + } + +sub outputLine() + { + my ($x1,$y1,$x2,$y2,$colour,$otherAttrs) = @_; + + if (! $colour) { + $colour = "black"; + } + + print SVG qq{\n}; + } + +sub outputHorizontalLine() + { + my ($x1,$x2,$colour,$endType,$otherAttrs) = @_; + + if (! $colour) { + $colour = "black"; + } + + print SVG qq{\n}; + } + +sub outputVerticalLine() + { + my ($x,$y1,$y2,$colour,$otherAttrs) = @_; + if (! $colour) { $colour = "black"; } + print SVG qq{\n}; + } + +sub outputRect($$$$) + { + my ($x,$y,$width,$height,$fill,$stroke) = @_; + print SVG qq {\n}; + } + +sub incrementY() + { + my $amount; + if ($_[0]) { + $amount = $_[0]; + } else { + $amount = 1; + } + $currentY += $incrementY * $amount; + } + +sub outputDocFooter() + { +################################### +# Begin Interpolated JavaScript # +################################### + print SVG <<'EOT' + + +EOT +################################# +# End Interpolated JavaScript # +################################# + }