зеркало из https://github.com/mozilla/gecko-dev.git
Bug 987902 - Add a "doctor" mach command; r=gps
--HG-- extra : rebase_source : a69a0d2bfc243ad5c00974ff0ffdec4fa8e99c3e
This commit is contained in:
Родитель
064a3f9b2d
Коммит
6b5b381d25
|
@ -0,0 +1,376 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, # You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import psutil
|
||||
|
||||
from distutils.util import strtobool
|
||||
from distutils.version import LooseVersion
|
||||
import mozpack.path as mozpath
|
||||
|
||||
# Minimum recommended logical processors in system.
|
||||
PROCESSORS_THRESHOLD = 4
|
||||
|
||||
# Minimum recommended total system memory, in gigabytes.
|
||||
MEMORY_THRESHOLD = 7.4
|
||||
|
||||
# Minimum recommended free space on each disk, in gigabytes.
|
||||
FREESPACE_THRESHOLD = 10
|
||||
|
||||
# Latest MozillaBuild version
|
||||
LATEST_MOZILLABUILD_VERSION = '1.11.0'
|
||||
|
||||
DISABLE_8DOT3_WIN = '''
|
||||
Disable 8.3 filename creation systemwide?
|
||||
This increases performance but some legacy applications may not be able to find
|
||||
files and directories that have long file names.
|
||||
https://support.microsoft.com/kb/121007
|
||||
'''
|
||||
|
||||
DISABLE_LASTACCESS_WIN = '''
|
||||
Disable the last access time feature?
|
||||
This improves the speed of file and
|
||||
directory access by deferring Last Access Time modification on disk by up to an
|
||||
hour. Backup programs that rely on this feature may be affected.
|
||||
https://technet.microsoft.com/en-us/library/cc785435.aspx
|
||||
'''
|
||||
|
||||
class Doctor(object):
|
||||
def __init__(self, srcdir, objdir, fix):
|
||||
self.srcdir = mozpath.normpath(srcdir)
|
||||
self.objdir = mozpath.normpath(objdir)
|
||||
self.srcdir_mount = self.getmount(self.srcdir)
|
||||
self.objdir_mount = self.getmount(self.objdir)
|
||||
self.path_mounts = [
|
||||
('srcdir', self.srcdir, self.srcdir_mount),
|
||||
('objdir', self.objdir, self.objdir_mount)
|
||||
]
|
||||
self.fix = fix
|
||||
self.results = []
|
||||
|
||||
def check_all(self):
|
||||
checks = [
|
||||
'cpu',
|
||||
'memory',
|
||||
'storage_freespace',
|
||||
'fs_8dot3',
|
||||
'fs_lastaccess',
|
||||
'mozillabuild'
|
||||
]
|
||||
for check in checks:
|
||||
self.report(getattr(self, check))
|
||||
good = True
|
||||
fixable = False
|
||||
denied = False
|
||||
for result in self.results:
|
||||
if result.get('status') != 'GOOD':
|
||||
good = False
|
||||
if result.get('fixable', False):
|
||||
fixable = True
|
||||
if result.get('denied', False):
|
||||
denied = True
|
||||
if denied:
|
||||
print('run "mach doctor --fix" AS ADMIN to re-attempt fixing your system')
|
||||
elif False: # elif fixable:
|
||||
print('run "mach doctor --fix" as admin to attempt fixing your system')
|
||||
return int(not good)
|
||||
|
||||
def getmount(self, path):
|
||||
while path != '/' and not os.path.ismount(path):
|
||||
path = mozpath.abspath(mozpath.join(path, os.pardir))
|
||||
return path
|
||||
|
||||
def prompt_bool(self, prompt, limit=5):
|
||||
''' Prompts the user with prompt and requires a boolean value. '''
|
||||
valid = False
|
||||
while not valid and limit > 0:
|
||||
try:
|
||||
choice = strtobool(raw_input(prompt + '[Y/N]\n'))
|
||||
valid = True
|
||||
except ValueError:
|
||||
print("ERROR! Please enter a valid option!")
|
||||
limit -= 1
|
||||
|
||||
if limit > 0:
|
||||
return choice
|
||||
else:
|
||||
raise Exception("Error! Reached max attempts of entering option.")
|
||||
|
||||
def report(self, results):
|
||||
# Handle single dict result or list of results.
|
||||
if isinstance(results, dict):
|
||||
results = [results]
|
||||
for result in results:
|
||||
status = result.get('status', 'UNSURE')
|
||||
if status == 'SKIPPED':
|
||||
continue
|
||||
self.results.append(result)
|
||||
print('%s...\t%s\n' % (
|
||||
result.get('desc', ''),
|
||||
status
|
||||
)
|
||||
).expandtabs(40)
|
||||
|
||||
@property
|
||||
def platform(self):
|
||||
platform = getattr(self, '_platform', None)
|
||||
if not platform:
|
||||
platform = sys.platform
|
||||
while platform[-1].isdigit():
|
||||
platform = platform[:-1]
|
||||
setattr(self, '_platform', platform)
|
||||
return platform
|
||||
|
||||
@property
|
||||
def cpu(self):
|
||||
cpu_count = psutil.cpu_count()
|
||||
if cpu_count < PROCESSORS_THRESHOLD:
|
||||
status = 'BAD'
|
||||
desc = '%d logical processors detected, <%d' % (
|
||||
cpu_count, PROCESSORS_THRESHOLD
|
||||
)
|
||||
else:
|
||||
status = 'GOOD'
|
||||
desc = '%d logical processors detected, >=%d' % (
|
||||
cpu_count, PROCESSORS_THRESHOLD
|
||||
)
|
||||
return {'status': status, 'desc': desc}
|
||||
|
||||
@property
|
||||
def memory(self):
|
||||
memory = psutil.virtual_memory().total
|
||||
# Convert to gigabytes.
|
||||
memory_GB = memory / 1024**3.0
|
||||
if memory_GB < MEMORY_THRESHOLD:
|
||||
status = 'BAD'
|
||||
desc = '%.1fGB of physical memory, <%.1fGB' % (
|
||||
memory_GB, MEMORY_THRESHOLD
|
||||
)
|
||||
else:
|
||||
status = 'GOOD'
|
||||
desc = '%.1fGB of physical memory, >%.1fGB' % (
|
||||
memory_GB, MEMORY_THRESHOLD
|
||||
)
|
||||
return {'status': status, 'desc': desc}
|
||||
|
||||
@property
|
||||
def storage_freespace(self):
|
||||
results = []
|
||||
desc = ''
|
||||
mountpoint_line = self.srcdir_mount != self.objdir_mount
|
||||
for (purpose, path, mount) in self.path_mounts:
|
||||
desc += '%s = %s\n' % (purpose, path)
|
||||
if not mountpoint_line:
|
||||
mountpoint_line = True
|
||||
continue
|
||||
try:
|
||||
usage = psutil.disk_usage(mount)
|
||||
freespace, size = usage.free, usage.total
|
||||
freespace_GB = freespace / 1024**3
|
||||
size_GB = size / 1024**3
|
||||
if freespace_GB < FREESPACE_THRESHOLD:
|
||||
status = 'BAD'
|
||||
desc += 'mountpoint = %s\n%dGB of %dGB free, <%dGB' % (
|
||||
mount, freespace_GB, size_GB, FREESPACE_THRESHOLD
|
||||
)
|
||||
else:
|
||||
status = 'GOOD'
|
||||
desc += 'mountpoint = %s\n%dGB of %dGB free, >=%dGB' % (
|
||||
mount, freespace_GB, size_GB, FREESPACE_THRESHOLD
|
||||
)
|
||||
except OSError:
|
||||
status = 'UNSURE'
|
||||
desc += 'path invalid'
|
||||
results.append({'status': status, 'desc': desc})
|
||||
return results
|
||||
|
||||
@property
|
||||
def fs_8dot3(self):
|
||||
if self.platform != 'win':
|
||||
return {'status': 'SKIPPED'}
|
||||
results = []
|
||||
fixable = False
|
||||
denied = False
|
||||
# See 'fsutil behavior':
|
||||
# https://technet.microsoft.com/en-us/library/cc785435.aspx
|
||||
try:
|
||||
command = 'fsutil behavior query disable8dot3'.split(' ')
|
||||
fsutil_output = subprocess.check_output(command)
|
||||
system8dot3 = int(fsutil_output.partition(':')[2][1])
|
||||
except subprocess.CalledProcessError:
|
||||
return {'status': 'UNSURE',
|
||||
'desc': 'unable to check 8dot3 behavior'}
|
||||
if system8dot3 == 1:
|
||||
return {'status': 'GOOD',
|
||||
'desc': '8dot3 disabled systemwide'}
|
||||
elif system8dot3 == 0:
|
||||
if False: # if self.fix:
|
||||
choice = self.prompt_bool(DISABLE_8DOT3_WIN)
|
||||
if not choice:
|
||||
return {'status': 'BAD, NOT FIXED',
|
||||
'desc': '8dot3 enabled systemwide'}
|
||||
try:
|
||||
command = 'fsutil behavior set disable8dot3 1'.split(' ')
|
||||
fsutil_output = subprocess.check_output(command)
|
||||
status = 'GOOD, FIXED'
|
||||
desc = '8dot3 disabled systemwide'
|
||||
except subprocess.CalledProcessError, e:
|
||||
desc = '8dot3 enabled systemwide'
|
||||
if e.output.find('denied') != -1:
|
||||
status = 'BAD, FIX DENIED'
|
||||
denied = True
|
||||
else:
|
||||
status = 'BAD, NOT FIXED'
|
||||
else:
|
||||
status = 'BAD, FIXABLE'
|
||||
desc = '8dot3 enabled systemwide'
|
||||
fixable = True
|
||||
return {'status': status, 'desc': desc, 'fixable': fixable,
|
||||
'denied': denied}
|
||||
# See 'fsutil 8dot3':
|
||||
# https://technet.microsoft.com/en-us/library/ff621566.aspx
|
||||
elif system8dot3 == 2 or system8dot3 == 3:
|
||||
# 2 = Individual disk behavior respected.
|
||||
# 3 = 8dot3 disabled on all except system disk.
|
||||
# Neither is a default value; assume that it's meant to be that
|
||||
# way and don't try to fix it.
|
||||
common_mountpoint = self.srcdir_mount == self.objdir_mount
|
||||
for (purpose, path, mount) in self.path_mounts:
|
||||
results.append(self.check_disk_8dot3(mount))
|
||||
if common_mountpoint:
|
||||
break
|
||||
return results
|
||||
|
||||
def check_disk_8dot3(self, path, disk):
|
||||
disk = disk.replace('/', '')
|
||||
try:
|
||||
command = ('fsutil behavior query disable8dot3 ' + disk).split(' ')
|
||||
fsutil_output = subprocess.check_output(command)
|
||||
(volumeLine, systemLine, emptyLine, effectLine, emptyLine2) = fsutil_output.split('\r\n')
|
||||
volume8dot3 = int(volumeLine.partition(':')[2][1])
|
||||
effective8dot3 = int(effectLine.find('disabled') != -1)
|
||||
if volume8dot3 == 1:
|
||||
# Current disk has 8dot3 disabled.
|
||||
status = 'GOOD'
|
||||
desc = '%s has 8dot3 disabled' % disk
|
||||
else:
|
||||
status = 'BAD'
|
||||
desc = '%s has 8dot3 disabled' % disk
|
||||
except subprocess.CalledProcessError:
|
||||
status = 'UNSURE'
|
||||
desc = '%s 8dot3 behavior unknown' % disk
|
||||
return {'status': status, 'desc': desc}
|
||||
|
||||
@property
|
||||
def fs_lastaccess(self):
|
||||
results = []
|
||||
if self.platform == 'win':
|
||||
fixable = False
|
||||
denied = False
|
||||
# See 'fsutil behavior':
|
||||
# https://technet.microsoft.com/en-us/library/cc785435.aspx
|
||||
try:
|
||||
command = 'fsutil behavior query disablelastaccess'.split(' ')
|
||||
fsutil_output = subprocess.check_output(command)
|
||||
disablelastaccess = int(fsutil_output.partition('=')[2][1])
|
||||
except subprocess.CalledProcessError:
|
||||
disablelastaccess = -1
|
||||
status = 'UNSURE'
|
||||
desc = 'unable to check lastaccess behavior'
|
||||
if disablelastaccess == 1:
|
||||
status = 'GOOD'
|
||||
desc = 'lastaccess disabled systemwide'
|
||||
elif disablelastaccess == 0:
|
||||
if False: # if self.fix:
|
||||
choice = self.prompt_bool(DISABLE_LASTACCESS_WIN)
|
||||
if not choice:
|
||||
return {'status': 'BAD, NOT FIXED',
|
||||
'desc': 'lastaccess enabled systemwide'}
|
||||
try:
|
||||
command = 'fsutil behavior set disablelastaccess 1'.split(' ')
|
||||
fsutil_output = subprocess.check_output(command)
|
||||
status = 'GOOD, FIXED'
|
||||
desc = 'lastaccess disabled systemwide'
|
||||
except subprocess.CalledProcessError, e:
|
||||
desc = 'lastaccess enabled systemwide'
|
||||
if e.output.find('denied') != -1:
|
||||
status = 'BAD, FIX DENIED'
|
||||
denied = True
|
||||
else:
|
||||
status = 'BAD, NOT FIXED'
|
||||
else:
|
||||
status = 'BAD, FIXABLE'
|
||||
desc = 'lastaccess enabled'
|
||||
fixable = True
|
||||
results.append({'status': status, 'desc': desc, 'fixable': fixable,
|
||||
'denied': denied})
|
||||
elif self.platform in ['darwin', 'freebsd', 'linux', 'openbsd']:
|
||||
common_mountpoint = self.srcdir_mount == self.objdir_mount
|
||||
for (purpose, path, mount) in self.path_mounts:
|
||||
results.append(self.check_mount_lastaccess(mount))
|
||||
if common_mountpoint:
|
||||
break
|
||||
else:
|
||||
results.append({'status': 'SKIPPED'})
|
||||
return results
|
||||
|
||||
def check_mount_lastaccess(self, mount):
|
||||
partitions = psutil.disk_partitions()
|
||||
atime_opts = {'atime', 'noatime', 'relatime', 'norelatime'}
|
||||
option = ''
|
||||
for partition in partitions:
|
||||
if partition.mountpoint == mount:
|
||||
mount_opts = set(partition.opts.split(','))
|
||||
intersection = list(atime_opts & mount_opts)
|
||||
if len(intersection) == 1:
|
||||
option = intersection[0]
|
||||
break
|
||||
if not option:
|
||||
status = 'BAD'
|
||||
if self.platform == 'linux':
|
||||
option = 'noatime/relatime'
|
||||
else:
|
||||
option = 'noatime'
|
||||
desc = '%s has no explicit %s mount option' % (
|
||||
mount, option
|
||||
)
|
||||
elif option == 'atime' or option == 'norelatime':
|
||||
status = 'BAD'
|
||||
desc = '%s has %s mount option' % (
|
||||
mount, option
|
||||
)
|
||||
elif option == 'noatime' or option == 'relatime':
|
||||
status = 'GOOD'
|
||||
desc = '%s has %s mount option' % (
|
||||
mount, option
|
||||
)
|
||||
return {'status': status, 'desc': desc}
|
||||
|
||||
@property
|
||||
def mozillabuild(self):
|
||||
if self.platform != 'win':
|
||||
return {'status': 'SKIPPED'}
|
||||
MOZILLABUILD = mozpath.normpath(os.environ.get('MOZILLABUILD', ''))
|
||||
if not MOZILLABUILD or not os.path.exists(MOZILLABUILD):
|
||||
return {'desc': 'not running under MozillaBuild'}
|
||||
try:
|
||||
with open(mozpath.join(MOZILLABUILD, 'VERSION'), 'r') as fh:
|
||||
version = fh.readline()
|
||||
if not version:
|
||||
raise ValueError()
|
||||
if LooseVersion(version) < LooseVersion(LATEST_MOZILLABUILD_VERSION):
|
||||
status = 'BAD'
|
||||
desc = 'MozillaBuild %s in use, <%s' % (
|
||||
version, LATEST_MOZILLABUILD_VERSION
|
||||
)
|
||||
else:
|
||||
status = 'GOOD'
|
||||
desc = 'MozillaBuild %s in use' % version
|
||||
except (IOError, ValueError):
|
||||
status = 'UNSURE'
|
||||
desc = 'MozillaBuild version not found'
|
||||
return {'status': status, 'desc': desc}
|
|
@ -599,6 +599,18 @@ class Build(MachCommandBase):
|
|||
return self._run_command_in_objdir(args=args, pass_thru=True,
|
||||
ensure_exit_code=False)
|
||||
|
||||
@CommandProvider
|
||||
class Doctor(MachCommandBase):
|
||||
"""Provide commands for diagnosing common build environment problems"""
|
||||
@Command('doctor', category='devenv',
|
||||
description='')
|
||||
@CommandArgument('--fix', default=None, action='store_true',
|
||||
help='Attempt to fix found problems.')
|
||||
def doctor(self, fix=None):
|
||||
self._activate_virtualenv()
|
||||
from mozbuild.doctor import Doctor
|
||||
doctor = Doctor(self.topsrcdir, self.topobjdir, fix)
|
||||
return doctor.check_all()
|
||||
|
||||
@CommandProvider
|
||||
class Warnings(MachCommandBase):
|
||||
|
|
Загрузка…
Ссылка в новой задаче