##// END OF EJS Templates
processes: show how many workers we have set from configuration.
marcink -
r3155:82f2bc2a default
parent child Browse files
Show More
@@ -1,171 +1,183 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2018 RhodeCode GmbH
3 # Copyright (C) 2016-2018 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 import logging
21 import logging
22
22
23 import psutil
23 import psutil
24 import signal
24 import signal
25 from pyramid.view import view_config
25 from pyramid.view import view_config
26
26
27 from rhodecode.apps._base import BaseAppView
27 from rhodecode.apps._base import BaseAppView
28 from rhodecode.apps.admin.navigation import navigation_list
28 from rhodecode.apps.admin.navigation import navigation_list
29 from rhodecode.lib import system_info
29 from rhodecode.lib.auth import (
30 from rhodecode.lib.auth import (
30 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
31 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
31 from rhodecode.lib.utils2 import safe_int, StrictAttributeDict
32 from rhodecode.lib.utils2 import safe_int, StrictAttributeDict
32
33
33 log = logging.getLogger(__name__)
34 log = logging.getLogger(__name__)
34
35
35
36
36 class AdminProcessManagementView(BaseAppView):
37 class AdminProcessManagementView(BaseAppView):
37 def load_default_context(self):
38 def load_default_context(self):
38 c = self._get_local_tmpl_context()
39 c = self._get_local_tmpl_context()
39 return c
40 return c
40
41
41 def _format_proc(self, proc, with_children=False):
42 def _format_proc(self, proc, with_children=False):
42 try:
43 try:
43 mem = proc.memory_info()
44 mem = proc.memory_info()
44 proc_formatted = StrictAttributeDict({
45 proc_formatted = StrictAttributeDict({
45 'pid': proc.pid,
46 'pid': proc.pid,
46 'name': proc.name(),
47 'name': proc.name(),
47 'mem_rss': mem.rss,
48 'mem_rss': mem.rss,
48 'mem_vms': mem.vms,
49 'mem_vms': mem.vms,
49 'cpu_percent': proc.cpu_percent(),
50 'cpu_percent': proc.cpu_percent(),
50 'create_time': proc.create_time(),
51 'create_time': proc.create_time(),
51 'cmd': ' '.join(proc.cmdline()),
52 'cmd': ' '.join(proc.cmdline()),
52 })
53 })
53
54
54 if with_children:
55 if with_children:
55 proc_formatted.update({
56 proc_formatted.update({
56 'children': [self._format_proc(x)
57 'children': [self._format_proc(x)
57 for x in proc.children(recursive=True)]
58 for x in proc.children(recursive=True)]
58 })
59 })
59 except Exception:
60 except Exception:
60 log.exception('Failed to load proc')
61 log.exception('Failed to load proc')
61 proc_formatted = None
62 proc_formatted = None
62 return proc_formatted
63 return proc_formatted
63
64
64 def get_processes(self):
65 def get_processes(self):
65 proc_list = []
66 proc_list = []
66 for p in psutil.process_iter():
67 for p in psutil.process_iter():
67 if 'gunicorn' in p.name():
68 if 'gunicorn' in p.name():
68 proc = self._format_proc(p, with_children=True)
69 proc = self._format_proc(p, with_children=True)
69 if proc:
70 if proc:
70 proc_list.append(proc)
71 proc_list.append(proc)
71
72
72 return proc_list
73 return proc_list
73
74
75 def get_workers(self):
76 workers = None
77 try:
78 rc_config = system_info.rhodecode_config().value['config']
79 workers = rc_config['server:main'].get('workers')
80 except Exception:
81 pass
82
83 return workers or '?'
84
74 @LoginRequired()
85 @LoginRequired()
75 @HasPermissionAllDecorator('hg.admin')
86 @HasPermissionAllDecorator('hg.admin')
76 @view_config(
87 @view_config(
77 route_name='admin_settings_process_management', request_method='GET',
88 route_name='admin_settings_process_management', request_method='GET',
78 renderer='rhodecode:templates/admin/settings/settings.mako')
89 renderer='rhodecode:templates/admin/settings/settings.mako')
79 def process_management(self):
90 def process_management(self):
80 _ = self.request.translate
91 _ = self.request.translate
81 c = self.load_default_context()
92 c = self.load_default_context()
82
93
83 c.active = 'process_management'
94 c.active = 'process_management'
84 c.navlist = navigation_list(self.request)
95 c.navlist = navigation_list(self.request)
85 c.gunicorn_processes = self.get_processes()
96 c.gunicorn_processes = self.get_processes()
97 c.gunicorn_workers = self.get_workers()
86 return self._get_template_context(c)
98 return self._get_template_context(c)
87
99
88 @LoginRequired()
100 @LoginRequired()
89 @HasPermissionAllDecorator('hg.admin')
101 @HasPermissionAllDecorator('hg.admin')
90 @view_config(
102 @view_config(
91 route_name='admin_settings_process_management_data', request_method='GET',
103 route_name='admin_settings_process_management_data', request_method='GET',
92 renderer='rhodecode:templates/admin/settings/settings_process_management_data.mako')
104 renderer='rhodecode:templates/admin/settings/settings_process_management_data.mako')
93 def process_management_data(self):
105 def process_management_data(self):
94 _ = self.request.translate
106 _ = self.request.translate
95 c = self.load_default_context()
107 c = self.load_default_context()
96 c.gunicorn_processes = self.get_processes()
108 c.gunicorn_processes = self.get_processes()
97 return self._get_template_context(c)
109 return self._get_template_context(c)
98
110
99 @LoginRequired()
111 @LoginRequired()
100 @HasPermissionAllDecorator('hg.admin')
112 @HasPermissionAllDecorator('hg.admin')
101 @CSRFRequired()
113 @CSRFRequired()
102 @view_config(
114 @view_config(
103 route_name='admin_settings_process_management_signal',
115 route_name='admin_settings_process_management_signal',
104 request_method='POST', renderer='json_ext')
116 request_method='POST', renderer='json_ext')
105 def process_management_signal(self):
117 def process_management_signal(self):
106 pids = self.request.json.get('pids', [])
118 pids = self.request.json.get('pids', [])
107 result = []
119 result = []
108
120
109 def on_terminate(proc):
121 def on_terminate(proc):
110 msg = "process `PID:{}` terminated with exit code {}".format(
122 msg = "process `PID:{}` terminated with exit code {}".format(
111 proc.pid, proc.returncode or 0)
123 proc.pid, proc.returncode or 0)
112 result.append(msg)
124 result.append(msg)
113
125
114 procs = []
126 procs = []
115 for pid in pids:
127 for pid in pids:
116 pid = safe_int(pid)
128 pid = safe_int(pid)
117 if pid:
129 if pid:
118 try:
130 try:
119 proc = psutil.Process(pid)
131 proc = psutil.Process(pid)
120 except psutil.NoSuchProcess:
132 except psutil.NoSuchProcess:
121 continue
133 continue
122
134
123 children = proc.children(recursive=True)
135 children = proc.children(recursive=True)
124 if children:
136 if children:
125 log.warning('Wont kill Master Process')
137 log.warning('Wont kill Master Process')
126 else:
138 else:
127 procs.append(proc)
139 procs.append(proc)
128
140
129 for p in procs:
141 for p in procs:
130 try:
142 try:
131 p.terminate()
143 p.terminate()
132 except psutil.AccessDenied as e:
144 except psutil.AccessDenied as e:
133 log.warning('Access denied: {}'.format(e))
145 log.warning('Access denied: {}'.format(e))
134
146
135 gone, alive = psutil.wait_procs(procs, timeout=10, callback=on_terminate)
147 gone, alive = psutil.wait_procs(procs, timeout=10, callback=on_terminate)
136 for p in alive:
148 for p in alive:
137 try:
149 try:
138 p.kill()
150 p.kill()
139 except psutil.AccessDenied as e:
151 except psutil.AccessDenied as e:
140 log.warning('Access denied: {}'.format(e))
152 log.warning('Access denied: {}'.format(e))
141
153
142 return {'result': result}
154 return {'result': result}
143
155
144 @LoginRequired()
156 @LoginRequired()
145 @HasPermissionAllDecorator('hg.admin')
157 @HasPermissionAllDecorator('hg.admin')
146 @CSRFRequired()
158 @CSRFRequired()
147 @view_config(
159 @view_config(
148 route_name='admin_settings_process_management_master_signal',
160 route_name='admin_settings_process_management_master_signal',
149 request_method='POST', renderer='json_ext')
161 request_method='POST', renderer='json_ext')
150 def process_management_master_signal(self):
162 def process_management_master_signal(self):
151 pid_data = self.request.json.get('pid_data', {})
163 pid_data = self.request.json.get('pid_data', {})
152 pid = safe_int(pid_data['pid'])
164 pid = safe_int(pid_data['pid'])
153 action = pid_data['action']
165 action = pid_data['action']
154 if pid:
166 if pid:
155 try:
167 try:
156 proc = psutil.Process(pid)
168 proc = psutil.Process(pid)
157 except psutil.NoSuchProcess:
169 except psutil.NoSuchProcess:
158 return {'result': 'failure_no_such_process'}
170 return {'result': 'failure_no_such_process'}
159
171
160 children = proc.children(recursive=True)
172 children = proc.children(recursive=True)
161 if children:
173 if children:
162 # master process
174 # master process
163 if action == '+' and len(children) <= 20:
175 if action == '+' and len(children) <= 20:
164 proc.send_signal(signal.SIGTTIN)
176 proc.send_signal(signal.SIGTTIN)
165 elif action == '-' and len(children) >= 2:
177 elif action == '-' and len(children) >= 2:
166 proc.send_signal(signal.SIGTTOU)
178 proc.send_signal(signal.SIGTTOU)
167 else:
179 else:
168 return {'result': 'failure_wrong_action'}
180 return {'result': 'failure_wrong_action'}
169 return {'result': 'success'}
181 return {'result': 'success'}
170
182
171 return {'result': 'failure_not_master'}
183 return {'result': 'failure_not_master'}
@@ -1,145 +1,146 b''
1
1
2 <div id="update_notice" style="display: none; margin: -40px 0px 20px 0px">
2 <div id="update_notice" style="display: none; margin: -40px 0px 20px 0px">
3 <div>${_('Checking for updates...')}</div>
3 <div>${_('Checking for updates...')}</div>
4 </div>
4 </div>
5
5
6
6
7 <div class="panel panel-default">
7 <div class="panel panel-default">
8 <div class="panel-heading">
8 <div class="panel-heading">
9 <h3 class="panel-title">${_('Gunicorn process management')}</h3>
9 <h3 class="panel-title">${_('Gunicorn process management')}</h3>
10 <div class="pull-right">
10 <div class="pull-right">
11 <a id="autoRefreshEnable" href="#autoRefreshEnable" onclick="enableAutoRefresh(); return false">${_('start auto refresh')}</a>
11 <a id="autoRefreshEnable" href="#autoRefreshEnable" onclick="enableAutoRefresh(); return false">${_('start auto refresh')}</a>
12 <a id="autoRefreshDisable" href="#autoRefreshDisable" onclick="disableAutoRefresh(); return false" style="display: none">${_('stop auto refresh')}</a>
12 <a id="autoRefreshDisable" href="#autoRefreshDisable" onclick="disableAutoRefresh(); return false" style="display: none">${_('stop auto refresh')}</a>
13 </div>
13 </div>
14 </div>
14 </div>
15 <div class="panel-body" id="app">
15 <div class="panel-body" id="app">
16 <h3>List of Gunicorn processes on this machine</h3>
16 <h3>List of Gunicorn processes on this machine</h3>
17 <p>RhodeCode workers set: ${c.gunicorn_workers}</p>
17 <%
18 <%
18 def get_name(proc):
19 def get_name(proc):
19 cmd = ' '.join(proc.cmdline())
20 cmd = ' '.join(proc.cmdline())
20 if 'vcsserver.ini' in cmd:
21 if 'vcsserver.ini' in cmd:
21 return 'VCSServer'
22 return 'VCSServer'
22 elif 'rhodecode.ini' in cmd:
23 elif 'rhodecode.ini' in cmd:
23 return 'RhodeCode'
24 return 'RhodeCode'
24 return proc.name()
25 return proc.name()
25 %>
26 %>
26 <%include file='settings_process_management_data.mako'/>
27 <%include file='settings_process_management_data.mako'/>
27 </div>
28 </div>
28 </div>
29 </div>
29
30
30 <script>
31 <script>
31
32
32
33
33 restart = function(elem, pid) {
34 restart = function(elem, pid) {
34
35
35 if ($(elem).hasClass('disabled')){
36 if ($(elem).hasClass('disabled')){
36 return;
37 return;
37 }
38 }
38 $(elem).addClass('disabled');
39 $(elem).addClass('disabled');
39 $(elem).html('processing...');
40 $(elem).html('processing...');
40
41
41 $.ajax({
42 $.ajax({
42 url: pyroutes.url('admin_settings_process_management_signal'),
43 url: pyroutes.url('admin_settings_process_management_signal'),
43 headers: {
44 headers: {
44 "X-CSRF-Token": CSRF_TOKEN,
45 "X-CSRF-Token": CSRF_TOKEN,
45 },
46 },
46 data: JSON.stringify({'pids': [pid]}),
47 data: JSON.stringify({'pids': [pid]}),
47 dataType: 'json',
48 dataType: 'json',
48 type: 'POST',
49 type: 'POST',
49 contentType: "application/json; charset=utf-8",
50 contentType: "application/json; charset=utf-8",
50 success: function (data) {
51 success: function (data) {
51 $(elem).html(data.result);
52 $(elem).html(data.result);
52 $(elem).removeClass('disabled');
53 $(elem).removeClass('disabled');
53 },
54 },
54 failure: function (data) {
55 failure: function (data) {
55 $(elem).text('FAILED TO LOAD RESULT');
56 $(elem).text('FAILED TO LOAD RESULT');
56 $(elem).removeClass('disabled');
57 $(elem).removeClass('disabled');
57 },
58 },
58 error: function (data) {
59 error: function (data) {
59 $(elem).text('FAILED TO LOAD RESULT');
60 $(elem).text('FAILED TO LOAD RESULT');
60 $(elem).removeClass('disabled');
61 $(elem).removeClass('disabled');
61 }
62 }
62 })
63 })
63 };
64 };
64
65
65 var intervalID = null;
66 var intervalID = null;
66 var currentRequest = null;
67 var currentRequest = null;
67
68
68 autoRefresh = function(value) {
69 autoRefresh = function(value) {
69 var url = pyroutes.url('admin_settings_process_management_data');
70 var url = pyroutes.url('admin_settings_process_management_data');
70 var loadData = function() {
71 var loadData = function() {
71 currentRequest = $.get(url)
72 currentRequest = $.get(url)
72 .done(function(data) {
73 .done(function(data) {
73
74
74 if(data.indexOf('id="processTimeStamp"') === -1){
75 if(data.indexOf('id="processTimeStamp"') === -1){
75 clearInterval(intervalID);
76 clearInterval(intervalID);
76 $('#procList').html('ERROR LOADING DATA. PLEASE REFRESH THE PAGE');
77 $('#procList').html('ERROR LOADING DATA. PLEASE REFRESH THE PAGE');
77 return
78 return
78 }
79 }
79
80
80 currentRequest = null;
81 currentRequest = null;
81 $('#procList').html(data);
82 $('#procList').html(data);
82 timeagoActivate();
83 timeagoActivate();
83 var beat = function(doCallback) {
84 var beat = function(doCallback) {
84 var callback = function () {};
85 var callback = function () {};
85 if (doCallback){
86 if (doCallback){
86 var callback = function () {beat(false)};
87 var callback = function () {beat(false)};
87 }
88 }
88 $('#processTimeStamp').animate({
89 $('#processTimeStamp').animate({
89 opacity: $('#processTimeStamp').css('opacity') == '1' ? '0.3' : '1'
90 opacity: $('#processTimeStamp').css('opacity') == '1' ? '0.3' : '1'
90 }, 500, callback);
91 }, 500, callback);
91 };
92 };
92 beat(true)
93 beat(true)
93 });
94 });
94 };
95 };
95
96
96 if (value) {
97 if (value) {
97 intervalID = setInterval(loadData, 5000);
98 intervalID = setInterval(loadData, 5000);
98 } else {
99 } else {
99 clearInterval(intervalID);
100 clearInterval(intervalID);
100 }
101 }
101 };
102 };
102
103
103 enableAutoRefresh = function() {
104 enableAutoRefresh = function() {
104 $('#autoRefreshEnable').hide();
105 $('#autoRefreshEnable').hide();
105 $('#autoRefreshDisable').show();
106 $('#autoRefreshDisable').show();
106 autoRefresh(true)
107 autoRefresh(true)
107 };
108 };
108
109
109 disableAutoRefresh = function() {
110 disableAutoRefresh = function() {
110 $('#autoRefreshEnable').show();
111 $('#autoRefreshEnable').show();
111 $('#autoRefreshDisable').hide();
112 $('#autoRefreshDisable').hide();
112 autoRefresh(false)
113 autoRefresh(false)
113 };
114 };
114
115
115 masterAction = function(pid, action) {
116 masterAction = function(pid, action) {
116 $.ajax({
117 $.ajax({
117 url: pyroutes.url('admin_settings_process_management_master_signal'),
118 url: pyroutes.url('admin_settings_process_management_master_signal'),
118 headers: {
119 headers: {
119 "X-CSRF-Token": CSRF_TOKEN,
120 "X-CSRF-Token": CSRF_TOKEN,
120 },
121 },
121 data: JSON.stringify({'pid_data': {'pid': pid, 'action': action}}),
122 data: JSON.stringify({'pid_data': {'pid': pid, 'action': action}}),
122 dataType: 'json',
123 dataType: 'json',
123 type: 'POST',
124 type: 'POST',
124 contentType: "application/json; charset=utf-8",
125 contentType: "application/json; charset=utf-8",
125 success: function (data) {
126 success: function (data) {
126
127
127 },
128 },
128 failure: function (data) {
129 failure: function (data) {
129
130
130 },
131 },
131 error: function (data) {
132 error: function (data) {
132
133
133 }
134 }
134 })
135 })
135 };
136 };
136
137
137 addWorker = function(pid) {
138 addWorker = function(pid) {
138 masterAction(pid, '+');
139 masterAction(pid, '+');
139 };
140 };
140
141
141 removeWorker = function(pid) {
142 removeWorker = function(pid) {
142 masterAction(pid, '-');
143 masterAction(pid, '-');
143 };
144 };
144
145
145 </script>
146 </script>
General Comments 0
You need to be logged in to leave comments. Login now