Created
February 5, 2014 20:36
-
-
Save scr512/8832480 to your computer and use it in GitHub Desktop.
duTree.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!python | |
# | |
# Disk space usage displayed as a tree - duTree.py | |
# | |
# Run program without arguments to see its usage. | |
import string, sys, os, getopt | |
from os.path import * | |
boolStrings = ['off', 'on'] | |
class Options: | |
'Holds program options as object attributes' | |
def __init__(self): | |
# When adding a new option, initialize it here. | |
self.cumPercent = 80 | |
self.topNOption = 'n' | |
self.maxDepth = 2 | |
self.showFiles = False | |
self.indentSize = 4 | |
self.followLinks = False | |
self.topN = 5 | |
self.percent = 10 | |
self.units = 'k' | |
def dump(self): | |
print 'Options:' | |
print 'TopN option = %s' % (self.topNOption) | |
if self.topNOption == 'c': | |
print 'Cumulative percentage = %d' % (self.cumPercent) | |
elif self.topNOption == 'p': | |
print 'Percentage = %d' % (self.percent) | |
else: | |
if self.topN > 0: | |
print 'TopN = %d' % (self.topN) | |
else: | |
print 'TopN = all' | |
if self.maxDepth >= 0: | |
print 'Max depth = %d' % (self.maxDepth) | |
else: | |
print 'Max depth = any' | |
print 'Show files = %s' % (boolStrings[self.showFiles]) | |
print 'Indent size = %d' % (self.indentSize) | |
print 'Follow links = %s' % (boolStrings[self.followLinks]) | |
print 'Units = %s' % (self.units) | |
def getIndentStr(depth, isDir, options): | |
s = '' | |
for i in range(depth): | |
if isDir and i == depth - 1: | |
firstChar = '+' | |
otherChars = '-' | |
else: | |
firstChar = '|' | |
otherChars = ' ' | |
s += firstChar + (otherChars * (options.indentSize - 1)) | |
return s | |
def printPath (path, bytes, pct, isDir, depth, options): | |
indentStr = getIndentStr(depth, isDir, options) | |
if path: | |
if options.units == 'k': | |
print '%s%-11.1f %3.0f%% %s' % (indentStr, bytes / 1000.0, pct, path) | |
elif options.units == 'm': | |
print '%s%-7.1f %3.0f%% %s' % (indentStr, bytes / 1000000.0, pct, path) | |
else: | |
print '%s%-12ld %3.0f%% %s' % (indentStr, bytes, pct, path) | |
else: | |
print indentStr | |
def isDir (item): | |
# Directories have 3 entries (size, path, list of contents) while files | |
# have 2 (size, path). | |
return len(item) == 3 | |
def printDir (path, dsize, pct, items, depth, options): | |
# Print entire tree starting with given directory | |
printPath(path, dsize, pct, True, depth, options) | |
count = 0 | |
cumBytes = 0 | |
dir = True | |
for item in items: | |
size = item[0] | |
path = item[1] | |
dir = isDir(item) | |
if dsize > 0: | |
pct = size * 100.0 / dsize | |
else: | |
pct = 0.0 | |
if dir: | |
dirContents = item[2] | |
printDir(path, size, pct, dirContents, depth+1, options) | |
else: | |
printPath(path, size, pct, False, depth+1, options) | |
count += 1 | |
cumBytes += size | |
# Add blank line if the last entry shown is a file | |
### if not dir: | |
### printPath('', 0, 0, False, depth, options) | |
def dirSize (dirPath, depth, options): | |
# For given directory, returns the list [size, [entry-1, entry-2, ...]] | |
total = 0L | |
try: | |
dirList = os.listdir (dirPath) | |
except: | |
if isdir (dirPath): | |
print 'Cannot list directory %s' % dirPath | |
return 0 | |
itemList = [] | |
for item in dirList: | |
path = '%s/%s' % (dirPath, item) | |
try: | |
stats = os.stat (path) | |
except: | |
print 'Cannot stat %s' % path | |
continue | |
size = stats[6] | |
if isdir (path) and (options.followLinks or \ | |
(not options.followLinks and not islink (path))): | |
dsize, items = dirSize (path, depth + 1, options) | |
size += dsize | |
if (options.maxDepth == -1 or depth < options.maxDepth): | |
itemList.append([size, item, items]) | |
elif options.showFiles: | |
if (options.maxDepth == -1 or depth < options.maxDepth): | |
itemList.append([size, item]) | |
total += size | |
# Sort in descending order | |
itemList.sort() | |
itemList.reverse() | |
# Keep only the items that will be displayed | |
cumBytes = 0 | |
i = 0 | |
for i, v in enumerate(itemList): | |
size = v[0] | |
path = v[1] | |
showItem = True | |
if options.topNOption == 'p': | |
showItem = (size * 100.0 / total) >= options.percent | |
if showItem: | |
if options.topNOption == 'n': | |
if options.topN and (i + 1) == options.topN: | |
break | |
elif options.topNOption == 'c': | |
cumBytes += size | |
if (cumBytes * 100.0 / total) >= options.cumPercent: | |
break | |
else: | |
break | |
if options.topNOption != 'p': | |
# Need to keep the current item | |
i += 1 | |
if i < len(itemList): | |
itemList[i:] = [] | |
return [total, itemList] | |
def usage (name): | |
options = Options() | |
print ''' | |
usage: %s [-c percent|-n top-n|-p percent] [-d depth] [-f on|off] [-i indent-size] [-l on|off] [-u b|k|m] dir [dir...]' | |
-c Cumulative percent contribution (default = %d) | |
-d Max depth of directories. '-d any' => no limit. (default = %d) | |
-f Show files (default = %s) | |
-i Indent size (default = %d) | |
-l Follow symbolic links (Unix only. default = %s) | |
-n The N in top-N. '-n all' => show all. (default = %d) | |
-p Percent contribution of each directory/file (defalut = %d) | |
-u Units to display size (default = %s) | |
b Bytes | |
k Kilobytes. k = 1000 | |
m Megabytes. m = 1,000,000 | |
Only one of -c, -n and -p can be specified. This controls how | |
many entries are shown for each directory. The default is -%s. | |
With -n, only the top N entries are shown. | |
With -c, the top entries that together contribute the given | |
percentage of a directory's size are shown. | |
With -p, all entries that contribute the given percentage or | |
greater of a directory's size are shown. | |
''' % (name, options.cumPercent, options.maxDepth, | |
boolStrings[options.showFiles], options.indentSize, | |
boolStrings[options.followLinks], options.topN, | |
options.percent, options.units, options.topNOption) | |
# Main program | |
if __name__ == '__main__': | |
try: | |
opts, args = getopt.getopt (sys.argv[1:], "c:d:f:i:l:n:p:u:") | |
except getopt.GetoptError: | |
usage (sys.argv[0], options) | |
sys.exit (1) | |
options = Options() | |
count = 0 | |
errmsg = '' | |
for o, a in opts: | |
if o == '-c': | |
try: | |
options.cumPercent = string.atoi (a) | |
except: | |
errmsg = 'Invalid value for cumulative percentage' | |
else: | |
if options.cumPercent == 0 or options.cumPercent > 100: | |
errmsg = 'Cumulative percentage must be between 1 and 100' | |
else: | |
++count | |
options.topNOption = 'c' | |
elif o == '-d': | |
if a == 'any': | |
options.maxDepth = -1 | |
else: | |
try: | |
options.maxDepth = string.atoi (a) | |
except: | |
errmsg = 'Invalid value for depth' | |
else: | |
if options.maxDepth < 0 and options.maxDepth != -1: | |
errmsg = 'Max depth must be >= 0 or -1' | |
elif o == '-f': | |
if a == 'on': | |
options.showFiles = True | |
elif a == 'off': | |
options.showFiles = False | |
else: | |
errmsg = 'Invalid value for -f' | |
elif o == '-i': | |
try: | |
options.indentSize = string.atoi (a) | |
except: | |
errmsg = 'Invalid value for indent size' | |
else: | |
if options.indentSize < 2: | |
errmsg = 'Indent size must be at least 2' | |
elif o == '-l': | |
if a == 'on': | |
options.followLinks = True | |
elif a == 'off': | |
options.followLinks = False | |
else: | |
errmsg = 'Invalid value for -l' | |
elif o == '-n': | |
if a == 'all': | |
options.topN = 0 | |
options.topNOption = 'n' | |
else: | |
try: | |
options.topN = string.atoi (a) | |
except: | |
errmsg = 'Invalid value for top-N' | |
else: | |
if options.topN > 0: | |
++count | |
options.topNOption = 'n' | |
else: | |
errmsg = 'Top-N value must be > 0' | |
elif o == '-p': | |
try: | |
options.percent = string.atoi (a) | |
except: | |
errmsg = 'Invalid value for percentage' | |
else: | |
if options.percent == 0 or options.percent > 100: | |
errmsg = 'Percentage must be between 1 and 100' | |
else: | |
++count | |
options.topNOption = 'p' | |
elif o == '-u': | |
units = a | |
if units == 'b' or units == 'k' or units == 'm': | |
options.units = units | |
else: | |
errmsg = 'Invalid value for units' | |
if errmsg: | |
print errmsg | |
usage (sys.argv[0]) | |
sys.exit(1) | |
if count > 1: | |
print 'The -c, -n and -p options are mutually exclusive' | |
sys.exit(1) | |
if len (args) < 1: | |
usage (sys.argv[0]) | |
sys.exit (1) | |
else: | |
paths = args | |
options.dump() | |
for path in paths: | |
if isdir (path): | |
dsize, items = dirSize (path, 0, options) | |
printDir (path, dsize, 100.0, items, 0, options) | |
else: | |
print 'Error:', path, 'is not a directory' | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment