Sunil Mohan Adapa fb1898befc
backups: Use the backup component in all apps
Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
Reviewed-by: Veiko Aasa <veiko17@disroot.org>
2021-01-04 13:47:38 +02:00

241 lines
7.6 KiB
Python

# SPDX-License-Identifier: AGPL-3.0-or-later
"""
FreedomBox app for system diagnostics.
"""
import collections
import importlib
import logging
import pathlib
import threading
import psutil
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ugettext_noop
from plinth import app as app_module
from plinth import cfg, daemon, glib, menu
from plinth.modules.apache.components import diagnose_url_on_all
from plinth.modules.backups.components import BackupRestore
from . import manifest
version = 1
is_essential = True
_description = [
_('The system diagnostic test will run a number of checks on your '
'system to confirm that applications and services are working as '
'expected.')
]
app = None
logger = logging.Logger(__name__)
running_task = None
current_results = {}
class DiagnosticsApp(app_module.App):
"""FreedomBox app for diagnostics."""
app_id = 'diagnostics'
def __init__(self):
"""Create components for the app."""
super().__init__()
info = app_module.Info(app_id=self.app_id, version=version,
is_essential=is_essential,
name=_('Diagnostics'), icon='fa-heartbeat',
description=_description,
manual_page='Diagnostics')
self.add(info)
menu_item = menu.Menu('menu-diagnostics', info.name, None, info.icon,
'diagnostics:index', parent_url_name='system')
self.add(menu_item)
backup_restore = BackupRestore('backup-restore-diagnostics',
**manifest.backup)
self.add(backup_restore)
# Check periodically for low RAM space
interval = 180 if cfg.develop else 3600
glib.schedule(interval, _warn_about_low_ram_space)
def diagnose(self):
"""Run diagnostics and return the results."""
results = super().diagnose()
results.append(daemon.diagnose_port_listening(8000, 'tcp4'))
results.extend(
diagnose_url_on_all('http://{host}/plinth/',
check_certificate=False))
return results
def start_task():
"""Start the run task in a separate thread."""
global running_task
if running_task:
raise Exception('Task already running')
running_task = threading.Thread(target=run_on_all_enabled_modules)
running_task.start()
def run_on_all_enabled_modules():
"""Run diagnostics on all the enabled modules and store the result."""
global current_results
current_results = {
'apps': [],
'results': collections.OrderedDict(),
'progress_percentage': 0
}
# Three result strings returned by tests, mark for translation and
# translate later.
ugettext_noop('passed')
ugettext_noop('failed')
ugettext_noop('error')
apps = []
for app in app_module.App.list():
# Don't run diagnostics on apps have not been setup yet.
# However, run on apps that need an upgrade.
module = importlib.import_module(app.__class__.__module__)
if module.setup_helper.get_state() == 'needs-setup':
continue
if not app.is_enabled():
continue
if not app.has_diagnostics():
continue
apps.append((app.app_id, app))
app_name = app.info.name or app.app_id
current_results['results'][app.app_id] = {'name': app_name}
current_results['apps'] = apps
for current_index, (app_id, app) in enumerate(apps):
app_results = {
'diagnosis': None,
'exception': None,
}
try:
app_results['diagnosis'] = app.diagnose()
except Exception as exception:
logger.exception('Error running %s diagnostics - %s', app_id,
exception)
app_results['exception'] = str(exception)
current_results['results'][app_id].update(app_results)
current_results['progress_percentage'] = \
int((current_index + 1) * 100 / len(apps))
global running_task
running_task = None
def _get_memory_info_from_cgroups():
"""Return information about RAM usage from cgroups."""
cgroups_memory_path = pathlib.Path('/sys/fs/cgroup/memory')
memory_limit_file = cgroups_memory_path / 'memory.limit_in_bytes'
memory_usage_file = cgroups_memory_path / 'memory.usage_in_bytes'
memory_stat_file = cgroups_memory_path / 'memory.stat'
try:
memory_total = int(memory_limit_file.read_text())
memory_usage = int(memory_usage_file.read_text())
memory_stat_lines = memory_stat_file.read_text().split('\n')
except OSError:
return {}
memory_inactive = int([
line.rsplit(maxsplit=1)[1] for line in memory_stat_lines
if line.startswith('total_inactive_file')
][0])
memory_used = memory_usage - memory_inactive
return {
'total_bytes': memory_total,
'percent_used': memory_used * 100 / memory_total,
'free_bytes': memory_total - memory_used
}
def _get_memory_info():
"""Return RAM usage information."""
memory_info = psutil.virtual_memory()
cgroups_memory_info = _get_memory_info_from_cgroups()
if cgroups_memory_info and cgroups_memory_info[
'total_bytes'] < memory_info.total:
return cgroups_memory_info
return {
'total_bytes': memory_info.total,
'percent_used': memory_info.percent,
'free_bytes': memory_info.available
}
def _warn_about_low_ram_space(request):
"""Warn about insufficient RAM space."""
from plinth.notification import Notification
memory_info = _get_memory_info()
if memory_info['free_bytes'] < 1024**3:
# Translators: This is the unit of computer storage Mebibyte similar to
# Megabyte.
memory_available_unit = ugettext_noop('MiB')
memory_available = memory_info['free_bytes'] / 1024**2
else:
# Translators: This is the unit of computer storage Gibibyte similar to
# Gigabyte.
memory_available_unit = ugettext_noop('GiB')
memory_available = memory_info['free_bytes'] / 1024**3
show = False
if memory_info['percent_used'] > 90:
severity = 'error'
advice_message = ugettext_noop(
'You should disable some apps to reduce memory usage.')
show = True
elif memory_info['percent_used'] > 75:
severity = 'warning'
advice_message = ugettext_noop(
'You should not install any new apps on this system.')
show = True
if not show:
try:
Notification.get('diagnostics-low-ram-space').delete()
except KeyError:
pass
return
message = ugettext_noop(
# xgettext:no-python-format
'System is low on memory: {percent_used}% used, {memory_available} '
'{memory_available_unit} free. {advice_message}')
title = ugettext_noop('Low Memory')
data = {
'app_icon': 'fa-heartbeat',
'app_name': 'translate:' + ugettext_noop('Diagnostics'),
'percent_used': f'{memory_info["percent_used"]:.1f}',
'memory_available': f'{memory_available:.1f}',
'memory_available_unit': 'translate:' + memory_available_unit,
'advice_message': 'translate:' + advice_message
}
actions = [{'type': 'dismiss'}]
Notification.update_or_create(id='diagnostics-low-ram-space',
app_id='diagnostics', severity=severity,
title=title, message=message,
actions=actions, data=data, group='admin')