src/rosclean/__init__.py
Go to the documentation of this file.
1 # Software License Agreement (BSD License)
2 #
3 # Copyright (c) 2010, Willow Garage, Inc.
4 # All rights reserved.
5 #
6 # Redistribution and use in source and binary forms, with or without
7 # modification, are permitted provided that the following conditions
8 # are met:
9 #
10 # * Redistributions of source code must retain the above copyright
11 # notice, this list of conditions and the following disclaimer.
12 # * Redistributions in binary form must reproduce the above
13 # copyright notice, this list of conditions and the following
14 # disclaimer in the documentation and/or other materials provided
15 # with the distribution.
16 # * Neither the name of Willow Garage, Inc. nor the names of its
17 # contributors may be used to endorse or promote products derived
18 # from this software without specific prior written permission.
19 #
20 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 # POSSIBILITY OF SUCH DAMAGE.
32 
33 from __future__ import print_function
34 
35 __version__ = '1.7.0'
36 
37 from distutils.spawn import find_executable
38 import argparse
39 import os
40 import sys
41 import platform
42 import subprocess
43 
44 import rospkg
45 
46 class CleanupException(Exception): pass
47 
48 def _ask_and_call(cmds, cwd=None):
49  """
50  Pretty print cmds, ask if they should be run, and if so, runs
51  them using _call().
52 
53  :param cmds: a list of commands executed one after another, ``list``
54  :param cwd: (optional) set cwd of command that is executed, ``str``
55  :returns: ``True`` if cmds were run.
56  """
57  # Pretty-print a string version of the commands
58  def quote(s):
59  return '"%s"'%s if ' ' in s else s
60  accepted = _ask('\n'.join([' '.join([quote(s) for s in c]) for c in cmds]))
61  if accepted:
62  _call(cmds, cwd)
63  return accepted
64 
65 def _ask(comment):
66  """
67  ask user with provided comment. If user responds with y, return True
68 
69  :param comment: comment, ``str``
70  :return: ``True`` if user responds with y
71  """
72  sys.stdout.write("Okay to perform:\n\n%s\n(y/n)?\n"%comment)
73  while 1:
74  input = sys.stdin.readline().strip().lower()
75  if input in ['y', 'n']:
76  break
77  return input == 'y'
78 
79 def _call(cmds, cwd=None):
80  """
81  Runs cmds using subprocess.check_call.
82 
83  :param cmds: a list of commands executed one after another, ``list``
84  :param cwd: (optional) set cwd of command that is executed, ``str``
85  """
86  for c in cmds:
87  if cwd:
88  subprocess.check_call(c, cwd=cwd)
89  else:
90  subprocess.check_call(c)
91 
92 def _usage():
93  print("""Usage: rosclean <command>
94 
95 Commands:
96 \trosclean check\tCheck usage of log files
97 \trosclean purge\tRemove log files
98 """)
99  sys.exit(getattr(os, 'EX_USAGE', 1))
100 
102  home_dir = rospkg.get_ros_home()
103  log_dir = rospkg.get_log_dir()
104  dirs = [ (log_dir, 'ROS node logs'),
105  (os.path.join(home_dir, 'rosmake'), 'rosmake logs')]
106  return [x for x in dirs if os.path.isdir(x[0])]
107 
109  dirs = _get_check_dirs()
110  for d, label in dirs:
112  print("%s %s"%(desc, label))
113 
115  total_size = 0
116  for dirpath, dirnames, filenames in os.walk(d):
117  for f in filenames:
118  fp = os.path.join(dirpath, f)
119  total_size += os.path.getsize(fp)
120  return total_size
121 
123  """
124  Get human-readable disk usage for directory
125 
126  :param d: directory path, ``str`
127  :returns: human-readable disk usage (du -h), ``str``
128  """
129  # only implemented on Linux and FreeBSD for now. Should work on OS X but need to verify first (du is not identical)
130  if platform.system() in ['Linux', 'FreeBSD']:
131  try:
132  return subprocess.Popen(['du', '-sh', d], stdout=subprocess.PIPE).communicate()[0].split()[0]
133  except:
134  raise CleanupException("rosclean is not supported on this platform")
135  elif platform.system() == 'Windows':
136  total_size = _get_disk_usage_by_walking_tree(d)
137  return "Total Size: " + str(total_size) + " " + d
138  else:
139  raise CleanupException("rosclean is not supported on this platform")
140 
142  """
143  Get disk usage in bytes for directory
144  :param d: directory path, ``str``
145  :returns: disk usage in bytes (du -b) or (du -A) * 1024, ``int``
146  :raises: :exc:`CleanupException` If get_disk_usage() cannot be used on this platform
147  """
148  if platform.system() == 'Windows':
150 
151  # only implemented on Linux and FreeBSD for now. Should work on OS X but need to verify first (du is not identical)
152  cmd = None
153  unit = 1
154  du = find_executable('du')
155  if du is not None:
156  if platform.system() == 'Linux':
157  cmd = [du, '-sb', d]
158  elif platform.system() == 'FreeBSD':
159  cmd = [du, '-skA', d]
160  unit = 1024
161  try:
162  # detect BusyBox du command by following symlink
163  if os.path.basename(os.readlink(du)) == 'busybox':
164  cmd = [du, '-sk', d]
165  unit = 1024
166  except OSError:
167  # readlink raises OSError if the target is not symlink
168  pass
169 
170  if cmd is None:
171  raise CleanupException("rosclean is not supported on this platform")
172  try:
173  return int(subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()[0].split()[0]) * unit
174  except:
175  raise CleanupException("rosclean is not supported on this platform")
176 
178  """
179  Get files and directories in specified path sorted by last modified time
180  :param d: directory path, ```str```
181  :return: a list of files and directories sorted by last modified time (old first), ```list```
182  """
183  files = os.listdir(d)
184  files.sort(key=lambda f: os.path.getmtime(os.path.join(d, f)))
185  return files
186 
188  dirs = _get_check_dirs()
189 
190  for d, label in dirs:
191  if not args.size:
192  print("Purging %s."%label)
193  cmds = [['rm', '-rf', d]]
194  try:
195  if args.y:
196  _call(cmds)
197  else:
198  print("PLEASE BE CAREFUL TO VERIFY THE COMMAND BELOW!")
199  _ask_and_call(cmds)
200  except:
201  print("FAILED to execute command", file=sys.stderr)
202  else:
203  files = _sort_file_by_oldest(d)
204  log_size = get_disk_usage(d)
205  if log_size <= args.size * 1024 * 1024:
206  print("Directory size of %s is %d MB which is already below the requested threshold of %d MB."%(label, log_size / 1024 / 1024, args.size))
207  continue
208  print("Purging %s until directory size is at most %d MB (currently %d MB)."%(label, args.size, log_size / 1024 / 1024))
209  if not args.y:
210  print("PLEASE BE CAREFUL TO VERIFY THE COMMAND BELOW!")
211  if not _ask("Purge some of old logs in %s"%d):
212  return
213  for f in files:
214  if log_size <= args.size * 1024 * 1024:
215  break
216  path = os.path.join(d, f)
217  log_size -= get_disk_usage(path)
218  if platform.system() == 'Windows':
219  cmds = [['rd', '/s', '/q', path]]
220  else:
221  cmds = [['rm', '-rf', path]]
222  try:
223  _call(cmds)
224  except:
225  print("FAILED to execute command", file=sys.stderr)
226 
227 def rosclean_main(argv=None):
228  if argv is None:
229  argv = sys.argv
230  parser = argparse.ArgumentParser(prog='rosclean')
231  subparsers = parser.add_subparsers()#help='sub-command help')
232  parser_check = subparsers.add_parser('check', help='Check usage of log files')
233  parser_check.set_defaults(func=_rosclean_cmd_check)
234  parser_purge = subparsers.add_parser('purge', help='Remove log files')
235  parser_purge.set_defaults(func=_rosclean_cmd_purge)
236  parser_purge.add_argument('-y', action='store_true', default=False, help='CAUTION: automatically confirms all questions to delete files')
237  parser_purge.add_argument('--size', action='store', default=None, type=int, help='Maximum total size in MB to keep when deleting old files')
238  args = parser.parse_args(argv[1:])
239  args.func(args)
240 
241 if __name__ == '__main__':
242  rosclean_main()
def rosclean_main(argv=None)
def _ask(comment)
def _rosclean_cmd_check(args)
def _sort_file_by_oldest(d)
def _get_disk_usage_by_walking_tree(d)
def get_human_readable_disk_usage(d)
def _call(cmds, cwd=None)
def _ask_and_call(cmds, cwd=None)
def _rosclean_cmd_purge(args)


rosclean
Author(s): Ken Conley
autogenerated on Tue Apr 2 2019 02:14:14