Skip to content

Commit dc81a50

Browse files
committed
Wiring dimensional metrics
1 parent caef2cc commit dc81a50

8 files changed

+342
-2
lines changed

newrelic/api/application.py

+8
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,14 @@ def record_custom_metrics(self, metrics):
142142
if self.active and metrics:
143143
self._agent.record_custom_metrics(self._name, metrics)
144144

145+
def record_dimensional_metric(self, name, value, tags=None):
146+
if self.active:
147+
self._agent.record_dimensional_metric(self._name, name, value, tags)
148+
149+
def record_dimensional_metrics(self, metrics):
150+
if self.active and metrics:
151+
self._agent.record_dimensional_metrics(self._name, metrics)
152+
145153
def record_custom_event(self, event_type, params):
146154
if self.active:
147155
self._agent.record_custom_event(self._name, event_type, params)

newrelic/api/transaction.py

+51-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
from newrelic.core.custom_event import create_custom_event
6565
from newrelic.core.log_event_node import LogEventNode
6666
from newrelic.core.stack_trace import exception_stack
67-
from newrelic.core.stats_engine import CustomMetrics, SampledDataSet
67+
from newrelic.core.stats_engine import CustomMetrics, DimensionalMetrics, SampledDataSet
6868
from newrelic.core.thread_utilization import utilization_tracker
6969
from newrelic.core.trace_cache import (
7070
TraceCacheActiveTraceError,
@@ -307,6 +307,7 @@ def __init__(self, application, enabled=None, source=None):
307307
self.synthetics_header = None
308308

309309
self._custom_metrics = CustomMetrics()
310+
self._dimensional_metrics = DimensionalMetrics()
310311

311312
global_settings = application.global_settings
312313

@@ -588,6 +589,7 @@ def __exit__(self, exc, value, tb):
588589
apdex_t=self.apdex,
589590
suppress_apdex=self.suppress_apdex,
590591
custom_metrics=self._custom_metrics,
592+
dimensional_metrics=self._dimensional_metrics,
591593
guid=self.guid,
592594
cpu_time=self._cpu_user_time_value,
593595
suppress_transaction_trace=self.suppress_transaction_trace,
@@ -1600,6 +1602,16 @@ def record_custom_metrics(self, metrics):
16001602
for name, value in metrics:
16011603
self._custom_metrics.record_custom_metric(name, value)
16021604

1605+
def record_dimensional_metric(self, name, value, tags=None):
1606+
self._dimensional_metrics.record_dimensional_metric(name, value, tags)
1607+
1608+
def record_dimensional_metrics(self, metrics):
1609+
for metric in metrics:
1610+
name, value = metric[:2]
1611+
tags = metric[2] if len(metric) >= 3 else None
1612+
1613+
self._dimensional_metrics.record_dimensional_metric(name, value, tags)
1614+
16031615
def record_custom_event(self, event_type, params):
16041616
settings = self._settings
16051617

@@ -1898,6 +1910,44 @@ def record_custom_metrics(metrics, application=None):
18981910
application.record_custom_metrics(metrics)
18991911

19001912

1913+
def record_dimensional_metric(name, value, tags=None, application=None):
1914+
if application is None:
1915+
transaction = current_transaction()
1916+
if transaction:
1917+
transaction.record_dimensional_metric(name, value, tags)
1918+
else:
1919+
_logger.debug(
1920+
"record_dimensional_metric has been called but no "
1921+
"transaction was running. As a result, the following metric "
1922+
"has not been recorded. Name: %r Value: %r Tags: %r. To correct this "
1923+
"problem, supply an application object as a parameter to this "
1924+
"record_dimensional_metrics call.",
1925+
name,
1926+
value,
1927+
tags,
1928+
)
1929+
elif application.enabled:
1930+
application.record_dimensional_metric(name, value, tags)
1931+
1932+
1933+
def record_dimensional_metrics(metrics, application=None):
1934+
if application is None:
1935+
transaction = current_transaction()
1936+
if transaction:
1937+
transaction.record_dimensional_metrics(metrics)
1938+
else:
1939+
_logger.debug(
1940+
"record_dimensional_metrics has been called but no "
1941+
"transaction was running. As a result, the following metrics "
1942+
"have not been recorded: %r. To correct this problem, "
1943+
"supply an application object as a parameter to this "
1944+
"record_dimensional_metric call.",
1945+
list(metrics),
1946+
)
1947+
elif application.enabled:
1948+
application.record_dimensional_metrics(metrics)
1949+
1950+
19011951
def record_custom_event(event_type, params, application=None):
19021952
"""Record a custom event.
19031953

newrelic/common/metric_utils.py

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Copyright 2010 New Relic, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""
16+
This module implements functions for creating a unique identity from a name and set of tags for use in dimensional metrics.
17+
"""
18+
19+
from newrelic.packages import six
20+
21+
22+
def create_metric_identity(name, tags=None):
23+
if tags:
24+
if isinstance(tags, dict):
25+
tags = frozenset(six.iteritems(tags)) if tags is not None else None
26+
elif not isinstance(tags, frozenset):
27+
tags = frozenset(tags)
28+
elif tags is not None:
29+
tags = None # Set empty iterables to None
30+
31+
return (name, tags)

newrelic/core/agent.py

+27
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,33 @@ def record_custom_metrics(self, app_name, metrics):
524524

525525
application.record_custom_metrics(metrics)
526526

527+
def record_dimensional_metric(self, app_name, name, value, tags=None):
528+
"""Records a basic metric for the named application. If there has
529+
been no prior request to activate the application, the metric is
530+
discarded.
531+
532+
"""
533+
534+
application = self._applications.get(app_name, None)
535+
if application is None or not application.active:
536+
return
537+
538+
application.record_dimensional_metric(name, value, tags)
539+
540+
def record_dimensional_metrics(self, app_name, metrics):
541+
"""Records the metrics for the named application. If there has
542+
been no prior request to activate the application, the metric is
543+
discarded. The metrics should be an iterable yielding tuples
544+
consisting of the name and value.
545+
546+
"""
547+
548+
application = self._applications.get(app_name, None)
549+
if application is None or not application.active:
550+
return
551+
552+
application.record_dimensional_metrics(metrics)
553+
527554
def record_custom_event(self, app_name, event_type, params):
528555
application = self._applications.get(app_name, None)
529556
if application is None or not application.active:

newrelic/core/application.py

+53
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,9 @@ def __init__(self, app_name, linked_applications=None):
106106
self._stats_custom_lock = threading.RLock()
107107
self._stats_custom_engine = StatsEngine()
108108

109+
self._stats_dimensional_lock = threading.RLock()
110+
self._stats_dimensional_engine = StatsEngine()
111+
109112
self._agent_commands_lock = threading.Lock()
110113
self._data_samplers_lock = threading.Lock()
111114
self._data_samplers_started = False
@@ -510,6 +513,9 @@ def connect_to_data_collector(self, activate_agent):
510513
with self._stats_custom_lock:
511514
self._stats_custom_engine.reset_stats(configuration)
512515

516+
with self._stats_dimensional_lock:
517+
self._stats_dimensional_engine.reset_stats(configuration)
518+
513519
# Record an initial start time for the reporting period and
514520
# clear record of last transaction processed.
515521

@@ -860,6 +866,50 @@ def record_custom_metrics(self, metrics):
860866
self._global_events_account += 1
861867
self._stats_custom_engine.record_custom_metric(name, value)
862868

869+
def record_dimensional_metric(self, name, value, tags=None):
870+
"""Record a dimensional metric against the application independent
871+
of a specific transaction.
872+
873+
NOTE that this will require locking of the stats engine for
874+
dimensional metrics and so under heavy use will have performance
875+
issues. It is better to record the dimensional metric against an
876+
active transaction as they will then be aggregated at the end of
877+
the transaction when all other metrics are aggregated and so no
878+
additional locking will be required.
879+
880+
"""
881+
882+
if not self._active_session:
883+
return
884+
885+
with self._stats_dimensional_lock:
886+
self._global_events_account += 1
887+
self._stats_dimensional_engine.record_dimensional_metric(name, value, tags)
888+
889+
def record_dimensional_metrics(self, metrics):
890+
"""Record a set of dimensional metrics against the application
891+
independent of a specific transaction.
892+
893+
NOTE that this will require locking of the stats engine for
894+
dimensional metrics and so under heavy use will have performance
895+
issues. It is better to record the dimensional metric against an
896+
active transaction as they will then be aggregated at the end of
897+
the transaction when all other metrics are aggregated and so no
898+
additional locking will be required.
899+
900+
"""
901+
902+
if not self._active_session:
903+
return
904+
905+
with self._stats_dimensional_lock:
906+
for metric in metrics:
907+
name, value = metric[:2]
908+
tags = metric[2] if len(metric) >= 3 else None
909+
910+
self._global_events_account += 1
911+
self._stats_dimensional_engine.record_dimensional_metric(name, value, tags)
912+
863913
def record_custom_event(self, event_type, params):
864914
if not self._active_session:
865915
return
@@ -1416,11 +1466,14 @@ def harvest(self, shutdown=False, flexible=False):
14161466
_logger.debug("Normalizing metrics for harvest of %r.", self._app_name)
14171467

14181468
metric_data = stats.metric_data(metric_normalizer)
1469+
dimensional_metric_data = stats.dimensional_metric_data(metric_normalizer)
14191470

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

14221473
# Send metrics
14231474
self._active_session.send_metric_data(self._period_start, period_end, metric_data)
1475+
if dimensional_metric_data:
1476+
self._active_session.send_dimensional_metric_data(self._period_start, period_end, dimensional_metric_data)
14241477

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

newrelic/core/data_collector.py

+21
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131

3232
_logger = logging.getLogger(__name__)
3333

34+
DIMENSIONAL_METRIC_DATA_TEMP = [] # TODO: REMOVE THIS
35+
3436

3537
class Session(object):
3638
PROTOCOL = AgentProtocol
@@ -128,6 +130,25 @@ def send_metric_data(self, start_time, end_time, metric_data):
128130
payload = (self.agent_run_id, start_time, end_time, metric_data)
129131
return self._protocol.send("metric_data", payload)
130132

133+
def send_dimensional_metric_data(self, start_time, end_time, metric_data):
134+
"""Called to submit dimensional metric data for specified period of time.
135+
Time values are seconds since UNIX epoch as returned by the
136+
time.time() function. The metric data should be iterable of
137+
specific metrics.
138+
139+
NOTE: This data is sent not sent to the normal agent endpoints but is sent
140+
to the MELT API endpoints to keep the entity separate. This is for use
141+
with the machine learning integration only.
142+
"""
143+
144+
payload = (self.agent_run_id, start_time, end_time, metric_data)
145+
# return self._protocol.send("metric_data", payload)
146+
147+
# TODO: REMOVE THIS. Replace with actual protocol.
148+
DIMENSIONAL_METRIC_DATA_TEMP.append(payload)
149+
_logger.debug("Dimensional Metrics: %r" % metric_data)
150+
return 200
151+
131152
def send_log_events(self, sampling_info, log_event_data):
132153
"""Called to submit sample set for log events."""
133154

0 commit comments

Comments
 (0)