summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoshua Harlow <harlowja@yahoo-inc.com>2015-09-11 17:09:24 -0700
committerJoshua Harlow <harlowja@yahoo-inc.com>2015-09-17 15:06:36 -0700
commite78737012c510fc5f7551bc6cf19b8c7f5643bc0 (patch)
treebb3d65409de849f852f3ec0d63431132ee424c39
parentccff90e5b604904831e2085b173ea9a66d0441b8 (diff)
downloadoslo-middleware-e78737012c510fc5f7551bc6cf19b8c7f5643bc0.tar.gz
Further improve the healthcheck output
Add on information about the active threads and active greenthreads and some basic server information to be able to show this to operators (or others) that have detailed mode enabled and can benefit from this information being output. This addition makes it so that basic diagnostic information can be obtained from the response of the healthcheck middleware and not just OK or NOT OK; making the eventlet backdoor feature that oslo.service provides less needed for operators. Change-Id: I469c56586597561633921ebf3916585646f45373
-rw-r--r--oslo_middleware/healthcheck/__init__.py99
1 files changed, 98 insertions, 1 deletions
diff --git a/oslo_middleware/healthcheck/__init__.py b/oslo_middleware/healthcheck/__init__.py
index 9aa2c2e..a2f3bc2 100644
--- a/oslo_middleware/healthcheck/__init__.py
+++ b/oslo_middleware/healthcheck/__init__.py
@@ -13,8 +13,12 @@
# License for the specific language governing permissions and limitations
# under the License.
+import gc
import json
+import platform
import socket
+import sys
+import traceback
try:
from collections import OrderedDict # noqa
@@ -25,15 +29,25 @@ except ImportError:
import jinja2
from oslo_utils import reflection
from oslo_utils import strutils
+from oslo_utils import timeutils
import six
import stevedore
import webob.dec
import webob.exc
import webob.response
+try:
+ import greenlet
+except ImportError:
+ greenlet = None
+
from oslo_middleware import base
+def _find_objects(t):
+ return [o for o in gc.get_objects() if isinstance(o, t)]
+
+
def _expand_template(contents, params):
tpl = jinja2.Template(source=contents,
undefined=jinja2.StrictUndefined)
@@ -112,9 +126,18 @@ class Healthcheck(base.ConfigurableMiddleware):
<HEAD><TITLE>Healthcheck Status</TITLE></HEAD>
<BODY>
{% if detailed -%}
+<H1>Server status</H1>
{% if hostname -%}
-<H1>Server status for {{hostname|e}}</H1>
+<B>Server hostname:</B><PRE>{{hostname|e}}</PRE>
{%- endif %}
+<B>Current time:</B><PRE>{{now|e}}</PRE>
+<B>Python version:</B><PRE>{{python_version|e}}</PRE>
+<B>Platform:</B><PRE>{{platform|e}}</PRE>
+<HR></HR>
+<H2>Garbage collector:</H2>
+<B>Counts:</B><PRE>{{gc.counts|e}}</PRE>
+<B>Thresholds:</B><PRE>{{gc.threshold|e}}</PRE>
+<HR></HR>
{%- endif %}
<H2>Result of {{results|length}} checks:</H2>
<TABLE bgcolor="#ffffff" border="1">
@@ -131,6 +154,34 @@ class Healthcheck(base.ConfigurableMiddleware):
{%- endfor %}
</TBODY>
</TABLE>
+<HR></HR>
+{% if detailed -%}
+{% if greenthreads -%}
+<H2>{{greenthreads|length}} greenthread(s) active:</H2>
+<TABLE bgcolor="#ffffff" border="1">
+<TBODY>
+{% for stack in greenthreads -%}
+<TR>
+ <TD><PRE>{{stack|e}}</PRE></TD>
+</TR>
+{%- endfor %}
+</TBODY>
+</TABLE>
+<HR></HR>
+{%- endif %}
+{% if threads -%}
+<H2>{{threads|length}} thread(s) active:</H2>
+<TABLE bgcolor="#ffffff" border="1">
+<TBODY>
+{% for stack in threads -%}
+<TR>
+ <TD><PRE>{{stack|e}}</PRE></TD>
+</TR>
+{%- endfor %}
+</TBODY>
+</TABLE>
+{%- endif %}
+{%- endif %}
</BODY>
</HTML>
"""
@@ -160,6 +211,34 @@ class Healthcheck(base.ConfigurableMiddleware):
self._default_accept = 'text/plain'
@staticmethod
+ def _get_threadstacks():
+ threadstacks = []
+ try:
+ active_frames = sys._current_frames()
+ except AttributeError:
+ pass
+ else:
+ buf = six.StringIO()
+ for stack in six.itervalues(active_frames):
+ traceback.print_stack(stack, file=buf)
+ threadstacks.append(buf.getvalue())
+ buf.seek(0)
+ buf.truncate()
+ return threadstacks
+
+ @staticmethod
+ def _get_greenstacks():
+ greenstacks = []
+ if greenlet is not None:
+ buf = six.StringIO()
+ for gt in _find_objects(greenlet.greenlet):
+ traceback.print_stack(gt.gr_frame, file=buf)
+ greenstacks.append(buf.getvalue())
+ buf.seek(0)
+ buf.truncate()
+ return greenstacks
+
+ @staticmethod
def _pretty_json_dumps(contents):
return json.dumps(contents, indent=4, sort_keys=True)
@@ -182,6 +261,13 @@ class Healthcheck(base.ConfigurableMiddleware):
if self._show_details:
body = {
'detailed': True,
+ 'python_version': sys.version,
+ 'now': str(timeutils.utcnow()),
+ 'platform': platform.platform(),
+ 'gc': {
+ 'counts': gc.get_count(),
+ 'threshold': gc.get_threshold(),
+ },
}
reasons = []
for result in results:
@@ -191,6 +277,8 @@ class Healthcheck(base.ConfigurableMiddleware):
fully_qualified=False),
})
body['reasons'] = reasons
+ body['greenthreads'] = self._get_greenstacks()
+ body['threads'] = self._get_threadstacks()
else:
body = {
'reasons': [result.reason for result in results],
@@ -215,6 +303,15 @@ class Healthcheck(base.ConfigurableMiddleware):
'hostname': hostname,
'results': translated_results,
'detailed': self._show_details,
+ 'now': str(timeutils.utcnow()),
+ 'python_version': sys.version,
+ 'platform': platform.platform(),
+ 'gc': {
+ 'counts': gc.get_count(),
+ 'threshold': gc.get_threshold(),
+ },
+ 'threads': self._get_threadstacks(),
+ 'greenthreads': self._get_threadstacks(),
}
body = _expand_template(self.HTML_RESPONSE_TEMPLATE, params)
return (body.strip(), 'text/html')