##// END OF EJS Templates
added latest changes
added latest changes

File last commit:

r5608:6d33e504 default
r5658:a109f5ac merge default
Show More
profile-mem.py
160 lines | 4.6 KiB | text/x-python | PythonLexer
# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
# (only), as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# This program is dual-licensed. If you wish to learn more about the
# RhodeCode Enterprise Edition, including its added features, Support services,
# and proprietary license terms, please see https://rhodecode.com/licenses/
"""
Utility to gather certain statistics about a process.
Used to generate data about the memory consumption of the vcsserver. It is
quite generic and should work for every process. Use the parameter `--help`
to see all options.
Example call::
python profile-mem.py --pid=89816 --ae --ae-key=YOUR_API_KEY
"""
import argparse
import json
import sys
import time
import datetime
import psutil
import logging
import socket
from webhelpers2.number import format_byte_size
logging.basicConfig(level=logging.DEBUG)
def parse_options():
parser = argparse.ArgumentParser(
description=__doc__)
parser.add_argument(
'--pid', required=True, type=int,
help="Process ID to monitor.")
parser.add_argument(
'--human', action='store_true',
help="Show Human numbers")
parser.add_argument(
'--interval', '-i', type=float, default=5,
help="Interval in secods.")
return parser.parse_args()
def profile():
config = parse_options()
try:
process = psutil.Process(config.pid)
except psutil.NoSuchProcess:
print("Process {pid} does not exist!".format(pid=config.pid))
sys.exit(1)
prev_stats = None
while True:
stats = process_stats(process, prev_stats)
prev_stats = stats
dump_stats(stats, human=config.human)
if config.appenlight:
client = AppenlightClient(
url=config.appenlight_url,
api_key=config.appenlight_api_key)
client.dump_stats(stats)
time.sleep(config.interval)
def process_stats(process, prev_stats):
mem = process.memory_info()
iso_now = datetime.datetime.utcnow().isoformat()
prev_rss_diff, prev_vms_diff = 0, 0
cur_rss = mem.rss
cur_vms = mem.vms
if prev_stats:
prev_rss_diff = cur_rss - prev_stats[0]['tags'][0][1]
prev_vms_diff = cur_vms - prev_stats[0]['tags'][1][1]
stats = [
{'message': 'Memory stats of process {pid}'.format(pid=process.pid),
'namespace': 'process.{pid}'.format(pid=process.pid),
'server': socket.getfqdn(socket.gethostname()),
'tags': [
['rss', cur_rss],
['vms', cur_vms]
],
'diff': [
['rss', prev_rss_diff],
['vms', prev_vms_diff]
],
'date': iso_now,
},
]
return stats
def dump_stats(stats, human=False):
for sample in stats:
if human:
diff = stats[0]['diff'][0][1]
if diff < 0:
diff = '-' + format_byte_size(abs(diff), binary=True)
elif diff > 0:
diff = '+' + format_byte_size(diff, binary=True)
else:
diff = ' ' + format_byte_size(diff, binary=True)
print('Sample:{message} RSS:{rss} RSS_DIFF:{rss_diff}'.format(
message=stats[0]['message'],
rss=format_byte_size(stats[0]['tags'][0][1], binary=True),
rss_diff=diff,
))
else:
print(json.dumps(sample))
class AppenlightClient(object):
url_template = '{url}?protocol_version=0.5'
def __init__(self, url, api_key):
self.url = self.url_template.format(url=url)
self.api_key = api_key
def dump_stats(self, stats):
import requests
response = requests.post(
self.url,
headers={
'X-appenlight-api-key': self.api_key},
data=json.dumps(stats))
if not response.status_code == 200:
logging.error(
'Sending to appenlight failed\n%s\n%s',
response.headers, response.text)
if __name__ == '__main__':
try:
profile()
except KeyboardInterrupt:
pass