Statistics
| Branch: | Tag: | Revision:

root / snf-stats-app / synnefo_stats / grapher.py @ 671db71c

History | View | Annotate | Download (8.3 kB)

1
# Copyright 2011-2013 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34
from django.http import HttpResponse
35

    
36
import gd
37
import os
38
import sys
39
import subprocess
40

    
41
from cgi import escape
42
from cStringIO import StringIO
43

    
44
import rrdtool
45

    
46
from synnefo_stats import settings
47

    
48
from synnefo.util.text import uenc
49
from snf_django.lib.api import faults, api_method
50

    
51
from logging import getLogger
52
log = getLogger(__name__)
53

    
54

    
55
def read_file(filepath):
56
    f = open(filepath, "r")
57

    
58
    try:
59
        data = f.read()
60
    finally:
61
        f.close()
62

    
63
    return data
64

    
65

    
66
def draw_cpu_bar(fname, outfname=None):
67
    fname = os.path.join(fname, "cpu", "virt_cpu_total.rrd")
68

    
69
    try:
70
        values = rrdtool.fetch(fname, "AVERAGE")[2][-20:]
71
    except rrdtool.error, e:
72
        values = [(0.0, )]
73

    
74
    v = [x[0] for x in values if x[0] is not None]
75
    if not v:
76
        # Fallback in case we only get NaNs
77
        v = [0.0]
78
    # Pick the last value
79
    value = v[-1]
80

    
81
    image = gd.image((settings.IMAGE_WIDTH, settings.HEIGHT))
82

    
83
    border_color = image.colorAllocate(settings.BAR_BORDER_COLOR)
84
    white = image.colorAllocate((0xff, 0xff, 0xff))
85
    background_color = image.colorAllocate(settings.BAR_BG_COLOR)
86

    
87
    if value >= 90.0:
88
        line_color = image.colorAllocate((0xff, 0x00, 0x00))
89
    elif value >= 75.0:
90
        line_color = image.colorAllocate((0xda, 0xaa, 0x00))
91
    else:
92
        line_color = image.colorAllocate((0x00, 0xa1, 0x00))
93

    
94
    image.rectangle((0, 0),
95
                    (settings.WIDTH - 1, settings.HEIGHT - 1),
96
                    border_color, background_color)
97
    image.rectangle((1, 1),
98
                    (int(value / 100.0 * (settings.WIDTH - 2)),
99
                     settings.HEIGHT - 2),
100
                    line_color, line_color)
101
    image.string_ttf(settings.FONT, 8.0, 0.0,
102
                     (settings.WIDTH + 1, settings.HEIGHT - 1),
103
                     "CPU: %.1f%%" % value, white)
104

    
105
    io = StringIO()
106
    image.writePng(io)
107
    io.seek(0)
108
    data = io.getvalue()
109
    io.close()
110
    return data
111

    
112

    
113
def draw_net_bar(fname, outfname=None):
114
    fname = os.path.join(fname, "interface", "if_octets-eth0.rrd")
115

    
116
    try:
117
        values = rrdtool.fetch(fname, "AVERAGE")[2][-20:]
118
    except rrdtool.error, e:
119
        values = [(0.0, 0.0)]
120

    
121
    v = [x for x in values if x[0] is not None and x[1] is not None]
122
    if not v:
123
        # Fallback in case we only get NaNs
124
        v = [(0.0, 0.0)]
125

    
126
    rx_value, tx_value = v[-1]
127

    
128
    # Convert to bits
129
    rx_value = rx_value * 8 / 10 ** 6
130
    tx_value = tx_value * 8 / 10 ** 6
131

    
132
    max_value = (int(max(rx_value, tx_value) / 50) + 1) * 50.0
133

    
134
    image = gd.image((settings.IMAGE_WIDTH, settings.HEIGHT))
135

    
136
    border_color = image.colorAllocate(settings.BAR_BORDER_COLOR)
137
    white = image.colorAllocate((0xff, 0xff, 0xff))
138
    background_color = image.colorAllocate(settings.BAR_BG_COLOR)
139

    
140
    tx_line_color = image.colorAllocate((0x00, 0xa1, 0x00))
141
    rx_line_color = image.colorAllocate((0x00, 0x00, 0xa1))
142

    
143
    image.rectangle((0, 0),
144
                    (settings.WIDTH - 1, settings.HEIGHT - 1),
145
                    border_color, background_color)
146
    image.rectangle((1, 1),
147
                    (int(tx_value / max_value * (settings.WIDTH - 2)),
148
                     settings.HEIGHT / 2 - 1),
149
                    tx_line_color, tx_line_color)
150
    image.rectangle((1, settings.HEIGHT / 2),
151
                    (int(rx_value / max_value * (settings.WIDTH - 2)),
152
                     settings.HEIGHT - 2),
153
                    rx_line_color, rx_line_color)
154
    image.string_ttf(settings.FONT, 8.0, 0.0,
155
                     (settings.WIDTH + 1, settings.HEIGHT - 1),
156
                     "TX/RX: %.2f/%.2f Mbps" % (tx_value, rx_value), white)
157

    
158
    io = StringIO()
159
    image.writePng(io)
160
    io.seek(0)
161
    data = io.getvalue()
162
    io.close()
163
    return data
164

    
165

    
166
def draw_cpu_ts(fname, outfname):
167
    fname = os.path.join(fname, "cpu", "virt_cpu_total.rrd")
168
    outfname += "-cpu.png"
169

    
170
    rrdtool.graph(outfname, "-s", "-1d", "-e", "-20s",
171
                  #"-t", "CPU usage",
172
                  "-v", "%",
173
                  #"--lazy",
174
                  "DEF:cpu=%s:ns:AVERAGE" % fname,
175
                  "LINE1:cpu#00ff00:")
176

    
177
    return read_file(outfname)
178

    
179

    
180
def draw_cpu_ts_w(fname, outfname):
181
    fname = os.path.join(fname, "cpu", "virt_cpu_total.rrd")
182
    outfname += "-cpu-weekly.png"
183

    
184
    rrdtool.graph(outfname, "-s", "-1w", "-e", "-20s",
185
                  #"-t", "CPU usage",
186
                  "-v", "%",
187
                  #"--lazy",
188
                  "DEF:cpu=%s:ns:AVERAGE" % fname,
189
                  "LINE1:cpu#00ff00:")
190

    
191
    return read_file(outfname)
192

    
193

    
194
def draw_net_ts(fname, outfname):
195
    fname = os.path.join(fname, "interface", "if_octets-eth0.rrd")
196
    outfname += "-net.png"
197

    
198
    rrdtool.graph(outfname, "-s", "-1d", "-e", "-20s",
199
              #"-t", "Network traffic",
200
              "--units", "si",
201
              "-v", "Bits/s",
202
              #"--lazy",
203
              "COMMENT:\t\t\tAverage network traffic\\n",
204
              "DEF:rx=%s:rx:AVERAGE" % fname,
205
              "DEF:tx=%s:tx:AVERAGE" % fname,
206
              "CDEF:rxbits=rx,8,*",
207
              "CDEF:txbits=tx,8,*",
208
              "LINE1:rxbits#00ff00:Incoming",
209
              "GPRINT:rxbits:AVERAGE:\t%4.0lf%sbps\t\g",
210
              "LINE1:txbits#0000ff:Outgoing",
211
              "GPRINT:txbits:AVERAGE:\t%4.0lf%sbps\\n")
212

    
213
    return read_file(outfname)
214

    
215

    
216
def draw_net_ts_w(fname, outfname):
217
    fname = os.path.join(fname, "interface", "if_octets-eth0.rrd")
218
    outfname += "-net-weekly.png"
219

    
220
    rrdtool.graph(outfname, "-s", "-1w", "-e", "-20s",
221
              #"-t", "Network traffic",
222
              "--units", "si",
223
              "-v", "Bits/s",
224
              #"--lazy",
225
              "COMMENT:\t\t\tAverage network traffic\\n",
226
              "DEF:rx=%s:rx:AVERAGE" % fname,
227
              "DEF:tx=%s:tx:AVERAGE" % fname,
228
              "CDEF:rxbits=rx,8,*",
229
              "CDEF:txbits=tx,8,*",
230
              "LINE1:rxbits#00ff00:Incoming",
231
              "GPRINT:rxbits:AVERAGE:\t%4.0lf%sbps\t\g",
232
              "LINE1:txbits#0000ff:Outgoing",
233
              "GPRINT:txbits:AVERAGE:\t%4.0lf%sbps\\n")
234

    
235
    return read_file(outfname)
236

    
237

    
238
available_graph_types = {
239
        'cpu-bar': draw_cpu_bar,
240
        'net-bar': draw_net_bar,
241
        'cpu-ts': draw_cpu_ts,
242
        'net-ts': draw_net_ts,
243
        'cpu-ts-w': draw_cpu_ts_w,
244
        'net-ts-w': draw_net_ts_w,
245
        }
246

    
247

    
248
@api_method(http_method='GET', token_required=False, user_required=False,
249
            format_allowed=False, logger=log)
250
def grapher(request, graph_type, hostname):
251
    fname = uenc(os.path.join(settings.RRD_PREFIX, hostname))
252
    if not os.path.isdir(fname):
253
        raise faults.ItemNotFound('No such instance')
254

    
255
    outfname = uenc(os.path.join(settings.GRAPH_PREFIX, hostname))
256
    draw_func = available_graph_types[graph_type]
257

    
258
    response = HttpResponse(draw_func(fname, outfname),
259
                            status=200, content_type="image/png")
260
    response.override_serialization = True
261

    
262
    return response