Created
March 3, 2014 23:38
-
-
Save DirectXMan12/9337007 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/bin/swift-init b/bin/swift-init | |
index 3c992f7..c5c3e8c 100755 | |
--- a/bin/swift-init | |
+++ b/bin/swift-init | |
@@ -17,6 +17,8 @@ | |
import sys | |
from optparse import OptionParser | |
+from swift.openstack.common.report import guru_meditation_report as gmr | |
+ | |
from swift.common.manager import Manager, UnknownCommandError, \ | |
KILL_WAIT, RUN_DIR | |
@@ -25,6 +27,15 @@ USAGE = """%prog <server> [<server> ...] <command> [options] | |
Commands: | |
""" + '\n'.join(["%16s: %s" % x for x in Manager.list_commands()]) | |
+class VerInfo(object): | |
+ def vendor_string(self): | |
+ return 'v' | |
+ | |
+ def product_string(self): | |
+ return 'p' | |
+ | |
+ def version_string_with_package(self): | |
+ return 'vp' | |
def main(): | |
parser = OptionParser(USAGE) | |
@@ -61,6 +72,8 @@ def main(): | |
command = args[-1] | |
servers = args[:-1] | |
+ gmr.TextGuruMeditation.setup_autorun(VerInfo()) | |
+ | |
# this is just a silly swap for me cause I always try to "start main" | |
commands = dict(Manager.list_commands()).keys() | |
if command not in commands and servers[0] in commands: | |
diff --git a/bin/swift-object-server b/bin/swift-object-server | |
index a3306b2..0e2a240 100755 | |
--- a/bin/swift-object-server | |
+++ b/bin/swift-object-server | |
@@ -19,9 +19,21 @@ from swift.common.utils import parse_options | |
from swift.common.wsgi import run_wsgi | |
from swift.obj import server | |
+from swift.openstack.common.report import guru_meditation_report as gmr | |
+ | |
+class VerInfo(object): | |
+ def vendor_string(self): | |
+ return 'v' | |
+ | |
+ def product_string(self): | |
+ return 'p' | |
+ | |
+ def version_string_with_package(self): | |
+ return 'vp' | |
if __name__ == '__main__': | |
conf_file, options = parse_options() | |
+ gmr.TextGuruMeditation.setup_autorun(VerInfo()) | |
sys.exit(run_wsgi(conf_file, 'object-server', default_port=6000, | |
global_conf_callback=server.global_conf_callback, | |
**options)) | |
diff --git a/swift/openstack/__init__.py b/swift/openstack/__init__.py | |
new file mode 100644 | |
index 0000000..e69de29 | |
diff --git a/swift/openstack/common/__init__.py b/swift/openstack/common/__init__.py | |
new file mode 100644 | |
index 0000000..e69de29 | |
diff --git a/swift/openstack/common/report/__init__.py b/swift/openstack/common/report/__init__.py | |
new file mode 100644 | |
index 0000000..35390ec | |
--- /dev/null | |
+++ b/swift/openstack/common/report/__init__.py | |
@@ -0,0 +1,25 @@ | |
+# 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 a way to generate serializable reports | |
+ | |
+This package/module provides mechanisms for defining reports | |
+which may then be serialized into various data types. Each | |
+report ( :class:`openstack.common.report.report.BasicReport` ) | |
+is composed of one or more report sections | |
+( :class:`openstack.common.report.report.BasicSection` ), | |
+which contain generators which generate data models | |
+( :class:`openstack.common.report.models.base.ReportModels` ), | |
+which are then serialized by views. | |
+""" | |
diff --git a/swift/openstack/common/report/generators/__init__.py b/swift/openstack/common/report/generators/__init__.py | |
new file mode 100644 | |
index 0000000..68473f2 | |
--- /dev/null | |
+++ b/swift/openstack/common/report/generators/__init__.py | |
@@ -0,0 +1,21 @@ | |
+# 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 Data Model Generators | |
+ | |
+This module defines classes for generating data models | |
+( :class:`openstack.common.report.models.base.ReportModel` ). | |
+A generator is any object which is callable with no parameters | |
+and returns a data model. | |
+""" | |
diff --git a/swift/openstack/common/report/generators/conf.py b/swift/openstack/common/report/generators/conf.py | |
new file mode 100644 | |
index 0000000..bcb2bc5 | |
--- /dev/null | |
+++ b/swift/openstack/common/report/generators/conf.py | |
@@ -0,0 +1,44 @@ | |
+# 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 Openstack config generators | |
+ | |
+This module defines a class for configuration | |
+generators for generating the model in | |
+:mod:`openstack.common.report.models.conf`. | |
+""" | |
+ | |
+from oslo.config import cfg | |
+ | |
+import swift.openstack.common.report.models.conf as cm | |
+ | |
+ | |
+class ConfigReportGenerator(object): | |
+ """A Configuration Data Generator | |
+ | |
+ This generator returns | |
+ :class:`openstack.common.report.models.conf.ConfigModel` , | |
+ by default using the configuration options stored | |
+ in :attr:`oslo.config.cfg.CONF`, which is where | |
+ Openstack stores everything. | |
+ | |
+ :param cnf: the configuration option object | |
+ :type cnf: :class:`oslo.config.cfg.ConfigOpts` | |
+ """ | |
+ | |
+ def __init__(self, cnf=cfg.CONF): | |
+ self.conf_obj = cnf | |
+ | |
+ def __call__(self): | |
+ return cm.ConfigModel(self.conf_obj) | |
diff --git a/swift/openstack/common/report/generators/threading.py b/swift/openstack/common/report/generators/threading.py | |
new file mode 100644 | |
index 0000000..9a2914f | |
--- /dev/null | |
+++ b/swift/openstack/common/report/generators/threading.py | |
@@ -0,0 +1,77 @@ | |
+# 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 thread-related generators | |
+ | |
+This module defines classes for threading-related | |
+generators for generating the models in | |
+:mod:`openstack.common.report.models.threading`. | |
+""" | |
+ | |
+import sys | |
+ | |
+import greenlet | |
+ | |
+import swift.openstack.common.report.models.threading as tm | |
+from swift.openstack.common.report.models import with_default_views as mwdv | |
+import swift.openstack.common.report.utils as rutils | |
+import swift.openstack.common.report.views.text.generic as text_views | |
+ | |
+ | |
+class ThreadReportGenerator(object): | |
+ """A Thread Data Generator | |
+ | |
+ This generator returns a collection of | |
+ :class:`openstack.common.report.models.threading.ThreadModel` | |
+ objects by introspecting the current python state using | |
+ :func:`sys._current_frames()` . | |
+ """ | |
+ | |
+ def __init__(self, curr_frame): | |
+ self.curr_frame = curr_frame | |
+ | |
+ def __call__(self): | |
+ threadModels = [ | |
+ tm.ThreadModel(thread_id, stack) | |
+ for thread_id, stack in sys._current_frames().items() | |
+ ] | |
+ threadModels.append(tm.ThreadModel(0, self.curr_frame)) | |
+ | |
+ thread_pairs = dict(zip(range(len(threadModels)), threadModels)) | |
+ return mwdv.ModelWithDefaultViews(thread_pairs, | |
+ text_view=text_views.MultiView()) | |
+ | |
+ | |
+class GreenThreadReportGenerator(object): | |
+ """A Green Thread Data Generator | |
+ | |
+ This generator returns a collection of | |
+ :class:`openstack.common.report.models.threading.GreenThreadModel` | |
+ objects by introspecting the current python garbage collection | |
+ state, and sifting through for :class:`greenlet.greenlet` objects. | |
+ | |
+ .. seealso:: | |
+ | |
+ Function :func:`openstack.common.report.utils._find_objects` | |
+ """ | |
+ | |
+ def __call__(self): | |
+ threadModels = [ | |
+ tm.GreenThreadModel(gr.gr_frame) | |
+ for gr in rutils._find_objects(greenlet.greenlet) | |
+ ] | |
+ | |
+ thread_pairs = dict(zip(range(len(threadModels)), threadModels)) | |
+ return mwdv.ModelWithDefaultViews(thread_pairs, | |
+ text_view=text_views.MultiView()) | |
diff --git a/swift/openstack/common/report/generators/version.py b/swift/openstack/common/report/generators/version.py | |
new file mode 100644 | |
index 0000000..d0cf54e | |
--- /dev/null | |
+++ b/swift/openstack/common/report/generators/version.py | |
@@ -0,0 +1,46 @@ | |
+# 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 Openstack version generators | |
+ | |
+This module defines a class for Openstack | |
+version and package information | |
+generators for generating the model in | |
+:mod:`openstack.common.report.models.version`. | |
+""" | |
+ | |
+import swift.openstack.common.report.models.version as vm | |
+ | |
+ | |
+class PackageReportGenerator(object): | |
+ """A Package Information Data Generator | |
+ | |
+ This generator returns | |
+ :class:`openstack.common.report.models.version.PackageModel`, | |
+ extracting data from the given version object, which should follow | |
+ the general format defined in Nova's version information (i.e. it | |
+ should contain the methods vendor_string, product_string, and | |
+ version_string_with_package). | |
+ | |
+ :param version_object: the version information object | |
+ """ | |
+ | |
+ def __init__(self, version_obj): | |
+ self.version_obj = version_obj | |
+ | |
+ def __call__(self): | |
+ return vm.PackageModel( | |
+ self.version_obj.vendor_string(), | |
+ self.version_obj.product_string(), | |
+ self.version_obj.version_string_with_package()) | |
diff --git a/swift/openstack/common/report/guru_meditation_report.py b/swift/openstack/common/report/guru_meditation_report.py | |
new file mode 100644 | |
index 0000000..c7309ec | |
--- /dev/null | |
+++ b/swift/openstack/common/report/guru_meditation_report.py | |
@@ -0,0 +1,188 @@ | |
+# 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 swift command module (under swift/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 swift.openstack.common.report.generators import conf as cgen | |
+from swift.openstack.common.report.generators import threading as tgen | |
+from swift.openstack.common.report.generators import version as pgen | |
+from swift.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, curr_frame, *args, **kwargs): | |
+ self.version_obj = version_obj | |
+ self.curr_frame = curr_frame | |
+ | |
+ 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=None): | |
+ """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 | |
+ """ | |
+ | |
+ if not signum and hasattr(signal, 'SIGUSR1'): | |
+ # SIGUSR1 is not supported on all platforms | |
+ signum = signal.SIGUSR1 | |
+ | |
+ if signum: | |
+ 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, args[1]).run() | |
+ except Exception: | |
+ print("Unable to run Guru Meditation Report!", | |
+ file=sys.stderr) | |
+ raise | |
+ 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.curr_frame)) | |
+ | |
+ 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, curr_frame): | |
+ super(TextGuruMeditation, self).__init__(version_obj, curr_frame, | |
+ 'Guru Meditation') | |
diff --git a/swift/openstack/common/report/models/__init__.py b/swift/openstack/common/report/models/__init__.py | |
new file mode 100644 | |
index 0000000..7bfed3d | |
--- /dev/null | |
+++ b/swift/openstack/common/report/models/__init__.py | |
@@ -0,0 +1,20 @@ | |
+# 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 data models | |
+ | |
+This module provides both the base data model, | |
+as well as several predefined specific data models | |
+to be used in reports. | |
+""" | |
diff --git a/swift/openstack/common/report/models/base.py b/swift/openstack/common/report/models/base.py | |
new file mode 100644 | |
index 0000000..90914ff | |
--- /dev/null | |
+++ b/swift/openstack/common/report/models/base.py | |
@@ -0,0 +1,114 @@ | |
+# 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 the base report model | |
+ | |
+This module defines a class representing the basic report | |
+data model from which all data models should inherit (or | |
+at least implement similar functionality). Data models | |
+store unserialized data generated by generators during | |
+the report serialization process. | |
+""" | |
+ | |
+import collections as col | |
+import copy | |
+ | |
+ | |
+class ReportModel(col.MutableMapping): | |
+ """A Report Data Model | |
+ | |
+ A report data model contains data generated by some | |
+ generator method or class. Data may be read or written | |
+ using dictionary-style access, and may be read (but not | |
+ written) using object-member-style access. Additionally, | |
+ a data model may have an associated view. This view is | |
+ used to serialize the model when str() is called on the | |
+ model. An appropriate object for a view is callable with | |
+ a single parameter: the model to be serialized. | |
+ | |
+ :param data: a dictionary of data to initially associate with the model | |
+ :param attached_view: a view object to attach to this model | |
+ """ | |
+ | |
+ def __init__(self, data=None, attached_view=None): | |
+ self.attached_view = attached_view | |
+ self.data = data or {} | |
+ | |
+ def __str__(self): | |
+ self_cpy = copy.deepcopy(self) | |
+ for key in self_cpy: | |
+ if getattr(self_cpy[key], 'attached_view', None) is not None: | |
+ self_cpy[key] = str(self_cpy[key]) | |
+ | |
+ if self.attached_view is not None: | |
+ return self.attached_view(self_cpy) | |
+ else: | |
+ raise Exception("Cannot stringify model: no attached view") | |
+ | |
+ def __repr__(self): | |
+ if self.attached_view is not None: | |
+ return ("<Model {cl.__module__}.{cl.__name__} {dt}" | |
+ " with view {vw.__module__}." | |
+ "{vw.__name__}>").format(cl=type(self), | |
+ dt=self.data, | |
+ vw=type(self.attached_view)) | |
+ else: | |
+ return ("<Model {cl.__module__}.{cl.__name__} {dt}" | |
+ " with no view>").format(cl=type(self), | |
+ dt=self.data) | |
+ | |
+ def __getitem__(self, attrname): | |
+ return self.data[attrname] | |
+ | |
+ def __setitem__(self, attrname, attrval): | |
+ self.data[attrname] = attrval | |
+ | |
+ def __delitem__(self, attrname): | |
+ del self.data[attrname] | |
+ | |
+ def __contains__(self, key): | |
+ return self.data.__contains__(key) | |
+ | |
+ def __getattr__(self, attrname): | |
+ try: | |
+ return self.data[attrname] | |
+ except KeyError: | |
+ raise AttributeError( | |
+ "'{cl}' object has no attribute '{an}'".format( | |
+ cl=type(self).__name__, an=attrname | |
+ ) | |
+ ) | |
+ | |
+ def __len__(self): | |
+ return len(self.data) | |
+ | |
+ def __iter__(self): | |
+ return self.data.__iter__() | |
+ | |
+ def set_current_view_type(self, tp): | |
+ """Set the current view type | |
+ | |
+ This method attempts to set the current view | |
+ type for this model and all submodels by calling | |
+ itself recursively on all values (and ignoring the | |
+ ones that are not themselves models) | |
+ | |
+ :param tp: the type of the view ('text', 'json', 'xml', etc) | |
+ """ | |
+ | |
+ for key in self: | |
+ try: | |
+ self[key].set_current_view_type(tp) | |
+ except AttributeError: | |
+ pass | |
diff --git a/swift/openstack/common/report/models/conf.py b/swift/openstack/common/report/models/conf.py | |
new file mode 100644 | |
index 0000000..553b52d | |
--- /dev/null | |
+++ b/swift/openstack/common/report/models/conf.py | |
@@ -0,0 +1,58 @@ | |
+# 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 Openstack Configuration Model | |
+ | |
+This module defines a class representing the data | |
+model for :mod:`oslo.config` configuration options | |
+""" | |
+ | |
+import swift.openstack.common.report.models.with_default_views as mwdv | |
+import swift.openstack.common.report.views.text.generic as generic_text_views | |
+ | |
+ | |
+class ConfigModel(mwdv.ModelWithDefaultViews): | |
+ """A Configuration Options Model | |
+ | |
+ This model holds data about a set of configuration options | |
+ from :mod:`oslo.config`. It supports both the default group | |
+ of options and named option groups. | |
+ | |
+ :param conf_obj: a configuration object | |
+ :type conf_obj: :class:`oslo.config.cfg.ConfigOpts` | |
+ """ | |
+ | |
+ def __init__(self, conf_obj): | |
+ kv_view = generic_text_views.KeyValueView(dict_sep=": ", | |
+ before_dict='') | |
+ super(ConfigModel, self).__init__(text_view=kv_view) | |
+ | |
+ def opt_title(optname, co): | |
+ return co._opts[optname]['opt'].name | |
+ | |
+ self['default'] = dict( | |
+ (opt_title(optname, conf_obj), conf_obj[optname]) | |
+ for optname in conf_obj._opts | |
+ ) | |
+ | |
+ groups = {} | |
+ for groupname in conf_obj._groups: | |
+ group_obj = conf_obj._groups[groupname] | |
+ curr_group_opts = dict( | |
+ (opt_title(optname, group_obj), conf_obj[groupname][optname]) | |
+ for optname in group_obj._opts | |
+ ) | |
+ groups[group_obj.name] = curr_group_opts | |
+ | |
+ self.update(groups) | |
diff --git a/swift/openstack/common/report/models/threading.py b/swift/openstack/common/report/models/threading.py | |
new file mode 100644 | |
index 0000000..7e530de | |
--- /dev/null | |
+++ b/swift/openstack/common/report/models/threading.py | |
@@ -0,0 +1,100 @@ | |
+# 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 threading and stack-trace models | |
+ | |
+This module defines classes representing thread, green | |
+thread, and stack trace data models | |
+""" | |
+ | |
+import traceback | |
+ | |
+import swift.openstack.common.report.models.with_default_views as mwdv | |
+import swift.openstack.common.report.views.text.threading as text_views | |
+ | |
+ | |
+class StackTraceModel(mwdv.ModelWithDefaultViews): | |
+ """A Stack Trace Model | |
+ | |
+ This model holds data from a python stack trace, | |
+ commonly extracted from running thread information | |
+ | |
+ :param stack_state: the python stack_state object | |
+ """ | |
+ | |
+ def __init__(self, stack_state): | |
+ super(StackTraceModel, self).__init__( | |
+ text_view=text_views.StackTraceView()) | |
+ | |
+ if (stack_state is not None): | |
+ self['lines'] = [ | |
+ {'filename': fn, 'line': ln, 'name': nm, 'code': cd} | |
+ for fn, ln, nm, cd in traceback.extract_stack(stack_state) | |
+ ] | |
+ | |
+ if stack_state.f_exc_type is not None: | |
+ self['root_exception'] = { | |
+ 'type': stack_state.f_exc_type, | |
+ 'value': stack_state.f_exc_value | |
+ } | |
+ else: | |
+ self['root_exception'] = None | |
+ else: | |
+ self['lines'] = [] | |
+ self['root_exception'] = None | |
+ | |
+ | |
+class ThreadModel(mwdv.ModelWithDefaultViews): | |
+ """A Thread Model | |
+ | |
+ This model holds data for information about an | |
+ individual thread. It holds both a thread id, | |
+ as well as a stack trace for the thread | |
+ | |
+ .. seealso:: | |
+ | |
+ Class :class:`StackTraceModel` | |
+ | |
+ :param int thread_id: the id of the thread | |
+ :param stack: the python stack state for the current thread | |
+ """ | |
+ | |
+ # threadId, stack in sys._current_frams().items() | |
+ def __init__(self, thread_id, stack): | |
+ super(ThreadModel, self).__init__(text_view=text_views.ThreadView()) | |
+ | |
+ self['thread_id'] = thread_id | |
+ self['stack_trace'] = StackTraceModel(stack) | |
+ | |
+ | |
+class GreenThreadModel(mwdv.ModelWithDefaultViews): | |
+ """A Green Thread Model | |
+ | |
+ This model holds data for information about an | |
+ individual thread. Unlike the thread model, | |
+ it holds just a stack trace, since green threads | |
+ do not have thread ids. | |
+ | |
+ .. seealso:: | |
+ | |
+ Class :class:`StackTraceModel` | |
+ | |
+ :param stack: the python stack state for the green thread | |
+ """ | |
+ | |
+ # gr in greenpool.coroutines_running --> gr.gr_frame | |
+ def __init__(self, stack): | |
+ super(GreenThreadModel, self).__init__( | |
+ {'stack_trace': StackTraceModel(stack)}, | |
+ text_view=text_views.GreenThreadView()) | |
diff --git a/swift/openstack/common/report/models/version.py b/swift/openstack/common/report/models/version.py | |
new file mode 100644 | |
index 0000000..6b18ec5 | |
--- /dev/null | |
+++ b/swift/openstack/common/report/models/version.py | |
@@ -0,0 +1,44 @@ | |
+# 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 Openstack Version Info Model | |
+ | |
+This module defines a class representing the data | |
+model for Openstack package and version information | |
+""" | |
+ | |
+import swift.openstack.common.report.models.with_default_views as mwdv | |
+import swift.openstack.common.report.views.text.generic as generic_text_views | |
+ | |
+ | |
+class PackageModel(mwdv.ModelWithDefaultViews): | |
+ """A Package Information Model | |
+ | |
+ This model holds information about the current | |
+ package. It contains vendor, product, and version | |
+ information. | |
+ | |
+ :param str vendor: the product vendor | |
+ :param str product: the product name | |
+ :param str version: the product version | |
+ """ | |
+ | |
+ def __init__(self, vendor, product, version): | |
+ super(PackageModel, self).__init__( | |
+ text_view=generic_text_views.KeyValueView() | |
+ ) | |
+ | |
+ self['vendor'] = vendor | |
+ self['product'] = product | |
+ self['version'] = version | |
diff --git a/swift/openstack/common/report/models/with_default_views.py b/swift/openstack/common/report/models/with_default_views.py | |
new file mode 100644 | |
index 0000000..0a25ab2 | |
--- /dev/null | |
+++ b/swift/openstack/common/report/models/with_default_views.py | |
@@ -0,0 +1,79 @@ | |
+# 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. | |
+ | |
+import copy | |
+ | |
+import swift.openstack.common.report.models.base as base_model | |
+import swift.openstack.common.report.views.text.generic as textviews | |
+import swift.openstack.common.report.views.xml.generic as xmlviews | |
+ | |
+ | |
+class ModelWithDefaultViews(base_model.ReportModel): | |
+ """A Model With Default Views of Various Types | |
+ | |
+ A model with default views has several predefined views, | |
+ each associated with a given type. This is often used for | |
+ when a submodel should have an attached view, but the view | |
+ differs depending on the serialization format | |
+ | |
+ Paramaters are as the superclass, with the exception | |
+ of any parameters ending in '_view': these parameters | |
+ get stored as default views. | |
+ | |
+ The default 'default views' are | |
+ | |
+ text | |
+ :class:`openstack.common.views.text.generic.KeyValueView` | |
+ xml | |
+ :class:`openstack.common.views.xml.generic.KeyValueView` | |
+ json | |
+ :class:`openstack.common.views.json.generic.KeyValueView` | |
+ | |
+ .. function:: to_type() | |
+ | |
+ ('type' is one of the 'default views' defined for this model) | |
+ Serializes this model using the default view for 'type' | |
+ | |
+ :rtype: str | |
+ :returns: this model serialized as 'type' | |
+ """ | |
+ | |
+ def __init__(self, *args, **kwargs): | |
+ self.views = { | |
+ 'text': textviews.KeyValueView(), | |
+ 'xml': xmlviews.KeyValueView() | |
+ } | |
+ | |
+ newargs = copy.copy(kwargs) | |
+ for k in kwargs: | |
+ if k.endswith('_view'): | |
+ self.views[k[:-5]] = kwargs[k] | |
+ del newargs[k] | |
+ super(ModelWithDefaultViews, self).__init__(*args, **newargs) | |
+ | |
+ def set_current_view_type(self, tp): | |
+ self.attached_view = self.views[tp] | |
+ super(ModelWithDefaultViews, self).set_current_view_type(tp) | |
+ | |
+ def __getattr__(self, attrname): | |
+ if attrname[:3] == 'to_': | |
+ if self.views[attrname[3:]] is not None: | |
+ return lambda: self.views[attrname[3:]](self) | |
+ else: | |
+ raise NotImplementedError(( | |
+ "Model {cn.__module__}.{cn.__name__} does not have" + | |
+ " a default view for " | |
+ "{tp}").format(cn=type(self), tp=attrname[3:])) | |
+ else: | |
+ return super(ModelWithDefaultViews, self).__getattr__(attrname) | |
diff --git a/swift/openstack/common/report/report.py b/swift/openstack/common/report/report.py | |
new file mode 100644 | |
index 0000000..490a4c8 | |
--- /dev/null | |
+++ b/swift/openstack/common/report/report.py | |
@@ -0,0 +1,189 @@ | |
+# 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 Report classes | |
+ | |
+This module defines various classes representing | |
+reports and report sections. All reports take the | |
+form of a report class containing various report sections. | |
+""" | |
+ | |
+import swift.openstack.common.report.views.text.header as header_views | |
+ | |
+ | |
+class BasicReport(object): | |
+ """A Basic Report | |
+ | |
+ A Basic Report consists of a collection of :class:`ReportSection` | |
+ objects, each of which contains a top-level model and generator. | |
+ It collects these sections into a cohesive report which may then | |
+ be serialized by calling :func:`run` | |
+ """ | |
+ | |
+ def __init__(self): | |
+ self.sections = [] | |
+ self._state = 0 | |
+ | |
+ def add_section(self, view, generator, index=None): | |
+ """Add a section to the report | |
+ | |
+ This method adds a section with the given view and | |
+ generator to the report. An index may be specified to | |
+ insert the section at a given location in the list; | |
+ If no index is specified, the section is appended to the | |
+ list. The view is called on the model which results from | |
+ the generator when the report is run. A generator is simply | |
+ a method or callable object which takes no arguments and | |
+ returns a :class:`openstack.common.report.models.base.ReportModel` | |
+ or similar object. | |
+ | |
+ :param view: the top-level view for the section | |
+ :param generator: the method or class which generates the model | |
+ :param index: the index at which to insert the section | |
+ (or None to append it) | |
+ :type index: int or None | |
+ """ | |
+ | |
+ if index is None: | |
+ self.sections.append(ReportSection(view, generator)) | |
+ else: | |
+ self.sections.insert(index, ReportSection(view, generator)) | |
+ | |
+ def run(self): | |
+ """Run the report | |
+ | |
+ This method runs the report, having each section generate | |
+ its data and serialize itself before joining the sections | |
+ together. The BasicReport accomplishes the joining | |
+ by joining the serialized sections together with newlines. | |
+ | |
+ :rtype: str | |
+ :returns: the serialized report | |
+ """ | |
+ | |
+ return "\n".join(str(sect) for sect in self.sections) | |
+ | |
+ | |
+class ReportSection(object): | |
+ """A Report Section | |
+ | |
+ A report section contains a generator and a top-level view. | |
+ When something attempts to serialize the section by calling | |
+ str() on it, the section runs the generator and calls the view | |
+ on the resulting model. | |
+ | |
+ .. seealso:: | |
+ | |
+ Class :class:`BasicReport` | |
+ :func:`BasicReport.add_section` | |
+ | |
+ :param view: the top-level view for this section | |
+ :param generator: the generator for this section | |
+ (any callable object which takes | |
+ no parameters and returns a data model) | |
+ """ | |
+ | |
+ def __init__(self, view, generator): | |
+ self.view = view | |
+ self.generator = generator | |
+ | |
+ def __str__(self): | |
+ return self.view(self.generator()) | |
+ | |
+ | |
+class ReportOfType(BasicReport): | |
+ """A Report of a Certain Type | |
+ | |
+ A ReportOfType has a predefined type associated with it. | |
+ This type is automatically propagated down to the each of | |
+ the sections upon serialization by wrapping the generator | |
+ for each section. | |
+ | |
+ .. seealso:: | |
+ | |
+ Class :class:`openstack.common.report.models.with_default_view.ModelWithDefaultView` # noqa | |
+ (the entire class) | |
+ | |
+ Class :class:`openstack.common.report.models.base.ReportModel` | |
+ :func:`openstack.common.report.models.base.ReportModel.set_current_view_type` # noqa | |
+ | |
+ :param str tp: the type of the report | |
+ """ | |
+ | |
+ def __init__(self, tp): | |
+ self.output_type = tp | |
+ super(ReportOfType, self).__init__() | |
+ | |
+ def add_section(self, view, generator, index=None): | |
+ def with_type(gen): | |
+ def newgen(): | |
+ res = gen() | |
+ try: | |
+ res.set_current_view_type(self.output_type) | |
+ except AttributeError: | |
+ pass | |
+ | |
+ return res | |
+ return newgen | |
+ | |
+ super(ReportOfType, self).add_section( | |
+ view, | |
+ with_type(generator), | |
+ index | |
+ ) | |
+ | |
+ | |
+class TextReport(ReportOfType): | |
+ """A Human-Readable Text Report | |
+ | |
+ This class defines a report that is designed to be read by a human | |
+ being. It has nice section headers, and a formatted title. | |
+ | |
+ :param str name: the title of the report | |
+ """ | |
+ | |
+ def __init__(self, name): | |
+ super(TextReport, self).__init__('text') | |
+ self.name = name | |
+ # add a title with a generator that creates an empty result model | |
+ self.add_section(name, lambda: ('|' * 72) + "\n\n") | |
+ | |
+ def add_section(self, heading, generator, index=None): | |
+ """Add a section to the report | |
+ | |
+ This method adds a section with the given title, and | |
+ generator to the report. An index may be specified to | |
+ insert the section at a given location in the list; | |
+ If no index is specified, the section is appended to the | |
+ list. The view is called on the model which results from | |
+ the generator when the report is run. A generator is simply | |
+ a method or callable object which takes no arguments and | |
+ returns a :class:`openstack.common.report.models.base.ReportModel` | |
+ or similar object. | |
+ | |
+ The model is told to serialize as text (if possible) at serialization | |
+ time by wrapping the generator. The view model's attached view | |
+ (if any) is wrapped in a | |
+ :class:`openstack.common.report.views.text.header.TitledView` | |
+ | |
+ :param str heading: the title for the section | |
+ :param generator: the method or class which generates the model | |
+ :param index: the index at which to insert the section | |
+ (or None to append) | |
+ :type index: int or None | |
+ """ | |
+ | |
+ super(TextReport, self).add_section(header_views.TitledView(heading), | |
+ generator, | |
+ index) | |
diff --git a/swift/openstack/common/report/utils.py b/swift/openstack/common/report/utils.py | |
new file mode 100644 | |
index 0000000..fb71e36 | |
--- /dev/null | |
+++ b/swift/openstack/common/report/utils.py | |
@@ -0,0 +1,46 @@ | |
+# 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. | |
+ | |
+"""Various utilities for report generation | |
+ | |
+This module includes various utilities | |
+used in generating reports. | |
+""" | |
+ | |
+import gc | |
+ | |
+ | |
+class StringWithAttrs(str): | |
+ """A String that can have arbitrary attributes | |
+ """ | |
+ | |
+ pass | |
+ | |
+ | |
+def _find_objects(t): | |
+ """Find Objects in the GC State | |
+ | |
+ This horribly hackish method locates objects of a | |
+ given class in the current python instance's garbage | |
+ collection state. In case you couldn't tell, this is | |
+ horribly hackish, but is necessary for locating all | |
+ green threads, since they don't keep track of themselves | |
+ like normal threads do in python. | |
+ | |
+ :param class t: the class of object to locate | |
+ :rtype: list | |
+ :returns: a list of objects of the given type | |
+ """ | |
+ | |
+ return [o for o in gc.get_objects() if isinstance(o, t)] | |
diff --git a/swift/openstack/common/report/views/__init__.py b/swift/openstack/common/report/views/__init__.py | |
new file mode 100644 | |
index 0000000..612959b | |
--- /dev/null | |
+++ b/swift/openstack/common/report/views/__init__.py | |
@@ -0,0 +1,22 @@ | |
+# 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 predefined views | |
+ | |
+This module provides a collection of predefined views | |
+for use in reports. It is separated by type (xml, json, or text). | |
+Each type contains a submodule called 'generic' containing | |
+several basic, universal views for that type. There is also | |
+a predefined view that utilizes Jinja. | |
+""" | |
diff --git a/swift/openstack/common/report/views/jinja_view.py b/swift/openstack/common/report/views/jinja_view.py | |
new file mode 100644 | |
index 0000000..a6f340e | |
--- /dev/null | |
+++ b/swift/openstack/common/report/views/jinja_view.py | |
@@ -0,0 +1,125 @@ | |
+# 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 Jinja Views | |
+ | |
+This module provides views that utilize the Jinja templating | |
+system for serialization. For more information on Jinja, please | |
+see http://jinja.pocoo.org/ . | |
+""" | |
+ | |
+import jinja2 | |
+ | |
+ | |
+class JinjaView(object): | |
+ """A Jinja View | |
+ | |
+ This view renders the given model using the provided Jinja | |
+ template. The template can be given in various ways. | |
+ If the `VIEw_TEXT` property is defined, that is used as template. | |
+ Othewise, if a `path` parameter is passed to the constructor, that | |
+ is used to load a file containing the template. If the `path` | |
+ parameter is None, the `text` parameter is used as the template. | |
+ | |
+ The leading newline character and trailing newline character are stripped | |
+ from the template (provided they exist). Baseline indentation is | |
+ also stripped from each line. The baseline indentation is determined by | |
+ checking the indentation of the first line, after stripping off the leading | |
+ newline (if any). | |
+ | |
+ :param str path: the path to the Jinja template | |
+ :param str text: the text of the Jinja template | |
+ """ | |
+ | |
+ def __init__(self, path=None, text=None): | |
+ try: | |
+ self._text = self.VIEW_TEXT | |
+ except AttributeError: | |
+ if path is not None: | |
+ with open(path, 'r') as f: | |
+ self._text = f.read() | |
+ elif text is not None: | |
+ self._text = text | |
+ else: | |
+ self._text = "" | |
+ | |
+ if self._text[0] == "\n": | |
+ self._text = self._text[1:] | |
+ | |
+ newtext = self._text.lstrip() | |
+ amt = len(self._text) - len(newtext) | |
+ if (amt > 0): | |
+ base_indent = self._text[0:amt] | |
+ lines = self._text.splitlines() | |
+ newlines = [] | |
+ for line in lines: | |
+ if line.startswith(base_indent): | |
+ newlines.append(line[amt:]) | |
+ else: | |
+ newlines.append(line) | |
+ self._text = "\n".join(newlines) | |
+ | |
+ if self._text[-1] == "\n": | |
+ self._text = self._text[:-1] | |
+ | |
+ self._regentemplate = True | |
+ self._templatecache = None | |
+ | |
+ def __call__(self, model): | |
+ return self.template.render(**model) | |
+ | |
+ @property | |
+ def template(self): | |
+ """Get the Compiled Template | |
+ | |
+ Gets the compiled template, using a cached copy if possible | |
+ (stored in attr:`_templatecache`) or otherwise recompiling | |
+ the template if the compiled template is not present or is | |
+ invalid (due to attr:`_regentemplate` being set to True). | |
+ | |
+ :returns: the compiled Jinja template | |
+ :rtype: :class:`jinja2.Template` | |
+ """ | |
+ | |
+ if self._templatecache is None or self._regentemplate: | |
+ self._templatecache = jinja2.Template(self._text) | |
+ self._regentemplate = False | |
+ | |
+ return self._templatecache | |
+ | |
+ def _gettext(self): | |
+ """Get the Template Text | |
+ | |
+ Gets the text of the current template | |
+ | |
+ :returns: the text of the Jinja template | |
+ :rtype: str | |
+ """ | |
+ | |
+ return self._text | |
+ | |
+ def _settext(self, textval): | |
+ """Set the Template Text | |
+ | |
+ Sets the text of the current template, marking it | |
+ for recompilation next time the compiled template | |
+ is retrived via attr:`template` . | |
+ | |
+ :param str textval: the new text of the Jinja template | |
+ """ | |
+ | |
+ self._text = textval | |
+ self.regentemplate = True | |
+ | |
+ text = property(_gettext, _settext) | |
diff --git a/swift/openstack/common/report/views/text/__init__.py b/swift/openstack/common/report/views/text/__init__.py | |
new file mode 100644 | |
index 0000000..c097484 | |
--- /dev/null | |
+++ b/swift/openstack/common/report/views/text/__init__.py | |
@@ -0,0 +1,19 @@ | |
+# 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 basic text views | |
+ | |
+This module provides several basic views which serialize | |
+models into human-readable text. | |
+""" | |
diff --git a/swift/openstack/common/report/views/text/generic.py b/swift/openstack/common/report/views/text/generic.py | |
new file mode 100644 | |
index 0000000..7363833 | |
--- /dev/null | |
+++ b/swift/openstack/common/report/views/text/generic.py | |
@@ -0,0 +1,202 @@ | |
+# 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 generic text views | |
+ | |
+This modules provides several generic views for | |
+serializing models into human-readable text. | |
+""" | |
+ | |
+import collections as col | |
+ | |
+import six | |
+ | |
+ | |
+class MultiView(object): | |
+ """A Text View Containing Multiple Views | |
+ | |
+ This view simply serializes each | |
+ value in the data model, and then | |
+ joins them with newlines (ignoring | |
+ the key values altogether). This is | |
+ useful for serializing lists of models | |
+ (as array-like dicts). | |
+ """ | |
+ | |
+ def __call__(self, model): | |
+ res = [str(model[key]) for key in model] | |
+ return "\n".join(res) | |
+ | |
+ | |
+class BasicKeyValueView(object): | |
+ """A Basic Key-Value Text View | |
+ | |
+ This view performs a naive serialization of a model into | |
+ text using a basic key-value method, where each | |
+ key-value pair is rendered as "key = str(value)" | |
+ """ | |
+ | |
+ def __call__(self, model): | |
+ res = "" | |
+ for key in model: | |
+ res += "{key} = {value}\n".format(key=key, value=model[key]) | |
+ | |
+ return res | |
+ | |
+ | |
+class KeyValueView(object): | |
+ """A Key-Value Text View | |
+ | |
+ This view performs an advanced serialization of a model | |
+ into text by following the following set of rules: | |
+ | |
+ key : text | |
+ key = text | |
+ | |
+ rootkey : Mapping | |
+ :: | |
+ | |
+ rootkey = | |
+ serialize(key, value) | |
+ | |
+ key : Sequence | |
+ :: | |
+ | |
+ key = | |
+ serialize(item) | |
+ | |
+ :param str indent_str: the string used to represent one "indent" | |
+ :param str key_sep: the separator to use between keys and values | |
+ :param str dict_sep: the separator to use after a dictionary root key | |
+ :param str list_sep: the separator to use after a list root key | |
+ :param str anon_dict: the "key" to use when there is a dict in a list | |
+ (does not automatically use the dict separator) | |
+ :param before_dict: content to place on the line(s) before the a dict | |
+ root key (use None to avoid inserting an extra line) | |
+ :type before_dict: str or None | |
+ :param before_list: content to place on the line(s) before the a list | |
+ root key (use None to avoid inserting an extra line) | |
+ :type before_list: str or None | |
+ """ | |
+ | |
+ def __init__(self, | |
+ indent_str=' ', | |
+ key_sep=' = ', | |
+ dict_sep=' = ', | |
+ list_sep=' = ', | |
+ anon_dict='[dict]', | |
+ before_dict=None, | |
+ before_list=None): | |
+ self.indent_str = indent_str | |
+ self.key_sep = key_sep | |
+ self.dict_sep = dict_sep | |
+ self.list_sep = list_sep | |
+ self.anon_dict = anon_dict | |
+ self.before_dict = before_dict | |
+ self.before_list = before_list | |
+ | |
+ def __call__(self, model): | |
+ def serialize(root, rootkey, indent): | |
+ res = [] | |
+ if rootkey is not None: | |
+ res.append((self.indent_str * indent) + rootkey) | |
+ | |
+ if isinstance(root, col.Mapping): | |
+ if rootkey is None and indent > 0: | |
+ res.append((self.indent_str * indent) + self.anon_dict) | |
+ elif rootkey is not None: | |
+ res[0] += self.dict_sep | |
+ if self.before_dict is not None: | |
+ res.insert(0, self.before_dict) | |
+ | |
+ for key in root: | |
+ res.extend(serialize(root[key], key, indent + 1)) | |
+ elif (isinstance(root, col.Sequence) and | |
+ not isinstance(root, six.string_types)): | |
+ if rootkey is not None: | |
+ res[0] += self.list_sep | |
+ if self.before_list is not None: | |
+ res.insert(0, self.before_list) | |
+ | |
+ for val in root: | |
+ res.extend(serialize(val, None, indent + 1)) | |
+ else: | |
+ str_root = str(root) | |
+ if '\n' in str_root: | |
+ # we are in a submodel | |
+ if rootkey is not None: | |
+ res[0] += self.dict_sep | |
+ | |
+ list_root = [(self.indent_str * (indent + 1)) + line | |
+ for line in str_root.split('\n')] | |
+ res.extend(list_root) | |
+ else: | |
+ # just a normal key or list entry | |
+ try: | |
+ res[0] += self.key_sep + str_root | |
+ except IndexError: | |
+ res = [(self.indent_str * indent) + str_root] | |
+ | |
+ return res | |
+ | |
+ return "\n".join(serialize(model, None, -1)) | |
+ | |
+ | |
+class TableView(object): | |
+ """A Basic Table Text View | |
+ | |
+ This view performs serialization of data into a basic table with | |
+ predefined column names and mappings. Column width is auto-calculated | |
+ evenly, column values are automatically truncated accordingly. Values | |
+ are centered in the columns. | |
+ | |
+ :param [str] column_names: the headers for each of the columns | |
+ :param [str] column_values: the item name to match each column to in | |
+ each row | |
+ :param str table_prop_name: the name of the property within the model | |
+ containing the row models | |
+ """ | |
+ | |
+ def __init__(self, column_names, column_values, table_prop_name): | |
+ self.table_prop_name = table_prop_name | |
+ self.column_names = column_names | |
+ self.column_values = column_values | |
+ self.column_width = (72 - len(column_names) + 1) / len(column_names) | |
+ | |
+ column_headers = "|".join( | |
+ "{ch[" + str(n) + "]: ^" + str(self.column_width) + "}" | |
+ for n in range(len(column_names)) | |
+ ) | |
+ | |
+ # correct for float-to-int roundoff error | |
+ test_fmt = column_headers.format(ch=column_names) | |
+ if len(test_fmt) < 72: | |
+ column_headers += ' ' * (72 - len(test_fmt)) | |
+ | |
+ vert_divider = '-' * 72 | |
+ self.header_fmt_str = column_headers + "\n" + vert_divider + "\n" | |
+ | |
+ self.row_fmt_str = "|".join( | |
+ "{cv[" + str(n) + "]: ^" + str(self.column_width) + "}" | |
+ for n in range(len(column_values)) | |
+ ) | |
+ | |
+ def __call__(self, model): | |
+ res = self.header_fmt_str.format(ch=self.column_names) | |
+ for raw_row in model[self.table_prop_name]: | |
+ row = [str(raw_row[prop_name]) for prop_name in self.column_values] | |
+ # double format is in case we have roundoff error | |
+ res += '{0: <72}\n'.format(self.row_fmt_str.format(cv=row)) | |
+ | |
+ return res | |
diff --git a/swift/openstack/common/report/views/text/header.py b/swift/openstack/common/report/views/text/header.py | |
new file mode 100644 | |
index 0000000..58d06c0 | |
--- /dev/null | |
+++ b/swift/openstack/common/report/views/text/header.py | |
@@ -0,0 +1,51 @@ | |
+# 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. | |
+ | |
+"""Text Views With Headers | |
+ | |
+This package defines several text views with headers | |
+""" | |
+ | |
+ | |
+class HeaderView(object): | |
+ """A Text View With a Header | |
+ | |
+ This view simply serializes the model and places the given | |
+ header on top. | |
+ | |
+ :param header: the header (can be anything on which str() can be called) | |
+ """ | |
+ | |
+ def __init__(self, header): | |
+ self.header = header | |
+ | |
+ def __call__(self, model): | |
+ return str(self.header) + "\n" + str(model) | |
+ | |
+ | |
+class TitledView(HeaderView): | |
+ """A Text View With a Title | |
+ | |
+ This view simply serializes the model, and places | |
+ a preformatted header containing the given title | |
+ text on top. The title text can be up to 64 characters | |
+ long. | |
+ | |
+ :param str title: the title of the view | |
+ """ | |
+ | |
+ FORMAT_STR = ('=' * 72) + "\n===={0: ^64}====\n" + ('=' * 72) | |
+ | |
+ def __init__(self, title): | |
+ super(TitledView, self).__init__(self.FORMAT_STR.format(title)) | |
diff --git a/swift/openstack/common/report/views/text/threading.py b/swift/openstack/common/report/views/text/threading.py | |
new file mode 100644 | |
index 0000000..47048a5 | |
--- /dev/null | |
+++ b/swift/openstack/common/report/views/text/threading.py | |
@@ -0,0 +1,80 @@ | |
+# 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 thread and stack-trace views | |
+ | |
+This module provides a collection of views for | |
+visualizing threads, green threads, and stack traces | |
+in human-readable form. | |
+""" | |
+ | |
+import swift.openstack.common.report.views.jinja_view as jv | |
+ | |
+ | |
+class StackTraceView(jv.JinjaView): | |
+ """A Stack Trace View | |
+ | |
+ This view displays stack trace models defined by | |
+ :class:`openstack.common.report.models.threading.StackTraceModel` | |
+ """ | |
+ | |
+ VIEW_TEXT = ( | |
+ "{% if root_exception is not none %}" | |
+ "Exception: {{ root_exception }}\n" | |
+ "------------------------------------\n" | |
+ "\n" | |
+ "{% endif %}" | |
+ "{% for line in lines %}\n" | |
+ "{{ line.filename }}:{{ line.line }} in {{ line.name }}\n" | |
+ " {% if line.code is not none %}" | |
+ "`{{ line.code }}`" | |
+ "{% else %}" | |
+ "(source not found)" | |
+ "{% endif %}\n" | |
+ "{% else %}\n" | |
+ "No Traceback!\n" | |
+ "{% endfor %}" | |
+ ) | |
+ | |
+ | |
+class GreenThreadView(object): | |
+ """A Green Thread View | |
+ | |
+ This view displays a green thread provided by the data | |
+ model :class:`openstack.common.report.models.threading.GreenThreadModel` # noqa | |
+ """ | |
+ | |
+ FORMAT_STR = "------{thread_str: ^60}------" + "\n" + "{stack_trace}" | |
+ | |
+ def __call__(self, model): | |
+ return self.FORMAT_STR.format( | |
+ thread_str=" Green Thread ", | |
+ stack_trace=model.stack_trace | |
+ ) | |
+ | |
+ | |
+class ThreadView(object): | |
+ """A Thread Collection View | |
+ | |
+ This view displays a python thread provided by the data | |
+ model :class:`openstack.common.report.models.threading.ThreadModel` # noqa | |
+ """ | |
+ | |
+ FORMAT_STR = "------{thread_str: ^60}------" + "\n" + "{stack_trace}" | |
+ | |
+ def __call__(self, model): | |
+ return self.FORMAT_STR.format( | |
+ thread_str=" Thread #{0} ".format(model.thread_id), | |
+ stack_trace=model.stack_trace | |
+ ) | |
diff --git a/swift/openstack/common/report/views/xml/__init__.py b/swift/openstack/common/report/views/xml/__init__.py | |
new file mode 100644 | |
index 0000000..a40fec9 | |
--- /dev/null | |
+++ b/swift/openstack/common/report/views/xml/__init__.py | |
@@ -0,0 +1,19 @@ | |
+# 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 basic XML views | |
+ | |
+This module provides several basic views which serialize | |
+models into XML. | |
+""" | |
diff --git a/swift/openstack/common/report/views/xml/generic.py b/swift/openstack/common/report/views/xml/generic.py | |
new file mode 100644 | |
index 0000000..998aa36 | |
--- /dev/null | |
+++ b/swift/openstack/common/report/views/xml/generic.py | |
@@ -0,0 +1,85 @@ | |
+# 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 generic XML views | |
+ | |
+This modules defines several basic views for serializing | |
+data to XML. Submodels that have already been serialized | |
+as XML may have their string values marked with `__is_xml__ | |
+= True` using :class:`openstack.common.report.utils.StringWithAttrs` | |
+(each of the classes within this module does this automatically, | |
+and non-naive serializers check for this attribute and handle | |
+such strings specially) | |
+""" | |
+ | |
+import collections as col | |
+import copy | |
+import xml.etree.ElementTree as ET | |
+ | |
+import six | |
+ | |
+import swift.openstack.common.report.utils as utils | |
+ | |
+ | |
+class KeyValueView(object): | |
+ """A Key-Value XML View | |
+ | |
+ This view performs advanced serialization of a data model | |
+ into XML. It first deserializes any values marked as XML so | |
+ that they can be properly reserialized later. It then follows | |
+ the following rules to perform serialization: | |
+ | |
+ key : text/xml | |
+ The tag name is the key name, and the contents are the text or xml | |
+ key : Sequence | |
+ A wrapper tag is created with the key name, and each item is placed | |
+ in an 'item' tag | |
+ key : Mapping | |
+ A wrapper tag is created with the key name, and the serialize is called | |
+ on each key-value pair (such that each key gets its own tag) | |
+ | |
+ :param str wrapper_name: the name of the top-level element | |
+ """ | |
+ | |
+ def __init__(self, wrapper_name="model"): | |
+ self.wrapper_name = wrapper_name | |
+ | |
+ def __call__(self, model): | |
+ # this part deals with subviews that were already serialized | |
+ cpy = copy.deepcopy(model) | |
+ for key, valstr in model.items(): | |
+ if getattr(valstr, '__is_xml__', False): | |
+ cpy[key] = ET.fromstring(valstr) | |
+ | |
+ def serialize(rootmodel, rootkeyname): | |
+ res = ET.Element(rootkeyname) | |
+ | |
+ if isinstance(rootmodel, col.Mapping): | |
+ for key in rootmodel: | |
+ res.append(serialize(rootmodel[key], key)) | |
+ elif (isinstance(rootmodel, col.Sequence) | |
+ and not isinstance(rootmodel, six.string_types)): | |
+ for val in rootmodel: | |
+ res.append(serialize(val, 'item')) | |
+ elif ET.iselement(rootmodel): | |
+ res.append(rootmodel) | |
+ else: | |
+ res.text = str(rootmodel) | |
+ | |
+ return res | |
+ | |
+ res = utils.StringWithAttrs(ET.tostring(serialize(cpy, | |
+ self.wrapper_name))) | |
+ res.__is_xml__ = True | |
+ return res |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment