def _increment_ref_count(callback, wrapped, instance, args, kwargs): transaction = retrieve_current_transaction() if hasattr(callback, '_nr_transaction'): if callback._nr_transaction is not None: if current_thread_id() != callback._nr_transaction.thread_id: # Callback being added not in the main thread; ignore. # Since we are not incrementing the counter for this callback, # we need to remove the transaction from the callback, so it # doesn't get decremented either. callback._nr_transaction = None return wrapped(*args, **kwargs) if transaction is not callback._nr_transaction: _logger.error( 'Attempt to add callback to ioloop with different ' 'transaction attached than in the cache. Please report this' ' issue to New Relic support.\n%s', ''.join(traceback.format_stack()[:-1])) # Since we are not incrementing the counter for this callback, # we need to remove the transaction from the callback, so it doesn't # get decremented either. callback._nr_transaction = None return wrapped(*args, **kwargs) if transaction is None: return wrapped(*args, **kwargs) transaction._ref_count += 1 return wrapped(*args, **kwargs)
def _requesthandler_function_trace(wrapped, instance, args, kwargs): # Use this function tracer when a function you want to trace is called # synchronously from a function that is already in the transaction, such as # web.RequestHandler._execute. transaction = retrieve_current_transaction() name = callable_name(wrapped) with FunctionTrace(transaction, name=name): return wrapped(*args, **kwargs)
def _nr_wrapper_Runner_run_(wrapped, instance, args, kwargs): result = wrapped(*args, **kwargs) transaction = retrieve_current_transaction() if transaction is None: return result if instance.finished: transaction._ref_count -= 1 return result
def _nr_wrapper_curl_httpclient_CurlAsyncHTTPClient_fetch_impl_( wrapped, instance, args, kwargs): def _bind_params(request, callback, *args, **kwargs): return request, callback request, callback = _bind_params(*args, **kwargs) # There's a delay between when streaming_callback is wrapped (and the # transaction is attached) and when it gets added to the IOLoop (no # transaction in the Transaction Cache). # # We want to bypass the check that makes sure the transaction in the # Transaction Cache matches the transaction attached to the callback # in order to add the callback without producing a logged error. # # Setting `_nr_transaction` to `None` will: # 1. Allow the callback to be added to the IOLoop without error message. # 2. Prevent transaction._ref_count from incrementing when added. # 3. Prevent transaction._ref_count from decrementing when run. # # Note that streaming_callback will still be traced when it runs. if request.streaming_callback: request.streaming_callback._nr_transaction = None if callback: transaction = retrieve_current_transaction() if transaction: transaction._ref_count += 1 @function_wrapper def _nr_wrapper_decrementer(_wrapped, _instance, _args, _kwargs): try: return _wrapped(*_args, **_kwargs) finally: transaction._ref_count -= 1 possibly_finalize_transaction(transaction) wrapped_callback = _nr_wrapper_decrementer(callback) # Replace callback with one that will decrement the ref_count # when it runs. if len(args) > 1: args = list(args) args[1] = wrapped_callback else: kwargs['callback'] = wrapped_callback return wrapped(*args, **kwargs)
def _wrap_decorated(wrapped, instance, args, kwargs): # Wraps the output of a tornado decorator. # # For an example see our wrapper for coroutine, # _nr_wrapper_coroutine_wrapper_. transaction = retrieve_current_transaction() if transaction is None: return wrapped(*args, **kwargs) name = callable_name(wrapped) with FunctionTrace(transaction, name=name): return wrapped(*args, **kwargs)
def _nr_wrapper_stack_context_wrap_(wrapped, instance, args, kwargs): # Lots of wrapping going on here. There's the original function, and # then 2 layers of wrapping around it. # # unwrapped_fxn (unwrapped original): # The original function passed into `stack_context.wrap()`. # # wrapped_fxn (wrapped by Tornado): # The resulting function after `unwrapped_fxn` has been wrapped by # `stack_context.wrap()`. # # transaction_aware_fxn (wrapped by NR agent): # The resulting function after our `create_transaction_aware_fxn()` # has wrapped `wrapped_fxn` and associated it with the current # transaction. def _fxn_arg_extractor(fn, *args, **kwargs): # fn is the name of the callable argument in stack_context.wrap return fn unwrapped_fxn = _fxn_arg_extractor(*args, **kwargs) wrapped_fxn = wrapped(*args, **kwargs) should_trace = not hasattr(unwrapped_fxn, '_nr_last_object') # There are circumstances we want to make a function transaction aware # even if we don't want to trace it. If a function is decorated with # @function_trace, it will be traced but it will not yet be linked to a # transaction. Calling create_transaction_aware_fxn with # `should_trace=False` will link the transaction to the function without # re-tracing it. transaction_aware_fxn = create_transaction_aware_fxn( wrapped_fxn, fxn_for_name=unwrapped_fxn, should_trace=should_trace) if transaction_aware_fxn is None: return wrapped_fxn # To prevent stack_context.wrap from re-wrapping this function we attach # Tornado's attribute indicating the function was wrapped here. transaction_aware_fxn._wrapped = True # To prevent us from re-wrapping and to associate the transaction with the # function, we attach the transaction as an attribute. transaction_aware_fxn._nr_transaction = retrieve_current_transaction() return transaction_aware_fxn
def _nr_wrapper_Future_add_done_callback(wrapped, instance, args, kwargs): def _fxn_arg_extractor(fn, *args, **kwargs): return fn fxn = _fxn_arg_extractor(*args, **kwargs) should_trace = not hasattr(fxn, '_nr_last_object') # In Tornado, the coroutine runs inside of a lambda or a closure. In this # case, we don't know the name of the actual function until it's run. The # name is passed as an attribute on the future. if hasattr(instance, '_nr_coroutine_name'): old_fxn = fxn def new_fxn(*_args, **_kwargs): return old_fxn(*_args, **_kwargs) fxn = new_fxn fxn._nr_coroutine_name = instance._nr_coroutine_name # Clear name off of future in case the future is reused delattr(instance, '_nr_coroutine_name') transaction_aware_fxn = create_transaction_aware_fxn( fxn, should_trace=should_trace) # If transaction_aware_fxn is None then it is already wrapped, or the fxn # is None. if transaction_aware_fxn is None: return wrapped(*args, **kwargs) transaction = retrieve_current_transaction() transaction_aware_fxn._nr_transaction = transaction # We replace the function we call in the callback with the transaction # aware version of the function. if len(args) > 0: args = list(args) args[0] = transaction_aware_fxn else: # Keyword argument name for the callable function is 'fn'. kwargs['fn'] = transaction_aware_fxn return wrapped(*args, **kwargs)
def _nr_wrapper_httpclient_AsyncHTTPClient_fetch_(wrapped, instance, args, kwargs): transaction = retrieve_current_transaction() if transaction is None: return wrapped(*args, **kwargs) req, _cb, _raise_error = _prepare_request(*args, **kwargs) # Prepare outgoing CAT headers outgoing_headers = ExternalTrace.generate_request_headers(transaction) for header_name, header_value in outgoing_headers: req.headers[header_name] = header_value trace = ExternalTrace(transaction, 'tornado.httpclient', req.url) def external_trace_done(future): exc_info = future.exc_info() if exc_info: trace.__exit__(*exc_info) else: response = future.result() # Process CAT response headers trace.process_response_headers(response.headers.get_all()) trace.__exit__(None, None, None) transaction._ref_count -= 1 transaction._ref_count += 1 trace.__enter__() # Because traces are terminal but can be generated concurrently in # tornado, pop the trace immediately after entering. if trace.transaction and trace.transaction.current_node is trace: trace.transaction._pop_current(trace) try: future = wrapped(req, _cb, _raise_error) future.add_done_callback(external_trace_done) except Exception: transaction._ref_count -= 1 trace.__exit__(*sys.exc_info()) raise return future
def _nr_wrapper_Runner__init__(wrapped, instance, args, kwargs): # We want to associate a function name from _make_coroutine_wrapper with # a call to Runner.__init__. This way we know the name of the function # running in Runner and can associate metrics with it. # One strategy would be to store the function name on the transaction. # This can be problematic because an asynchronous/blocking call can occur # in _make_coroutine_wrapper.wrapper before the call to Runner.__init__. # This means other coroutines could run in the transaction between when we # record the function name in the transaction and before Runner.__init__ # is called. Since these coroutines run asynchronously and don't nest, we # can't use a stack to keep track of which _make_coroutine_wrapper.wrapper # call belongs to which Runner.__init__ call. # Instead of storing the name in the transaction, we will look up the call # stack to get the function name. transaction = retrieve_current_transaction() if transaction is None: return wrapped(*args, **kwargs) try: # The only place in the Tornado code where a Runner is instantiated # is in _make_coroutine_wrapper in Tornado's gen.py. We look up the # stack from this call to __init__ to get the function name. frame = get_frame(1) except ValueError: _logger.debug( 'tornado.gen.Runner is being created at the top of the ' 'stack. That means the Runner object is being created outside ' 'of a tornado.gen decorator. NewRelic will not be able to ' 'name this instrumented function meaningfully (it will be ' 'named lambda.') return wrapped(*args, **kwargs) # In Python 2 we look up one frame. In Python 3 and PyPy, our wrapping # gets in the way and we have to look up 2 frames. In general, we want # to go up the call stack until we first encounter the tornado.gen # module. We restrict our search to 5 frames. max_frame_depth = 5 frame_depth = 1 while frame and frame_depth <= max_frame_depth: if frame.f_globals['__name__'] == 'tornado.gen': break frame = frame.f_back frame_depth += 1 # Verify that we are in the frame we think we are. if ('__name__' in frame.f_globals and frame.f_globals['__name__'] == 'tornado.gen' and 'func' in frame.f_locals and 'replace_callback' in frame.f_locals and frame.f_code.co_name == 'wrapper'): instance._nr_coroutine_name = _coroutine_name(frame.f_locals['func']) else: _logger.debug( 'tornado.gen.Runner is being called outside of a ' 'tornado.gen decorator (or the tornado implemenation has ' 'changed). NewRelic will not be able to name this instrumented' ' function meaningfully (it will be named lambda or inner).') # Bump the ref count, so we don't end the transaction before # the Runner finishes. transaction._ref_count += 1 return wrapped(*args, **kwargs)