Introduces The Guru Meditation Report
Introduces the classes for the actual Guru Meditation Report, and utilities for creating a hook to autorun it on SIGUSR1. The Text version is introduced in this commit; it is called TextGuruMeditation. This is the last of the Guru Meditation Report/Openstack Common Reporting Framework commits. Change-Id: I89cf868c0b515b6caf20987bfa2d6b73c860672a Implements: bp guru-meditation-report
This commit is contained in:
182
openstack/common/report/guru_meditation_report.py
Normal file
182
openstack/common/report/guru_meditation_report.py
Normal file
@@ -0,0 +1,182 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# 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
|
||||
|
||||
CONF = cfg.CONF
|
||||
# maybe import some options here...
|
||||
|
||||
def main():
|
||||
config.parse_args(sys.argv)
|
||||
logging.setup('blah')
|
||||
|
||||
TextGuruMeditation.register_section('Some Special Section',
|
||||
special_section_generator)
|
||||
TextGuruMeditation.setup_autorun(version_object)
|
||||
|
||||
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 -USR1 $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 signal
|
||||
import sys
|
||||
|
||||
from openstack.common.report.generators import conf as cgen
|
||||
from openstack.common.report.generators import threading as tgen
|
||||
from openstack.common.report.generators import version as pgen
|
||||
from openstack.common.report import report
|
||||
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
def __init__(self, version_obj, *args, **kwargs):
|
||||
self.version_obj = version_obj
|
||||
|
||||
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, signum=signal.SIGUSR1):
|
||||
"""Set Up Auto-Run
|
||||
|
||||
This method sets up the Guru Meditation Report to automatically
|
||||
get dumped to stderr when the given signal is received.
|
||||
|
||||
:param version: the version object for the current product
|
||||
:param signum: the signal to associate with running the report
|
||||
"""
|
||||
|
||||
signal.signal(signum, lambda *args: cls.handle_signal(version, *args))
|
||||
|
||||
@classmethod
|
||||
def handle_signal(cls, version, *args):
|
||||
"""The Signal Handler
|
||||
|
||||
This method (indirectly) handles receiving a registered signal and
|
||||
dumping the Guru Meditation Report to stderr. 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
|
||||
"""
|
||||
|
||||
try:
|
||||
res = cls(version).run()
|
||||
except Exception:
|
||||
print("Unable to run Guru Meditation Report!",
|
||||
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.add_section('Green Threads',
|
||||
tgen.GreenThreadReportGenerator())
|
||||
|
||||
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
|
||||
|
||||
- Configuration Options
|
||||
|
||||
:param version_obj: the version object for the current product
|
||||
"""
|
||||
|
||||
def __init__(self, version_obj):
|
||||
super(TextGuruMeditation, self).__init__(version_obj,
|
||||
'Guru Meditation')
|
||||
154
tests/unit/reports/test_guru_meditation_report.py
Normal file
154
tests/unit/reports/test_guru_meditation_report.py
Normal file
@@ -0,0 +1,154 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# 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.
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
import re
|
||||
import signal
|
||||
import StringIO
|
||||
import sys
|
||||
|
||||
# needed to get greenthreads
|
||||
import greenlet
|
||||
|
||||
from openstack.common.report import guru_meditation_report as gmr
|
||||
from openstack.common.report.models import with_default_views as mwdv
|
||||
from tests import utils
|
||||
|
||||
|
||||
class FakeVersionObj(object):
|
||||
def vendor_string(self):
|
||||
return 'Cheese Shoppe'
|
||||
|
||||
def product_string(self):
|
||||
return 'Sharp Cheddar'
|
||||
|
||||
def version_string_with_package(self):
|
||||
return '1.0.0'
|
||||
|
||||
|
||||
def skip_body_lines(start_line, report_lines):
|
||||
curr_line = start_line
|
||||
while (len(report_lines[curr_line]) == 0
|
||||
or report_lines[curr_line][0] != '='):
|
||||
curr_line += 1
|
||||
|
||||
return curr_line
|
||||
|
||||
|
||||
class TestGuruMeditationReport(utils.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(TestGuruMeditationReport, self).setUp()
|
||||
|
||||
self.curr_g = greenlet.getcurrent()
|
||||
|
||||
self.report = gmr.TextGuruMeditation(FakeVersionObj())
|
||||
|
||||
self.old_stderr = None
|
||||
|
||||
def test_basic_report(self):
|
||||
report_lines = self.report.run().split('\n')
|
||||
|
||||
target_str_header = ['========================================================================', # noqa
|
||||
'==== Guru Meditation ====', # noqa
|
||||
'========================================================================', # noqa
|
||||
'||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||', # noqa
|
||||
'',
|
||||
'',
|
||||
'========================================================================', # noqa
|
||||
'==== Package ====', # noqa
|
||||
'========================================================================', # noqa
|
||||
'product = Sharp Cheddar',
|
||||
'version = 1.0.0',
|
||||
'vendor = Cheese Shoppe',
|
||||
'========================================================================', # noqa
|
||||
'==== Threads ====', # noqa
|
||||
'========================================================================'] # noqa
|
||||
|
||||
# first the header and version info...
|
||||
self.assertEquals(target_str_header,
|
||||
report_lines[0:len(target_str_header)])
|
||||
|
||||
# followed by at least one thread...
|
||||
self.assertTrue(re.match(r'------(\s+)Thread #\d+\1\s?------',
|
||||
report_lines[len(target_str_header)]))
|
||||
self.assertEquals('', report_lines[len(target_str_header) + 1])
|
||||
|
||||
# followed by more thread stuff stuff...
|
||||
curr_line = skip_body_lines(len(target_str_header) + 2, report_lines)
|
||||
|
||||
# followed by at least one green thread
|
||||
target_str_gt = ['========================================================================', # noqa
|
||||
'==== Green Threads ====', # noqa
|
||||
'========================================================================', # noqa
|
||||
'------ Green Thread ------', # noqa
|
||||
'']
|
||||
end_bound = curr_line + len(target_str_gt)
|
||||
self.assertEquals(target_str_gt,
|
||||
report_lines[curr_line:end_bound])
|
||||
|
||||
# followed by some more green thread stuff
|
||||
curr_line = skip_body_lines(curr_line + len(target_str_gt),
|
||||
report_lines)
|
||||
|
||||
# followed finally by the configuration
|
||||
target_str_config = ['========================================================================', # noqa
|
||||
'==== Configuration ====', # noqa
|
||||
'========================================================================', # noqa
|
||||
'']
|
||||
end_bound = curr_line + len(target_str_config)
|
||||
self.assertEquals(target_str_config,
|
||||
report_lines[curr_line:end_bound])
|
||||
|
||||
def test_reg_persistent_section(self):
|
||||
def fake_gen():
|
||||
fake_data = {'cheddar': ['sharp', 'mild'],
|
||||
'swiss': ['with holes', 'with lots of holes'],
|
||||
'american': ['orange', 'yellow']}
|
||||
|
||||
return mwdv.ModelWithDefaultViews(data=fake_data)
|
||||
|
||||
gmr.TextGuruMeditation.register_section('Cheese Types', fake_gen)
|
||||
|
||||
report_lines = self.report.run()
|
||||
target_lst = ['========================================================================', # noqa
|
||||
'==== Cheese Types ====', # noqa
|
||||
'========================================================================', # noqa
|
||||
'swiss = ',
|
||||
' with holes',
|
||||
' with lots of holes',
|
||||
'american = ',
|
||||
' orange',
|
||||
' yellow',
|
||||
'cheddar = ',
|
||||
' sharp',
|
||||
' mild']
|
||||
target_str = '\n'.join(target_lst)
|
||||
self.assertIn(target_str, report_lines)
|
||||
|
||||
def test_register_autorun(self):
|
||||
gmr.TextGuruMeditation.setup_autorun(FakeVersionObj())
|
||||
self.old_stderr = sys.stderr
|
||||
sys.stderr = StringIO.StringIO()
|
||||
|
||||
os.kill(os.getpid(), signal.SIGUSR1)
|
||||
self.assertIn('Guru Meditation', sys.stderr.getvalue())
|
||||
|
||||
def tearDown(self):
|
||||
super(TestGuruMeditationReport, self).tearDown()
|
||||
if self.old_stderr is not None:
|
||||
sys.stderr = self.old_stderr
|
||||
Reference in New Issue
Block a user