##// END OF EJS Templates
feat(system-info): properly report storage stats after diskcache removal
super-admin -
r5421:0968de67 default
parent child Browse files
Show More
@@ -1,237 +1,238 b''
1 1
2 2
3 3 # Copyright (C) 2016-2023 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 import logging
22 22 import urllib.request
23 23 import urllib.error
24 24 import urllib.parse
25 25 import os
26 26
27 27 import rhodecode
28 28 from rhodecode.apps._base import BaseAppView
29 29 from rhodecode.apps._base.navigation import navigation_list
30 30 from rhodecode.lib import helpers as h
31 31 from rhodecode.lib.auth import (LoginRequired, HasPermissionAllDecorator)
32 32 from rhodecode.lib.utils2 import str2bool
33 33 from rhodecode.lib import system_info
34 34 from rhodecode.model.update import UpdateModel
35 35
36 36 log = logging.getLogger(__name__)
37 37
38 38
39 39 class AdminSystemInfoSettingsView(BaseAppView):
40 40 def load_default_context(self):
41 41 c = self._get_local_tmpl_context()
42 42 return c
43 43
44 44 def get_env_data(self):
45 45 black_list = [
46 46 'NIX_LDFLAGS',
47 47 'NIX_CFLAGS_COMPILE',
48 48 'propagatedBuildInputs',
49 49 'propagatedNativeBuildInputs',
50 50 'postInstall',
51 51 'buildInputs',
52 52 'buildPhase',
53 53 'preShellHook',
54 54 'preShellHook',
55 55 'preCheck',
56 56 'preBuild',
57 57 'postShellHook',
58 58 'postFixup',
59 59 'postCheck',
60 60 'nativeBuildInputs',
61 61 'installPhase',
62 62 'installCheckPhase',
63 63 'checkPhase',
64 64 'configurePhase',
65 65 'shellHook'
66 66 ]
67 67 secret_list = [
68 68 'RHODECODE_USER_PASS'
69 69 ]
70 70
71 71 for k, v in sorted(os.environ.items()):
72 72 if k in black_list:
73 73 continue
74 74 if k in secret_list:
75 75 v = '*****'
76 76 yield k, v
77 77
78 78 @LoginRequired()
79 79 @HasPermissionAllDecorator('hg.admin')
80 80 def settings_system_info(self):
81 81 _ = self.request.translate
82 82 c = self.load_default_context()
83 83
84 84 c.active = 'system'
85 85 c.navlist = navigation_list(self.request)
86 86
87 87 # TODO(marcink), figure out how to allow only selected users to do this
88 88 c.allowed_to_snapshot = self._rhodecode_user.admin
89 89
90 90 snapshot = str2bool(self.request.params.get('snapshot'))
91 91
92 92 c.rhodecode_update_url = UpdateModel().get_update_url()
93 93 c.env_data = self.get_env_data()
94 94 server_info = system_info.get_system_info(self.request.environ)
95 95
96 96 for key, val in server_info.items():
97 97 setattr(c, key, val)
98 98
99 99 def val(name, subkey='human_value'):
100 100 return server_info[name][subkey]
101 101
102 102 def state(name):
103 103 return server_info[name]['state']
104 104
105 105 def val2(name):
106 106 val = server_info[name]['human_value']
107 107 state = server_info[name]['state']
108 108 return val, state
109 109
110 110 update_info_msg = _('Note: please make sure this server can '
111 111 'access `${url}` for the update link to work',
112 112 mapping=dict(url=c.rhodecode_update_url))
113 113 version = UpdateModel().get_stored_version()
114 114 is_outdated = UpdateModel().is_outdated(
115 115 rhodecode.__version__, version)
116 116 update_state = {
117 117 'type': 'warning',
118 118 'message': 'New version available: {}'.format(version)
119 119 } \
120 120 if is_outdated else {}
121 121 c.data_items = [
122 122 # update info
123 123 (_('Update info'), h.literal(
124 124 '<span class="link" id="check_for_update" >%s.</span>' % (
125 125 _('Check for updates')) +
126 126 '<br/> <span >%s.</span>' % (update_info_msg)
127 127 ), ''),
128 128
129 129 # RhodeCode specific
130 130 (_('RhodeCode Version'), val('rhodecode_app')['text'], state('rhodecode_app')),
131 131 (_('Latest version'), version, update_state),
132 132 (_('RhodeCode Base URL'), val('rhodecode_config')['config'].get('app.base_url'), state('rhodecode_config')),
133 133 (_('RhodeCode Server IP'), val('server')['server_ip'], state('server')),
134 134 (_('RhodeCode Server ID'), val('server')['server_id'], state('server')),
135 135 (_('RhodeCode Configuration'), val('rhodecode_config')['path'], state('rhodecode_config')),
136 136 (_('RhodeCode Certificate'), val('rhodecode_config')['cert_path'], state('rhodecode_config')),
137 137 (_('Workers'), val('rhodecode_config')['config']['server:main'].get('workers', '?'), state('rhodecode_config')),
138 138 (_('Worker Type'), val('rhodecode_config')['config']['server:main'].get('worker_class', 'sync'), state('rhodecode_config')),
139 139 ('', '', ''), # spacer
140 140
141 141 # Database
142 142 (_('Database'), val('database')['url'], state('database')),
143 143 (_('Database version'), val('database')['version'], state('database')),
144 144 ('', '', ''), # spacer
145 145
146 146 # Platform/Python
147 147 (_('Platform'), val('platform')['name'], state('platform')),
148 148 (_('Platform UUID'), val('platform')['uuid'], state('platform')),
149 149 (_('Lang'), val('locale'), state('locale')),
150 150 (_('Python version'), val('python')['version'], state('python')),
151 151 (_('Python path'), val('python')['executable'], state('python')),
152 152 ('', '', ''), # spacer
153 153
154 154 # Systems stats
155 155 (_('CPU'), val('cpu')['text'], state('cpu')),
156 156 (_('Load'), val('load')['text'], state('load')),
157 157 (_('Memory'), val('memory')['text'], state('memory')),
158 158 (_('Uptime'), val('uptime')['text'], state('uptime')),
159 159 ('', '', ''), # spacer
160 160
161 161 # ulimit
162 162 (_('Ulimit'), val('ulimit')['text'], state('ulimit')),
163 163
164 164 # Repo storage
165 165 (_('Storage location'), val('storage')['path'], state('storage')),
166 166 (_('Storage info'), val('storage')['text'], state('storage')),
167 167 (_('Storage inodes'), val('storage_inodes')['text'], state('storage_inodes')),
168 168
169 169 (_('Gist storage location'), val('storage_gist')['path'], state('storage_gist')),
170 170 (_('Gist storage info'), val('storage_gist')['text'], state('storage_gist')),
171 171
172 (_('Archive cache storage type'), val('storage_archive')['type'], state('storage_archive')),
172 173 (_('Archive cache storage location'), val('storage_archive')['path'], state('storage_archive')),
173 174 (_('Archive cache info'), val('storage_archive')['text'], state('storage_archive')),
174 175
175 176 (_('Temp storage location'), val('storage_temp')['path'], state('storage_temp')),
176 177 (_('Temp storage info'), val('storage_temp')['text'], state('storage_temp')),
177 178
178 179 (_('Search info'), val('search')['text'], state('search')),
179 180 (_('Search location'), val('search')['location'], state('search')),
180 181 ('', '', ''), # spacer
181 182
182 183 # VCS specific
183 184 (_('VCS Backends'), val('vcs_backends'), state('vcs_backends')),
184 185 (_('VCS Server'), val('vcs_server')['text'], state('vcs_server')),
185 186 (_('GIT'), val('git'), state('git')),
186 187 (_('HG'), val('hg'), state('hg')),
187 188 (_('SVN'), val('svn'), state('svn')),
188 189
189 190 ]
190 191
191 192 c.vcsserver_data_items = [
192 193 (k, v) for k,v in (val('vcs_server_config') or {}).items()
193 194 ]
194 195
195 196 if snapshot:
196 197 if c.allowed_to_snapshot:
197 198 c.data_items.pop(0) # remove server info
198 199 self.request.override_renderer = 'admin/settings/settings_system_snapshot.mako'
199 200 else:
200 201 h.flash('You are not allowed to do this', category='warning')
201 202 return self._get_template_context(c)
202 203
203 204 @LoginRequired()
204 205 @HasPermissionAllDecorator('hg.admin')
205 206 def settings_system_info_check_update(self):
206 207 _ = self.request.translate
207 208 c = self.load_default_context()
208 209
209 210 update_url = UpdateModel().get_update_url()
210 211
211 212 def _err(s):
212 213 return '<div style="color:#ff8888; padding:4px 0px">{}</div>'.format(s)
213 214 try:
214 215 data = UpdateModel().get_update_data(update_url)
215 216 except urllib.error.URLError as e:
216 217 log.exception("Exception contacting upgrade server")
217 218 self.request.override_renderer = 'string'
218 219 return _err('Failed to contact upgrade server: %r' % e)
219 220 except ValueError as e:
220 221 log.exception("Bad data sent from update server")
221 222 self.request.override_renderer = 'string'
222 223 return _err('Bad data sent from update server')
223 224
224 225 latest = data['versions'][0]
225 226
226 227 c.update_url = update_url
227 228 c.latest_data = latest
228 229 c.latest_ver = latest['version']
229 230 c.cur_ver = rhodecode.__version__
230 231 c.should_upgrade = False
231 232
232 233 is_oudated = UpdateModel().is_outdated(c.cur_ver, c.latest_ver)
233 234 if is_oudated:
234 235 c.should_upgrade = True
235 236 c.important_notices = latest['general']
236 237 UpdateModel().store_version(latest['version'])
237 238 return self._get_template_context(c)
@@ -1,30 +1,71 b''
1 1 # Copyright (C) 2015-2024 RhodeCode GmbH
2 2 #
3 3 # This program is free software: you can redistribute it and/or modify
4 4 # it under the terms of the GNU Affero General Public License, version 3
5 5 # (only), as published by the Free Software Foundation.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU Affero General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 #
15 15 # This program is dual-licensed. If you wish to learn more about the
16 16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 import os
18 19
19 20
20 21 class ArchiveCacheLock(Exception):
21 22 pass
22 23
23 24
24 25 def archive_iterator(_reader, block_size: int = 4096 * 512):
25 26 # 4096 * 64 = 64KB
26 27 while 1:
27 28 data = _reader.read(block_size)
28 29 if not data:
29 30 break
30 31 yield data
32
33
34 def get_directory_statistics(start_path):
35 """
36 total_files, total_size, directory_stats = get_directory_statistics(start_path)
37
38 print(f"Directory statistics for: {start_path}\n")
39 print(f"Total files: {total_files}")
40 print(f"Total size: {format_size(total_size)}\n")
41
42 :param start_path:
43 :return:
44 """
45
46 total_files = 0
47 total_size = 0
48 directory_stats = {}
49
50 for dir_path, dir_names, file_names in os.walk(start_path):
51 dir_size = 0
52 file_count = len(file_names)
53
54 for file in file_names:
55 filepath = os.path.join(dir_path, file)
56 file_size = os.path.getsize(filepath)
57 dir_size += file_size
58
59 directory_stats[dir_path] = {'file_count': file_count, 'size': dir_size}
60 total_files += file_count
61 total_size += dir_size
62
63 return total_files, total_size, directory_stats
64
65
66 def format_size(size):
67 # Convert size in bytes to a human-readable format (e.g., KB, MB, GB)
68 for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
69 if size < 1024:
70 return f"{size:.2f} {unit}"
71 size /= 1024
@@ -1,851 +1,834 b''
1 1 # Copyright (C) 2017-2023 RhodeCode GmbH
2 2 #
3 3 # This program is free software: you can redistribute it and/or modify
4 4 # it under the terms of the GNU Affero General Public License, version 3
5 5 # (only), as published by the Free Software Foundation.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU Affero General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 #
15 15 # This program is dual-licensed. If you wish to learn more about the
16 16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19
20 20 import os
21 21 import sys
22 22 import time
23 23 import platform
24 24 import collections
25 25 import psutil
26 26 from functools import wraps
27 27
28 28 import pkg_resources
29 29 import logging
30 30 import resource
31 31
32 32 import configparser
33 33
34 34 from rc_license.models import LicenseModel
35 35 from rhodecode.lib.str_utils import safe_str
36 36
37 37 log = logging.getLogger(__name__)
38 38
39 39
40 40 _NA = 'NOT AVAILABLE'
41 41 _NA_FLOAT = 0.0
42 42
43 43 STATE_OK = 'ok'
44 44 STATE_ERR = 'error'
45 45 STATE_WARN = 'warning'
46 46
47 47 STATE_OK_DEFAULT = {'message': '', 'type': STATE_OK}
48 48
49 49
50 50 registered_helpers = {}
51 51
52 52
53 53 def register_sysinfo(func):
54 54 """
55 55 @register_helper
56 56 def db_check():
57 57 pass
58 58
59 59 db_check == registered_helpers['db_check']
60 60 """
61 61 global registered_helpers
62 62 registered_helpers[func.__name__] = func
63 63
64 64 @wraps(func)
65 65 def _wrapper(*args, **kwargs):
66 66 return func(*args, **kwargs)
67 67 return _wrapper
68 68
69 69
70 70 # HELPERS
71 71 def percentage(part: (int, float), whole: (int, float)):
72 72 whole = float(whole)
73 73 if whole > 0:
74 74 return round(100 * float(part) / whole, 1)
75 75 return 0.0
76 76
77 77
78 78 def get_storage_size(storage_path):
79 79 sizes = []
80 80 for file_ in os.listdir(storage_path):
81 81 storage_file = os.path.join(storage_path, file_)
82 82 if os.path.isfile(storage_file):
83 83 try:
84 84 sizes.append(os.path.getsize(storage_file))
85 85 except OSError:
86 86 log.exception('Failed to get size of storage file %s', storage_file)
87 87 pass
88 88
89 89 return sum(sizes)
90 90
91 91
92 92 def get_resource(resource_type):
93 93 try:
94 94 return resource.getrlimit(resource_type)
95 95 except Exception:
96 96 return 'NOT_SUPPORTED'
97 97
98 98
99 99 def get_cert_path(ini_path):
100 100 default = '/etc/ssl/certs/ca-certificates.crt'
101 101 control_ca_bundle = os.path.join(
102 102 os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(ini_path)))),
103 '.rccontrol-profile/etc/ca-bundle.crt')
103 '/etc/ssl/certs/ca-certificates.crt')
104 104 if os.path.isfile(control_ca_bundle):
105 105 default = control_ca_bundle
106 106
107 107 return default
108 108
109 109
110 110 class SysInfoRes(object):
111 111 def __init__(self, value, state=None, human_value=None):
112 112 self.value = value
113 113 self.state = state or STATE_OK_DEFAULT
114 114 self.human_value = human_value or value
115 115
116 116 def __json__(self):
117 117 return {
118 118 'value': self.value,
119 119 'state': self.state,
120 120 'human_value': self.human_value,
121 121 }
122 122
123 123 def get_value(self):
124 124 return self.__json__()
125 125
126 126 def __str__(self):
127 127 return f'<SysInfoRes({self.__json__()})>'
128 128
129 129
130 130 class SysInfo(object):
131 131
132 132 def __init__(self, func_name, **kwargs):
133 133 self.function_name = func_name
134 134 self.value = _NA
135 135 self.state = None
136 136 self.kwargs = kwargs or {}
137 137
138 138 def __call__(self):
139 139 computed = self.compute(**self.kwargs)
140 140 if not isinstance(computed, SysInfoRes):
141 141 raise ValueError(
142 142 'computed value for {} is not instance of '
143 143 '{}, got {} instead'.format(
144 144 self.function_name, SysInfoRes, type(computed)))
145 145 return computed.__json__()
146 146
147 147 def __str__(self):
148 148 return f'<SysInfo({self.function_name})>'
149 149
150 150 def compute(self, **kwargs):
151 151 return self.function_name(**kwargs)
152 152
153 153
154 154 # SysInfo functions
155 155 @register_sysinfo
156 156 def python_info():
157 157 value = dict(version=f'{platform.python_version()}:{platform.python_implementation()}',
158 158 executable=sys.executable)
159 159 return SysInfoRes(value=value)
160 160
161 161
162 162 @register_sysinfo
163 163 def py_modules():
164 164 mods = dict([(p.project_name, {'version': p.version, 'location': p.location})
165 165 for p in pkg_resources.working_set])
166 166
167 167 value = sorted(mods.items(), key=lambda k: k[0].lower())
168 168 return SysInfoRes(value=value)
169 169
170 170
171 171 @register_sysinfo
172 172 def platform_type():
173 173 from rhodecode.lib.utils import generate_platform_uuid
174 174
175 175 value = dict(
176 176 name=safe_str(platform.platform()),
177 177 uuid=generate_platform_uuid()
178 178 )
179 179 return SysInfoRes(value=value)
180 180
181 181
182 182 @register_sysinfo
183 183 def locale_info():
184 184 import locale
185 185
186 186 def safe_get_locale(locale_name):
187 187 try:
188 188 locale.getlocale(locale_name)
189 189 except TypeError:
190 190 return f'FAILED_LOCALE_GET:{locale_name}'
191 191
192 192 value = dict(
193 193 locale_default=locale.getlocale(),
194 194 locale_lc_all=safe_get_locale(locale.LC_ALL),
195 195 locale_lc_ctype=safe_get_locale(locale.LC_CTYPE),
196 196 lang_env=os.environ.get('LANG'),
197 197 lc_all_env=os.environ.get('LC_ALL'),
198 198 local_archive_env=os.environ.get('LOCALE_ARCHIVE'),
199 199 )
200 200 human_value = \
201 201 f"LANG: {value['lang_env']}, \
202 202 locale LC_ALL: {value['locale_lc_all']}, \
203 203 locale LC_CTYPE: {value['locale_lc_ctype']}, \
204 204 Default locales: {value['locale_default']}"
205 205
206 206 return SysInfoRes(value=value, human_value=human_value)
207 207
208 208
209 209 @register_sysinfo
210 210 def ulimit_info():
211 211 data = collections.OrderedDict([
212 212 ('cpu time (seconds)', get_resource(resource.RLIMIT_CPU)),
213 213 ('file size', get_resource(resource.RLIMIT_FSIZE)),
214 214 ('stack size', get_resource(resource.RLIMIT_STACK)),
215 215 ('core file size', get_resource(resource.RLIMIT_CORE)),
216 216 ('address space size', get_resource(resource.RLIMIT_AS)),
217 217 ('locked in mem size', get_resource(resource.RLIMIT_MEMLOCK)),
218 218 ('heap size', get_resource(resource.RLIMIT_DATA)),
219 219 ('rss size', get_resource(resource.RLIMIT_RSS)),
220 220 ('number of processes', get_resource(resource.RLIMIT_NPROC)),
221 221 ('open files', get_resource(resource.RLIMIT_NOFILE)),
222 222 ])
223 223
224 224 text = ', '.join(f'{k}:{v}' for k, v in data.items())
225 225
226 226 value = {
227 227 'limits': data,
228 228 'text': text,
229 229 }
230 230 return SysInfoRes(value=value)
231 231
232 232
233 233 @register_sysinfo
234 234 def uptime():
235 235 from rhodecode.lib.helpers import age, time_to_datetime
236 236 from rhodecode.translation import TranslationString
237 237
238 238 value = dict(boot_time=0, uptime=0, text='')
239 239 state = STATE_OK_DEFAULT
240 240
241 241 boot_time = psutil.boot_time()
242 242 value['boot_time'] = boot_time
243 243 value['uptime'] = time.time() - boot_time
244 244
245 245 date_or_age = age(time_to_datetime(boot_time))
246 246 if isinstance(date_or_age, TranslationString):
247 247 date_or_age = date_or_age.interpolate()
248 248
249 249 human_value = value.copy()
250 250 human_value['boot_time'] = time_to_datetime(boot_time)
251 251 human_value['uptime'] = age(time_to_datetime(boot_time), show_suffix=False)
252 252
253 253 human_value['text'] = f'Server started {date_or_age}'
254 254 return SysInfoRes(value=value, human_value=human_value)
255 255
256 256
257 257 @register_sysinfo
258 258 def memory():
259 259 from rhodecode.lib.helpers import format_byte_size_binary
260 260 value = dict(available=0, used=0, used_real=0, cached=0, percent=0,
261 261 percent_used=0, free=0, inactive=0, active=0, shared=0,
262 262 total=0, buffers=0, text='')
263 263
264 264 state = STATE_OK_DEFAULT
265 265
266 266 value.update(dict(psutil.virtual_memory()._asdict()))
267 267 value['used_real'] = value['total'] - value['available']
268 268 value['percent_used'] = psutil._common.usage_percent(value['used_real'], value['total'], 1)
269 269
270 270 human_value = value.copy()
271 271 human_value['text'] = '{}/{}, {}% used'.format(
272 272 format_byte_size_binary(value['used_real']),
273 273 format_byte_size_binary(value['total']),
274 274 value['percent_used'])
275 275
276 276 keys = list(value.keys())[::]
277 277 keys.pop(keys.index('percent'))
278 278 keys.pop(keys.index('percent_used'))
279 279 keys.pop(keys.index('text'))
280 280 for k in keys:
281 281 human_value[k] = format_byte_size_binary(value[k])
282 282
283 283 if state['type'] == STATE_OK and value['percent_used'] > 90:
284 284 msg = 'Critical: your available RAM memory is very low.'
285 285 state = {'message': msg, 'type': STATE_ERR}
286 286
287 287 elif state['type'] == STATE_OK and value['percent_used'] > 70:
288 288 msg = 'Warning: your available RAM memory is running low.'
289 289 state = {'message': msg, 'type': STATE_WARN}
290 290
291 291 return SysInfoRes(value=value, state=state, human_value=human_value)
292 292
293 293
294 294 @register_sysinfo
295 295 def machine_load():
296 296 value = {'1_min': _NA_FLOAT, '5_min': _NA_FLOAT, '15_min': _NA_FLOAT, 'text': ''}
297 297 state = STATE_OK_DEFAULT
298 298
299 299 # load averages
300 300 if hasattr(psutil.os, 'getloadavg'):
301 301 value.update(dict(
302 302 list(zip(['1_min', '5_min', '15_min'], psutil.os.getloadavg()))
303 303 ))
304 304
305 305 human_value = value.copy()
306 306 human_value['text'] = '1min: {}, 5min: {}, 15min: {}'.format(
307 307 value['1_min'], value['5_min'], value['15_min'])
308 308
309 309 if state['type'] == STATE_OK and value['15_min'] > 5.0:
310 310 msg = 'Warning: your machine load is very high.'
311 311 state = {'message': msg, 'type': STATE_WARN}
312 312
313 313 return SysInfoRes(value=value, state=state, human_value=human_value)
314 314
315 315
316 316 @register_sysinfo
317 317 def cpu():
318 318 value = {'cpu': 0, 'cpu_count': 0, 'cpu_usage': []}
319 319 state = STATE_OK_DEFAULT
320 320
321 321 value['cpu'] = psutil.cpu_percent(0.5)
322 322 value['cpu_usage'] = psutil.cpu_percent(0.5, percpu=True)
323 323 value['cpu_count'] = psutil.cpu_count()
324 324
325 325 human_value = value.copy()
326 human_value['text'] = '{} cores at {} %'.format(value['cpu_count'], value['cpu'])
326 human_value['text'] = f'{value["cpu_count"]} cores at {value["cpu"]} %'
327 327
328 328 return SysInfoRes(value=value, state=state, human_value=human_value)
329 329
330 330
331 331 @register_sysinfo
332 332 def storage():
333 333 from rhodecode.lib.helpers import format_byte_size_binary
334 334 from rhodecode.lib.utils import get_rhodecode_repo_store_path
335 335 path = get_rhodecode_repo_store_path()
336 336
337 337 value = dict(percent=0, used=0, total=0, path=path, text='')
338 338 state = STATE_OK_DEFAULT
339 339
340 340 try:
341 341 value.update(dict(psutil.disk_usage(path)._asdict()))
342 342 except Exception as e:
343 343 log.exception('Failed to fetch disk info')
344 344 state = {'message': str(e), 'type': STATE_ERR}
345 345
346 346 human_value = value.copy()
347 347 human_value['used'] = format_byte_size_binary(value['used'])
348 348 human_value['total'] = format_byte_size_binary(value['total'])
349 349 human_value['text'] = "{}/{}, {}% used".format(
350 350 format_byte_size_binary(value['used']),
351 351 format_byte_size_binary(value['total']),
352 352 value['percent'])
353 353
354 354 if state['type'] == STATE_OK and value['percent'] > 90:
355 355 msg = 'Critical: your disk space is very low.'
356 356 state = {'message': msg, 'type': STATE_ERR}
357 357
358 358 elif state['type'] == STATE_OK and value['percent'] > 70:
359 359 msg = 'Warning: your disk space is running low.'
360 360 state = {'message': msg, 'type': STATE_WARN}
361 361
362 362 return SysInfoRes(value=value, state=state, human_value=human_value)
363 363
364 364
365 365 @register_sysinfo
366 366 def storage_inodes():
367 367 from rhodecode.lib.utils import get_rhodecode_repo_store_path
368 368 path = get_rhodecode_repo_store_path()
369 369
370 370 value = dict(percent=0.0, free=0, used=0, total=0, path=path, text='')
371 371 state = STATE_OK_DEFAULT
372 372
373 373 try:
374 374 i_stat = os.statvfs(path)
375 375 value['free'] = i_stat.f_ffree
376 376 value['used'] = i_stat.f_files-i_stat.f_favail
377 377 value['total'] = i_stat.f_files
378 378 value['percent'] = percentage(value['used'], value['total'])
379 379 except Exception as e:
380 380 log.exception('Failed to fetch disk inodes info')
381 381 state = {'message': str(e), 'type': STATE_ERR}
382 382
383 383 human_value = value.copy()
384 384 human_value['text'] = "{}/{}, {}% used".format(
385 385 value['used'], value['total'], value['percent'])
386 386
387 387 if state['type'] == STATE_OK and value['percent'] > 90:
388 388 msg = 'Critical: your disk free inodes are very low.'
389 389 state = {'message': msg, 'type': STATE_ERR}
390 390
391 391 elif state['type'] == STATE_OK and value['percent'] > 70:
392 392 msg = 'Warning: your disk free inodes are running low.'
393 393 state = {'message': msg, 'type': STATE_WARN}
394 394
395 395 return SysInfoRes(value=value, state=state, human_value=human_value)
396 396
397 397
398 398 @register_sysinfo
399 399 def storage_archives():
400 400 import rhodecode
401 from rhodecode.lib.utils import safe_str
402 401 from rhodecode.lib.helpers import format_byte_size_binary
402 from rhodecode.lib.rc_cache.archive_cache.utils import get_directory_statistics
403 403
404 404 storage_type = rhodecode.ConfigGet().get_str('archive_cache.backend.type')
405 405 storage_key = 'archive_cache.filesystem.store_dir'
406 406
407 407 default_msg = 'Archive cache storage is controlled by '\
408 408 f'{storage_key}=/path/to/cache option in the .ini file'
409 409 path = rhodecode.ConfigGet().get_str(storage_key, missing=default_msg)
410 410
411 value = dict(percent=0, used=0, total=0, items=0, path=path, text='')
411 value = dict(percent=0, used=0, total=0, items=0, path=path, text='', type=storage_type)
412 412 state = STATE_OK_DEFAULT
413 413 try:
414 414 if storage_type != 'filesystem':
415 415 # raise Exc to stop reporting on different type
416 416 raise ValueError('Storage type must be "filesystem"')
417 417
418 items_count = 0
419 used = 0
420 for root, dirs, files in os.walk(path):
421 if root == path:
422 items_count = len(dirs)
418 total_files, total_size, _directory_stats = get_directory_statistics(path)
423 419
424 for f in files:
425 try:
426 used += os.path.getsize(os.path.join(root, f))
427 except OSError:
428 pass
429 420 value.update({
430 421 'percent': 100,
431 'used': used,
432 'total': used,
433 'items': items_count
422 'used': total_size,
423 'total': total_size,
424 'items': total_files
434 425 })
435 426
436 427 except Exception as e:
437 428 log.exception('failed to fetch archive cache storage')
438 429 state = {'message': str(e), 'type': STATE_ERR}
439 430
440 431 human_value = value.copy()
441 432 human_value['used'] = format_byte_size_binary(value['used'])
442 433 human_value['total'] = format_byte_size_binary(value['total'])
443 434 human_value['text'] = "{} ({} items)".format(
444 435 human_value['used'], value['items'])
445 436
446 437 return SysInfoRes(value=value, state=state, human_value=human_value)
447 438
448 439
449 440 @register_sysinfo
450 441 def storage_gist():
451 442 from rhodecode.model.gist import GIST_STORE_LOC
452 443 from rhodecode.lib.utils import safe_str, get_rhodecode_repo_store_path
453 444 from rhodecode.lib.helpers import format_byte_size_binary
445 from rhodecode.lib.rc_cache.archive_cache.utils import get_directory_statistics
446
454 447 path = safe_str(os.path.join(
455 448 get_rhodecode_repo_store_path(), GIST_STORE_LOC))
456 449
457 450 # gist storage
458 451 value = dict(percent=0, used=0, total=0, items=0, path=path, text='')
459 452 state = STATE_OK_DEFAULT
460 453
461 454 try:
462 items_count = 0
463 used = 0
464 for root, dirs, files in os.walk(path):
465 if root == path:
466 items_count = len(dirs)
467
468 for f in files:
469 try:
470 used += os.path.getsize(os.path.join(root, f))
471 except OSError:
472 pass
455 total_files, total_size, _directory_stats = get_directory_statistics(path)
473 456 value.update({
474 457 'percent': 100,
475 'used': used,
476 'total': used,
477 'items': items_count
458 'used': total_size,
459 'total': total_size,
460 'items': total_files
478 461 })
479 462 except Exception as e:
480 463 log.exception('failed to fetch gist storage items')
481 464 state = {'message': str(e), 'type': STATE_ERR}
482 465
483 466 human_value = value.copy()
484 467 human_value['used'] = format_byte_size_binary(value['used'])
485 468 human_value['total'] = format_byte_size_binary(value['total'])
486 469 human_value['text'] = "{} ({} items)".format(
487 470 human_value['used'], value['items'])
488 471
489 472 return SysInfoRes(value=value, state=state, human_value=human_value)
490 473
491 474
492 475 @register_sysinfo
493 476 def storage_temp():
494 477 import tempfile
495 478 from rhodecode.lib.helpers import format_byte_size_binary
496 479
497 480 path = tempfile.gettempdir()
498 481 value = dict(percent=0, used=0, total=0, items=0, path=path, text='')
499 482 state = STATE_OK_DEFAULT
500 483
501 484 if not psutil:
502 485 return SysInfoRes(value=value, state=state)
503 486
504 487 try:
505 488 value.update(dict(psutil.disk_usage(path)._asdict()))
506 489 except Exception as e:
507 490 log.exception('Failed to fetch temp dir info')
508 491 state = {'message': str(e), 'type': STATE_ERR}
509 492
510 493 human_value = value.copy()
511 494 human_value['used'] = format_byte_size_binary(value['used'])
512 495 human_value['total'] = format_byte_size_binary(value['total'])
513 496 human_value['text'] = "{}/{}, {}% used".format(
514 497 format_byte_size_binary(value['used']),
515 498 format_byte_size_binary(value['total']),
516 499 value['percent'])
517 500
518 501 return SysInfoRes(value=value, state=state, human_value=human_value)
519 502
520 503
521 504 @register_sysinfo
522 505 def search_info():
523 506 import rhodecode
524 507 from rhodecode.lib.index import searcher_from_config
525 508
526 509 backend = rhodecode.CONFIG.get('search.module', '')
527 510 location = rhodecode.CONFIG.get('search.location', '')
528 511
529 512 try:
530 513 searcher = searcher_from_config(rhodecode.CONFIG)
531 514 searcher = searcher.__class__.__name__
532 515 except Exception:
533 516 searcher = None
534 517
535 518 value = dict(
536 519 backend=backend, searcher=searcher, location=location, text='')
537 520 state = STATE_OK_DEFAULT
538 521
539 522 human_value = value.copy()
540 523 human_value['text'] = "backend:`{}`".format(human_value['backend'])
541 524
542 525 return SysInfoRes(value=value, state=state, human_value=human_value)
543 526
544 527
545 528 @register_sysinfo
546 529 def git_info():
547 530 from rhodecode.lib.vcs.backends import git
548 531 state = STATE_OK_DEFAULT
549 532 value = human_value = ''
550 533 try:
551 534 value = git.discover_git_version(raise_on_exc=True)
552 535 human_value = f'version reported from VCSServer: {value}'
553 536 except Exception as e:
554 537 state = {'message': str(e), 'type': STATE_ERR}
555 538
556 539 return SysInfoRes(value=value, state=state, human_value=human_value)
557 540
558 541
559 542 @register_sysinfo
560 543 def hg_info():
561 544 from rhodecode.lib.vcs.backends import hg
562 545 state = STATE_OK_DEFAULT
563 546 value = human_value = ''
564 547 try:
565 548 value = hg.discover_hg_version(raise_on_exc=True)
566 549 human_value = f'version reported from VCSServer: {value}'
567 550 except Exception as e:
568 551 state = {'message': str(e), 'type': STATE_ERR}
569 552 return SysInfoRes(value=value, state=state, human_value=human_value)
570 553
571 554
572 555 @register_sysinfo
573 556 def svn_info():
574 557 from rhodecode.lib.vcs.backends import svn
575 558 state = STATE_OK_DEFAULT
576 559 value = human_value = ''
577 560 try:
578 561 value = svn.discover_svn_version(raise_on_exc=True)
579 562 human_value = f'version reported from VCSServer: {value}'
580 563 except Exception as e:
581 564 state = {'message': str(e), 'type': STATE_ERR}
582 565 return SysInfoRes(value=value, state=state, human_value=human_value)
583 566
584 567
585 568 @register_sysinfo
586 569 def vcs_backends():
587 570 import rhodecode
588 571 value = rhodecode.CONFIG.get('vcs.backends')
589 572 human_value = 'Enabled backends in order: {}'.format(','.join(value))
590 573 return SysInfoRes(value=value, human_value=human_value)
591 574
592 575
593 576 @register_sysinfo
594 577 def vcs_server():
595 578 import rhodecode
596 579 from rhodecode.lib.vcs.backends import get_vcsserver_service_data
597 580
598 581 server_url = rhodecode.CONFIG.get('vcs.server')
599 582 enabled = rhodecode.CONFIG.get('vcs.server.enable')
600 583 protocol = rhodecode.CONFIG.get('vcs.server.protocol') or 'http'
601 584 state = STATE_OK_DEFAULT
602 585 version = None
603 586 workers = 0
604 587
605 588 try:
606 589 data = get_vcsserver_service_data()
607 590 if data and 'version' in data:
608 591 version = data['version']
609 592
610 593 if data and 'config' in data:
611 594 conf = data['config']
612 595 workers = conf.get('workers', 'NOT AVAILABLE')
613 596
614 597 connection = 'connected'
615 598 except Exception as e:
616 599 connection = 'failed'
617 600 state = {'message': str(e), 'type': STATE_ERR}
618 601
619 602 value = dict(
620 603 url=server_url,
621 604 enabled=enabled,
622 605 protocol=protocol,
623 606 connection=connection,
624 607 version=version,
625 608 text='',
626 609 )
627 610
628 611 human_value = value.copy()
629 612 human_value['text'] = \
630 613 '{url}@ver:{ver} via {mode} mode[workers:{workers}], connection:{conn}'.format(
631 614 url=server_url, ver=version, workers=workers, mode=protocol,
632 615 conn=connection)
633 616
634 617 return SysInfoRes(value=value, state=state, human_value=human_value)
635 618
636 619
637 620 @register_sysinfo
638 621 def vcs_server_config():
639 622 from rhodecode.lib.vcs.backends import get_vcsserver_service_data
640 623 state = STATE_OK_DEFAULT
641 624
642 625 value = {}
643 626 try:
644 627 data = get_vcsserver_service_data()
645 628 value = data['app_config']
646 629 except Exception as e:
647 630 state = {'message': str(e), 'type': STATE_ERR}
648 631
649 632 human_value = value.copy()
650 633 human_value['text'] = 'VCS Server config'
651 634
652 635 return SysInfoRes(value=value, state=state, human_value=human_value)
653 636
654 637
655 638 @register_sysinfo
656 639 def rhodecode_app_info():
657 640 import rhodecode
658 641 edition = rhodecode.CONFIG.get('rhodecode.edition')
659 642
660 643 value = dict(
661 644 rhodecode_version=rhodecode.__version__,
662 645 rhodecode_lib_path=os.path.abspath(rhodecode.__file__),
663 646 text=''
664 647 )
665 648 human_value = value.copy()
666 649 human_value['text'] = 'RhodeCode {edition}, version {ver}'.format(
667 650 edition=edition, ver=value['rhodecode_version']
668 651 )
669 652 return SysInfoRes(value=value, human_value=human_value)
670 653
671 654
672 655 @register_sysinfo
673 656 def rhodecode_config():
674 657 import rhodecode
675 658 path = rhodecode.CONFIG.get('__file__')
676 659 rhodecode_ini_safe = rhodecode.CONFIG.copy()
677 660 cert_path = get_cert_path(path)
678 661
679 662 try:
680 663 config = configparser.ConfigParser()
681 664 config.read(path)
682 665 parsed_ini = config
683 666 if parsed_ini.has_section('server:main'):
684 667 parsed_ini = dict(parsed_ini.items('server:main'))
685 668 except Exception:
686 669 log.exception('Failed to read .ini file for display')
687 670 parsed_ini = {}
688 671
689 672 rhodecode_ini_safe['server:main'] = parsed_ini
690 673
691 674 blacklist = [
692 675 f'rhodecode_{LicenseModel.LICENSE_DB_KEY}',
693 676 'routes.map',
694 677 'sqlalchemy.db1.url',
695 678 'channelstream.secret',
696 679 'beaker.session.secret',
697 680 'rhodecode.encrypted_values.secret',
698 681 'rhodecode_auth_github_consumer_key',
699 682 'rhodecode_auth_github_consumer_secret',
700 683 'rhodecode_auth_google_consumer_key',
701 684 'rhodecode_auth_google_consumer_secret',
702 685 'rhodecode_auth_bitbucket_consumer_secret',
703 686 'rhodecode_auth_bitbucket_consumer_key',
704 687 'rhodecode_auth_twitter_consumer_secret',
705 688 'rhodecode_auth_twitter_consumer_key',
706 689
707 690 'rhodecode_auth_twitter_secret',
708 691 'rhodecode_auth_github_secret',
709 692 'rhodecode_auth_google_secret',
710 693 'rhodecode_auth_bitbucket_secret',
711 694
712 695 'appenlight.api_key',
713 696 ('app_conf', 'sqlalchemy.db1.url')
714 697 ]
715 698 for k in blacklist:
716 699 if isinstance(k, tuple):
717 700 section, key = k
718 701 if section in rhodecode_ini_safe:
719 702 rhodecode_ini_safe[section] = '**OBFUSCATED**'
720 703 else:
721 704 rhodecode_ini_safe.pop(k, None)
722 705
723 706 # TODO: maybe put some CONFIG checks here ?
724 707 return SysInfoRes(value={'config': rhodecode_ini_safe,
725 708 'path': path, 'cert_path': cert_path})
726 709
727 710
728 711 @register_sysinfo
729 712 def database_info():
730 713 import rhodecode
731 714 from sqlalchemy.engine import url as engine_url
732 715 from rhodecode.model import meta
733 716 from rhodecode.model.meta import Session
734 717 from rhodecode.model.db import DbMigrateVersion
735 718
736 719 state = STATE_OK_DEFAULT
737 720
738 721 db_migrate = DbMigrateVersion.query().filter(
739 722 DbMigrateVersion.repository_id == 'rhodecode_db_migrations').one()
740 723
741 724 db_url_obj = engine_url.make_url(rhodecode.CONFIG['sqlalchemy.db1.url'])
742 725
743 726 try:
744 727 engine = meta.get_engine()
745 728 db_server_info = engine.dialect._get_server_version_info(
746 729 Session.connection(bind=engine))
747 730 db_version = '.'.join(map(str, db_server_info))
748 731 except Exception:
749 732 log.exception('failed to fetch db version')
750 733 db_version = 'UNKNOWN'
751 734
752 735 db_info = dict(
753 736 migrate_version=db_migrate.version,
754 737 type=db_url_obj.get_backend_name(),
755 738 version=db_version,
756 739 url=repr(db_url_obj)
757 740 )
758 741 current_version = db_migrate.version
759 742 expected_version = rhodecode.__dbversion__
760 743 if state['type'] == STATE_OK and current_version != expected_version:
761 744 msg = 'Critical: database schema mismatch, ' \
762 745 'expected version {}, got {}. ' \
763 746 'Please run migrations on your database.'.format(
764 747 expected_version, current_version)
765 748 state = {'message': msg, 'type': STATE_ERR}
766 749
767 750 human_value = db_info.copy()
768 751 human_value['url'] = "{} @ migration version: {}".format(
769 752 db_info['url'], db_info['migrate_version'])
770 753 human_value['version'] = "{} {}".format(db_info['type'], db_info['version'])
771 754 return SysInfoRes(value=db_info, state=state, human_value=human_value)
772 755
773 756
774 757 @register_sysinfo
775 758 def server_info(environ):
776 759 import rhodecode
777 760 from rhodecode.lib.base import get_server_ip_addr, get_server_port
778 761
779 762 value = {
780 763 'server_ip': '{}:{}'.format(
781 764 get_server_ip_addr(environ, log_errors=False),
782 765 get_server_port(environ)
783 766 ),
784 767 'server_id': rhodecode.CONFIG.get('instance_id'),
785 768 }
786 769 return SysInfoRes(value=value)
787 770
788 771
789 772 @register_sysinfo
790 773 def usage_info():
791 774 from rhodecode.model.db import User, Repository, true
792 775 value = {
793 776 'users': User.query().count(),
794 777 'users_active': User.query().filter(User.active == true()).count(),
795 778 'repositories': Repository.query().count(),
796 779 'repository_types': {
797 780 'hg': Repository.query().filter(
798 781 Repository.repo_type == 'hg').count(),
799 782 'git': Repository.query().filter(
800 783 Repository.repo_type == 'git').count(),
801 784 'svn': Repository.query().filter(
802 785 Repository.repo_type == 'svn').count(),
803 786 },
804 787 }
805 788 return SysInfoRes(value=value)
806 789
807 790
808 791 def get_system_info(environ):
809 792 environ = environ or {}
810 793 return {
811 794 'rhodecode_app': SysInfo(rhodecode_app_info)(),
812 795 'rhodecode_config': SysInfo(rhodecode_config)(),
813 796 'rhodecode_usage': SysInfo(usage_info)(),
814 797 'python': SysInfo(python_info)(),
815 798 'py_modules': SysInfo(py_modules)(),
816 799
817 800 'platform': SysInfo(platform_type)(),
818 801 'locale': SysInfo(locale_info)(),
819 802 'server': SysInfo(server_info, environ=environ)(),
820 803 'database': SysInfo(database_info)(),
821 804 'ulimit': SysInfo(ulimit_info)(),
822 805 'storage': SysInfo(storage)(),
823 806 'storage_inodes': SysInfo(storage_inodes)(),
824 807 'storage_archive': SysInfo(storage_archives)(),
825 808 'storage_gist': SysInfo(storage_gist)(),
826 809 'storage_temp': SysInfo(storage_temp)(),
827 810
828 811 'search': SysInfo(search_info)(),
829 812
830 813 'uptime': SysInfo(uptime)(),
831 814 'load': SysInfo(machine_load)(),
832 815 'cpu': SysInfo(cpu)(),
833 816 'memory': SysInfo(memory)(),
834 817
835 818 'vcs_backends': SysInfo(vcs_backends)(),
836 819 'vcs_server': SysInfo(vcs_server)(),
837 820
838 821 'vcs_server_config': SysInfo(vcs_server_config)(),
839 822
840 823 'git': SysInfo(git_info)(),
841 824 'hg': SysInfo(hg_info)(),
842 825 'svn': SysInfo(svn_info)(),
843 826 }
844 827
845 828
846 829 def load_system_info(key):
847 830 """
848 831 get_sys_info('vcs_server')
849 832 get_sys_info('database')
850 833 """
851 834 return SysInfo(registered_helpers[key])()
General Comments 0
You need to be logged in to leave comments. Login now