Skip to content

Commit

Permalink
Add filter for stats name (#52)
Browse files Browse the repository at this point in the history
* Add filter for stats name (#43)

* Turn lamba collection into if collection

* Accept multiple regexes

* Better name for other_stats
  • Loading branch information
rthouvenin authored and majormoses committed Oct 4, 2017
1 parent 96fa4df commit fb21c75
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 43 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ This CHANGELOG follows the format listed at [Keep A Changelog](http://keepachang


## [Unreleased]
### Added
- metric-per-processes.py: Add metrics filter (@rthouvenin)

## [2.4.0] - 2017-07-18
### Added
Expand Down
151 changes: 108 additions & 43 deletions bin/metrics-per-process.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
#
# USAGE:
#
# metrics-per-process.py -n <process_name> | -p <path_to_process_pid_file> [-s <graphite_scheme>]
# metrics-per-process.py -n <process_name> | -p <path_to_process_pid_file> [-s <graphite_scheme>] [-m <metrics_regexes>]
#
# NOTES:
# The plugin requires to read files in the /proc file system, make sure the owner
Expand Down Expand Up @@ -66,6 +66,7 @@
import os
import optparse
import psutil
import re
import sys
import time

Expand All @@ -89,6 +90,7 @@
'CLOSING',
'NONE'
]
MEMORY_STATS = ['rss', 'vms', 'shared', 'text', 'lib', 'data', 'dirty']

def find_pids_from_name(process_name):
'''Find process PID from name using /proc/<pids>/comm'''
Expand All @@ -107,60 +109,115 @@ def find_pids_from_name(process_name):
pass
return pids

def stats_per_pid(pid):
'''Gets process stats using psutil module
def additional_stats(process_handler, metrics_regexp):
stats = {}

details at http://pythonhosted.org/psutil/#process-class'''
if metrics_regexp.match('cpu.user'):
stats['cpu.user'] = process_handler.cpu_times().user

stats = {}
process_handler = psutil.Process(pid)
if metrics_regexp.match('cpu.system'):
stats['cpu.system'] = process_handler.cpu_times().system

if metrics_regexp.match('cpu.percent'):
stats['cpu.percent'] = process_handler.cpu_percent()

if metrics_regexp.match('threads'):
stats['threads'] = process_handler.num_threads()

if metrics_regexp.match('memory.percent'):
stats['memory.percent'] = process_handler.memory_percent()

if metrics_regexp.match('fds'):
stats['fds'] = process_handler.num_fds()

if metrics_regexp.match('ctx_switches.voluntary'):
stats['ctx_switches.voluntary'] = process_handler.num_ctx_switches().voluntary

if metrics_regexp.match('ctx_switches.involuntary'):
stats['ctx_switches.involuntary'] = process_handler.num_ctx_switches().involuntary

if metrics_regexp.match('io_counters.read_count'):
stats['io_counters.read_count'] = process_handler.io_counters().read_count

if metrics_regexp.match('io_counters.write_count'):
stats['io_counters.write_count'] = process_handler.io_counters().write_count

if metrics_regexp.match('io_counters.read_bytes'):
stats['io_counters.read_bytes'] = process_handler.io_counters().read_bytes

if metrics_regexp.match('io_counters.write_bytes'):
stats['io_counters.write_bytes'] = process_handler.io_counters().write_bytes

return stats

# Memory info
def memory_stats(process_handler, metrics_regexp):
if psutil.version_info < (4,0,0):
process_memory_info = process_handler.memory_info_ex()
else:
process_memory_info = process_handler.memory_info()
stats['cpu.user'] = process_handler.cpu_times().user
stats['cpu.system'] = process_handler.cpu_times().system
stats['cpu.percent'] = process_handler.cpu_percent()
stats['threads'] = process_handler.num_threads()
stats['memory.rss'] = process_memory_info.rss
stats['memory.vms'] = process_memory_info.vms
stats['memory.shared'] = process_memory_info.shared
stats['memory.text'] = process_memory_info.text
stats['memory.lib'] = process_memory_info.lib
stats['memory.data'] = process_memory_info.data
stats['memory.dirty'] = process_memory_info.dirty
stats['memory.percent'] = process_handler.memory_percent()
stats['fds'] = process_handler.num_fds()
stats['ctx_switches.voluntary'] = process_handler.num_ctx_switches().voluntary
stats['ctx_switches.involuntary'] = process_handler.num_ctx_switches().involuntary
stats['io_counters.read_count'] = process_handler.io_counters().read_count
stats['io_counters.write_count'] = process_handler.io_counters().write_count
stats['io_counters.read_bytes'] = process_handler.io_counters().read_bytes
stats['io_counters.write_bytes'] = process_handler.io_counters().write_bytes
# TCP/UDP/Unix Socket Connections
tcp_conns = process_handler.connections(kind='tcp')

stats = {}
for stat in MEMORY_STATS:
if metrics_regexp.match('memory.' + stat):
stats['memory.' + stat] = getattr(process_memory_info, stat)

return stats

# TCP/UDP/Unix Socket Connections
def connection_stats(process_handler, metrics_regexp):
tcp_stats = ['total'] + [s.lower() for s in TCP_CONN_STATUSES]
tcp_conns = None
tcp_conns_count = {}
for stat in tcp_stats:
if metrics_regexp.match('conns.tcp.' + stat):
if tcp_conns is None:
tcp_conns = process_handler.connections(kind='tcp')

stats = {}
if tcp_conns:
stats['conns.tcp.total'] = len(tcp_conns)
tcp_conns_count = {}
for tcp_status in TCP_CONN_STATUSES:
tcp_conns_count['conns.tcp.' + tcp_status.lower()] = 0
for conn in tcp_conns:
if conn.status == tcp_status:
tcp_conns_count['conns.tcp.' + tcp_status.lower()] = tcp_conns_count[
'conns.tcp.' + tcp_status.lower()] + 1
stat = 'conns.tcp.' + tcp_status.lower()
if metrics_regexp.match(stat):
tcp_conns_count[stat] = 0
for conn in tcp_conns:
if conn.status == tcp_status:
tcp_conns_count[stat] = tcp_conns_count[stat] + 1
stats.update(tcp_conns_count)
udp_conns = process_handler.connections(kind='udp')
if udp_conns:
stats['conns.udp.total'] = len(udp_conns)
unix_conns = process_handler.connections(kind='unix')
if unix_conns:
stats['conns.unix_sockets.total'] = len(unix_conns)

if metrics_regexp.match('conns.udp.total'):
udp_conns = process_handler.connections(kind='udp')
if udp_conns:
stats['conns.udp.total'] = len(udp_conns)

if metrics_regexp.match('conns.unix_sockets.total'):
unix_conns = process_handler.connections(kind='unix')
if unix_conns:
stats['conns.unix_sockets.total'] = len(unix_conns)

return stats

def stats_per_pid(pid, metrics_regexes):
'''Gets process stats using psutil module
Returns only the stats with a name that matches one of the metrics_regexes
details at http://pythonhosted.org/psutil/#process-class'''

stats = {}
process_handler = psutil.Process(pid)

for metrics_regexp in metrics_regexes:
stats.update(memory_stats(process_handler, metrics_regexp))
stats.update(connection_stats(process_handler, metrics_regexp))
stats.update(additional_stats(process_handler, metrics_regexp))

return stats

def multi_pid_process_stats(pids):
def multi_pid_process_stats(pids, metrics_regexes):
stats = {'total_processes': len(pids)}
for pid in pids:
stats = Counter(stats) + Counter(stats_per_pid(pid))
stats = Counter(stats) + Counter(stats_per_pid(pid, metrics_regexes))
return stats

def recursive_dict_sum(dictionnary):
Expand Down Expand Up @@ -208,6 +265,12 @@ def main():
dest = 'graphite_scheme',
metavar = 'GRAPHITE_SCHEME')

parser.add_option('-m', '--metrics-regexes',
help = 'comma-separated list of regexes used to match the metric names to collect, default to .*',
default = '.*',
dest = 'metrics_regexes',
metavar = 'METRICS_REGEXES')

(options, args) = parser.parse_args()

if options.process_name and options.process_pid_file:
Expand All @@ -218,13 +281,15 @@ def main():
print 'A process name or a process pid file path is needed'
sys.exit(1)

options.metrics_regexes = [re.compile(regex) for regex in options.metrics_regexes.split(',')]

if options.process_name:
pids = find_pids_from_name(options.process_name)
graphite_printer(multi_pid_process_stats(pids), options.graphite_scheme)
graphite_printer(multi_pid_process_stats(pids, options.metrics_regexes), options.graphite_scheme)

if options.process_pid_file:
pid = get_pid_from_pid_file(options.process_pid_file)
graphite_printer(stats_per_pid(pid), options.graphite_scheme)
graphite_printer(stats_per_pid(pid, options.metrics_regexes), options.graphite_scheme)
#
if __name__ == '__main__':
main()

0 comments on commit fb21c75

Please sign in to comment.