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:
Solly Ross
2013-07-09 14:15:43 -04:00
parent 39c59eb3d1
commit 851d0ec901
2 changed files with 336 additions and 0 deletions

View 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')

View 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