smem 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690
  1. #!/usr/bin/env python
  2. #
  3. # smem - a tool for meaningful memory reporting
  4. #
  5. # Copyright 2008-2009 Matt Mackall <mpm@selenic.com>
  6. #
  7. # This software may be used and distributed according to the terms of
  8. # the GNU General Public License version 2 or later, incorporated
  9. # herein by reference.
  10. import re, os, sys, pwd, optparse, errno, tarfile
  11. warned = False
  12. class procdata(object):
  13. def __init__(self, source):
  14. self._ucache = {}
  15. self._gcache = {}
  16. self.source = source and source or ""
  17. self._memdata = None
  18. def _list(self):
  19. return os.listdir(self.source + "/proc")
  20. def _read(self, f):
  21. return file(self.source + '/proc/' + f).read()
  22. def _readlines(self, f):
  23. return self._read(f).splitlines(True)
  24. def _stat(self, f):
  25. return os.stat(self.source + "/proc/" + f)
  26. def pids(self):
  27. '''get a list of processes'''
  28. return [int(e) for e in self._list()
  29. if e.isdigit() and not iskernel(e)]
  30. def mapdata(self, pid):
  31. return self._readlines('%s/smaps' % pid)
  32. def memdata(self):
  33. if self._memdata is None:
  34. self._memdata = self._readlines('meminfo')
  35. return self._memdata
  36. def version(self):
  37. return self._readlines('version')[0]
  38. def pidname(self, pid):
  39. try:
  40. l = self._read('%d/stat' % pid)
  41. return l[l.find('(') + 1: l.find(')')]
  42. except:
  43. return '?'
  44. def pidcmd(self, pid):
  45. try:
  46. c = self._read('%s/cmdline' % pid)[:-1]
  47. return c.replace('\0', ' ')
  48. except:
  49. return '?'
  50. def piduser(self, pid):
  51. try:
  52. return self._stat('%d' % pid).st_uid
  53. except:
  54. return -1
  55. def pidgroup(self, pid):
  56. try:
  57. return self._stat('%d' % pid).st_gid
  58. except:
  59. return -1
  60. def username(self, uid):
  61. if uid == -1:
  62. return '?'
  63. if uid not in self._ucache:
  64. try:
  65. self._ucache[uid] = pwd.getpwuid(uid)[0]
  66. except KeyError:
  67. self._ucache[uid] = str(uid)
  68. return self._ucache[uid]
  69. def groupname(self, gid):
  70. if gid == -1:
  71. return '?'
  72. if gid not in self._gcache:
  73. try:
  74. self._gcache[gid] = pwd.getgrgid(gid)[0]
  75. except KeyError:
  76. self._gcache[gid] = str(gid)
  77. return self._gcache[gid]
  78. class tardata(procdata):
  79. def __init__(self, source):
  80. procdata.__init__(self, source)
  81. self.tar = tarfile.open(source)
  82. def _list(self):
  83. for ti in self.tar:
  84. if ti.name.endswith('/smaps'):
  85. d,f = ti.name.split('/')
  86. yield d
  87. def _read(self, f):
  88. return self.tar.extractfile(f).read()
  89. def _readlines(self, f):
  90. return self.tar.extractfile(f).readlines()
  91. def piduser(self, p):
  92. t = self.tar.getmember("%d" % p)
  93. if t.uname:
  94. self._ucache[t.uid] = t.uname
  95. return t.uid
  96. def pidgroup(self, p):
  97. t = self.tar.getmember("%d" % p)
  98. if t.gname:
  99. self._gcache[t.gid] = t.gname
  100. return t.gid
  101. def username(self, u):
  102. return self._ucache.get(u, str(u))
  103. def groupname(self, g):
  104. return self._gcache.get(g, str(g))
  105. _totalmem = 0
  106. def totalmem():
  107. global _totalmem
  108. if not _totalmem:
  109. if options.realmem:
  110. _totalmem = fromunits(options.realmem) / 1024
  111. else:
  112. _totalmem = memory()['memtotal']
  113. return _totalmem
  114. _kernelsize = 0
  115. def kernelsize():
  116. global _kernelsize
  117. if not _kernelsize and options.kernel:
  118. try:
  119. d = os.popen("size %s" % options.kernel).readlines()[1]
  120. _kernelsize = int(d.split()[3]) / 1024
  121. except:
  122. try:
  123. # try some heuristic to find gzipped part in kernel image
  124. packedkernel = open(options.kernel).read()
  125. pos = packedkernel.find('\x1F\x8B')
  126. if pos >= 0 and pos < 25000:
  127. sys.stderr.write("Maybe uncompressed kernel can be extracted by the command:\n"
  128. " dd if=%s bs=1 skip=%d | gzip -d >%s.unpacked\n\n" % (options.kernel, pos, options.kernel))
  129. except:
  130. pass
  131. sys.stderr.write("Parameter '%s' should be an original uncompressed compiled kernel file.\n\n" % options.kernel)
  132. return _kernelsize
  133. def pidmaps(pid):
  134. global warned
  135. maps = {}
  136. start = None
  137. seen = False
  138. empty = True
  139. for l in src.mapdata(pid):
  140. empty = False
  141. f = l.split()
  142. if f[-1] == 'kB':
  143. if f[0].startswith('Pss'):
  144. seen = True
  145. maps[start][f[0][:-1].lower()] = int(f[1])
  146. elif '-' in f[0] and ':' not in f[0]: # looks like a mapping range
  147. start, end = f[0].split('-')
  148. start = int(start, 16)
  149. name = "<anonymous>"
  150. if len(f) > 5:
  151. name = f[5]
  152. maps[start] = dict(end=int(end, 16), mode=f[1],
  153. offset=int(f[2], 16),
  154. device=f[3], inode=f[4], name=name)
  155. if not empty and not seen and not warned:
  156. sys.stderr.write('warning: kernel does not appear to support PSS measurement\n')
  157. warned = True
  158. if not options.sort:
  159. options.sort = 'rss'
  160. if options.mapfilter:
  161. f = {}
  162. for m in maps:
  163. if not filter(options.mapfilter, m, lambda x: maps[x]['name']):
  164. f[m] = maps[m]
  165. return f
  166. return maps
  167. def sortmaps(totals, key):
  168. l = []
  169. for pid in totals:
  170. l.append((totals[pid][key], pid))
  171. l.sort()
  172. return [pid for pid,key in l]
  173. def iskernel(pid):
  174. return src.pidcmd(pid) == ""
  175. def memory():
  176. t = {}
  177. f = re.compile('(\\S+):\\s+(\\d+) kB')
  178. for l in src.memdata():
  179. m = f.match(l)
  180. if m:
  181. t[m.group(1).lower()] = int(m.group(2))
  182. return t
  183. def units(x):
  184. s = ''
  185. if x == 0:
  186. return '0'
  187. for s in ('', 'K', 'M', 'G', 'T'):
  188. if x < 1024:
  189. break
  190. x /= 1024.0
  191. return "%.1f%s" % (x, s)
  192. def fromunits(x):
  193. s = dict(k=2**10, K=2**10, kB=2**10, KB=2**10,
  194. M=2**20, MB=2**20, G=2**30, GB=2**30,
  195. T=2**40, TB=2**40)
  196. for k,v in s.items():
  197. if x.endswith(k):
  198. return int(float(x[:-len(k)])*v)
  199. sys.stderr.write("Memory size should be written with units, for example 1024M\n")
  200. sys.exit(-1)
  201. def pidusername(pid):
  202. return src.username(src.piduser(pid))
  203. def showamount(a, total):
  204. if options.abbreviate:
  205. return units(a * 1024)
  206. elif options.percent:
  207. if total == 0:
  208. return 'N/A'
  209. return "%.2f%%" % (100.0 * a / total)
  210. return a
  211. def filter(opt, arg, *sources):
  212. if not opt:
  213. return False
  214. for f in sources:
  215. if re.search(opt, f(arg)):
  216. return False
  217. return True
  218. def pidtotals(pid):
  219. maps = pidmaps(pid)
  220. t = dict(size=0, rss=0, pss=0, shared_clean=0, shared_dirty=0,
  221. private_clean=0, private_dirty=0, referenced=0, swap=0)
  222. for m in maps.iterkeys():
  223. for k in t:
  224. t[k] += maps[m].get(k, 0)
  225. t['uss'] = t['private_clean'] + t['private_dirty']
  226. t['maps'] = len(maps)
  227. return t
  228. def processtotals(pids):
  229. totals = {}
  230. for pid in pids:
  231. if (filter(options.processfilter, pid, src.pidname, src.pidcmd) or
  232. filter(options.userfilter, pid, pidusername)):
  233. continue
  234. try:
  235. p = pidtotals(pid)
  236. if p['maps'] != 0:
  237. totals[pid] = p
  238. except:
  239. continue
  240. return totals
  241. def showpids():
  242. p = src.pids()
  243. pt = processtotals(p)
  244. def showuser(p):
  245. if options.numeric:
  246. return src.piduser(p)
  247. return pidusername(p)
  248. fields = dict(
  249. pid=('PID', lambda n: n, '% 5s', lambda x: len(pt),
  250. 'process ID'),
  251. user=('User', showuser, '%-8s', lambda x: len(dict.fromkeys(x)),
  252. 'owner of process'),
  253. name=('Name', src.pidname, '%-24.24s', None,
  254. 'name of process'),
  255. command=('Command', src.pidcmd, '%-27.27s', None,
  256. 'process command line'),
  257. maps=('Maps',lambda n: pt[n]['maps'], '% 5s', sum,
  258. 'total number of mappings'),
  259. swap=('Swap',lambda n: pt[n]['swap'], '% 8a', sum,
  260. 'amount of swap space consumed (ignoring sharing)'),
  261. uss=('USS', lambda n: pt[n]['uss'], '% 8a', sum,
  262. 'unique set size'),
  263. rss=('RSS', lambda n: pt[n]['rss'], '% 8a', sum,
  264. 'resident set size (ignoring sharing)'),
  265. pss=('PSS', lambda n: pt[n]['pss'], '% 8a', sum,
  266. 'proportional set size (including sharing)'),
  267. vss=('VSS', lambda n: pt[n]['size'], '% 8a', sum,
  268. 'virtual set size (total virtual memory mapped)'),
  269. )
  270. columns = options.columns or 'pid user command swap uss pss rss'
  271. showtable(pt.keys(), fields, columns.split(), options.sort or 'pss')
  272. def maptotals(pids):
  273. totals = {}
  274. for pid in pids:
  275. if (filter(options.processfilter, pid, src.pidname, src.pidcmd) or
  276. filter(options.userfilter, pid, pidusername)):
  277. continue
  278. try:
  279. maps = pidmaps(pid)
  280. seen = {}
  281. for m in maps.iterkeys():
  282. name = maps[m]['name']
  283. if name not in totals:
  284. t = dict(size=0, rss=0, pss=0, shared_clean=0,
  285. shared_dirty=0, private_clean=0, count=0,
  286. private_dirty=0, referenced=0, swap=0, pids=0)
  287. else:
  288. t = totals[name]
  289. for k in t:
  290. t[k] += maps[m].get(k, 0)
  291. t['count'] += 1
  292. if name not in seen:
  293. t['pids'] += 1
  294. seen[name] = 1
  295. totals[name] = t
  296. except EnvironmentError:
  297. continue
  298. return totals
  299. def showmaps():
  300. p = src.pids()
  301. pt = maptotals(p)
  302. fields = dict(
  303. map=('Map', lambda n: n, '%-40.40s', len,
  304. 'mapping name'),
  305. count=('Count', lambda n: pt[n]['count'], '% 5s', sum,
  306. 'number of mappings found'),
  307. pids=('PIDs', lambda n: pt[n]['pids'], '% 5s', sum,
  308. 'number of PIDs using mapping'),
  309. swap=('Swap',lambda n: pt[n]['swap'], '% 8a', sum,
  310. 'amount of swap space consumed (ignoring sharing)'),
  311. uss=('USS', lambda n: pt[n]['private_clean']
  312. + pt[n]['private_dirty'], '% 8a', sum,
  313. 'unique set size'),
  314. rss=('RSS', lambda n: pt[n]['rss'], '% 8a', sum,
  315. 'resident set size (ignoring sharing)'),
  316. pss=('PSS', lambda n: pt[n]['pss'], '% 8a', sum,
  317. 'proportional set size (including sharing)'),
  318. vss=('VSS', lambda n: pt[n]['size'], '% 8a', sum,
  319. 'virtual set size (total virtual address space mapped)'),
  320. avgpss=('AVGPSS', lambda n: int(1.0 * pt[n]['pss']/pt[n]['pids']),
  321. '% 8a', sum,
  322. 'average PSS per PID'),
  323. avguss=('AVGUSS', lambda n: int(1.0 * pt[n]['uss']/pt[n]['pids']),
  324. '% 8a', sum,
  325. 'average USS per PID'),
  326. avgrss=('AVGRSS', lambda n: int(1.0 * pt[n]['rss']/pt[n]['pids']),
  327. '% 8a', sum,
  328. 'average RSS per PID'),
  329. )
  330. columns = options.columns or 'map pids avgpss pss'
  331. showtable(pt.keys(), fields, columns.split(), options.sort or 'pss')
  332. def usertotals(pids):
  333. totals = {}
  334. for pid in pids:
  335. if (filter(options.processfilter, pid, src.pidname, src.pidcmd) or
  336. filter(options.userfilter, pid, pidusername)):
  337. continue
  338. try:
  339. maps = pidmaps(pid)
  340. if len(maps) == 0:
  341. continue
  342. except EnvironmentError:
  343. continue
  344. user = src.piduser(pid)
  345. if user not in totals:
  346. t = dict(size=0, rss=0, pss=0, shared_clean=0,
  347. shared_dirty=0, private_clean=0, count=0,
  348. private_dirty=0, referenced=0, swap=0)
  349. else:
  350. t = totals[user]
  351. for m in maps.iterkeys():
  352. for k in t:
  353. t[k] += maps[m].get(k, 0)
  354. t['count'] += 1
  355. totals[user] = t
  356. return totals
  357. def showusers():
  358. p = src.pids()
  359. pt = usertotals(p)
  360. def showuser(u):
  361. if options.numeric:
  362. return u
  363. return src.username(u)
  364. fields = dict(
  365. user=('User', showuser, '%-8s', None,
  366. 'user name or ID'),
  367. count=('Count', lambda n: pt[n]['count'], '% 5s', sum,
  368. 'number of processes'),
  369. swap=('Swap',lambda n: pt[n]['swap'], '% 8a', sum,
  370. 'amount of swapspace consumed (ignoring sharing)'),
  371. uss=('USS', lambda n: pt[n]['private_clean']
  372. + pt[n]['private_dirty'], '% 8a', sum,
  373. 'unique set size'),
  374. rss=('RSS', lambda n: pt[n]['rss'], '% 8a', sum,
  375. 'resident set size (ignoring sharing)'),
  376. pss=('PSS', lambda n: pt[n]['pss'], '% 8a', sum,
  377. 'proportional set size (including sharing)'),
  378. vss=('VSS', lambda n: pt[n]['pss'], '% 8a', sum,
  379. 'virtual set size (total virtual memory mapped)'),
  380. )
  381. columns = options.columns or 'user count swap uss pss rss'
  382. showtable(pt.keys(), fields, columns.split(), options.sort or 'pss')
  383. def showsystem():
  384. t = totalmem()
  385. ki = kernelsize()
  386. m = memory()
  387. mt = m['memtotal']
  388. f = m['memfree']
  389. # total amount used by hardware
  390. fh = max(t - mt - ki, 0)
  391. # total amount mapped into userspace (ie mapped an unmapped pages)
  392. u = m['anonpages'] + m['mapped']
  393. # total amount allocated by kernel not for userspace
  394. kd = mt - f - u
  395. # total amount in kernel caches
  396. kdc = m['buffers'] + m['sreclaimable'] + (m['cached'] - m['mapped'])
  397. l = [("firmware/hardware", fh, 0),
  398. ("kernel image", ki, 0),
  399. ("kernel dynamic memory", kd, kdc),
  400. ("userspace memory", u, m['mapped']),
  401. ("free memory", f, f)]
  402. fields = dict(
  403. order=('Order', lambda n: n, '% 1s', lambda x: '',
  404. 'hierarchical order'),
  405. area=('Area', lambda n: l[n][0], '%-24s', lambda x: '',
  406. 'memory area'),
  407. used=('Used', lambda n: l[n][1], '%10a', sum,
  408. 'area in use'),
  409. cache=('Cache', lambda n: l[n][2], '%10a', sum,
  410. 'area used as reclaimable cache'),
  411. noncache=('Noncache', lambda n: l[n][1] - l[n][2], '%10a', sum,
  412. 'area not reclaimable'))
  413. columns = options.columns or 'area used cache noncache'
  414. showtable(range(len(l)), fields, columns.split(), options.sort or 'order')
  415. def showfields(fields, f):
  416. if f != list:
  417. print "unknown field", f
  418. print "known fields:"
  419. for l in sorted(fields.keys()):
  420. print "%-8s %s" % (l, fields[l][-1])
  421. def showtable(rows, fields, columns, sort):
  422. header = ""
  423. format = ""
  424. formatter = []
  425. if sort not in fields:
  426. showfields(fields, sort)
  427. sys.exit(-1)
  428. if options.pie:
  429. columns.append(options.pie)
  430. if options.bar:
  431. columns.append(options.bar)
  432. mt = totalmem()
  433. st = memory()['swaptotal']
  434. for n in columns:
  435. if n not in fields:
  436. showfields(fields, n)
  437. sys.exit(-1)
  438. f = fields[n][2]
  439. if 'a' in f:
  440. if n == 'swap':
  441. formatter.append(lambda x: showamount(x, st))
  442. else:
  443. formatter.append(lambda x: showamount(x, mt))
  444. f = f.replace('a', 's')
  445. else:
  446. formatter.append(lambda x: x)
  447. format += f + " "
  448. header += f % fields[n][0] + " "
  449. l = []
  450. for n in rows:
  451. r = [fields[c][1](n) for c in columns]
  452. l.append((fields[sort][1](n), r))
  453. l.sort(reverse=bool(options.reverse))
  454. if options.pie:
  455. showpie(l, sort)
  456. return
  457. elif options.bar:
  458. showbar(l, columns, sort)
  459. return
  460. if not options.no_header:
  461. print header
  462. for k,r in l:
  463. print format % tuple([f(v) for f,v in zip(formatter, r)])
  464. if options.totals:
  465. # totals
  466. t = []
  467. for c in columns:
  468. f = fields[c][3]
  469. if f:
  470. t.append(f([fields[c][1](n) for n in rows]))
  471. else:
  472. t.append("")
  473. print "-" * len(header)
  474. print format % tuple([f(v) for f,v in zip(formatter, t)])
  475. def showpie(l, sort):
  476. try:
  477. import pylab
  478. except ImportError:
  479. sys.stderr.write("pie chart requires matplotlib\n")
  480. sys.exit(-1)
  481. if (l[0][0] < l[-1][0]):
  482. l.reverse()
  483. labels = [r[1][-1] for r in l]
  484. values = [r[0] for r in l] # sort field
  485. tm = totalmem()
  486. s = sum(values)
  487. unused = tm - s
  488. t = 0
  489. while values and (t + values[-1] < (tm * .02) or
  490. values[-1] < (tm * .005)):
  491. t += values.pop()
  492. labels.pop()
  493. if t:
  494. values.append(t)
  495. labels.append('other')
  496. explode = [0] * len(values)
  497. if unused > 0:
  498. values.insert(0, unused)
  499. labels.insert(0, 'unused')
  500. explode.insert(0, .05)
  501. pylab.figure(1, figsize=(6,6))
  502. ax = pylab.axes([0.1, 0.1, 0.8, 0.8])
  503. pylab.pie(values, explode = explode, labels=labels,
  504. autopct="%.2f%%", shadow=True)
  505. pylab.title('%s by %s' % (options.pie, sort))
  506. pylab.show()
  507. def showbar(l, columns, sort):
  508. try:
  509. import pylab, numpy
  510. except ImportError:
  511. sys.stderr.write("bar chart requires matplotlib\n")
  512. sys.exit(-1)
  513. if (l[0][0] < l[-1][0]):
  514. l.reverse()
  515. rc = []
  516. key = []
  517. for n in range(len(columns) - 1):
  518. try:
  519. if columns[n] in 'pid user group'.split():
  520. continue
  521. float(l[0][1][n])
  522. rc.append(n)
  523. key.append(columns[n])
  524. except:
  525. pass
  526. width = 1.0 / (len(rc) + 1)
  527. offset = width / 2
  528. def gc(n):
  529. return 'bgrcmyw'[n % 7]
  530. pl = []
  531. ind = numpy.arange(len(l))
  532. for n in xrange(len(rc)):
  533. pl.append(pylab.bar(ind + offset + width * n,
  534. [x[1][rc[n]] for x in l], width, color=gc(n)))
  535. #plt.xticks(ind + .5, )
  536. pylab.gca().set_xticks(ind + .5)
  537. pylab.gca().set_xticklabels([x[1][-1] for x in l], rotation=45)
  538. pylab.legend([p[0] for p in pl], key)
  539. pylab.show()
  540. parser = optparse.OptionParser("%prog [options]")
  541. parser.add_option("-H", "--no-header", action="store_true",
  542. help="disable header line")
  543. parser.add_option("-c", "--columns", type="str",
  544. help="columns to show")
  545. parser.add_option("-t", "--totals", action="store_true",
  546. help="show totals")
  547. parser.add_option("-R", "--realmem", type="str",
  548. help="amount of physical RAM")
  549. parser.add_option("-K", "--kernel", type="str",
  550. help="path to kernel image")
  551. parser.add_option("-m", "--mappings", action="store_true",
  552. help="show mappings")
  553. parser.add_option("-u", "--users", action="store_true",
  554. help="show users")
  555. parser.add_option("-w", "--system", action="store_true",
  556. help="show whole system")
  557. parser.add_option("-P", "--processfilter", type="str",
  558. help="process filter regex")
  559. parser.add_option("-M", "--mapfilter", type="str",
  560. help="map filter regex")
  561. parser.add_option("-U", "--userfilter", type="str",
  562. help="user filter regex")
  563. parser.add_option("-n", "--numeric", action="store_true",
  564. help="numeric output")
  565. parser.add_option("-s", "--sort", type="str",
  566. help="field to sort on")
  567. parser.add_option("-r", "--reverse", action="store_true",
  568. help="reverse sort")
  569. parser.add_option("-p", "--percent", action="store_true",
  570. help="show percentage")
  571. parser.add_option("-k", "--abbreviate", action="store_true",
  572. help="show unit suffixes")
  573. parser.add_option("", "--pie", type='str',
  574. help="show pie graph")
  575. parser.add_option("", "--bar", type='str',
  576. help="show bar graph")
  577. parser.add_option("-S", "--source", type="str",
  578. help="/proc data source")
  579. defaults = {}
  580. parser.set_defaults(**defaults)
  581. (options, args) = parser.parse_args()
  582. try:
  583. src = tardata(options.source)
  584. except:
  585. src = procdata(options.source)
  586. try:
  587. if options.mappings:
  588. showmaps()
  589. elif options.users:
  590. showusers()
  591. elif options.system:
  592. showsystem()
  593. else:
  594. showpids()
  595. except IOError, e:
  596. if e.errno == errno.EPIPE:
  597. pass
  598. except KeyboardInterrupt:
  599. pass