Statistics
| Branch: | Tag: | Revision:

root / snf-stats-app / synnefo_stats / grapher.py @ bd16bf3e

History | View | Annotate | Download (8.5 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 Crypto.Cipher import AES
47
from base64 import urlsafe_b64decode
48
from hashlib import sha256
49

    
50
from synnefo_stats import settings
51

    
52
from synnefo.util.text import uenc
53
from snf_django.lib.api import faults, api_method
54

    
55
from logging import getLogger
56
log = getLogger(__name__)
57

    
58

    
59
def read_file(filepath):
60
    f = open(filepath, "r")
61

    
62
    try:
63
        data = f.read()
64
    finally:
65
        f.close()
66

    
67
    return data
68

    
69

    
70
def draw_cpu_bar(fname, outfname=None):
71
    fname = os.path.join(fname, "cpu", "virt_cpu_total.rrd")
72

    
73
    try:
74
        values = rrdtool.fetch(fname, "AVERAGE")[2][-20:]
75
    except rrdtool.error, e:
76
        values = [(0.0, )]
77

    
78
    v = [x[0] for x in values if x[0] is not None]
79
    if not v:
80
        # Fallback in case we only get NaNs
81
        v = [0.0]
82
    # Pick the last value
83
    value = v[-1]
84

    
85
    image = gd.image((settings.IMAGE_WIDTH, settings.HEIGHT))
86

    
87
    border_color = image.colorAllocate(settings.BAR_BORDER_COLOR)
88
    white = image.colorAllocate((0xff, 0xff, 0xff))
89
    background_color = image.colorAllocate(settings.BAR_BG_COLOR)
90

    
91
    if value >= 90.0:
92
        line_color = image.colorAllocate((0xff, 0x00, 0x00))
93
    elif value >= 75.0:
94
        line_color = image.colorAllocate((0xda, 0xaa, 0x00))
95
    else:
96
        line_color = image.colorAllocate((0x00, 0xa1, 0x00))
97

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

    
109
    io = StringIO()
110
    image.writePng(io)
111
    io.seek(0)
112
    data = io.getvalue()
113
    io.close()
114
    return data
115

    
116

    
117
def draw_net_bar(fname, outfname=None):
118
    fname = os.path.join(fname, "interface", "if_octets-eth0.rrd")
119

    
120
    try:
121
        values = rrdtool.fetch(fname, "AVERAGE")[2][-20:]
122
    except rrdtool.error, e:
123
        values = [(0.0, 0.0)]
124

    
125
    v = [x for x in values if x[0] is not None and x[1] is not None]
126
    if not v:
127
        # Fallback in case we only get NaNs
128
        v = [(0.0, 0.0)]
129

    
130
    rx_value, tx_value = v[-1]
131

    
132
    # Convert to bits
133
    rx_value = rx_value * 8 / 10 ** 6
134
    tx_value = tx_value * 8 / 10 ** 6
135

    
136
    max_value = (int(max(rx_value, tx_value) / 50) + 1) * 50.0
137

    
138
    image = gd.image((settings.IMAGE_WIDTH, settings.HEIGHT))
139

    
140
    border_color = image.colorAllocate(settings.BAR_BORDER_COLOR)
141
    white = image.colorAllocate((0xff, 0xff, 0xff))
142
    background_color = image.colorAllocate(settings.BAR_BG_COLOR)
143

    
144
    tx_line_color = image.colorAllocate((0x00, 0xa1, 0x00))
145
    rx_line_color = image.colorAllocate((0x00, 0x00, 0xa1))
146

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

    
162
    io = StringIO()
163
    image.writePng(io)
164
    io.seek(0)
165
    data = io.getvalue()
166
    io.close()
167
    return data
168

    
169

    
170
def draw_cpu_ts(fname, outfname):
171
    fname = os.path.join(fname, "cpu", "virt_cpu_total.rrd")
172
    outfname += "-cpu.png"
173

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

    
181
    return read_file(outfname)
182

    
183

    
184
def draw_cpu_ts_w(fname, outfname):
185
    fname = os.path.join(fname, "cpu", "virt_cpu_total.rrd")
186
    outfname += "-cpu-weekly.png"
187

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

    
195
    return read_file(outfname)
196

    
197

    
198
def draw_net_ts(fname, outfname):
199
    fname = os.path.join(fname, "interface", "if_octets-eth0.rrd")
200
    outfname += "-net.png"
201

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

    
215
    return read_file(outfname)
216

    
217

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

    
222
    rrdtool.graph(outfname, "-s", "-1w", "-e", "-20s",
223
              "--units", "si",
224
              "-v", "Bits/s",
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
def decrypt(secret):
239
    # Make sure key is 32 bytes long
240
    key = sha256(settings.STATS_SECRET_KEY).digest()
241

    
242
    aes = AES.new(key)
243
    return aes.decrypt(urlsafe_b64decode(secret)).rstrip('\x00')
244

    
245

    
246
available_graph_types = {
247
        'cpu-bar': draw_cpu_bar,
248
        'net-bar': draw_net_bar,
249
        'cpu-ts': draw_cpu_ts,
250
        'net-ts': draw_net_ts,
251
        'cpu-ts-w': draw_cpu_ts_w,
252
        'net-ts-w': draw_net_ts_w,
253
        }
254

    
255

    
256
@api_method(http_method='GET', token_required=False, user_required=False,
257
            format_allowed=False, logger=log)
258
def grapher(request, graph_type, hostname):
259
    hostname = decrypt(uenc(hostname))
260
    fname = uenc(os.path.join(settings.RRD_PREFIX, hostname))
261
    if not os.path.isdir(fname):
262
        raise faults.ItemNotFound('No such instance')
263

    
264
    outfname = uenc(os.path.join(settings.GRAPH_PREFIX, hostname))
265
    draw_func = available_graph_types[graph_type]
266

    
267
    response = HttpResponse(draw_func(fname, outfname),
268
                            status=200, content_type="image/png")
269
    response.override_serialization = True
270

    
271
    return response