d23e0a65b2
In case if application is under Apache+mod_wsgi it is not recommended to use signals [1]. We need to have configuration option for handling 'touch file' event instead of signal. Alternative solutions: 1) watchdog: can monitor only directories, has issues with eventlet [2]. 2) inotify: works only with linux-based systems. [1] https://code.google.com/p/modwsgi/wiki/ConfigurationDirectives#WSGIRestrictSignal [2] https://github.com/gorakhargosh/watchdog/issues/332 Change-Id: I6ef02457f21da8e6fbd50e57bfa503b3c31ddd76 Implements: blueprint guru-meditation-report-file-touch
300 lines
10 KiB
Python
300 lines
10 KiB
Python
# Copyright 2013 Red Hat, Inc.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
"""Provides Guru Meditation Report
|
|
|
|
This module defines the actual OpenStack Guru Meditation
|
|
Report class.
|
|
|
|
This can be used in the OpenStack command definition files.
|
|
For example, in a nova command module (under nova/cmd):
|
|
|
|
.. code-block:: python
|
|
:emphasize-lines: 8,9,10
|
|
|
|
from oslo_config import cfg
|
|
from oslo_log import log as oslo_logging
|
|
from oslo_reports import opts as gmr_opts
|
|
from oslo_reports import guru_meditation_report as gmr
|
|
|
|
CONF = cfg.CONF
|
|
# maybe import some options here...
|
|
|
|
def main():
|
|
oslo_logging.register_options(CONF)
|
|
gmr_opts.set_defaults(CONF)
|
|
|
|
CONF(sys.argv[1:], default_config_files=['myapp.conf'])
|
|
oslo_logging.setup(CONF, 'myapp')
|
|
|
|
gmr.TextGuruMeditation.register_section('Some Special Section',
|
|
special_section_generator)
|
|
gmr.TextGuruMeditation.setup_autorun(version_object, conf=CONF)
|
|
|
|
server = service.Service.create(binary='some-service',
|
|
topic=CONF.some_service_topic)
|
|
service.serve(server)
|
|
service.wait()
|
|
|
|
Then, you can do
|
|
|
|
.. code-block:: bash
|
|
|
|
$ kill -USR2 $SERVICE_PID
|
|
|
|
and get a Guru Meditation Report in the file or terminal
|
|
where stderr is logged for that given service.
|
|
"""
|
|
|
|
from __future__ import print_function
|
|
|
|
import inspect
|
|
import logging
|
|
import os
|
|
import signal
|
|
import stat
|
|
import sys
|
|
import threading
|
|
import time
|
|
import traceback
|
|
|
|
from oslo_utils import timeutils
|
|
|
|
from oslo_reports._i18n import _LE
|
|
from oslo_reports._i18n import _LW
|
|
from oslo_reports.generators import conf as cgen
|
|
from oslo_reports.generators import process as prgen
|
|
from oslo_reports.generators import threading as tgen
|
|
from oslo_reports.generators import version as pgen
|
|
from oslo_reports import report
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
class GuruMeditation(object):
|
|
"""A Guru Meditation Report Mixin/Base Class
|
|
|
|
This class is a base class for Guru Meditation Reports.
|
|
It provides facilities for registering sections and
|
|
setting up functionality to auto-run the report on
|
|
a certain signal or use file modification events.
|
|
|
|
This class should always be used in conjunction with
|
|
a Report class via multiple inheritance. It should
|
|
always come first in the class list to ensure the
|
|
MRO is correct.
|
|
"""
|
|
|
|
timestamp_fmt = "%Y%m%d%H%M%S"
|
|
|
|
def __init__(self, version_obj, sig_handler_tb=None, *args, **kwargs):
|
|
self.version_obj = version_obj
|
|
self.traceback = sig_handler_tb
|
|
|
|
super(GuruMeditation, self).__init__(*args, **kwargs)
|
|
self.start_section_index = len(self.sections)
|
|
|
|
@classmethod
|
|
def register_section(cls, section_title, generator):
|
|
"""Register a New Section
|
|
|
|
This method registers a persistent section for the current
|
|
class.
|
|
|
|
:param str section_title: the title of the section
|
|
:param generator: the generator for the section
|
|
"""
|
|
|
|
try:
|
|
cls.persistent_sections.append([section_title, generator])
|
|
except AttributeError:
|
|
cls.persistent_sections = [[section_title, generator]]
|
|
|
|
@classmethod
|
|
def setup_autorun(cls, version, service_name=None,
|
|
log_dir=None, signum=None, conf=None):
|
|
"""Set Up Auto-Run
|
|
|
|
This method sets up the Guru Meditation Report to automatically
|
|
get dumped to stderr or a file in a given dir when the given signal
|
|
is received. It can also use file modification events instead of
|
|
signals.
|
|
|
|
:param version: the version object for the current product
|
|
:param service_name: this program name used to construct logfile name
|
|
:param logdir: path to a log directory where to create a file
|
|
:param signum: the signal to associate with running the report
|
|
:param conf: Configuration object, managed by the caller.
|
|
"""
|
|
|
|
if log_dir is None and conf is not None:
|
|
log_dir = conf.oslo_reports.log_dir
|
|
|
|
if signum:
|
|
cls._setup_signal(signum, version, service_name, log_dir)
|
|
return
|
|
|
|
if conf and conf.oslo_reports.file_event_handler:
|
|
cls._setup_file_watcher(
|
|
conf.oslo_reports.file_event_handler,
|
|
conf.oslo_reports.file_event_handler_interval,
|
|
version, service_name, log_dir)
|
|
else:
|
|
if hasattr(signal, 'SIGUSR1'):
|
|
# TODO(dims) We need to remove this in the "O" release cycle
|
|
LOG.warning(_LW("Guru meditation now registers SIGUSR1 and "
|
|
"SIGUSR2 by default for backward "
|
|
"compatibility. SIGUSR1 will no longer be "
|
|
"registered in a future release, so please "
|
|
"use SIGUSR2 to generate reports."))
|
|
cls._setup_signal(signal.SIGUSR1,
|
|
version, service_name, log_dir)
|
|
if hasattr(signal, 'SIGUSR2'):
|
|
cls._setup_signal(signal.SIGUSR2,
|
|
version, service_name, log_dir)
|
|
|
|
@classmethod
|
|
def _setup_file_watcher(cls, filepath, interval, version, service_name,
|
|
log_dir):
|
|
|
|
st = os.stat(filepath)
|
|
if not bool(st.st_mode & stat.S_IRGRP):
|
|
LOG.error(_LE("Guru Meditation Report does not have read "
|
|
"permissions to '%s' file."), filepath)
|
|
|
|
def _handler():
|
|
mtime = time.time()
|
|
while True:
|
|
try:
|
|
stat = os.stat(filepath)
|
|
if stat.st_mtime > mtime:
|
|
cls.handle_signal(version, service_name, log_dir, None)
|
|
mtime = stat.st_mtime
|
|
except OSError:
|
|
msg = ("Guru Meditation Report cannot read " +
|
|
"'{0}' file".format(filepath))
|
|
raise IOError(msg)
|
|
finally:
|
|
time.sleep(interval)
|
|
|
|
th = threading.Thread(target=_handler)
|
|
th.daemon = True
|
|
th.start()
|
|
|
|
@classmethod
|
|
def _setup_signal(cls, signum, version, service_name, log_dir):
|
|
signal.signal(signum,
|
|
lambda sn, f: cls.handle_signal(
|
|
version, service_name, log_dir, f))
|
|
|
|
@classmethod
|
|
def handle_signal(cls, version, service_name, log_dir, frame):
|
|
"""The Signal Handler
|
|
|
|
This method (indirectly) handles receiving a registered signal and
|
|
dumping the Guru Meditation Report to stderr or a file in a given dir.
|
|
If service name and log dir are not None, the report will be dumped to
|
|
a file named $service_name_gurumeditation_$current_time in the log_dir
|
|
directory.
|
|
This method is designed to be curried into a proper signal handler by
|
|
currying out the version
|
|
parameter.
|
|
|
|
:param version: the version object for the current product
|
|
:param service_name: this program name used to construct logfile name
|
|
:param logdir: path to a log directory where to create a file
|
|
:param frame: the frame object provided to the signal handler
|
|
"""
|
|
|
|
try:
|
|
res = cls(version, frame).run()
|
|
except Exception:
|
|
traceback.print_exc(file=sys.stderr)
|
|
print("Unable to run Guru Meditation Report!",
|
|
file=sys.stderr)
|
|
else:
|
|
if log_dir:
|
|
service_name = service_name or os.path.basename(
|
|
inspect.stack()[-1][1])
|
|
filename = "%s_gurumeditation_%s" % (
|
|
service_name, timeutils.utcnow().strftime(
|
|
cls.timestamp_fmt))
|
|
filepath = os.path.join(log_dir, filename)
|
|
try:
|
|
with open(filepath, "w") as dumpfile:
|
|
dumpfile.write(res)
|
|
except Exception:
|
|
print("Unable to dump Guru Meditation Report to file %s" %
|
|
(filepath,), file=sys.stderr)
|
|
else:
|
|
print(res, file=sys.stderr)
|
|
|
|
def _readd_sections(self):
|
|
del self.sections[self.start_section_index:]
|
|
|
|
self.add_section('Package',
|
|
pgen.PackageReportGenerator(self.version_obj))
|
|
|
|
self.add_section('Threads',
|
|
tgen.ThreadReportGenerator(self.traceback))
|
|
|
|
self.add_section('Green Threads',
|
|
tgen.GreenThreadReportGenerator())
|
|
|
|
self.add_section('Processes',
|
|
prgen.ProcessReportGenerator())
|
|
|
|
self.add_section('Configuration',
|
|
cgen.ConfigReportGenerator())
|
|
|
|
try:
|
|
for section_title, generator in self.persistent_sections:
|
|
self.add_section(section_title, generator)
|
|
except AttributeError:
|
|
pass
|
|
|
|
def run(self):
|
|
self._readd_sections()
|
|
return super(GuruMeditation, self).run()
|
|
|
|
|
|
# GuruMeditation must come first to get the correct MRO
|
|
class TextGuruMeditation(GuruMeditation, report.TextReport):
|
|
"""A Text Guru Meditation Report
|
|
|
|
This report is the basic human-readable Guru Meditation Report
|
|
|
|
It contains the following sections by default
|
|
(in addition to any registered persistent sections):
|
|
|
|
- Package Information
|
|
|
|
- Threads List
|
|
|
|
- Green Threads List
|
|
|
|
- Process List
|
|
|
|
- Configuration Options
|
|
|
|
:param version_obj: the version object for the current product
|
|
:param traceback: an (optional) frame object providing the actual
|
|
traceback for the current thread
|
|
"""
|
|
|
|
def __init__(self, version_obj, traceback=None):
|
|
super(TextGuruMeditation, self).__init__(version_obj, traceback,
|
|
'Guru Meditation')
|