Change-Id: I71f003998461723241edd7fc16f8a7970ebdc0d0
Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
This commit is contained in:
Stephen Finucane
2026-03-08 16:06:20 +00:00
parent 5a01ecb559
commit bd377bc3c2
48 changed files with 2065 additions and 1380 deletions
+7 -11
View File
@@ -12,18 +12,14 @@ repos:
- id: debug-statements
- id: check-yaml
files: .*\.(yaml|yml)$
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.5
hooks:
- id: ruff-check
args: ['--fix', '--unsafe-fixes']
- id: ruff-format
- repo: https://opendev.org/openstack/hacking
rev: 7.0.0
rev: 8.0.0
hooks:
- id: hacking
additional_dependencies: []
- repo: https://github.com/PyCQA/bandit
rev: 1.8.6
hooks:
- id: bandit
args: ['-c', 'pyproject.toml']
- repo: https://github.com/asottile/pyupgrade
rev: v3.20.0
hooks:
- id: pyupgrade
args: [--py3-only]
+10 -9
View File
@@ -31,9 +31,7 @@ import sys
# directory, add these directories to sys.path here. If the directory
# is relative to the documentation root, use os.path.abspath to make
# it absolute, like shown here.
sys.path.extend([
os.path.abspath('../..'),
])
sys.path.extend([os.path.abspath('../..')])
# -- General configuration ---------------------
@@ -98,7 +96,7 @@ html_theme = 'openstackdocs'
html_static_path = []
# Output file base name for HTML help builder.
htmlhelp_basename = '%sdoc' % project
htmlhelp_basename = f'{project}doc'
# -- Options for LaTeX output --------------
@@ -108,10 +106,13 @@ latex_elements = {}
# (source start file, target name, title, author,
# documentclass [howto/manual]).
latex_documents = [
('index',
'%s.tex' % project,
'%s Documentation' % project,
'OpenStack Foundation', 'manual'),
(
'index',
f'{project}.tex',
f'{project} Documentation',
'OpenStack Foundation',
'manual',
),
]
# -- Options for Texinfo output -----------
@@ -127,7 +128,7 @@ texinfo_documents = [
'OSprofiler Team',
'OSprofiler',
'One line description of project.',
'Miscellaneous'
'Miscellaneous',
),
]
+6 -6
View File
@@ -31,8 +31,7 @@ def split(text, strip=True):
if isinstance(text, (tuple, list)):
return text
if not isinstance(text, str):
raise TypeError(
"Unknown how to split '{}': {}".format(text, type(text)))
raise TypeError(f"Unknown how to split '{text}': {type(text)}")
if strip:
return [t.strip() for t in text.split(",") if t.strip()]
else:
@@ -104,13 +103,14 @@ def signed_unpack(data, hmac_data, hmac_keys):
for hmac_key in hmac_keys:
try:
user_hmac_data = generate_hmac(data, hmac_key)
except Exception: # nosec
except Exception: # noqa: S110
pass
else:
if hmac.compare_digest(hmac_data, user_hmac_data):
try:
contents = json.loads(
binary_decode(base64.urlsafe_b64decode(data)))
binary_decode(base64.urlsafe_b64decode(data))
)
contents["hmac_key"] = hmac_key
return contents
except Exception:
@@ -124,7 +124,7 @@ def itersubclasses(cls, _seen=None):
_seen = _seen or set()
try:
subs = cls.__subclasses__()
except TypeError: # fails only when cls is type
except TypeError: # fails only when cls is type
subs = cls.__subclasses__(cls)
for sub in subs:
if sub not in _seen:
@@ -146,7 +146,7 @@ def import_modules_from_package(package):
if filename.startswith("__") or not filename.endswith(".py"):
continue
new_package = ".".join(root.split(os.sep)).split("....")[1]
module_name = "{}.{}".format(new_package, filename[:-3])
module_name = f"{new_package}.{filename[:-3]}"
__import__(module_name)
+2
View File
@@ -37,9 +37,11 @@ def arg(*args, **kwargs):
... def entity_create(args):
... pass
"""
def _decorator(func):
add_arg(func, *args, **kwargs)
return func
return _decorator
+100 -46
View File
@@ -33,26 +33,56 @@ class TraceCommands(BaseCommand):
group_name = "trace"
@cliutils.arg("trace", help="File with trace or trace id")
@cliutils.arg("--connection-string", dest="conn_str",
default=(cliutils.env("OSPROFILER_CONNECTION_STRING")),
help="Storage driver's connection string. Defaults to "
"env[OSPROFILER_CONNECTION_STRING] if set")
@cliutils.arg("--transport-url", dest="transport_url",
help="Oslo.messaging transport URL (for messaging:// driver "
"only), e.g. rabbit://user:password@host:5672/")
@cliutils.arg("--idle-timeout", dest="idle_timeout", type=int, default=1,
help="How long to wait for the trace to finish, in seconds "
"(for messaging:// driver only)")
@cliutils.arg("--json", dest="use_json", action="store_true",
help="show trace in JSON")
@cliutils.arg("--html", dest="use_html", action="store_true",
help="show trace in HTML")
@cliutils.arg("--local-libs", dest="local_libs", action="store_true",
help="use local static files of html in /libs/")
@cliutils.arg("--dot", dest="use_dot", action="store_true",
help="show trace in DOT language")
@cliutils.arg("--render-dot", dest="render_dot_filename",
help="filename for rendering the dot graph in pdf format")
@cliutils.arg(
"--connection-string",
dest="conn_str",
default=(cliutils.env("OSPROFILER_CONNECTION_STRING")),
help="Storage driver's connection string. Defaults to "
"env[OSPROFILER_CONNECTION_STRING] if set",
)
@cliutils.arg(
"--transport-url",
dest="transport_url",
help="Oslo.messaging transport URL (for messaging:// driver "
"only), e.g. rabbit://user:password@host:5672/",
)
@cliutils.arg(
"--idle-timeout",
dest="idle_timeout",
type=int,
default=1,
help="How long to wait for the trace to finish, in seconds "
"(for messaging:// driver only)",
)
@cliutils.arg(
"--json",
dest="use_json",
action="store_true",
help="show trace in JSON",
)
@cliutils.arg(
"--html",
dest="use_html",
action="store_true",
help="show trace in HTML",
)
@cliutils.arg(
"--local-libs",
dest="local_libs",
action="store_true",
help="use local static files of html in /libs/",
)
@cliutils.arg(
"--dot",
dest="use_dot",
action="store_true",
help="show trace in DOT language",
)
@cliutils.arg(
"--render-dot",
dest="render_dot_filename",
help="filename for rendering the dot graph in pdf format",
)
@cliutils.arg("--out", dest="file_name", help="save output in file")
def show(self, args):
"""Display trace results in HTML, JSON or DOT format."""
@@ -61,7 +91,8 @@ class TraceCommands(BaseCommand):
raise exc.CommandError(
"You must provide connection string via"
" either --connection-string or "
"via env[OSPROFILER_CONNECTION_STRING]")
"via env[OSPROFILER_CONNECTION_STRING]"
)
trace = None
@@ -76,8 +107,10 @@ class TraceCommands(BaseCommand):
trace = engine.get_report(args.trace)
if not trace or not trace.get("children"):
msg = ("Trace with UUID %s not found. Please check the HMAC key "
"used in the command." % args.trace)
msg = (
f"Trace with UUID {args.trace} not found. Please check the "
f"HMAC key used in the command."
)
raise exc.CommandError(msg)
# Since datetime.datetime is not JSON serializable by default,
@@ -89,16 +122,25 @@ class TraceCommands(BaseCommand):
return obj
if args.use_json:
output = json.dumps(trace, default=datetime_json_serialize,
separators=(",", ": "),
indent=2)
output = json.dumps(
trace,
default=datetime_json_serialize,
separators=(",", ": "),
indent=2,
)
elif args.use_html:
with open(os.path.join(os.path.dirname(__file__),
"template.html")) as html_template:
with open(
os.path.join(os.path.dirname(__file__), "template.html")
) as html_template:
output = html_template.read().replace(
"$DATA", json.dumps(trace, indent=4,
separators=(",", ": "),
default=datetime_json_serialize))
"$DATA",
json.dumps(
trace,
indent=4,
separators=(",", ": "),
default=datetime_json_serialize,
),
)
if args.local_libs:
output = output.replace("$LOCAL", "true")
else:
@@ -109,8 +151,10 @@ class TraceCommands(BaseCommand):
if args.render_dot_filename:
dot_graph.render(args.render_dot_filename, cleanup=True)
else:
raise exc.CommandError("You should choose one of the following "
"output formats: json, html or dot.")
raise exc.CommandError(
"You should choose one of the following "
"output formats: json, html or dot."
)
if args.file_name:
with open(args.file_name, "w+") as output_file:
@@ -123,7 +167,8 @@ class TraceCommands(BaseCommand):
import graphviz
except ImportError:
raise exc.CommandError(
"graphviz library is required to use this option.")
"graphviz library is required to use this option."
)
dot = graphviz.Digraph(format="pdf")
next_id = [0]
@@ -132,14 +177,15 @@ class TraceCommands(BaseCommand):
time_taken = info["finished"] - info["started"]
service = info["service"] + ":" if "service" in info else ""
name = info["name"]
label = "%s%s - %d ms" % (service, name, time_taken)
label = f"{service}{name} - {time_taken} ms"
if name == "wsgi":
req = info["meta.raw_payload.wsgi-start"]["info"]["request"]
label = "{}\\n{} {}..".format(label, req["method"],
req["path"][:30])
label = "{}\\n{} {}..".format(
label, req["method"], req["path"][:30]
)
elif name == "rpc" or name == "driver":
raw = info["meta.raw_payload.%s-start" % name]
raw = info[f"meta.raw_payload.{name}-start"]
fn_name = raw["info"]["function"]["name"]
label = "{}\\n{}".format(label, fn_name.split(".")[-1])
@@ -158,20 +204,28 @@ class TraceCommands(BaseCommand):
_create_sub_graph(trace)
return dot
@cliutils.arg("--connection-string", dest="conn_str",
default=cliutils.env("OSPROFILER_CONNECTION_STRING"),
help="Storage driver's connection string. Defaults to "
"env[OSPROFILER_CONNECTION_STRING] if set")
@cliutils.arg("--error-trace", dest="error_trace",
type=bool, default=False,
help="List all traces that contain error.")
@cliutils.arg(
"--connection-string",
dest="conn_str",
default=cliutils.env("OSPROFILER_CONNECTION_STRING"),
help="Storage driver's connection string. Defaults to "
"env[OSPROFILER_CONNECTION_STRING] if set",
)
@cliutils.arg(
"--error-trace",
dest="error_trace",
type=bool,
default=False,
help="List all traces that contain error.",
)
def list(self, args):
"""List all traces"""
if not args.conn_str:
raise exc.CommandError(
"You must provide connection string via"
" either --connection-string or "
"via env[OSPROFILER_CONNECTION_STRING]")
"via env[OSPROFILER_CONNECTION_STRING]"
)
try:
engine = base.get_driver(args.conn_str, **args.__dict__)
except Exception as e:
+17 -13
View File
@@ -31,7 +31,6 @@ from osprofiler import opts
class OSProfilerShell:
def __init__(self, argv):
args = self._get_base_parser().parse_args(argv)
opts.set_defaults(cfg.CONF)
@@ -40,14 +39,12 @@ class OSProfilerShell:
def _get_base_parser(self):
parser = argparse.ArgumentParser(
prog="osprofiler",
description=__doc__.strip(),
add_help=True
prog="osprofiler", description=__doc__.strip(), add_help=True
)
parser.add_argument("-v", "--version",
action="version",
version=osprofiler.__version__)
parser.add_argument(
"-v", "--version", action="version", version=osprofiler.__version__
)
self._append_subcommands(parser)
@@ -60,22 +57,29 @@ class OSProfilerShell:
subcommand_parser = group_parser.add_subparsers()
for name, callback in inspect.getmembers(
group_cls(), predicate=inspect.ismethod):
group_cls(), predicate=inspect.ismethod
):
command = name.replace("_", "-")
desc = callback.__doc__ or ""
help_message = desc.strip().split("\n")[0]
arguments = getattr(callback, "arguments", [])
command_parser = subcommand_parser.add_parser(
command, help=help_message, description=desc)
for (args, kwargs) in arguments:
command, help=help_message, description=desc
)
for args, kwargs in arguments:
command_parser.add_argument(*args, **kwargs)
command_parser.set_defaults(func=callback)
def _no_project_and_domain_set(self, args):
if not (args.os_project_id or (args.os_project_name
and (args.os_user_domain_name or args.os_user_domain_id))
or (args.os_tenant_id or args.os_tenant_name)):
if not (
args.os_project_id
or (
args.os_project_name
and (args.os_user_domain_name or args.os_user_domain_id)
)
or (args.os_tenant_id or args.os_tenant_name)
):
return True
else:
return False
+1 -1
View File
@@ -1,7 +1,7 @@
from osprofiler.drivers import base # noqa
from osprofiler.drivers import elasticsearch_driver # noqa
from osprofiler.drivers import jaeger # noqa
from osprofiler.drivers import otlp # noqa
from osprofiler.drivers import otlp # noqa
from osprofiler.drivers import loginsight # noqa
from osprofiler.drivers import messaging # noqa
from osprofiler.drivers import mongodb # noqa
+70 -48
View File
@@ -31,22 +31,30 @@ def get_driver(connection_string, *args, **kwargs):
connection_string += "://"
parsed_connection = urlparse.urlparse(connection_string)
LOG.debug("String %s looks like a connection string, trying it.",
connection_string)
LOG.debug(
"String %s looks like a connection string, trying it.",
connection_string,
)
backend = parsed_connection.scheme
# NOTE(toabctl): To be able to use the connection_string for as sqlalchemy
# connection string, transform the backend to the correct driver
# See https://docs.sqlalchemy.org/en/latest/core/engines.html#database-urls
if backend in ["mysql", "mysql+pymysql", "mysql+mysqldb",
"postgresql", "postgresql+psycopg2"]:
if backend in [
"mysql",
"mysql+pymysql",
"mysql+mysqldb",
"postgresql",
"postgresql+psycopg2",
]:
backend = "sqlalchemy"
for driver in _utils.itersubclasses(Driver):
if backend == driver.get_name():
return driver(connection_string, *args, **kwargs)
raise ValueError("Driver not found for connection string: "
"%s" % connection_string)
raise ValueError(
f"Driver not found for connection string: {connection_string}"
)
class Driver:
@@ -60,8 +68,9 @@ class Driver:
default_trace_fields = {"base_id", "timestamp"}
def __init__(self, connection_str, project=None, service=None, host=None,
**kwargs):
def __init__(
self, connection_str, project=None, service=None, host=None, **kwargs
):
self.connection_str = connection_str
self.project = project
self.service = service
@@ -94,18 +103,20 @@ class Driver:
With parent_id and trace_id it's quite simple to build
tree of trace elements, which simplify analyze of trace.
"""
raise NotImplementedError("{}: This method is either not supported "
"or has to be overridden".format(
self.get_name()))
raise NotImplementedError(
f"{self.get_name()}: This method is either not supported "
"or has to be overridden"
)
def get_report(self, base_id):
"""Forms and returns report composed from the stored notifications.
:param base_id: Base id of trace elements.
"""
raise NotImplementedError("{}: This method is either not supported "
"or has to be overridden".format(
self.get_name()))
raise NotImplementedError(
f"{self.get_name()}: This method is either not supported "
"or has to be overridden"
)
@classmethod
def get_name(cls):
@@ -120,9 +131,10 @@ class Driver:
:returns: List of traces, where each trace is a dictionary containing
at least `base_id` and `timestamp`.
"""
raise NotImplementedError("{}: This method is either not supported "
"or has to be overridden".format(
self.get_name()))
raise NotImplementedError(
f"{self.get_name()}: This method is either not supported "
"or has to be overridden"
)
def list_error_traces(self):
"""Query all error traces from the storage.
@@ -130,23 +142,24 @@ class Driver:
:return List of traces, where each trace is a dictionary containing
`base_id` and `timestamp`.
"""
raise NotImplementedError("{}: This method is either not supported "
"or has to be overridden".format(
self.get_name()))
raise NotImplementedError(
f"{self.get_name()}: This method is either not supported "
"or has to be overridden"
)
@staticmethod
def _build_tree(nodes):
"""Builds the tree (forest) data structure based on the list of nodes.
Tree building works in O(n*log(n)).
Tree building works in O(n*log(n)).
:param nodes: dict of nodes, where each node is a dictionary with fields
"parent_id", "trace_id", "info"
:returns: list of top level ("root") nodes in form of dictionaries,
each containing the "info" and "children" fields, where
"children" is the list of child nodes ("children" will be
empty for leafs)
"""
:param nodes: dict of nodes, where each node is a dictionary with
fields "parent_id", "trace_id", "info"
:returns: list of top level ("root") nodes in form of dictionaries,
each containing the "info" and "children" fields, where
"children" is the list of child nodes ("children" will be
empty for leafs)
"""
tree = []
@@ -162,12 +175,22 @@ class Driver:
for trace_id in nodes:
nodes[trace_id]["children"].sort(
key=lambda x: x["info"]["started"])
key=lambda x: x["info"]["started"]
)
return sorted(tree, key=lambda x: x["info"]["started"])
def _append_results(self, trace_id, parent_id, name, project, service,
host, timestamp, raw_payload=None):
def _append_results(
self,
trace_id,
parent_id,
name,
project,
service,
host,
timestamp,
raw_payload=None,
):
"""Appends the notification to the dictionary of notifications.
:param trace_id: UUID of current trace point
@@ -181,8 +204,9 @@ class Driver:
:param raw_payload: raw notification without any filtering, with all
fields included
"""
timestamp = datetime.datetime.strptime(timestamp,
"%Y-%m-%dT%H:%M:%S.%f")
timestamp = datetime.datetime.strptime(
timestamp, "%Y-%m-%dT%H:%M:%S.%f"
)
if trace_id not in self.result:
self.result[trace_id] = {
"info": {
@@ -195,8 +219,7 @@ class Driver:
"parent_id": parent_id,
}
self.result[trace_id]["info"]["meta.raw_payload.%s"
% name] = raw_payload
self.result[trace_id]["info"][f"meta.raw_payload.{name}"] = raw_payload
if name.endswith("stop"):
self.result[trace_id]["info"]["finished"] = timestamp
@@ -224,8 +247,9 @@ class Driver:
def msec(dt):
# NOTE(boris-42): Unfortunately this is the simplest way that works
# in py26 and py27
microsec = (dt.microseconds + (dt.seconds + dt.days * 24 * 3600)
* 1e6)
microsec = (
dt.microseconds + (dt.seconds + dt.days * 24 * 3600) * 1e6
)
return int(microsec / 1000.0)
stats = {}
@@ -241,18 +265,14 @@ class Driver:
op_type = r["info"]["name"]
op_started = msec(r["info"]["started"] - self.started_at)
op_finished = msec(r["info"]["finished"]
- self.started_at)
op_finished = msec(r["info"]["finished"] - self.started_at)
duration = op_finished - op_started
r["info"]["started"] = op_started
r["info"]["finished"] = op_finished
if op_type not in stats:
stats[op_type] = {
"count": 1,
"duration": duration
}
stats[op_type] = {"count": 1, "duration": duration}
else:
stats[op_type]["count"] += 1
stats[op_type]["duration"] += duration
@@ -261,13 +281,15 @@ class Driver:
"info": {
"name": "total",
"started": 0,
"finished": msec(
self.finished_at - self.started_at
) if self.started_at else None,
"finished": msec(self.finished_at - self.started_at)
if self.started_at
else None,
"last_trace_started": msec(
self.last_started_at - self.started_at
) if self.started_at else None
)
if self.started_at
else None,
},
"children": self._build_tree(self.result),
"stats": stats
"stats": stats,
}
+59 -36
View File
@@ -22,27 +22,38 @@ from osprofiler import exc
class ElasticsearchDriver(base.Driver):
def __init__(self, connection_str, index_name="osprofiler-notifications",
project=None, service=None, host=None, conf=cfg.CONF,
**kwargs):
def __init__(
self,
connection_str,
index_name="osprofiler-notifications",
project=None,
service=None,
host=None,
conf=cfg.CONF,
**kwargs,
):
"""Elasticsearch driver for OSProfiler."""
super().__init__(connection_str,
project=project,
service=service,
host=host,
conf=conf,
**kwargs)
super().__init__(
connection_str,
project=project,
service=service,
host=host,
conf=conf,
**kwargs,
)
try:
from elasticsearch import Elasticsearch
except ImportError:
raise exc.CommandError(
"To use OSProfiler with ElasticSearch driver, "
"please install `elasticsearch` library. "
"To install with pip:\n `pip install elasticsearch`.")
"To install with pip:\n `pip install elasticsearch`."
)
client_url = parser.urlunparse(parser.urlparse(self.connection_str)
._replace(scheme="http"))
client_url = parser.urlunparse(
parser.urlparse(self.connection_str)._replace(scheme="http")
)
self.conf = conf
self.client = Elasticsearch(client_url)
self.index_name = index_name
@@ -69,11 +80,16 @@ class ElasticsearchDriver(base.Driver):
info = info.copy()
info["project"] = self.project
info["service"] = self.service
self.client.index(index=self.index_name,
doc_type=self.conf.profiler.es_doc_type, body=info)
self.client.index(
index=self.index_name,
doc_type=self.conf.profiler.es_doc_type,
body=info,
)
if (self.filter_error_trace
and info.get("info", {}).get("etype") is not None):
if (
self.filter_error_trace
and info.get("info", {}).get("etype") is not None
):
self.notify_error_trace(info)
def notify_error_trace(self, info):
@@ -81,7 +97,7 @@ class ElasticsearchDriver(base.Driver):
self.client.index(
index=self.index_name_error,
doc_type=self.conf.profiler.es_doc_type,
body={"base_id": info["base_id"], "timestamp": info["timestamp"]}
body={"base_id": info["base_id"], "timestamp": info["timestamp"]},
)
def _hits(self, response):
@@ -96,9 +112,9 @@ class ElasticsearchDriver(base.Driver):
while scroll_size > 0:
for hit in response["hits"]["hits"]:
result.append(hit["_source"])
response = self.client.scroll(scroll_id=scroll_id,
scroll=self.conf.profiler.
es_scroll_time)
response = self.client.scroll(
scroll_id=scroll_id, scroll=self.conf.profiler.es_scroll_time
)
scroll_id = response["_scroll_id"]
scroll_size = len(response["hits"]["hits"])
@@ -115,12 +131,17 @@ class ElasticsearchDriver(base.Driver):
query = {"match_all": {}}
fields = set(fields or self.default_trace_fields)
response = self.client.search(index=self.index_name,
doc_type=self.conf.profiler.es_doc_type,
size=self.conf.profiler.es_scroll_size,
scroll=self.conf.profiler.es_scroll_time,
body={"_source": fields, "query": query,
"sort": [{"timestamp": "asc"}]})
response = self.client.search(
index=self.index_name,
doc_type=self.conf.profiler.es_doc_type,
size=self.conf.profiler.es_scroll_size,
scroll=self.conf.profiler.es_scroll_time,
body={
"_source": fields,
"query": query,
"sort": [{"timestamp": "asc"}],
},
)
return self._hits(response)
@@ -134,8 +155,8 @@ class ElasticsearchDriver(base.Driver):
body={
"_source": self.default_trace_fields,
"query": {"match_all": {}},
"sort": [{"timestamp": "asc"}]
}
"sort": [{"timestamp": "asc"}],
},
)
return self._hits(response)
@@ -145,12 +166,13 @@ class ElasticsearchDriver(base.Driver):
:param base_id: Base id of trace elements.
"""
response = self.client.search(index=self.index_name,
doc_type=self.conf.profiler.es_doc_type,
size=self.conf.profiler.es_scroll_size,
scroll=self.conf.profiler.es_scroll_time,
body={"query": {
"match": {"base_id": base_id}}})
response = self.client.search(
index=self.index_name,
doc_type=self.conf.profiler.es_doc_type,
size=self.conf.profiler.es_scroll_size,
scroll=self.conf.profiler.es_scroll_time,
body={"query": {"match": {"base_id": base_id}}},
)
for n in self._hits(response):
trace_id = n["trace_id"]
@@ -161,7 +183,8 @@ class ElasticsearchDriver(base.Driver):
host = n["info"]["host"]
timestamp = n["timestamp"]
self._append_results(trace_id, parent_id, name, project, service,
host, timestamp, n)
self._append_results(
trace_id, parent_id, name, project, service, host, timestamp, n
)
return self._parse_results()
+9 -2
View File
@@ -19,8 +19,15 @@ from osprofiler import exc
# TODO(tkajinam): Remove this and the deprecated options after G-release
class Jaeger(base.Driver):
def __init__(self, connection_str, project=None, service=None, host=None,
conf=None, **kwargs):
def __init__(
self,
connection_str,
project=None,
service=None,
host=None,
conf=None,
**kwargs,
):
"""Jaeger driver for OSProfiler."""
raise exc.CommandError('Jaeger driver is no longer supported')
+84 -55
View File
@@ -46,23 +46,25 @@ class LogInsightDriver(base.Driver):
to Log Insight server at 10.1.2.3 using username "osprofiler" and password
"p@ssword" is: loginsight://osprofiler:p%40ssword@10.1.2.3
"""
def __init__(
self, connection_str, project=None, service=None, host=None,
**kwargs):
super().__init__(connection_str,
project=project,
service=service,
host=host)
self, connection_str, project=None, service=None, host=None, **kwargs
):
super().__init__(
connection_str, project=project, service=service, host=host
)
parsed_connection = urlparse.urlparse(connection_str)
try:
creds, host = parsed_connection.netloc.split("@")
username, password = creds.split(":")
except ValueError:
raise ValueError("Connection string format is: loginsight://"
"<username>:<password>@<loginsight-host>. If the "
"username or password contains the character '@' "
"or ':', it must be escaped using URL encoding.")
raise ValueError(
"Connection string format is: loginsight://"
"<username>:<password>@<loginsight-host>. If the "
"username or password contains the character '@' "
"or ':', it must be escaped using URL encoding."
)
username = urlparse.unquote(username)
password = urlparse.unquote(password)
@@ -86,12 +88,14 @@ class LogInsightDriver(base.Driver):
def _create_field(name, content):
return {"name": name, "content": content}
event["fields"] = [_create_field("base_id", trace["base_id"]),
_create_field("trace_id", trace["trace_id"]),
_create_field("project", trace["project"]),
_create_field("service", trace["service"]),
_create_field("name", trace["name"]),
_create_field("trace", json.dumps(trace))]
event["fields"] = [
_create_field("base_id", trace["base_id"]),
_create_field("trace_id", trace["trace_id"]),
_create_field("project", trace["project"]),
_create_field("service", trace["service"]),
_create_field("name", trace["name"]),
_create_field("trace", json.dumps(trace)),
]
self._client.send_event(event)
@@ -119,8 +123,15 @@ class LogInsightDriver(base.Driver):
timestamp = trace["timestamp"]
self._append_results(
trace_id, parent_id, name, project, service, host,
timestamp, trace)
trace_id,
parent_id,
name,
project,
service,
host,
timestamp,
trace,
)
break
return self._parse_results()
@@ -134,11 +145,18 @@ class LogInsightClient:
# API paths
SESSIONS_PATH = "api/v1/sessions"
CURRENT_SESSIONS_PATH = "api/v1/sessions/current"
EVENTS_INGEST_PATH = "api/v1/events/ingest/%s" % LI_OSPROFILER_AGENT_ID
EVENTS_INGEST_PATH = f"api/v1/events/ingest/{LI_OSPROFILER_AGENT_ID}"
QUERY_EVENTS_BASE_PATH = "api/v1/events"
def __init__(self, host, username, password, api_port=9000,
api_ssl_port=9543, query_timeout=60000):
def __init__(
self,
host,
username,
password,
api_port=9000,
api_ssl_port=9543,
query_timeout=60000,
):
self._host = host
self._username = username
self._password = password
@@ -149,11 +167,13 @@ class LogInsightClient:
self._session_id = None
def _build_base_url(self, scheme):
proto_str = "%s://" % scheme
host_str = ("[%s]" % self._host if netaddr.valid_ipv6(self._host)
else self._host)
port_str = ":%d" % (self._api_ssl_port if scheme == "https"
else self._api_port)
proto_str = f"{scheme}://"
host_str = (
f"[{self._host}]" if netaddr.valid_ipv6(self._host) else self._host
)
port_str = ":%s" % (
self._api_ssl_port if scheme == "https" else self._api_port
)
return proto_str + host_str + port_str
def _check_response(self, resp):
@@ -173,8 +193,9 @@ class LogInsightClient:
raise exc.LogInsightAPIError(msg)
def _send_request(
self, method, scheme, path, headers=None, body=None, params=None):
url = "{}/{}".format(self._build_base_url(scheme), path)
self, method, scheme, path, headers=None, body=None, params=None
):
url = f"{self._build_base_url(scheme)}/{path}"
headers = headers or {}
headers["content-type"] = "application/json"
@@ -182,7 +203,8 @@ class LogInsightClient:
params = params or {}
req = requests.Request(
method, url, headers=headers, data=json.dumps(body), params=params)
method, url, headers=headers, data=json.dumps(body), params=params
)
req = req.prepare()
resp = self._session.send(req, verify=False)
@@ -198,16 +220,20 @@ class LogInsightClient:
def _is_current_session_active(self):
try:
self._send_request("get",
"https",
self.CURRENT_SESSIONS_PATH,
headers=self._get_auth_header())
LOG.debug("Current session %s is active.",
self._trunc_session_id())
self._send_request(
"get",
"https",
self.CURRENT_SESSIONS_PATH,
headers=self._get_auth_header(),
)
LOG.debug(
"Current session %s is active.", self._trunc_session_id()
)
return True
except (exc.LogInsightLoginTimeout, exc.LogInsightAPIError):
LOG.debug("Current session %s is not active.",
self._trunc_session_id())
LOG.debug(
"Current session %s is not active.", self._trunc_session_id()
)
return False
@synchronized("li_login_lock")
@@ -218,40 +244,43 @@ class LogInsightClient:
return
LOG.info("Logging into Log Insight server: %s.", self._host)
resp = self._send_request("post",
"https",
self.SESSIONS_PATH,
body={"username": self._username,
"password": self._password})
resp = self._send_request(
"post",
"https",
self.SESSIONS_PATH,
body={"username": self._username, "password": self._password},
)
self._session_id = resp["sessionId"]
LOG.debug("Established session %s.", self._trunc_session_id())
def send_event(self, event):
events = {"events": [event]}
self._send_request("post",
"http",
self.EVENTS_INGEST_PATH,
body=events)
self._send_request(
"post", "http", self.EVENTS_INGEST_PATH, body=events
)
def query_events(self, params):
# Assumes that the keys and values in the params are strings and
# the operator is "CONTAINS".
constraints = []
for field, value in params.items():
constraints.append("{}/CONTAINS+{}".format(field, value))
constraints.append(f"{field}/CONTAINS+{value}")
constraints.append("timestamp/GT+0")
path = "{}/{}".format(self.QUERY_EVENTS_BASE_PATH,
"/".join(constraints))
path = "{}/{}".format(
self.QUERY_EVENTS_BASE_PATH, "/".join(constraints)
)
def _query_events():
return self._send_request("get",
"https",
path,
headers=self._get_auth_header(),
params={"limit": 20000,
"timeout": self._query_timeout})
return self._send_request(
"get",
"https",
path,
headers=self._get_auth_header(),
params={"limit": 20000, "timeout": self._query_timeout},
)
try:
resp = _query_events()
except exc.LogInsightLoginTimeout:
+51 -25
View File
@@ -23,9 +23,18 @@ from osprofiler.drivers import base
class Messaging(base.Driver):
def __init__(self, connection_str, project=None, service=None, host=None,
context=None, conf=None, transport_url=None,
idle_timeout=1, **kwargs):
def __init__(
self,
connection_str,
project=None,
service=None,
host=None,
context=None,
conf=None,
transport_url=None,
idle_timeout=1,
**kwargs,
):
"""Driver that uses messaging as transport for notifications
:param connection_str: OSProfiler driver connection string,
@@ -46,19 +55,22 @@ class Messaging(base.Driver):
self.oslo_messaging = importutils.try_import("oslo_messaging")
if not self.oslo_messaging:
raise ValueError("Oslo.messaging library is required for "
"messaging driver")
raise ValueError(
"Oslo.messaging library is required for messaging driver"
)
super().__init__(connection_str, project=project,
service=service, host=host)
super().__init__(
connection_str, project=project, service=service, host=host
)
self.context = context
if not conf:
oslo_config = importutils.try_import("oslo_config")
if not oslo_config:
raise ValueError("Oslo.config library is required for "
"messaging driver")
raise ValueError(
"Oslo.config library is required for messaging driver"
)
conf = oslo_config.cfg.CONF
transport_kwargs = {}
@@ -66,10 +78,15 @@ class Messaging(base.Driver):
transport_kwargs["url"] = transport_url
self.transport = self.oslo_messaging.get_notification_transport(
conf, **transport_kwargs)
conf, **transport_kwargs
)
self.client = self.oslo_messaging.Notifier(
self.transport, publisher_id=self.host, driver="messaging",
topics=["profiler"], retry=0)
self.transport,
publisher_id=self.host,
driver="messaging",
topics=["profiler"],
retry=0,
)
self.idle_timeout = idle_timeout
@@ -95,16 +112,19 @@ class Messaging(base.Driver):
info["project"] = self.project
info["service"] = self.service
self.client.info(context or self.context,
"profiler.%s" % info["service"],
info)
self.client.info(
context or self.context,
"profiler.{}".format(info["service"]),
info,
)
def get_report(self, base_id):
notification_endpoint = NotifyEndpoint(self.oslo_messaging, base_id)
endpoints = [notification_endpoint]
targets = [self.oslo_messaging.Target(topic="profiler")]
server = self.oslo_messaging.notify.get_notification_listener(
self.transport, targets, endpoints, executor="threading")
self.transport, targets, endpoints, executor="threading"
)
state = dict(running=False)
sfn = functools.partial(signal_handler, state=state)
@@ -119,8 +139,10 @@ class Messaging(base.Driver):
# failed to start the server
raise
except SignalExit:
print("Execution interrupted while trying to connect to "
"messaging server. No data was collected.")
print(
"Execution interrupted while trying to connect to "
"messaging server. No data was collected."
)
return {}
# connected to server, now read the data
@@ -148,9 +170,12 @@ class Messaging(base.Driver):
events = notification_endpoint.get_messages()
if not events:
print("No events are collected for Trace UUID %s. Please note "
"that osprofiler has read ALL events from profiler topic, "
"but has not found any for specified Trace UUID." % base_id)
print(
f"No events are collected for Trace UUID {base_id}. "
f"Please note that osprofiler has read ALL events from "
f"profiler topic, but has not found any for specified Trace "
f"UUID."
)
for n in events:
trace_id = n["trace_id"]
@@ -161,19 +186,20 @@ class Messaging(base.Driver):
host = n["info"]["host"]
timestamp = n["timestamp"]
self._append_results(trace_id, parent_id, name, project, service,
host, timestamp, n)
self._append_results(
trace_id, parent_id, name, project, service, host, timestamp, n
)
return self._parse_results()
class NotifyEndpoint:
def __init__(self, oslo_messaging, base_id):
self.received_messages = []
self.last_read_time = time.time()
self.filter_rule = oslo_messaging.NotificationFilter(
payload={"base_id": base_id})
payload={"base_id": base_id}
)
def info(self, ctxt, publisher_id, event_type, payload, metadata):
self.received_messages.append(payload)
+32 -12
View File
@@ -18,19 +18,32 @@ from osprofiler import exc
class MongoDB(base.Driver):
def __init__(self, connection_str, db_name="osprofiler", project=None,
service=None, host=None, **kwargs):
def __init__(
self,
connection_str,
db_name="osprofiler",
project=None,
service=None,
host=None,
**kwargs,
):
"""MongoDB driver for OSProfiler."""
super().__init__(connection_str, project=project,
service=service, host=host, **kwargs)
super().__init__(
connection_str,
project=project,
service=service,
host=host,
**kwargs,
)
try:
from pymongo import MongoClient
except ImportError:
raise exc.CommandError(
"To use OSProfiler with MongoDB driver, "
"please install `pymongo` library. "
"To install with pip:\n `pip install pymongo`.")
"To install with pip:\n `pip install pymongo`."
)
client = MongoClient(self.connection_str, connect=False)
self.db = client[db_name]
@@ -57,8 +70,10 @@ class MongoDB(base.Driver):
data["service"] = self.service
self.db.profiler.insert_one(data)
if (self.filter_error_trace
and data.get("info", {}).get("etype") is not None):
if (
self.filter_error_trace
and data.get("info", {}).get("etype") is not None
):
self.notify_error_trace(data)
def notify_error_trace(self, data):
@@ -66,7 +81,7 @@ class MongoDB(base.Driver):
self.db.profiler_error.update(
{"base_id": data["base_id"]},
{"base_id": data["base_id"], "timestamp": data["timestamp"]},
upsert=True
upsert=True,
)
def list_traces(self, fields=None):
@@ -81,8 +96,12 @@ class MongoDB(base.Driver):
ids = self.db.profiler.find({}).distinct("base_id")
out_format = {"base_id": 1, "timestamp": 1, "_id": 0}
out_format.update({i: 1 for i in fields})
return [self.db.profiler.find(
{"base_id": i}, out_format).sort("timestamp")[0] for i in ids]
return [
self.db.profiler.find({"base_id": i}, out_format).sort(
"timestamp"
)[0]
for i in ids
]
def list_error_traces(self):
"""Returns all traces that have error/exception."""
@@ -103,7 +122,8 @@ class MongoDB(base.Driver):
host = n["info"]["host"]
timestamp = n["timestamp"]
self._append_results(trace_id, parent_id, name, project, service,
host, timestamp, n)
self._append_results(
trace_id, parent_id, name, project, service, host, timestamp, n
)
return self._parse_results()
+56 -30
View File
@@ -24,17 +24,31 @@ from osprofiler import exc
class OTLP(base.Driver):
def __init__(self, connection_str, project=None, service=None, host=None,
conf=cfg.CONF, **kwargs):
def __init__(
self,
connection_str,
project=None,
service=None,
host=None,
conf=cfg.CONF,
**kwargs,
):
"""OTLP driver using OTLP exporters."""
super().__init__(connection_str, project=project,
service=service, host=host,
conf=conf, **kwargs)
super().__init__(
connection_str,
project=project,
service=service,
host=host,
conf=conf,
**kwargs,
)
try:
from opentelemetry import trace as trace_api
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter # noqa
from opentelemetry.exporter.otlp.proto.http.trace_exporter import (
OTLPSpanExporter,
) # noqa
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.sdk.trace import TracerProvider
@@ -46,33 +60,31 @@ class OTLP(base.Driver):
"please install `opentelemetry-sdk` and "
"opentelemetry-exporter-otlp libraries. "
"To install with pip:\n `pip install opentelemetry-sdk "
"opentelemetry-exporter-otlp`.")
"opentelemetry-exporter-otlp`."
)
service_name = self._get_service_name(conf, project, service)
resource = Resource(attributes={
"service.name": service_name
})
resource = Resource(attributes={"service.name": service_name})
parsed_url = parser.urlparse(connection_str)
# TODO("sahid"): We also want to handle https scheme?
parsed_url = parsed_url._replace(scheme="http")
self.trace_api.set_tracer_provider(
TracerProvider(resource=resource))
self.trace_api.set_tracer_provider(TracerProvider(resource=resource))
self.tracer = self.trace_api.get_tracer(__name__)
exporter = OTLPSpanExporter("{}/v1/traces".format(
parsed_url.geturl()))
exporter = OTLPSpanExporter(f"{parsed_url.geturl()}/v1/traces")
self.trace_api.get_tracer_provider().add_span_processor(
BatchSpanProcessor(exporter))
BatchSpanProcessor(exporter)
)
self.spans = collections.deque()
def _get_service_name(self, conf, project, service):
prefix = conf.profiler_otlp.service_name_prefix
if prefix:
return "{}-{}-{}".format(prefix, project, service)
return "{}-{}".format(project, service)
return f"{prefix}-{project}-{service}"
return f"{project}-{service}"
@classmethod
def get_name(cls):
@@ -81,7 +93,7 @@ class OTLP(base.Driver):
def _kind(self, name):
if "wsgi" in name:
return self.trace_api.SpanKind.SERVER
elif ("db" in name or "http" in name or "api" in name):
elif "db" in name or "http" in name or "api" in name:
return self.trace_api.SpanKind.CLIENT
return self.trace_api.SpanKind.INTERNAL
@@ -89,13 +101,16 @@ class OTLP(base.Driver):
info = payload["info"]
if info.get("request"):
return "WSGI_{}_{}".format(
info["request"]["method"], info["request"]["path"])
info["request"]["method"], info["request"]["path"]
)
elif info.get("db"):
return "SQL_{}".format(
info["db"]["statement"].split(' ', 1)[0].upper())
info["db"]["statement"].split(' ', 1)[0].upper()
)
elif info.get("requests"):
return "REQUESTS_{}_{}".format(
info["requests"]["method"], info["requests"]["hostname"])
info["requests"]["method"], info["requests"]["hostname"]
)
return payload["name"].rstrip("-start")
def notify(self, payload):
@@ -105,24 +120,29 @@ class OTLP(base.Driver):
span_id=utils.shorten_id(payload["parent_id"]),
is_remote=False,
trace_flags=self.trace_api.TraceFlags(
self.trace_api.TraceFlags.SAMPLED))
self.trace_api.TraceFlags.SAMPLED
),
)
ctx = self.trace_api.set_span_in_context(
self.trace_api.NonRecordingSpan(parent))
self.trace_api.NonRecordingSpan(parent)
)
# OTLP Tracing span
span = self.tracer.start_span(
name=self._name(payload),
kind=self._kind(payload['name']),
attributes=self.create_span_tags(payload),
context=ctx)
context=ctx,
)
span._context = self.trace_api.SpanContext(
trace_id=span.context.trace_id,
span_id=utils.shorten_id(payload["trace_id"]),
is_remote=span.context.is_remote,
trace_flags=span.context.trace_flags,
trace_state=span.context.trace_state)
trace_state=span.context.trace_state,
)
self.spans.append(span)
else:
@@ -132,17 +152,23 @@ class OTLP(base.Driver):
for call in ("db", "function"):
if payload.get("info", {}).get(call, {}).get("result"):
span.set_attribute(
"result", payload["info"][call]["result"])
"result", payload["info"][call]["result"]
)
# Store result of requests
if payload.get("info", {}).get("requests"):
span.set_attribute(
"status_code", payload["info"]["requests"]["status_code"])
"status_code", payload["info"]["requests"]["status_code"]
)
# Span error tag and log
if payload["info"].get("etype"):
span.set_attribute("error", True)
span.add_event("log", {
"error.kind": payload["info"]["etype"],
"message": payload["info"]["message"]})
span.add_event(
"log",
{
"error.kind": payload["info"]["etype"],
"message": payload["info"]["message"],
},
)
span.end()
def get_report(self, base_id):
+90 -38
View File
@@ -25,24 +25,41 @@ from osprofiler import exc
class Redis(base.Driver):
@removals.removed_kwarg("db", message="'db' parameter is deprecated "
"and will be removed in future. "
"Please specify 'db' in "
"'connection_string' instead.")
def __init__(self, connection_str, db=0, project=None,
service=None, host=None, conf=cfg.CONF, **kwargs):
@removals.removed_kwarg(
"db",
message="'db' parameter is deprecated "
"and will be removed in future. "
"Please specify 'db' in "
"'connection_string' instead.",
)
def __init__(
self,
connection_str,
db=0,
project=None,
service=None,
host=None,
conf=cfg.CONF,
**kwargs,
):
"""Redis driver for OSProfiler."""
super().__init__(connection_str, project=project,
service=service, host=host,
conf=conf, **kwargs)
super().__init__(
connection_str,
project=project,
service=service,
host=host,
conf=conf,
**kwargs,
)
try:
from redis import Redis as _Redis
except ImportError:
raise exc.CommandError(
"To use OSProfiler with Redis driver, "
"please install `redis` library. "
"To install with pip:\n `pip install redis`.")
"To install with pip:\n `pip install redis`."
)
# only connection over network is supported with schema
# redis://[:password]@host[:port][/db]
@@ -74,17 +91,18 @@ class Redis(base.Driver):
key = self.namespace_opt + data["base_id"]
self.db.lpush(key, jsonutils.dumps(data))
if (self.filter_error_trace
and data.get("info", {}).get("etype") is not None):
if (
self.filter_error_trace
and data.get("info", {}).get("etype") is not None
):
self.notify_error_trace(data)
def notify_error_trace(self, data):
"""Store base_id and timestamp of error trace to a separate key."""
key = self.namespace_error + data["base_id"]
value = jsonutils.dumps({
"base_id": data["base_id"],
"timestamp": data["timestamp"]
})
value = jsonutils.dumps(
{"base_id": data["base_id"], "timestamp": data["timestamp"]}
)
self.db.set(key, value)
def list_traces(self, fields=None):
@@ -105,8 +123,13 @@ class Redis(base.Driver):
for i in ids:
# for each trace query the first event to have a timestamp
first_event = jsonutils.loads(self.db.lindex(i, 1))
result.append({key: value for key, value in first_event.items()
if key in fields})
result.append(
{
key: value
for key, value in first_event.items()
if key in fields
}
)
return result
def _list_traces_legacy(self, fields):
@@ -121,8 +144,13 @@ class Redis(base.Driver):
for trace in traces:
if trace["base_id"] not in seen_ids:
seen_ids.add(trace["base_id"])
result.append({key: value for key, value in trace.items()
if key in fields})
result.append(
{
key: value
for key, value in trace.items()
if key in fields
}
)
return result
def list_error_traces(self):
@@ -144,9 +172,11 @@ class Redis(base.Driver):
:param base_id: Base id of trace elements.
"""
def iterate_events():
for key in self.db.scan_iter(
match=self.namespace + base_id + "*"): # legacy
match=self.namespace + base_id + "*"
): # legacy
yield self.db.get(key)
yield from self.db.lrange(self.namespace_opt + base_id, 0, -1)
@@ -161,40 +191,62 @@ class Redis(base.Driver):
host = n["info"]["host"]
timestamp = n["timestamp"]
self._append_results(trace_id, parent_id, name, project, service,
host, timestamp, n)
self._append_results(
trace_id, parent_id, name, project, service, host, timestamp, n
)
return self._parse_results()
class RedisSentinel(Redis, base.Driver):
@removals.removed_kwarg("db", message="'db' parameter is deprecated "
"and will be removed in future. "
"Please specify 'db' in "
"'connection_string' instead.")
def __init__(self, connection_str, db=0, project=None,
service=None, host=None, conf=cfg.CONF, **kwargs):
@removals.removed_kwarg(
"db",
message="'db' parameter is deprecated "
"and will be removed in future. "
"Please specify 'db' in "
"'connection_string' instead.",
)
def __init__(
self,
connection_str,
db=0,
project=None,
service=None,
host=None,
conf=cfg.CONF,
**kwargs,
):
"""Redis driver for OSProfiler."""
super().__init__(connection_str, project=project,
service=service, host=host,
conf=conf, **kwargs)
super().__init__(
connection_str,
project=project,
service=service,
host=host,
conf=conf,
**kwargs,
)
try:
from redis.sentinel import Sentinel
except ImportError:
raise exc.CommandError(
"To use this command, you should install "
"'redis' manually. Use command:\n "
"'pip install redis'.")
"'pip install redis'."
)
self.conf = conf
socket_timeout = self.conf.profiler.socket_timeout
parsed_url = parser.urlparse(self.connection_str)
sentinel = Sentinel([(parsed_url.hostname, int(parsed_url.port))],
password=parsed_url.password,
socket_timeout=socket_timeout)
self.db = sentinel.master_for(self.conf.profiler.sentinel_service_name,
socket_timeout=socket_timeout)
sentinel = Sentinel(
[(parsed_url.hostname, int(parsed_url.port))],
password=parsed_url.password,
socket_timeout=socket_timeout,
)
self.db = sentinel.master_for(
self.conf.profiler.sentinel_service_name,
socket_timeout=socket_timeout,
)
@classmethod
def get_name(cls):
+42 -18
View File
@@ -24,10 +24,12 @@ LOG = logging.getLogger(__name__)
class SQLAlchemyDriver(base.Driver):
def __init__(self, connection_str, project=None, service=None, host=None,
**kwargs):
super().__init__(connection_str, project=project,
service=service, host=host)
def __init__(
self, connection_str, project=None, service=None, host=None, **kwargs
):
super().__init__(
connection_str, project=project, service=service, host=host
)
try:
from sqlalchemy import create_engine
@@ -38,7 +40,8 @@ class SQLAlchemyDriver(base.Driver):
else:
self._metadata = MetaData()
self._data_table = Table(
"data", self._metadata,
"data",
self._metadata,
Column("id", Integer, primary_key=True),
# timestamp - date/time of the trace point
Column("timestamp", String(26), index=True),
@@ -54,7 +57,7 @@ class SQLAlchemyDriver(base.Driver):
Column("service", String(255), index=True),
# name - trace point name
Column("name", String(255), index=True),
Column("data", JSON)
Column("data", JSON),
)
# we don't want to kill any service that does use osprofiler
@@ -66,8 +69,10 @@ class SQLAlchemyDriver(base.Driver):
# startup when using the sqlalchemy driver...
self._metadata.create_all(self._engine, checkfirst=True)
except Exception:
LOG.exception("Failed to create engine/connection and setup "
"intial database tables")
LOG.exception(
"Failed to create engine/connection and setup "
"intial database tables"
)
@classmethod
def get_name(cls):
@@ -95,19 +100,23 @@ class SQLAlchemyDriver(base.Driver):
service=service,
host=host,
name=name,
data=jsonutils.dumps(data)
data=jsonutils.dumps(data),
)
self._conn.execute(ins)
except Exception:
LOG.exception("Can not store osprofiler tracepoint {} "
"(base_id {})".format(trace_id, base_id))
LOG.exception(
"Can not store osprofiler tracepoint %s (base id %s)",
trace_id,
base_id,
)
def list_traces(self, fields=None):
try:
from sqlalchemy.sql import select
except ImportError:
raise exc.CommandError(
"To use this command, you should install 'SQLAlchemy'")
"To use this command, you should install 'SQLAlchemy'"
)
stmt = select([self._data_table])
seen_ids = set()
result = []
@@ -115,8 +124,13 @@ class SQLAlchemyDriver(base.Driver):
for trace in traces:
if trace["base_id"] not in seen_ids:
seen_ids.add(trace["base_id"])
result.append({key: value for key, value in trace.items()
if key in fields})
result.append(
{
key: value
for key, value in trace.items()
if key in fields
}
)
return result
def get_report(self, base_id):
@@ -124,9 +138,11 @@ class SQLAlchemyDriver(base.Driver):
from sqlalchemy.sql import select
except ImportError:
raise exc.CommandError(
"To use this command, you should install 'SQLAlchemy'")
"To use this command, you should install 'SQLAlchemy'"
)
stmt = select([self._data_table]).where(
self._data_table.c.base_id == base_id)
self._data_table.c.base_id == base_id
)
results = self._conn.execute(stmt).fetchall()
for n in results:
timestamp = n["timestamp"]
@@ -137,6 +153,14 @@ class SQLAlchemyDriver(base.Driver):
service = n["service"]
host = n["host"]
data = jsonutils.loads(n["data"])
self._append_results(trace_id, parent_id, name, project, service,
host, timestamp, data)
self._append_results(
trace_id,
parent_id,
name,
project,
service,
host,
timestamp,
data,
)
return self._parse_results()
+68 -43
View File
@@ -30,11 +30,10 @@ import tokenize
from hacking import core
re_no_construct_dict = re.compile(
r"\sdict\(\)")
re_no_construct_list = re.compile(
r"\slist\(\)")
re_str_format = re.compile(r"""
re_no_construct_dict = re.compile(r"\sdict\(\)")
re_no_construct_list = re.compile(r"\slist\(\)")
re_str_format = re.compile(
r"""
% # start of specifier
\(([^)]+)\) # mapping key, in group 1
[#0 +\-]? # optional conversion flag
@@ -42,9 +41,10 @@ re_str_format = re.compile(r"""
(?:\.\d*)? # optional precision
[hLl]? # optional length modifier
[A-z%] # conversion modifier
""", re.X)
re_raises = re.compile(
r"\s:raise[^s] *.*$|\s:raises *:.*$|\s:raises *[^:]+$")
""",
re.X,
)
re_raises = re.compile(r"\s:raise[^s] *.*$|\s:raises *:.*$|\s:raises *[^:]+$")
@core.flake8ext
@@ -68,7 +68,7 @@ def _parse_assert_mock_str(line):
if point != -1:
end_pos = line[point:].find("(") + point
return point, line[point + 1: end_pos], line[: point]
return point, line[point + 1 : end_pos], line[:point]
else:
return None, None, None
@@ -83,8 +83,12 @@ def check_assert_methods_from_mock(logical_line, filename):
N303 - related to nonexistent "assert_called_once"
"""
correct_names = ["assert_any_call", "assert_called_once_with",
"assert_called_with", "assert_has_calls"]
correct_names = [
"assert_any_call",
"assert_called_once_with",
"assert_called_with",
"assert_has_calls",
]
ignored_files = ["./tests/unit/test_hacking.py"]
if filename.startswith("./tests") and filename not in ignored_files:
@@ -93,31 +97,42 @@ def check_assert_methods_from_mock(logical_line, filename):
if pos:
if method_name not in correct_names:
error_number = "N301"
msg = ("%(error_number)s:'%(method)s' is not present in `mock`"
" library. %(custom_msg)s For more details, visit "
"http://www.voidspace.org.uk/python/mock/ .")
msg = (
"%(error_number)s:'%(method)s' is not present in `mock`"
" library. %(custom_msg)s For more details, visit "
"http://www.voidspace.org.uk/python/mock/ ."
)
if method_name == "assert_called":
error_number = "N302"
custom_msg = ("Maybe, you should try to use "
"'assertTrue(%s.called)' instead." %
obj_name)
custom_msg = (
"Maybe, you should try to use "
f"'assertTrue({obj_name}.called)' instead."
)
elif method_name == "assert_called_once":
# For more details, see a bug in Rally:
# https://bugs.launchpad.net/rally/+bug/1305991
error_number = "N303"
custom_msg = ("Maybe, you should try to use "
"'assertEqual(1, %s.call_count)' "
"or '%s.assert_called_once_with()'"
" instead." % (obj_name, obj_name))
custom_msg = (
"Maybe, you should try to use "
f"'assertEqual(1, {obj_name}.call_count)' "
f"or '{obj_name}.assert_called_once_with()'"
" instead."
)
else:
custom_msg = ("Correct 'assert_*' methods: '%s'."
% "', '".join(correct_names))
custom_msg = "Correct 'assert_*' methods: '{}'.".format(
"', '".join(correct_names)
)
yield (pos, msg % {
"error_number": error_number,
"method": method_name,
"custom_msg": custom_msg})
yield (
pos,
msg
% {
"error_number": error_number,
"method": method_name,
"custom_msg": custom_msg,
},
)
@skip_ignored_lines
@@ -132,12 +147,10 @@ def check_quotes(logical_line, filename):
in_multiline_string = False
single_quotas_are_used = False
check_tripple = (
lambda line, i, char: (
i + 2 < len(line)
and (char == line[i] == line[i + 1] == line[i + 2])
def check_tripple(line, i, char):
return i + 2 < len(line) and (
char == line[i] == line[i + 1] == line[i + 2]
)
)
i = 0
while i < len(logical_line):
@@ -199,9 +212,11 @@ def check_dict_formatting_in_string(logical_line, tokens):
# NOTE(stpierre): Can't use @skip_ignored_lines here because it's
# a stupid decorator that only works on functions that take
# (logical_line, filename) as arguments.
if (not logical_line
if (
not logical_line
or logical_line.startswith("#")
or logical_line.endswith("# noqa")):
or logical_line.endswith("# noqa")
):
return
current_string = ""
@@ -241,9 +256,11 @@ def check_dict_formatting_in_string(logical_line, tokens):
for match in re_str_format.finditer(current_string):
format_keys.add(match.group(1))
if len(format_keys) == 1:
yield (0,
"N352 Do not use mapping key string formatting "
"with a single key")
yield (
0,
"N352 Do not use mapping key string formatting "
"with a single key",
)
if text != ")":
# NOTE(stpierre): You can have a parenthesized string
# followed by %, so a closing paren doesn't obviate
@@ -267,8 +284,11 @@ def check_using_unicode(logical_line, filename):
"""
if re.search(r"\bunicode\(", logical_line):
yield (0, "N353 'unicode' function is absent in python3. Please "
"use 'str' instead.")
yield (
0,
"N353 'unicode' function is absent in python3. Please "
"use 'str' instead.",
)
@core.flake8ext
@@ -278,9 +298,14 @@ def check_raises(physical_line, filename):
N354
"""
ignored_files = ["./tests/unit/test_hacking.py",
"./tests/hacking/checks.py"]
ignored_files = [
"./tests/unit/test_hacking.py",
"./tests/hacking/checks.py",
]
if filename not in ignored_files:
if re_raises.search(physical_line):
return (0, "N354 ':Please use ':raises Exception: conditions' "
"in docstrings.")
return (
0,
"N354 ':Please use ':raises Exception: conditions' "
"in docstrings.",
)
+2 -1
View File
@@ -37,7 +37,8 @@ def init_from_conf(conf, context, project, service, host, **kwargs):
service=service,
host=host,
conf=conf,
**kwargs)
**kwargs,
)
notifier.set(_notifier)
web.enable(conf.profiler.hmac_keys)
if conf.profiler.trace_requests:
+12 -7
View File
@@ -46,9 +46,9 @@ def get():
def set(notifier):
"""Service that are going to use profiler should set callable notifier.
Callable notifier is instance of callable object, that accept exactly
one argument "info". "info" - is dictionary of values that contains
profiling information.
Callable notifier is instance of callable object, that accept exactly
one argument "info". "info" - is dictionary of values that contains
profiling information.
"""
global __notifier
__notifier = notifier
@@ -68,11 +68,16 @@ def create(connection_string, *args, **kwargs):
try:
driver = base.get_driver(connection_string, *args, **kwargs)
__notifier_cache[connection_string] = driver.notify
LOG.info("osprofiler is enabled with connection string: %s",
connection_string)
LOG.info(
"osprofiler is enabled with connection string: %s",
connection_string,
)
except Exception:
LOG.exception("Could not initialize driver for connection string "
"%s, osprofiler is disabled", connection_string)
LOG.exception(
"Could not initialize driver for connection string "
"%s, osprofiler is disabled",
connection_string,
)
__notifier_cache[connection_string] = _noop_notifier
return __notifier_cache[connection_string]
+84 -49
View File
@@ -29,7 +29,8 @@ _profiler_opt_group = cfg.OptGroup(
OSprofiler library allows to trace requests going through various OpenStack
services and create the accumulated report of what time was spent on each
request processing step.
""")
""",
)
_enabled_opt = cfg.BoolOpt(
"enabled",
@@ -46,7 +47,8 @@ Possible values:
* False: Disables the feature. The profiling cannot be started via this project
operations. If the profiling is triggered by another project, this project
part will be empty.
""")
""",
)
_trace_sqlalchemy_opt = cfg.BoolOpt(
"trace_sqlalchemy",
@@ -62,7 +64,8 @@ Possible values:
trace and can the be analyzed by how much time was spent for that.
* False: Disables SQL requests profiling. The spent time is only shown on a
higher level of operations. Single SQL queries cannot be analyzed this way.
""")
""",
)
_trace_requests_opt = cfg.BoolOpt(
"trace_requests",
@@ -78,7 +81,8 @@ Possible values:
* True: Enables requests profiling.
* False: Disables requests profiling.
""")
""",
)
_hmac_keys_opt = cfg.StrOpt(
"hmac_keys",
@@ -97,7 +101,8 @@ profiling. Also, to generate correct profiling information across all services
at least one key needs to be consistent between OpenStack projects. This
ensures it can be used from client side to generate the trace, containing
information from all possible resources.
""")
""",
)
_connection_string_opt = cfg.StrOpt(
"connection_string",
@@ -116,14 +121,16 @@ Examples of possible values:
spans.
* ``otlp://127.0.0.1:4318`` - use OpenTelementry as driver for sending spans
to jaeger.
""")
""",
)
_es_doc_type_opt = cfg.StrOpt(
"es_doc_type",
default="notification",
help="""
Document type for notification indexing in elasticsearch.
""")
""",
)
_es_scroll_time_opt = cfg.StrOpt(
"es_scroll_time",
@@ -132,7 +139,8 @@ _es_scroll_time_opt = cfg.StrOpt(
This parameter is a time value parameter (for example: es_scroll_time=2m),
indicating for how long the nodes that participate in the search will maintain
relevant resources in order to continue and support it.
""")
""",
)
_es_scroll_size_opt = cfg.IntOpt(
"es_scroll_size",
@@ -140,7 +148,8 @@ _es_scroll_size_opt = cfg.IntOpt(
help="""
Elasticsearch splits large requests in batches. This parameter defines
maximum size of each batch (for example: es_scroll_size=10000).
""")
""",
)
_socket_timeout_opt = cfg.FloatOpt(
"socket_timeout",
@@ -148,7 +157,8 @@ _socket_timeout_opt = cfg.FloatOpt(
help="""
Redissentinel provides a timeout option on the connections.
This parameter defines that timeout (for example: socket_timeout=0.1).
""")
""",
)
_sentinel_service_name_opt = cfg.StrOpt(
"sentinel_service_name",
@@ -157,7 +167,8 @@ _sentinel_service_name_opt = cfg.StrOpt(
Redissentinel uses a service name to identify a master redis service.
This parameter defines the name (for example:
``sentinal_service_name=mymaster``).
""")
""",
)
_filter_error_trace = cfg.BoolOpt(
"filter_error_trace",
@@ -171,7 +182,8 @@ Possible values:
* True: Enable filter traces that contain error/exception.
* False: Disable the filter.
""")
""",
)
_PROFILER_OPTS = [
_enabled_opt,
@@ -184,14 +196,14 @@ _PROFILER_OPTS = [
_es_scroll_size_opt,
_socket_timeout_opt,
_sentinel_service_name_opt,
_filter_error_trace
_filter_error_trace,
]
cfg.CONF.register_opts(_PROFILER_OPTS, group=_profiler_opt_group)
_jaegerprofiler_opt_group = cfg.OptGroup(
"profiler_jaeger",
title="Jaeger's profiler driver related options")
"profiler_jaeger", title="Jaeger's profiler driver related options"
)
_service_name_prefix = cfg.StrOpt(
"service_name_prefix",
@@ -199,7 +211,8 @@ _service_name_prefix = cfg.StrOpt(
deprecated_reason="Jager driver is no longer supported",
help="""
Set service name prefix to Jaeger service name.
""")
""",
)
_process_tags = cfg.DictOpt(
"process_tags",
@@ -208,24 +221,23 @@ _process_tags = cfg.DictOpt(
deprecated_reason="Jager driver is no longer supported",
help="""
Set process tracer tags.
""")
""",
)
_JAEGER_OPTS = [
_service_name_prefix,
_process_tags
]
_JAEGER_OPTS = [_service_name_prefix, _process_tags]
cfg.CONF.register_opts(_JAEGER_OPTS, group=_jaegerprofiler_opt_group)
_otlp_profiler_opt_group = cfg.OptGroup(
"profiler_otlp",
title="OTLP's profiler driver related options")
"profiler_otlp", title="OTLP's profiler driver related options"
)
_otlp_service_name_prefix = cfg.StrOpt(
"service_name_prefix",
help="""
Set service name prefix to OTLP exporters.
""")
""",
)
_OTLP_OPTS = [
_otlp_service_name_prefix,
@@ -234,45 +246,66 @@ _OTLP_OPTS = [
cfg.CONF.register_opts(_OTLP_OPTS, group=_otlp_profiler_opt_group)
def set_defaults(conf, enabled=None, trace_sqlalchemy=None, hmac_keys=None,
connection_string=None, es_doc_type=None,
es_scroll_time=None, es_scroll_size=None,
socket_timeout=None, sentinel_service_name=None):
def set_defaults(
conf,
enabled=None,
trace_sqlalchemy=None,
hmac_keys=None,
connection_string=None,
es_doc_type=None,
es_scroll_time=None,
es_scroll_size=None,
socket_timeout=None,
sentinel_service_name=None,
):
conf.register_opts(_PROFILER_OPTS, group=_profiler_opt_group)
if enabled is not None:
conf.set_default("enabled", enabled,
group=_profiler_opt_group.name)
conf.set_default("enabled", enabled, group=_profiler_opt_group.name)
if trace_sqlalchemy is not None:
conf.set_default("trace_sqlalchemy", trace_sqlalchemy,
group=_profiler_opt_group.name)
conf.set_default(
"trace_sqlalchemy",
trace_sqlalchemy,
group=_profiler_opt_group.name,
)
if hmac_keys is not None:
conf.set_default("hmac_keys", hmac_keys,
group=_profiler_opt_group.name)
conf.set_default(
"hmac_keys", hmac_keys, group=_profiler_opt_group.name
)
if connection_string is not None:
conf.set_default("connection_string", connection_string,
group=_profiler_opt_group.name)
conf.set_default(
"connection_string",
connection_string,
group=_profiler_opt_group.name,
)
if es_doc_type is not None:
conf.set_default("es_doc_type", es_doc_type,
group=_profiler_opt_group.name)
conf.set_default(
"es_doc_type", es_doc_type, group=_profiler_opt_group.name
)
if es_scroll_time is not None:
conf.set_default("es_scroll_time", es_scroll_time,
group=_profiler_opt_group.name)
conf.set_default(
"es_scroll_time", es_scroll_time, group=_profiler_opt_group.name
)
if es_scroll_size is not None:
conf.set_default("es_scroll_size", es_scroll_size,
group=_profiler_opt_group.name)
conf.set_default(
"es_scroll_size", es_scroll_size, group=_profiler_opt_group.name
)
if socket_timeout is not None:
conf.set_default("socket_timeout", socket_timeout,
group=_profiler_opt_group.name)
conf.set_default(
"socket_timeout", socket_timeout, group=_profiler_opt_group.name
)
if sentinel_service_name is not None:
conf.set_default("sentinel_service_name", sentinel_service_name,
group=_profiler_opt_group.name)
conf.set_default(
"sentinel_service_name",
sentinel_service_name,
group=_profiler_opt_group.name,
)
def is_trace_enabled(conf=None):
@@ -302,6 +335,8 @@ def disable_web_trace(conf=None):
def list_opts():
return [(_profiler_opt_group.name, _PROFILER_OPTS),
(_jaegerprofiler_opt_group, _JAEGER_OPTS),
(_otlp_profiler_opt_group, _OTLP_OPTS)]
return [
(_profiler_opt_group.name, _PROFILER_OPTS),
(_jaegerprofiler_opt_group, _JAEGER_OPTS),
(_otlp_profiler_opt_group, _OTLP_OPTS),
]
+47 -28
View File
@@ -39,10 +39,11 @@ def _ensure_no_multiple_traced(traceable_attrs):
for attr_name, attr in traceable_attrs:
traced_times = getattr(attr, "__traced__", 0)
if traced_times:
raise ValueError("Can not apply new trace on top of"
" previously traced attribute '%s' since"
" it has been traced %s times previously"
% (attr_name, traced_times))
raise ValueError(
"Can not apply new trace on top of"
f" previously traced attribute '{attr_name}' since"
f" it has been traced {traced_times} times previously"
)
def init(hmac_key, base_id=None, parent_id=None):
@@ -57,8 +58,9 @@ def init(hmac_key, base_id=None, parent_id=None):
:returns: Profiler instance
"""
if get() is None:
__local_ctx.profiler = _Profiler(hmac_key, base_id=base_id,
parent_id=parent_id)
__local_ctx.profiler = _Profiler(
hmac_key, base_id=base_id, parent_id=parent_id
)
return __local_ctx.profiler
@@ -89,8 +91,13 @@ def stop(info=None):
profiler.stop(info=info)
def trace(name, info=None, hide_args=False, hide_result=True,
allow_multiple_trace=True):
def trace(
name,
info=None,
hide_args=False,
hide_result=True,
allow_multiple_trace=True,
):
"""Trace decorator for functions.
Very useful if you would like to add trace point on existing function:
@@ -122,8 +129,9 @@ def trace(name, info=None, hide_args=False, hide_result=True,
def decorator(f):
trace_times = getattr(f, "__traced__", 0)
if not allow_multiple_trace and trace_times:
raise ValueError("Function '%s' has already"
" been traced %s times" % (f, trace_times))
raise ValueError(
f"Function '{f}' has already been traced {trace_times} times"
)
try:
f.__traced__ = trace_times + 1
@@ -160,7 +168,7 @@ def trace(name, info=None, hide_args=False, hide_result=True,
except Exception as ex:
stop_info = {
"etype": reflection.get_class_name(ex),
"message": str(ex)
"message": str(ex),
}
raise
else:
@@ -175,9 +183,16 @@ def trace(name, info=None, hide_args=False, hide_result=True,
return decorator
def trace_cls(name, info=None, hide_args=False, hide_result=True,
trace_private=False, allow_multiple_trace=True,
trace_class_methods=False, trace_static_methods=False):
def trace_cls(
name,
info=None,
hide_args=False,
hide_result=True,
trace_private=False,
allow_multiple_trace=True,
trace_class_methods=False,
trace_static_methods=False,
):
"""Trace decorator for instances of class .
Very useful if you would like to add trace point on existing method:
@@ -254,8 +269,9 @@ def trace_cls(name, info=None, hide_args=False, hide_result=True,
# halfway trace this class).
_ensure_no_multiple_traced(traceable_attrs)
for i, (attr_name, attr) in enumerate(traceable_attrs):
wrapped_method = trace(name, info=info, hide_args=hide_args,
hide_result=hide_result)(attr)
wrapped_method = trace(
name, info=info, hide_args=hide_args, hide_result=hide_result
)(attr)
wrapper = traceable_wrappers[i]
if wrapper is not None:
wrapped_method = wrapper(wrapped_method)
@@ -288,6 +304,7 @@ class TracedMeta(type):
mandatory key included - "name", that will define name of action to be
traced - E.g. wsgi, rpc, db, etc...
"""
def __init__(cls, cls_name, bases, attrs):
super().__init__(cls_name, bases, attrs)
@@ -295,14 +312,17 @@ class TracedMeta(type):
trace_private = trace_args.pop("trace_private", False)
allow_multiple_trace = trace_args.pop("allow_multiple_trace", True)
if "name" not in trace_args:
raise TypeError("Please specify __trace_args__ class level "
"dictionary attribute with mandatory 'name' key - "
"e.g. __trace_args__ = {'name': 'rpc'}")
raise TypeError(
"Please specify __trace_args__ class level "
"dictionary attribute with mandatory 'name' key - "
"e.g. __trace_args__ = {'name': 'rpc'}"
)
traceable_attrs = []
for attr_name, attr_value in attrs.items():
if not (inspect.ismethod(attr_value)
or inspect.isfunction(attr_value)):
if not (
inspect.ismethod(attr_value) or inspect.isfunction(attr_value)
):
continue
if attr_name.startswith("__"):
continue
@@ -314,12 +334,12 @@ class TracedMeta(type):
# halfway trace this class).
_ensure_no_multiple_traced(traceable_attrs)
for attr_name, attr_value in traceable_attrs:
setattr(cls, attr_name, trace(**trace_args)(getattr(cls,
attr_name)))
setattr(
cls, attr_name, trace(**trace_args)(getattr(cls, attr_name))
)
class Trace:
def __init__(self, name, info=None):
"""With statement way to use profiler start()/stop().
@@ -346,13 +366,12 @@ class Trace:
if etype:
info = {
"etype": reflection.get_class_name(etype),
"message": value.args[0] if value.args else None
"message": value.args[0] if value.args else None,
}
stop(info=info)
class _Profiler:
def __init__(self, hmac_key, base_id=None, parent_id=None):
self.hmac_key = hmac_key
if not base_id:
@@ -403,7 +422,7 @@ class _Profiler:
info["host"] = self._host
self._name.append(name)
self._trace_stack.append(str(uuidutils.generate_uuid()))
self._notify("%s-start" % name, info)
self._notify(f"{name}-start", info)
def stop(self, info=None):
"""Finish latest event.
@@ -414,7 +433,7 @@ class _Profiler:
"""
info = info or {}
info["host"] = self._host
self._notify("%s-stop" % self._name.pop(), info)
self._notify(f"{self._name.pop()}-stop", info)
self._trace_stack.pop()
def _notify(self, name, info):
+20 -13
View File
@@ -31,6 +31,7 @@ try:
except ImportError:
pass
else:
def send(self, request, *args, **kwargs):
parsed_url = parser.urlparse(request.url)
@@ -41,23 +42,27 @@ else:
elif not port and parsed_url.scheme == "https":
port = 443
profiler.start(parsed_url.scheme, info={"requests": {
"method": request.method,
"query": parsed_url.query,
"path": parsed_url.path,
"hostname": parsed_url.hostname,
"port": port,
"scheme": parsed_url.scheme}})
profiler.start(
parsed_url.scheme,
info={
"requests": {
"method": request.method,
"query": parsed_url.query,
"path": parsed_url.path,
"hostname": parsed_url.hostname,
"port": port,
"scheme": parsed_url.scheme,
}
},
)
# Profiling headers are overrident to take in account this new
# context/span.
request.headers.update(
web.get_trace_id_headers())
request.headers.update(web.get_trace_id_headers())
response = _FUNC(self, request, *args, **kwargs)
profiler.stop(info={"requests": {
"status_code": response.status_code}})
profiler.stop(info={"requests": {"status_code": response.status_code}})
return response
@@ -69,5 +74,7 @@ def enable():
HTTPAdapter.send = send
LOG.debug("profiling requests enabled")
else:
LOG.warning("unable to activate profiling for requests, "
"please ensure that python requests is installed.")
LOG.warning(
"unable to activate profiling for requests, "
"please ensure that python requests is installed."
)
+15 -18
View File
@@ -42,11 +42,13 @@ def add_tracing(sqlalchemy, engine, name, hide_result=True):
"""Add tracing to all sqlalchemy calls."""
if not _DISABLED:
sqlalchemy.event.listen(engine, "before_cursor_execute",
_before_cursor_execute(name))
sqlalchemy.event.listen(
engine, "after_cursor_execute",
_after_cursor_execute(hide_result=hide_result)
engine, "before_cursor_execute", _before_cursor_execute(name)
)
sqlalchemy.event.listen(
engine,
"after_cursor_execute",
_after_cursor_execute(hide_result=hide_result),
)
sqlalchemy.event.listen(engine, "handle_error", handle_error)
@@ -64,10 +66,7 @@ def _before_cursor_execute(name):
"""Add listener that will send trace info before query is executed."""
def handler(conn, cursor, statement, params, context, executemany):
info = {"db": {
"statement": statement,
"params": params}
}
info = {"db": {"statement": statement, "params": params}}
profiler.start(name, info=info)
return handler
@@ -84,11 +83,7 @@ def _after_cursor_execute(hide_result=True):
def handler(conn, cursor, statement, params, context, executemany):
if not hide_result:
# Add SQL result to trace info in *-stop phase
info = {
"db": {
"result": str(cursor._rows)
}
}
info = {"db": {"result": str(cursor._rows)}}
profiler.stop(info=info)
else:
profiler.stop()
@@ -99,7 +94,8 @@ def _after_cursor_execute(hide_result=True):
def handle_error(exception_context):
"""Handle SQLAlchemy errors"""
exception_class_name = reflection.get_class_name(
exception_context.original_exception)
exception_context.original_exception
)
original_exception = str(exception_context.original_exception)
chained_exception = str(exception_context.chained_exception)
@@ -108,9 +104,10 @@ def handle_error(exception_context):
"message": original_exception,
"db": {
"original_exception": original_exception,
"chained_exception": chained_exception
}
"chained_exception": chained_exception,
},
}
profiler.stop(info=info)
LOG.debug("OSProfiler has handled SQLAlchemy error: %s",
original_exception)
LOG.debug(
"OSProfiler has handled SQLAlchemy error: %s", original_exception
)
+55 -37
View File
@@ -39,17 +39,15 @@ class Foo:
class DriverTestCase(test.FunctionalTestCase):
SERVICE = "service"
PROJECT = "project"
def setUp(self):
super().setUp()
CONF(["--config-file", os.path.dirname(__file__) + "/config.cfg"])
opts.set_defaults(CONF,
enabled=True,
trace_sqlalchemy=False,
hmac_keys="SECRET_KEY")
opts.set_defaults(
CONF, enabled=True, trace_sqlalchemy=False, hmac_keys="SECRET_KEY"
)
def _assert_dict(self, info, **kwargs):
for key in kwargs:
@@ -58,28 +56,33 @@ class DriverTestCase(test.FunctionalTestCase):
def _assert_child_dict(self, child, base_id, parent_id, name, fn_name):
self.assertEqual(parent_id, child["parent_id"])
exp_info = {"name": "rpc",
"service": self.SERVICE,
"project": self.PROJECT}
exp_info = {
"name": "rpc",
"service": self.SERVICE,
"project": self.PROJECT,
}
self._assert_dict(child["info"], **exp_info)
raw_start = child["info"]["meta.raw_payload.%s-start" % name]
raw_start = child["info"][f"meta.raw_payload.{name}-start"]
self.assertEqual(fn_name, raw_start["info"]["function"]["name"])
exp_raw = {"name": "%s-start" % name,
"service": self.SERVICE,
"trace_id": child["trace_id"],
"project": self.PROJECT,
"base_id": base_id}
exp_raw = {
"name": f"{name}-start",
"service": self.SERVICE,
"trace_id": child["trace_id"],
"project": self.PROJECT,
"base_id": base_id,
}
self._assert_dict(raw_start, **exp_raw)
raw_stop = child["info"]["meta.raw_payload.%s-stop" % name]
exp_raw["name"] = "%s-stop" % name
raw_stop = child["info"][f"meta.raw_payload.{name}-stop"]
exp_raw["name"] = f"{name}-stop"
self._assert_dict(raw_stop, **exp_raw)
def test_get_report(self):
# initialize profiler notifier (the same way as in services)
initializer.init_from_conf(
CONF, {}, self.PROJECT, self.SERVICE, "host")
CONF, {}, self.PROJECT, self.SERVICE, "host"
)
profiler.init("SECRET_KEY")
# grab base_id
@@ -90,11 +93,13 @@ class DriverTestCase(test.FunctionalTestCase):
foo.bar(1)
# instantiate report engine (the same way as in osprofiler CLI)
engine = base.get_driver(CONF.profiler.connection_string,
project=self.PROJECT,
service=self.SERVICE,
host="host",
conf=CONF)
engine = base.get_driver(
CONF.profiler.connection_string,
project=self.PROJECT,
service=self.SERVICE,
host="host",
conf=CONF,
)
# generate the report
report = engine.get_report(base_id)
@@ -107,30 +112,41 @@ class DriverTestCase(test.FunctionalTestCase):
cbar = report["children"][0]
self._assert_child_dict(
cbar, base_id, base_id, "rpc",
"osprofiler.tests.functional.test_driver.Foo.bar")
cbar,
base_id,
base_id,
"rpc",
"osprofiler.tests.functional.test_driver.Foo.bar",
)
self.assertEqual(1, len(cbar["children"]))
cbaz = cbar["children"][0]
self._assert_child_dict(
cbaz, base_id, cbar["trace_id"], "rpc",
"osprofiler.tests.functional.test_driver.Foo.baz")
cbaz,
base_id,
cbar["trace_id"],
"rpc",
"osprofiler.tests.functional.test_driver.Foo.baz",
)
class RedisDriverTestCase(DriverTestCase):
def setUp(self):
super(DriverTestCase, self).setUp()
CONF([])
opts.set_defaults(CONF,
connection_string="redis://localhost:6379",
enabled=True,
trace_sqlalchemy=False,
hmac_keys="SECRET_KEY")
opts.set_defaults(
CONF,
connection_string="redis://localhost:6379",
enabled=True,
trace_sqlalchemy=False,
hmac_keys="SECRET_KEY",
)
def test_list_traces(self):
# initialize profiler notifier (the same way as in services)
initializer.init_from_conf(
CONF, {}, self.PROJECT, self.SERVICE, "host")
CONF, {}, self.PROJECT, self.SERVICE, "host"
)
profiler.init("SECRET_KEY")
# grab base_id
@@ -141,11 +157,13 @@ class RedisDriverTestCase(DriverTestCase):
foo.bar(1)
# instantiate report engine (the same way as in osprofiler CLI)
engine = base.get_driver(CONF.profiler.connection_string,
project=self.PROJECT,
service=self.SERVICE,
host="host",
conf=CONF)
engine = base.get_driver(
CONF.profiler.connection_string,
project=self.PROJECT,
service=self.SERVICE,
host="host",
conf=CONF,
)
# generate the report
traces = engine.list_traces()
+1
View File
@@ -21,6 +21,7 @@ from testtools import testcase
class TestCase(testcase.TestCase):
"""Test case base class for all osprofiler unit tests."""
pass
+63 -43
View File
@@ -28,7 +28,6 @@ from osprofiler.tests import test
@ddt.ddt
class ShellTestCase(test.TestCase):
TRACE_ID = "c598094d-bbee-40b6-b317-d76003b679d3"
def setUp(self):
@@ -40,8 +39,8 @@ class ShellTestCase(test.TestCase):
os.environ = self.old_environment
def _trace_show_cmd(self, format_=None):
cmd = "trace show --connection-string redis:// %s" % self.TRACE_ID
return cmd if format_ is None else "{} --{}".format(cmd, format_)
cmd = f"trace show --connection-string redis:// {self.TRACE_ID}"
return cmd if format_ is None else f"{cmd} --{format_}"
@mock.patch("sys.stdout", io.StringIO())
@mock.patch("osprofiler.cmd.shell.OSProfilerShell")
@@ -61,41 +60,45 @@ class ShellTestCase(test.TestCase):
else:
raise ValueError(
"Expected: `osprofiler.exc.CommandError` is raised with "
"message: '%s'." % expected_message)
f"message: '{expected_message}'."
)
@mock.patch("osprofiler.drivers.redis_driver.Redis.get_report")
def test_trace_show_no_selected_format(self, mock_get):
mock_get.return_value = self._create_mock_notifications()
msg = ("You should choose one of the following output formats: "
"json, html or dot.")
msg = (
"You should choose one of the following output formats: "
"json, html or dot."
)
self._test_with_command_error(self._trace_show_cmd(), msg)
@mock.patch("osprofiler.drivers.redis_driver.Redis.get_report")
@ddt.data(None, {"info": {"started": 0, "finished": 1, "name": "total"},
"children": []})
@ddt.data(
None,
{
"info": {"started": 0, "finished": 1, "name": "total"},
"children": [],
},
)
def test_trace_show_trace_id_not_found(self, notifications, mock_get):
mock_get.return_value = notifications
msg = ("Trace with UUID %s not found. Please check the HMAC key "
"used in the command." % self.TRACE_ID)
msg = (
f"Trace with UUID {self.TRACE_ID} not found. Please check the "
f"HMAC key used in the command."
)
self._test_with_command_error(self._trace_show_cmd(), msg)
def _create_mock_notifications(self):
notifications = {
"info": {
"started": 0,
"finished": 1,
"name": "total"
},
"children": [{
"info": {
"started": 0,
"finished": 1,
"name": "total"
},
"children": []
}]
"info": {"started": 0, "finished": 1, "name": "total"},
"children": [
{
"info": {"started": 0, "finished": 1, "name": "total"},
"children": [],
}
],
}
return notifications
@@ -106,9 +109,16 @@ class ShellTestCase(test.TestCase):
mock_get.return_value = notifications
self.run_command(self._trace_show_cmd(format_="json"))
self.assertEqual("%s\n" % json.dumps(notifications, indent=2,
separators=(",", ": "),),
sys.stdout.getvalue())
self.assertEqual(
"{}\n".format(
json.dumps(
notifications,
indent=2,
separators=(",", ": "),
)
),
sys.stdout.getvalue(),
)
@mock.patch("sys.stdout", io.StringIO())
@mock.patch("osprofiler.drivers.redis_driver.Redis.get_report")
@@ -124,20 +134,27 @@ class ShellTestCase(test.TestCase):
"It is a period of civil war. Rebel"
"spaceships, striking from a hidden"
"base, have won their first victory"
"against the evil Galactic Empire.")
"against the evil Galactic Empire."
)
with mock.patch("osprofiler.cmd.commands.open",
mock.mock_open(read_data=html_template), create=True):
with mock.patch(
"osprofiler.cmd.commands.open",
mock.mock_open(read_data=html_template),
create=True,
):
self.run_command(self._trace_show_cmd(format_="html"))
self.assertEqual("A long time ago in a galaxy far, far away..."
" some_data = %s"
"It is a period of civil war. Rebel"
"spaceships, striking from a hidden"
"base, have won their first victory"
"against the evil Galactic Empire."
"\n" % json.dumps(notifications, indent=4,
separators=(",", ": ")),
sys.stdout.getvalue())
self.assertEqual(
"A long time ago in a galaxy far, far away..."
" some_data = {}"
"It is a period of civil war. Rebel"
"spaceships, striking from a hidden"
"base, have won their first victory"
"against the evil Galactic Empire."
"\n".format(
json.dumps(notifications, indent=4, separators=(",", ": "))
),
sys.stdout.getvalue(),
)
@mock.patch("sys.stdout", io.StringIO())
@mock.patch("osprofiler.drivers.redis_driver.Redis.get_report")
@@ -145,11 +162,14 @@ class ShellTestCase(test.TestCase):
notifications = self._create_mock_notifications()
mock_get.return_value = notifications
with mock.patch("osprofiler.cmd.commands.open",
mock.mock_open(), create=True) as mock_open:
self.run_command("%s --out='/file'" %
self._trace_show_cmd(format_="json"))
with mock.patch(
"osprofiler.cmd.commands.open", mock.mock_open(), create=True
) as mock_open:
self.run_command(
"{} --out='/file'".format(self._trace_show_cmd(format_="json"))
)
output = mock_open.return_value.__enter__.return_value
output.write.assert_called_once_with(
json.dumps(notifications, indent=2, separators=(",", ": ")))
json.dumps(notifications, indent=2, separators=(",", ": "))
)
+40 -22
View File
@@ -20,11 +20,15 @@ from osprofiler.tests import test
class TitlesTestCase(test.TestCase):
specs_path = os.path.join(
os.path.dirname(__file__),
os.pardir, os.pardir, os.pardir, os.pardir,
"doc", "specs")
os.pardir,
os.pardir,
os.pardir,
os.pardir,
"doc",
"specs",
)
def _get_title(self, section_tree):
section = {"subtitles": []}
@@ -51,21 +55,27 @@ class TitlesTestCase(test.TestCase):
msgs = []
if len(missing_sections) > 0:
msgs.append("Missing sections: %s" % missing_sections)
msgs.append(f"Missing sections: {missing_sections}")
if len(extra_sections) > 0:
msgs.append("Extra sections: %s" % extra_sections)
msgs.append(f"Extra sections: {extra_sections}")
for section in expect.keys():
missing_subsections = [x for x in expect[section]
if x not in actual.get(section, {})]
missing_subsections = [
x for x in expect[section] if x not in actual.get(section, {})
]
# extra subsections are allowed
if len(missing_subsections) > 0:
msgs.append("Section '%s' is missing subsections: %s"
% (section, missing_subsections))
msgs.append(
f"Section '{section}' is missing subsections: "
f"{missing_subsections}"
)
if len(msgs) > 0:
self.fail("While checking '%s':\n %s"
% (filename, "\n ".join(msgs)))
self.fail(
"While checking '{}':\n {}".format(
filename, "\n ".join(msgs)
)
)
def _check_lines_wrapping(self, tpl, raw):
for i, line in enumerate(raw.split("\n")):
@@ -73,22 +83,28 @@ class TitlesTestCase(test.TestCase):
continue
self.assertTrue(
len(line) < 80,
msg="%s:%d: Line limited to a maximum of 79 characters." %
(tpl, i + 1))
msg=(
f"{tpl}:{i + 1}: Line limited to a maximum of 79 "
f"characters."
),
)
def _check_no_cr(self, tpl, raw):
matches = re.findall("\r", raw)
self.assertEqual(
len(matches), 0,
"Found %s literal carriage returns in file %s" %
(len(matches), tpl))
len(matches),
0,
f"Found {len(matches)} literal carriage returns in file {tpl}",
)
def _check_trailing_spaces(self, tpl, raw):
for i, line in enumerate(raw.split("\n")):
trailing_spaces = re.findall(" +$", line)
self.assertEqual(
len(trailing_spaces), 0,
"Found trailing spaces on line {} of {}".format(i + 1, tpl))
len(trailing_spaces),
0,
f"Found trailing spaces on line {i + 1} of {tpl}",
)
def test_template(self):
with open(os.path.join(self.specs_path, "template.rst")) as f:
@@ -98,17 +114,19 @@ class TitlesTestCase(test.TestCase):
template_titles = self._get_titles(spec)
for d in ["implemented", "in-progress"]:
spec_dir = "{}/{}".format(self.specs_path, d)
spec_dir = f"{self.specs_path}/{d}"
self.assertTrue(os.path.isdir(spec_dir),
"%s is not a directory" % spec_dir)
self.assertTrue(
os.path.isdir(spec_dir), f"{spec_dir} is not a directory"
)
for filename in glob.glob(spec_dir + "/*"):
if filename.endswith("README.rst"):
continue
self.assertTrue(
filename.endswith(".rst"),
"spec's file must have .rst ext. Found: %s" % filename)
f"spec's file must have .rst ext. Found: {filename}",
)
with open(filename) as f:
data = f.read()
+56 -26
View File
@@ -18,7 +18,6 @@ from osprofiler.tests import test
class NotifierBaseTestCase(test.TestCase):
def test_factory(self):
class A(base.Driver):
@@ -34,7 +33,6 @@ class NotifierBaseTestCase(test.TestCase):
def test_factory_with_args(self):
class B(base.Driver):
def __init__(self, c_str, a, b=10):
self.a = a
self.b = b
@@ -49,9 +47,11 @@ class NotifierBaseTestCase(test.TestCase):
self.assertEqual(22, base.get_driver("b://", 5, b=7).notify(10))
def test_driver_not_found(self):
self.assertRaises(ValueError, base.get_driver,
"Driver not found for connection string: "
"nonexisting://")
self.assertRaises(
ValueError,
base.get_driver,
"Driver not found for connection string: nonexisting://",
)
def test_build_empty_tree(self):
class C(base.Driver):
@@ -73,12 +73,21 @@ class NotifierBaseTestCase(test.TestCase):
"21": {"parent_id": "2", "trace_id": "21", "info": {"started": 6}},
"22": {"parent_id": "2", "trace_id": "22", "info": {"started": 7}},
"11": {"parent_id": "1", "trace_id": "11", "info": {"started": 1}},
"113": {"parent_id": "11", "trace_id": "113",
"info": {"started": 3}},
"112": {"parent_id": "11", "trace_id": "112",
"info": {"started": 2}},
"114": {"parent_id": "11", "trace_id": "114",
"info": {"started": 5}}
"113": {
"parent_id": "11",
"trace_id": "113",
"info": {"started": 3},
},
"112": {
"parent_id": "11",
"trace_id": "112",
"info": {"started": 2},
},
"114": {
"parent_id": "11",
"trace_id": "114",
"info": {"started": 5},
},
}
expected_output = [
@@ -92,28 +101,49 @@ class NotifierBaseTestCase(test.TestCase):
"trace_id": "11",
"info": {"started": 1},
"children": [
{"parent_id": "11", "trace_id": "112",
"info": {"started": 2}, "children": []},
{"parent_id": "11", "trace_id": "113",
"info": {"started": 3}, "children": []},
{"parent_id": "11", "trace_id": "114",
"info": {"started": 5}, "children": []}
]
{
"parent_id": "11",
"trace_id": "112",
"info": {"started": 2},
"children": [],
},
{
"parent_id": "11",
"trace_id": "113",
"info": {"started": 3},
"children": [],
},
{
"parent_id": "11",
"trace_id": "114",
"info": {"started": 5},
"children": [],
},
],
}
]
],
},
{
"parent_id": "0",
"trace_id": "2",
"info": {"started": 1},
"children": [
{"parent_id": "2", "trace_id": "21",
"info": {"started": 6}, "children": []},
{"parent_id": "2", "trace_id": "22",
"info": {"started": 7}, "children": []}
]
}
{
"parent_id": "2",
"trace_id": "21",
"info": {"started": 6},
"children": [],
},
{
"parent_id": "2",
"trace_id": "22",
"info": {"started": 7},
"children": [],
},
],
},
]
self.assertEqual(
expected_output, base.get_driver("d://")._build_tree(test_input))
expected_output, base.get_driver("d://")._build_tree(test_input)
)
@@ -20,11 +20,11 @@ from osprofiler.tests import test
class ElasticsearchTestCase(test.TestCase):
def setUp(self):
super().setUp()
self.elasticsearch = ElasticsearchDriver(
"elasticsearch://localhost:9001/")
"elasticsearch://localhost:9001/"
)
self.elasticsearch.project = "project"
self.elasticsearch.service = "service"
@@ -35,23 +35,20 @@ class ElasticsearchTestCase(test.TestCase):
service = "service"
host = "host"
info = {
"a": 10,
"project": project,
"service": service,
"host": host
}
info = {"a": 10, "project": project, "service": service, "host": host}
self.elasticsearch.notify(info)
self.elasticsearch.client\
.index.assert_called_once_with(index="osprofiler-notifications",
doc_type="notification",
body=info)
self.elasticsearch.client.index.assert_called_once_with(
index="osprofiler-notifications",
doc_type="notification",
body=info,
)
def test_get_empty_report(self):
self.elasticsearch.client = mock.MagicMock()
self.elasticsearch.client.search = mock\
.MagicMock(return_value={"_scroll_id": "1", "hits": {"hits": []}})
self.elasticsearch.client.search = mock.MagicMock(
return_value={"_scroll_id": "1", "hits": {"hits": []}}
)
self.elasticsearch.client.reset_mock()
get_report = self.elasticsearch.get_report
@@ -59,14 +56,13 @@ class ElasticsearchTestCase(test.TestCase):
get_report(base_id)
self.elasticsearch.client\
.search.assert_called_once_with(index="osprofiler-notifications",
doc_type="notification",
size=10000,
scroll="2m",
body={"query": {
"match": {"base_id": base_id}}
})
self.elasticsearch.client.search.assert_called_once_with(
index="osprofiler-notifications",
doc_type="notification",
size=10000,
scroll="2m",
body={"query": {"match": {"base_id": base_id}}},
)
def test_get_non_empty_report(self):
base_id = "1"
@@ -82,34 +78,37 @@ class ElasticsearchTestCase(test.TestCase):
"service": "service",
"parent_id": "0",
"name": "test",
"info": {
"host": "host"
},
"trace_id": "1"
"info": {"host": "host"},
"trace_id": "1",
}
}
]}}
]
},
}
elasticsearch_second_response = {
"_scroll_id": base_id,
"hits": {"hits": []}}
"hits": {"hits": []},
}
self.elasticsearch.client = mock.MagicMock()
self.elasticsearch.client.search = \
mock.MagicMock(return_value=elasticsearch_first_response)
self.elasticsearch.client.scroll = \
mock.MagicMock(return_value=elasticsearch_second_response)
self.elasticsearch.client.search = mock.MagicMock(
return_value=elasticsearch_first_response
)
self.elasticsearch.client.scroll = mock.MagicMock(
return_value=elasticsearch_second_response
)
self.elasticsearch.client.reset_mock()
self.elasticsearch.get_report(base_id)
self.elasticsearch.client\
.search.assert_called_once_with(index="osprofiler-notifications",
doc_type="notification",
size=10000,
scroll="2m",
body={"query": {
"match": {"base_id": base_id}}
})
self.elasticsearch.client.search.assert_called_once_with(
index="osprofiler-notifications",
doc_type="notification",
size=10000,
scroll="2m",
body={"query": {"match": {"base_id": base_id}}},
)
self.elasticsearch.client\
.scroll.assert_called_once_with(scroll_id=base_id, scroll="2m")
self.elasticsearch.client.scroll.assert_called_once_with(
scroll_id=base_id, scroll="2m"
)
+148 -91
View File
@@ -25,7 +25,6 @@ from osprofiler.tests import test
@ddt.ddt
class LogInsightDriverTestCase(test.TestCase):
BASE_ID = "8d28af1e-acc0-498c-9890-6908e33eff5f"
def setUp(self):
@@ -34,13 +33,15 @@ class LogInsightDriverTestCase(test.TestCase):
self._project = "cinder"
self._service = "osapi_volume"
self._host = "ubuntu"
with mock.patch.object(loginsight, "LogInsightClient",
return_value=self._client):
with mock.patch.object(
loginsight, "LogInsightClient", return_value=self._client
):
self._driver = loginsight.LogInsightDriver(
"loginsight://username:password@host",
project=self._project,
service=self._service,
host=self._host)
host=self._host,
)
@mock.patch.object(loginsight, "LogInsightClient")
def test_init(self, client_class):
@@ -51,9 +52,11 @@ class LogInsightDriverTestCase(test.TestCase):
client_class.assert_called_once_with("host", "username", "password")
client.login.assert_called_once_with()
@ddt.data("loginsight://username@host",
"loginsight://username:p@ssword@host",
"loginsight://us:rname:password@host")
@ddt.data(
"loginsight://username@host",
"loginsight://username:p@ssword@host",
"loginsight://us:rname:password@host",
)
def test_init_with_invalid_connection_string(self, conn_str):
self.assertRaises(ValueError, loginsight.LogInsightDriver, conn_str)
@@ -69,18 +72,22 @@ class LogInsightDriverTestCase(test.TestCase):
def test_get_name(self):
self.assertEqual("loginsight", self._driver.get_name())
def _create_trace(self,
name,
timestamp,
parent_id="8d28af1e-acc0-498c-9890-6908e33eff5f",
base_id=BASE_ID,
trace_id="e465db5c-9672-45a1-b90b-da918f30aef6"):
return {"parent_id": parent_id,
"name": name,
"base_id": base_id,
"trace_id": trace_id,
"timestamp": timestamp,
"info": {"host": self._host}}
def _create_trace(
self,
name,
timestamp,
parent_id="8d28af1e-acc0-498c-9890-6908e33eff5f",
base_id=BASE_ID,
trace_id="e465db5c-9672-45a1-b90b-da918f30aef6",
):
return {
"parent_id": parent_id,
"name": name,
"base_id": base_id,
"trace_id": trace_id,
"timestamp": timestamp,
"info": {"host": self._host},
}
def _create_start_trace(self):
return self._create_trace("wsgi-start", "2016-10-04t11:50:21.902303")
@@ -98,20 +105,17 @@ class LogInsightDriverTestCase(test.TestCase):
trace["project"] = self._project
trace["service"] = self._service
exp_event = {"text": "OSProfiler trace",
"fields": [{"name": "base_id",
"content": trace["base_id"]},
{"name": "trace_id",
"content": trace["trace_id"]},
{"name": "project",
"content": trace["project"]},
{"name": "service",
"content": trace["service"]},
{"name": "name",
"content": trace["name"]},
{"name": "trace",
"content": json_str}]
}
exp_event = {
"text": "OSProfiler trace",
"fields": [
{"name": "base_id", "content": trace["base_id"]},
{"name": "trace_id", "content": trace["trace_id"]},
{"name": "project", "content": trace["project"]},
{"name": "service", "content": trace["service"]},
{"name": "name", "content": trace["name"]},
{"name": "trace", "content": json_str},
],
}
self._client.send_event.assert_called_once_with(exp_event)
@mock.patch.object(loginsight.LogInsightDriver, "_append_results")
@@ -125,61 +129,83 @@ class LogInsightDriverTestCase(test.TestCase):
stop_trace["project"] = self._project
stop_trace["service"] = self._service
resp = {"events": [{"text": "OSProfiler trace",
"fields": [{"name": "trace",
"content": json.dumps(start_trace)
}
]
},
{"text": "OSProfiler trace",
"fields": [{"name": "trace",
"content": json.dumps(stop_trace)
}
]
}
]
}
resp = {
"events": [
{
"text": "OSProfiler trace",
"fields": [
{"name": "trace", "content": json.dumps(start_trace)}
],
},
{
"text": "OSProfiler trace",
"fields": [
{"name": "trace", "content": json.dumps(stop_trace)}
],
},
]
}
self._client.query_events = mock.Mock(return_value=resp)
self._driver.get_report(self.BASE_ID)
self._client.query_events.assert_called_once_with({"base_id":
self.BASE_ID})
self._client.query_events.assert_called_once_with(
{"base_id": self.BASE_ID}
)
append_results.assert_has_calls(
[mock.call(start_trace["trace_id"], start_trace["parent_id"],
start_trace["name"], start_trace["project"],
start_trace["service"], start_trace["info"]["host"],
start_trace["timestamp"], start_trace),
mock.call(stop_trace["trace_id"], stop_trace["parent_id"],
stop_trace["name"], stop_trace["project"],
stop_trace["service"], stop_trace["info"]["host"],
stop_trace["timestamp"], stop_trace)
])
[
mock.call(
start_trace["trace_id"],
start_trace["parent_id"],
start_trace["name"],
start_trace["project"],
start_trace["service"],
start_trace["info"]["host"],
start_trace["timestamp"],
start_trace,
),
mock.call(
stop_trace["trace_id"],
stop_trace["parent_id"],
stop_trace["name"],
stop_trace["project"],
stop_trace["service"],
stop_trace["info"]["host"],
stop_trace["timestamp"],
stop_trace,
),
]
)
parse_results.assert_called_once_with()
class LogInsightClientTestCase(test.TestCase):
def setUp(self):
super().setUp()
self._host = "localhost"
self._username = "username"
self._password = "password" # nosec
self._password = "password" # noqa: S105
self._client = loginsight.LogInsightClient(
self._host, self._username, self._password)
self._host, self._username, self._password
)
self._client._session_id = "4ff800d1-3175-4b49-9209-39714ea56416"
def test_check_response_login_timeout(self):
resp = mock.Mock(status_code=440)
self.assertRaises(
exc.LogInsightLoginTimeout, self._client._check_response, resp)
exc.LogInsightLoginTimeout, self._client._check_response, resp
)
def test_check_response_api_error(self):
resp = mock.Mock(status_code=401, ok=False)
resp.text = json.dumps(
{"errorMessage": "Invalid username or password.",
"errorCode": "FIELD_ERROR"})
{
"errorMessage": "Invalid username or password.",
"errorCode": "FIELD_ERROR",
}
)
e = self.assertRaises(
exc.LogInsightAPIError, self._client._check_response, resp)
exc.LogInsightAPIError, self._client._check_response, resp
)
self.assertEqual("Invalid username or password.", str(e))
@mock.patch("requests.Request")
@@ -204,16 +230,24 @@ class LogInsightClientTestCase(test.TestCase):
body = mock.sentinel.body
params = mock.sentinel.params
ret = self._client._send_request(
"get", "https", "api/v1/events", header, body, params)
"get", "https", "api/v1/events", header, body, params
)
self.assertEqual(resp_json, ret)
exp_headers = {"X-LI-Session-Id": "foo",
"content-type": "application/json"}
exp_headers = {
"X-LI-Session-Id": "foo",
"content-type": "application/json",
}
request_class.assert_called_once_with(
"get", "https://localhost:9543/api/v1/events", headers=exp_headers,
data=data, params=mock.sentinel.params)
self._client._session.send.assert_called_once_with(prep_req,
verify=False)
"get",
"https://localhost:9543/api/v1/events",
headers=exp_headers,
data=data,
params=mock.sentinel.params,
)
self._client._session.send.assert_called_once_with(
prep_req, verify=False
)
check_resp.assert_called_once_with(resp)
@mock.patch.object(loginsight.LogInsightClient, "_send_request")
@@ -221,29 +255,41 @@ class LogInsightClientTestCase(test.TestCase):
self.assertTrue(self._client._is_current_session_active())
exp_header = {"X-LI-Session-Id": self._client._session_id}
send_request.assert_called_once_with(
"get", "https", "api/v1/sessions/current", headers=exp_header)
"get", "https", "api/v1/sessions/current", headers=exp_header
)
@mock.patch.object(loginsight.LogInsightClient, "_send_request")
def test_is_current_session_active_with_expired_session(self,
send_request):
def test_is_current_session_active_with_expired_session(
self, send_request
):
send_request.side_effect = exc.LogInsightLoginTimeout
self.assertFalse(self._client._is_current_session_active())
send_request.assert_called_once_with(
"get", "https", "api/v1/sessions/current",
headers={"X-LI-Session-Id": self._client._session_id})
"get",
"https",
"api/v1/sessions/current",
headers={"X-LI-Session-Id": self._client._session_id},
)
@mock.patch.object(loginsight.LogInsightClient,
"_is_current_session_active", return_value=True)
@mock.patch.object(
loginsight.LogInsightClient,
"_is_current_session_active",
return_value=True,
)
@mock.patch.object(loginsight.LogInsightClient, "_send_request")
def test_login_with_current_session_active(self, send_request,
is_current_session_active):
def test_login_with_current_session_active(
self, send_request, is_current_session_active
):
self._client.login()
is_current_session_active.assert_called_once_with()
send_request.assert_not_called()
@mock.patch.object(loginsight.LogInsightClient,
"_is_current_session_active", return_value=False)
@mock.patch.object(
loginsight.LogInsightClient,
"_is_current_session_active",
return_value=False,
)
@mock.patch.object(loginsight.LogInsightClient, "_send_request")
def test_login(self, send_request, is_current_session_active):
new_session_id = "569a80aa-be5c-49e5-82c1-bb62392d2667"
@@ -254,7 +300,8 @@ class LogInsightClientTestCase(test.TestCase):
is_current_session_active.assert_called_once_with()
exp_body = {"username": self._username, "password": self._password}
send_request.assert_called_once_with(
"post", "https", "api/v1/sessions", body=exp_body)
"post", "https", "api/v1/sessions", body=exp_body
)
self.assertEqual(new_session_id, self._client._session_id)
@mock.patch.object(loginsight.LogInsightClient, "_send_request")
@@ -263,10 +310,12 @@ class LogInsightClientTestCase(test.TestCase):
self._client.send_event(event)
exp_body = {"events": [event]}
exp_path = ("api/v1/events/ingest/%s" %
self._client.LI_OSPROFILER_AGENT_ID)
exp_path = (
f"api/v1/events/ingest/{self._client.LI_OSPROFILER_AGENT_ID}"
)
send_request.assert_called_once_with(
"post", "http", exp_path, body=exp_body)
"post", "http", exp_path, body=exp_body
)
@mock.patch.object(loginsight.LogInsightClient, "_send_request")
def test_query_events(self, send_request):
@@ -277,8 +326,12 @@ class LogInsightClientTestCase(test.TestCase):
exp_header = {"X-LI-Session-Id": self._client._session_id}
exp_params = {"limit": 20000, "timeout": self._client._query_timeout}
send_request.assert_called_once_with(
"get", "https", "api/v1/events/foo/CONTAINS+bar/timestamp/GT+0",
headers=exp_header, params=exp_params)
"get",
"https",
"api/v1/events/foo/CONTAINS+bar/timestamp/GT+0",
headers=exp_header,
params=exp_params,
)
@mock.patch.object(loginsight.LogInsightClient, "_send_request")
@mock.patch.object(loginsight.LogInsightClient, "login")
@@ -291,6 +344,10 @@ class LogInsightClientTestCase(test.TestCase):
exp_header = {"X-LI-Session-Id": self._client._session_id}
exp_params = {"limit": 20000, "timeout": self._client._query_timeout}
exp_send_request_call = mock.call(
"get", "https", "api/v1/events/foo/CONTAINS+bar/timestamp/GT+0",
headers=exp_header, params=exp_params)
"get",
"https",
"api/v1/events/foo/CONTAINS+bar/timestamp/GT+0",
headers=exp_header,
params=exp_params,
)
send_request.assert_has_calls([exp_send_request_call] * 2)
+25 -16
View File
@@ -20,15 +20,19 @@ from osprofiler.tests import test
class MessagingTestCase(test.TestCase):
@mock.patch("oslo_utils.importutils.try_import")
def test_init_no_oslo_messaging(self, try_import_mock):
try_import_mock.return_value = None
self.assertRaises(
ValueError, base.get_driver,
"messaging://", project="project", service="service",
host="host", context={})
ValueError,
base.get_driver,
"messaging://",
project="project",
service="service",
host="host",
context={},
)
@mock.patch("oslo_utils.importutils.try_import")
def test_init_and_notify(self, try_import_mock):
@@ -48,25 +52,30 @@ class MessagingTestCase(test.TestCase):
oslo_messaging_mock.get_notification_transport.return_value = transport
notify_func = base.get_driver(
"messaging://", project=project, service=service,
context=context, host=host).notify
"messaging://",
project=project,
service=service,
context=context,
host=host,
).notify
oslo_messaging_mock.Notifier.assert_called_once_with(
transport, publisher_id=host, driver="messaging",
topics=["profiler"], retry=0)
transport,
publisher_id=host,
driver="messaging",
topics=["profiler"],
retry=0,
)
info = {
"a": 10,
"project": project,
"service": service,
"host": host
}
info = {"a": 10, "project": project, "service": service, "host": host}
notify_func(info)
notifier_mock.info.assert_called_once_with(
context, "profiler.service", info)
context, "profiler.service", info
)
notifier_mock.reset_mock()
notify_func(info, context="my_context")
notifier_mock.info.assert_called_once_with(
"my_context", "profiler.service", info)
"my_context", "profiler.service", info
)
+219 -165
View File
@@ -34,12 +34,21 @@ class MongoDBParserTestCase(test.TestCase):
"21": {"parent_id": "2", "trace_id": "21", "info": {"started": 6}},
"22": {"parent_id": "2", "trace_id": "22", "info": {"started": 7}},
"11": {"parent_id": "1", "trace_id": "11", "info": {"started": 1}},
"113": {"parent_id": "11", "trace_id": "113",
"info": {"started": 3}},
"112": {"parent_id": "11", "trace_id": "112",
"info": {"started": 2}},
"114": {"parent_id": "11", "trace_id": "114",
"info": {"started": 5}}
"113": {
"parent_id": "11",
"trace_id": "113",
"info": {"started": 3},
},
"112": {
"parent_id": "11",
"trace_id": "112",
"info": {"started": 2},
},
"114": {
"parent_id": "11",
"trace_id": "114",
"info": {"started": 5},
},
}
expected_output = [
@@ -53,27 +62,47 @@ class MongoDBParserTestCase(test.TestCase):
"trace_id": "11",
"info": {"started": 1},
"children": [
{"parent_id": "11", "trace_id": "112",
"info": {"started": 2}, "children": []},
{"parent_id": "11", "trace_id": "113",
"info": {"started": 3}, "children": []},
{"parent_id": "11", "trace_id": "114",
"info": {"started": 5}, "children": []}
]
{
"parent_id": "11",
"trace_id": "112",
"info": {"started": 2},
"children": [],
},
{
"parent_id": "11",
"trace_id": "113",
"info": {"started": 3},
"children": [],
},
{
"parent_id": "11",
"trace_id": "114",
"info": {"started": 5},
"children": [],
},
],
}
]
],
},
{
"parent_id": "0",
"trace_id": "2",
"info": {"started": 1},
"children": [
{"parent_id": "2", "trace_id": "21",
"info": {"started": 6}, "children": []},
{"parent_id": "2", "trace_id": "22",
"info": {"started": 7}, "children": []}
]
}
{
"parent_id": "2",
"trace_id": "21",
"info": {"started": 6},
"children": [],
},
{
"parent_id": "2",
"trace_id": "22",
"info": {"started": 7},
"children": [],
},
],
},
]
result = self.mongodb._build_tree(test_input)
@@ -88,7 +117,7 @@ class MongoDBParserTestCase(test.TestCase):
"name": "total",
"started": 0,
"finished": None,
"last_trace_started": None
"last_trace_started": None,
},
"children": [],
"stats": {},
@@ -108,9 +137,9 @@ class MongoDBParserTestCase(test.TestCase):
"path": "/v2/a322b5049d224a90bf8786c644409400/volumes",
"scheme": "http",
"method": "POST",
"query": ""
"query": "",
},
"service": None
"service": None,
},
"name": "wsgi-start",
"service": "main",
@@ -118,35 +147,24 @@ class MongoDBParserTestCase(test.TestCase):
"trace_id": "06320327-2c2c-45ae-923a-515de890276a",
"project": "keystone",
"parent_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
"base_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4"
"base_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
},
{
"info": {
"project": None,
"host": "ubuntu",
"service": None
},
"info": {"project": None, "host": "ubuntu", "service": None},
"name": "wsgi-stop",
"service": "main",
"timestamp": "2015-12-23T14:02:22.380405",
"trace_id": "839ca3f1-afcb-45be-a4a1-679124c552bf",
"project": "keystone",
"parent_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
"base_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4"
"base_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
},
{
"info": {
"project": None,
"host": "ubuntu",
"db": {
"params": {
},
"statement": "SELECT 1"
},
"service": None
"db": {"params": {}, "statement": "SELECT 1"},
"service": None,
},
"name": "db-start",
"service": "main",
@@ -154,24 +172,18 @@ class MongoDBParserTestCase(test.TestCase):
"trace_id": "1baf1d24-9ca9-4f4c-bd3f-01b7e0c0735a",
"project": "keystone",
"parent_id": "06320327-2c2c-45ae-923a-515de890276a",
"base_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4"
"base_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
},
{
"info": {
"project": None,
"host": "ubuntu",
"service": None
},
"info": {"project": None, "host": "ubuntu", "service": None},
"name": "db-stop",
"service": "main",
"timestamp": "2015-12-23T14:02:22.415486",
"trace_id": "1baf1d24-9ca9-4f4c-bd3f-01b7e0c0735a",
"project": "keystone",
"parent_id": "06320327-2c2c-45ae-923a-515de890276a",
"base_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4"
"base_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
},
{
"info": {
"project": None,
@@ -180,9 +192,9 @@ class MongoDBParserTestCase(test.TestCase):
"path": "/v2/a322b5049d224a90bf8786c644409400/volumes",
"scheme": "http",
"method": "GET",
"query": ""
"query": "",
},
"service": None
"service": None,
},
"name": "wsgi-start",
"service": "main",
@@ -190,124 +202,167 @@ class MongoDBParserTestCase(test.TestCase):
"trace_id": "016c97fd-87f3-40b2-9b55-e431156b694b",
"project": "keystone",
"parent_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
"base_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4"
}]
"base_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
},
]
expected = {"children": [{"children": [{
"children": [],
"info": {"finished": 76,
"host": "ubuntu",
"meta.raw_payload.db-start": {
"base_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
"info": {"db": {"params": {},
"statement": "SELECT 1"},
"host": "ubuntu",
"project": None,
"service": None},
"name": "db-start",
"parent_id": "06320327-2c2c-45ae-923a-515de890276a",
"project": "keystone",
"service": "main",
"timestamp": "2015-12-23T14:02:22.395365",
"trace_id": "1baf1d24-9ca9-4f4c-bd3f-01b7e0c0735a"},
"meta.raw_payload.db-stop": {
"base_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
"info": {"host": "ubuntu",
"project": None,
"service": None},
"name": "db-stop",
"parent_id": "06320327-2c2c-45ae-923a-515de890276a",
"project": "keystone",
"service": "main",
"timestamp": "2015-12-23T14:02:22.415486",
"trace_id": "1baf1d24-9ca9-4f4c-bd3f-01b7e0c0735a"},
"name": "db",
"project": "keystone",
"service": "main",
"started": 56,
"exception": "None"},
"parent_id": "06320327-2c2c-45ae-923a-515de890276a",
"trace_id": "1baf1d24-9ca9-4f4c-bd3f-01b7e0c0735a"}],
"info": {"finished": 0,
"host": "ubuntu",
"meta.raw_payload.wsgi-start": {
"base_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
"info": {"host": "ubuntu",
"project": None,
"request": {"method": "POST",
"path": "/v2/a322b5049d224a90bf8"
"786c644409400/volumes",
"query": "",
"scheme": "http"},
"service": None},
"name": "wsgi-start",
"parent_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
"project": "keystone",
"service": "main",
"timestamp": "2015-12-23T14:02:22.338776",
"trace_id": "06320327-2c2c-45ae-923a-515de890276a"},
"name": "wsgi",
"project": "keystone",
"service": "main",
"started": 0},
"parent_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
"trace_id": "06320327-2c2c-45ae-923a-515de890276a"},
{"children": [],
"info": {"finished": 41,
"host": "ubuntu",
"meta.raw_payload.wsgi-stop": {
"base_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
"info": {"host": "ubuntu",
"project": None,
"service": None},
"name": "wsgi-stop",
"parent_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
"project": "keystone",
"service": "main",
"timestamp": "2015-12-23T14:02:22.380405",
"trace_id": "839ca3f1-afcb-45be-a4a1-679124c552bf"},
"name": "wsgi",
"project": "keystone",
"service": "main",
"started": 41,
"exception": "None"},
"parent_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
"trace_id": "839ca3f1-afcb-45be-a4a1-679124c552bf"},
{"children": [],
"info": {"finished": 88,
"host": "ubuntu",
"meta.raw_payload.wsgi-start": {
"base_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
"info": {"host": "ubuntu",
"project": None,
"request": {"method": "GET",
"path": "/v2/a322b5049d224a90bf"
"8786c644409400/volumes",
"query": "",
"scheme": "http"},
"service": None},
"name": "wsgi-start",
"parent_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
"project": "keystone",
"service": "main",
"timestamp": "2015-12-23T14:02:22.427444",
"trace_id": "016c97fd-87f3-40b2-9b55-e431156b694b"},
"name": "wsgi",
"project": "keystone",
"service": "main",
"started": 88},
"parent_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
"trace_id": "016c97fd-87f3-40b2-9b55-e431156b694b"}],
expected = {
"children": [
{
"children": [
{
"children": [],
"info": {
"finished": 76,
"host": "ubuntu",
"meta.raw_payload.db-start": {
"base_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4", # noqa: E501
"info": {
"db": {
"params": {},
"statement": "SELECT 1",
},
"host": "ubuntu",
"project": None,
"service": None,
},
"name": "db-start",
"parent_id": "06320327-2c2c-45ae-923a-515de890276a", # noqa: E501
"project": "keystone",
"service": "main",
"timestamp": "2015-12-23T14:02:22.395365",
"trace_id": "1baf1d24-9ca9-4f4c-bd3f-01b7e0c0735a", # noqa: E501
},
"meta.raw_payload.db-stop": {
"base_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4", # noqa: E501
"info": {
"host": "ubuntu",
"project": None,
"service": None,
},
"name": "db-stop",
"parent_id": "06320327-2c2c-45ae-923a-515de890276a", # noqa: E501
"project": "keystone",
"service": "main",
"timestamp": "2015-12-23T14:02:22.415486",
"trace_id": "1baf1d24-9ca9-4f4c-bd3f-01b7e0c0735a", # noqa: E501
},
"name": "db",
"project": "keystone",
"service": "main",
"started": 56,
"exception": "None",
},
"parent_id": "06320327-2c2c-45ae-923a-515de890276a", # noqa: E501
"trace_id": "1baf1d24-9ca9-4f4c-bd3f-01b7e0c0735a",
}
],
"info": {
"finished": 0,
"host": "ubuntu",
"meta.raw_payload.wsgi-start": {
"base_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
"info": {
"host": "ubuntu",
"project": None,
"request": {
"method": "POST",
"path": "/v2/a322b5049d224a90bf8"
"786c644409400/volumes",
"query": "",
"scheme": "http",
},
"service": None,
},
"name": "wsgi-start",
"parent_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4", # noqa: E501
"project": "keystone",
"service": "main",
"timestamp": "2015-12-23T14:02:22.338776",
"trace_id": "06320327-2c2c-45ae-923a-515de890276a",
},
"name": "wsgi",
"project": "keystone",
"service": "main",
"started": 0,
},
"parent_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
"trace_id": "06320327-2c2c-45ae-923a-515de890276a",
},
{
"children": [],
"info": {
"finished": 41,
"host": "ubuntu",
"meta.raw_payload.wsgi-stop": {
"base_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
"info": {
"host": "ubuntu",
"project": None,
"service": None,
},
"name": "wsgi-stop",
"parent_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4", # noqa: E501
"project": "keystone",
"service": "main",
"timestamp": "2015-12-23T14:02:22.380405",
"trace_id": "839ca3f1-afcb-45be-a4a1-679124c552bf",
},
"name": "wsgi",
"project": "keystone",
"service": "main",
"started": 41,
"exception": "None",
},
"parent_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
"trace_id": "839ca3f1-afcb-45be-a4a1-679124c552bf",
},
{
"children": [],
"info": {
"finished": 88,
"host": "ubuntu",
"meta.raw_payload.wsgi-start": {
"base_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
"info": {
"host": "ubuntu",
"project": None,
"request": {
"method": "GET",
"path": "/v2/a322b5049d224a90bf"
"8786c644409400/volumes",
"query": "",
"scheme": "http",
},
"service": None,
},
"name": "wsgi-start",
"parent_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4", # noqa: E501
"project": "keystone",
"service": "main",
"timestamp": "2015-12-23T14:02:22.427444",
"trace_id": "016c97fd-87f3-40b2-9b55-e431156b694b",
},
"name": "wsgi",
"project": "keystone",
"service": "main",
"started": 88,
},
"parent_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
"trace_id": "016c97fd-87f3-40b2-9b55-e431156b694b",
},
],
"info": {
"finished": 88,
"name": "total",
"started": 0,
"last_trace_started": 88},
"stats": {"db": {"count": 1, "duration": 20},
"wsgi": {"count": 3, "duration": 0}}}
"last_trace_started": 88,
},
"stats": {
"db": {"count": 1, "duration": 20},
"wsgi": {"count": 3, "duration": 0},
},
}
self.mongodb.db.profiler.find.return_value = results
@@ -316,6 +371,5 @@ class MongoDBParserTestCase(test.TestCase):
result = self.mongodb.get_report(base_id)
expected_filter = [{"base_id": base_id}, {"_id": 0}]
self.mongodb.db.profiler.find.assert_called_once_with(
*expected_filter)
self.mongodb.db.profiler.find.assert_called_once_with(*expected_filter)
self.assertEqual(expected, result)
+18 -30
View File
@@ -22,7 +22,6 @@ from osprofiler.tests import test
class OTLPTestCase(test.TestCase):
def setUp(self):
super().setUp()
@@ -34,9 +33,7 @@ class OTLPTestCase(test.TestCase):
"trace_id": "1c089ea8-28fe-4f3d-8c00-f6daa2bc32f1",
"parent_id": "e2715537-3d1c-4f0c-b3af-87355dc5fc5b",
"timestamp": "2018-05-03T04:31:51.781381",
"info": {
"host": "test"
}
"info": {"host": "test"},
}
self.payload_stop = {
@@ -45,18 +42,15 @@ class OTLPTestCase(test.TestCase):
"trace_id": "1c089ea8-28fe-4f3d-8c00-f6daa2bc32f1",
"parent_id": "e2715537-3d1c-4f0c-b3af-87355dc5fc5b",
"timestamp": "2018-05-03T04:31:51.781381",
"info": {
"host": "test",
"function": {
"result": 1
}
}
"info": {"host": "test", "function": {"result": 1}},
}
self.driver = otlp.OTLP(
"otlp://127.0.0.1:6831",
project="nova", service="api",
conf=cfg.CONF)
project="nova",
service="api",
conf=cfg.CONF,
)
def test_notify_start(self):
self.driver.notify(self.payload_start)
@@ -70,12 +64,7 @@ class OTLPTestCase(test.TestCase):
mock_end.assert_called_once()
def test_notify_stop_with_db_result(self):
self.payload_stop["info"] = {
"host": "test",
"db": {
"result": "()"
}
}
self.payload_stop["info"] = {"host": "test", "db": {"result": "()"}}
mock_end = mock.MagicMock()
self.driver.notify(self.payload_start)
self.driver.spans[0].end = mock_end
@@ -90,7 +79,7 @@ class OTLPTestCase(test.TestCase):
"db": {
"original_exception": "test_exception",
"chained_exception": "test_chained_exception",
}
},
}
mock_end = mock.MagicMock()
self.driver.notify(self.payload_start)
@@ -99,10 +88,7 @@ class OTLPTestCase(test.TestCase):
mock_end.assert_called_once()
def test_notify_stop_without_function_result(self):
self.payload_stop["info"] = {
"host": "test",
"function": {}
}
self.payload_stop["info"] = {"host": "test", "function": {}}
mock_end = mock.MagicMock()
self.driver.notify(self.payload_start)
self.driver.spans[0].end = mock_end
@@ -114,7 +100,7 @@ class OTLPTestCase(test.TestCase):
"host": "test",
"requests": {
"status_code": 200,
}
},
}
mock_end = mock.MagicMock()
self.driver.notify(self.payload_start)
@@ -123,14 +109,16 @@ class OTLPTestCase(test.TestCase):
mock_end.assert_called_once()
def test_service_name_default(self):
self.assertEqual("pr1-svc1", self.driver._get_service_name(
cfg.CONF, "pr1", "svc1"))
self.assertEqual(
"pr1-svc1", self.driver._get_service_name(cfg.CONF, "pr1", "svc1")
)
def test_service_name_prefix(self):
cfg.CONF.set_default(
"service_name_prefix", "prx1", "profiler_otlp")
self.assertEqual("prx1-pr1-svc1", self.driver._get_service_name(
cfg.CONF, "pr1", "svc1"))
cfg.CONF.set_default("service_name_prefix", "prx1", "profiler_otlp")
self.assertEqual(
"prx1-pr1-svc1",
self.driver._get_service_name(cfg.CONF, "pr1", "svc1"),
)
def test_process_tags(self):
# Need to be implemented.
+228 -167
View File
@@ -37,12 +37,21 @@ class RedisParserTestCase(test.TestCase):
"21": {"parent_id": "2", "trace_id": "21", "info": {"started": 6}},
"22": {"parent_id": "2", "trace_id": "22", "info": {"started": 7}},
"11": {"parent_id": "1", "trace_id": "11", "info": {"started": 1}},
"113": {"parent_id": "11", "trace_id": "113",
"info": {"started": 3}},
"112": {"parent_id": "11", "trace_id": "112",
"info": {"started": 2}},
"114": {"parent_id": "11", "trace_id": "114",
"info": {"started": 5}}
"113": {
"parent_id": "11",
"trace_id": "113",
"info": {"started": 3},
},
"112": {
"parent_id": "11",
"trace_id": "112",
"info": {"started": 2},
},
"114": {
"parent_id": "11",
"trace_id": "114",
"info": {"started": 5},
},
}
expected_output = [
@@ -56,27 +65,47 @@ class RedisParserTestCase(test.TestCase):
"trace_id": "11",
"info": {"started": 1},
"children": [
{"parent_id": "11", "trace_id": "112",
"info": {"started": 2}, "children": []},
{"parent_id": "11", "trace_id": "113",
"info": {"started": 3}, "children": []},
{"parent_id": "11", "trace_id": "114",
"info": {"started": 5}, "children": []}
]
{
"parent_id": "11",
"trace_id": "112",
"info": {"started": 2},
"children": [],
},
{
"parent_id": "11",
"trace_id": "113",
"info": {"started": 3},
"children": [],
},
{
"parent_id": "11",
"trace_id": "114",
"info": {"started": 5},
"children": [],
},
],
}
]
],
},
{
"parent_id": "0",
"trace_id": "2",
"info": {"started": 1},
"children": [
{"parent_id": "2", "trace_id": "21",
"info": {"started": 6}, "children": []},
{"parent_id": "2", "trace_id": "22",
"info": {"started": 7}, "children": []}
]
}
{
"parent_id": "2",
"trace_id": "21",
"info": {"started": 6},
"children": [],
},
{
"parent_id": "2",
"trace_id": "22",
"info": {"started": 7},
"children": [],
},
],
},
]
result = self.redisdb._build_tree(test_input)
@@ -91,7 +120,7 @@ class RedisParserTestCase(test.TestCase):
"name": "total",
"started": 0,
"finished": None,
"last_trace_started": None
"last_trace_started": None,
},
"children": [],
"stats": {},
@@ -111,9 +140,9 @@ class RedisParserTestCase(test.TestCase):
"path": "/v2/a322b5049d224a90bf8786c644409400/volumes",
"scheme": "http",
"method": "POST",
"query": ""
"query": "",
},
"service": None
"service": None,
},
"name": "wsgi-start",
"service": "main",
@@ -121,35 +150,24 @@ class RedisParserTestCase(test.TestCase):
"trace_id": "06320327-2c2c-45ae-923a-515de890276a",
"project": "keystone",
"parent_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
"base_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4"
"base_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
},
{
"info": {
"project": None,
"host": "ubuntu",
"service": None
},
"info": {"project": None, "host": "ubuntu", "service": None},
"name": "wsgi-stop",
"service": "main",
"timestamp": "2015-12-23T14:02:22.380405",
"trace_id": "839ca3f1-afcb-45be-a4a1-679124c552bf",
"project": "keystone",
"parent_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
"base_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4"
"base_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
},
{
"info": {
"project": None,
"host": "ubuntu",
"db": {
"params": {
},
"statement": "SELECT 1"
},
"service": None
"db": {"params": {}, "statement": "SELECT 1"},
"service": None,
},
"name": "db-start",
"service": "main",
@@ -157,24 +175,18 @@ class RedisParserTestCase(test.TestCase):
"trace_id": "1baf1d24-9ca9-4f4c-bd3f-01b7e0c0735a",
"project": "keystone",
"parent_id": "06320327-2c2c-45ae-923a-515de890276a",
"base_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4"
"base_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
},
{
"info": {
"project": None,
"host": "ubuntu",
"service": None
},
"info": {"project": None, "host": "ubuntu", "service": None},
"name": "db-stop",
"service": "main",
"timestamp": "2015-12-23T14:02:22.415486",
"trace_id": "1baf1d24-9ca9-4f4c-bd3f-01b7e0c0735a",
"project": "keystone",
"parent_id": "06320327-2c2c-45ae-923a-515de890276a",
"base_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4"
"base_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
},
{
"info": {
"project": None,
@@ -183,9 +195,9 @@ class RedisParserTestCase(test.TestCase):
"path": "/v2/a322b5049d224a90bf8786c644409400/volumes",
"scheme": "http",
"method": "GET",
"query": ""
"query": "",
},
"service": None
"service": None,
},
"name": "wsgi-start",
"service": "main",
@@ -193,127 +205,175 @@ class RedisParserTestCase(test.TestCase):
"trace_id": "016c97fd-87f3-40b2-9b55-e431156b694b",
"project": "keystone",
"parent_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
"base_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4"
}]
results = {result["base_id"] + "_" + result["trace_id"]
+ "_" + result["timestamp"]: result
for result in result_elements}
"base_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
},
]
results = {
result["base_id"]
+ "_"
+ result["trace_id"]
+ "_"
+ result["timestamp"]: result
for result in result_elements
}
expected = {"children": [{"children": [{
"children": [],
"info": {"finished": 76,
"host": "ubuntu",
"meta.raw_payload.db-start": {
"base_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
"info": {"db": {"params": {},
"statement": "SELECT 1"},
"host": "ubuntu",
"project": None,
"service": None},
"name": "db-start",
"parent_id": "06320327-2c2c-45ae-923a-515de890276a",
"project": "keystone",
"service": "main",
"timestamp": "2015-12-23T14:02:22.395365",
"trace_id": "1baf1d24-9ca9-4f4c-bd3f-01b7e0c0735a"},
"meta.raw_payload.db-stop": {
"base_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
"info": {"host": "ubuntu",
"project": None,
"service": None},
"name": "db-stop",
"parent_id": "06320327-2c2c-45ae-923a-515de890276a",
"project": "keystone",
"service": "main",
"timestamp": "2015-12-23T14:02:22.415486",
"trace_id": "1baf1d24-9ca9-4f4c-bd3f-01b7e0c0735a"},
"name": "db",
"project": "keystone",
"service": "main",
"started": 56,
"exception": "None"},
"parent_id": "06320327-2c2c-45ae-923a-515de890276a",
"trace_id": "1baf1d24-9ca9-4f4c-bd3f-01b7e0c0735a"}],
"info": {"finished": 0,
"host": "ubuntu",
"meta.raw_payload.wsgi-start": {
"base_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
"info": {"host": "ubuntu",
"project": None,
"request": {"method": "POST",
"path": "/v2/a322b5049d224a90bf8"
"786c644409400/volumes",
"query": "",
"scheme": "http"},
"service": None},
"name": "wsgi-start",
"parent_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
"project": "keystone",
"service": "main",
"timestamp": "2015-12-23T14:02:22.338776",
"trace_id": "06320327-2c2c-45ae-923a-515de890276a"},
"name": "wsgi",
"project": "keystone",
"service": "main",
"started": 0},
"parent_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
"trace_id": "06320327-2c2c-45ae-923a-515de890276a"},
{"children": [],
"info": {"finished": 41,
"host": "ubuntu",
"meta.raw_payload.wsgi-stop": {
"base_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
"info": {"host": "ubuntu",
"project": None,
"service": None},
"name": "wsgi-stop",
"parent_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
"project": "keystone",
"service": "main",
"timestamp": "2015-12-23T14:02:22.380405",
"trace_id": "839ca3f1-afcb-45be-a4a1-679124c552bf"},
"name": "wsgi",
"project": "keystone",
"service": "main",
"started": 41,
"exception": "None"},
"parent_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
"trace_id": "839ca3f1-afcb-45be-a4a1-679124c552bf"},
{"children": [],
"info": {"finished": 88,
"host": "ubuntu",
"meta.raw_payload.wsgi-start": {
"base_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
"info": {"host": "ubuntu",
"project": None,
"request": {"method": "GET",
"path": "/v2/a322b5049d224a90bf"
"8786c644409400/volumes",
"query": "",
"scheme": "http"},
"service": None},
"name": "wsgi-start",
"parent_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
"project": "keystone",
"service": "main",
"timestamp": "2015-12-23T14:02:22.427444",
"trace_id": "016c97fd-87f3-40b2-9b55-e431156b694b"},
"name": "wsgi",
"project": "keystone",
"service": "main",
"started": 88},
"parent_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
"trace_id": "016c97fd-87f3-40b2-9b55-e431156b694b"}],
expected = {
"children": [
{
"children": [
{
"children": [],
"info": {
"finished": 76,
"host": "ubuntu",
"meta.raw_payload.db-start": {
"base_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4", # noqa: E501
"info": {
"db": {
"params": {},
"statement": "SELECT 1",
},
"host": "ubuntu",
"project": None,
"service": None,
},
"name": "db-start",
"parent_id": "06320327-2c2c-45ae-923a-515de890276a", # noqa: E501
"project": "keystone",
"service": "main",
"timestamp": "2015-12-23T14:02:22.395365",
"trace_id": "1baf1d24-9ca9-4f4c-bd3f-01b7e0c0735a", # noqa: E501
},
"meta.raw_payload.db-stop": {
"base_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4", # noqa: E501
"info": {
"host": "ubuntu",
"project": None,
"service": None,
},
"name": "db-stop",
"parent_id": "06320327-2c2c-45ae-923a-515de890276a", # noqa: E501
"project": "keystone",
"service": "main",
"timestamp": "2015-12-23T14:02:22.415486",
"trace_id": "1baf1d24-9ca9-4f4c-bd3f-01b7e0c0735a", # noqa: E501
},
"name": "db",
"project": "keystone",
"service": "main",
"started": 56,
"exception": "None",
},
"parent_id": "06320327-2c2c-45ae-923a-515de890276a", # noqa: E501
"trace_id": "1baf1d24-9ca9-4f4c-bd3f-01b7e0c0735a",
}
],
"info": {
"finished": 0,
"host": "ubuntu",
"meta.raw_payload.wsgi-start": {
"base_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
"info": {
"host": "ubuntu",
"project": None,
"request": {
"method": "POST",
"path": "/v2/a322b5049d224a90bf8"
"786c644409400/volumes",
"query": "",
"scheme": "http",
},
"service": None,
},
"name": "wsgi-start",
"parent_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4", # noqa: E501
"project": "keystone",
"service": "main",
"timestamp": "2015-12-23T14:02:22.338776",
"trace_id": "06320327-2c2c-45ae-923a-515de890276a",
},
"name": "wsgi",
"project": "keystone",
"service": "main",
"started": 0,
},
"parent_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
"trace_id": "06320327-2c2c-45ae-923a-515de890276a",
},
{
"children": [],
"info": {
"finished": 41,
"host": "ubuntu",
"meta.raw_payload.wsgi-stop": {
"base_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
"info": {
"host": "ubuntu",
"project": None,
"service": None,
},
"name": "wsgi-stop",
"parent_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4", # noqa: E501
"project": "keystone",
"service": "main",
"timestamp": "2015-12-23T14:02:22.380405",
"trace_id": "839ca3f1-afcb-45be-a4a1-679124c552bf",
},
"name": "wsgi",
"project": "keystone",
"service": "main",
"started": 41,
"exception": "None",
},
"parent_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
"trace_id": "839ca3f1-afcb-45be-a4a1-679124c552bf",
},
{
"children": [],
"info": {
"finished": 88,
"host": "ubuntu",
"meta.raw_payload.wsgi-start": {
"base_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
"info": {
"host": "ubuntu",
"project": None,
"request": {
"method": "GET",
"path": "/v2/a322b5049d224a90bf"
"8786c644409400/volumes",
"query": "",
"scheme": "http",
},
"service": None,
},
"name": "wsgi-start",
"parent_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4", # noqa: E501
"project": "keystone",
"service": "main",
"timestamp": "2015-12-23T14:02:22.427444",
"trace_id": "016c97fd-87f3-40b2-9b55-e431156b694b",
},
"name": "wsgi",
"project": "keystone",
"service": "main",
"started": 88,
},
"parent_id": "7253ca8c-33b3-4f84-b4f1-f5a4311ddfa4",
"trace_id": "016c97fd-87f3-40b2-9b55-e431156b694b",
},
],
"info": {
"finished": 88,
"name": "total",
"started": 0,
"last_trace_started": 88},
"stats": {"db": {"count": 1, "duration": 20},
"wsgi": {"count": 3, "duration": 0}}}
"last_trace_started": 88,
},
"stats": {
"db": {"count": 1, "duration": 20},
"wsgi": {"count": 3, "duration": 0},
},
}
self.redisdb.db.scan_iter.return_value = list(results.keys())
@@ -328,5 +388,6 @@ class RedisParserTestCase(test.TestCase):
expected_filter = self.redisdb.namespace + "10*"
self.redisdb.db.scan_iter.assert_called_once_with(
match=expected_filter)
match=expected_filter
)
self.assertEqual(expected, result)
+10 -5
View File
@@ -18,12 +18,12 @@ from osprofiler import initializer
class InitializerTestCase(testtools.TestCase):
@mock.patch("osprofiler.notifier.set")
@mock.patch("osprofiler.notifier.create")
@mock.patch("osprofiler.web.enable")
def test_initializer(self, web_enable_mock, notifier_create_mock,
notifier_set_mock):
def test_initializer(
self, web_enable_mock, notifier_create_mock, notifier_set_mock
):
conf = mock.Mock()
conf.profiler.connection_string = "driver://"
conf.profiler.hmac_keys = "hmac_keys"
@@ -38,7 +38,12 @@ class InitializerTestCase(testtools.TestCase):
initializer.init_from_conf(conf, context, project, service, host)
notifier_create_mock.assert_called_once_with(
"driver://", context=context, project=project, service=service,
host=host, conf=conf)
"driver://",
context=context,
project=project,
service=service,
host=host,
conf=conf,
)
notifier_set_mock.assert_called_once_with(notifier_mock)
web_enable_mock.assert_called_once_with("hmac_keys")
-1
View File
@@ -20,7 +20,6 @@ from osprofiler.tests import test
class NotifierTestCase(test.TestCase):
def tearDown(self):
notifier.set(notifier._noop_notifier) # restore defaults
notifier.clear_notifier_cache()
+13 -9
View File
@@ -30,19 +30,22 @@ class ConfigTestCase(test.TestCase):
opts.set_defaults(self.conf_fixture.conf)
self.assertFalse(self.conf_fixture.conf.profiler.enabled)
self.assertFalse(self.conf_fixture.conf.profiler.trace_sqlalchemy)
self.assertEqual("SECRET_KEY",
self.conf_fixture.conf.profiler.hmac_keys)
self.assertEqual(
"SECRET_KEY", self.conf_fixture.conf.profiler.hmac_keys
)
self.assertFalse(opts.is_trace_enabled(self.conf_fixture.conf))
self.assertFalse(opts.is_db_trace_enabled(self.conf_fixture.conf))
def test_options_defaults_override(self):
opts.set_defaults(self.conf_fixture.conf, enabled=True,
trace_sqlalchemy=True,
hmac_keys="MY_KEY")
opts.set_defaults(
self.conf_fixture.conf,
enabled=True,
trace_sqlalchemy=True,
hmac_keys="MY_KEY",
)
self.assertTrue(self.conf_fixture.conf.profiler.enabled)
self.assertTrue(self.conf_fixture.conf.profiler.trace_sqlalchemy)
self.assertEqual("MY_KEY",
self.conf_fixture.conf.profiler.hmac_keys)
self.assertEqual("MY_KEY", self.conf_fixture.conf.profiler.hmac_keys)
self.assertTrue(opts.is_trace_enabled(self.conf_fixture.conf))
self.assertTrue(opts.is_db_trace_enabled(self.conf_fixture.conf))
@@ -58,8 +61,9 @@ class ConfigTestCase(test.TestCase):
@mock.patch("osprofiler.web.enable")
@mock.patch("osprofiler.web.disable")
def test_web_trace_enabled(self, mock_disable, mock_enable):
opts.set_defaults(self.conf_fixture.conf, enabled=True,
hmac_keys="MY_KEY")
opts.set_defaults(
self.conf_fixture.conf, enabled=True, hmac_keys="MY_KEY"
)
opts.enable_web_trace(self.conf_fixture.conf)
opts.disable_web_trace(self.conf_fixture.conf)
mock_enable.assert_called_once_with("MY_KEY")
+110 -87
View File
@@ -25,7 +25,6 @@ from osprofiler.tests import test
class ProfilerGlobMethodsTestCase(test.TestCase):
def test_get_profiler_not_inited(self):
profiler.clean()
self.assertIsNone(profiler.get())
@@ -60,7 +59,6 @@ class ProfilerGlobMethodsTestCase(test.TestCase):
class ProfilerTestCase(test.TestCase):
def test_profiler_get_shorten_id(self):
uuid_id = "4e3e0ec6-2938-40b1-8504-09eb1d4b0dee"
prof = profiler._Profiler("secret", base_id="1", parent_id="2")
@@ -103,8 +101,9 @@ class ProfilerTestCase(test.TestCase):
@mock.patch("osprofiler.profiler.timeutils")
@mock.patch("osprofiler.profiler.uuidutils.generate_uuid")
@mock.patch("osprofiler.profiler.notifier.notify")
def test_profiler_start(self, mock_notify, mock_generate_uuid,
mock_timeutils):
def test_profiler_start(
self, mock_notify, mock_generate_uuid, mock_timeutils
):
mock_generate_uuid.return_value = "44"
now = datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None)
mock_timeutils.utcnow.return_value = now
@@ -156,7 +155,6 @@ class ProfilerTestCase(test.TestCase):
class WithTraceTestCase(test.TestCase):
@mock.patch("osprofiler.profiler.stop")
@mock.patch("osprofiler.profiler.start")
def test_with_trace(self, mock_start, mock_stop):
@@ -180,10 +178,9 @@ class WithTraceTestCase(test.TestCase):
self.assertRaises(ValueError, foo)
mock_start.assert_called_once_with("foo", info=None)
mock_stop.assert_called_once_with(info={
"etype": "ValueError",
"message": "bar"
})
mock_stop.assert_called_once_with(
info={"etype": "ValueError", "message": "bar"}
)
@profiler.trace("function", info={"info": "some_info"})
@@ -207,7 +204,6 @@ def trace_with_result_func(a, i=10):
class TraceDecoratorTestCase(test.TestCase):
@mock.patch("osprofiler.profiler.stop")
@mock.patch("osprofiler.profiler.start")
def test_duplicate_trace_disallow(self, mock_start, mock_stop):
@@ -219,7 +215,8 @@ class TraceDecoratorTestCase(test.TestCase):
self.assertRaises(
ValueError,
profiler.trace("test-again", allow_multiple_trace=False),
trace_me)
trace_me,
)
@mock.patch("osprofiler.profiler.stop")
@mock.patch("osprofiler.profiler.start")
@@ -230,8 +227,8 @@ class TraceDecoratorTestCase(test.TestCase):
"function": {
"name": "osprofiler.tests.unit.test_profiler.traced_func",
"args": str((1,)),
"kwargs": str({})
}
"kwargs": str({}),
},
}
mock_start.assert_called_once_with("function", info=expected_info)
mock_stop.assert_called_once_with(info=None)
@@ -243,7 +240,7 @@ class TraceDecoratorTestCase(test.TestCase):
expected_info = {
"function": {
"name": "osprofiler.tests.unit.test_profiler"
".trace_hide_args_func"
".trace_hide_args_func"
}
}
mock_start.assert_called_once_with("hide_args", info=expected_info)
@@ -270,23 +267,18 @@ class TraceDecoratorTestCase(test.TestCase):
start_info = {
"function": {
"name": "osprofiler.tests.unit.test_profiler"
".trace_with_result_func",
".trace_with_result_func",
"args": str((1,)),
"kwargs": str({"i": 2})
"kwargs": str({"i": 2}),
}
}
stop_info = {
"function": {
"result": str((1, 2))
}
}
stop_info = {"function": {"result": str((1, 2))}}
mock_start.assert_called_once_with("hide_result", info=start_info)
mock_stop.assert_called_once_with(info=stop_info)
class FakeTracedCls:
def method1(self, a, b, c=10):
return a + b + c
@@ -345,8 +337,9 @@ class FakeTraceClassMethodSkip(FakeTraceClassMethodBase):
def py3_info(info):
# NOTE(boris-42): py33 I hate you.
info_py3 = copy.deepcopy(info)
new_name = re.sub("FakeTrace[^.]*", "FakeTracedCls",
info_py3["function"]["name"])
new_name = re.sub(
"FakeTrace[^.]*", "FakeTracedCls", info_py3["function"]["name"]
)
info_py3["function"]["name"] = new_name
return info_py3
@@ -357,7 +350,6 @@ def possible_mock_calls(name, info):
class TraceClsDecoratorTestCase(test.TestCase):
@mock.patch("osprofiler.profiler.stop")
@mock.patch("osprofiler.profiler.start")
def test_args(self, mock_start, mock_stop):
@@ -366,15 +358,19 @@ class TraceClsDecoratorTestCase(test.TestCase):
expected_info = {
"a": 10,
"function": {
"name": ("osprofiler.tests.unit.test_profiler"
".FakeTraceClassWithInfo.method1"),
"name": (
"osprofiler.tests.unit.test_profiler"
".FakeTraceClassWithInfo.method1"
),
"args": str((fake_cls, 5, 15)),
"kwargs": str({})
}
"kwargs": str({}),
},
}
self.assertEqual(1, len(mock_start.call_args_list))
self.assertIn(mock_start.call_args_list[0],
possible_mock_calls("rpc", expected_info))
self.assertIn(
mock_start.call_args_list[0],
possible_mock_calls("rpc", expected_info),
)
mock_stop.assert_called_once_with(info=None)
@mock.patch("osprofiler.profiler.stop")
@@ -385,15 +381,19 @@ class TraceClsDecoratorTestCase(test.TestCase):
expected_info = {
"a": 10,
"function": {
"name": ("osprofiler.tests.unit.test_profiler"
".FakeTraceClassWithInfo.method3"),
"name": (
"osprofiler.tests.unit.test_profiler"
".FakeTraceClassWithInfo.method3"
),
"args": str((fake_cls,)),
"kwargs": str({"g": 5, "h": 10})
}
"kwargs": str({"g": 5, "h": 10}),
},
}
self.assertEqual(1, len(mock_start.call_args_list))
self.assertIn(mock_start.call_args_list[0],
possible_mock_calls("rpc", expected_info))
self.assertIn(
mock_start.call_args_list[0],
possible_mock_calls("rpc", expected_info),
)
mock_stop.assert_called_once_with(info=None)
@mock.patch("osprofiler.profiler.stop")
@@ -412,14 +412,18 @@ class TraceClsDecoratorTestCase(test.TestCase):
expected_info = {
"b": 20,
"function": {
"name": ("osprofiler.tests.unit.test_profiler"
".FakeTraceClassHideArgs.method1"),
}
"name": (
"osprofiler.tests.unit.test_profiler"
".FakeTraceClassHideArgs.method1"
),
},
}
self.assertEqual(1, len(mock_start.call_args_list))
self.assertIn(mock_start.call_args_list[0],
possible_mock_calls("a", expected_info))
self.assertIn(
mock_start.call_args_list[0],
possible_mock_calls("a", expected_info),
)
mock_stop.assert_called_once_with(info=None)
@mock.patch("osprofiler.profiler.stop")
@@ -430,23 +434,28 @@ class TraceClsDecoratorTestCase(test.TestCase):
expected_info = {
"function": {
"name": ("osprofiler.tests.unit.test_profiler"
".FakeTracePrivate._method"),
"name": (
"osprofiler.tests.unit.test_profiler"
".FakeTracePrivate._method"
),
"args": str((fake_cls, 5)),
"kwargs": str({})
"kwargs": str({}),
}
}
self.assertEqual(1, len(mock_start.call_args_list))
self.assertIn(mock_start.call_args_list[0],
possible_mock_calls("rpc", expected_info))
self.assertIn(
mock_start.call_args_list[0],
possible_mock_calls("rpc", expected_info),
)
mock_stop.assert_called_once_with(info=None)
@mock.patch("osprofiler.profiler.stop")
@mock.patch("osprofiler.profiler.start")
@test.testcase.skip(
"Static method tracing was disabled due the bug. This test should be "
"skipped until we find the way to address it.")
"skipped until we find the way to address it."
)
def test_static(self, mock_start, mock_stop):
fake_cls = FakeTraceStaticMethod()
@@ -459,18 +468,19 @@ class TraceClsDecoratorTestCase(test.TestCase):
# expect to see method4 because method is
# static and doesn't have reference to class
# - and FakeTraceStatic.method4 in PY3
"name":
"osprofiler.tests.unit.test_profiler"
"osprofiler.tests.unit.test_profiler.FakeTraceStatic"
".method4",
"name": "osprofiler.tests.unit.test_profiler"
"osprofiler.tests.unit.test_profiler.FakeTraceStatic"
".method4",
"args": str((25,)),
"kwargs": str({})
"kwargs": str({}),
}
}
self.assertEqual(1, len(mock_start.call_args_list))
self.assertIn(mock_start.call_args_list[0],
possible_mock_calls("rpc", expected_info))
self.assertIn(
mock_start.call_args_list[0],
possible_mock_calls("rpc", expected_info),
)
mock_stop.assert_called_once_with(info=None)
@mock.patch("osprofiler.profiler.stop")
@@ -489,8 +499,7 @@ class TraceClsDecoratorTestCase(test.TestCase):
class FakeTraceWithMetaclassBase(metaclass=profiler.TracedMeta):
__trace_args__ = {"name": "rpc",
"info": {"a": 10}}
__trace_args__ = {"name": "rpc", "info": {"a": 10}}
def method1(self, a, b, c=10):
return a + b + c
@@ -511,29 +520,27 @@ class FakeTraceDummy(FakeTraceWithMetaclassBase):
class FakeTraceWithMetaclassHideArgs(FakeTraceWithMetaclassBase):
__trace_args__ = {"name": "a",
"info": {"b": 20},
"hide_args": True}
__trace_args__ = {"name": "a", "info": {"b": 20}, "hide_args": True}
def method5(self, k, l):
return k + l
class FakeTraceWithMetaclassPrivate(FakeTraceWithMetaclassBase):
__trace_args__ = {"name": "rpc",
"trace_private": True}
__trace_args__ = {"name": "rpc", "trace_private": True}
def _new_private_method(self, m):
return 2 * m
class TraceWithMetaclassTestCase(test.TestCase):
def test_no_name_exception(self):
def define_class_with_no_name():
class FakeTraceWithMetaclassNoName(FakeTracedCls,
metaclass=profiler.TracedMeta):
class FakeTraceWithMetaclassNoName(
FakeTracedCls, metaclass=profiler.TracedMeta
):
pass
self.assertRaises(TypeError, define_class_with_no_name, 1)
@mock.patch("osprofiler.profiler.stop")
@@ -544,15 +551,19 @@ class TraceWithMetaclassTestCase(test.TestCase):
expected_info = {
"a": 10,
"function": {
"name": ("osprofiler.tests.unit.test_profiler"
".FakeTraceWithMetaclassBase.method1"),
"name": (
"osprofiler.tests.unit.test_profiler"
".FakeTraceWithMetaclassBase.method1"
),
"args": str((fake_cls, 5, 15)),
"kwargs": str({})
}
"kwargs": str({}),
},
}
self.assertEqual(1, len(mock_start.call_args_list))
self.assertIn(mock_start.call_args_list[0],
possible_mock_calls("rpc", expected_info))
self.assertIn(
mock_start.call_args_list[0],
possible_mock_calls("rpc", expected_info),
)
mock_stop.assert_called_once_with(info=None)
@mock.patch("osprofiler.profiler.stop")
@@ -563,15 +574,19 @@ class TraceWithMetaclassTestCase(test.TestCase):
expected_info = {
"a": 10,
"function": {
"name": ("osprofiler.tests.unit.test_profiler"
".FakeTraceWithMetaclassBase.method3"),
"name": (
"osprofiler.tests.unit.test_profiler"
".FakeTraceWithMetaclassBase.method3"
),
"args": str((fake_cls,)),
"kwargs": str({"g": 5, "h": 10})
}
"kwargs": str({"g": 5, "h": 10}),
},
}
self.assertEqual(1, len(mock_start.call_args_list))
self.assertIn(mock_start.call_args_list[0],
possible_mock_calls("rpc", expected_info))
self.assertIn(
mock_start.call_args_list[0],
possible_mock_calls("rpc", expected_info),
)
mock_stop.assert_called_once_with(info=None)
@mock.patch("osprofiler.profiler.stop")
@@ -590,14 +605,18 @@ class TraceWithMetaclassTestCase(test.TestCase):
expected_info = {
"b": 20,
"function": {
"name": ("osprofiler.tests.unit.test_profiler"
".FakeTraceWithMetaclassHideArgs.method5")
}
"name": (
"osprofiler.tests.unit.test_profiler"
".FakeTraceWithMetaclassHideArgs.method5"
)
},
}
self.assertEqual(1, len(mock_start.call_args_list))
self.assertIn(mock_start.call_args_list[0],
possible_mock_calls("a", expected_info))
self.assertIn(
mock_start.call_args_list[0],
possible_mock_calls("a", expected_info),
)
mock_stop.assert_called_once_with(info=None)
@mock.patch("osprofiler.profiler.stop")
@@ -608,14 +627,18 @@ class TraceWithMetaclassTestCase(test.TestCase):
expected_info = {
"function": {
"name": ("osprofiler.tests.unit.test_profiler"
".FakeTraceWithMetaclassPrivate._new_private_method"),
"name": (
"osprofiler.tests.unit.test_profiler"
".FakeTraceWithMetaclassPrivate._new_private_method"
),
"args": str((fake_cls, 5)),
"kwargs": str({})
"kwargs": str({}),
}
}
self.assertEqual(1, len(mock_start.call_args_list))
self.assertIn(mock_start.call_args_list[0],
possible_mock_calls("rpc", expected_info))
self.assertIn(
mock_start.call_args_list[0],
possible_mock_calls("rpc", expected_info),
)
mock_stop.assert_called_once_with(info=None)
+11 -13
View File
@@ -22,7 +22,6 @@ from osprofiler.tests import test
class SqlalchemyTracingTestCase(test.TestCase):
@mock.patch("osprofiler.sqlalchemy.profiler")
def test_before_execute(self, mock_profiler):
handler = sqlalchemy._before_cursor_execute("sql")
@@ -43,11 +42,7 @@ class SqlalchemyTracingTestCase(test.TestCase):
cursor = mock.MagicMock()
cursor._rows = (1,)
handler(1, cursor, 2, 3, 4, 5)
info = {
"db": {
"result": str(cursor._rows)
}
}
info = {"db": {"result": str(cursor._rows)}}
mock_profiler.stop.assert_called_once_with(info=info)
@mock.patch("osprofiler.sqlalchemy.profiler")
@@ -66,15 +61,16 @@ class SqlalchemyTracingTestCase(test.TestCase):
"db": {
"original_exception": str(original_exception),
"chained_exception": str(chained_exception),
}
},
}
mock_profiler.stop.assert_called_once_with(info=expected_info)
@mock.patch("osprofiler.sqlalchemy.handle_error")
@mock.patch("osprofiler.sqlalchemy._before_cursor_execute")
@mock.patch("osprofiler.sqlalchemy._after_cursor_execute")
def test_add_tracing(self, mock_after_exc, mock_before_exc,
mock_handle_error):
def test_add_tracing(
self, mock_after_exc, mock_before_exc, mock_handle_error
):
sa = mock.MagicMock()
engine = mock.MagicMock()
@@ -96,8 +92,9 @@ class SqlalchemyTracingTestCase(test.TestCase):
@mock.patch("osprofiler.sqlalchemy.handle_error")
@mock.patch("osprofiler.sqlalchemy._before_cursor_execute")
@mock.patch("osprofiler.sqlalchemy._after_cursor_execute")
def test_wrap_session(self, mock_after_exc, mock_before_exc,
mock_handle_error):
def test_wrap_session(
self, mock_after_exc, mock_before_exc, mock_handle_error
):
sa = mock.MagicMock()
@contextlib.contextmanager
@@ -131,8 +128,9 @@ class SqlalchemyTracingTestCase(test.TestCase):
@mock.patch("osprofiler.sqlalchemy._before_cursor_execute")
@mock.patch("osprofiler.sqlalchemy._after_cursor_execute")
@mock.patch("osprofiler.profiler")
def test_with_sql_result(self, mock_profiler, mock_after_exc,
mock_before_exc, mock_handle_error):
def test_with_sql_result(
self, mock_profiler, mock_after_exc, mock_before_exc, mock_handle_error
):
sa = mock.MagicMock()
engine = mock.MagicMock()
+3 -3
View File
@@ -25,7 +25,6 @@ from osprofiler.tests import test
class UtilsTestCase(test.TestCase):
def test_split(self):
self.assertEqual([1, 2], utils.split([1, 2]))
self.assertEqual(["A", "B"], utils.split("A, B"))
@@ -35,8 +34,9 @@ class UtilsTestCase(test.TestCase):
self.assertRaises(TypeError, utils.split, 1)
def test_binary_encode_and_decode(self):
self.assertEqual("text",
utils.binary_decode(utils.binary_encode("text")))
self.assertEqual(
"text", utils.binary_decode(utils.binary_encode("text"))
)
def test_binary_encode_invalid_type(self):
self.assertRaises(TypeError, utils.binary_encode, 1234)
+59 -57
View File
@@ -29,7 +29,6 @@ def dummy_app(environ, response):
class WebTestCase(test.TestCase):
def setUp(self):
super().setUp()
profiler.clean()
@@ -43,11 +42,13 @@ class WebTestCase(test.TestCase):
def test_get_trace_id_headers(self):
profiler.init("key", base_id="y", parent_id="z")
headers = web.get_trace_id_headers()
self.assertEqual(sorted(headers.keys()),
sorted(["X-Trace-Info", "X-Trace-HMAC"]))
self.assertEqual(
sorted(headers.keys()), sorted(["X-Trace-Info", "X-Trace-HMAC"])
)
trace_info = utils.signed_unpack(headers["X-Trace-Info"],
headers["X-Trace-HMAC"], ["key"])
trace_info = utils.signed_unpack(
headers["X-Trace-Info"], headers["X-Trace-HMAC"], ["key"]
)
self.assertIn("hmac_key", trace_info)
self.assertEqual("key", trace_info.pop("hmac_key"))
self.assertEqual({"parent_id": "z", "base_id": "y"}, trace_info)
@@ -83,9 +84,9 @@ class WebMiddlewareTestCase(test.TestCase):
self.assertTrue(wsgi.enabled)
self.assertEqual(wsgi.hmac_keys, [local_conf["hmac_keys"]])
def _test_wsgi_middleware_with_invalid_trace(self, headers, hmac_key,
mock_profiler_init,
enabled=True):
def _test_wsgi_middleware_with_invalid_trace(
self, headers, hmac_key, mock_profiler_init, enabled=True
):
request = mock.MagicMock()
request.get_response.return_value = "yeah!"
request.headers = headers
@@ -103,21 +104,19 @@ class WebMiddlewareTestCase(test.TestCase):
"a": "1",
"b": "2",
"X-Trace-Info": pack[0],
"X-Trace-HMAC": pack[1]
"X-Trace-HMAC": pack[1],
}
self._test_wsgi_middleware_with_invalid_trace(headers, hmac_key,
mock_profiler_init,
enabled=False)
self._test_wsgi_middleware_with_invalid_trace(
headers, hmac_key, mock_profiler_init, enabled=False
)
@mock.patch("osprofiler.web.profiler.init")
def test_wsgi_middleware_no_trace(self, mock_profiler_init):
headers = {
"a": "1",
"b": "2"
}
self._test_wsgi_middleware_with_invalid_trace(headers, "secret",
mock_profiler_init)
headers = {"a": "1", "b": "2"}
self._test_wsgi_middleware_with_invalid_trace(
headers, "secret", mock_profiler_init
)
@mock.patch("osprofiler.web.profiler.init")
def test_wsgi_middleware_invalid_trace_headers(self, mock_profiler_init):
@@ -125,22 +124,20 @@ class WebMiddlewareTestCase(test.TestCase):
"a": "1",
"b": "2",
"X-Trace-Info": "abbababababa",
"X-Trace-HMAC": "abbababababa"
"X-Trace-HMAC": "abbababababa",
}
self._test_wsgi_middleware_with_invalid_trace(headers, "secret",
mock_profiler_init)
self._test_wsgi_middleware_with_invalid_trace(
headers, "secret", mock_profiler_init
)
@mock.patch("osprofiler.web.profiler.init")
def test_wsgi_middleware_no_trace_hmac(self, mock_profiler_init):
hmac_key = "secret"
pack = utils.signed_pack({"base_id": "1", "parent_id": "2"}, hmac_key)
headers = {
"a": "1",
"b": "2",
"X-Trace-Info": pack[0]
}
self._test_wsgi_middleware_with_invalid_trace(headers, hmac_key,
mock_profiler_init)
headers = {"a": "1", "b": "2", "X-Trace-Info": pack[0]}
self._test_wsgi_middleware_with_invalid_trace(
headers, hmac_key, mock_profiler_init
)
@mock.patch("osprofiler.web.profiler.init")
def test_wsgi_middleware_invalid_hmac(self, mock_profiler_init):
@@ -150,24 +147,27 @@ class WebMiddlewareTestCase(test.TestCase):
"a": "1",
"b": "2",
"X-Trace-Info": pack[0],
"X-Trace-HMAC": "not valid hmac"
"X-Trace-HMAC": "not valid hmac",
}
self._test_wsgi_middleware_with_invalid_trace(headers, hmac_key,
mock_profiler_init)
self._test_wsgi_middleware_with_invalid_trace(
headers, hmac_key, mock_profiler_init
)
@mock.patch("osprofiler.web.profiler.init")
def test_wsgi_middleware_invalid_trace_info(self, mock_profiler_init):
hmac_key = "secret"
pack = utils.signed_pack([{"base_id": "1"}, {"parent_id": "2"}],
hmac_key)
pack = utils.signed_pack(
[{"base_id": "1"}, {"parent_id": "2"}], hmac_key
)
headers = {
"a": "1",
"b": "2",
"X-Trace-Info": pack[0],
"X-Trace-HMAC": pack[1]
"X-Trace-HMAC": pack[1],
}
self._test_wsgi_middleware_with_invalid_trace(headers, hmac_key,
mock_profiler_init)
self._test_wsgi_middleware_with_invalid_trace(
headers, hmac_key, mock_profiler_init
)
@mock.patch("osprofiler.web.profiler.init")
def test_wsgi_middleware_key_passthrough(self, mock_profiler_init):
@@ -187,15 +187,16 @@ class WebMiddlewareTestCase(test.TestCase):
"a": "1",
"b": "2",
"X-Trace-Info": pack[0],
"X-Trace-HMAC": pack[1]
"X-Trace-HMAC": pack[1],
}
middleware = web.WsgiMiddleware("app", "secret1,%s" % hmac_key,
enabled=True)
middleware = web.WsgiMiddleware(
"app", f"secret1,{hmac_key}", enabled=True
)
self.assertEqual("yeah!", middleware(request))
mock_profiler_init.assert_called_once_with(hmac_key=hmac_key,
base_id="1",
parent_id="2")
mock_profiler_init.assert_called_once_with(
hmac_key=hmac_key, base_id="1", parent_id="2"
)
@mock.patch("osprofiler.web.profiler.init")
def test_wsgi_middleware_key_passthrough2(self, mock_profiler_init):
@@ -215,15 +216,16 @@ class WebMiddlewareTestCase(test.TestCase):
"a": "1",
"b": "2",
"X-Trace-Info": pack[0],
"X-Trace-HMAC": pack[1]
"X-Trace-HMAC": pack[1],
}
middleware = web.WsgiMiddleware("app", "%s,secret2" % hmac_key,
enabled=True)
middleware = web.WsgiMiddleware(
"app", f"{hmac_key},secret2", enabled=True
)
self.assertEqual("yeah!", middleware(request))
mock_profiler_init.assert_called_once_with(hmac_key=hmac_key,
base_id="1",
parent_id="2")
mock_profiler_init.assert_called_once_with(
hmac_key=hmac_key, base_id="1", parent_id="2"
)
@mock.patch("osprofiler.web.profiler.Trace")
@mock.patch("osprofiler.web.profiler.init")
@@ -244,20 +246,20 @@ class WebMiddlewareTestCase(test.TestCase):
"a": "1",
"b": "2",
"X-Trace-Info": pack[0],
"X-Trace-HMAC": pack[1]
"X-Trace-HMAC": pack[1],
}
middleware = web.WsgiMiddleware("app", hmac_key, enabled=True)
self.assertEqual("yeah!", middleware(request))
mock_profiler_init.assert_called_once_with(hmac_key=hmac_key,
base_id="1",
parent_id="2")
mock_profiler_init.assert_called_once_with(
hmac_key=hmac_key, base_id="1", parent_id="2"
)
expected_info = {
"request": {
"path": request.path,
"query": request.query_string,
"method": request.method,
"scheme": request.scheme
"scheme": request.scheme,
}
}
mock_profiler_trace.assert_called_once_with("wsgi", info=expected_info)
@@ -288,15 +290,15 @@ class WebMiddlewareTestCase(test.TestCase):
"a": "1",
"b": "2",
"X-Trace-Info": pack[0],
"X-Trace-HMAC": pack[1]
"X-Trace-HMAC": pack[1],
}
web.enable("super_secret_key1,super_secret_key2")
middleware = web.WsgiMiddleware("app", enabled=True)
self.assertEqual("yeah!", middleware(request))
mock_profiler_init.assert_called_once_with(hmac_key=hmac_key,
base_id="1",
parent_id="2")
mock_profiler_init.assert_called_once_with(
hmac_key=hmac_key, base_id="1", parent_id="2"
)
def test_disable(self):
web.disable()
+14 -10
View File
@@ -37,10 +37,7 @@ def get_trace_id_headers():
if p and p.hmac_key:
data = {"base_id": p.get_base_id(), "parent_id": p.get_id()}
pack = utils.signed_pack(data, p.hmac_key)
return {
X_TRACE_INFO: pack[0],
X_TRACE_HMAC: pack[1]
}
return {X_TRACE_INFO: pack[0], X_TRACE_HMAC: pack[1]}
return {}
@@ -92,6 +89,7 @@ class WsgiMiddleware:
def factory(cls, global_conf, **local_conf):
def filter_(app):
return cls(app, **local_conf)
return filter_
def _trace_is_valid(self, trace_info):
@@ -106,13 +104,19 @@ class WsgiMiddleware:
@webob.dec.wsgify
def __call__(self, request):
if (_ENABLED is not None and not _ENABLED
or _ENABLED is None and not self.enabled):
if (
_ENABLED is not None
and not _ENABLED
or _ENABLED is None
and not self.enabled
):
return request.get_response(self.application)
trace_info = utils.signed_unpack(request.headers.get(X_TRACE_INFO),
request.headers.get(X_TRACE_HMAC),
_HMAC_KEYS or self.hmac_keys)
trace_info = utils.signed_unpack(
request.headers.get(X_TRACE_INFO),
request.headers.get(X_TRACE_HMAC),
_HMAC_KEYS or self.hmac_keys,
)
if not self._trace_is_valid(trace_info):
return request.get_response(self.application)
@@ -123,7 +127,7 @@ class WsgiMiddleware:
"path": request.path,
"query": request.query_string,
"method": request.method,
"scheme": request.scheme
"scheme": request.scheme,
}
}
try:
+11 -2
View File
@@ -65,5 +65,14 @@ packages = [
"osprofiler"
]
[tool.bandit]
exclude_dirs = ["tests"]
[tool.ruff]
line-length = 79
[tool.ruff.format]
quote-style = "preserve"
docstring-code-format = true
[tool.ruff.lint]
select = ["E4", "E5", "E7", "E9", "F", "G", "LOG", "S", "UP"]
external = ["H"]
ignore = ["E741"]
+23 -13
View File
@@ -195,10 +195,8 @@ htmlhelp_basename = 'osprofilerReleaseNotesDoc'
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
# 'preamble': '',
}
@@ -207,9 +205,13 @@ latex_elements = {
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
('index', 'osprofilerReleaseNotes.tex',
'osprofiler Release Notes Documentation',
'osprofiler Developers', 'manual'),
(
'index',
'osprofilerReleaseNotes.tex',
'osprofiler Release Notes Documentation',
'osprofiler Developers',
'manual',
),
]
# The name of an image file (relative to this directory) to place at the top of
@@ -238,9 +240,13 @@ latex_documents = [
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'osprofilerReleaseNotes',
'osprofiler Release Notes Documentation',
['osprofiler Developers'], 1)
(
'index',
'osprofilerReleaseNotes',
'osprofiler Release Notes Documentation',
['osprofiler Developers'],
1,
)
]
# If true, show URL addresses after external links.
@@ -253,11 +259,15 @@ man_pages = [
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
('index', 'osprofilerReleaseNotes',
'osprofiler Release Notes Documentation',
'osprofiler Developers', 'osprofilerReleaseNotes',
'One line description of project.',
'Miscellaneous'),
(
'index',
'osprofilerReleaseNotes',
'osprofiler Release Notes Documentation',
'osprofiler Developers',
'osprofilerReleaseNotes',
'One line description of project.',
'Miscellaneous',
),
]
# Documents to append as an appendix to all manuals.
+1 -3
View File
@@ -14,6 +14,4 @@
# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
import setuptools
setuptools.setup(
setup_requires=['pbr>=2.0'],
pbr=True)
setuptools.setup(setup_requires=['pbr>=2.0'], pbr=True)
+1 -1
View File
@@ -22,7 +22,7 @@ ENABLED_PYLINT_MSGS = ['W0611']
def main(dirpath):
enable_opt = '--enable=%s' % ','.join(ENABLED_PYLINT_MSGS)
enable_opt = '--enable={}'.format(','.join(ENABLED_PYLINT_MSGS))
lint.Run(['--reports=n', '--disable=all', enable_opt, dirpath])
+16 -11
View File
@@ -29,18 +29,23 @@ def main(argv):
venv = os.environ['VIRTUAL_ENV']
pip_requires = first_file([
os.path.join(root, 'requirements.txt'),
os.path.join(root, 'tools', 'pip-requires'),
])
test_requires = first_file([
os.path.join(root, 'test-requirements.txt'),
os.path.join(root, 'tools', 'test-requires'),
])
py_version = "python{}.{}".format(sys.version_info[0], sys.version_info[1])
pip_requires = first_file(
[
os.path.join(root, 'requirements.txt'),
os.path.join(root, 'tools', 'pip-requires'),
]
)
test_requires = first_file(
[
os.path.join(root, 'test-requirements.txt'),
os.path.join(root, 'tools', 'test-requires'),
]
)
py_version = f"python{sys.version_info[0]}.{sys.version_info[1]}"
project = 'oslo'
install = install_venv.InstallVenv(root, venv, pip_requires, test_requires,
py_version, project)
install = install_venv.InstallVenv(
root, venv, pip_requires, test_requires, py_version, project
)
# NOTE(dprince): For Tox we only run post_process, which patches files, etc
install.post_process()
+15 -17
View File
@@ -52,16 +52,23 @@ commands =
sphinx-build -W --keep-going -b html -d doc/build/doctrees doc/source doc/build/html
usedevelop = false
[testenv:releasenotes]
deps =
-c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
-r{toxinidir}/doc/requirements.txt
allowlist_externals = rm
commands =
rm -rf releasenotes/build
sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html
[flake8]
show-source = true
builtins = _
# E741 ambiguous variable name 'l'
# W503 line break before binary operator
ignore = E741,W503
enable-extensions = H211,H214,H215
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,tools,setup.py,build,releasenotes
import-order-style = pep8
application-import-names = osprofiler
exclude = .venv,.git,.tox,dist,doc,*lib/python*,*egg,build,__init__.py
# We only enable the hacking (H) and osprofiler (N) checks
select = H
# H301 Black will put commas after imports that can't fit on one line
# H405 Multi-line docstrings are fine
ignore = H301,H405
[flake8:local-plugins]
extension =
@@ -71,12 +78,3 @@ extension =
N353 = checks:check_using_unicode
N354 = checks:check_raises
paths = ./osprofiler/hacking
[testenv:releasenotes]
deps =
-c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
-r{toxinidir}/doc/requirements.txt
allowlist_externals = rm
commands =
rm -rf releasenotes/build
sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html