Use file modification events instead of signal handler
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
This commit is contained in:
parent
71ce8f5f30
commit
d23e0a65b2
@ -63,11 +63,15 @@ 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
|
||||
@ -75,6 +79,7 @@ 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__)
|
||||
|
||||
|
||||
@ -84,7 +89,7 @@ class GuruMeditation(object):
|
||||
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.
|
||||
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
|
||||
@ -124,7 +129,8 @@ class GuruMeditation(object):
|
||||
|
||||
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.
|
||||
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
|
||||
@ -140,16 +146,52 @@ class GuruMeditation(object):
|
||||
cls._setup_signal(signum, version, service_name, log_dir)
|
||||
return
|
||||
|
||||
if hasattr(signal, 'SIGUSR1'):
|
||||
# TODO(dims) We need to remove this in the "O" release cycle
|
||||
LOG.warning(_LW("Guru mediation 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)
|
||||
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):
|
||||
|
@ -27,6 +27,16 @@ _option_group = 'oslo_reports'
|
||||
_options = [
|
||||
cfg.StrOpt('log_dir',
|
||||
help=_('Path to a log directory where to create a file')),
|
||||
cfg.StrOpt('file_event_handler',
|
||||
help=_('The path to a file to watch for changes to trigger '
|
||||
'the reports, instead of signals. Setting this option '
|
||||
'disables the signal trigger for the reports. If '
|
||||
'application is running as a WSGI application it is '
|
||||
'recommended to use this instead of signals.')),
|
||||
cfg.IntOpt('file_event_handler_interval',
|
||||
default=1,
|
||||
help=_('How many seconds to wait between polls when '
|
||||
'file_event_handler is set'))
|
||||
]
|
||||
|
||||
|
||||
|
@ -19,6 +19,7 @@ import os
|
||||
import re
|
||||
import signal
|
||||
import sys
|
||||
import threading
|
||||
|
||||
# needed to get greenthreads
|
||||
import fixtures
|
||||
@ -27,8 +28,15 @@ import mock
|
||||
from oslotest import base
|
||||
import six
|
||||
|
||||
import oslo_config
|
||||
from oslo_config import fixture
|
||||
from oslo_reports import guru_meditation_report as gmr
|
||||
from oslo_reports.models import with_default_views as mwdv
|
||||
from oslo_reports import opts
|
||||
|
||||
|
||||
CONF = oslo_config.cfg.CONF
|
||||
opts.set_defaults(CONF)
|
||||
|
||||
|
||||
class FakeVersionObj(object):
|
||||
@ -51,6 +59,24 @@ def skip_body_lines(start_line, report_lines):
|
||||
return curr_line
|
||||
|
||||
|
||||
class GmrConfigFixture(fixture.Config):
|
||||
def setUp(self):
|
||||
super(GmrConfigFixture, self).setUp()
|
||||
|
||||
self.conf.set_override(
|
||||
'file_event_handler',
|
||||
'/tmp/file',
|
||||
group='oslo_reports')
|
||||
self.conf.set_override(
|
||||
'file_event_handler_interval',
|
||||
10,
|
||||
group='oslo_reports')
|
||||
self.conf.set_override(
|
||||
'log_dir',
|
||||
'/var/fake_log',
|
||||
group='oslo_reports')
|
||||
|
||||
|
||||
class TestGuruMeditationReport(base.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(TestGuruMeditationReport, self).setUp()
|
||||
@ -61,6 +87,8 @@ class TestGuruMeditationReport(base.BaseTestCase):
|
||||
|
||||
self.old_stderr = None
|
||||
|
||||
self.CONF = self.useFixture(GmrConfigFixture(CONF)).conf
|
||||
|
||||
def test_basic_report(self):
|
||||
report_lines = self.report.run().split('\n')
|
||||
|
||||
@ -168,6 +196,28 @@ class TestGuruMeditationReport(base.BaseTestCase):
|
||||
os.kill(os.getpid(), signal.SIGUSR2)
|
||||
self.assertIn('Guru Meditation', sys.stderr.getvalue())
|
||||
|
||||
@mock.patch.object(gmr.TextGuruMeditation, '_setup_file_watcher')
|
||||
def test_register_autorun_without_signals(self, mock_setup_fh):
|
||||
version = FakeVersionObj()
|
||||
gmr.TextGuruMeditation.setup_autorun(version, conf=self.CONF)
|
||||
mock_setup_fh.assert_called_once_with(
|
||||
'/tmp/file', 10, version, None, '/var/fake_log')
|
||||
|
||||
@mock.patch('os.stat')
|
||||
@mock.patch('time.sleep')
|
||||
@mock.patch.object(threading.Thread, 'start')
|
||||
def test_setup_file_watcher(self, mock_thread, mock_sleep, mock_stat):
|
||||
version = FakeVersionObj()
|
||||
mock_stat.return_value.st_mtime = 3
|
||||
|
||||
gmr.TextGuruMeditation._setup_file_watcher(
|
||||
self.CONF.oslo_reports.file_event_handler,
|
||||
self.CONF.oslo_reports.file_event_handler_interval,
|
||||
version, None, self.CONF.oslo_reports.log_dir)
|
||||
|
||||
mock_stat.assert_called_once_with('/tmp/file')
|
||||
self.assertEqual(1, mock_thread.called)
|
||||
|
||||
@mock.patch('oslo_utils.timeutils.utcnow',
|
||||
return_value=datetime.datetime(2014, 1, 1, 12, 0, 0))
|
||||
def test_register_autorun_log_dir(self, mock_strtime):
|
||||
|
Loading…
Reference in New Issue
Block a user