Skip to content

Commit

Permalink
Add profile_trace testing (#858)
Browse files Browse the repository at this point in the history
* Include isort stdlibs for determining stdlib modules

* Use isort & sys to eliminate std & builtin modules

Previously, the logic would fail to identify third party modules installed within the
local user socpe. This fixes that issue by skipping builtin and stdlib modules by name,
instead of attempting to identify third party modules based on file paths.

* Handle importlib_metadata.version being a callable

* Add isort into third party notices

* [Mega-Linter] Apply linters fixes

* Remove Python 2.7 and pypy2 testing (#835)

* Change setup-python to @v2 for py2.7

* Remove py27 and pypy testing

* Fix syntax errors

* Fix comma related syntax errors

* Fix more issues in tox

* Remove gearman test

* Containerized CI Pipeline (#836)

* Revert "Remove Python 2.7 and pypy2 testing (#835)"

This reverts commit abb6405.

* Containerize CI process

* Publish new docker container for CI images

* Rename github actions job

* Copyright tag scripts

* Drop debug line

* Swap to new CI image

* Move pip install to just main python

* Remove libcurl special case from tox

* Install special case packages into main image

* Remove unused packages

* Remove all other triggers besides manual

* Add make run command

* Cleanup small bugs

* Fix CI Image Tagging (#838)

* Correct templated CI image name

* Pin pypy2.7 in image

* Fix up scripting

* Temporarily Restore Old CI Pipeline (#841)

* Restore old pipelines

* Remove python 2 from setup-python

* Rework CI Pipeline (#839)

Change pypy to pypy27 in tox.

Fix checkout logic

Pin tox requires

* Fix Tests on New CI (#843)

* Remove non-root user

* Test new CI image

* Change pypy to pypy27 in tox.

* Fix checkout logic

* Fetch git tags properly

* Pin tox requires

* Adjust default db settings for github actions

* Rename elasticsearch services

* Reset to new pipelines

* [Mega-Linter] Apply linters fixes

* Fix timezone

* Fix docker networking

* Pin dev image to new sha

* Standardize gearman DB settings

* Fix elasticsearch settings bug

* Fix gearman bug

* Add missing odbc headers

* Add more debug messages

* Swap out dev ci image

* Fix required virtualenv version

* Swap out dev ci image

* Swap out dev ci image

* Remove aioredis v1 for EOL

* Add coverage paths for docker container

* Unpin ci container

---------

Co-authored-by: TimPansino <TimPansino@users.noreply.github.com>

* Trigger tests

* Add testing for profile trace.

* [Mega-Linter] Apply linters fixes

* Ignore __call__ from coverage on profile_trace.

* [Mega-Linter] Apply linters fixes

---------

Co-authored-by: Hannah Stepanek <hstepanek@newrelic.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Co-authored-by: hmstepanek <hmstepanek@users.noreply.github.com>
Co-authored-by: Lalleh Rafeei <84813886+lrafeei@users.noreply.github.com>
Co-authored-by: Timothy Pansino <11214426+TimPansino@users.noreply.github.com>
Co-authored-by: TimPansino <TimPansino@users.noreply.github.com>
Co-authored-by: umaannamalai <umaannamalai@users.noreply.github.com>
  • Loading branch information
8 people authored Jun 30, 2023
1 parent 998b035 commit 66c2e19
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 31 deletions.
50 changes: 19 additions & 31 deletions newrelic/api/profile_trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,27 @@
# limitations under the License.

import functools
import sys
import os
import sys

from newrelic.packages import six

from newrelic.api.time_trace import current_trace
from newrelic import __file__ as AGENT_PACKAGE_FILE
from newrelic.api.function_trace import FunctionTrace
from newrelic.common.object_wrapper import FunctionWrapper, wrap_object
from newrelic.api.time_trace import current_trace
from newrelic.common.object_names import callable_name
from newrelic.common.object_wrapper import FunctionWrapper, wrap_object
from newrelic.packages import six

from newrelic import __file__ as AGENT_PACKAGE_FILE
AGENT_PACKAGE_DIRECTORY = os.path.dirname(AGENT_PACKAGE_FILE) + '/'
AGENT_PACKAGE_DIRECTORY = os.path.dirname(AGENT_PACKAGE_FILE) + "/"


class ProfileTrace(object):

def __init__(self, depth):
self.function_traces = []
self.maximum_depth = depth
self.current_depth = 0

def __call__(self, frame, event, arg):

if event not in ['call', 'c_call', 'return', 'c_return',
'exception', 'c_exception']:
def __call__(self, frame, event, arg): # pragma: no cover
if event not in ["call", "c_call", "return", "c_return", "exception", "c_exception"]:
return

parent = current_trace()
Expand All @@ -49,8 +45,7 @@ def __call__(self, frame, event, arg):
# coroutine systems based on greenlets so don't run
# if we detect may be using greenlets.

if (hasattr(sys, '_current_frames') and
parent.thread_id not in sys._current_frames()):
if hasattr(sys, "_current_frames") and parent.thread_id not in sys._current_frames():
return

co = frame.f_code
Expand Down Expand Up @@ -84,7 +79,7 @@ def _callable():
except Exception:
pass

if event in ['call', 'c_call']:
if event in ["call", "c_call"]:
# Skip the outermost as we catch that with the root
# function traces for the profile trace.

Expand All @@ -100,19 +95,17 @@ def _callable():
self.function_traces.append(None)
return

if event == 'call':
if event == "call":
func = _callable()
if func:
name = callable_name(func)
else:
name = '%s:%s#%s' % (func_filename, func_name,
func_line_no)
name = "%s:%s#%s" % (func_filename, func_name, func_line_no)
else:
func = arg
name = callable_name(arg)
if not name:
name = '%s:@%s#%s' % (func_filename, func_name,
func_line_no)
name = "%s:@%s#%s" % (func_filename, func_name, func_line_no)

function_trace = FunctionTrace(name=name, parent=parent)
function_trace.__enter__()
Expand All @@ -127,7 +120,7 @@ def _callable():
self.function_traces.append(function_trace)
self.current_depth += 1

elif event in ['return', 'c_return', 'c_exception']:
elif event in ["return", "c_return", "c_exception"]:
if not self.function_traces:
return

Expand All @@ -143,9 +136,7 @@ def _callable():
self.current_depth -= 1


def ProfileTraceWrapper(wrapped, name=None, group=None, label=None,
params=None, depth=3):

def ProfileTraceWrapper(wrapped, name=None, group=None, label=None, params=None, depth=3):
def wrapper(wrapped, instance, args, kwargs):
parent = current_trace()

Expand Down Expand Up @@ -192,7 +183,7 @@ def wrapper(wrapped, instance, args, kwargs):
_params = params

with FunctionTrace(_name, _group, _label, _params, parent=parent, source=wrapped):
if not hasattr(sys, 'getprofile'):
if not hasattr(sys, "getprofile"):
return wrapped(*args, **kwargs)

profiler = sys.getprofile()
Expand All @@ -212,11 +203,8 @@ def wrapper(wrapped, instance, args, kwargs):


def profile_trace(name=None, group=None, label=None, params=None, depth=3):
return functools.partial(ProfileTraceWrapper, name=name,
group=group, label=label, params=params, depth=depth)
return functools.partial(ProfileTraceWrapper, name=name, group=group, label=label, params=params, depth=depth)


def wrap_profile_trace(module, object_path, name=None,
group=None, label=None, params=None, depth=3):
return wrap_object(module, object_path, ProfileTraceWrapper,
(name, group, label, params, depth))
def wrap_profile_trace(module, object_path, name=None, group=None, label=None, params=None, depth=3):
return wrap_object(module, object_path, ProfileTraceWrapper, (name, group, label, params, depth))
88 changes: 88 additions & 0 deletions tests/agent_features/test_profile_trace.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Copyright 2010 New Relic, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


from testing_support.validators.validate_transaction_metrics import (
validate_transaction_metrics,
)

from newrelic.api.background_task import background_task
from newrelic.api.profile_trace import ProfileTraceWrapper, profile_trace


def test_profile_trace_wrapper():
def _test():
def nested_fn():
pass

nested_fn()

wrapped_test = ProfileTraceWrapper(_test)
wrapped_test()


@validate_transaction_metrics("test_profile_trace:test_profile_trace_empty_args", background_task=True)
@background_task()
def test_profile_trace_empty_args():
@profile_trace()
def _test():
pass

_test()


_test_profile_trace_defined_args_scoped_metrics = [("Custom/TestTrace", 1)]


@validate_transaction_metrics(
"test_profile_trace:test_profile_trace_defined_args",
scoped_metrics=_test_profile_trace_defined_args_scoped_metrics,
background_task=True,
)
@background_task()
def test_profile_trace_defined_args():
@profile_trace(name="TestTrace", group="Custom", label="Label", params={"key": "value"}, depth=7)
def _test():
pass

_test()


_test_profile_trace_callable_args_scoped_metrics = [("Function/TestProfileTrace", 1)]


@validate_transaction_metrics(
"test_profile_trace:test_profile_trace_callable_args",
scoped_metrics=_test_profile_trace_callable_args_scoped_metrics,
background_task=True,
)
@background_task()
def test_profile_trace_callable_args():
def name_callable():
return "TestProfileTrace"

def group_callable():
return "Function"

def label_callable():
return "HSM"

def params_callable():
return {"account_id": "12345"}

@profile_trace(name=name_callable, group=group_callable, label=label_callable, params=params_callable, depth=0)
def _test():
pass

_test()

0 comments on commit 66c2e19

Please sign in to comment.