CMS 3D CMS Logo

 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Pages
fff_deletion.py
Go to the documentation of this file.
1 import os
2 import logging
3 import re
4 import datetime
5 import subprocess
6 import socket
7 import time
8 
9 logging.basicConfig(level=logging.INFO)
10 log = logging
11 
12 re_files = re.compile(r"^run(?P<run>\d+)/run(?P<runf>\d+)_ls(?P<ls>\d+)_.+\.(dat|raw)+(\.deleted)*")
14  m = re_files.match(rl)
15  if not m:
16  return None
17 
18  d = m.groupdict()
19  sort_key = (int(d["run"]), int(d["runf"]), int(d["ls"]), )
20  return sort_key
21 
22 def iterate(top, stopSize, action):
23  # entry format (path, size)
24  collected = []
25 
26  for root, dirs, files in os.walk(top, topdown=True):
27  for name in files:
28  fp = os.path.join(root, name)
29  rl = os.path.relpath(fp, top)
30 
31  sort_key = parse_file_name(rl)
32  if sort_key:
33  fsize = os.stat(fp).st_size
34  if fsize == 0:
35  continue
36 
37  sort_key = parse_file_name(rl)
38  collected.append((sort_key, fp, fsize, ))
39 
40  # for now just use simple sort
41  collected.sort(key=lambda x: x[0])
42 
43  # do the action
44  for sort_key, fp, fsize in collected:
45  if stopSize <= 0:
46  break
47 
48  action(fp)
49  stopSize = stopSize - fsize
50 
51 def cleanup_threshold(top, threshold, action, string):
52  st = os.statvfs(top)
53  total = st.f_blocks * st.f_frsize
54  used = total - (st.f_bavail * st.f_frsize)
55  threshold = used - float(total * threshold) / 100
56 
57  def p(x):
58  return float(x) * 100 / total
59 
60  log.info("Using %d (%.02f%%) of %d space, %d (%.02f%%) above %s threshold.",
61  used, p(used), total, threshold, p(threshold), string)
62 
63  if threshold > 0:
64  iterate(top, threshold, action)
65  log.info("Done cleaning up for %s threshold.", string)
66  else:
67  log.info("Threshold %s not reached, doing nothing.", string)
68 
69 def diskusage(top):
70  st = os.statvfs(top)
71  total = st.f_blocks * st.f_frsize
72  used = total - (st.f_bavail * st.f_frsize)
73  return float(used) * 100 / total
74 
76  def __init__(self, top, thresholds, email_to, fake=True, ):
77  self.top = top
78  self.fake = fake
79  self.email_to = email_to
80  self.thresholds = thresholds
81 
82  self.last_email = None
83  self.min_interval = datetime.timedelta(seconds=60*10)
84  self.hostname = socket.gethostname()
85 
86  def rename(self, f):
87  if f.endswith(".deleted"):
88  return
89 
90  fn = f + ".deleted"
91 
92  if self.fake:
93  log.warning("Renaming file (fake): %s -> %s", f,
94  os.path.relpath(fn, os.path.dirname(f)))
95  else:
96  log.warning("Renaming file: %s -> %s", f,
97  os.path.relpath(fn, os.path.dirname(f)))
98 
99  os.rename(f, fn)
100 
101  def delete(self, f):
102  if not f.endswith(".deleted"):
103  return
104 
105  if self.fake:
106  log.warning("Truncating file (fake): %s", f)
107  else:
108  log.warning("Truncating file: %s", f)
109  open(f, "w").close()
110 
111  def send_smg(self, used_pc):
112  now = datetime.datetime.now()
113 
114  if (self.last_email is not None):
115  if (now - self.last_email) < self.min_interval:
116  return
117 
118  self.last_email = now
119 
120  # sms service does not accept an email with a several recipients
121  # so we send one-by-one
122  for email in self.email_to:
123  subject = "Disk out of space (%.02f%%) on %s." % (used_pc, self.hostname)
124  if "mail2sms" in email:
125  text = ""
126  else:
127  text = subject
128 
129  log.info("Sending email: %s", repr(["/bin/mail", "-s", subject, email]))
130  p = subprocess.Popen(["/bin/mail", "-s", subject, email], stdin=subprocess.PIPE, shell=False)
131  p.communicate(input=text)
132 
133  def run(self):
134  cleanup_threshold(self.top, self.thresholds['rename'], self.rename, "rename")
135  cleanup_threshold(self.top, self.thresholds['delete'], self.delete, "delete")
136 
137  du = diskusage(self.top)
138  if du > self.thresholds['email']:
139  deleter.send_smg(du)
140 
141 # use a named socket check if we are running
142 # this is very clean and atomic and leave no files
143 # from: http://stackoverflow.com/a/7758075
144 def lock(pname):
145  sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
146  try:
147  sock.bind('\0' + pname)
148  return sock
149  except socket.error:
150  return None
151 
152 def daemon(deleter, delay_seconds=30):
153  while True:
154  deleter.run()
155  time.sleep(delay_seconds)
156 
157 import sys
158 if __name__ == "__main__":
159  #import argparse
160  #parser = argparse.ArgumentParser(description="Delete files if disk space usage reaches critical level.")
161  #parser.add_argument("-r", "--renameT", type=float, help="Percentage of total disk space used for file renaming.")
162  #parser.add_argument("-d", "--deleteT", type=float, help="Percentage of total disk space used for file deletion.")
163  #parser.add_argument("-t", "--top", type=str, help="Top level directory.", default="/fff/ramdisk/")
164  #args = parser.parse_args()
165 
166  # try to take the lock or quit
167  sock = lock("fff_deleter")
168  if sock is None:
169  log.info("Already running, exitting.")
170  sys.exit(0)
171 
172  # threshold rename and delete must be in order
173  # in other words, always: delete > rename
174  # this is because delete only deletes renamed files
175 
176  # email threshold has no restrictions
177  top = "/fff.changeme/ramdisk"
178  thresholds = {
179  'delete': 80,
180  'rename': 60,
181  'email': 90,
182  }
183  fake = not (len(sys.argv) > 1 and sys.argv[1] == "doit")
184 
185  deleter = FileDeleter(
186  top = top,
187  thresholds = thresholds,
188  # put "41XXXXXXXXX@mail2sms.cern.ch" to send the sms
189  email_to = ["dmitrijus.bugelskis@cern.ch", ],
190  fake = fake,
191  )
192 
193  daemon(deleter=deleter)
def cleanup_threshold
Definition: fff_deletion.py:51
list object
Definition: dbtoconf.py:77
def parse_file_name
Definition: fff_deletion.py:13