-
Notifications
You must be signed in to change notification settings - Fork 2
/
cleanbuilds.py
executable file
·146 lines (126 loc) · 4.87 KB
/
cleanbuilds.py
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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
#! /usr/bin/env python
#
# Copyright (c) 2014 Citrix Systems, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
import os, sys
from pprint import pprint
from time import asctime, localtime, time
class SubprocessFailure(Exception):
pass
def check_output(args):
"""Nasty check_output implementation; do not wish to assume Python 2.6+"""
cmd = ' '.join(args)
fobj = os.popen(cmd, 'r')
output = fobj.read()
code = fobj.close()
if code != None:
raise SubprocessFailure(cmd, code)
return output
ROOTS = [] # add list of directories to clean here
def make_agelist(root, depth):
"""Return a dictionary where keys are build creation times
and values are lists of build directories, for each build
we can find"""
agelist = {}
for directory, dirnames, filenames in os.walk(root):
spl = directory[len(root):].split('/')
if len(spl) != depth or os.path.split(directory)[1].startswith('.'):
continue
dstat = os.stat(directory)
agelist.setdefault(dstat.st_mtime, list())
agelist[dstat.st_mtime].append(directory)
del dirnames[:]
return agelist
def make_branches(agelist):
"""Given an agelist (see make_agelist's result),
return (number of builds, branches dict) where
branches dict is a dictionary mapping branch directories
to a a dictionary mapping creation times to list of builds"""
branches = {}
for age in sorted(agelist.keys()):
for directory in agelist[age]:
base, item = os.path.split(directory)
branches.setdefault(base, dict())
branches[base][age] = item
return branches
def get_free(root):
return float(check_output(['df', root]).split()[-3]) / 1e6
def get_bname(branch):
return branch.split('/')[-1]
def run(*args):
print '+' + ' '.join(args)
if '-a' in sys.argv:
check_output(args)
def main():
"""Figure out what builds to delete"""
byage = {}
latest = {}
for root, depth, offload, space in ROOTS:
if not os.path.isdir(root):
continue
if get_free(root) > space:
continue
branches = make_branches(make_agelist(root, depth))
for branch in branches:
bname = get_bname(branch)
ages = sorted(branches[branch])
for age in ages:
byage.setdefault(age, list())
byage[age].append(( branch+'/'+branches[branch][age],
root, depth, offload, space))
if ages:
latest.setdefault(bname, (0, None))
if latest[bname][0] < ages[-1]:
latest[bname] = (ages[-1],
branch+'/'+branches[branch][ages[-1]])
killed = 0
for age in sorted(byage.keys()):
for dname, root, depth, offload, space in byage[age]:
if get_free(root) > space:
continue
segs = dname.split('/')
branch = get_bname(dname)
print '# build',asctime(localtime(age)), dname
if branch in latest:
_, last = latest[branch]
if last == dname:
print '# keep', last, 'since it the latest', branch, 'build'
continue
else:
print '# newer', last, 'so can kill', dname
if killed > 5000:
print '# killed plenty for now'
else:
print '# only', int(get_free(dname)), 'GB free on', dname
if offload:
segs = dname.split('/')
name = segs[-2]
dest = offload+'/'+name
run('rsync', '-rap', dname, dest)
run('rm', '-rf', dname)
killed += 1
# remove empty branch directories
for root, depth, _, _ in ROOTS:
if not os.path.isdir(root):
continue
for directory, dirnames, filenames in os.walk(root):
here = len(directory[len(root):].split('/'))
if here == depth -1 and len(dirnames) == 0 and len(filenames) == 0:
run('rmdir', directory)
if here >= depth -1:
del dirnames[:]
main()