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