Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aggvpn
GitHub Repository: aggvpn/ws
Path: blob/main/system/ram.sh
567 views
1
#!/usr/bin/env python
2
3
# Try to determine how much RAM is currently being used per program.
4
# Note per _program_, not per process. So for example this script
5
# will report RAM used by all httpd process together. In detail it reports:
6
# sum(private RAM for program processes) + sum(Shared RAM for program processes)
7
# The shared RAM is problematic to calculate, and this script automatically
8
# selects the most accurate method available for your kernel.
9
10
# Licence: LGPLv2
11
# Author: [email protected]
12
# Source: http://www.pixelbeat.org/scripts/ps_mem.py
13
14
# V1.0 06 Jul 2005 Initial release
15
# V1.1 11 Aug 2006 root permission required for accuracy
16
# V1.2 08 Nov 2006 Add total to output
17
# Use KiB,MiB,... for units rather than K,M,...
18
# V1.3 22 Nov 2006 Ignore shared col from /proc/$pid/statm for
19
# 2.6 kernels up to and including 2.6.9.
20
# There it represented the total file backed extent
21
# V1.4 23 Nov 2006 Remove total from output as it's meaningless
22
# (the shared values overlap with other programs).
23
# Display the shared column. This extra info is
24
# useful, especially as it overlaps between programs.
25
# V1.5 26 Mar 2007 Remove redundant recursion from human()
26
# V1.6 05 Jun 2007 Also report number of processes with a given name.
27
# Patch from [email protected]
28
# V1.7 20 Sep 2007 Use PSS from /proc/$pid/smaps if available, which
29
# fixes some over-estimation and allows totalling.
30
# Enumerate the PIDs directly rather than using ps,
31
# which fixes the possible race between reading
32
# RSS with ps, and shared memory with this program.
33
# Also we can show non truncated command names.
34
# V1.8 28 Sep 2007 More accurate matching for stats in /proc/$pid/smaps
35
# as otherwise could match libraries causing a crash.
36
# Patch from [email protected]
37
# V1.9 20 Feb 2008 Fix invalid values reported when PSS is available.
38
# Reported by Andrey Borzenkov <[email protected]>
39
# V3.8 17 Jun 2016
40
# http://github.com/pixelb/scripts/commits/master/scripts/ps_mem.py
41
42
# Notes:
43
#
44
# All interpreted programs where the interpreter is started
45
# by the shell or with env, will be merged to the interpreter
46
# (as that's what's given to exec). For e.g. all python programs
47
# starting with "#!/usr/bin/env python" will be grouped under python.
48
# You can change this by using the full command line but that will
49
# have the undesirable affect of splitting up programs started with
50
# differing parameters (for e.g. mingetty tty[1-6]).
51
#
52
# For 2.6 kernels up to and including 2.6.13 and later 2.4 redhat kernels
53
# (rmap vm without smaps) it can not be accurately determined how many pages
54
# are shared between processes in general or within a program in our case:
55
# http://lkml.org/lkml/2005/7/6/250
56
# A warning is printed if overestimation is possible.
57
# In addition for 2.6 kernels up to 2.6.9 inclusive, the shared
58
# value in /proc/$pid/statm is the total file-backed extent of a process.
59
# We ignore that, introducing more overestimation, again printing a warning.
60
# Since kernel 2.6.23-rc8-mm1 PSS is available in smaps, which allows
61
# us to calculate a more accurate value for the total RAM used by programs.
62
#
63
# Programs that use CLONE_VM without CLONE_THREAD are discounted by assuming
64
# they're the only programs that have the same /proc/$PID/smaps file for
65
# each instance. This will fail if there are multiple real instances of a
66
# program that then use CLONE_VM without CLONE_THREAD, or if a clone changes
67
# its memory map while we're checksumming each /proc/$PID/smaps.
68
#
69
# I don't take account of memory allocated for a program
70
# by other programs. For e.g. memory used in the X server for
71
# a program could be determined, but is not.
72
#
73
# FreeBSD is supported if linprocfs is mounted at /compat/linux/proc/
74
# FreeBSD 8.0 supports up to a level of Linux 2.6.16
75
76
import getopt
77
import time
78
import errno
79
import os
80
import sys
81
82
# The following exits cleanly on Ctrl-C or EPIPE
83
# while treating other exceptions as before.
84
def std_exceptions(etype, value, tb):
85
sys.excepthook = sys.__excepthook__
86
if issubclass(etype, KeyboardInterrupt):
87
pass
88
elif issubclass(etype, IOError) and value.errno == errno.EPIPE:
89
pass
90
else:
91
sys.__excepthook__(etype, value, tb)
92
sys.excepthook = std_exceptions
93
94
#
95
# Define some global variables
96
#
97
98
PAGESIZE = os.sysconf("SC_PAGE_SIZE") / 1024 #KiB
99
our_pid = os.getpid()
100
101
have_pss = 0
102
have_swap_pss = 0
103
104
class Proc:
105
def __init__(self):
106
uname = os.uname()
107
if uname[0] == "FreeBSD":
108
self.proc = '/compat/linux/proc'
109
else:
110
self.proc = '/proc'
111
112
def path(self, *args):
113
return os.path.join(self.proc, *(str(a) for a in args))
114
115
def open(self, *args):
116
try:
117
if sys.version_info < (3,):
118
return open(self.path(*args))
119
else:
120
return open(self.path(*args), errors='ignore')
121
except (IOError, OSError):
122
val = sys.exc_info()[1]
123
if (val.errno == errno.ENOENT or # kernel thread or process gone
124
val.errno == errno.EPERM):
125
raise LookupError
126
raise
127
128
proc = Proc()
129
130
131
#
132
# Functions
133
#
134
135
def parse_options():
136
try:
137
long_options = [
138
'split-args',
139
'help',
140
'total',
141
'discriminate-by-pid',
142
'swap'
143
]
144
opts, args = getopt.getopt(sys.argv[1:], "shtdSp:w:", long_options)
145
except getopt.GetoptError:
146
sys.stderr.write(help())
147
sys.exit(3)
148
149
if len(args):
150
sys.stderr.write("Extraneous arguments: %s\n" % args)
151
sys.exit(3)
152
153
# ps_mem.py options
154
split_args = False
155
pids_to_show = None
156
discriminate_by_pid = False
157
show_swap = False
158
watch = None
159
only_total = False
160
161
for o, a in opts:
162
if o in ('-s', '--split-args'):
163
split_args = True
164
if o in ('-t', '--total'):
165
only_total = True
166
if o in ('-d', '--discriminate-by-pid'):
167
discriminate_by_pid = True
168
if o in ('-S', '--swap'):
169
show_swap = True
170
if o in ('-h', '--help'):
171
sys.stdout.write(help())
172
sys.exit(0)
173
if o in ('-p',):
174
try:
175
pids_to_show = [int(x) for x in a.split(',')]
176
except:
177
sys.stderr.write(help())
178
sys.exit(3)
179
if o in ('-w',):
180
try:
181
watch = int(a)
182
except:
183
sys.stderr.write(help())
184
sys.exit(3)
185
186
return (
187
split_args,
188
pids_to_show,
189
watch,
190
only_total,
191
discriminate_by_pid,
192
show_swap
193
)
194
195
196
def help():
197
help_msg = 'Usage: ps_mem [OPTION]...\n' \
198
'Show program core memory usage\n' \
199
'\n' \
200
' -h, -help Show this help\n' \
201
' -p <pid>[,pid2,...pidN] Only show memory usage PIDs in the '\
202
'specified list\n' \
203
' -s, --split-args Show and separate by, all command line'\
204
' arguments\n' \
205
' -t, --total Show only the total value\n' \
206
' -d, --discriminate-by-pid Show by process rather than by program\n' \
207
' -S, --swap Show swap information\n' \
208
' -w <N> Measure and show process memory every'\
209
' N seconds\n'
210
211
return help_msg
212
213
214
# (major,minor,release)
215
def kernel_ver():
216
kv = proc.open('sys/kernel/osrelease').readline().split(".")[:3]
217
last = len(kv)
218
if last == 2:
219
kv.append('0')
220
last -= 1
221
while last > 0:
222
for char in "-_":
223
kv[last] = kv[last].split(char)[0]
224
try:
225
int(kv[last])
226
except:
227
kv[last] = 0
228
last -= 1
229
return (int(kv[0]), int(kv[1]), int(kv[2]))
230
231
232
#return Private,Shared
233
#Note shared is always a subset of rss (trs is not always)
234
def getMemStats(pid):
235
global have_pss
236
global have_swap_pss
237
mem_id = pid #unique
238
Private_lines = []
239
Shared_lines = []
240
Pss_lines = []
241
Rss = (int(proc.open(pid, 'statm').readline().split()[1])
242
* PAGESIZE)
243
Swap_lines = []
244
Swap_pss_lines = []
245
246
Swap = 0
247
Swap_pss = 0
248
249
if os.path.exists(proc.path(pid, 'smaps')): # stat
250
lines = proc.open(pid, 'smaps').readlines() # open
251
# Note we checksum smaps as maps is usually but
252
# not always different for separate processes.
253
mem_id = hash(''.join(lines))
254
for line in lines:
255
if line.startswith("Shared"):
256
Shared_lines.append(line)
257
elif line.startswith("Private"):
258
Private_lines.append(line)
259
elif line.startswith("Pss"):
260
have_pss = 1
261
Pss_lines.append(line)
262
elif line.startswith("Swap:"):
263
Swap_lines.append(line)
264
elif line.startswith("SwapPss:"):
265
have_swap_pss = 1
266
Swap_pss_lines.append(line)
267
Shared = sum([int(line.split()[1]) for line in Shared_lines])
268
Private = sum([int(line.split()[1]) for line in Private_lines])
269
#Note Shared + Private = Rss above
270
#The Rss in smaps includes video card mem etc.
271
if have_pss:
272
pss_adjust = 0.5 # add 0.5KiB as this avg error due to truncation
273
Pss = sum([float(line.split()[1])+pss_adjust for line in Pss_lines])
274
Shared = Pss - Private
275
# Note that Swap = Private swap + Shared swap.
276
Swap = sum([int(line.split()[1]) for line in Swap_lines])
277
if have_swap_pss:
278
# The kernel supports SwapPss, that shows proportional swap share.
279
# Note that Swap - SwapPss is not Private Swap.
280
Swap_pss = sum([int(line.split()[1]) for line in Swap_pss_lines])
281
elif (2,6,1) <= kernel_ver() <= (2,6,9):
282
Shared = 0 #lots of overestimation, but what can we do?
283
Private = Rss
284
else:
285
Shared = int(proc.open(pid, 'statm').readline().split()[2])
286
Shared *= PAGESIZE
287
Private = Rss - Shared
288
return (Private, Shared, mem_id, Swap, Swap_pss)
289
290
291
def getCmdName(pid, split_args, discriminate_by_pid):
292
cmdline = proc.open(pid, 'cmdline').read().split("\0")
293
if cmdline[-1] == '' and len(cmdline) > 1:
294
cmdline = cmdline[:-1]
295
296
path = proc.path(pid, 'exe')
297
try:
298
path = os.readlink(path)
299
# Some symlink targets were seen to contain NULs on RHEL 5 at least
300
# https://github.com/pixelb/scripts/pull/10, so take string up to NUL
301
path = path.split('\0')[0]
302
except OSError:
303
val = sys.exc_info()[1]
304
if (val.errno == errno.ENOENT or # either kernel thread or process gone
305
val.errno == errno.EPERM):
306
raise LookupError
307
raise
308
309
if split_args:
310
return " ".join(cmdline)
311
if path.endswith(" (deleted)"):
312
path = path[:-10]
313
if os.path.exists(path):
314
path += " [updated]"
315
else:
316
#The path could be have prelink stuff so try cmdline
317
#which might have the full path present. This helped for:
318
#/usr/libexec/notification-area-applet.#prelink#.fX7LCT (deleted)
319
if os.path.exists(cmdline[0]):
320
path = cmdline[0] + " [updated]"
321
else:
322
path += " [deleted]"
323
exe = os.path.basename(path)
324
cmd = proc.open(pid, 'status').readline()[6:-1]
325
if exe.startswith(cmd):
326
cmd = exe #show non truncated version
327
#Note because we show the non truncated name
328
#one can have separated programs as follows:
329
#584.0 KiB + 1.0 MiB = 1.6 MiB mozilla-thunder (exe -> bash)
330
# 56.0 MiB + 22.2 MiB = 78.2 MiB mozilla-thunderbird-bin
331
if sys.version_info >= (3,):
332
cmd = cmd.encode(errors='replace').decode()
333
if discriminate_by_pid:
334
cmd = '%s [%d]' % (cmd, pid)
335
return cmd
336
337
338
#The following matches "du -h" output
339
#see also human.py
340
def human(num, power="Ki", units=None):
341
if units is None:
342
powers = ["Ki", "Mi", "Gi", "Ti"]
343
while num >= 1000: #4 digits
344
num /= 1024.0
345
power = powers[powers.index(power)+1]
346
return "%.1f %sB" % (num, power)
347
else:
348
return "%.f" % ((num * 1024) / units)
349
350
351
def cmd_with_count(cmd, count):
352
if count > 1:
353
return "%s (%u)" % (cmd, count)
354
else:
355
return cmd
356
357
#Warn of possible inaccuracies
358
#2 = accurate & can total
359
#1 = accurate only considering each process in isolation
360
#0 = some shared mem not reported
361
#-1= all shared mem not reported
362
def shared_val_accuracy():
363
"""http://wiki.apache.org/spamassassin/TopSharedMemoryBug"""
364
kv = kernel_ver()
365
pid = os.getpid()
366
if kv[:2] == (2,4):
367
if proc.open('meminfo').read().find("Inact_") == -1:
368
return 1
369
return 0
370
elif kv[:2] == (2,6):
371
if os.path.exists(proc.path(pid, 'smaps')):
372
if proc.open(pid, 'smaps').read().find("Pss:")!=-1:
373
return 2
374
else:
375
return 1
376
if (2,6,1) <= kv <= (2,6,9):
377
return -1
378
return 0
379
elif kv[0] > 2 and os.path.exists(proc.path(pid, 'smaps')):
380
return 2
381
else:
382
return 1
383
384
def show_shared_val_accuracy( possible_inacc, only_total=False ):
385
level = ("Warning","Error")[only_total]
386
if possible_inacc == -1:
387
sys.stderr.write(
388
"%s: Shared memory is not reported by this system.\n" % level
389
)
390
sys.stderr.write(
391
"Values reported will be too large, and totals are not reported\n"
392
)
393
elif possible_inacc == 0:
394
sys.stderr.write(
395
"%s: Shared memory is not reported accurately by this system.\n" % level
396
)
397
sys.stderr.write(
398
"Values reported could be too large, and totals are not reported\n"
399
)
400
elif possible_inacc == 1:
401
sys.stderr.write(
402
"%s: Shared memory is slightly over-estimated by this system\n"
403
"for each program, so totals are not reported.\n" % level
404
)
405
sys.stderr.close()
406
if only_total and possible_inacc != 2:
407
sys.exit(1)
408
409
410
def get_memory_usage(pids_to_show, split_args, discriminate_by_pid,
411
include_self=False, only_self=False):
412
cmds = {}
413
shareds = {}
414
mem_ids = {}
415
count = {}
416
swaps = {}
417
shared_swaps = {}
418
for pid in os.listdir(proc.path('')):
419
if not pid.isdigit():
420
continue
421
pid = int(pid)
422
423
# Some filters
424
if only_self and pid != our_pid:
425
continue
426
if pid == our_pid and not include_self:
427
continue
428
if pids_to_show is not None and pid not in pids_to_show:
429
continue
430
431
try:
432
cmd = getCmdName(pid, split_args, discriminate_by_pid)
433
except LookupError:
434
#operation not permitted
435
#kernel threads don't have exe links or
436
#process gone
437
continue
438
439
try:
440
private, shared, mem_id, swap, swap_pss = getMemStats(pid)
441
except RuntimeError:
442
continue #process gone
443
if shareds.get(cmd):
444
if have_pss: #add shared portion of PSS together
445
shareds[cmd] += shared
446
elif shareds[cmd] < shared: #just take largest shared val
447
shareds[cmd] = shared
448
else:
449
shareds[cmd] = shared
450
cmds[cmd] = cmds.setdefault(cmd, 0) + private
451
if cmd in count:
452
count[cmd] += 1
453
else:
454
count[cmd] = 1
455
mem_ids.setdefault(cmd, {}).update({mem_id: None})
456
457
# Swap (overcounting for now...)
458
swaps[cmd] = swaps.setdefault(cmd, 0) + swap
459
if have_swap_pss:
460
shared_swaps[cmd] = shared_swaps.setdefault(cmd, 0) + swap_pss
461
else:
462
shared_swaps[cmd] = 0
463
464
# Total swaped mem for each program
465
total_swap = 0
466
467
# Total swaped shared mem for each program
468
total_shared_swap = 0
469
470
# Add shared mem for each program
471
total = 0
472
473
for cmd in cmds:
474
cmd_count = count[cmd]
475
if len(mem_ids[cmd]) == 1 and cmd_count > 1:
476
# Assume this program is using CLONE_VM without CLONE_THREAD
477
# so only account for one of the processes
478
cmds[cmd] /= cmd_count
479
if have_pss:
480
shareds[cmd] /= cmd_count
481
cmds[cmd] = cmds[cmd] + shareds[cmd]
482
total += cmds[cmd] # valid if PSS available
483
total_swap += swaps[cmd]
484
if have_swap_pss:
485
total_shared_swap += shared_swaps[cmd]
486
487
sorted_cmds = sorted(cmds.items(), key=lambda x:x[1])
488
sorted_cmds = [x for x in sorted_cmds if x[1]]
489
490
return sorted_cmds, shareds, count, total, swaps, shared_swaps, \
491
total_swap, total_shared_swap
492
493
494
def print_header(show_swap, discriminate_by_pid):
495
output_string = " Private + Shared = RAM used"
496
if show_swap:
497
if have_swap_pss:
498
output_string += " " * 5 + "Shared Swap"
499
output_string += " Swap used"
500
output_string += "\tProgram"
501
if discriminate_by_pid:
502
output_string += "[pid]"
503
output_string += "\n\n"
504
sys.stdout.write(output_string)
505
506
507
def print_memory_usage(sorted_cmds, shareds, count, total, swaps, total_swap,
508
shared_swaps, total_shared_swap, show_swap):
509
for cmd in sorted_cmds:
510
511
output_string = "%9s + %9s = %9s"
512
output_data = (human(cmd[1]-shareds[cmd[0]]),
513
human(shareds[cmd[0]]), human(cmd[1]))
514
if show_swap:
515
if have_swap_pss:
516
output_string += "\t%9s"
517
output_data += (human(shared_swaps[cmd[0]]),)
518
output_string += " %9s"
519
output_data += (human(swaps[cmd[0]]),)
520
output_string += "\t%s\n"
521
output_data += (cmd_with_count(cmd[0], count[cmd[0]]),)
522
523
sys.stdout.write(output_string % output_data)
524
525
if have_pss:
526
if show_swap:
527
if have_swap_pss:
528
sys.stdout.write("%s\n%s%9s%s%9s%s%9s\n%s\n" %
529
("-" * 61, " " * 24, human(total), " " * 7,
530
human(total_shared_swap), " " * 3,
531
human(total_swap), "=" * 61))
532
else:
533
sys.stdout.write("%s\n%s%9s%s%9s\n%s\n" %
534
("-" * 45, " " * 24, human(total), " " * 3,
535
human(total_swap), "=" * 45))
536
else:
537
sys.stdout.write("%s\n%s%9s\n%s\n" %
538
("-" * 33, " " * 24, human(total), "=" * 33))
539
540
541
def verify_environment():
542
if os.geteuid() != 0:
543
sys.stderr.write("Sorry, root permission required.\n")
544
sys.stderr.close()
545
sys.exit(1)
546
547
try:
548
kernel_ver()
549
except (IOError, OSError):
550
val = sys.exc_info()[1]
551
if val.errno == errno.ENOENT:
552
sys.stderr.write(
553
"Couldn't access " + proc.path('') + "\n"
554
"Only GNU/Linux and FreeBSD (with linprocfs) are supported\n")
555
sys.exit(2)
556
else:
557
raise
558
559
def main():
560
split_args, pids_to_show, watch, only_total, discriminate_by_pid, \
561
show_swap = parse_options()
562
563
verify_environment()
564
565
if not only_total:
566
print_header(show_swap, discriminate_by_pid)
567
568
if watch is not None:
569
try:
570
sorted_cmds = True
571
while sorted_cmds:
572
sorted_cmds, shareds, count, total, swaps, shared_swaps, \
573
total_swap, total_shared_swap = \
574
get_memory_usage(pids_to_show, split_args,
575
discriminate_by_pid)
576
if only_total and have_pss:
577
sys.stdout.write(human(total, units=1)+'\n')
578
elif not only_total:
579
print_memory_usage(sorted_cmds, shareds, count, total,
580
swaps, total_swap, shared_swaps,
581
total_shared_swap, show_swap)
582
583
sys.stdout.flush()
584
time.sleep(watch)
585
else:
586
sys.stdout.write('Process does not exist anymore.\n')
587
except KeyboardInterrupt:
588
pass
589
else:
590
# This is the default behavior
591
sorted_cmds, shareds, count, total, swaps, shared_swaps, total_swap, \
592
total_shared_swap = get_memory_usage(pids_to_show, split_args,
593
discriminate_by_pid)
594
if only_total and have_pss:
595
sys.stdout.write(human(total, units=1)+'\n')
596
elif not only_total:
597
print_memory_usage(sorted_cmds, shareds, count, total, swaps,
598
total_swap, shared_swaps, total_shared_swap,
599
show_swap)
600
601
# We must close explicitly, so that any EPIPE exception
602
# is handled by our excepthook, rather than the default
603
# one which is reenabled after this script finishes.
604
sys.stdout.close()
605
606
vm_accuracy = shared_val_accuracy()
607
show_shared_val_accuracy( vm_accuracy, only_total )
608
609
if __name__ == '__main__': main()
610
611