Revision 04733cdb
b/.gitignore | ||
---|---|---|
1 |
settings.py |
|
2 |
*.pyc |
|
3 |
rrds/ |
|
4 |
*.db |
b/core/admin.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- vim:encoding=utf-8: |
|
2 |
# vim: tabstop=4:shiftwidth=4:softtabstop=4:expandtab |
|
3 |
|
|
4 |
from core.models import * |
|
5 |
|
|
6 |
from django.contrib import admin |
|
7 |
|
|
8 |
admin.site.register(Lun) |
b/core/management/__init__.py | ||
---|---|---|
1 |
|
b/core/management/commands/__dbfuncs__.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- vim:encoding=utf-8: |
|
2 |
# vim: tabstop=4:shiftwidth=4:softtabstop=4:expandtab |
|
3 |
|
|
4 |
from django.conf import settings |
|
5 |
from core.models import * |
|
6 |
from graphs.models import * |
|
7 |
from datetime import datetime, timedelta |
|
8 |
import time |
|
9 |
import sys |
|
10 |
|
|
11 |
def expireoldgraphs(node, type): |
|
12 |
if node.snmpmanagednode.perm_fail == 1: |
|
13 |
graphs = Graph.objects.filter(host=node) |
|
14 |
graphs.delete() |
|
15 |
return |
|
16 |
graphs = Graph.objects.filter(host=node, type=type, |
|
17 |
date__lt=datetime.now()-timedelta(settings.RG_EXPIRE_DAYS)) |
|
18 |
graphs.delete() |
|
19 |
|
|
20 |
def dbreg(type, rrdpath, lun, **kwargs): |
|
21 |
try: |
|
22 |
rrdfile = RrdFile.objects.get(path=rrdpath) |
|
23 |
except RrdFile.DoesNotExist, e: |
|
24 |
rrdfile = RrdFile(path=rrdpath) |
|
25 |
rrdfile.save() |
|
26 |
except RrdFile.MultipleObjectsReturned, e: |
|
27 |
#This should never happen |
|
28 |
sys.stderr.write('Multiple RrdFiles in database. This should never happen\n') |
|
29 |
sys.stderr.write('RRD File: %s\n' % rrdpath) |
|
30 |
sys.stderr.write('Refusing to proceed. Fix the problem\n') |
|
31 |
sys.exit() |
|
32 |
|
|
33 |
if 'tags' in kwargs: |
|
34 |
tags = kwargs['tags'] |
|
35 |
else: |
|
36 |
tags = [] |
|
37 |
#Making the assumption that an RRDfile will never contain data sources not in the database |
|
38 |
#Also since the rrdfiles are created by mrtg datasource names are hardcoded. |
|
39 |
if rrdfile.datasource_set.count() == 0: |
|
40 |
ds0 = DataSource(name='ds0') |
|
41 |
ds0.rrdfile=rrdfile |
|
42 |
ds0.save() |
|
43 |
ds0.tags.clear() |
|
44 |
for tag in tags: |
|
45 |
ds0.tags.add(tag) |
|
46 |
# ds0.content_object = content |
|
47 |
|
|
48 |
ds1 = DataSource(name='ds1') |
|
49 |
ds1.rrdfile=rrdfile |
|
50 |
ds1.save() |
|
51 |
ds1.tags.clear() |
|
52 |
for tag in tags: |
|
53 |
ds1.tags.add(tag) |
|
54 |
# ds1.content_object = content |
|
55 |
|
|
56 |
datasources=[ds0,ds1] |
|
57 |
else: |
|
58 |
datasources=rrdfile.datasource_set.all() |
|
59 |
for tempds in datasources: |
|
60 |
tempds.tags.clear() |
|
61 |
for tag in tags: |
|
62 |
tempds.tags.add(tag) |
|
63 |
|
|
64 |
|
|
65 |
datasourcespk = [ds.pk for ds in datasources] |
|
66 |
graphs = Graph.objects.filter(type=type,tags__name='lun:%s'%lun.pk,datasources__pk__in=datasourcespk).distinct() |
|
67 |
if len(graphs) == 1: |
|
68 |
graph = graphs[0] |
|
69 |
if len(graphs) < 1: |
|
70 |
graph = Graph(type=type) |
|
71 |
graph.save() |
|
72 |
graph.tags.add('lun:%s'%lun.pk) |
|
73 |
if 'description' in kwargs: |
|
74 |
graph.tags.add('graphdescription:%s %s'%(lun.name, kwargs['description'])) |
|
75 |
elif len(graphs) > 1: |
|
76 |
sys.stderr.write('Found more than one possible graph. Using oldest one as an approximation: %s\n' % e) |
|
77 |
graph = Graph.objects.filter(type=type,tags__name='lun:%s'%lun.pk,datasources__pk__in=datasourcespk).distinct().order_by('-date')[0] |
|
78 |
|
|
79 |
# Setting various attributes through kwargs |
|
80 |
for i in ('pc95','units','logarithmic','label','description'): |
|
81 |
if i in kwargs: |
|
82 |
setattr(graph,i,kwargs[i]) |
|
83 |
|
|
84 |
graph.save() |
|
85 |
|
|
86 |
for datasource in datasources: |
|
87 |
try: |
|
88 |
grds = GraphDataSource.objects.get(graph=graph,datasource=datasource) |
|
89 |
except GraphDataSource.DoesNotExist, e: |
|
90 |
grds = GraphDataSource(graph=graph,datasource=datasource) |
|
91 |
# Some defaults |
|
92 |
grds.args = '' |
|
93 |
grds.function = 'LINE1' |
|
94 |
grds.color = '#0000bbbb' |
|
95 |
# Hardcoded default. Max Datasources in a graph. |
|
96 |
for n in range(0,1024): |
|
97 |
for i in ( 'function', 'color', 'args', 'legend', 'stackorder' ): |
|
98 |
if datasource.name == 'ds%s' % n and 'ds%s%s' % ( n, i ) in kwargs: |
|
99 |
setattr(grds,i,kwargs['ds%s%s' % ( n, i)]) |
|
100 |
grds.save() |
|
101 |
|
|
102 |
try: |
|
103 |
f = open(rrdpath) |
|
104 |
except IOError: |
|
105 |
necreation_epoch = int(time.time()) |
|
106 |
f = rrdtool.create(str(rrdpath), str("--start"), str(1349965501), [str('DS:ds0:COUNTER:300:U:U'), str('DS:ds1:COUNTER:300:U:U')], str('RRA:AVERAGE:0.5:1:288'), str('RRA:AVERAGE:0.5:3:672'), str('RRA:AVERAGE:0.5:12:744'), str('RRA:AVERAGE:0.5:72:1460')) |
|
107 |
|
b/core/management/commands/__genrgconfig__.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- vim:encoding=utf-8: |
|
2 |
# vim: tabstop=4:shiftwidth=4:softtabstop=4:expandtab |
|
3 |
from __dbfuncs__ import * |
|
4 |
from core.models import * |
|
5 |
from django.conf import settings |
|
6 |
import rrdtool |
|
7 |
import time |
|
8 |
import datetime |
|
9 |
|
|
10 |
|
|
11 |
|
|
12 |
def diskio_genconfig(lun): |
|
13 |
devfilename = "lun_%s" % lun.pk |
|
14 |
seriesfilename = 'blocksiostats' |
|
15 |
target = "%s_%s" % (devfilename, seriesfilename) |
|
16 |
rrdpath = settings.RRDFILEDIR + '/%s.rrd' % target |
|
17 |
|
|
18 |
dbreg('diskio', rrdpath, lun, description='Disk IO' , label=target, |
|
19 |
ds0legend='Blocks Read', ds1legend='Blocks Write', |
|
20 |
ds0function='AREA', ds1function='LINE1', |
|
21 |
ds0color='#00bb00bb', ds1color='#0000bbbb', |
|
22 |
pc95=False, units='blocks', tags=['lun:%s'%lun.pk, 'ds0:blocksread', "ds1:blockswrite", "type:blocksiostats"]) |
|
23 |
|
|
24 |
def requests_genconfig(lun): |
|
25 |
devfilename = "lun_%s" % lun.pk |
|
26 |
seriesfilename = 'requeststats' |
|
27 |
target = "%s_%s" % (devfilename, seriesfilename) |
|
28 |
rrdpath = settings.RRDFILEDIR + '/%s.rrd' % target |
|
29 |
|
|
30 |
dbreg('requests', rrdpath, lun, description='Requests IO' , label=target, |
|
31 |
ds0legend='Read Requests', ds1legend='Write Requests', |
|
32 |
ds0function='AREA', ds1function='LINE1', |
|
33 |
ds0color='#00bb00bb', ds1color='#0000bbbb', |
|
34 |
pc95=False, units='reuests', tags=['lun:%s'%lun.pk, 'ds0:readrequests', "ds1:writerequests", "type:requeststats"]) |
|
35 |
|
|
36 |
|
|
37 |
|
b/core/management/commands/__init__.py | ||
---|---|---|
1 |
|
b/core/management/commands/generate_csv.py | ||
---|---|---|
1 |
# -*- coing: utf-8 -*- vim:encoding=utf-8: |
|
2 |
# vim: tabstop=4:shiftwidth=4:softtabstop=4:expandtab |
|
3 |
|
|
4 |
# Copyright 2013 Leonidas Poulopoulos |
|
5 |
# |
|
6 |
# Licensed under the Apache License, Version 2.0 (the "License"); |
|
7 |
# you may not use this file except in compliance with the License. |
|
8 |
# You may obtain a copy of the License at |
|
9 |
# |
|
10 |
# http://www.apache.org/licenses/LICENSE-2.0 |
|
11 |
# |
|
12 |
# Unless required by applicable law or agreed to in writing, software |
|
13 |
# distributed under the License is distributed on an "AS IS" BASIS, |
|
14 |
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
15 |
# See the License for the specific language governing permissions and |
|
16 |
# limitations under the License. |
|
17 |
|
|
18 |
from django.core.management.base import BaseCommand, CommandError |
|
19 |
from django.conf import settings |
|
20 |
from core.models import * |
|
21 |
import csv |
|
22 |
import time |
|
23 |
import random |
|
24 |
|
|
25 |
class Command(BaseCommand): |
|
26 |
help = "Populates the lun series measurements from a csv file" \ |
|
27 |
"It assumes that the meassurement file is formatted like:\n" \ |
|
28 |
"<unix_timestamp>\n"\ |
|
29 |
"LUNID, METRIC_A, METRIC_B, METRIC_C, METRIC_D\n"\ |
|
30 |
"1, 0, 2, 2, 1\n"\ |
|
31 |
"2, 0, 0, 0, 0\n" \ |
|
32 |
"...\n" \ |
|
33 |
"N, 1, 2, 3, 1 \n" \ |
|
34 |
|
|
35 |
args = "" |
|
36 |
label = '' |
|
37 |
# HAve that in mind: |
|
38 |
# '''{'lun_id': 12, stats'diskio':['blocksread':189202196, 'blockswrite':279338720], 'requests':[readreq':121123, 'writereq': 2121234], 'timestamp':1412341234}''' |
|
39 |
def handle(self, *args, **options): |
|
40 |
f = open(settings.CSV_FILE, "w") |
|
41 |
out = str(int(time.time())) + ",\n" |
|
42 |
lun_ids = [10, 20, 21, 22, 12, 42, 32, 31, 144, 11, 233, 321134, 324, 4234, 2343, 631, 3456, 2341, 635, 152345, 12345123, 123496, 65431] |
|
43 |
header = ["Blocks Read","Blocks Write","Read Reqs","Write Reqs"] |
|
44 |
out = out + "LUN," +",".join(header) + "," + "\n" |
|
45 |
for (i,k) in enumerate(lun_ids): |
|
46 |
out = out + str(lun_ids[i]) + "," |
|
47 |
for j in header: |
|
48 |
out = out + str(random.randint(8000, 9000)) + "," |
|
49 |
out = out + "\n" |
|
50 |
f.write(out) |
|
51 |
f.close() |
|
52 |
|
|
53 |
|
|
54 |
def handle_json(self, *args, **options): |
|
55 |
f = open(settings.CSV_FILE, "w") |
|
56 |
out = str(int(time.time())) + "\n" |
|
57 |
lun_ids = [10, 20, 21, 22, 12, 42, 32, 31, 144, 11, 233, 321134, 324, 4234, 2343, 631, 3456, 2341, 635, 152345, 12345123, 123496, 65431] |
|
58 |
out = '{"luns": [' |
|
59 |
for id in lun_ids: |
|
60 |
out = out + '{"lun_id":%s, "diskio":{"blocksread":%s, "blockswrite":%s}, "requests":{"readreq":%s, "writereq": %s}, "timestamp":%s}' % (id, random.randint(100, 10000), random.randint(100, 10000), random.randint(100, 10000), random.randint(100, 10000), int(time.time())) |
|
61 |
out = out + "," |
|
62 |
out = out.rstrip(',') |
|
63 |
out = out + ']}' |
|
64 |
f.write(out) |
|
65 |
f.close() |
|
66 |
|
|
67 |
|
|
68 |
|
|
69 |
|
|
70 |
|
b/core/management/commands/parse_csv.py | ||
---|---|---|
1 |
# -*- coing: utf-8 -*- vim:encoding=utf-8: |
|
2 |
# vim: tabstop=4:shiftwidth=4:softtabstop=4:expandtab |
|
3 |
|
|
4 |
# Copyright 2013 Leonidas Poulopoulos |
|
5 |
# |
|
6 |
# Licensed under the Apache License, Version 2.0 (the "License"); |
|
7 |
# you may not use this file except in compliance with the License. |
|
8 |
# You may obtain a copy of the License at |
|
9 |
# |
|
10 |
# http://www.apache.org/licenses/LICENSE-2.0 |
|
11 |
# |
|
12 |
# Unless required by applicable law or agreed to in writing, software |
|
13 |
# distributed under the License is distributed on an "AS IS" BASIS, |
|
14 |
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
15 |
# See the License for the specific language governing permissions and |
|
16 |
# limitations under the License. |
|
17 |
|
|
18 |
from django.core.management.base import BaseCommand, CommandError |
|
19 |
from django.conf import settings |
|
20 |
from core.models import * |
|
21 |
from graphs.models import * |
|
22 |
import csv |
|
23 |
import time |
|
24 |
|
|
25 |
import json |
|
26 |
from __genrgconfig__ import * |
|
27 |
from __dbfuncs__ import * |
|
28 |
|
|
29 |
# for file listing |
|
30 |
from stat import S_ISREG, ST_CTIME, ST_MODE |
|
31 |
import os, sys, time, datetime |
|
32 |
|
|
33 |
class Command(BaseCommand): |
|
34 |
help = "Populates the lun series measurements from a csv file" \ |
|
35 |
"It assumes that the meassurement file is formatted like:\n" \ |
|
36 |
"<unix_timestamp>\n"\ |
|
37 |
"LUNID, METRIC_A, METRIC_B, METRIC_C, METRIC_D\n"\ |
|
38 |
"1, 0, 2, 2, 1\n"\ |
|
39 |
"2, 0, 0, 0, 0\n" \ |
|
40 |
"...\n" \ |
|
41 |
"N, 1, 2, 3, 1 \n" \ |
|
42 |
|
|
43 |
args = "" |
|
44 |
label = '' |
|
45 |
|
|
46 |
def handle(self, *args, **options): |
|
47 |
for statsfile in self.get_files_listing(): |
|
48 |
self.getgraph(statsfile) |
|
49 |
print "Finished!" |
|
50 |
|
|
51 |
def get_files_listing(self): |
|
52 |
|
|
53 |
# path to the directory (relative or absolute) |
|
54 |
dirpath = '/srv/emc-stats/incoming' |
|
55 |
|
|
56 |
# get all entries in the directory w/ stats |
|
57 |
entries = (os.path.join(dirpath, fn) for fn in os.listdir(dirpath)) |
|
58 |
entries = ((os.stat(path), path) for path in entries) |
|
59 |
|
|
60 |
# leave only regular files, insert creation date |
|
61 |
entries = ((stat[ST_CTIME], path) |
|
62 |
for stat, path in entries if S_ISREG(stat[ST_MODE])) |
|
63 |
#NOTE: on Windows `ST_CTIME` is a creation date |
|
64 |
# but on Unix it could be something else |
|
65 |
#NOTE: use `ST_MTIME` to sort by a modification date |
|
66 |
statfiles = [] |
|
67 |
for cdate, path in sorted(entries): |
|
68 |
statfiles.append("%s/%s"%('/srv/emc-stats/incoming',os.path.basename(path))) |
|
69 |
return statfiles |
|
70 |
|
|
71 |
def getgraph(self, statsfile): |
|
72 |
# We assume that the following is received |
|
73 |
# 1380622204 |
|
74 |
# LUN,Blocks Read,Blocks Write,Read Reqs,Write Reqs, |
|
75 |
# 10,7346,8149,5311,9510, |
|
76 |
# 20,4581,3879,2894,9419, |
|
77 |
|
|
78 |
f = open(statsfile) |
|
79 |
#print statsfile |
|
80 |
# get first line. This is the timestamp |
|
81 |
timestamp = "%s" %f.readline().replace('\n', '') |
|
82 |
reader = csv.reader(self.remove_last_char(f)) |
|
83 |
timestamp = self.round_to_5(int(timestamp)) |
|
84 |
out = {} |
|
85 |
luns = [] |
|
86 |
for i,serieslist in enumerate(reader): |
|
87 |
#print serieslist |
|
88 |
lundict = {} |
|
89 |
if i == 0: |
|
90 |
#those are headers, ignore (for the time) |
|
91 |
continue |
|
92 |
lundict['lun_name'] = serieslist[0] |
|
93 |
lundict['timestamp'] = timestamp |
|
94 |
lundict['blocksread'] = serieslist[1] |
|
95 |
lundict['blockswrite'] = serieslist[2] |
|
96 |
lundict['readreq'] = serieslist[3] |
|
97 |
lundict['writereq'] = serieslist[4] |
|
98 |
lun, created = Lun.objects.get_or_create(name="%s"%lundict['lun_name']) |
|
99 |
if created: |
|
100 |
types = settings.LUN_GRAPH_TYPES |
|
101 |
for type in types: |
|
102 |
self.dbupdate(lun, type) |
|
103 |
lundict['lun_id'] = lun.pk |
|
104 |
luns.append(lundict) |
|
105 |
self.update_graphs(luns) |
|
106 |
f.close() |
|
107 |
print "Done with %s" %statsfile |
|
108 |
|
|
109 |
def update_graphs(self, lunslist): |
|
110 |
for lundict in lunslist: |
|
111 |
self.update_graph(lundict) |
|
112 |
|
|
113 |
|
|
114 |
def update_graph(self, lundict): |
|
115 |
# Try to automate once proof of concept is done |
|
116 |
#print "updating Lun %s"%(lundict['lun_name']) |
|
117 |
dss_block = DataSource.objects.values('rrdfile').filter(tags__name="lun:%s"%lundict['lun_id']).filter(tags__name="type:blocksiostats").distinct() |
|
118 |
# This should return a single element list |
|
119 |
if len(dss_block) != 1: |
|
120 |
print "ERROR: It appears that there are %s blocksiostats datasources for lun %s" %(len(dss_block), lundict['lun_name']) |
|
121 |
else: |
|
122 |
rrdfile = dss_block[0]['rrdfile'] |
|
123 |
try: |
|
124 |
rrdtool.update( |
|
125 |
str('%s'%(RrdFile.objects.get(pk=rrdfile).path)), |
|
126 |
str('--template'), str('ds0:ds1'), |
|
127 |
str(lundict['timestamp']+":" +lundict['blocksread']+":"+lundict['blockswrite'])) |
|
128 |
except Exception as e: |
|
129 |
#print e |
|
130 |
pass |
|
131 |
|
|
132 |
dss_req = DataSource.objects.values('rrdfile').filter(tags__name="lun:%s"%lundict['lun_id']).filter(tags__name="type:requeststats").distinct() |
|
133 |
# This should return a single element list |
|
134 |
if len(dss_req) != 1: |
|
135 |
print "ERROR: It appears that there are %s requeststats datasources for lun %s" %(len(dss_req), lundict['lun_name']) |
|
136 |
else: |
|
137 |
rrdfile = dss_req[0]['rrdfile'] |
|
138 |
try: |
|
139 |
rrdtool.update( |
|
140 |
str('%s'%(RrdFile.objects.get(pk=rrdfile).path)), |
|
141 |
str('--template'), str('ds0:ds1'), |
|
142 |
str(lundict['timestamp']+":" +lundict['readreq']+":"+lundict['writereq'])) |
|
143 |
except Exception as e: |
|
144 |
#print e |
|
145 |
pass |
|
146 |
|
|
147 |
def round_to_5(self, timestamp): |
|
148 |
|
|
149 |
tm = datetime.datetime.fromtimestamp(timestamp) |
|
150 |
tm = tm - datetime.timedelta(minutes=tm.minute % 5, |
|
151 |
seconds=tm.second, |
|
152 |
microseconds=tm.microsecond) |
|
153 |
return tm.strftime("%s") |
|
154 |
|
|
155 |
def remove_last_char(self, fileobj): |
|
156 |
for line in fileobj: |
|
157 |
yield line.strip()[:-1] |
|
158 |
|
|
159 |
def dbupdate(self, device, type): |
|
160 |
# Ok. A couple of hacks here. Instead of a if then elif then elif.... for each type |
|
161 |
# we get first from the settings dictionnary the confdir and then the function used |
|
162 |
# for this type from the globals() dictionary |
|
163 |
try: |
|
164 |
# dir = getattr(settings,'RG_%s_CONFDIR' % type.upper()) |
|
165 |
# self.checkdir(dir) |
|
166 |
|
|
167 |
func = globals()['%s_genconfig' % type] |
|
168 |
func(device) |
|
169 |
except AttributeError as e: |
|
170 |
print "%s is not going to be configured because %s" % (type, e) |
|
171 |
return |
|
172 |
|
|
173 |
|
b/core/models.py | ||
---|---|---|
1 |
from django.db import models |
|
2 |
# Create your models here. |
|
3 |
|
|
4 |
from taggit.managers import TaggableManager |
|
5 |
|
|
6 |
class Lun(models.Model): |
|
7 |
name = models.CharField(max_length=80, null=True, blank=True) |
|
8 |
tags = TaggableManager() |
|
9 |
|
|
10 |
def __unicode__(self): |
|
11 |
return 'ID: %s, Name: %s' % (self.pk, self.name) |
|
12 |
|
|
13 |
def getGraphs(self): |
|
14 |
lungraphs = Graph.objects.filter(tags__name='lun:%s'%self.pk) |
|
15 |
return lungraphs |
|
16 |
|
|
17 |
def graphCounters(self, dataset): |
|
18 |
'''dataset format\n'''\ |
|
19 |
'''{'lun_id': 12, 'diskio':['blocksread':189202196, 'blockswrite':279338720], 'requests':[readreq':121123, 'writereq': 2121234], 'timestamp':1412341234}''' |
|
20 |
|
|
21 |
if not dataset: |
|
22 |
return "Empty data for lun %s" %(self.name) |
|
23 |
|
|
24 |
reps = {'/':'_', '#':'_','-':'_', ' ':'_'} |
|
25 |
|
|
26 |
pmpoints_list = res |
|
27 |
for pmpoints_meassurement in res: |
|
28 |
period_epoch = pmpoints_meassurement['timestamp'] |
|
29 |
for serie in self.series.all(): |
|
30 |
counterLink = DeviceSeries.objects.get(device=self, series__name=serie.name) |
|
31 |
try: |
|
32 |
rrdfilename = counterLink.getGraphUrl() |
|
33 |
except Exception as e: |
|
34 |
print e |
|
35 |
continue |
|
36 |
if rrdfilename: |
|
37 |
c_value = pmpoints_meassurement[serie.name] |
|
38 |
try: |
|
39 |
rrdtool.update(str('%s'%(rrdfilename)), str(period_epoch+":" + c_value)) |
|
40 |
print "done updating" |
|
41 |
except Exception as e: |
|
42 |
print e |
|
43 |
continue |
|
44 |
else: |
|
45 |
print "No rrd file found" |
|
46 |
pass |
|
47 |
|
|
48 |
from graphs.models import * |
|
49 |
|
|
50 |
|
b/core/tests.py | ||
---|---|---|
1 |
""" |
|
2 |
This file demonstrates two different styles of tests (one doctest and one |
|
3 |
unittest). These will both pass when you run "manage.py test". |
|
4 |
|
|
5 |
Replace these with more appropriate tests for your application. |
|
6 |
""" |
|
7 |
|
|
8 |
from django.test import TestCase |
|
9 |
|
|
10 |
class SimpleTest(TestCase): |
|
11 |
def test_basic_addition(self): |
|
12 |
""" |
|
13 |
Tests that 1 + 1 always equals 2. |
|
14 |
""" |
|
15 |
self.failUnlessEqual(1 + 1, 2) |
|
16 |
|
|
17 |
__test__ = {"doctest": """ |
|
18 |
Another way to test that 1 + 1 is equal to 2. |
|
19 |
|
|
20 |
>>> 1 + 1 == 2 |
|
21 |
True |
|
22 |
"""} |
|
23 |
|
b/core/views.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- vim:encoding=utf-8: |
|
2 |
# vim: tabstop=4:shiftwidth=4:softtabstop=4:expandtab |
|
3 |
|
|
4 |
from core.models import * |
|
5 |
from graphs.models import * |
|
6 |
from django.core.urlresolvers import reverse |
|
7 |
from django.shortcuts import render_to_response,get_object_or_404 |
|
8 |
from django.http import HttpResponse,HttpResponseRedirect,Http404 |
|
9 |
from django.conf import settings |
|
10 |
|
|
11 |
from django.views.decorators.cache import cache_page |
|
12 |
from django.views.decorators.cache import never_cache |
|
13 |
from django.core.cache import cache |
|
14 |
|
|
15 |
from django.template import RequestContext |
|
16 |
|
|
17 |
import json |
|
18 |
|
|
19 |
import datetime |
|
20 |
import time |
|
21 |
import rrdtool |
|
22 |
import tempfile |
|
23 |
import os |
|
24 |
import re |
|
25 |
import random |
|
26 |
import pprint |
|
27 |
|
|
28 |
from gevent.pool import Pool |
|
29 |
from gevent.timeout import Timeout |
|
30 |
|
|
31 |
from util.analyze import analyze |
|
32 |
|
|
33 |
def index(request): |
|
34 |
luns = Lun.objects.all() |
|
35 |
graphs = Graph.objects.all() |
|
36 |
nl = cache.get('lun:stats') |
|
37 |
if nl is None: |
|
38 |
nl = analyze(luns) |
|
39 |
urls = [] |
|
40 |
|
|
41 |
for i in nl: |
|
42 |
lun = luns.get(pk=i['lunpk']) |
|
43 |
graph = graphs.get(pk=i['graph']) |
|
44 |
if int(i['avg']) == 0: |
|
45 |
continue |
|
46 |
#for graph in lungraphs: |
|
47 |
graph_dict = {} |
|
48 |
graph.url = reverse(thumb, args=(graph.pk,)) |
|
49 |
graph_dict['url'] = graph.url |
|
50 |
graph_dict['graph'] = graph |
|
51 |
graph_dict['lun'] = lun.name |
|
52 |
urls.append(graph_dict) |
|
53 |
ret = { |
|
54 |
'urls' :urls |
|
55 |
} |
|
56 |
return render_to_response('media.html', ret, |
|
57 |
context_instance=RequestContext(request)) |
|
58 |
|
|
59 |
def graphjson(request, graph_id): |
|
60 |
ret = {"graph_pk": graph_id} |
|
61 |
return render_to_response('graph.html', ret, |
|
62 |
context_instance=RequestContext(request)) |
|
63 |
|
|
64 |
|
|
65 |
def graphperiods(request, graph_id): |
|
66 |
graph = Graph.objects.get(pk=graph_id) |
|
67 |
return render_to_response('graphperiods.html', {'graph': graph, }, |
|
68 |
context_instance=RequestContext(request)) |
|
69 |
|
|
70 |
|
|
71 |
def get_all_json(lazy=False, legend=False, start='-2d', end='-8m'): |
|
72 |
graphs = Graph.objects.all() |
|
73 |
kwargs = { 'start': str(start), 'end': str(end), 'lazy': lazy, 'legend': legend, 'jsonexport':True } |
|
74 |
results = [] |
|
75 |
for graph in graphs: |
|
76 |
results.extend(graph.create_graph(path=None, **kwargs)) |
|
77 |
# p = Pool(40) |
|
78 |
# |
|
79 |
# def _get_graphs_json(graph): |
|
80 |
# t = Timeout(5) |
|
81 |
# t.start() |
|
82 |
# try: |
|
83 |
# results.extend(graph.create_graph(path=None, **kwargs)) |
|
84 |
# except (Timeout): |
|
85 |
# pass |
|
86 |
# finally: |
|
87 |
# t.cancel() |
|
88 |
# p.imap(_get_graphs_json, graphs) |
|
89 |
return results |
|
90 |
|
|
91 |
|
|
92 |
def getpngdata(path,static): |
|
93 |
|
|
94 |
file = open(path,'r') |
|
95 |
data = file.read() |
|
96 |
file.close() |
|
97 |
|
|
98 |
if static == False: |
|
99 |
os.remove(path) |
|
100 |
|
|
101 |
response = HttpResponse(mimetype='image/png') |
|
102 |
response.write(data) |
|
103 |
return response |
|
104 |
|
|
105 |
def drawpng(request, graph_id, static=True, lazy=False, legend=False, start='-30h', end='-8m', jsonexport=False): |
|
106 |
|
|
107 |
graph = get_object_or_404(Graph,pk=graph_id) |
|
108 |
if static == True: |
|
109 |
lazy = True |
|
110 |
path = '%s/%s%s%s.png' % (settings.RRD_PNG_STATICPATH, graph_id, start, end) |
|
111 |
try: |
|
112 |
statinfo = os.stat(path) |
|
113 |
if time.time() - statinfo.st_mtime < 300 : |
|
114 |
return getpngdata(path,static) |
|
115 |
except OSError: |
|
116 |
pass |
|
117 |
else: |
|
118 |
path = tempfile.NamedTemporaryFile().name |
|
119 |
'''Removing unicode from strings while constructing kwargs''' |
|
120 |
kwargs = { 'start': str(start), 'end': str(end), 'lazy': lazy, 'legend': legend, 'jsonexport':jsonexport } |
|
121 |
result = graph.create_graph(path, **kwargs) |
|
122 |
if result == None: |
|
123 |
raise Http404 |
|
124 |
if jsonexport: |
|
125 |
return HttpResponse(json.dumps(result), mimetype="application/json") |
|
126 |
return getpngdata(path, static) |
|
127 |
|
|
128 |
|
|
129 |
def thumb(request,graph_id,legend=True,lazy=True,static=True): |
|
130 |
|
|
131 |
start='-1d' |
|
132 |
end='-300' |
|
133 |
|
|
134 |
graph = get_object_or_404(Graph,pk=graph_id) |
|
135 |
|
|
136 |
path = '%s/thumb-%s.png' % (settings.RRD_PNG_STATICPATH, graph_id) |
|
137 |
try: |
|
138 |
statinfo = os.stat(path) |
|
139 |
if time.time() - statinfo.st_mtime < 300 : |
|
140 |
return getpngdata(path,static) |
|
141 |
except OSError: |
|
142 |
pass |
|
143 |
|
|
144 |
'''Removing unicode from strings while constructing kwargs''' |
|
145 |
kwargs = { 'start': str(start), 'end': str(end), 'lazy': lazy, 'legend': legend, 'thumb': True, } |
|
146 |
|
|
147 |
result=graph.create_graph(path,**kwargs) |
|
148 |
return getpngdata(path,static) |
|
149 |
|
b/graphs/admin.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- vim:encoding=utf-8: |
|
2 |
# vim: tabstop=4:shiftwidth=4:softtabstop=4:expandtab |
|
3 |
|
|
4 |
from graphs.models import * |
|
5 |
|
|
6 |
from django.contrib import admin |
|
7 |
|
|
8 |
admin.site.register(Graph) |
|
9 |
admin.site.register(GraphLabel) |
|
10 |
admin.site.register(RrdFile) |
|
11 |
admin.site.register(DataSource) |
|
12 |
admin.site.register(GraphDataSource) |
b/graphs/management/commands/__dbfuncs__.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- vim:encoding=utf-8: |
|
2 |
# vim: tabstop=4:shiftwidth=4:softtabstop=4:expandtab |
|
3 |
|
|
4 |
from django.conf import settings |
|
5 |
from core.models import * |
|
6 |
from graphs.models import * |
|
7 |
from datetime import datetime, timedelta |
|
8 |
import time |
|
9 |
import sys |
|
10 |
|
|
11 |
def expireoldgraphs(node, type): |
|
12 |
if node.snmpmanagednode.perm_fail == 1: |
|
13 |
graphs = Graph.objects.filter(host=node) |
|
14 |
graphs.delete() |
|
15 |
return |
|
16 |
graphs = Graph.objects.filter(host=node, type=type, |
|
17 |
date__lt=datetime.now()-timedelta(settings.RG_EXPIRE_DAYS)) |
|
18 |
graphs.delete() |
|
19 |
|
|
20 |
def dbreg(type, rrdpath, lun, **kwargs): |
|
21 |
try: |
|
22 |
rrdfile = RrdFile.objects.get(path=rrdpath) |
|
23 |
except RrdFile.DoesNotExist, e: |
|
24 |
rrdfile = RrdFile(path=rrdpath) |
|
25 |
rrdfile.save() |
|
26 |
except RrdFile.MultipleObjectsReturned, e: |
|
27 |
#This should never happen |
|
28 |
sys.stderr.write('Multiple RrdFiles in database. This should never happen\n') |
|
29 |
sys.stderr.write('RRD File: %s\n' % rrdpath) |
|
30 |
sys.stderr.write('Refusing to proceed. Fix the problem\n') |
|
31 |
sys.exit() |
|
32 |
|
|
33 |
if 'tags' in kwargs: |
|
34 |
tags = kwargs['tags'] |
|
35 |
else: |
|
36 |
tags = [] |
|
37 |
#Making the assumption that an RRDfile will never contain data sources not in the database |
|
38 |
#Also since the rrdfiles are created by mrtg datasource names are hardcoded. |
|
39 |
if rrdfile.datasource_set.count() == 0: |
|
40 |
ds0 = DataSource(name='ds0') |
|
41 |
ds0.rrdfile=rrdfile |
|
42 |
ds0.save() |
|
43 |
ds0.tags.clear() |
|
44 |
for tag in tags: |
|
45 |
if 'ds1:' in tag: |
|
46 |
continue |
|
47 |
ds0.tags.add(tag) |
|
48 |
|
|
49 |
# ds0.content_object = content |
|
50 |
|
|
51 |
ds1 = DataSource(name='ds1') |
|
52 |
ds1.rrdfile=rrdfile |
|
53 |
ds1.save() |
|
54 |
ds1.tags.clear() |
|
55 |
for tag in tags: |
|
56 |
if 'ds0:' in tag: |
|
57 |
continue |
|
58 |
ds1.tags.add(tag) |
|
59 |
# ds1.content_object = content |
|
60 |
|
|
61 |
datasources=[ds0,ds1] |
|
62 |
else: |
|
63 |
datasources=rrdfile.datasource_set.all() |
|
64 |
for tempds in datasources: |
|
65 |
tempds.tags.clear() |
|
66 |
for tag in tags: |
|
67 |
if 'ds1:' in tag and tempds.name == 'ds0': |
|
68 |
continue |
|
69 |
if 'ds0:' in tag and tempds.name == 'ds1': |
|
70 |
continue |
|
71 |
tempds.tags.add(tag) |
|
72 |
|
|
73 |
|
|
74 |
datasourcespk = [ds.pk for ds in datasources] |
|
75 |
graphs = Graph.objects.filter(type=type,tags__name='lun:%s'%lun.pk,datasources__pk__in=datasourcespk).distinct() |
|
76 |
if len(graphs) == 1: |
|
77 |
graph = graphs[0] |
|
78 |
if len(graphs) < 1: |
|
79 |
graph = Graph(type=type) |
|
80 |
graph.save() |
|
81 |
graph.tags.add('lun:%s'%lun.pk) |
|
82 |
if 'description' in kwargs: |
|
83 |
graph.tags.add('graphdescription:%s %s'%(lun.name, kwargs['description'])) |
|
84 |
elif len(graphs) > 1: |
|
85 |
sys.stderr.write('Found more than one possible graph. Using oldest one as an approximation: %s\n' % e) |
|
86 |
graph = Graph.objects.filter(type=type,tags__name='lun:%s'%lun.pk,datasources__pk__in=datasourcespk).distinct().order_by('-date')[0] |
|
87 |
|
|
88 |
# Setting various attributes through kwargs |
|
89 |
for i in ('pc95','units','logarithmic','label','description'): |
|
90 |
if i in kwargs: |
|
91 |
setattr(graph,i,kwargs[i]) |
|
92 |
|
|
93 |
graph.save() |
|
94 |
|
|
95 |
for datasource in datasources: |
|
96 |
try: |
|
97 |
grds = GraphDataSource.objects.get(graph=graph,datasource=datasource) |
|
98 |
except GraphDataSource.DoesNotExist, e: |
|
99 |
grds = GraphDataSource(graph=graph,datasource=datasource) |
|
100 |
# Some defaults |
|
101 |
grds.args = '' |
|
102 |
grds.function = 'LINE1' |
|
103 |
grds.color = '#0000bbbb' |
|
104 |
# Hardcoded default. Max Datasources in a graph. |
|
105 |
for n in range(0,1024): |
|
106 |
for i in ( 'function', 'color', 'args', 'legend', 'stackorder' ): |
|
107 |
if datasource.name == 'ds%s' % n and 'ds%s%s' % ( n, i ) in kwargs: |
|
108 |
setattr(grds,i,kwargs['ds%s%s' % ( n, i)]) |
|
109 |
grds.save() |
|
110 |
|
|
111 |
try: |
|
112 |
f = open(rrdpath) |
|
113 |
except IOError: |
|
114 |
necreation_epoch = int(time.time()) |
|
115 |
f = rrdtool.create(str(rrdpath), str("--start"), str(1349965501), [str('DS:ds0:COUNTER:300:U:U'), str('DS:ds1:COUNTER:300:U:U')], str('RRA:AVERAGE:0.5:1:288'), str('RRA:AVERAGE:0.5:3:672'), str('RRA:AVERAGE:0.5:12:744'), str('RRA:AVERAGE:0.5:72:1460')) |
|
116 |
|
b/graphs/management/commands/__genrgconfig__.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- vim:encoding=utf-8: |
|
2 |
# vim: tabstop=4:shiftwidth=4:softtabstop=4:expandtab |
|
3 |
from __dbfuncs__ import * |
|
4 |
from core.models import * |
|
5 |
from django.conf import settings |
|
6 |
import rrdtool |
|
7 |
import time |
|
8 |
import datetime |
|
9 |
|
|
10 |
|
|
11 |
|
|
12 |
def diskio_genconfig(lun): |
|
13 |
devfilename = "lun_%s" % lun.pk |
|
14 |
seriesfilename = 'blocksiostats' |
|
15 |
target = "%s_%s" % (devfilename, seriesfilename) |
|
16 |
rrdpath = settings.RRDFILEDIR + '/%s.rrd' % target |
|
17 |
|
|
18 |
dbreg('diskio', rrdpath, lun, description='Disk IO' , label=target, |
|
19 |
ds0legend='Blocks Read', ds1legend='Blocks Write', |
|
20 |
ds0function='AREA', ds1function='LINE1', |
|
21 |
ds0color='#00bb00bb', ds1color='#0000bbbb', |
|
22 |
pc95=False, units='blocks', tags=['lun:%s'%lun.pk, 'ds0:blocksread', "ds1:blockswrite", "type:blocksiostats"]) |
|
23 |
|
|
24 |
def requests_genconfig(lun): |
|
25 |
devfilename = "lun_%s" % lun.pk |
|
26 |
seriesfilename = 'requeststats' |
|
27 |
target = "%s_%s" % (devfilename, seriesfilename) |
|
28 |
rrdpath = settings.RRDFILEDIR + '/%s.rrd' % target |
|
29 |
|
|
30 |
dbreg('requests', rrdpath, lun, description='Requests IO' , label=target, |
|
31 |
ds0legend='Read Requests', ds1legend='Write Requests', |
|
32 |
ds0function='AREA', ds1function='LINE1', |
|
33 |
ds0color='#00bb00bb', ds1color='#0000bbbb', |
|
34 |
pc95=False, units='reuests', tags=['lun:%s'%lun.pk, 'ds0:readrequests', "ds1:writerequests", "type:requeststats"]) |
|
35 |
|
|
36 |
|
|
37 |
|
b/graphs/management/commands/analyze.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- vim:encoding=utf-8: |
|
2 |
# vim: tabstop=4:shiftwidth=4:softtabstop=4:expandtab |
|
3 |
|
|
4 |
from django.core.management.base import BaseCommand, CommandError |
|
5 |
from django.conf import settings |
|
6 |
from core.models import * |
|
7 |
from util.analyze import analyze |
|
8 |
|
|
9 |
class Command(BaseCommand): |
|
10 |
|
|
11 |
def handle(self, *args, **options): |
|
12 |
luns = Lun.objects.all() |
|
13 |
analyze(luns) |
|
14 |
print "Done" |
|
15 |
return |
|
16 |
|
b/graphs/management/commands/clear_rrds.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- vim:encoding=utf-8: |
|
2 |
# vim: tabstop=4:shiftwidth=4:softtabstop=4:expandtab |
|
3 |
|
|
4 |
from django.core.management.base import BaseCommand, CommandError |
|
5 |
from django.conf import settings |
|
6 |
import sys |
|
7 |
import os |
|
8 |
|
|
9 |
|
|
10 |
class Command(BaseCommand): |
|
11 |
# help = "Creates the configuration for rg project. Will output mrtg, collectd and munin configuration files as well as populate accordingly the rg database." |
|
12 |
# args = "[[device]]" |
|
13 |
# label = "Domain of devices to be run for(e.g. example.com) and type of configuration run(or all if you wish everything). An optional device name can also be given to run the program for a single node. Valid types are" + ", ".join(settings.RG_CREATE_TYPES) |
|
14 |
|
|
15 |
def handle(self, *args, **options): |
|
16 |
dir = os.listdir(settings.RRDFILEDIR) |
|
17 |
for fname in dir: |
|
18 |
os.remove("%s/%s"%(settings.RRDFILEDIR,fname)) |
|
19 |
print "Removed %s"%fname |
|
20 |
print "done" |
b/graphs/management/commands/rgconf.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- vim:encoding=utf-8: |
|
2 |
# vim: tabstop=4:shiftwidth=4:softtabstop=4:expandtab |
|
3 |
|
|
4 |
from django.core.management.base import BaseCommand, CommandError |
|
5 |
from django.conf import settings |
|
6 |
from core.models import * |
|
7 |
from datetime import datetime, timedelta |
|
8 |
import sys |
|
9 |
import os |
|
10 |
from __genrgconfig__ import * |
|
11 |
from __dbfuncs__ import * |
|
12 |
|
|
13 |
class Command(BaseCommand): |
|
14 |
|
|
15 |
def handle(self, *args, **options): |
|
16 |
luns = Lun.objects.all() |
|
17 |
types = settings.LUN_GRAPH_TYPES |
|
18 |
for lun in luns: |
|
19 |
for type in types: |
|
20 |
self.dbupdate(lun, type) |
|
21 |
print 'Node: %s ...done' % lun.pk |
|
22 |
|
|
23 |
def dbupdate(self, device, type): |
|
24 |
# Ok. A couple of hacks here. Instead of a if then elif then elif.... for each type |
|
25 |
# we get first from the settings dictionnary the confdir and then the function used |
|
26 |
# for this type from the globals() dictionary |
|
27 |
try: |
|
28 |
# dir = getattr(settings,'RG_%s_CONFDIR' % type.upper()) |
|
29 |
# self.checkdir(dir) |
|
30 |
|
|
31 |
func = globals()['%s_genconfig' % type] |
|
32 |
func(device) |
|
33 |
print "Done with device" |
|
34 |
except AttributeError as e: |
|
35 |
print "%s is not going to be configured because %s" % (type, e) |
|
36 |
return |
|
37 |
|
b/graphs/models.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- vim:encoding=utf-8: |
|
2 |
# vim: tabstop=4:shiftwidth=4:softtabstop=4:expandtab |
|
3 |
|
|
4 |
from django.db import models |
|
5 |
from django.conf import settings |
|
6 |
from django.utils.encoding import iri_to_uri |
|
7 |
from django.contrib.contenttypes.models import ContentType |
|
8 |
from django.contrib.contenttypes import generic |
|
9 |
from django.core.urlresolvers import reverse |
|
10 |
from taggit.managers import TaggableManager |
|
11 |
from util.rrd2json import rrd2json |
|
12 |
import rrdtool, json |
|
13 |
from core.models import Lun |
|
14 |
|
|
15 |
from numpy import * |
|
16 |
|
|
17 |
class RrdFile(models.Model): |
|
18 |
RRDPATH = settings.RRDFILEDIR |
|
19 |
|
|
20 |
path = models.FilePathField(path=RRDPATH,match='.*\.rrd$', recursive=True,max_length=255) |
|
21 |
|
|
22 |
def __unicode__(self): |
|
23 |
return 'RRD file: %s' % (self.path) |
|
24 |
|
|
25 |
class DataSource(models.Model): |
|
26 |
name = models.CharField(max_length=30) |
|
27 |
rrdfile = models.ForeignKey(RrdFile) |
|
28 |
tags = TaggableManager() |
|
29 |
|
|
30 |
def __unicode__(self): |
|
31 |
return 'Data Source: %s, RRD file: %s' % (self.name,self.rrdfile.path) |
|
32 |
|
|
33 |
def get_plain_graphs(self): |
|
34 |
graphs = self.graph_set.filter(aggrfunction=None) |
|
35 |
return graphs |
|
36 |
|
|
37 |
class Graph(models.Model): |
|
38 |
AGGREGATE_FUNCTIONS = ( |
|
39 |
( u'+', u'Addition' ), |
|
40 |
( u'-', u'Substraction' ), |
|
41 |
( u'*', u'Multiplication' ), |
|
42 |
( u'/', u'Division' ), |
|
43 |
( u'%', u'Modulo' ), |
|
44 |
( u'ADDNAN', u'Nan-aware Addition'), |
|
45 |
) |
|
46 |
|
|
47 |
datasources = models.ManyToManyField(DataSource,through='GraphDataSource') |
|
48 |
date = models.DateTimeField(auto_now=True) |
|
49 |
type = models.CharField(max_length=35) |
|
50 |
label = models.CharField(max_length=60, null=True) |
|
51 |
units = models.CharField(max_length=30) |
|
52 |
description = models.CharField(max_length=255, null=True) |
|
53 |
pc95 = models.BooleanField(verbose_name="95th Percentile Creation",default=False) |
|
54 |
logarithmic = models.BooleanField(verbose_name="Logarithmic Y-Axis Scaling",default=False) |
|
55 |
aggrfunction = models.CharField(max_length=10, null=True, blank=True, choices=AGGREGATE_FUNCTIONS, |
|
56 |
verbose_name='Aggregate Function') |
|
57 |
aggrlabel = models.CharField(max_length=10, null=True,blank=True, verbose_name='Aggregate Function Label' ) |
|
58 |
aggrgfunction = models.CharField(max_length=10, null=True, blank=True, verbose_name='Aggregate Graphing Function') |
|
59 |
aggrcolor = models.CharField(max_length=9, blank=True) |
|
60 |
tags = TaggableManager() |
|
61 |
|
|
62 |
class Meta: |
|
63 |
ordering=('label',) |
|
64 |
|
|
65 |
def __unicode__(self): |
|
66 |
return 'Tags: %s, type: %s, label: %s, description: %s' % (self.tags.all(), self.type, |
|
67 |
self.label, self.description) |
|
68 |
|
|
69 |
def create_graph(self, path, **kwargs): |
|
70 |
BOTTOMLOGO = settings.RRD_BOTTOMLOGO |
|
71 |
|
|
72 |
start = kwargs['start'] |
|
73 |
end = kwargs['end'] |
|
74 |
lazy = kwargs['lazy'] |
|
75 |
legend = kwargs['legend'] |
|
76 |
|
|
77 |
args = list() |
|
78 |
tempgraphargs = list() |
|
79 |
|
|
80 |
args.append(path) |
|
81 |
if lazy == True: |
|
82 |
args.append('--lazy') |
|
83 |
legend = True |
|
84 |
if legend == False: |
|
85 |
args.append('--no-legend') |
|
86 |
args.append('-s %s ' % start ) |
|
87 |
args.append('-e %s ' % end ) |
|
88 |
args.append('-W %s' % ( BOTTOMLOGO )) |
|
89 |
tags = [t.name for t in self.tags.all() if 'graphdescription' in t.name] |
|
90 |
if len(tags) > 0: |
|
91 |
description = ",".join([tname.split(':')[1] for tname in tags]) |
|
92 |
args.append('-t %s' % (description[:60])) |
|
93 |
# if self.description != None: |
|
94 |
# args.append('-t %s' % (self.description[:60])) |
|
95 |
# else: |
|
96 |
# args.append('-t %s ' % self.type) |
|
97 |
|
|
98 |
args.append('-v %s' % ( self.units )) |
|
99 |
args.append('--slope-mode') |
|
100 |
if 'thumb' in kwargs: |
|
101 |
args.append('-h 160') |
|
102 |
args.append('-w 360') |
|
103 |
|
|
104 |
# args.append('-j') |
|
105 |
|
|
106 |
# # Logarithmic care |
|
107 |
# if self.logarithmic == True: |
|
108 |
# args.append('--logarithmic') |
|
109 |
# args.append('--units=si') |
|
110 |
# else: |
|
111 |
# args.append('-l %s' % ( '0' )) |
|
112 |
|
|
113 |
dsnames=list() |
|
114 |
if 'thumb' not in kwargs: |
|
115 |
args.append('-h 180') |
|
116 |
args.append('-w 470') |
|
117 |
args.append("-c") |
|
118 |
args.append("FONT#333333") |
|
119 |
args.append("-c") |
|
120 |
args.append("AXIS#333333") |
|
121 |
args.append("-c") |
|
122 |
args.append("ARROW#3D2828") |
|
123 |
args.append("-n") |
|
124 |
args.append("DEFAULT:0:Arial") |
|
125 |
graphdatasources = self.graphdatasource_set.all() |
|
126 |
legends = [grds.legend for grds in graphdatasources] |
|
127 |
if self.aggrlabel != None: |
|
128 |
legends.append(self.aggrlabel) |
|
129 |
maxl = len(max(legends, key=len)) |
|
130 |
|
|
131 |
if self.pc95: |
|
132 |
pc95cdef = 'CDEF:pc95bits=' |
|
133 |
if self.aggrfunction: |
|
134 |
aggrfcdef= 'CDEF:aggrf=' |
|
135 |
|
|
136 |
i=0 |
|
137 |
for grds in graphdatasources: |
|
138 |
name = grds.datasource.name |
|
139 |
dsnames.append(name) |
|
140 |
# Internal variable for usage with rrdtool DEFs,CDEFs. The d at the start is dummy and is |
|
141 |
# used because rrdtool seems to want its CDEFs,DEFs starting with a letter in some cases |
|
142 |
grname = 'd%s' % grds.pk |
|
143 |
filename = grds.datasource.rrdfile.path |
|
144 |
args.append('DEF:%s=%s:%s:AVERAGE' % ( grname, filename, name )) |
|
145 |
if grds.graphable: |
|
146 |
padding = ' ' * (maxl - len(grds.legend)) |
|
147 |
if grds.legend != '': |
|
148 |
tempstack = '%s:%s%s%s:%s%s\:\g' % ( grds.function, grname, grds.args, grds.color, grds.legend, padding) |
|
149 |
else: |
|
150 |
tempstack = '%s:%s%s%s:%s' % ( grds.function, grname, grds.args, grds.color, grds.legend) |
|
151 |
if grds.stackorder != 0: |
|
152 |
tempstack = tempstack + ':STACK' |
|
153 |
tempgraphargs.append(tempstack) |
|
154 |
if grds.legend != '': |
|
155 |
tempgraphargs.append('%s:%s%s:%s:%s' % ( 'GPRINT', grname, grds.args, 'MAX', '\tMax\: %4.2lf%s\g' )) |
|
156 |
tempgraphargs.append('%s:%s%s:%s:%s' % ( 'GPRINT', grname, grds.args, 'MIN', '\tMin\: %4.2lf%s\g' )) |
|
157 |
tempgraphargs.append('%s:%s%s:%s:%s' % ( 'GPRINT', grname, grds.args, 'AVERAGE', '\tAvg\: \t%4.2lf%s\g' )) |
|
158 |
tempgraphargs.append('%s:%s%s:%s:%s' % ( 'GPRINT', grname, grds.args, 'LAST', '\tLast\: %4.2lf%s\\n' )) |
|
159 |
|
|
160 |
|
|
161 |
if self.pc95: |
|
162 |
pc95cdef = pc95cdef + grname + grds.args + ',' |
|
163 |
|
|
164 |
if self.aggrfunction: |
|
165 |
aggrfcdef = '%s%s%s,' % ( aggrfcdef, grname, grds.args ) |
|
166 |
if i > 0: |
|
167 |
aggrfcdef = aggrfcdef + self.aggrfunction + ',' |
|
168 |
i = i + 1 |
|
169 |
|
|
170 |
if self.pc95: |
|
171 |
pc95cdef = pc95cdef + 'MAX' |
|
172 |
args.append(pc95cdef) |
|
173 |
args.append('VDEF:pc95=pc95bits,95,PERCENT') |
|
174 |
|
|
175 |
if not self.aggrfunction: |
|
176 |
args.extend(tempgraphargs) |
|
177 |
|
|
178 |
if self.aggrfunction: |
|
179 |
padding = ' ' * (maxl - len(self.aggrlabel)) |
|
180 |
args.append('%s:aggrf%s:%s%s\:\g' % (self.aggrgfunction, self.aggrcolor, self.aggrlabel, padding)) |
|
181 |
args.append('GPRINT:aggrf:MAX:\tMax\: %4.2lf%s\g') |
|
182 |
args.append('GPRINT:aggrf:MIN:\tMin\: %4.2lf%s\g') |
|
183 |
args.append('GPRINT:aggrf:AVERAGE:\tAvg\: %4.2lf%s\g') |
|
184 |
args.append('GPRINT:aggrf:LAST:\tLast\: %4.2lf%s\\n') |
|
185 |
|
|
186 |
if self.pc95: |
|
187 |
args.append('COMMENT: \\n') |
|
188 |
args.append('HRULE:pc95#ff0000a0:95th Percentile\::dashes') |
|
189 |
args.append('GPRINT:pc95:%4.2lf%s\\n') |
|
190 |
|
|
191 |
# This is for rrdtool which hates unicode |
|
192 |
args=[str(val) for val in args] |
|
193 |
try: |
|
194 |
return rrdtool.graphv(*args) |
|
195 |
except Exception as e: |
|
196 |
print e |
|
197 |
|
|
198 |
return None |
|
199 |
|
|
200 |
|
|
201 |
def analyze_graph(self, start="-2d", end='-10m', extended=False): |
|
202 |
graphdatasources = self.graphdatasource_set.all() |
|
203 |
for grds in graphdatasources: |
|
204 |
filename = grds.datasource.rrdfile.path |
|
205 |
graph_data = rrdtool.fetch(str('%s'%filename), str('AVERAGE'), str('-s'), str("%s"%start), str('-e'), str('%s'%end)) |
|
206 |
json_ret = [] |
|
207 |
tojsondict = rrd2json(graph_data) |
|
208 |
graphdss = self.datasources.all() |
|
209 |
if tojsondict: |
|
210 |
for k,v in tojsondict.items(): |
|
211 |
tojson = {} |
|
212 |
na = array(map(lambda x:x[1], v)) |
|
213 |
tojson['label']= ":".join([t.name for t in graphdss.get(name=k).tags.all()]) |
|
214 |
if extended: |
|
215 |
tojson['data'] = v |
|
216 |
tojson['graph'] = self.pk |
|
217 |
tojson['lunpk'] = Lun.objects.get(pk=int(self.tags.get(name__contains='lun').name.replace('lun:',''))).pk |
|
218 |
#tojson['ds'] = graphdss.get(name=k).tags.get(name__startswith='ds').name |
|
219 |
#tojson['avg'] = reduce(lambda x, y: x+y, map(lambda x:x[1], v)) / len(v) |
|
220 |
tojson['avg'] = na.mean() |
|
221 |
tojson['dev'] = na.std() |
|
222 |
json_ret.append(tojson) |
|
223 |
jret = [sorted(json_ret, key=lambda k: k['dev'], reverse=True)[0]] |
|
224 |
return jret |
|
225 |
|
|
226 |
|
|
227 |
@models.permalink |
|
228 |
def get_absolute_url(self): |
|
229 |
return ('core.views.drawpng', [str(self.id)]) |
|
230 |
|
|
231 |
@models.permalink |
|
232 |
def get_draw_url(self): |
|
233 |
return ('core.views.drawpng', [str(self.id)]) |
|
234 |
|
|
235 |
@models.permalink |
|
236 |
def get_thumb_url(self): |
|
237 |
return ('core.views.thumb', [str(self.id)]) |
|
238 |
|
|
239 |
@models.permalink |
|
240 |
def get_hour_url(self): |
|
241 |
return ('core.views.drawpng', [str(self.id),'-1h','-300']) |
|
242 |
|
|
243 |
@models.permalink |
|
244 |
def get_day_url(self): |
|
245 |
return ('core.views.drawpng', [str(self.id),'-2d','-30m']) |
|
246 |
|
|
247 |
@models.permalink |
|
248 |
def get_week_url(self): |
|
249 |
return ('core.views.drawpng', [str(self.id),'-1w','-30m']) |
|
250 |
|
|
251 |
@models.permalink |
|
252 |
def get_month_url(self): |
|
253 |
return ('core.views.drawpng', [str(self.id),'-1m','-30m']) |
|
254 |
|
|
255 |
@models.permalink |
|
256 |
def get_year_url(self): |
|
257 |
return ('core.views.drawpng', [str(self.id),'-1y','-30m']) |
|
258 |
|
|
259 |
|
|
260 |
|
|
261 |
class GraphDataSource(models.Model): |
|
262 |
GRAPHFUNCTIONS = ( |
|
263 |
( u'PRINT', u'PRINT' ), |
|
264 |
( u'GPRINT', u'GPRINT' ), |
|
265 |
( u'VRULE', u'VRULE' ), |
|
266 |
( u'HRULE', u'HRULE' ), |
|
267 |
( u'LINE', u'LINE' ), |
|
268 |
( u'AREA', u'AREA' ), |
|
269 |
( u'TICK', u'TICK' ), |
|
270 |
( u'SHIFT', u'SHIFT' ), |
|
271 |
( u'PRINT', u'PRINT' ), |
|
272 |
( u'', u'' ), |
|
273 |
) |
|
274 |
|
|
275 |
graph = models.ForeignKey(Graph) |
|
276 |
datasource = models.ForeignKey(DataSource) |
|
277 |
graphable = models.BooleanField(default=True) |
|
278 |
legend = models.CharField(max_length=35, null=True, blank=True) |
|
279 |
function = models.CharField(max_length=5, null=True, choices=GRAPHFUNCTIONS ) |
|
280 |
color = models.CharField(max_length=9, blank=True) |
|
281 |
args = models.CharField(max_length=255, null=True) |
|
282 |
stackorder = models.PositiveIntegerField(default=0) |
|
283 |
|
|
284 |
class Meta: |
|
285 |
ordering = ('stackorder',) |
|
286 |
|
|
287 |
def __unicode__(self): |
|
288 |
return 'Graph: %s, %s, Graphing Variables: %s:%s%s:%s' % (self.graph.__unicode__(), self.datasource.__unicode__(), self.function, self.args, self.color, self.legend) |
|
289 |
|
|
290 |
|
|
291 |
class GraphLabel(models.Model): |
|
292 |
graph = models.ManyToManyField(Graph) |
|
293 |
key = models.CharField(max_length=30,db_index=True) |
|
294 |
data = models.CharField(max_length=255) |
|
295 |
|
|
296 |
def __unicode__(self): |
|
297 |
return 'Key: %s, data: %s' % (self.key,self.data) |
|
298 |
|
b/graphs/tests.py | ||
---|---|---|
1 |
""" |
|
2 |
This file demonstrates two different styles of tests (one doctest and one |
|
3 |
unittest). These will both pass when you run "manage.py test". |
|
4 |
|
|
5 |
Replace these with more appropriate tests for your application. |
|
6 |
""" |
|
7 |
|
|
8 |
from django.test import TestCase |
|
9 |
|
|
10 |
class SimpleTest(TestCase): |
|
11 |
def test_basic_addition(self): |
|
12 |
""" |
|
13 |
Tests that 1 + 1 always equals 2. |
|
14 |
""" |
|
15 |
self.failUnlessEqual(1 + 1, 2) |
|
16 |
|
|
17 |
__test__ = {"doctest": """ |
|
18 |
Another way to test that 1 + 1 is equal to 2. |
|
19 |
|
|
20 |
>>> 1 + 1 == 2 |
|
21 |
True |
|
22 |
"""} |
|
23 |
|
b/graphs/views.py | ||
---|---|---|
1 |
# Create your views here. |
b/manage.py | ||
---|---|---|
1 |
#!/usr/bin/python |
|
2 |
from django.core.management import execute_manager |
|
3 |
try: |
|
4 |
import settings # Assumed to be in the same directory. |
|
5 |
except ImportError: |
|
6 |
import sys |
|
7 |
sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__) |
|
8 |
sys.exit(1) |
|
9 |
|
|
10 |
if __name__ == "__main__": |
|
11 |
execute_manager(settings) |
b/settings.py.dist | ||
---|---|---|
1 |
# Django settings for lunstats project. |
|
2 |
|
|
3 |
DEBUG = True |
|
4 |
TEMPLATE_DEBUG = DEBUG |
|
5 |
|
|
6 |
import os |
|
7 |
here = lambda x: os.path.join(os.path.abspath(os.path.dirname(__file__)), x) |
|
8 |
|
|
9 |
ADMINS = ( |
|
10 |
# ('Your Name', 'your_email@domain.com'), |
|
11 |
) |
|
12 |
|
|
13 |
MANAGERS = ADMINS |
|
14 |
|
|
15 |
DATABASES = { |
|
16 |
'default': { |
|
17 |
'ENGINE': 'django.db.backends.', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. |
|
18 |
'NAME': '' # Or path to database file if using sqlite3. |
|
19 |
'USER': '', # Not used with sqlite3. |
|
20 |
'PASSWORD': '', # Not used with sqlite3. |
|
21 |
'HOST': '', # Set to empty string for localhost. Not used with sqlite3. |
|
22 |
'PORT': '', # Set to empty string for default. Not used with sqlite3. |
|
23 |
} |
|
24 |
} |
|
25 |
|
|
26 |
# Hosts/domain names that are valid for this site; required if DEBUG is False |
|
27 |
# See https://docs.djangoproject.com/en/{{ docs_version }}/ref/settings/#allowed-hosts |
|
28 |
ALLOWED_HOSTS = [] |
|
29 |
|
|
30 |
# Local time zone for this installation. Choices can be found here: |
|
31 |
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name |
|
32 |
# although not all choices may be available on all operating systems. |
|
33 |
# On Unix systems, a value of None will cause Django to use the same |
|
34 |
# timezone as the operating system. |
|
35 |
# If running in a Windows environment this must be set to the same as your |
|
36 |
# system time zone. |
|
37 |
TIME_ZONE = 'Europe/Athens' |
|
38 |
|
|
39 |
# Language code for this installation. All choices can be found here: |
|
40 |
# http://www.i18nguy.com/unicode/language-identifiers.html |
|
41 |
LANGUAGE_CODE = 'en-us' |
|
42 |
|
|
43 |
SITE_ID = 1 |
|
44 |
|
|
45 |
# If you set this to False, Django will make some optimizations so as not |
|
46 |
# to load the internationalization machinery. |
|
47 |
USE_I18N = True |
|
48 |
|
|
49 |
# If you set this to False, Django will not format dates, numbers and |
|
50 |
# calendars according to the current locale |
|
51 |
USE_L10N = True |
|
52 |
|
|
53 |
# Absolute path to the directory that holds media. |
|
54 |
# Example: "/home/media/media.lawrence.com/" |
|
55 |
MEDIA_ROOT = '' |
|
56 |
|
|
57 |
# URL that handles the media served from MEDIA_ROOT. Make sure to use a |
|
58 |
# trailing slash if there is a path component (optional in other cases). |
|
59 |
# Examples: "http://media.lawrence.com", "http://example.com/media/" |
|
60 |
MEDIA_URL = '' |
|
61 |
|
|
62 |
STATIC_URL = '' |
|
63 |
|
|
64 |
# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a |
|
65 |
# trailing slash. |
|
66 |
# Examples: "http://foo.com/media/", "/media/". |
|
67 |
ADMIN_MEDIA_PREFIX = '/media/' |
|
68 |
|
|
69 |
# Make this unique, and don't share it with anybody. |
|
70 |
SECRET_KEY = '#8(lzp_r(u4r6(+_&do44v-)8zrnl=272tob+#vvd378s8%kit' |
|
71 |
|
|
72 |
# List of callables that know how to import templates from various sources. |
|
73 |
TEMPLATE_LOADERS = ( |
|
74 |
'django.template.loaders.filesystem.Loader', |
|
75 |
'django.template.loaders.app_directories.Loader', |
|
76 |
# 'django.template.loaders.eggs.Loader', |
|
77 |
) |
|
78 |
|
|
79 |
MIDDLEWARE_CLASSES = ( |
|
80 |
'django.middleware.common.CommonMiddleware', |
|
81 |
'django.contrib.sessions.middleware.SessionMiddleware', |
|
82 |
'django.middleware.csrf.CsrfViewMiddleware', |
|
83 |
'django.contrib.auth.middleware.AuthenticationMiddleware', |
|
84 |
'django.contrib.messages.middleware.MessageMiddleware', |
|
85 |
) |
|
86 |
|
|
87 |
ROOT_URLCONF = 'lunstats.urls' |
|
88 |
|
|
89 |
TEMPLATE_DIRS = ( |
|
90 |
here('templates'), |
|
91 |
# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". |
|
92 |
# Always use forward slashes, even on Windows. |
|
93 |
# Don't forget to use absolute paths, not relative paths. |
|
94 |
) |
|
95 |
|
|
96 |
INSTALLED_APPS = ( |
|
97 |
'django.contrib.auth', |
|
98 |
'django.contrib.contenttypes', |
|
99 |
'django.contrib.sessions', |
|
100 |
'django.contrib.sites', |
|
101 |
'django.contrib.messages', |
|
102 |
'core', |
|
103 |
'graphs', |
|
104 |
'taggit', |
|
105 |
'django_extensions', |
|
106 |
'south', |
|
107 |
# Uncomment the next line to enable the admin: |
|
108 |
'django.contrib.admin', |
|
109 |
# Uncomment the next line to enable admin documentation: |
|
110 |
# 'django.contrib.admindocs', |
|
111 |
) |
|
112 |
|
|
113 |
CACHE_BACKEND = 'memcached://127.0.0.1:11211/?timeout=50000' |
|
114 |
|
|
115 |
|
|
116 |
RRDFILEDIR = here('rrds') |
|
117 |
RRD_PNG_STATICPATH = here('rrd_pngs') |
|
118 |
RRD_BOTTOMLOGO = u'' |
|
119 |
|
|
120 |
LUN_COUNTERS = ( |
|
121 |
( u'diskio', u'Disk IO' ), |
|
122 |
( u'requests', u'Requests' ), |
|
123 |
) |
|
124 |
|
|
125 |
LUN_COUNTER_SERIES = { |
|
126 |
'diskio':['blocksread', 'blockswrite'], |
|
127 |
'requests':['writereq', 'readreq'] |
|
128 |
} |
|
129 |
|
|
130 |
LUN_GRAPH_TYPES = [type[0] for type in LUN_COUNTERS] |
b/static/css/isotope.css | ||
---|---|---|
1 |
.isotope, |
|
2 |
.isotope .isotope-item { |
|
3 |
/* change duration value to whatever you like */ |
|
4 |
-webkit-transition-duration: 0.8s; |
|
5 |
-moz-transition-duration: 0.8s; |
|
6 |
-ms-transition-duration: 0.8s; |
|
7 |
-o-transition-duration: 0.8s; |
|
8 |
transition-duration: 0.8s; |
|
9 |
} |
|
10 |
|
|
11 |
.isotope { |
|
12 |
-webkit-transition-property: height, width; |
|
13 |
-moz-transition-property: height, width; |
|
14 |
-ms-transition-property: height, width; |
|
15 |
-o-transition-property: height, width; |
Also available in: Unified diff