Skip to content

Instantly share code, notes, and snippets.

@DirectXMan12
Created March 3, 2014 23:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save DirectXMan12/9337007 to your computer and use it in GitHub Desktop.
Save DirectXMan12/9337007 to your computer and use it in GitHub Desktop.
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