summaryrefslogtreecommitdiff
path: root/src/python/graph.py
blob: 9be9d6dd7889ca3da3a3aa08a70fe21b96b6a0ea (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# libavg - Media Playback Engine.
# Copyright (C) 2003-2014 Ulrich von Zadow
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Current versions can be found at www.libavg.de

import math
import time

from libavg import avg, player, Point2D


class Graph(avg.DivNode):
    def __init__(self, title='', getValue=None, parent=None, **kwargs):
        super(Graph, self).__init__(**kwargs)
        self.registerInstance(self, parent)

        self._getValue = getValue
        self._xSkip = 2
        self._curUsage = 0
        self.sensitive = False

        avg.RectNode(parent=self, strokewidth=0, fillopacity=0.6, fillcolor="FFFFFF",
                size=self.size)
        self._textNode0 = avg.WordsNode(parent=self, x=10, y=self.size.y - 22,
                color="000080")
        self._textNode1 = avg.WordsNode(parent=self, x=10, y=self.size.y - 39,
                color="000080")
        self._maxLineNode = avg.PolyLineNode(parent=self, color="880000")
        self._lineNode = avg.PolyLineNode(parent=self, color="008000")
        self.__graphText = avg.WordsNode(parent=self, x=10, y=0, color="000080")
        self.__graphText.text = title
        self._setup()

    def _setup(self):
        raise RuntimeError('Please overload _setup() function')


class AveragingGraph(Graph):

    def __init__(self, title='', getValue=None, parent=None, **kwargs):
        super(AveragingGraph, self).__init__(title, getValue, parent, **kwargs)
        self.registerInstance(self, None)

    def unlink(self, kill):
        player.clearInterval(self.__interval)
        self.__interval = None
        super(AveragingGraph, self).unlink(kill)
    
    def _setup(self):
        self.__interval = player.setInterval(1000, self._nextMemSample)
        self.__numSamples = 0
        self._usage = [0]
        self._maxUsage = [0]
        self._minutesUsage = [0]
        self._minutesMaxUsage = [0]
        self._nextMemSample()

    def _nextMemSample(self):
        curUsage = self._getValue()
        self._usage.append(curUsage)
        maxUsage = self._maxUsage[-1]

        if curUsage > maxUsage:
            maxUsage = curUsage
            lastMaxChangeTime = time.time()
            self._textNode1.text = ("Last increase in maximum: "
                    + time.strftime("%d.%m.%Y %H:%M:%S",
                    time.localtime(lastMaxChangeTime)))
        self._maxUsage.append(maxUsage)
        self.__numSamples += 1

        if self.__numSamples % 60 == 0:
            lastMinuteAverage = sum(self._usage[-60:]) / 60
            self._minutesUsage.append(lastMinuteAverage)
            self._minutesMaxUsage.append(maxUsage)

        if self.__numSamples < 60 * 60:
            self._plotLine(self._usage, self._lineNode, maxUsage)
            self._plotLine(self._maxUsage, self._maxLineNode, maxUsage)
        else:
            self._plotLine(self._minutesUsage, self._lineNode, maxUsage)
            self._plotLine(self._minutesMaxUsage, self._maxLineNode, maxUsage)

        self._textNode0.text = ("Max. memory usage: %(size).2f MB" %
                {"size": maxUsage / (1024 * 1024.0)})

        if self.__numSamples % 3600 == 0:
            del self._usage[0:3600]
            del self._maxUsage[0:3599]
            if self.__numSamples == 604800:
                self.__numSamples == 0

    def _plotLine(self, data, node, maxy):
        yfactor = (self.size.y - 10.0) / float(maxy)
        xfactor = (self.size.x - 10.0) / float(len(data) - 1)
        node.pos = [(pos[0] * xfactor + 10, (maxy - pos[1]) * yfactor + 10.0)
                    for pos in enumerate(data)]


class SlidingGraph(Graph):
    def __init__(self, title='', getValue=None, limit=120.0, parent=None, **kwargs):
        super(SlidingGraph, self).__init__(title, getValue, parent, **kwargs)
        self.registerInstance(self, None)
        self._limitValue = float(limit)

    def _setup(self):
        self.__frameHandlerID = player.subscribe(avg.Player.ON_FRAME, 
                self._nextFrameTimeSample)
        self._numSamples = 0
        self._lastCurUsage = 0
        self._maxFrameTime = 0
        self._values = []

    def _nextFrameTimeSample(self):
        val = self._frameTimeSample()
        self._appendValue(val)
        self._numSamples += 1

    def _appendValue(self, value):
        maxValue = min(self._limitValue, value)
        y = self.height - (self.height * (maxValue / self._limitValue))
        y = max(0, y)
        numValues = int(self.width / self._xSkip)
        self._values = (self._values + [y])[-numValues:]
        self._plotGraph()

    def _frameTimeSample(self):
        frameTime = self._getValue()
        diff = frameTime - self._lastCurUsage
        if self._numSamples < 2:
            self._maxFrameTime = 0
        if diff > self._maxFrameTime:
            lastMaxChangeTime = time.time()
            self._maxFrameTime = diff
            self._textNode0.text = ("Max FrameTime: %.f" % self._maxFrameTime + " ms" +
                    "   Time: " + time.strftime("%d.%m.%Y %H:%M:%S",
                    time.localtime(lastMaxChangeTime)))

        self._lastCurUsage = frameTime
        self._textNode1.text = ("Current FrameTime: %.f" % diff + " ms")
        return diff

    def _plotGraph(self):
        self._lineNode.pos = self._getCoords()

    def _getCoords(self):
        return zip(xrange(0, len(self._values) * self._xSkip, self._xSkip), self._values)


class BinBar(avg.DivNode):
    def __init__(self, label, parent=None, **kwargs):
        super(BinBar, self).__init__(**kwargs)
        self.registerInstance(self, parent)

        avg.WordsNode(text=label, fontsize=8, alignment='center',
                pos=(self.size.x / 2, self.size.y - 12), parent=self)

        self._vbar = avg.RectNode(opacity=0, fillopacity=0.4,
                fillcolor='ff0000', parent=self)

        self.update(0)

    def update(self, value):
        value = min(max(value, 0), 1)

        height = (self.size.y - 15) * value
        self._vbar.size = (self.size.x - 2, height)
        self._vbar.pos = (1, self.size.y - height - 12)


class BinsGraph(avg.DivNode):
    def __init__(self, binsThresholds, parent=None, **kwargs):
        super(BinsGraph, self).__init__(**kwargs)
        self.registerInstance(self, parent)

        avg.RectNode(size=self.size, parent=self)
        colWidth = self.size.x / len(binsThresholds)
        self._binBars = [BinBar(str(int(thr)),
                pos=(idx * colWidth, 0),
                size=(colWidth, self.size.y),
                parent=self)
                for idx, thr in enumerate(binsThresholds)]

    def update(self, values):
        s = sum(values)

        for binBar, value in zip(self._binBars, values):
            normValue = float(value) / s
            logValue = math.log10(normValue * 9 + 1) if normValue != 0 else 0
            binBar.update(logValue)


class SlidingBinnedGraph(SlidingGraph):
    def __init__(self, title='', getValue=None, limit=120.0, binsThresholds=[],
            parent=None, **kwargs):
        super(SlidingBinnedGraph, self).__init__(title, getValue, limit, parent, **kwargs)
        if not all([isinstance(x, (int, float)) for x in binsThresholds]):
            raise RuntimeError('Bins thresholds must be provided as list of numbers')

        self._bins = [0] * len(binsThresholds)
        self._binsThresholds = binsThresholds
        self._binsGraph = BinsGraph(binsThresholds=binsThresholds,
                pos=(self.size.x - 120, 5), size=(90, self.size.y - 10),
                parent=self)

    def _appendValue(self, value):
        for i in xrange(len(self._binsThresholds) - 1, -1, -1):
            if value >= self._binsThresholds[i]:
                self._bins[i] += 1
                break

        if sum(self._bins) % 100 == 0:
            self._binsGraph.update(self._bins)

        super(SlidingBinnedGraph, self)._appendValue(value)