Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add sklearn instrumentation and ML model feature support #921

Merged
merged 65 commits into from
Sep 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
8e0175b
Add tree model function traces (#691)
hmstepanek Dec 6, 2022
1036f0f
Merge branch 'main' into develop-scikitlearn
hmstepanek Dec 6, 2022
2e9a83b
Add config setting for sklearn inference event capture. (#706)
umaannamalai Dec 7, 2022
9b00bf0
Merge branch 'main' into develop-scikitlearn
hmstepanek Dec 7, 2022
9df29a9
Capture scorer results (#694)
hmstepanek Dec 8, 2022
c6a9d4c
Add ensemble model function traces (#697)
lrafeei Dec 13, 2022
d9d5636
Include training step in metric scorer name (#712)
hmstepanek Dec 16, 2022
f33d21e
Add cluster model function traces (#700)
lrafeei Dec 20, 2022
e3f43f2
Add calibration model function traces (#709)
lrafeei Dec 21, 2022
43d5c51
Merge branch 'main' into develop-scikitlearn
lrafeei Jan 4, 2023
beedc9c
Add svm model function traces (#733)
lrafeei Jan 6, 2023
22952d3
Add semi supervised models (#732)
lrafeei Jan 6, 2023
90b7dc1
Add pipeline model function traces (#730)
lrafeei Jan 6, 2023
d785977
Add neural network model function traces (#729)
lrafeei Jan 6, 2023
49a22ec
Add neighbors models (#728)
lrafeei Jan 6, 2023
c4f1157
Add mixture models (#725)
lrafeei Jan 6, 2023
8839131
Add model selection model function traces (#726)
lrafeei Jan 6, 2023
5a50130
Add naive bayes models (#724)
lrafeei Jan 7, 2023
734fa2a
Add multioutput models (#723)
lrafeei Jan 7, 2023
58f02de
Add multiclass models (#722)
lrafeei Jan 7, 2023
f880cde
Add kernel ridge model function traces (#721)
lrafeei Jan 8, 2023
0d56315
Add custom feature events for sklearn (#727)
umaannamalai Jan 9, 2023
cc15c43
Add feature selection model function traces (#719)
lrafeei Jan 9, 2023
bdea25d
Add dummy model function traces (#718)
lrafeei Jan 9, 2023
3ad3096
Add gaussian process model function traces (#720)
lrafeei Jan 9, 2023
22b9588
Add discriminant analysis model (#717)
lrafeei Jan 9, 2023
600a0f1
Add cross decomposition models (#716)
lrafeei Jan 10, 2023
178a000
Add covariance model (#714)
lrafeei Jan 10, 2023
b9ceef4
Merge branch 'main' into develop-scikitlearn
lrafeei Jan 11, 2023
e9ce7f7
Add compose models (#713)
lrafeei Jan 11, 2023
db76aca
Add linear model function traces (#703)
lrafeei Jan 12, 2023
6273e4d
Add ml_model function wrapper API (#739)
umaannamalai Jan 25, 2023
1ed0605
Report feature event w/o value (#754)
hmstepanek Jan 27, 2023
bb90384
Merge branch 'main' into develop-scikitlearn
hmstepanek Feb 13, 2023
ba3584e
Prediction metric stats (#715)
hmstepanek Feb 15, 2023
b6218d0
Merge branch 'main' into develop-scikitlearn
hmstepanek Apr 25, 2023
e8a4078
Merge branch 'main' into develop-scikitlearn
hmstepanek May 1, 2023
1b706a3
Add new ML event type (#802)
hmstepanek May 2, 2023
d10c9f7
Add machine learning and ml_event config options (#811)
hmstepanek May 5, 2023
a726625
Merge branch 'main' into develop-scikitlearn
hmstepanek Jun 2, 2023
e970884
Dimensional Metrics (#815)
TimPansino Jun 8, 2023
30f0bf5
Add OTLP protocol class & protos (#821)
hmstepanek Jun 9, 2023
4876a98
Fix Testing Failures (#828)
TimPansino Jun 12, 2023
2f6581f
Fix pytest test filtering when running tox (#823)
hmstepanek Jun 12, 2023
a69a704
Merge branch 'main' into develop-scikitlearn
hmstepanek Jun 21, 2023
ad05014
OTLP Serialization for Dimensional Metrics (#826)
TimPansino Jun 23, 2023
cccd7f4
Fix attribute name mismatches from mlops sdk (#845)
hmstepanek Jun 23, 2023
e94d3d3
Backport main into develop-scikitlearn (#847)
TimPansino Jun 26, 2023
f2ac729
Use Dimensional Metrics in SKLearn (#850)
TimPansino Jun 27, 2023
7175c29
Merge branch 'main' into develop-scikitlearn
hmstepanek Jun 27, 2023
f6ec42e
Fixup dependency pinning
hmstepanek Jun 27, 2023
8e45f4d
Hook up ml event to OTLP (#822)
hmstepanek Jun 27, 2023
db915d0
Fix OTLP Count Metric Serialization (#856)
TimPansino Jun 28, 2023
c77adce
Merge main (#874)
hmstepanek Jul 31, 2023
c043782
Only send 1 event per prediction & fix tests (#867)
hmstepanek Aug 2, 2023
00dc996
Merge redis changes mlops (#914)
lrafeei Aug 31, 2023
127ad0c
Make inferences a separate event (#910)
lrafeei Aug 31, 2023
284e3c4
Fix logic bug in warning (#915)
hmstepanek Aug 31, 2023
8d38e26
Add wrap_mlmodel to newrelic.agent (#920)
umaannamalai Sep 14, 2023
08fd063
Merge main into mlops (#912)
lrafeei Sep 14, 2023
709227d
Fix merge conflicts for develop scikitlearn (#922)
TimPansino Sep 14, 2023
fe749ff
Merge branch 'main' into develop-scikitlearn
TimPansino Sep 14, 2023
86f0df8
Add third party notice for opentelemetry
TimPansino Sep 14, 2023
3465cdd
Disable Sending of ML Events by Default (#923)
umaannamalai Sep 20, 2023
42af773
Disable sending of ML inference events by default. (#924)
umaannamalai Sep 20, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ include newrelic/packages/wrapt/LICENSE
include newrelic/packages/wrapt/README
include newrelic/packages/urllib3/LICENSE.txt
include newrelic/packages/isort/LICENSE
include newrelic/packages/opentelemetry_proto/LICENSE.txt
9 changes: 9 additions & 0 deletions THIRD_PARTY_NOTICES.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ Distributed under the following license(s):
* [The MIT License](http://opensource.org/licenses/MIT)


## [opentelemetry-proto](https://pypi.org/project/opentelemetry-proto)

Copyright (c) The OpenTelemetry Authors

Distributed under the following license(s):

* [The Apache License, Version 2.0 License](https://opensource.org/license/apache-2-0/)


## [six](https://pypi.org/project/six)

Copyright (c) 2010-2013 Benjamin Peterson
Expand Down
9 changes: 4 additions & 5 deletions newrelic/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
from newrelic.api.transaction import record_custom_metric as __record_custom_metric
from newrelic.api.transaction import record_custom_metrics as __record_custom_metrics
from newrelic.api.transaction import record_log_event as __record_log_event
from newrelic.api.transaction import record_ml_event as __record_ml_event
from newrelic.api.transaction import set_background_task as __set_background_task
from newrelic.api.transaction import set_transaction_name as __set_transaction_name
from newrelic.api.transaction import suppress_apdex_metric as __suppress_apdex_metric
Expand Down Expand Up @@ -152,6 +153,7 @@ def __asgi_application(*args, **kwargs):
from newrelic.api.message_transaction import (
wrap_message_transaction as __wrap_message_transaction,
)
from newrelic.api.ml_model import wrap_mlmodel as __wrap_mlmodel
from newrelic.api.profile_trace import ProfileTraceWrapper as __ProfileTraceWrapper
from newrelic.api.profile_trace import profile_trace as __profile_trace
from newrelic.api.profile_trace import wrap_profile_trace as __wrap_profile_trace
Expand Down Expand Up @@ -206,11 +208,6 @@ def __asgi_application(*args, **kwargs):
# EXPERIMENTAL - Generator traces are currently experimental and may not
# exist in this form in future versions of the agent.


# EXPERIMENTAL - Profile traces are currently experimental and may not
# exist in this form in future versions of the agent.


initialize = __initialize
extra_settings = __wrap_api_call(__extra_settings, "extra_settings")
global_settings = __wrap_api_call(__global_settings, "global_settings")
Expand Down Expand Up @@ -248,6 +245,7 @@ def __asgi_application(*args, **kwargs):
record_custom_metrics = __wrap_api_call(__record_custom_metrics, "record_custom_metrics")
record_custom_event = __wrap_api_call(__record_custom_event, "record_custom_event")
record_log_event = __wrap_api_call(__record_log_event, "record_log_event")
record_ml_event = __wrap_api_call(__record_ml_event, "record_ml_event")
accept_distributed_trace_payload = __wrap_api_call(
__accept_distributed_trace_payload, "accept_distributed_trace_payload"
)
Expand Down Expand Up @@ -341,3 +339,4 @@ def __asgi_application(*args, **kwargs):
wrap_out_function = __wrap_api_call(__wrap_out_function, "wrap_out_function")
insert_html_snippet = __wrap_api_call(__insert_html_snippet, "insert_html_snippet")
verify_body_exists = __wrap_api_call(__verify_body_exists, "verify_body_exists")
wrap_mlmodel = __wrap_api_call(__wrap_mlmodel, "wrap_mlmodel")
12 changes: 12 additions & 0 deletions newrelic/api/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,22 @@ 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)

def record_ml_event(self, event_type, params):
if self.active:
self._agent.record_ml_event(self._name, event_type, params)

def record_transaction(self, data):
if self.active:
self._agent.record_transaction(self._name, data)
Expand Down
35 changes: 35 additions & 0 deletions newrelic/api/ml_model.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.

import sys

from newrelic.common.object_names import callable_name
from newrelic.hooks.mlmodel_sklearn import _nr_instrument_model


def wrap_mlmodel(model, name=None, version=None, feature_names=None, label_names=None, metadata=None):
model_callable_name = callable_name(model)
_class = model.__class__.__name__
module = sys.modules[model_callable_name.split(":")[0]]
_nr_instrument_model(module, _class)
if name:
model._nr_wrapped_name = name
if version:
model._nr_wrapped_version = version
if feature_names:
model._nr_wrapped_feature_names = feature_names
if label_names:
model._nr_wrapped_label_names = label_names
if metadata:
model._nr_wrapped_metadata = metadata
102 changes: 100 additions & 2 deletions newrelic/api/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,15 @@
DST_NONE,
DST_TRANSACTION_TRACER,
)
from newrelic.core.config import CUSTOM_EVENT_RESERVOIR_SIZE, LOG_EVENT_RESERVOIR_SIZE
from newrelic.core.config import (
CUSTOM_EVENT_RESERVOIR_SIZE,
LOG_EVENT_RESERVOIR_SIZE,
ML_EVENT_RESERVOIR_SIZE,
)
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 @@ -305,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 All @@ -328,12 +333,14 @@ def __init__(self, application, enabled=None, source=None):
self._custom_events = SampledDataSet(
capacity=self._settings.event_harvest_config.harvest_limits.custom_event_data
)
self._ml_events = SampledDataSet(capacity=self._settings.event_harvest_config.harvest_limits.ml_event_data)
self._log_events = SampledDataSet(
capacity=self._settings.event_harvest_config.harvest_limits.log_event_data
)
else:
self._custom_events = SampledDataSet(capacity=CUSTOM_EVENT_RESERVOIR_SIZE)
self._log_events = SampledDataSet(capacity=LOG_EVENT_RESERVOIR_SIZE)
self._ml_events = SampledDataSet(capacity=ML_EVENT_RESERVOIR_SIZE)

def __del__(self):
self._dead = True
Expand Down Expand Up @@ -580,10 +587,12 @@ def __exit__(self, exc, value, tb):
errors=tuple(self._errors),
slow_sql=tuple(self._slow_sql),
custom_events=self._custom_events,
ml_events=self._ml_events,
log_events=self._log_events,
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 @@ -1607,6 +1616,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 All @@ -1620,6 +1639,19 @@ def record_custom_event(self, event_type, params):
if event:
self._custom_events.add(event, priority=self.priority)

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

if not settings:
return

if not settings.ml_insights_events.enabled:
return

event = create_custom_event(event_type, params)
if event:
self._ml_events.add(event, priority=self.priority)

def _intern_string(self, value):
return self._string_cache.setdefault(value, value)

Expand Down Expand Up @@ -1913,6 +1945,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 Expand Up @@ -1941,6 +2011,34 @@ def record_custom_event(event_type, params, application=None):
application.record_custom_event(event_type, params)


def record_ml_event(event_type, params, application=None):
"""Record a machine learning custom event.

Args:
event_type (str): The type (name) of the ml event.
params (dict): Attributes to add to the event.
application (newrelic.api.Application): Application instance.

"""

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


def record_log_event(message, level=None, timestamp=None, application=None, priority=None):
"""Record a log event.

Expand Down
Loading
Loading