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
|
# Copyright (C) 2018-2019 Arthur de Jong
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.
import cgi
import json
import os
import sys
import time
from munin import get_info, get_resolutions, get_values
sys.stdout = sys.stderr
def static_serve(environ, start_response):
path = environ.get('PATH_INFO', '').lstrip('/') or 'index.html'
path = os.path.normpath(os.sep + path).lstrip(os.sep)
content_type = 'text/html'
if path.endswith('.js'):
content_type = 'text/javascript'
elif path.endswith('.css'):
content_type = 'text/css'
csp = "default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; " + \
"script-src 'self' 'unsafe-eval'; frame-ancestors 'none'"
start_response('200 OK', [
('Content-Type', content_type),
('Content-Security-Policy', csp)])
return [open(os.path.join('static', path), 'rb').read()]
def list_graphs(environ, start_response):
start_response('200 OK', [
('Content-Type', 'application/json')])
return [json.dumps(get_info(), indent=2, sort_keys=True).encode('utf-8')]
def _field_key(x):
"""Order field.min, field, field.max."""
if x.endswith('.min'):
return x[:-4] + '.0'
elif x.endswith('.max'):
return x[:-4] + '.2'
return x + '.1'
def _parse_timestamp(timestamp):
"""Return a timestamp value from the specified string."""
formats = (
'%Y-%m-%d %H:%M:%S',
'%Y-%m-%d %H:%M',
'%Y-%m-%d')
for fmt in formats:
try:
return time.mktime(time.strptime(timestamp, fmt))
except ValueError:
pass
raise ValueError('time data %r does not match any known format' % timestamp)
def get_data(environ, start_response):
path = environ.get('PATH_INFO', '').lstrip('/')
_, group, host, graph = path.split('/')
last_update, resolutions = get_resolutions(group, host, graph)
parameters = cgi.parse_qs(environ.get('QUERY_STRING', ''))
# get the time range to fetch the data for
end = parameters.get('end')
end = _parse_timestamp(end[0]) if end else last_update
start = parameters.get('start')
start = _parse_timestamp(start[0]) if start else end - 24 * 60 * 60 * 7
# calculate the minimum resolution that we want
resolution = min((
parameters.get('resolution', (end - start) / 5000),
resolutions[-1][0]))
# loop over resolutions to find the data
values = []
for res, rows in resolutions:
if res >= resolution:
s = max((last_update - res * rows, start))
e = min((last_update, end))
if e > s:
values = get_values(group, host, graph, s, e, res) + values
end = s
# return the values as CSV
start_response('200 OK', [
('Content-Type', 'text/plain')])
if values:
keys = (x for x in values[0].keys() if x != 'remove')
keys = ['time'] + sorted((k for k in keys if k != 'time'), key=_field_key)
yield ('%s\n' % (','.join(keys))).encode('utf-8')
for value in values:
value['time'] = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(value['time']))
yield ('%s\n' % (','.join(str(value.get(key, '')) for key in keys))).encode('utf-8')
def application(environ, start_response):
# get request path
path = environ.get('PATH_INFO', '').lstrip('/')
if path.startswith('graphs'):
return list_graphs(environ, start_response)
elif path.startswith('data/'):
return get_data(environ, start_response)
else:
return static_serve(environ, start_response)
if __name__ == '__main__':
from wsgiref.simple_server import make_server
srv = make_server('0.0.0.0', 8080, application)
srv.serve_forever()
|