Welcome, guest | Sign In | My Account | Store | Cart

This tool (inspired by azat@stackoverflow, see http://stackoverflow.com/a/16082562/1281485) allows to watch the progress of the file descriptors of another process. This can be used, for example, if you transfer a file to another host and the transferring program does not show any progress indication itself. Instead of waiting blindly until the routine is done, with this tool you can use Linux's proc file system to monitor the progress of the other process while it walks through the file.

The tool continuously monitors the position in and the size of the files the given process's file descriptors point to. For growing (or shrinking, but that's very unusual) files, a time when it was (or will be) empty is computed ("emptyTime"), for moving file descriptors (the typical case), the time when it started at position 0 ("startTime"), the time when it will reach the current size of the file ("reachTime") and when it will meet with the end of a growing (or shrinking) file is computed ("meetTime").

For fixed-size files the meetTime will be the same as the reachTime of course. The meetTime only makes sense in case a file is growing and at the same time read (e. g. when a movie is downloaded to a file by one process and converted by a different process; using this tool can tell you when the converter process might run dry on the input because the download wasn't fast enough, and in this case you maybe can pause the converter to prevent this situation).

The tool is designed as a library; the display of the information is independent from the gathering of the data. Please feel free to create more fancy displays, add percentage output etc.

Python, 138 lines
  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
#!/usr/bin/env python
#
# fdprogress.py -- by Alfe (alfe@alfe.de), inspired by azat@Stackoverflow
#
# usage:  fdprogress.py <pid>
#

import time, os, os.path

from collections import defaultdict

def getFds(pid):
  return os.listdir('/proc/%s/fd/' % pid)

def getPos(pid, fd):
  with open('/proc/%s/fdinfo/%s' % (pid, fd)) as f:
    return int(f.readline()[5:])

def getSize(pid, fd):
  return os.path.getsize(getPath(pid, fd))

class FdIsPipe(Exception): pass

def getPath(pid, fd):
  result = os.readlink('/proc/%s/fd/%s' % (pid, fd))
  if result.startswith('pipe:['):
    raise FdIsPipe(result)
  return result

def extendHistory(history, pid):
  for fd in getFds(pid):
    try:
      history[fd, getPath(pid, fd)].append(
        (time.time(), getPos(pid, fd), getSize(pid, fd)))
    except FdIsPipe:
      pass  # ignore fds to pipe

def initHistory(pid):
  result = defaultdict(list)
  extendHistory(result, pid)
  return result

def reduceHistory(history):
  for key, value in history.iteritems():
    if len(value) > 2:
      del value[1:-2]  # only keep first and last
      # (this can be more clever in the future)

def entryPrediction(fd, path, values):
  t1, pos1, size1 = values[0]
  t2, pos2, size2 = values[-1]
  if t1 == t2:  # no time passed yet?
    return fd, path, (t2, pos2, size2), None, None, None, None, None, None, None
  growth = (size2 - size1) / (t2 - t1)  # bytes/sec growth of file
  if growth != 0:
    tSize0 = t1 - size1 / growth  # time when size was 0
  else:
    tSize0 = None
  speed = (pos2 - pos1) / (t2 - t1)  # speed of pos in bytes/sec
  if speed != 0:
    tPos0 = t1 - pos1 / speed  # time when pos was 0
    tPosSize2 = t1 + (size2 - pos1) / speed  # time of pos reaching size2
  else:
    tPos0 = tPosSize2 = None
  if speed != growth:  # when will both meet?
    tm = t2 + (size2 - pos2) / (speed - growth)
    sizeM = size2 + growth * (tm - t2)
  else:
    tm = sizeM = None
  return (fd, path, (t2, pos2, size2), growth, speed, tSize0, tPos0,
          tPosSize2, tm, sizeM)

def eachPrediction(history):
  for (fd, path), values in history.iteritems():
    yield entryPrediction(fd, path, values)

def displayTime(t):
  if t is None:
    return "<>"
  d = t - time.time()
  try:
    lt = time.localtime(t)
  except:
    return "??"
  return (
    time.strftime("%%F (now%+dy)" % (d/86400/365), lt)
    if abs(d) > 2 * 86400 * 365 else
    time.strftime("%%F (now%+dM)" % (d/86400/30), lt)
    if abs(d) > 2 * 86400 * 30 else
    time.strftime("%%F (now%+dd)" % (d/86400), lt)
    if abs(d) > 2 * 86400 else
    time.strftime("%%a, %%T (now%+dh)" % (d/3600), lt)
    if time.strftime('%F', lt) != time.strftime('%F', time.localtime()) else
    time.strftime("%%T (now%+dh)" % (d/3600), lt)
    if abs(d) > 2 * 3600 else
    time.strftime("%%T (now%+dm)" % (d/60), lt)
    if abs(d) > 2 * 60 else
    time.strftime("%%T (now%+ds)" % d, lt))

def displaySize(size):
  return (
    "<>" if size is None else
    "%d B" % size
    if size < 1e3 else
    "%.2f kB" % (size / 1e3)
    if size < 1e6 else
    "%.2f MB" % (size / 1e6)
    if size < 1e9 else
    "%.2f GB" % (size / 1e9))

def displaySpeed(speed):
  return displaySize(speed) + "/s"

def printPrediction(history):
  for (fd, path, (t2, pos2, size2), growth, speed, tSize0, tPos0,
       tPosSize2, tm, sizeM) in eachPrediction(history):
    print '\n', fd, "->", os.path.basename(path)
    dT = displayTime
    dSi = displaySize
    dSp = displaySpeed
    print "size:", dSi(size2), "\tgrowth:", dSp(growth), \
          "\t\tpos:", dSi(pos2), "\tspeed:", dSp(speed)
    print "emptyTime:", dT(tSize0), "\tstartTime:", dT(tPos0), \
          "\treachTime:", dT(tPosSize2), "\tmeetTime:", dT(tm)

def main(argv):
  pid = argv[1]
  history = initHistory(pid)
  while True:
    os.system('clear')
    printPrediction(history)
    extendHistory(history, pid)
    reduceHistory(history)
    time.sleep(1.0)

if __name__ == '__main__':
  import sys
  sys.exit(main(sys.argv))