Skip to content

Commit

Permalink
Merge branch 'develop-scikitlearn' into add-oltp-protocol-class
Browse files Browse the repository at this point in the history
  • Loading branch information
mergify[bot] authored Jun 8, 2023
2 parents 8ae5ec1 + e970884 commit d4816fa
Show file tree
Hide file tree
Showing 13 changed files with 583 additions and 6 deletions.
8 changes: 8 additions & 0 deletions newrelic/api/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,14 @@ def record_custom_metrics(self, metrics):
if self.active and metrics:
self._agent.record_custom_metrics(self._name, metrics)

def record_dimensional_metric(self, name, value, tags=None):
if self.active:
self._agent.record_dimensional_metric(self._name, name, value, tags)

def record_dimensional_metrics(self, metrics):
if self.active and metrics:
self._agent.record_dimensional_metrics(self._name, metrics)

def record_custom_event(self, event_type, params):
if self.active:
self._agent.record_custom_event(self._name, event_type, params)
Expand Down
52 changes: 51 additions & 1 deletion newrelic/api/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
from newrelic.core.custom_event import create_custom_event
from newrelic.core.log_event_node import LogEventNode
from newrelic.core.stack_trace import exception_stack
from newrelic.core.stats_engine import CustomMetrics, SampledDataSet
from newrelic.core.stats_engine import CustomMetrics, DimensionalMetrics, SampledDataSet
from newrelic.core.thread_utilization import utilization_tracker
from newrelic.core.trace_cache import (
TraceCacheActiveTraceError,
Expand Down Expand Up @@ -309,6 +309,7 @@ def __init__(self, application, enabled=None, source=None):
self.synthetics_header = None

self._custom_metrics = CustomMetrics()
self._dimensional_metrics = DimensionalMetrics()

global_settings = application.global_settings

Expand Down Expand Up @@ -591,6 +592,7 @@ def __exit__(self, exc, value, tb):
apdex_t=self.apdex,
suppress_apdex=self.suppress_apdex,
custom_metrics=self._custom_metrics,
dimensional_metrics=self._dimensional_metrics,
guid=self.guid,
cpu_time=self._cpu_user_time_value,
suppress_transaction_trace=self.suppress_transaction_trace,
Expand Down Expand Up @@ -1597,6 +1599,16 @@ def record_custom_metrics(self, metrics):
for name, value in metrics:
self._custom_metrics.record_custom_metric(name, value)

def record_dimensional_metric(self, name, value, tags=None):
self._dimensional_metrics.record_dimensional_metric(name, value, tags)

def record_dimensional_metrics(self, metrics):
for metric in metrics:
name, value = metric[:2]
tags = metric[2] if len(metric) >= 3 else None

self._dimensional_metrics.record_dimensional_metric(name, value, tags)

def record_custom_event(self, event_type, params):
settings = self._settings

Expand Down Expand Up @@ -1908,6 +1920,44 @@ def record_custom_metrics(metrics, application=None):
application.record_custom_metrics(metrics)


def record_dimensional_metric(name, value, tags=None, application=None):
if application is None:
transaction = current_transaction()
if transaction:
transaction.record_dimensional_metric(name, value, tags)
else:
_logger.debug(
"record_dimensional_metric has been called but no "
"transaction was running. As a result, the following metric "
"has not been recorded. Name: %r Value: %r Tags: %r. To correct this "
"problem, supply an application object as a parameter to this "
"record_dimensional_metrics call.",
name,
value,
tags,
)
elif application.enabled:
application.record_dimensional_metric(name, value, tags)


def record_dimensional_metrics(metrics, application=None):
if application is None:
transaction = current_transaction()
if transaction:
transaction.record_dimensional_metrics(metrics)
else:
_logger.debug(
"record_dimensional_metrics has been called but no "
"transaction was running. As a result, the following metrics "
"have not been recorded: %r. To correct this problem, "
"supply an application object as a parameter to this "
"record_dimensional_metric call.",
list(metrics),
)
elif application.enabled:
application.record_dimensional_metrics(metrics)


def record_custom_event(event_type, params, application=None):
"""Record a custom event.
Expand Down
35 changes: 35 additions & 0 deletions newrelic/common/metric_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# 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.

"""
This module implements functions for creating a unique identity from a name and set of tags for use in dimensional metrics.
"""

from newrelic.core.attribute import process_user_attribute


def create_metric_identity(name, tags=None):
if tags:
# Convert dicts to an iterable of tuples, other iterables should already be in this form
if isinstance(tags, dict):
tags = tags.items()

# Apply attribute system sanitization.
# process_user_attribute returns (None, None) for results that fail sanitization.
# The filter removes these results from the iterable before creating the frozenset.
tags = frozenset(filter(lambda args: args[0] is not None, map(lambda args: process_user_attribute(*args), tags)))

tags = tags or None # Set empty iterables after filtering to None

return (name, tags)
27 changes: 27 additions & 0 deletions newrelic/core/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,33 @@ def record_custom_metrics(self, app_name, metrics):

application.record_custom_metrics(metrics)

def record_dimensional_metric(self, app_name, name, value, tags=None):
"""Records a basic metric for the named application. If there has
been no prior request to activate the application, the metric is
discarded.
"""

application = self._applications.get(app_name, None)
if application is None or not application.active:
return

application.record_dimensional_metric(name, value, tags)

def record_dimensional_metrics(self, app_name, metrics):
"""Records the metrics for the named application. If there has
been no prior request to activate the application, the metric is
discarded. The metrics should be an iterable yielding tuples
consisting of the name and value.
"""

application = self._applications.get(app_name, None)
if application is None or not application.active:
return

application.record_dimensional_metrics(metrics)

def record_custom_event(self, app_name, event_type, params):
application = self._applications.get(app_name, None)
if application is None or not application.active:
Expand Down
50 changes: 50 additions & 0 deletions newrelic/core/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,9 @@ def connect_to_data_collector(self, activate_agent):
with self._stats_custom_lock:
self._stats_custom_engine.reset_stats(configuration)

with self._stats_lock:
self._stats_engine.reset_stats(configuration)

# Record an initial start time for the reporting period and
# clear record of last transaction processed.

Expand Down Expand Up @@ -860,6 +863,50 @@ def record_custom_metrics(self, metrics):
self._global_events_account += 1
self._stats_custom_engine.record_custom_metric(name, value)

def record_dimensional_metric(self, name, value, tags=None):
"""Record a dimensional metric against the application independent
of a specific transaction.
NOTE that this will require locking of the stats engine for
dimensional metrics and so under heavy use will have performance
issues. It is better to record the dimensional metric against an
active transaction as they will then be aggregated at the end of
the transaction when all other metrics are aggregated and so no
additional locking will be required.
"""

if not self._active_session:
return

with self._stats_lock:
self._global_events_account += 1
self._stats_engine.record_dimensional_metric(name, value, tags)

def record_dimensional_metrics(self, metrics):
"""Record a set of dimensional metrics against the application
independent of a specific transaction.
NOTE that this will require locking of the stats engine for
dimensional metrics and so under heavy use will have performance
issues. It is better to record the dimensional metric against an
active transaction as they will then be aggregated at the end of
the transaction when all other metrics are aggregated and so no
additional locking will be required.
"""

if not self._active_session:
return

with self._stats_lock:
for metric in metrics:
name, value = metric[:2]
tags = metric[2] if len(metric) >= 3 else None

self._global_events_account += 1
self._stats_engine.record_dimensional_metric(name, value, tags)

def record_custom_event(self, event_type, params):
if not self._active_session:
return
Expand Down Expand Up @@ -1452,11 +1499,14 @@ def harvest(self, shutdown=False, flexible=False):
_logger.debug("Normalizing metrics for harvest of %r.", self._app_name)

metric_data = stats.metric_data(metric_normalizer)
dimensional_metric_data = stats.dimensional_metric_data(metric_normalizer)

_logger.debug("Sending metric data for harvest of %r.", self._app_name)

# Send metrics
self._active_session.send_metric_data(self._period_start, period_end, metric_data)
if dimensional_metric_data:
self._active_session.send_dimensional_metric_data(self._period_start, period_end, dimensional_metric_data)

_logger.debug("Done sending data for harvest of %r.", self._app_name)

Expand Down
21 changes: 21 additions & 0 deletions newrelic/core/data_collector.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@

_logger = logging.getLogger(__name__)

DIMENSIONAL_METRIC_DATA_TEMP = [] # TODO: REMOVE THIS


class Session(object):
PROTOCOL = AgentProtocol
Expand Down Expand Up @@ -143,6 +145,25 @@ def send_metric_data(self, start_time, end_time, metric_data):
payload = (self.agent_run_id, start_time, end_time, metric_data)
return self._protocol.send("metric_data", payload)

def send_dimensional_metric_data(self, start_time, end_time, metric_data):
"""Called to submit dimensional metric data for specified period of time.
Time values are seconds since UNIX epoch as returned by the
time.time() function. The metric data should be iterable of
specific metrics.
NOTE: This data is sent not sent to the normal agent endpoints but is sent
to the MELT API endpoints to keep the entity separate. This is for use
with the machine learning integration only.
"""

payload = (self.agent_run_id, start_time, end_time, metric_data)
# return self._protocol.send("metric_data", payload)

# TODO: REMOVE THIS. Replace with actual protocol.
DIMENSIONAL_METRIC_DATA_TEMP.append(payload)
_logger.debug("Dimensional Metrics: %r" % metric_data)
return 200

def send_log_events(self, sampling_info, log_event_data):
"""Called to submit sample set for log events."""

Expand Down
Loading

0 comments on commit d4816fa

Please sign in to comment.