| 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) |
|---|