storage: simplified use of df to get disk information

- Use df --block-size=1 instead --human-readable to fix locale issue #1043.
- Code cleanup: get available space directly, better variable/module names.

Signed-off-by: Johannes Keyser <johanneskeyser@posteo.de>
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
Johannes Keyser 2017-10-08 16:58:14 +02:00 committed by James Valleroy
parent 1247f2ef93
commit 10df3004ac
No known key found for this signature in database
GPG Key ID: 77C0C75E7B650808
3 changed files with 59 additions and 74 deletions

View File

@ -64,8 +64,8 @@ def get_disks():
def _get_disks_from_df(): def _get_disks_from_df():
"""Return the list of disks and free space available using 'df'.""" """Return the list of disks and free space available using 'df'."""
command = ['df', '--exclude-type=tmpfs', '--exclude-type=devtmpfs', command = ['df', '--exclude-type=tmpfs', '--exclude-type=devtmpfs',
'--output=source,target,fstype,size,used,pcent', '--block-size=1',
'--human-readable'] '--output=source,target,fstype,size,used,avail,pcent']
try: try:
process = subprocess.run(command, stdout=subprocess.PIPE, check=True) process = subprocess.run(command, stdout=subprocess.PIPE, check=True)
except subprocess.CalledProcessError as exception: except subprocess.CalledProcessError as exception:
@ -78,9 +78,15 @@ def _get_disks_from_df():
for line in output.splitlines()[1:]: for line in output.splitlines()[1:]:
parts = line.split() parts = line.split()
keys = ('device', 'mount_point', 'file_system_type', 'size', 'used', keys = ('device', 'mount_point', 'file_system_type', 'size', 'used',
'percentage_used') 'free', 'percent_used')
disk = dict(zip(keys, parts)) disk = dict(zip(keys, parts))
disk['percentage_used'] = int(disk['percentage_used'].rstrip('%')) disk['percent_used'] = int(disk['percent_used'].rstrip('%'))
disk['size'] = int(disk['size'])
disk['used'] = int(disk['used'])
disk['free'] = int(disk['free'])
disk['size_str'] = format_bytes(disk['size'])
disk['used_str'] = format_bytes(disk['used'])
disk['free_str'] = format_bytes(disk['free'])
disks.append(disk) disks.append(disk)
return disks return disks
@ -130,3 +136,27 @@ def is_expandable(device):
def expand_partition(device): def expand_partition(device):
"""Expand a partition.""" """Expand a partition."""
actions.superuser_run('storage', ['expand-partition', device]) actions.superuser_run('storage', ['expand-partition', device])
def format_bytes(size):
"""Return human readable disk size from bytes."""
if not size:
return size
if size < 1024:
return _('{disk_size:.1f} bytes').format(disk_size=size)
if size < 1024 ** 2:
size /= 1024
return _('{disk_size:.1f} KiB').format(disk_size=size)
if size < 1024 ** 3:
size /= 1024 ** 2
return _('{disk_size:.1f} MiB').format(disk_size=size)
if size < 1024 ** 4:
size /= 1024 ** 3
return _('{disk_size:.1f} GiB').format(disk_size=size)
size /= 1024 ** 4
return _('{disk_size:.1f} TiB').format(disk_size=size)

View File

@ -52,20 +52,20 @@
<td>{{ disk.file_system_type }}</td> <td>{{ disk.file_system_type }}</td>
<td> <td>
<div class="progress"> <div class="progress">
{% if disk.percentage_used < 75 %} {% if disk.percent_used < 75 %}
<div class="progress-bar progress-bar-striped progress-bar-success" <div class="progress-bar progress-bar-striped progress-bar-success"
{% elif disk.percentage_used < 90 %} {% elif disk.percent_used < 90 %}
<div class="progress-bar progress-bar-striped progress-bar-warning" <div class="progress-bar progress-bar-striped progress-bar-warning"
{% else %} {% else %}
<div class="progress-bar progress-bar-striped progress-bar-danger" <div class="progress-bar progress-bar-striped progress-bar-danger"
{% endif %} {% endif %}
role="progressbar" aria-valuenow="disk.percentage_used" role="progressbar" aria-valuenow="disk.percent_used"
aria-valuemin="0" aria-valuemax="100" aria-valuemin="0" aria-valuemax="100"
style="width: {{ disk.percentage_used }}%;"> style="width: {{ disk.percent_used }}%;">
{{ disk.percentage_used }}% {{ disk.percent_used }}%
</div> </div>
</div> </div>
<div>{{ disk.used }} / {{ disk.size }}</div> <div>{{ disk.used_str }} / {{ disk.size_str }}</div>
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}

View File

@ -25,7 +25,7 @@ from django.shortcuts import redirect
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from django.urls import reverse from django.urls import reverse
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from plinth.modules import storage as storage_module from plinth.modules import storage
from plinth.utils import format_lazy, is_user_admin from plinth.utils import format_lazy, is_user_admin
@ -34,10 +34,10 @@ logger = logging.getLogger(__name__)
def index(request): def index(request):
"""Show connection list.""" """Show connection list."""
disks = storage_module.get_disks() disks = storage.get_disks()
root_device = storage_module.get_root_device(disks) root_device = storage.get_root_device(disks)
expandable_root_size = storage_module.is_expandable(root_device) expandable_root_size = storage.is_expandable(root_device)
expandable_root_size = _format_bytes(expandable_root_size) expandable_root_size = storage.format_bytes(expandable_root_size)
warn_about_low_disk_space(request) warn_about_low_disk_space(request)
@ -49,15 +49,15 @@ def index(request):
def expand(request): def expand(request):
"""Warn and expand the root partition.""" """Warn and expand the root partition."""
disks = storage_module.get_disks() disks = storage.get_disks()
root_device = storage_module.get_root_device(disks) root_device = storage.get_root_device(disks)
if request.method == 'POST': if request.method == 'POST':
expand_partition(request, root_device) expand_partition(request, root_device)
return redirect(reverse('storage:index')) return redirect(reverse('storage:index'))
expandable_root_size = storage_module.is_expandable(root_device) expandable_root_size = storage.is_expandable(root_device)
expandable_root_size = _format_bytes(expandable_root_size) expandable_root_size = storage.format_bytes(expandable_root_size)
return TemplateResponse(request, 'storage_expand.html', return TemplateResponse(request, 'storage_expand.html',
{'title': _('Expand Root Partition'), {'title': _('Expand Root Partition'),
'expandable_root_size': expandable_root_size}) 'expandable_root_size': expandable_root_size})
@ -66,7 +66,7 @@ def expand(request):
def expand_partition(request, device): def expand_partition(request, device):
"""Expand the partition.""" """Expand the partition."""
try: try:
storage_module.expand_partition(device) storage.expand_partition(device)
except Exception as exception: except Exception as exception:
messages.error(request, _('Error expanding partition: {exception}') messages.error(request, _('Error expanding partition: {exception}')
.format(exception=exception)) .format(exception=exception))
@ -79,71 +79,26 @@ def warn_about_low_disk_space(request):
if not is_user_admin(request, cached=True): if not is_user_admin(request, cached=True):
return return
disks = storage_module.get_disks() disks = storage.get_disks()
list_root = [disk for disk in disks if disk['mountpoint'] == '/'] list_root = [disk for disk in disks if disk['mountpoint'] == '/']
if not list_root: if not list_root:
logger.error('Error getting information about root partition.') logger.error('Error getting information about root partition.')
return return
perc_used = list_root[0]['percentage_used'] percent_used = list_root[0]['percent_used']
size_bytes = _interpret_size_string(list_root[0]['size']) size_bytes = list_root[0]['size']
free_bytes = size_bytes * (100 - perc_used) / 100 free_bytes = list_root[0]['free']
free_gib = free_bytes / (1024 ** 3)
message = format_lazy( message = format_lazy(
# Translators: xgettext:no-python-format # Translators: xgettext:no-python-format
_('Warning: Low space on system partition ({percent_used}% used, ' _('Warning: Low space on system partition ({percent_used}% used, '
'{free_space} free).'), '{free_space} free).'),
percent_used=perc_used, free_space=_format_bytes(free_bytes)) percent_used=percent_used,
free_space=storage.format_bytes(free_bytes))
free_gib = free_bytes / (1024 ** 3) if percent_used > 90 or free_gib < 1:
if perc_used > 90 or free_gib < 1:
messages.error(request, message) messages.error(request, message)
elif perc_used > 75 or free_gib < 2: elif percent_used > 75 or free_gib < 2:
messages.warning(request, message) messages.warning(request, message)
def _interpret_size_string(size_str):
"""Convert size string to number of bytes."""
# TODO: Drop --human-readable from df command (github issue #1043)
size_str = size_str.replace(',', '.') # some locales use commas
size_str = size_str.replace('٫', '.') # arabic decimal separator
if size_str[-1] in '0123456789':
return float(size_str[:-1])
if size_str[-1] == 'K':
return float(size_str[:-1]) * 1024
if size_str[-1] == 'M':
return float(size_str[:-1]) * 1024 ** 2
if size_str[-1] == 'G':
return float(size_str[:-1]) * 1024 ** 3
if size_str[-1] == 'T':
return float(size_str[:-1]) * 1024 ** 4
return -1
def _format_bytes(size):
"""Return human readable disk size from bytes."""
if not size:
return size
if size < 1024:
return _('{disk_size:.1f} bytes').format(disk_size=size)
if size < 1024 ** 2:
size /= 1024
return _('{disk_size:.1f} KiB').format(disk_size=size)
if size < 1024 ** 3:
size /= 1024 ** 2
return _('{disk_size:.1f} MiB').format(disk_size=size)
if size < 1024 ** 4:
size /= 1024 ** 3
return _('{disk_size:.1f} GiB').format(disk_size=size)
size /= 1024 ** 4
return _('{disk_size:.1f} TiB').format(disk_size=size)