source: trunk/src/testing/ui/Graph.py @ 4

Revision 4, 42.7 KB checked in by ajaworski, 13 years ago (diff)

Added modified SAGE sources

Line 
1############################################################################
2#
3# SAGE UI - A Graphical User Interface for SAGE
4# Copyright (C) 2005 Electronic Visualization Laboratory,
5# University of Illinois at Chicago
6#
7# All rights reserved.
8#
9# Redistribution and use in source and binary forms, with or without
10# modification, are permitted provided that the following conditions are met:
11#
12#  * Redistributions of source code must retain the above copyright
13#    notice, this list of conditions and the following disclaimer.
14#  * Redistributions in binary form must reproduce the above
15#    copyright notice, this list of conditions and the following disclaimer
16#    in the documentation and/or other materials provided with the distribution.
17#  * Neither the name of the University of Illinois at Chicago nor
18#    the names of its contributors may be used to endorse or promote
19#    products derived from this software without specific prior written permission.
20#
21# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
25# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
26# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
27# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
28# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
29# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
30# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
31# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32#
33# Direct questions, comments etc about SAGE UI to www.evl.uic.edu/cavern/forum
34#
35# Author: Allan Spale
36#         Ratko Jagodic
37#
38############################################################################
39
40
41import wx
42import time
43import wx.lib.newevent
44import thread
45
46# (AKS 2005-04-14) Modified a single line of wx.lib.plot to remove
47# an obstructed call if the platform was Mac
48#import plot
49from wx.lib import plot
50
51import string
52import random
53import sys
54import time
55import os
56from globals import *
57from Mywx import *
58
59# This creates a new Event class and a EVT binder function
60(UpdateDataEvent, EVT_UPDATE_GRAPH) = wx.lib.newevent.NewEvent()
61
62
63
64
65
66
67
68############################################################################
69
70# it gets the data from the SAGE messages (sageUIdataInfo).
71# this is the actual graph (lines and data)
72#
73
74class PerformanceGraph: 
75    def __init__( self, appName, stDataTitle, iArraySize, iPerfUpdateInterval ): 
76
77        # CLASS CONSTANTS
78        # self.__fMin, self.__fMax, self.__fAvg, self.__fCurrent
79        self.I_MINIMUM = 0
80        self.I_MAXIMUM = 1
81        self.I_AVERAGE = 2
82        self.I_CURRENT = 3
83
84        # instance variables
85        self.__fAvg = 0.0
86        self.__fMax = 0.0
87        self.__fMin = 0.0
88        self.__fCurrent = 0.0
89        self.__iArraySize = iArraySize
90        self.__stDataTitle = stDataTitle
91        self.__stLabel = ""
92        self.__stAppName = appName
93
94        # (AKS 2005-05-03) Performance update interval
95        self.__iPerformanceUpdateInterval = iPerfUpdateInterval
96
97
98        # create panel
99        #self.__wxpGraphPanel = wx.Panel( self.__wxContainer, id=wx.ID_ANY,
100        #                                 pos=(-1,-1), size=(-1,-1) )
101
102        # setup data structure
103        self.__naData = [ [0]*2 for i in range(self.__iArraySize) ]
104
105
106        # (AKS 2004-10-26) Because data is in reverse order (most recent value
107        # is index 0), it is necessary to reverse the x values in the array
108        iTimeValue = -(self.__iArraySize-1) * self.__iPerformanceUpdateInterval
109        for index in range( 0, self.__iArraySize ):
110            # print self.__iArraySize - index - 1
111            #RJ self.__naData[ self.__iArraySize - index - 1, 0 ] = iTimeValue
112            self.__naData[ self.__iArraySize - index - 1][ 0 ] = iTimeValue
113           
114            iTimeValue = iTimeValue + self.__iPerformanceUpdateInterval
115           
116            #self.__naData[ index - self.__iArraySize, 0 ] = index
117
118
119        # create graph
120        self.__lines = plot.PolyLine(self.__naData, colour='red', width=3)
121
122        self.__graph = plot.PlotGraphics( [self.__lines],"", "Time (s)",
123                                            self.__stDataTitle)
124
125#------------------------------------------------------------------------------       
126   
127    def update( self, naNewData ):
128        # Because naData is sent as a reference, it is necessary to copy the
129        # data from naNewData to naData to preserve these references
130
131        # (AKS 2005-05-09)
132        self.__fCurrent = float( naNewData[ 0 ] )
133       
134        fSum = 0.0
135
136        # (AKS 2005-02-08) Max and min for a range
137        self.__fAvg = 0.0
138        self.__fMax = 0.0
139        self.__fMin = float( sys.maxint )
140     
141        for index in range( 0, self.__iArraySize ):
142            # (AKS 2005-05-09)
143            item = float( naNewData[ index ] )
144
145            #RJ self.__naData[ index, 1 ] = item
146            self.__naData[ index ][ 1 ] = item
147            fSum = fSum + item
148
149            # Set maximum value
150            if ( item > self.__fMax ):
151                self.__fMax = item
152
153            # Set minimum value
154            elif ( item < self.__fMin ):
155                self.__fMin = item
156
157        # Calculate average
158        #self.__fAvg = fSum / self.__iArraySize
159
160        # (AKS 2005-05-09)
161        self.__fAvg = float( fSum / self.__iArraySize )
162       
163
164        # (AKS 2005-03-08) Update graph
165        self.__lines = plot.PolyLine(self.__naData, colour='red', width=3)
166
167        self.__graph = plot.PlotGraphics( [self.__lines],"", "Time (s)",
168                                          self.__stDataTitle)
169     
170
171#------------------------------------------------------------------------------
172
173    def setGraphType( self, stType ):
174        if ( stType == 'frame rate' ):
175            self.__stLabel = " fps"
176        elif ( stType == 'bandwidth' ):
177            self.__stLabel = " Mbps"
178   
179#------------------------------------------------------------------------------       
180   
181    def getGraph( self ):
182        return self.__graph
183   
184#------------------------------------------------------------------------------
185
186    def getStatistics( self ):
187        return ( self.__fMin, self.__fMax, self.__fAvg, self.__fCurrent )
188
189#------------------------------------------------------------------------------
190
191    def getCurrentValue( self ):
192        return self.__fCurrent
193
194#------------------------------------------------------------------------------
195
196    def getPolyLine( self ):
197        return self.__lines
198
199
200
201
202
203
204
205
206   
207############################################################################
208
209# (AKS 2005-04-26) Acts like PerformanceGraph (used with SimpleGraph) but supports
210# multiple plots of the same metric (i.e. bandwidth, frame rate) although this is
211# more of a usage rule rather than a code-enforced rule.  This class will contain
212# an array of PerformanceGraph instances across applications.
213
214class MultiPerformanceGraph:
215    # NEED TO CHANGE listPerformanceGraphs to hashPerformanceGraphs to allow
216    # for easy addition and deletion as apps come and go.
217    def __init__( self, hashPerformanceGraphs, stDataTitle ): 
218
219        # CLASS CONSTANTS
220        # self.__fMin, self.__fMax, self.__fAvg, self.__fCurrent
221        self.I_MINIMUM = 0
222        self.I_MAXIMUM = 1
223        self.I_AVERAGE = 2
224        self.I_CURRENT = 3
225
226        # instance variables
227        self.__fAvg = 0.0
228        self.__fMax = 0.0
229        self.__fMin = 0.0
230        self.__fCurrent = 0.0
231        self.__stDataTitle = stDataTitle
232        self.__stLabel = ""
233
234        self.__listLines = []
235        self.__hashPerformanceGraphs = {}
236
237        for windowId in hashPerformanceGraphs:
238            graph = hashPerformanceGraphs[ windowId ]
239            self.__hashPerformanceGraphs[ windowId ] = graph
240            self.__listLines.append( graph.getPolyLine() )
241
242        # create graph
243        self.__graph = plot.PlotGraphics( self.__listLines, "", "Time (s)",
244                                            self.__stDataTitle)
245
246#------------------------------------------------------------------------------       
247   
248    def update( self ):
249        if ( len( self.__hashPerformanceGraphs ) > 0 ):
250            # Because naData is sent as a reference, it is necessary to copy the
251            # data from naNewData to naData to preserve these references
252
253            # Get current data
254            #print ">>> UPDATE WITH: ", naNewData
255            #print "new data size = ", len( naNewData )
256            #print "plottable data size = ", self.__naData.shape
257            self.__fCurrent = naNewData[ 0 ]
258
259            fSum = 0.0
260
261            # (AKS 2005-02-08) Max and min for a range
262            self.__fAvg = 0.0
263            self.__fMax = 0.0
264            self.__fMin = float( sys.maxint )
265
266            for graph in self.__hashPerformanceGraphs.values():
267                fMin, fMax, fAvg, fCurrent = graph.getStatistics()
268               
269                #print index
270                fSum = fSum + fCurrent
271
272                # Set maximum value
273                if ( fMax > self.__fMax ):
274                    self.__fMax = fMax
275
276                    # Set minimum value
277                elif ( fMin < self.__fMin ):
278                    self.__fMin = fMin
279                   
280            # end loop
281
282
283            # Calculate average
284            self.__fAvg = fSum / self.__iArraySize
285
286
287            # (AKS 2005-03-08) Update graph
288            del self.__lines[ 0:len( self.__lines ) ]
289       
290            for windowId in self.__hashPerformanceGraphs:
291                graph = self.__hashPerformanceGraphs[ windowId ]
292                self.__listLines.append( graph.getPolyLine() )
293
294                self.__graph = plot.PlotGraphics( self.__listLines,"", "Time",
295                                                  self.__stDataTitle)
296        # end
297
298        else:
299            self.__fAvg = 0.0
300            self.__fMax = 0.0
301            self.__fMin = 0.0
302            self.__fCurrent = 0.0
303
304        # end if
305
306#------------------------------------------------------------------------------
307
308    # (AKS 2005-04-29) Need this function because apps come and go.
309    def addPerformanceGraph( self, windowId, pgGraph ):
310        self.__hashPerformanceGraphs[ windowId ] = pgGraph
311
312#------------------------------------------------------------------------------
313
314    def removePerformanceGraph( self, windowId ):
315        del self.__hashPerformanceGraphs[ windowId ]
316
317#------------------------------------------------------------------------------
318
319    def setGraphType( self, stType ):
320        if ( stType == 'frame rate' ):
321            self.__stLabel = " fps"
322        elif ( stType == 'bandwidth' ):
323            self.__stLabel = " Mbps"
324   
325#------------------------------------------------------------------------------       
326   
327    def getGraph( self ):
328        return self.__graph
329   
330#------------------------------------------------------------------------------
331
332    def getStatistics( self ):
333        return ( self.__fCurrent, self.__fMin, self.__fMax, self.__fAvg )
334
335#------------------------------------------------------------------------------
336
337
338
339
340
341
342
343
344
345############################################################################
346
347# the main panel that shows the totals for an application for all the
348# different metrics.
349# one of these exists for every application
350
351class SparklineGraphs( wx.Panel ):
352    def __init__( self, wxfParentFrame, list_pgGraph, list_stWindowTitle,
353                  list_stGraphTitle, list_stYAxisTitle, stSingleWindowTitle, id, frame=None ):
354
355        ### Setup element sizes
356        btnSize = ( 89, 24 )
357
358        ### initial spacing and positioning constants
359        xOrigin = 5
360        yOrigin = 5
361        titleY = yOrigin
362        titleX = xOrigin
363        titleHeight = 25
364        renderX = xOrigin
365        renderY = titleY + titleHeight
366        renderHeight = 40 - titleHeight
367        btnX = xOrigin
368        btnY = renderY + renderHeight
369        btnHeight = btnSize[1]
370        btnWidth = btnSize[0]
371        labelXOffset = 15         #from the end of the buttons (or app label) to the right
372        labelX = btnX + btnWidth + labelXOffset
373        labelY = 0 #yOrigin
374        labelWidth = 60
375        labelHeight = titleHeight
376        perfLabelX = labelX
377        numArea1X = btnX + btnWidth
378        numArea1Y = btnY
379        numAreaWidth = 4*labelWidth + labelXOffset + 10 #numArea1X + 4*labelWidth
380        numAreaHeight = 2*btnHeight
381        numArea2X = numArea1X
382        numArea2Y = numArea1Y + numAreaHeight + renderHeight
383
384        # panel size
385        width = numArea1X + numAreaWidth + xOrigin #415
386        height = numArea2Y + numAreaHeight
387        self._buffer = wx.EmptyBitmap(width, height)  # for double buffering
388
389        self.frame = frame
390        wx.Panel.__init__(self, wxfParentFrame, wx.ID_ANY, (3,3), (width,height))
391        self.Bind(wx.EVT_CLOSE, self.shutdown)
392        self.Bind(wx.EVT_PAINT, self.OnPaint)
393        self.SetBackgroundColour(appPanelColor)
394        # since SetBackgroundColour doesnt work always, manually draw the background
395        #self.background = MyImage( "images/app_info_background.png", 0,0, False, width, height)
396       
397        # Font setup
398        dc = wx.ClientDC(self)   # this makes font the same size on all platforms
399        ppp = 72.0 / dc.GetPPI().height
400        del dc
401        self.boldFont = BoldFont()
402        self.normalFont = StandardFont()
403
404        # Order of items in the list must adhere to the following constants
405        # INTERNAL CLASS CONSTANTS
406        self.I_RENDER_BW = 0
407        self.I_RENDER_FPS = 1
408        self.I_RENDER_NODES = 2
409        self.I_RENDER_STREAMS = 3       
410        self.I_DISPLAY_BW = 4
411        self.I_DISPLAY_FPS = 5
412        self.I_DISPLAY_NODES = 6
413        self.I_DISPLAY_STREAMS = 7       
414       
415        # Initialize instance variables
416        self.__windowId = id   # (AKS 2005-03-10) Added for shutdown callback
417        self.__list_stGraphTitle = list_stGraphTitle
418        self.__list_stWindowTitle = list_stWindowTitle
419
420        # FIX: delete the metrics we are not using this time... just use the bw and fps for rendering and display
421        self.__list_pgGraph = list_pgGraph
422        #self.__
423
424        self.__list_stYAxisTitle = list_stYAxisTitle
425        self.__stAppName = stSingleWindowTitle
426
427        ### Header Row
428        self.__colLabels = []
429        self.__colLabels.append((self.__stAppName + " " + str(self.__windowId), xOrigin, 0, btnWidth, titleHeight))
430        self.__colLabels.append(("Current", labelX, labelY, labelWidth, labelHeight))
431        labelX = labelX + labelWidth
432        self.__colLabels.append(("Min", labelX, labelY, labelWidth, labelHeight))
433        labelX = labelX + labelWidth
434        self.__colLabels.append(("Ave", labelX, labelY, labelWidth, labelHeight))
435        labelX = labelX + labelWidth
436        self.__colLabels.append(("Max", labelX, labelY, labelWidth, labelHeight))
437       
438        ### backgrounds for the two areas with the actual numbers
439        self.__numAreaBg = []
440        self.__numAreaBg.append(MyImage( "images/stats_green.jpg",
441                                         numArea1X, numArea1Y, False,
442                                         numAreaWidth, numAreaHeight))
443        self.__numAreaBg.append(MyImage( "images/stats_green.jpg",
444                                         numArea2X, numArea2Y, False,
445                                         numAreaWidth, numAreaHeight))
446       
447
448        self.__divLabels = []
449        self.__divLabels.append(("RENDER", renderX+185, renderY-2, btnWidth, renderHeight))
450        self.__divLabels.append(("DISPLAY", renderX+185, btnY + btnHeight*2-2, btnWidth, renderHeight))
451       
452        # graphs and performance data
453        self.__graphButtons = {}  #stores the graph buttons keyed by data type
454        self.__hash_sgSimpleGraph = {}
455        self.__perfNumLabels = {}  # stores the top-left corners of the first label and the size of each label
456
457        # buttons for showing the graphs for each metric
458        #self.__btnList = {0:"BW", 1:"FPS", 2:"Nodes", 3:"Streams", 4:"BW", 5:"FPS", 6:"Nodes", 7:"Streams"}
459        self.__btnList = {0:"BW", 1:"FPS", 4:"BW", 5:"FPS"}
460       
461
462        # load the buttons and create the labels that will hold all the performance data
463        for row in [0,1,4,5]: #range( 0, len( self.__btnList ) ):
464            # Space between each row
465            if row == 4:
466                btnY = btnY + renderHeight
467            if row == 0:
468                pass  #we already set the first one
469            else:
470                btnY = btnY + btnHeight
471
472            btnId =  row ##wx.ID_ANY  ## RJ 05-08-2005 - id by row
473            btn = PerfToggleButton(self, btnId,
474                                     self.__createGraph,
475                                     self.removeGraph,
476                                     "images/stats_"+self.__btnList[btnId]+"_up.jpg",
477                                     "images/stats_"+self.__btnList[btnId]+"_down.jpg",
478                                     "images/stats_"+self.__btnList[btnId]+"_over.jpg",
479                                     ( btnX, btnY ), btnSize)
480            self.__graphButtons[btnId] = btn
481           
482            self.__perfNumLabels[row] = (perfLabelX, btnY, labelWidth, btnHeight)
483        # end of loop
484
485        self.__redraw()
486
487
488    def Redraw(self):
489        self.__redraw()
490
491#------------------------------------------------------------------------------
492               
493    def registerShutdownCallback( self, function ):
494        self.__shutdownCallback = function       
495       
496#------------------------------------------------------------------------------   
497
498    def shutdown( self, evt=None ):
499        self.__shutdownCallback( self.__windowId )
500
501#------------------------------------------------------------------------------
502
503    # (AKS 2005-03-17)  This function will be called if the user closed a "higher-
504    # level" window that sends a "close event" down the window hierarchy.  In this
505    # case, the higher-level window will be able to track what IDs to remove from
506    # its data structures and will not need to receive any "callback" function
507    # to indicate this information.
508
509    def manualShutdown( self ):
510        # shutdown all the associated SingleGraphs
511        for iKey in self.__hash_sgSimpleGraph:
512            self.__hash_sgSimpleGraph[ iKey ].manualShutdown()
513       
514        # (AKS 2005-03-17) Clear all keys after graphs are shutdown
515        self.__hash_sgSimpleGraph.clear()
516        self.Destroy()       # destroy panel
517
518
519#------------------------------------------------------------------------------
520
521    def getGraphList( self ):
522        return self.__list_pgGraph
523
524#------------------------------------------------------------------------------
525
526    def removeGraph( self, iGraphID ):
527        self.__hash_sgSimpleGraph[ iGraphID ].manualShutdown()
528        del self.__hash_sgSimpleGraph[ iGraphID ]
529       
530        # Untoggle button
531        self.__graphButtons[ iGraphID ].SetValue( False )
532
533#------------------------------------------------------------------------------
534
535    # Call in case no function was registered
536    def __ignoreFunction( self ):
537        pass
538
539#------------------------------------------------------------------------------               
540
541    def OnPaint(self, event):
542        # for some reason double buffered panel doesnt show up right on macs so just
543        # do the normal drawing... single buffering
544        if "__WXMAC__" in wx.PlatformInfo:
545            dc = wx.PaintDC(self)
546            self.__redraw(dc=dc)
547        else:
548            dc = wx.BufferedPaintDC(self, self._buffer)
549       
550
551    def __redraw( self, evt=None, dc=None ):
552        if not self.IsShown():  #only redraw the panel if it's shown
553            return
554        if "__WXMAC__" in wx.PlatformInfo:
555            if dc == None: dc = wx.ClientDC(self)
556        else:
557            dc = wx.BufferedDC(wx.ClientDC(self), self._buffer)
558        dc.SetBackground( wx.Brush(self.GetBackgroundColour()))
559        dc.SetTextForeground(wx.WHITE)
560        dc.Clear()
561
562        # draw the column labels
563        dc.SetFont(self.normalFont)
564        i = 0
565        for col in self.__colLabels:
566            if i==0:
567                dc.SetFont(self.boldFont)
568                dc.DrawLabel(col[0], wx.Rect(col[1], col[2], col[3], col[4]), wx.ALIGN_TOP | wx.ALIGN_LEFT)
569                dc.SetFont(self.normalFont)
570            else: dc.DrawLabel(col[0], wx.Rect(col[1], col[2], col[3], col[4]), wx.ALIGN_TOP | wx.ALIGN_RIGHT)
571            i=1
572
573        # draw the "render" and "display" labels
574        dc.SetTextForeground(wx.Colour(102, 204, 153))
575        for div in self.__divLabels:
576            dc.DrawLabel(div[0], wx.Rect(div[1], div[2], div[3], div[4]), wx.ALIGN_LEFT)
577        dc.SetTextForeground(wx.WHITE)
578                       
579        # draw the number area backgrounds
580        for im in self.__numAreaBg:
581            dc.DrawBitmap( im.GetBitmap(), im.GetX(), im.GetY(), im.IsTransparent())
582
583        # update the SparklineGraph panel
584        for iIndex in [0,1,4,5]: #range( 0, 4): #len( self.__list_pgGraph ) ):
585            pgGraph = self.__list_pgGraph[ iIndex ]
586            (x,y, w,h) = self.__perfNumLabels[iIndex]  # get the location and size of the first label in each row
587
588            # Get statistics               
589            tupleStats = pgGraph.getStatistics()
590
591            # draw the labels for each metric
592            for index in (pgGraph.I_CURRENT, pgGraph.I_MINIMUM, pgGraph.I_AVERAGE, pgGraph.I_MAXIMUM):
593                if (iIndex == self.I_RENDER_BW) or (iIndex == self.I_RENDER_FPS) or (iIndex == self.I_DISPLAY_BW) or (iIndex == self.I_DISPLAY_FPS):
594                    stFormatString = "%5.1f" % tupleStats[ index ]
595                else:
596                    stFormatString = "%5d" % tupleStats[ index ]
597                dc.DrawLabel(stFormatString, wx.Rect(x,y,w,h), wx.ALIGN_CENTRE | wx.ALIGN_RIGHT)
598                x+=w  # move the next label to the right by the width of the label
599
600        # redraw the buttons on mac
601        if "__WXMAC__" in wx.PlatformInfo:
602            del dc
603            for btn in self.__graphButtons.itervalues():
604                btn.OnPaint()
605       
606        # now update all the SingleGraphs
607        for k, singleGraph in self.__hash_sgSimpleGraph.iteritems():
608            singleGraph.Redraw()
609               
610
611
612
613    ### RJ 05-08-2005 - this function replaces the 8 below
614    def __OnToggleButton(self, event):
615        button = event.GetEventObject()
616        if button.GetValue():   #RJ 05-08-2005
617            self.__createGraph( button.GetId() )
618        else:
619            self.removeGraph( button.GetId() )
620
621
622       
623#------------------------------------------------------------------------------
624
625       
626    def __createGraph( self, iGraphCode ):
627        stRevisedWindowTitle = self.__stAppName + ": " + self.__list_stWindowTitle[ iGraphCode ]
628
629        #if self.frame = None
630        self.__hash_sgSimpleGraph[ iGraphCode ] = SingleGraph(
631            self.frame, self.__list_pgGraph[ iGraphCode ], stRevisedWindowTitle,
632            self.__list_stGraphTitle[ iGraphCode ],
633            self.__list_stYAxisTitle[ iGraphCode ], iGraphCode )
634
635        # registered call
636        #print "........creating graph and registering shutdown callback for graph #", iGraphCode
637        self.__hash_sgSimpleGraph[ iGraphCode ].registerShutdownCallback( self.removeGraph )
638           
639#------------------------------------------------------------------------------
640
641    # (AKS 2005-05-03) Send a list of PerformanceGraphs related to a single app.
642    # Then add each PerformanceGraph to the appropriate MultiPerformanceGraph
643    # (one MultiPerformanceGraph for each performance metric).
644    # CAUTION: Ordering of performance metrics is assumed to be identical
645    # to the related graphs in listPerformanceGraphs
646
647    def addAppPerformanceGraphList( self, windowId, listPerformanceGraphs,
648                                    iPerformanceMetricCount ):
649
650        # CAUTION: Only works for MultiPerformanceGraph instances
651        for iIndex in range( 0, iPerformanceMetricCount ):
652            self.__list_pgGraph[ iIndex ].addPerformanceGraph(
653                windowId, listPerformanceGraphs[ index ] )
654               
655        # end loop       
656
657#------------------------------------------------------------------------------
658
659    # (AKS 2005-05-03) Remove a PerformanceGraph with the given windowId from each
660    # MultiPerformanceGraph.
661    # CAUTION: Ordering of performance metrics is assumed to be identical
662    # to the related graphs in listPerformanceGraphs
663
664    def reomveAppPerformanceGraphList( self, windowId, iPerformanceMetricCount ):
665        print "Removing from totals sparkline APP ID #", windowId
666
667        for iIndex in range( 0, iPerformanceMetricCount ):
668            self.__list_pgGraph[ iIndex ].removePerformanceGraph( windowId )
669
670        # end loop
671
672#------------------------------------------------------------------------------
673
674    # (AKS 2005-05-04) ONLY CALL WHEN USING MultiPerformanceGraphs in this class
675    # so that totals can be calculated.
676
677    def updateMultiPerformanceGraphs( self, iPerformanceMetricCount ):
678        print "Updating totals sparkline..."       
679        for iIndex in range( 0, iPerformanceMetricCount ):
680            self.__list_pgGraph[ iIndex ].update()
681       
682
683
684
685
686
687
688############################################################################
689# it displays only one metric as a graph in a frame
690# it runs in a separate thread spawned by the SparklineGraph
691
692class SingleGraph( wx.Frame ):
693    def __init__( self, wxfParentFrame, pgGraph, stWindowTitle, stGraphTitle, stYAxisTitle, iGraphCode ):
694
695        # Font setup
696        self.normalFont = StandardFont()#wx.Font( 11, wx.DEFAULT, wx.NORMAL, wx.NORMAL )
697
698        # Initialize instance variables
699        self.__stGraphTitle = stGraphTitle
700        self.__stWindowTitle = stWindowTitle
701        self.__pgGraph = pgGraph
702        self.__stYAxisTitle = stYAxisTitle
703        self.__iGraphCode = iGraphCode
704
705        # (AKS 2005-05-05) Pause playback flag
706        self.__bPausePlayback = False
707
708        # Setup window               
709        wx.Frame.__init__(self, wxfParentFrame, wx.ID_ANY, stWindowTitle, wx.DefaultPosition, size=( 400, 300 ))
710        wx.EVT_CLOSE(self, self.shutdown)
711       
712        self.CenterOnParent()
713
714        # (AKS 2005-04-26) Use status bar instead
715        self.CreateStatusBar()
716
717        # (AKS 2005-05-05) Create menu bar to pause
718        self.SetMenuBar( self.CreateMenu() )
719
720        # Setup graph
721        self.__wxpcPlotCanvas = plot.PlotCanvas( self )#, wx.ID_ANY, pos=( 0, 0 ), size=( 400,300 ) )
722        self.__wxpcPlotCanvas.SetSize((400,300))
723        # this is a workaround for what appears to be a bug in wx.lib.plot
724        # On macs OnSize is not getting called therefore the buffer is not getting
725        # created anywhere so we forcefully call it here
726        #if wx.Platform == "__WXMAC__":
727        #    print "calling on size..."
728        #self.__wxpcPlotCanvas.OnSize(None)
729           
730        #self.__wxpcPlotCanvas.OnSize(wx.EVT_SIZE)
731        self.__wxpcPlotCanvas.SetEnableZoom( True )
732        self.__wxpcPlotCanvas.SetEnableGrid( True )
733
734        self.Bind( EVT_UPDATE_GRAPH, self.__redraw )
735        self.__redraw()
736        self.Show( True )
737
738
739    def Redraw(self):
740        self.__redraw()
741
742#------------------------------------------------------------------------------   
743
744    def shutdown( self, evt=None ):
745        # registered callback function to notify any listeners that graph
746        # was destroyed
747        self.__shutdownCallback( self.__iGraphCode ) 
748       
749#------------------------------------------------------------------------------
750
751    # (AKS 2005-03-17)  This function will be called if the user closed a "higher-
752    # level" window that sends a "close event" down the window hierarchy.  In this
753    # case, the higher-level window will be able to track what IDs to remove from
754    # its data structures and will not need to receive any "callback" function
755    # to indicate this information.
756   
757    def manualShutdown( self ):
758        self.Destroy()
759       
760#------------------------------------------------------------------------------
761   
762    def registerShutdownCallback( self, function ):
763        self.__shutdownCallback = function
764       
765#------------------------------------------------------------------------------
766
767    def __redraw( self, evt=None ):
768        #print "...redraw..."
769        tupleStats = self.__pgGraph.getStatistics()
770
771        if ( self.__bPausePlayback == False ):
772       
773            stStatusText = "Minimum: %4.2f   Average: %4.2f   Maximum: %4.2f   Current: %4.2f" % (
774                tupleStats[ self.__pgGraph.I_MINIMUM ],
775                tupleStats[ self.__pgGraph.I_AVERAGE ],
776                tupleStats[ self.__pgGraph.I_MAXIMUM ],
777                tupleStats[ self.__pgGraph.I_CURRENT ] )
778
779            #self.__wxsInfoLine.SetLabel( stStatusText )
780
781            fNewMax = tupleStats[ self.__pgGraph.I_MAXIMUM ] * 1.2
782            # graphics are already stored in PerformanceGraph
783            self.__wxpcPlotCanvas.Draw( self.__pgGraph.getGraph(), yAxis=(0, fNewMax) ) #, xAxis="Time",
784                                        #yAxis=self.__stYAxisTitle )
785        else:
786            stStatusText = "|| DRAWING PAUSED || Minimum: %4.2f   Average: %4.2f   Maximum: %4.2f   Current: %4.2f" % (
787                tupleStats[ self.__pgGraph.I_MINIMUM ],
788                tupleStats[ self.__pgGraph.I_AVERAGE ],
789                tupleStats[ self.__pgGraph.I_MAXIMUM ],
790                tupleStats[ self.__pgGraph.I_CURRENT ] )
791
792        # end if
793
794        # (AKS 2005-04-26) Use status bar instead
795        self.SetStatusText( stStatusText )
796
797#------------------------------------------------------------------------------
798
799    def CreateMenu(self):
800        menuBar = wx.MenuBar()
801
802        # Playback
803        menuFile = wx.Menu()
804        self.miPlaybackPause = menuFile.Append(-1, "&Pause\tAlt-P", "", wx.ITEM_CHECK)
805        self.Bind(wx.EVT_MENU, self.OnPause, self.miPlaybackPause)
806        menuBar.Append(menuFile, "&Playback")
807
808        return menuBar
809
810#------------------------------------------------------------------------------
811   
812    def OnPause( self, evt ):
813        if ( self.miPlaybackPause.IsChecked() == True ):
814            self.__bPausePlayback = True
815        else:
816            self.__bPausePlayback = False
817
818
819
820
821
822
823
824
825   
826###########################################################################
827
828# it encapsulates all the performance data monitoring for all the apps
829# it creates SparklineGraphs
830# created only once during the execution of the program
831
832class GraphManager:
833    def __init__( self, wxfParentFrame, sageAppState ):
834
835        # (AKS 2005-04-29) Put MultiPerformanceGraph instances in GraphManager.
836        # There is one MultiPerformanceGraph for each metric listed in
837        # self.__list_stGraphTitles.  Multiple applications will post their
838        # PerformanceGraph PolyLine data into MultiPerformanceGraph.
839       
840
841        # UTILITY FUNCTIONS FOR GUI BUILDING...
842        normalFont = StandardFont()#wx.Font( 11, wx.DEFAULT, wx.NORMAL, wx.NORMAL )
843
844        self.__gmSageAppState = sageAppState
845        self.__wxfParentFrame = wxfParentFrame
846        self.__hashAppGraph = {}
847        self.__hashAppGraphUpdateFlag = {}
848        #self.__bCloseSparklines = False
849        self.__updateList = []   #all the containers that need updating
850       
851        # (AKS 2005-05-03) Use the variable to store the performance update interval
852        self.__iPerformanceUpdateInterval = 2
853
854        self.__hashCallback = {}
855
856       
857        self.__list_stGraphTitles = [ "Rendering Bandwidth (Mbps)",
858            "Rendering Frame Rate (Frames/Sec)", "Rendering Nodes", "Rendering Streams",
859            "Display Bandwidth (Mbps)", "Display Frame Rate (Frames/Sec)", "Display Nodes",
860            "Display Streams" ]
861
862        self.__list_stYAxisTitles = [ "Mbps", "fps", "Nodes", "Streams",
863                                      "Mbps", "fps", "Nodes", "Streams" ]
864
865        self.__list_stOverallGraphTitles = [ "Total Rendering Bandwidth",
866            "Rendering: Average Frames/Sec", "Total Rendering Streams",
867            "Total Rendering Bandwidth", "Rendering: Average Frames/Sec",
868            "Total Rendering Streams" ]
869
870        self.__list_stOverallYAxisTitles = [ "Mbps", "fps", "Streams",
871                                             "Mbps", "fps", "Streams" ]       
872
873
874        # (AKS 2005-05-03) Create totals launcher for multiplot graphs
875        self.__bTotalsLauncherExists = False
876
877
878#------------------------------------------------------------------------------   
879
880    # Responds to 40002...remember, all data comes at once
881    def update( self, windowId ):
882        # Send the update message
883        if windowId in self.__hashAppGraph:
884
885            # Get graphs from Sparklines
886            sgAppSparklines = self.__hashAppGraph[ windowId ]
887            list_pgGraphs = sgAppSparklines.getGraphList()
888
889            #print "Get render bandwidth..."
890            list_pgGraphs[ sgAppSparklines.I_RENDER_BW ].update(
891                self.__gmSageAppState.getRenderItem( 'bandWidth', windowId, 30 ) )
892
893            list_pgGraphs[ sgAppSparklines.I_RENDER_FPS ].update(
894                self.__gmSageAppState.getRenderItem( 'frameRate', windowId, 30 ) )
895
896            list_pgGraphs[ sgAppSparklines.I_RENDER_NODES ].update(
897                self.__gmSageAppState.getRenderItem( 'nodes', windowId, 30 ) )
898
899            list_pgGraphs[ sgAppSparklines.I_RENDER_STREAMS ].update(
900                self.__gmSageAppState.getRenderItem( 'cpu', windowId, 30 ) )
901           
902
903            #print "Get display bandwidth..."       
904            list_pgGraphs[ sgAppSparklines.I_DISPLAY_BW ].update(
905                self.__gmSageAppState.getDisplayItem( 'bandWidth', windowId, 30 ) )
906
907            list_pgGraphs[ sgAppSparklines.I_DISPLAY_FPS ].update(
908                self.__gmSageAppState.getDisplayItem( 'frameRate', windowId, 30 ) )
909
910            list_pgGraphs[ sgAppSparklines.I_DISPLAY_NODES ].update(
911                self.__gmSageAppState.getDisplayItem( 'nodes', windowId, 30 ) )
912
913            list_pgGraphs[ sgAppSparklines.I_DISPLAY_STREAMS ].update(
914                self.__gmSageAppState.getDisplayItem( 'cpu', windowId, 30 ) )
915           
916
917            self.__hashAppGraphUpdateFlag[ windowId ] = True
918           
919           
920       
921
922        # send an update to all the windows
923        for win in self.__updateList:   
924            try:
925                win.GetSize()
926            except Exception:
927                #print "exception occured... deleting the window"
928                del win
929            else:
930                win.Redraw()  # call redraw on SparklineGraph
931       
932
933        # (AKS 2005-05-04) ALWAYS EXECUTE...Call update function for totals Sparkline;
934        # instances of PerformanceGraphs in MultiPerformanceGRaphs in Sparkline
935        # have already updated since they share the same reference address
936        # (hopefully).  Now, just call a special update that will get the
937        # totals Sparkline to calculate totals based on these updates.
938       
939        if ( self.__bTotalsLauncherExists == True ):
940            self.__sgTotalsLauncher.updateMultiPerformanceGraphs( self.__iPerformanceMetricCount )
941
942             
943#------------------------------------------------------------------------------
944   
945    # Responds to 40000, 40001...OR JUST RIGHT-CLICK
946    def addGraph( self, stAppName, windowId, parent=None ):  #RJ added "parent=None"
947        if parent == None:
948            parent = self.__wxfParentFrame
949        list_pgGraphs = []
950
951        for stGraphTitle in self.__list_stGraphTitles:
952            list_pgGraphs.append( PerformanceGraph(
953                stAppName, stGraphTitle, 30, self.__iPerformanceUpdateInterval ) )
954
955       
956        # Create an application Sparkline graph launcher
957        # (AKS 2005-05-03) Only create the SparklineGraph if it has not already been created
958
959        if ( windowId not in self.__hashAppGraph ):
960           
961            slTotalsGraphLauncher = SparklineGraphs( parent,   #RJ - put parent instead of __wxf...
962                list_pgGraphs, self.__list_stGraphTitles,
963                self.__list_stGraphTitles, self.__list_stYAxisTitles, stAppName, windowId, self.__wxfParentFrame )
964           
965
966            # (AKS 2005-03-10) Register callback to destroy graph when the sparklines
967            # window is closed.
968            slTotalsGraphLauncher.registerShutdownCallback( self.removeGraph )
969
970            # Update the hashes
971            self.__hashAppGraph[ windowId ] = slTotalsGraphLauncher
972            self.__hashAppGraphUpdateFlag[ windowId ] = False
973            self.AddToUpdateList(slTotalsGraphLauncher)
974
975            # (AKS 2005-05-04) Add newly created PerformanceGraphs to totals Sparkline
976            if ( self.__bTotalsLauncherExists == True ):
977                self.__sgTotalsLauncher.addAppPerformanceGraphList( windowId, list_pgGraphs,
978                    self.__iPerformanceMetricCount )                                   
979               
980        # end...else do nothing
981
982
983    # RJ 05-08-2005
984    def GetSparklineGraph(self, windowId):
985        if not windowId in self.__hashAppGraph:
986            return -1
987        else:
988            return self.__hashAppGraph[ windowId ]
989
990
991    def AddToUpdateList(self, win):
992        if not win in self.__updateList:
993            self.__updateList.append(win)
994
995    def RemoveFromUpdateList(self, win):
996        if win in self.__updateList:
997            self.__updateList.remove(win)
998       
999   
1000#------------------------------------------------------------------------------
1001
1002    # Responds to 40003...or nothing
1003    def removeGraph( self, windowId ):
1004        if windowId in self.__hashAppGraph:
1005           
1006            # (AKS 2005-05-04) Remove performance data related to departing application
1007            # from totals Sparkline
1008            if ( self.__bTotalsLauncherExists == True ):
1009                self.__sgTotalsLauncher.reomveAppPerformanceGraphList( windowId,
1010                    self.__iPerformanceMetricCount )
1011               
1012           
1013            sgAppSparklines = self.__hashAppGraph[ windowId ]
1014            self.RemoveFromUpdateList(sgAppSparklines)
1015            sgAppSparklines.manualShutdown()
1016           
1017            del self.__hashAppGraph[ windowId ]     # remove app id
1018           
1019        # else, do nothing         
1020           
1021#------------------------------------------------------------------------------
1022
1023    def showTotalsGraph( self ):
1024        self.__bCloseSparklines = False
1025
1026        list_pgGraphs = []
1027
1028        # Create totals graphs
1029        for iIndex in range( 0,len( self.__list_stOverallGraphTitles ) ):
1030            list_pgGraphs.append( PerformanceGraph( "SAGE Totals",
1031                self.__list_stOverallGraphTitles[ iIndex ], 30 ) )
1032
1033        # (AKS 2005-03-10) Need "bogus app id" for totals graph; it will be 88888888
1034        self.__slTotalsGraphLauncher = SparklineGraphs(
1035            self.__wxfParentFrame, list_pgGraphs, self.__list_stOverallGraphTitles,
1036            self.__list_stOverallGraphTitles, self.__list_stOverallYAxisTitles, 88888888 )
1037
1038        self.__slTotalsGraphLauncher.registerCallback( self.onCloseTotalsGraph )
1039
1040#------------------------------------------------------------------------------
1041   
1042    def onCloseTotalsGraph( self ):
1043        self.__slTotalsGraphLauncher = None
1044   
1045#------------------------------------------------------------------------------
1046       
1047    def shutdown( self ):
1048        # For each graph, shut down
1049        keys = self.__hashAppGraph.keys()
1050        for key in keys:
1051            self.__hashAppGraph[ key ].shutdown()
1052
1053        # Makes sure to only call shutdown if the window still exists
1054        if ( self.__bTotalsLauncherExists == True ):
1055            self.__bTotalsLauncherExists = False
1056            self.__slTotalsGraphLauncher.shutdown()
1057
1058
1059#------------------------------------------------------------------------------
1060    # (AKS 2005-05-03) Get the performance data update interval
1061    def getPerformanceUpdateInterval( self ):
1062        return self.__iPerformanceUpdateInterval
1063
1064#------------------------------------------------------------------------------
1065    # (AKS 2005-05-03) Create the totals performance graph
1066
1067    def createTotalsLauncher( self ):
1068        # Create list of MultiPerformanceGraphs...one for each performance metric
1069        list_mpgMultiPerfGraphs = []
1070       
1071        for index in range( 0, len( self.__list_stGraphTitles ) ):
1072            list_mpgMultiPerfGraphs.append( MultiPerformanceGraph(
1073                {}, self.__list_stGraphTitles ) )
1074
1075        # end loop
1076       
1077        # Create the "totals" SparklineGraphs object...need bogus app id of -1
1078        # CAREFUL: DIFFERENT TYPE...SENDING LIST OF MultiPerformanceGraph instances
1079        # and not PerformanceGraph instances!!!
1080       
1081        self.__sgTotalsLauncher = SparklineGraphs( self.__wxfParentFrame,
1082            list_mpgMultiPerfGraphs, self.__list_stGraphTitles,
1083            self.__list_stGraphTitles, self.__list_stYAxisTitles,
1084            "SAGE Application Totals", -1 )
1085
1086        self.__iPerformanceMetricCount = len( self.__list_stGraphTitles )
1087       
1088        # Populate the MultiPerformanceGraphs from existing app SparklineGraphs
1089        for windowId in self.__hashAppGraph:
1090            sgAppLauncher = self.__hashAppGraph[ windowId ]
1091            list_pgGraphs = sgAppLauncher.getGraphList()
1092
1093            self.__sgTotalsLauncher.addPerformanceGraphList( list_pgGraphs,
1094                self.__iPerformanceMetricCount )
1095
1096        # end loop
1097        self.__bTotalsLauncherExists = True
1098
1099#------------------------------------------------------------------------------
1100    # (AKS 2005-05-04) Shutdown the totals performance graph; since this
1101    # exists independent of the SparklineGraph objects, the totals launcher
1102    # can be destroyed independently and created independently at any time
1103   
1104    def shutdownTotalsLauncher( self ):
1105        if ( ( self.__sgTotalsGraphLauncher != None ) and
1106             ( self.__sgTotalsGraphLauncher.__class__.__name__ ==
1107                 SparklineGraphs.__name__ ) ):
1108            self.__sgTotalsGraphLauncher.shutdown()
1109
1110
1111    def getSparkline( self, windowId ):
1112        if ( windowId in self.__hashAppGraph ):
1113            return self.__hashAppGraph[ windowId ]
1114
1115
1116
Note: See TracBrowser for help on using the repository browser.