1 | #!/usr/bin/env python |
---|
2 | |
---|
3 | # copy .rrd files from a remote munin master host, sum the 'df' stats from a |
---|
4 | # list of hosts, use them to estimate a rate-of-change for the past month, |
---|
5 | # then extrapolate to guess how many weeks/months/years of storage space we |
---|
6 | # have left, and output it to another munin graph |
---|
7 | |
---|
8 | |
---|
9 | import sys, os, time |
---|
10 | import rrdtool |
---|
11 | |
---|
12 | MUNIN_HOST = "munin.allmydata.com" |
---|
13 | PREFIX = "%s:/var/lib/munin/prodtahoe/" % MUNIN_HOST |
---|
14 | FILES = [ "prodtahoe%d.allmydata.com-df-_dev_sd%s3-g.rrd" % (a,b) |
---|
15 | for a in (1,2,3,4,5) |
---|
16 | for b in ("a", "b", "c", "d") |
---|
17 | ] |
---|
18 | REMOTEFILES = [ PREFIX + f for f in FILES ] |
---|
19 | LOCALFILES = ["/var/lib/munin/prodtahoe/" + f for f in FILES ] |
---|
20 | WEBFILE = "/var/www/tahoe/spacetime.json" |
---|
21 | |
---|
22 | |
---|
23 | def rsync_rrd(): |
---|
24 | # copy the RRD files from your munin master host to a local one |
---|
25 | cmd = "rsync %s rrds/" % (" ".join(REMOTEFILES)) |
---|
26 | rc = os.system(cmd) |
---|
27 | assert rc == 0, rc |
---|
28 | |
---|
29 | def format_time(t): |
---|
30 | return time.strftime("%b %d %H:%M", time.localtime(t)) |
---|
31 | |
---|
32 | def predict_future(past_s): |
---|
33 | |
---|
34 | start_df = [] |
---|
35 | end_df = [] |
---|
36 | durations = [] |
---|
37 | |
---|
38 | for fn in LOCALFILES: |
---|
39 | d = rrdtool.fetch(fn, "AVERAGE", "-s", "-"+past_s, "-e", "-1hr") |
---|
40 | # ((start, end, step), (name1, name2, ...), [(data1, data2, ..), ...]) |
---|
41 | (start_time, end_time ,step) = d[0] |
---|
42 | #print format_time(start_time), " - ", format_time(end_time), step |
---|
43 | #for points in d[2]: |
---|
44 | # point = points[0] |
---|
45 | # print point |
---|
46 | start_space = d[2][0][0] |
---|
47 | if start_space is None: |
---|
48 | return None |
---|
49 | # I don't know why, but the last few points are always bogus. Running |
---|
50 | # 'rrdtool fetch' on the command line is usually ok.. I blame the python |
---|
51 | # bindinds. |
---|
52 | end_space = d[2][-4][0] |
---|
53 | if end_space is None: |
---|
54 | return None |
---|
55 | end_time = end_time - (4*step) |
---|
56 | start_df.append(start_space) |
---|
57 | end_df.append(end_space) |
---|
58 | durations.append(end_time - start_time) |
---|
59 | |
---|
60 | avg_start_df = sum(start_df) / len(start_df) |
---|
61 | avg_end_df = sum(end_df) / len(end_df) |
---|
62 | avg_duration = sum(durations) / len(durations) |
---|
63 | #print avg_start_df, avg_end_df, avg_duration |
---|
64 | |
---|
65 | rate = (avg_end_df - avg_start_df) / avg_duration |
---|
66 | #print "Rate", rate, " %/s" |
---|
67 | #print "measured over", avg_duration / 86400, "days" |
---|
68 | remaining = 100 - avg_end_df |
---|
69 | remaining_seconds = remaining / rate |
---|
70 | #print "remaining seconds", remaining_seconds |
---|
71 | remaining_days = remaining_seconds / 86400 |
---|
72 | #print "remaining days", remaining_days |
---|
73 | return remaining_days |
---|
74 | |
---|
75 | def write_to_file(samples): |
---|
76 | # write a JSON-formatted dictionary |
---|
77 | f = open(WEBFILE + ".tmp", "w") |
---|
78 | f.write("{ ") |
---|
79 | f.write(", ".join(['"%s": %s' % (k, samples[k]) |
---|
80 | for k in sorted(samples.keys())])) |
---|
81 | f.write("}\n") |
---|
82 | f.close() |
---|
83 | os.rename(WEBFILE + ".tmp", WEBFILE) |
---|
84 | |
---|
85 | if len(sys.argv) > 1 and sys.argv[1] == "config": |
---|
86 | print("""\ |
---|
87 | graph_title Tahoe Remaining Space Predictor |
---|
88 | graph_vlabel days remaining |
---|
89 | graph_category tahoe |
---|
90 | graph_info This graph shows the estimated number of days left until storage space is exhausted |
---|
91 | days_2wk.label days left (2wk sample) |
---|
92 | days_2wk.draw LINE2 |
---|
93 | days_4wk.label days left (4wk sample) |
---|
94 | days_4wk.draw LINE2""") |
---|
95 | sys.exit(0) |
---|
96 | |
---|
97 | #rsync_rrd() |
---|
98 | samples = {} |
---|
99 | remaining_4wk = predict_future("4wk") |
---|
100 | if remaining_4wk is not None: |
---|
101 | print("days_4wk.value", remaining_4wk) |
---|
102 | samples["remaining_4wk"] = remaining_4wk |
---|
103 | remaining_2wk = predict_future("2wk") |
---|
104 | if remaining_2wk is not None: |
---|
105 | print("days_2wk.value", remaining_2wk) |
---|
106 | samples["remaining_2wk"] = remaining_2wk |
---|
107 | write_to_file(samples) |
---|