##// END OF EJS Templates
profiling: updated profile-memory script to be more human friendly.
marcink -
r3857:73e6cefb default
parent child Browse files
Show More
@@ -1,135 +1,172 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Utility to gather certain statistics about a process.
23 23
24 24 Used to generate data about the memory consumption of the vcsserver. It is
25 25 quite generic and should work for every process. Use the parameter `--help`
26 26 to see all options.
27 27
28 28 Example call::
29 29
30 30 python profile-mem.py --pid=89816 --ae --ae-key=YOUR_API_KEY
31 31
32 32 """
33 33
34 34
35 35 import argparse
36 36 import json
37 37 import sys
38 38 import time
39 39
40 40 import datetime
41 import requests
42 41 import psutil
43 42
44 43 import logging
45 44 import socket
46 logging.basicConfig(level=logging.DEBUG)
47
48 45
49 def profile():
50 config = parse_options()
51 try:
52 process = psutil.Process(config.pid)
53 except psutil.NoSuchProcess:
54 print("Process {pid} does not exist!".format(pid=config.pid))
55 sys.exit(1)
46 from webhelpers.number import format_byte_size
56 47
57 while True:
58 stats = process_stats(process)
59 dump_stats(stats)
60 if config.appenlight:
61 client = AppenlightClient(
62 url=config.appenlight_url,
63 api_key=config.appenlight_api_key)
64 client.dump_stats(stats)
65 time.sleep(config.interval)
48 logging.basicConfig(level=logging.DEBUG)
66 49
67 50
68 51 def parse_options():
69 52 parser = argparse.ArgumentParser(
70 53 description=__doc__)
71 54 parser.add_argument(
72 55 '--pid', required=True, type=int,
73 56 help="Process ID to monitor.")
74 57 parser.add_argument(
58 '--human', action='store_true',
59 help="Show Human numbers")
60 parser.add_argument(
75 61 '--interval', '-i', type=float, default=5,
76 62 help="Interval in secods.")
77 63 parser.add_argument(
78 64 '--appenlight', '--ae', action='store_true')
79 65 parser.add_argument(
80 66 '--appenlight-url', '--ae-url',
81 67 default='https://ae.rhodecode.com/api/logs',
82 68 help='URL of the Appenlight API endpoint, defaults to "%(default)s".')
83 69 parser.add_argument(
84 70 '--appenlight-api-key', '--ae-key',
85 71 help='API key to use when sending data to appenlight. This has to be '
86 72 'set if Appenlight is enabled.')
87 73 return parser.parse_args()
88 74
89 75
90 def process_stats(process):
76 def profile():
77 config = parse_options()
78 try:
79 process = psutil.Process(config.pid)
80 except psutil.NoSuchProcess:
81 print("Process {pid} does not exist!".format(pid=config.pid))
82 sys.exit(1)
83
84 prev_stats = None
85 while True:
86 stats = process_stats(process, prev_stats)
87 prev_stats = stats
88 dump_stats(stats, human=config.human)
89
90 if config.appenlight:
91 client = AppenlightClient(
92 url=config.appenlight_url,
93 api_key=config.appenlight_api_key)
94 client.dump_stats(stats)
95 time.sleep(config.interval)
96
97
98 def process_stats(process, prev_stats):
91 99 mem = process.memory_info()
92 100 iso_now = datetime.datetime.utcnow().isoformat()
101 prev_rss_diff, prev_vms_diff = 0, 0
102 cur_rss = mem.rss
103 cur_vms = mem.vms
104
105 if prev_stats:
106 prev_rss_diff = cur_rss - prev_stats[0]['tags'][0][1]
107 prev_vms_diff = cur_vms - prev_stats[0]['tags'][1][1]
108
93 109 stats = [
94 110 {'message': 'Memory stats of process {pid}'.format(pid=process.pid),
95 111 'namespace': 'process.{pid}'.format(pid=process.pid),
96 112 'server': socket.getfqdn(socket.gethostname()),
97 113 'tags': [
98 ['rss', mem.rss],
99 ['vms', mem.vms]],
114 ['rss', cur_rss],
115 ['vms', cur_vms]
116 ],
117 'diff': [
118 ['rss', prev_rss_diff],
119 ['vms', prev_vms_diff]
120 ],
100 121 'date': iso_now,
101 122 },
102 123 ]
103 124 return stats
104 125
105 126
106 def dump_stats(stats):
127 def dump_stats(stats, human=False):
107 128 for sample in stats:
108 print(json.dumps(sample))
129 if human:
130 diff = stats[0]['diff'][0][1]
131 if diff < 0:
132 diff = '-' + format_byte_size(abs(diff), binary=True)
133 elif diff > 0:
134 diff = '+' + format_byte_size(diff, binary=True)
135 else:
136 diff = ' ' + format_byte_size(diff, binary=True)
137
138 print('Sample:{message} RSS:{rss} RSS_DIFF:{rss_diff}'.format(
139 message=stats[0]['message'],
140 rss=format_byte_size(stats[0]['tags'][0][1], binary=True),
141 rss_diff=diff,
142 ))
143 else:
144 print(json.dumps(sample))
109 145
110 146
111 class AppenlightClient():
147 class AppenlightClient(object):
112 148
113 149 url_template = '{url}?protocol_version=0.5'
114 150
115 151 def __init__(self, url, api_key):
116 152 self.url = self.url_template.format(url=url)
117 153 self.api_key = api_key
118 154
119 155 def dump_stats(self, stats):
156 import requests
120 157 response = requests.post(
121 158 self.url,
122 159 headers={
123 160 'X-appenlight-api-key': self.api_key},
124 161 data=json.dumps(stats))
125 162 if not response.status_code == 200:
126 163 logging.error(
127 164 'Sending to appenlight failed\n%s\n%s',
128 165 response.headers, response.text)
129 166
130 167
131 168 if __name__ == '__main__':
132 169 try:
133 170 profile()
134 171 except KeyboardInterrupt:
135 172 pass
General Comments 0
You need to be logged in to leave comments. Login now