import cherrypy
import os
import psutil
import socket
import threading
import time
import girder
from girder import logger
from girder.models import getDbConnection
def _objectToDict(obj):
"""
Convert an object to a dictionary. Any non-private attribute that is an
integer, float, or string is returned in the dictionary.
:param obj: a python object or class.
:returns: a dictionary of values for the object.
"""
return {
key: getattr(obj, key)
for key in dir(obj)
if not key.startswith('_') and isinstance(
getattr(obj, key),
(float, tuple, str, int))
}
def _computeSlowStatus(process, status, db):
status['diskPartitions'] = [_objectToDict(part) for part in
psutil.disk_partitions()]
try:
# This fails in certain environments, so guard it
status['diskIO'] = _objectToDict(psutil.disk_io_counters())
except Exception:
pass
# Report on the disk usage where the script is located
if hasattr(girder, '__file__'):
status['girderPath'] = os.path.abspath(girder.__file__)
status['girderDiskUsage'] = _objectToDict(
psutil.disk_usage(status['girderPath']))
# Report where our logs are and how much space is available for them
status['logs'] = []
for handler in logger.handlers:
try:
logInfo = {'path': handler.baseFilename}
logInfo['diskUsage'] = _objectToDict(
psutil.disk_usage(logInfo['path']))
status['logs'].append(logInfo)
except Exception:
# If we can't read information about the log, don't throw an
# exception
pass
status['mongoDbStats'] = db.command('dbStats')
try:
# This can fail when mongo is on a different system.
status['mongoDbPath'] = getDbConnection().admin.command(
'getCmdLineOpts')['parsed']['storage']['dbPath']
status['mongoDbDiskUsage'] = _objectToDict(
psutil.disk_usage(status['mongoDbPath']))
except Exception:
pass
status['processDirectChildrenCount'] = len(process.children())
status['processAllChildrenCount'] = len(process.children(True))
status['openFiles'] = [_objectToDict(file) for file in
process.open_files()]
# I'd rather see textual names for the family and type of connections,
# so make a lookup table for them
connFamily = {getattr(socket, key): key for key in dir(socket)
if key.startswith('AF_')}
connType = {getattr(socket, key): key for key in dir(socket)
if key.startswith('SOCK_')}
connections = []
for conn in process.connections():
connDict = _objectToDict(conn)
connDict.pop('raddr', None)
connDict.pop('laddr', None)
connDict['family'] = connFamily.get(connDict['family'],
connDict['family'])
connDict['type'] = connType.get(connDict['type'], connDict['type'])
connections.append(connDict)
status['connections'] = connections
if hasattr(process, 'io_counters'):
status['ioCounters'] = _objectToDict(process.io_counters())
status['cherrypyThreads'] = {}
for threadId in cherrypy.tools.status.seenThreads:
info = cherrypy.tools.status.seenThreads[threadId].copy()
if 'end' in info:
info['duration'] = info['end'] - info['start']
info['idle'] = time.time() - info['end']
status['cherrypyThreads'][threadId] = info
[docs]def getStatus(mode='basic', user=None):
"""
Get a dictionary of status information regarding the Girder server.
:param mode: 'basic' returns values available to any anonymous user.
'quick' returns only values that are cheap to acquire.
'slow' provides all of that information and adds additional
:param user: a user record. Must have admin access to get anything other
than basic mode.
:returns: a status dictionary.
"""
isAdmin = (user is not None and user['admin'])
status = {}
status['bootTime'] = psutil.boot_time()
status['currentTime'] = time.time()
process = psutil.Process(os.getpid())
status['processStartTime'] = process.create_time()
if mode in ('quick', 'slow') and isAdmin:
status['virtualMemory'] = _objectToDict(psutil.virtual_memory())
status['swap'] = _objectToDict(psutil.swap_memory())
status['cpuCount'] = psutil.cpu_count()
status['processMemory'] = _objectToDict(process.memory_info())
status['processName'] = process.name()
status['cmdline'] = process.cmdline()
status['exe'] = process.exe()
status['cwd'] = process.cwd()
status['userName'] = process.username()
status['processCpuTimes'] = _objectToDict(process.cpu_times())
db = getDbConnection().get_database()
status['mongoBuildInfo'] = db.command('buildInfo')
status['cherrypyThreadsMaxUsed'] = len(
cherrypy.tools.status.seenThreads)
status['cherrypyThreadsInUse'] = len([
True for threadId in cherrypy.tools.status.seenThreads
if 'end' not in cherrypy.tools.status.seenThreads[threadId]])
status['cherrypyThreadPoolSize'] = cherrypy.server.thread_pool
if mode == 'slow' and isAdmin:
_computeSlowStatus(process, status, db)
return status
# This class is used to monitor which threads in cherrypy are actively serving
# responses, and what each was last used for. It is based on the example at
# http://tools.cherrypy.org/wiki/StatusTool, but has changes to handle yield-
# based responses.
[docs]class StatusMonitor(cherrypy.Tool):
"""Register the status of each thread."""
def __init__(self):
self._point = 'on_start_resource'
self._name = 'status'
self._priority = 50
self.seenThreads = {}
def callable(self):
threadId = threading.current_thread().ident
self.seenThreads[threadId] = {
'start': cherrypy.response.time, 'url': cherrypy.url()}
[docs] def unregister(self):
"""Unregister the current thread."""
threadID = threading.current_thread().ident
if threadID in self.seenThreads:
self.seenThreads[threadID]['end'] = time.time()
def _setup(self):
cherrypy.Tool._setup(self)
cherrypy.request.hooks.attach('on_end_request', self.unregister)
cherrypy.tools.status = StatusMonitor()
cherrypy.config.update({'tools.status.on': True})