Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions src/sagemaker/remote_function/client.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -40,6 +40,8 @@
from sagemaker.utils import name_from_base, base_from_name
from sagemaker.remote_function.spark_config import SparkConfig
from sagemaker.remote_function.custom_file_filter import CustomFileFilter
from sagemaker.telemetry.telemetry_logging import _telemetry_emitter
from sagemaker.telemetry.constants import Feature

_API_CALL_LIMIT = {
"SubmittingIntervalInSecs": 1,
Expand All@@ -57,6 +59,7 @@
logger = logging_config.get_logger()


@_telemetry_emitter(feature=Feature.REMOTE_FUNCTION, func_name="remote_function.remote")
Comment thread
qidewenwhen marked this conversation as resolved.
def remote(
_func=None,
*,
Expand Down
1 change: 1 addition & 0 deletions src/sagemaker/telemetry/constants.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,6 +24,7 @@ class Feature(Enum):

SDK_DEFAULTS = 1
LOCAL_MODE = 2
REMOTE_FUNCTION = 3

def __str__(self): # pylint: disable=E0307
"""Return the feature name."""
Expand Down
177 changes: 102 additions & 75 deletions src/sagemaker/telemetry/telemetry_logging.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,7 +17,11 @@
import sys
from time import perf_counter
from typing import List
import functools
import requests

import boto3
from sagemaker.session import Session
from sagemaker.utils import resolve_value_from_config
from sagemaker.config.config_schema import TELEMETRY_OPT_OUT_PATH
from sagemaker.telemetry.constants import (
Expand DownExpand Up@@ -47,6 +51,7 @@
FEATURE_TO_CODE = {
str(Feature.SDK_DEFAULTS): 1,
str(Feature.LOCAL_MODE): 2,
str(Feature.REMOTE_FUNCTION): 3,
}

STATUS_TO_CODE = {
Expand All@@ -59,86 +64,103 @@ def _telemetry_emitter(feature: str, func_name: str):
"""Decorator to emit telemetry logs for SageMaker Python SDK functions"""

def decorator(func):
def wrapper(self, *args, **kwargs):
logger.info(TELEMETRY_OPT_OUT_MESSAGING)
response = None
caught_ex = None
studio_app_type = process_studio_metadata_file()

# Check if telemetry is opted out
telemetry_opt_out_flag = resolve_value_from_config(
direct_input=None,
config_path=TELEMETRY_OPT_OUT_PATH,
default_value=False,
sagemaker_session=self.sagemaker_session,
)
logger.debug("TelemetryOptOut flag is set to: %s", telemetry_opt_out_flag)

# Construct the feature list to track feature combinations
feature_list: List[int] = [FEATURE_TO_CODE[str(feature)]]
if self.sagemaker_session:
if self.sagemaker_session.sagemaker_config and feature != Feature.SDK_DEFAULTS:
@functools.wraps(func)
def wrapper(*args, **kwargs):
sagemaker_session = None
if len(args) > 0 and hasattr(args[0], "sagemaker_session"):
# Get the sagemaker_session from the instance method args
sagemaker_session = args[0].sagemaker_session
elif feature == Feature.REMOTE_FUNCTION:
# Get the sagemaker_session from the function keyword arguments for remote function
sagemaker_session = kwargs.get(
"sagemaker_session", _get_default_sagemaker_session()
)

if sagemaker_session:
logger.debug("sagemaker_session found, preparing to emit telemetry...")
logger.info(TELEMETRY_OPT_OUT_MESSAGING)
response = None
caught_ex = None
studio_app_type = process_studio_metadata_file()

# Check if telemetry is opted out
telemetry_opt_out_flag = resolve_value_from_config(
direct_input=None,
config_path=TELEMETRY_OPT_OUT_PATH,
default_value=False,
sagemaker_session=sagemaker_session,
)
logger.debug("TelemetryOptOut flag is set to: %s", telemetry_opt_out_flag)

# Construct the feature list to track feature combinations
feature_list: List[int] = [FEATURE_TO_CODE[str(feature)]]

if sagemaker_session.sagemaker_config and feature != Feature.SDK_DEFAULTS:
feature_list.append(FEATURE_TO_CODE[str(Feature.SDK_DEFAULTS)])

if self.sagemaker_session.local_mode and feature != Feature.LOCAL_MODE:
if sagemaker_session.local_mode and feature != Feature.LOCAL_MODE:
feature_list.append(FEATURE_TO_CODE[str(Feature.LOCAL_MODE)])

# Construct the extra info to track platform and environment usage metadata
extra = (
f"{func_name}"
f"&x-sdkVersion={SDK_VERSION}"
f"&x-env={PYTHON_VERSION}"
f"&x-sys={OS_NAME_VERSION}"
f"&x-platform={studio_app_type}"
)

# Add endpoint ARN to the extra info if available
if self.sagemaker_session and self.sagemaker_session.endpoint_arn:
extra += f"&x-endpointArn={self.sagemaker_session.endpoint_arn}"

start_timer = perf_counter()
try:
# Call the original function
response = func(self, *args, **kwargs)
stop_timer = perf_counter()
elapsed = stop_timer - start_timer
extra += f"&x-latency={round(elapsed, 2)}"
if not telemetry_opt_out_flag:
_send_telemetry_request(
STATUS_TO_CODE[str(Status.SUCCESS)],
feature_list,
self.sagemaker_session,
None,
None,
extra,
)
except Exception as e: # pylint: disable=W0703
stop_timer = perf_counter()
elapsed = stop_timer - start_timer
extra += f"&x-latency={round(elapsed, 2)}"
if not telemetry_opt_out_flag:
_send_telemetry_request(
STATUS_TO_CODE[str(Status.FAILURE)],
feature_list,
self.sagemaker_session,
str(e),
e.__class__.__name__,
extra,
)
caught_ex = e
finally:
if caught_ex:
raise caught_ex
return response # pylint: disable=W0150
# Construct the extra info to track platform and environment usage metadata
extra = (
f"{func_name}"
f"&x-sdkVersion={SDK_VERSION}"
f"&x-env={PYTHON_VERSION}"
f"&x-sys={OS_NAME_VERSION}"
f"&x-platform={studio_app_type}"
)

# Add endpoint ARN to the extra info if available
if sagemaker_session.endpoint_arn:
extra += f"&x-endpointArn={sagemaker_session.endpoint_arn}"

start_timer = perf_counter()
try:
# Call the original function
response = func(*args, **kwargs)
stop_timer = perf_counter()
elapsed = stop_timer - start_timer
extra += f"&x-latency={round(elapsed, 2)}"
if not telemetry_opt_out_flag:
_send_telemetry_request(
STATUS_TO_CODE[str(Status.SUCCESS)],
feature_list,
sagemaker_session,
None,
None,
extra,
)
except Exception as e: # pylint: disable=W0703
stop_timer = perf_counter()
elapsed = stop_timer - start_timer
extra += f"&x-latency={round(elapsed, 2)}"
if not telemetry_opt_out_flag:
_send_telemetry_request(
STATUS_TO_CODE[str(Status.FAILURE)],
feature_list,
sagemaker_session,
str(e),
e.__class__.__name__,
extra,
)
caught_ex = e
finally:
if caught_ex:
raise caught_ex
return response # pylint: disable=W0150
else:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: we can move the else branch on top to reduce to clean the code a little bit. For example:

if not sagemaker:
logger.debug(...)
return ...
logger.debug("sagemaker_session found, preparing to emit telemetry...")
...

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ack, Will do this in the follow-up PR for telemetry.

logger.debug(
"Unable to send telemetry for function %s. "
"sagemaker_session is not provided or not valid.",
func_name,
)
return func(*args, **kwargs)

return wrapper

return decorator


from sagemaker.session import Session # noqa: E402 pylint: disable=C0413


def _send_telemetry_request(
status: int,
feature_list: List[int],
Expand All@@ -165,9 +187,9 @@ def _send_telemetry_request(
# Send the telemetry request
logger.debug("Sending telemetry request to [%s]", url)
_requests_helper(url, 2)
logger.debug("SageMaker Python SDK telemetry successfully emitted!")
logger.debug("SageMaker Python SDK telemetry successfully emitted.")
except Exception: # pylint: disable=W0703
logger.debug("SageMaker Python SDK telemetry not emitted!!")
logger.debug("SageMaker Python SDK telemetry not emitted!")


def _construct_url(
Expand DownExpand Up@@ -196,9 +218,6 @@ def _construct_url(
return base_url


import requests # noqa: E402 pylint: disable=C0413,C0411


def _requests_helper(url, timeout):
"""Make a GET request to the given URL"""

Expand DownExpand Up@@ -227,3 +246,11 @@ def _get_region_or_default(session):
return session.boto_session.region_name
except Exception: # pylint: disable=W0703
return DEFAULT_AWS_REGION


def _get_default_sagemaker_session():
"""Return the default sagemaker session"""
boto_session = boto3.Session(region_name=DEFAULT_AWS_REGION)
sagemaker_session = Session(boto_session=boto_session)

return sagemaker_session
7 changes: 3 additions & 4 deletions tests/unit/sagemaker/remote_function/test_client.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,6 +15,7 @@
import os
import threading
import time
import inspect

import pytest
from mock import MagicMock, patch, Mock, ANY, call
Expand DownExpand Up@@ -1498,7 +1499,6 @@ def test_consistency_between_remote_and_step_decorator():
from sagemaker.workflow.function_step import step

remote_args_to_ignore = [
"_remote",
Comment thread
qidewenwhen marked this conversation as resolved.
"include_local_workdir",
"custom_file_filter",
"s3_kms_key",
Expand All@@ -1508,7 +1508,7 @@ def test_consistency_between_remote_and_step_decorator():

step_args_to_ignore = ["_step", "name", "display_name", "description", "retry_policies"]

remote_decorator_args = remote.__code__.co_varnames
remote_decorator_args = inspect.signature(remote).parameters.keys()
common_remote_decorator_args = set(remote_args_to_ignore) ^ set(remote_decorator_args)

step_decorator_args = step.__code__.co_varnames
Expand All@@ -1522,8 +1522,7 @@ def test_consistency_between_remote_and_executor():
executor_arg_list.remove("self")
executor_arg_list.remove("max_parallel_jobs")

remote_args_list = list(remote.__code__.co_varnames)
remote_args_list.remove("_remote")
remote_args_list = list(inspect.signature(remote).parameters.keys())
remote_args_list.remove("_func")

assert executor_arg_list == remote_args_list
18 changes: 18 additions & 0 deletions tests/unit/sagemaker/telemetry/test_telemetry_logging.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,6 +15,7 @@
import pytest
import requests
from unittest.mock import Mock, patch, MagicMock
import boto3
import sagemaker
from sagemaker.telemetry.constants import Feature
from sagemaker.telemetry.telemetry_logging import (
Expand All@@ -24,6 +25,7 @@
_get_accountId,
_requests_helper,
_get_region_or_default,
_get_default_sagemaker_session,
OS_NAME_VERSION,
PYTHON_VERSION,
)
Expand DownExpand Up@@ -282,3 +284,19 @@ def test_get_region_or_default_exception(self):
region = _get_region_or_default(mock_session)
assert region == "us-west-2"
assert "Error creating boto session" in str(exception)

@patch.object(boto3.Session, "region_name", "us-west-2")
def test_get_default_sagemaker_session(self):
sagemaker_session = _get_default_sagemaker_session()

assert isinstance(sagemaker_session, sagemaker.Session) is True
assert sagemaker_session.boto_session.region_name == "us-west-2"

@patch.object(boto3.Session, "region_name", None)
def test_get_default_sagemaker_session_with_no_region(self):
with self.assertRaises(ValueError) as context:
_get_default_sagemaker_session()

assert "Must setup local AWS configuration with a region supported by SageMaker." in str(
context.exception
)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions src/sagemaker/remote_function/client.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -40,6 +40,8 @@
from sagemaker.utils import name_from_base, base_from_name
from sagemaker.remote_function.spark_config import SparkConfig
from sagemaker.remote_function.custom_file_filter import CustomFileFilter
from sagemaker.telemetry.telemetry_logging import _telemetry_emitter
from sagemaker.telemetry.constants import Feature

_API_CALL_LIMIT = {
"SubmittingIntervalInSecs": 1,
Expand All@@ -57,6 +59,7 @@
logger = logging_config.get_logger()


@_telemetry_emitter(feature=Feature.REMOTE_FUNCTION, func_name="remote_function.remote")
Comment thread
qidewenwhen marked this conversation as resolved.
def remote(
_func=None,
*,
Expand Down
1 change: 1 addition & 0 deletions src/sagemaker/telemetry/constants.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,6 +24,7 @@ class Feature(Enum):

SDK_DEFAULTS = 1
LOCAL_MODE = 2
REMOTE_FUNCTION = 3

def __str__(self): # pylint: disable=E0307
"""Return the feature name."""
Expand Down
177 changes: 102 additions & 75 deletions src/sagemaker/telemetry/telemetry_logging.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,7 +17,11 @@
import sys
from time import perf_counter
from typing import List
import functools
import requests

import boto3
from sagemaker.session import Session
from sagemaker.utils import resolve_value_from_config
from sagemaker.config.config_schema import TELEMETRY_OPT_OUT_PATH
from sagemaker.telemetry.constants import (
Expand DownExpand Up@@ -47,6 +51,7 @@
FEATURE_TO_CODE = {
str(Feature.SDK_DEFAULTS): 1,
str(Feature.LOCAL_MODE): 2,
str(Feature.REMOTE_FUNCTION): 3,
}

STATUS_TO_CODE = {
Expand All@@ -59,86 +64,103 @@ def _telemetry_emitter(feature: str, func_name: str):
"""Decorator to emit telemetry logs for SageMaker Python SDK functions"""

def decorator(func):
def wrapper(self, *args, **kwargs):
logger.info(TELEMETRY_OPT_OUT_MESSAGING)
response = None
caught_ex = None
studio_app_type = process_studio_metadata_file()

# Check if telemetry is opted out
telemetry_opt_out_flag = resolve_value_from_config(
direct_input=None,
config_path=TELEMETRY_OPT_OUT_PATH,
default_value=False,
sagemaker_session=self.sagemaker_session,
)
logger.debug("TelemetryOptOut flag is set to: %s", telemetry_opt_out_flag)

# Construct the feature list to track feature combinations
feature_list: List[int] = [FEATURE_TO_CODE[str(feature)]]
if self.sagemaker_session:
if self.sagemaker_session.sagemaker_config and feature != Feature.SDK_DEFAULTS:
@functools.wraps(func)
def wrapper(*args, **kwargs):
sagemaker_session = None
if len(args) > 0 and hasattr(args[0], "sagemaker_session"):
# Get the sagemaker_session from the instance method args
sagemaker_session = args[0].sagemaker_session
elif feature == Feature.REMOTE_FUNCTION:
# Get the sagemaker_session from the function keyword arguments for remote function
sagemaker_session = kwargs.get(
"sagemaker_session", _get_default_sagemaker_session()
)

if sagemaker_session:
logger.debug("sagemaker_session found, preparing to emit telemetry...")
logger.info(TELEMETRY_OPT_OUT_MESSAGING)
response = None
caught_ex = None
studio_app_type = process_studio_metadata_file()

# Check if telemetry is opted out
telemetry_opt_out_flag = resolve_value_from_config(
direct_input=None,
config_path=TELEMETRY_OPT_OUT_PATH,
default_value=False,
sagemaker_session=sagemaker_session,
)
logger.debug("TelemetryOptOut flag is set to: %s", telemetry_opt_out_flag)

# Construct the feature list to track feature combinations
feature_list: List[int] = [FEATURE_TO_CODE[str(feature)]]

if sagemaker_session.sagemaker_config and feature != Feature.SDK_DEFAULTS:
feature_list.append(FEATURE_TO_CODE[str(Feature.SDK_DEFAULTS)])

if self.sagemaker_session.local_mode and feature != Feature.LOCAL_MODE:
if sagemaker_session.local_mode and feature != Feature.LOCAL_MODE:
feature_list.append(FEATURE_TO_CODE[str(Feature.LOCAL_MODE)])

# Construct the extra info to track platform and environment usage metadata
extra = (
f"{func_name}"
f"&x-sdkVersion={SDK_VERSION}"
f"&x-env={PYTHON_VERSION}"
f"&x-sys={OS_NAME_VERSION}"
f"&x-platform={studio_app_type}"
)

# Add endpoint ARN to the extra info if available
if self.sagemaker_session and self.sagemaker_session.endpoint_arn:
extra += f"&x-endpointArn={self.sagemaker_session.endpoint_arn}"

start_timer = perf_counter()
try:
# Call the original function
response = func(self, *args, **kwargs)
stop_timer = perf_counter()
elapsed = stop_timer - start_timer
extra += f"&x-latency={round(elapsed, 2)}"
if not telemetry_opt_out_flag:
_send_telemetry_request(
STATUS_TO_CODE[str(Status.SUCCESS)],
feature_list,
self.sagemaker_session,
None,
None,
extra,
)
except Exception as e: # pylint: disable=W0703
stop_timer = perf_counter()
elapsed = stop_timer - start_timer
extra += f"&x-latency={round(elapsed, 2)}"
if not telemetry_opt_out_flag:
_send_telemetry_request(
STATUS_TO_CODE[str(Status.FAILURE)],
feature_list,
self.sagemaker_session,
str(e),
e.__class__.__name__,
extra,
)
caught_ex = e
finally:
if caught_ex:
raise caught_ex
return response # pylint: disable=W0150
# Construct the extra info to track platform and environment usage metadata
extra = (
f"{func_name}"
f"&x-sdkVersion={SDK_VERSION}"
f"&x-env={PYTHON_VERSION}"
f"&x-sys={OS_NAME_VERSION}"
f"&x-platform={studio_app_type}"
)

# Add endpoint ARN to the extra info if available
if sagemaker_session.endpoint_arn:
extra += f"&x-endpointArn={sagemaker_session.endpoint_arn}"

start_timer = perf_counter()
try:
# Call the original function
response = func(*args, **kwargs)
stop_timer = perf_counter()
elapsed = stop_timer - start_timer
extra += f"&x-latency={round(elapsed, 2)}"
if not telemetry_opt_out_flag:
_send_telemetry_request(
STATUS_TO_CODE[str(Status.SUCCESS)],
feature_list,
sagemaker_session,
None,
None,
extra,
)
except Exception as e: # pylint: disable=W0703
stop_timer = perf_counter()
elapsed = stop_timer - start_timer
extra += f"&x-latency={round(elapsed, 2)}"
if not telemetry_opt_out_flag:
_send_telemetry_request(
STATUS_TO_CODE[str(Status.FAILURE)],
feature_list,
sagemaker_session,
str(e),
e.__class__.__name__,
extra,
)
caught_ex = e
finally:
if caught_ex:
raise caught_ex
return response # pylint: disable=W0150
else:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: we can move the else branch on top to reduce to clean the code a little bit. For example:

if not sagemaker:
logger.debug(...)
return ...
logger.debug("sagemaker_session found, preparing to emit telemetry...")
...

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ack, Will do this in the follow-up PR for telemetry.

logger.debug(
"Unable to send telemetry for function %s. "
"sagemaker_session is not provided or not valid.",
func_name,
)
return func(*args, **kwargs)

return wrapper

return decorator


from sagemaker.session import Session # noqa: E402 pylint: disable=C0413


def _send_telemetry_request(
status: int,
feature_list: List[int],
Expand All@@ -165,9 +187,9 @@ def _send_telemetry_request(
# Send the telemetry request
logger.debug("Sending telemetry request to [%s]", url)
_requests_helper(url, 2)
logger.debug("SageMaker Python SDK telemetry successfully emitted!")
logger.debug("SageMaker Python SDK telemetry successfully emitted.")
except Exception: # pylint: disable=W0703
logger.debug("SageMaker Python SDK telemetry not emitted!!")
logger.debug("SageMaker Python SDK telemetry not emitted!")


def _construct_url(
Expand DownExpand Up@@ -196,9 +218,6 @@ def _construct_url(
return base_url


import requests # noqa: E402 pylint: disable=C0413,C0411


def _requests_helper(url, timeout):
"""Make a GET request to the given URL"""

Expand DownExpand Up@@ -227,3 +246,11 @@ def _get_region_or_default(session):
return session.boto_session.region_name
except Exception: # pylint: disable=W0703
return DEFAULT_AWS_REGION


def _get_default_sagemaker_session():
"""Return the default sagemaker session"""
boto_session = boto3.Session(region_name=DEFAULT_AWS_REGION)
sagemaker_session = Session(boto_session=boto_session)

return sagemaker_session
7 changes: 3 additions & 4 deletions tests/unit/sagemaker/remote_function/test_client.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,6 +15,7 @@
import os
import threading
import time
import inspect

import pytest
from mock import MagicMock, patch, Mock, ANY, call
Expand DownExpand Up@@ -1498,7 +1499,6 @@ def test_consistency_between_remote_and_step_decorator():
from sagemaker.workflow.function_step import step

remote_args_to_ignore = [
"_remote",
Comment thread
qidewenwhen marked this conversation as resolved.
"include_local_workdir",
"custom_file_filter",
"s3_kms_key",
Expand All@@ -1508,7 +1508,7 @@ def test_consistency_between_remote_and_step_decorator():

step_args_to_ignore = ["_step", "name", "display_name", "description", "retry_policies"]

remote_decorator_args = remote.__code__.co_varnames
remote_decorator_args = inspect.signature(remote).parameters.keys()
common_remote_decorator_args = set(remote_args_to_ignore) ^ set(remote_decorator_args)

step_decorator_args = step.__code__.co_varnames
Expand All@@ -1522,8 +1522,7 @@ def test_consistency_between_remote_and_executor():
executor_arg_list.remove("self")
executor_arg_list.remove("max_parallel_jobs")

remote_args_list = list(remote.__code__.co_varnames)
remote_args_list.remove("_remote")
remote_args_list = list(inspect.signature(remote).parameters.keys())
remote_args_list.remove("_func")

assert executor_arg_list == remote_args_list
18 changes: 18 additions & 0 deletions tests/unit/sagemaker/telemetry/test_telemetry_logging.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,6 +15,7 @@
import pytest
import requests
from unittest.mock import Mock, patch, MagicMock
import boto3
import sagemaker
from sagemaker.telemetry.constants import Feature
from sagemaker.telemetry.telemetry_logging import (
Expand All@@ -24,6 +25,7 @@
_get_accountId,
_requests_helper,
_get_region_or_default,
_get_default_sagemaker_session,
OS_NAME_VERSION,
PYTHON_VERSION,
)
Expand DownExpand Up@@ -282,3 +284,19 @@ def test_get_region_or_default_exception(self):
region = _get_region_or_default(mock_session)
assert region == "us-west-2"
assert "Error creating boto session" in str(exception)

@patch.object(boto3.Session, "region_name", "us-west-2")
def test_get_default_sagemaker_session(self):
sagemaker_session = _get_default_sagemaker_session()

assert isinstance(sagemaker_session, sagemaker.Session) is True
assert sagemaker_session.boto_session.region_name == "us-west-2"

@patch.object(boto3.Session, "region_name", None)
def test_get_default_sagemaker_session_with_no_region(self):
with self.assertRaises(ValueError) as context:
_get_default_sagemaker_session()

assert "Must setup local AWS configuration with a region supported by SageMaker." in str(
context.exception
)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions src/sagemaker/remote_function/client.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -40,6 +40,8 @@
from sagemaker.utils import name_from_base, base_from_name
from sagemaker.remote_function.spark_config import SparkConfig
from sagemaker.remote_function.custom_file_filter import CustomFileFilter
from sagemaker.telemetry.telemetry_logging import _telemetry_emitter
from sagemaker.telemetry.constants import Feature

_API_CALL_LIMIT = {
"SubmittingIntervalInSecs": 1,
Expand All@@ -57,6 +59,7 @@
logger = logging_config.get_logger()


@_telemetry_emitter(feature=Feature.REMOTE_FUNCTION, func_name="remote_function.remote")
Comment thread
qidewenwhen marked this conversation as resolved.
def remote(
_func=None,
*,
Expand Down
1 change: 1 addition & 0 deletions src/sagemaker/telemetry/constants.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,6 +24,7 @@ class Feature(Enum):

SDK_DEFAULTS = 1
LOCAL_MODE = 2
REMOTE_FUNCTION = 3

def __str__(self): # pylint: disable=E0307
"""Return the feature name."""
Expand Down
177 changes: 102 additions & 75 deletions src/sagemaker/telemetry/telemetry_logging.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,7 +17,11 @@
import sys
from time import perf_counter
from typing import List
import functools
import requests

import boto3
from sagemaker.session import Session
from sagemaker.utils import resolve_value_from_config
from sagemaker.config.config_schema import TELEMETRY_OPT_OUT_PATH
from sagemaker.telemetry.constants import (
Expand DownExpand Up@@ -47,6 +51,7 @@
FEATURE_TO_CODE = {
str(Feature.SDK_DEFAULTS): 1,
str(Feature.LOCAL_MODE): 2,
str(Feature.REMOTE_FUNCTION): 3,
}

STATUS_TO_CODE = {
Expand All@@ -59,86 +64,103 @@ def _telemetry_emitter(feature: str, func_name: str):
"""Decorator to emit telemetry logs for SageMaker Python SDK functions"""

def decorator(func):
def wrapper(self, *args, **kwargs):
logger.info(TELEMETRY_OPT_OUT_MESSAGING)
response = None
caught_ex = None
studio_app_type = process_studio_metadata_file()

# Check if telemetry is opted out
telemetry_opt_out_flag = resolve_value_from_config(
direct_input=None,
config_path=TELEMETRY_OPT_OUT_PATH,
default_value=False,
sagemaker_session=self.sagemaker_session,
)
logger.debug("TelemetryOptOut flag is set to: %s", telemetry_opt_out_flag)

# Construct the feature list to track feature combinations
feature_list: List[int] = [FEATURE_TO_CODE[str(feature)]]
if self.sagemaker_session:
if self.sagemaker_session.sagemaker_config and feature != Feature.SDK_DEFAULTS:
@functools.wraps(func)
def wrapper(*args, **kwargs):
sagemaker_session = None
if len(args) > 0 and hasattr(args[0], "sagemaker_session"):
# Get the sagemaker_session from the instance method args
sagemaker_session = args[0].sagemaker_session
elif feature == Feature.REMOTE_FUNCTION:
# Get the sagemaker_session from the function keyword arguments for remote function
sagemaker_session = kwargs.get(
"sagemaker_session", _get_default_sagemaker_session()
)

if sagemaker_session:
logger.debug("sagemaker_session found, preparing to emit telemetry...")
logger.info(TELEMETRY_OPT_OUT_MESSAGING)
response = None
caught_ex = None
studio_app_type = process_studio_metadata_file()

# Check if telemetry is opted out
telemetry_opt_out_flag = resolve_value_from_config(
direct_input=None,
config_path=TELEMETRY_OPT_OUT_PATH,
default_value=False,
sagemaker_session=sagemaker_session,
)
logger.debug("TelemetryOptOut flag is set to: %s", telemetry_opt_out_flag)

# Construct the feature list to track feature combinations
feature_list: List[int] = [FEATURE_TO_CODE[str(feature)]]

if sagemaker_session.sagemaker_config and feature != Feature.SDK_DEFAULTS:
feature_list.append(FEATURE_TO_CODE[str(Feature.SDK_DEFAULTS)])

if self.sagemaker_session.local_mode and feature != Feature.LOCAL_MODE:
if sagemaker_session.local_mode and feature != Feature.LOCAL_MODE:
feature_list.append(FEATURE_TO_CODE[str(Feature.LOCAL_MODE)])

# Construct the extra info to track platform and environment usage metadata
extra = (
f"{func_name}"
f"&x-sdkVersion={SDK_VERSION}"
f"&x-env={PYTHON_VERSION}"
f"&x-sys={OS_NAME_VERSION}"
f"&x-platform={studio_app_type}"
)

# Add endpoint ARN to the extra info if available
if self.sagemaker_session and self.sagemaker_session.endpoint_arn:
extra += f"&x-endpointArn={self.sagemaker_session.endpoint_arn}"

start_timer = perf_counter()
try:
# Call the original function
response = func(self, *args, **kwargs)
stop_timer = perf_counter()
elapsed = stop_timer - start_timer
extra += f"&x-latency={round(elapsed, 2)}"
if not telemetry_opt_out_flag:
_send_telemetry_request(
STATUS_TO_CODE[str(Status.SUCCESS)],
feature_list,
self.sagemaker_session,
None,
None,
extra,
)
except Exception as e: # pylint: disable=W0703
stop_timer = perf_counter()
elapsed = stop_timer - start_timer
extra += f"&x-latency={round(elapsed, 2)}"
if not telemetry_opt_out_flag:
_send_telemetry_request(
STATUS_TO_CODE[str(Status.FAILURE)],
feature_list,
self.sagemaker_session,
str(e),
e.__class__.__name__,
extra,
)
caught_ex = e
finally:
if caught_ex:
raise caught_ex
return response # pylint: disable=W0150
# Construct the extra info to track platform and environment usage metadata
extra = (
f"{func_name}"
f"&x-sdkVersion={SDK_VERSION}"
f"&x-env={PYTHON_VERSION}"
f"&x-sys={OS_NAME_VERSION}"
f"&x-platform={studio_app_type}"
)

# Add endpoint ARN to the extra info if available
if sagemaker_session.endpoint_arn:
extra += f"&x-endpointArn={sagemaker_session.endpoint_arn}"

start_timer = perf_counter()
try:
# Call the original function
response = func(*args, **kwargs)
stop_timer = perf_counter()
elapsed = stop_timer - start_timer
extra += f"&x-latency={round(elapsed, 2)}"
if not telemetry_opt_out_flag:
_send_telemetry_request(
STATUS_TO_CODE[str(Status.SUCCESS)],
feature_list,
sagemaker_session,
None,
None,
extra,
)
except Exception as e: # pylint: disable=W0703
stop_timer = perf_counter()
elapsed = stop_timer - start_timer
extra += f"&x-latency={round(elapsed, 2)}"
if not telemetry_opt_out_flag:
_send_telemetry_request(
STATUS_TO_CODE[str(Status.FAILURE)],
feature_list,
sagemaker_session,
str(e),
e.__class__.__name__,
extra,
)
caught_ex = e
finally:
if caught_ex:
raise caught_ex
return response # pylint: disable=W0150
else:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: we can move the else branch on top to reduce to clean the code a little bit. For example:

if not sagemaker:
logger.debug(...)
return ...
logger.debug("sagemaker_session found, preparing to emit telemetry...")
...

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ack, Will do this in the follow-up PR for telemetry.

logger.debug(
"Unable to send telemetry for function %s. "
"sagemaker_session is not provided or not valid.",
func_name,
)
return func(*args, **kwargs)

return wrapper

return decorator


from sagemaker.session import Session # noqa: E402 pylint: disable=C0413


def _send_telemetry_request(
status: int,
feature_list: List[int],
Expand All@@ -165,9 +187,9 @@ def _send_telemetry_request(
# Send the telemetry request
logger.debug("Sending telemetry request to [%s]", url)
_requests_helper(url, 2)
logger.debug("SageMaker Python SDK telemetry successfully emitted!")
logger.debug("SageMaker Python SDK telemetry successfully emitted.")
except Exception: # pylint: disable=W0703
logger.debug("SageMaker Python SDK telemetry not emitted!!")
logger.debug("SageMaker Python SDK telemetry not emitted!")


def _construct_url(
Expand DownExpand Up@@ -196,9 +218,6 @@ def _construct_url(
return base_url


import requests # noqa: E402 pylint: disable=C0413,C0411


def _requests_helper(url, timeout):
"""Make a GET request to the given URL"""

Expand DownExpand Up@@ -227,3 +246,11 @@ def _get_region_or_default(session):
return session.boto_session.region_name
except Exception: # pylint: disable=W0703
return DEFAULT_AWS_REGION


def _get_default_sagemaker_session():
"""Return the default sagemaker session"""
boto_session = boto3.Session(region_name=DEFAULT_AWS_REGION)
sagemaker_session = Session(boto_session=boto_session)

return sagemaker_session
7 changes: 3 additions & 4 deletions tests/unit/sagemaker/remote_function/test_client.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,6 +15,7 @@
import os
import threading
import time
import inspect

import pytest
from mock import MagicMock, patch, Mock, ANY, call
Expand DownExpand Up@@ -1498,7 +1499,6 @@ def test_consistency_between_remote_and_step_decorator():
from sagemaker.workflow.function_step import step

remote_args_to_ignore = [
"_remote",
Comment thread
qidewenwhen marked this conversation as resolved.
"include_local_workdir",
"custom_file_filter",
"s3_kms_key",
Expand All@@ -1508,7 +1508,7 @@ def test_consistency_between_remote_and_step_decorator():

step_args_to_ignore = ["_step", "name", "display_name", "description", "retry_policies"]

remote_decorator_args = remote.__code__.co_varnames
remote_decorator_args = inspect.signature(remote).parameters.keys()
common_remote_decorator_args = set(remote_args_to_ignore) ^ set(remote_decorator_args)

step_decorator_args = step.__code__.co_varnames
Expand All@@ -1522,8 +1522,7 @@ def test_consistency_between_remote_and_executor():
executor_arg_list.remove("self")
executor_arg_list.remove("max_parallel_jobs")

remote_args_list = list(remote.__code__.co_varnames)
remote_args_list.remove("_remote")
remote_args_list = list(inspect.signature(remote).parameters.keys())
remote_args_list.remove("_func")

assert executor_arg_list == remote_args_list
18 changes: 18 additions & 0 deletions tests/unit/sagemaker/telemetry/test_telemetry_logging.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,6 +15,7 @@
import pytest
import requests
from unittest.mock import Mock, patch, MagicMock
import boto3
import sagemaker
from sagemaker.telemetry.constants import Feature
from sagemaker.telemetry.telemetry_logging import (
Expand All@@ -24,6 +25,7 @@
_get_accountId,
_requests_helper,
_get_region_or_default,
_get_default_sagemaker_session,
OS_NAME_VERSION,
PYTHON_VERSION,
)
Expand DownExpand Up@@ -282,3 +284,19 @@ def test_get_region_or_default_exception(self):
region = _get_region_or_default(mock_session)
assert region == "us-west-2"
assert "Error creating boto session" in str(exception)

@patch.object(boto3.Session, "region_name", "us-west-2")
def test_get_default_sagemaker_session(self):
sagemaker_session = _get_default_sagemaker_session()

assert isinstance(sagemaker_session, sagemaker.Session) is True
assert sagemaker_session.boto_session.region_name == "us-west-2"

@patch.object(boto3.Session, "region_name", None)
def test_get_default_sagemaker_session_with_no_region(self):
with self.assertRaises(ValueError) as context:
_get_default_sagemaker_session()

assert "Must setup local AWS configuration with a region supported by SageMaker." in str(
context.exception
)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions src/sagemaker/remote_function/client.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -40,6 +40,8 @@
from sagemaker.utils import name_from_base, base_from_name
from sagemaker.remote_function.spark_config import SparkConfig
from sagemaker.remote_function.custom_file_filter import CustomFileFilter
from sagemaker.telemetry.telemetry_logging import _telemetry_emitter
from sagemaker.telemetry.constants import Feature

_API_CALL_LIMIT = {
"SubmittingIntervalInSecs": 1,
Expand All@@ -57,6 +59,7 @@
logger = logging_config.get_logger()


@_telemetry_emitter(feature=Feature.REMOTE_FUNCTION, func_name="remote_function.remote")
Comment thread
qidewenwhen marked this conversation as resolved.
def remote(
_func=None,
*,
Expand Down
1 change: 1 addition & 0 deletions src/sagemaker/telemetry/constants.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,6 +24,7 @@ class Feature(Enum):

SDK_DEFAULTS = 1
LOCAL_MODE = 2
REMOTE_FUNCTION = 3

def __str__(self): # pylint: disable=E0307
"""Return the feature name."""
Expand Down
177 changes: 102 additions & 75 deletions src/sagemaker/telemetry/telemetry_logging.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,7 +17,11 @@
import sys
from time import perf_counter
from typing import List
import functools
import requests

import boto3
from sagemaker.session import Session
from sagemaker.utils import resolve_value_from_config
from sagemaker.config.config_schema import TELEMETRY_OPT_OUT_PATH
from sagemaker.telemetry.constants import (
Expand DownExpand Up@@ -47,6 +51,7 @@
FEATURE_TO_CODE = {
str(Feature.SDK_DEFAULTS): 1,
str(Feature.LOCAL_MODE): 2,
str(Feature.REMOTE_FUNCTION): 3,
}

STATUS_TO_CODE = {
Expand All@@ -59,86 +64,103 @@ def _telemetry_emitter(feature: str, func_name: str):
"""Decorator to emit telemetry logs for SageMaker Python SDK functions"""

def decorator(func):
def wrapper(self, *args, **kwargs):
logger.info(TELEMETRY_OPT_OUT_MESSAGING)
response = None
caught_ex = None
studio_app_type = process_studio_metadata_file()

# Check if telemetry is opted out
telemetry_opt_out_flag = resolve_value_from_config(
direct_input=None,
config_path=TELEMETRY_OPT_OUT_PATH,
default_value=False,
sagemaker_session=self.sagemaker_session,
)
logger.debug("TelemetryOptOut flag is set to: %s", telemetry_opt_out_flag)

# Construct the feature list to track feature combinations
feature_list: List[int] = [FEATURE_TO_CODE[str(feature)]]
if self.sagemaker_session:
if self.sagemaker_session.sagemaker_config and feature != Feature.SDK_DEFAULTS:
@functools.wraps(func)
def wrapper(*args, **kwargs):
sagemaker_session = None
if len(args) > 0 and hasattr(args[0], "sagemaker_session"):
# Get the sagemaker_session from the instance method args
sagemaker_session = args[0].sagemaker_session
elif feature == Feature.REMOTE_FUNCTION:
# Get the sagemaker_session from the function keyword arguments for remote function
sagemaker_session = kwargs.get(
"sagemaker_session", _get_default_sagemaker_session()
)

if sagemaker_session:
logger.debug("sagemaker_session found, preparing to emit telemetry...")
logger.info(TELEMETRY_OPT_OUT_MESSAGING)
response = None
caught_ex = None
studio_app_type = process_studio_metadata_file()

# Check if telemetry is opted out
telemetry_opt_out_flag = resolve_value_from_config(
direct_input=None,
config_path=TELEMETRY_OPT_OUT_PATH,
default_value=False,
sagemaker_session=sagemaker_session,
)
logger.debug("TelemetryOptOut flag is set to: %s", telemetry_opt_out_flag)

# Construct the feature list to track feature combinations
feature_list: List[int] = [FEATURE_TO_CODE[str(feature)]]

if sagemaker_session.sagemaker_config and feature != Feature.SDK_DEFAULTS:
feature_list.append(FEATURE_TO_CODE[str(Feature.SDK_DEFAULTS)])

if self.sagemaker_session.local_mode and feature != Feature.LOCAL_MODE:
if sagemaker_session.local_mode and feature != Feature.LOCAL_MODE:
feature_list.append(FEATURE_TO_CODE[str(Feature.LOCAL_MODE)])

# Construct the extra info to track platform and environment usage metadata
extra = (
f"{func_name}"
f"&x-sdkVersion={SDK_VERSION}"
f"&x-env={PYTHON_VERSION}"
f"&x-sys={OS_NAME_VERSION}"
f"&x-platform={studio_app_type}"
)

# Add endpoint ARN to the extra info if available
if self.sagemaker_session and self.sagemaker_session.endpoint_arn:
extra += f"&x-endpointArn={self.sagemaker_session.endpoint_arn}"

start_timer = perf_counter()
try:
# Call the original function
response = func(self, *args, **kwargs)
stop_timer = perf_counter()
elapsed = stop_timer - start_timer
extra += f"&x-latency={round(elapsed, 2)}"
if not telemetry_opt_out_flag:
_send_telemetry_request(
STATUS_TO_CODE[str(Status.SUCCESS)],
feature_list,
self.sagemaker_session,
None,
None,
extra,
)
except Exception as e: # pylint: disable=W0703
stop_timer = perf_counter()
elapsed = stop_timer - start_timer
extra += f"&x-latency={round(elapsed, 2)}"
if not telemetry_opt_out_flag:
_send_telemetry_request(
STATUS_TO_CODE[str(Status.FAILURE)],
feature_list,
self.sagemaker_session,
str(e),
e.__class__.__name__,
extra,
)
caught_ex = e
finally:
if caught_ex:
raise caught_ex
return response # pylint: disable=W0150
# Construct the extra info to track platform and environment usage metadata
extra = (
f"{func_name}"
f"&x-sdkVersion={SDK_VERSION}"
f"&x-env={PYTHON_VERSION}"
f"&x-sys={OS_NAME_VERSION}"
f"&x-platform={studio_app_type}"
)

# Add endpoint ARN to the extra info if available
if sagemaker_session.endpoint_arn:
extra += f"&x-endpointArn={sagemaker_session.endpoint_arn}"

start_timer = perf_counter()
try:
# Call the original function
response = func(*args, **kwargs)
stop_timer = perf_counter()
elapsed = stop_timer - start_timer
extra += f"&x-latency={round(elapsed, 2)}"
if not telemetry_opt_out_flag:
_send_telemetry_request(
STATUS_TO_CODE[str(Status.SUCCESS)],
feature_list,
sagemaker_session,
None,
None,
extra,
)
except Exception as e: # pylint: disable=W0703
stop_timer = perf_counter()
elapsed = stop_timer - start_timer
extra += f"&x-latency={round(elapsed, 2)}"
if not telemetry_opt_out_flag:
_send_telemetry_request(
STATUS_TO_CODE[str(Status.FAILURE)],
feature_list,
sagemaker_session,
str(e),
e.__class__.__name__,
extra,
)
caught_ex = e
finally:
if caught_ex:
raise caught_ex
return response # pylint: disable=W0150
else:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: we can move the else branch on top to reduce to clean the code a little bit. For example:

if not sagemaker:
logger.debug(...)
return ...
logger.debug("sagemaker_session found, preparing to emit telemetry...")
...

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ack, Will do this in the follow-up PR for telemetry.

logger.debug(
"Unable to send telemetry for function %s. "
"sagemaker_session is not provided or not valid.",
func_name,
)
return func(*args, **kwargs)

return wrapper

return decorator


from sagemaker.session import Session # noqa: E402 pylint: disable=C0413


def _send_telemetry_request(
status: int,
feature_list: List[int],
Expand All@@ -165,9 +187,9 @@ def _send_telemetry_request(
# Send the telemetry request
logger.debug("Sending telemetry request to [%s]", url)
_requests_helper(url, 2)
logger.debug("SageMaker Python SDK telemetry successfully emitted!")
logger.debug("SageMaker Python SDK telemetry successfully emitted.")
except Exception: # pylint: disable=W0703
logger.debug("SageMaker Python SDK telemetry not emitted!!")
logger.debug("SageMaker Python SDK telemetry not emitted!")


def _construct_url(
Expand DownExpand Up@@ -196,9 +218,6 @@ def _construct_url(
return base_url


import requests # noqa: E402 pylint: disable=C0413,C0411


def _requests_helper(url, timeout):
"""Make a GET request to the given URL"""

Expand DownExpand Up@@ -227,3 +246,11 @@ def _get_region_or_default(session):
return session.boto_session.region_name
except Exception: # pylint: disable=W0703
return DEFAULT_AWS_REGION


def _get_default_sagemaker_session():
"""Return the default sagemaker session"""
boto_session = boto3.Session(region_name=DEFAULT_AWS_REGION)
sagemaker_session = Session(boto_session=boto_session)

return sagemaker_session
7 changes: 3 additions & 4 deletions tests/unit/sagemaker/remote_function/test_client.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,6 +15,7 @@
import os
import threading
import time
import inspect

import pytest
from mock import MagicMock, patch, Mock, ANY, call
Expand DownExpand Up@@ -1498,7 +1499,6 @@ def test_consistency_between_remote_and_step_decorator():
from sagemaker.workflow.function_step import step

remote_args_to_ignore = [
"_remote",
Comment thread
qidewenwhen marked this conversation as resolved.
"include_local_workdir",
"custom_file_filter",
"s3_kms_key",
Expand All@@ -1508,7 +1508,7 @@ def test_consistency_between_remote_and_step_decorator():

step_args_to_ignore = ["_step", "name", "display_name", "description", "retry_policies"]

remote_decorator_args = remote.__code__.co_varnames
remote_decorator_args = inspect.signature(remote).parameters.keys()
common_remote_decorator_args = set(remote_args_to_ignore) ^ set(remote_decorator_args)

step_decorator_args = step.__code__.co_varnames
Expand All@@ -1522,8 +1522,7 @@ def test_consistency_between_remote_and_executor():
executor_arg_list.remove("self")
executor_arg_list.remove("max_parallel_jobs")

remote_args_list = list(remote.__code__.co_varnames)
remote_args_list.remove("_remote")
remote_args_list = list(inspect.signature(remote).parameters.keys())
remote_args_list.remove("_func")

assert executor_arg_list == remote_args_list
18 changes: 18 additions & 0 deletions tests/unit/sagemaker/telemetry/test_telemetry_logging.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,6 +15,7 @@
import pytest
import requests
from unittest.mock import Mock, patch, MagicMock
import boto3
import sagemaker
from sagemaker.telemetry.constants import Feature
from sagemaker.telemetry.telemetry_logging import (
Expand All@@ -24,6 +25,7 @@
_get_accountId,
_requests_helper,
_get_region_or_default,
_get_default_sagemaker_session,
OS_NAME_VERSION,
PYTHON_VERSION,
)
Expand DownExpand Up@@ -282,3 +284,19 @@ def test_get_region_or_default_exception(self):
region = _get_region_or_default(mock_session)
assert region == "us-west-2"
assert "Error creating boto session" in str(exception)

@patch.object(boto3.Session, "region_name", "us-west-2")
def test_get_default_sagemaker_session(self):
sagemaker_session = _get_default_sagemaker_session()

assert isinstance(sagemaker_session, sagemaker.Session) is True
assert sagemaker_session.boto_session.region_name == "us-west-2"

@patch.object(boto3.Session, "region_name", None)
def test_get_default_sagemaker_session_with_no_region(self):
with self.assertRaises(ValueError) as context:
_get_default_sagemaker_session()

assert "Must setup local AWS configuration with a region supported by SageMaker." in str(
context.exception
)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions src/sagemaker/remote_function/client.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -40,6 +40,8 @@
from sagemaker.utils import name_from_base, base_from_name
from sagemaker.remote_function.spark_config import SparkConfig
from sagemaker.remote_function.custom_file_filter import CustomFileFilter
from sagemaker.telemetry.telemetry_logging import _telemetry_emitter
from sagemaker.telemetry.constants import Feature

_API_CALL_LIMIT = {
"SubmittingIntervalInSecs": 1,
Expand All@@ -57,6 +59,7 @@
logger = logging_config.get_logger()


@_telemetry_emitter(feature=Feature.REMOTE_FUNCTION, func_name="remote_function.remote")
Comment thread
qidewenwhen marked this conversation as resolved.
def remote(
_func=None,
*,
Expand Down
1 change: 1 addition & 0 deletions src/sagemaker/telemetry/constants.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,6 +24,7 @@ class Feature(Enum):

SDK_DEFAULTS = 1
LOCAL_MODE = 2
REMOTE_FUNCTION = 3

def __str__(self): # pylint: disable=E0307
"""Return the feature name."""
Expand Down
177 changes: 102 additions & 75 deletions src/sagemaker/telemetry/telemetry_logging.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,7 +17,11 @@
import sys
from time import perf_counter
from typing import List
import functools
import requests

import boto3
from sagemaker.session import Session
from sagemaker.utils import resolve_value_from_config
from sagemaker.config.config_schema import TELEMETRY_OPT_OUT_PATH
from sagemaker.telemetry.constants import (
Expand DownExpand Up@@ -47,6 +51,7 @@
FEATURE_TO_CODE = {
str(Feature.SDK_DEFAULTS): 1,
str(Feature.LOCAL_MODE): 2,
str(Feature.REMOTE_FUNCTION): 3,
}

STATUS_TO_CODE = {
Expand All@@ -59,86 +64,103 @@ def _telemetry_emitter(feature: str, func_name: str):
"""Decorator to emit telemetry logs for SageMaker Python SDK functions"""

def decorator(func):
def wrapper(self, *args, **kwargs):
logger.info(TELEMETRY_OPT_OUT_MESSAGING)
response = None
caught_ex = None
studio_app_type = process_studio_metadata_file()

# Check if telemetry is opted out
telemetry_opt_out_flag = resolve_value_from_config(
direct_input=None,
config_path=TELEMETRY_OPT_OUT_PATH,
default_value=False,
sagemaker_session=self.sagemaker_session,
)
logger.debug("TelemetryOptOut flag is set to: %s", telemetry_opt_out_flag)

# Construct the feature list to track feature combinations
feature_list: List[int] = [FEATURE_TO_CODE[str(feature)]]
if self.sagemaker_session:
if self.sagemaker_session.sagemaker_config and feature != Feature.SDK_DEFAULTS:
@functools.wraps(func)
def wrapper(*args, **kwargs):
sagemaker_session = None
if len(args) > 0 and hasattr(args[0], "sagemaker_session"):
# Get the sagemaker_session from the instance method args
sagemaker_session = args[0].sagemaker_session
elif feature == Feature.REMOTE_FUNCTION:
# Get the sagemaker_session from the function keyword arguments for remote function
sagemaker_session = kwargs.get(
"sagemaker_session", _get_default_sagemaker_session()
)

if sagemaker_session:
logger.debug("sagemaker_session found, preparing to emit telemetry...")
logger.info(TELEMETRY_OPT_OUT_MESSAGING)
response = None
caught_ex = None
studio_app_type = process_studio_metadata_file()

# Check if telemetry is opted out
telemetry_opt_out_flag = resolve_value_from_config(
direct_input=None,
config_path=TELEMETRY_OPT_OUT_PATH,
default_value=False,
sagemaker_session=sagemaker_session,
)
logger.debug("TelemetryOptOut flag is set to: %s", telemetry_opt_out_flag)

# Construct the feature list to track feature combinations
feature_list: List[int] = [FEATURE_TO_CODE[str(feature)]]

if sagemaker_session.sagemaker_config and feature != Feature.SDK_DEFAULTS:
feature_list.append(FEATURE_TO_CODE[str(Feature.SDK_DEFAULTS)])

if self.sagemaker_session.local_mode and feature != Feature.LOCAL_MODE:
if sagemaker_session.local_mode and feature != Feature.LOCAL_MODE:
feature_list.append(FEATURE_TO_CODE[str(Feature.LOCAL_MODE)])

# Construct the extra info to track platform and environment usage metadata
extra = (
f"{func_name}"
f"&x-sdkVersion={SDK_VERSION}"
f"&x-env={PYTHON_VERSION}"
f"&x-sys={OS_NAME_VERSION}"
f"&x-platform={studio_app_type}"
)

# Add endpoint ARN to the extra info if available
if self.sagemaker_session and self.sagemaker_session.endpoint_arn:
extra += f"&x-endpointArn={self.sagemaker_session.endpoint_arn}"

start_timer = perf_counter()
try:
# Call the original function
response = func(self, *args, **kwargs)
stop_timer = perf_counter()
elapsed = stop_timer - start_timer
extra += f"&x-latency={round(elapsed, 2)}"
if not telemetry_opt_out_flag:
_send_telemetry_request(
STATUS_TO_CODE[str(Status.SUCCESS)],
feature_list,
self.sagemaker_session,
None,
None,
extra,
)
except Exception as e: # pylint: disable=W0703
stop_timer = perf_counter()
elapsed = stop_timer - start_timer
extra += f"&x-latency={round(elapsed, 2)}"
if not telemetry_opt_out_flag:
_send_telemetry_request(
STATUS_TO_CODE[str(Status.FAILURE)],
feature_list,
self.sagemaker_session,
str(e),
e.__class__.__name__,
extra,
)
caught_ex = e
finally:
if caught_ex:
raise caught_ex
return response # pylint: disable=W0150
# Construct the extra info to track platform and environment usage metadata
extra = (
f"{func_name}"
f"&x-sdkVersion={SDK_VERSION}"
f"&x-env={PYTHON_VERSION}"
f"&x-sys={OS_NAME_VERSION}"
f"&x-platform={studio_app_type}"
)

# Add endpoint ARN to the extra info if available
if sagemaker_session.endpoint_arn:
extra += f"&x-endpointArn={sagemaker_session.endpoint_arn}"

start_timer = perf_counter()
try:
# Call the original function
response = func(*args, **kwargs)
stop_timer = perf_counter()
elapsed = stop_timer - start_timer
extra += f"&x-latency={round(elapsed, 2)}"
if not telemetry_opt_out_flag:
_send_telemetry_request(
STATUS_TO_CODE[str(Status.SUCCESS)],
feature_list,
sagemaker_session,
None,
None,
extra,
)
except Exception as e: # pylint: disable=W0703
stop_timer = perf_counter()
elapsed = stop_timer - start_timer
extra += f"&x-latency={round(elapsed, 2)}"
if not telemetry_opt_out_flag:
_send_telemetry_request(
STATUS_TO_CODE[str(Status.FAILURE)],
feature_list,
sagemaker_session,
str(e),
e.__class__.__name__,
extra,
)
caught_ex = e
finally:
if caught_ex:
raise caught_ex
return response # pylint: disable=W0150
else:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: we can move the else branch on top to reduce to clean the code a little bit. For example:

if not sagemaker:
logger.debug(...)
return ...
logger.debug("sagemaker_session found, preparing to emit telemetry...")
...

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ack, Will do this in the follow-up PR for telemetry.

logger.debug(
"Unable to send telemetry for function %s. "
"sagemaker_session is not provided or not valid.",
func_name,
)
return func(*args, **kwargs)

return wrapper

return decorator


from sagemaker.session import Session # noqa: E402 pylint: disable=C0413


def _send_telemetry_request(
status: int,
feature_list: List[int],
Expand All@@ -165,9 +187,9 @@ def _send_telemetry_request(
# Send the telemetry request
logger.debug("Sending telemetry request to [%s]", url)
_requests_helper(url, 2)
logger.debug("SageMaker Python SDK telemetry successfully emitted!")
logger.debug("SageMaker Python SDK telemetry successfully emitted.")
except Exception: # pylint: disable=W0703
logger.debug("SageMaker Python SDK telemetry not emitted!!")
logger.debug("SageMaker Python SDK telemetry not emitted!")


def _construct_url(
Expand DownExpand Up@@ -196,9 +218,6 @@ def _construct_url(
return base_url


import requests # noqa: E402 pylint: disable=C0413,C0411


def _requests_helper(url, timeout):
"""Make a GET request to the given URL"""

Expand DownExpand Up@@ -227,3 +246,11 @@ def _get_region_or_default(session):
return session.boto_session.region_name
except Exception: # pylint: disable=W0703
return DEFAULT_AWS_REGION


def _get_default_sagemaker_session():
"""Return the default sagemaker session"""
boto_session = boto3.Session(region_name=DEFAULT_AWS_REGION)
sagemaker_session = Session(boto_session=boto_session)

return sagemaker_session
7 changes: 3 additions & 4 deletions tests/unit/sagemaker/remote_function/test_client.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,6 +15,7 @@
import os
import threading
import time
import inspect

import pytest
from mock import MagicMock, patch, Mock, ANY, call
Expand DownExpand Up@@ -1498,7 +1499,6 @@ def test_consistency_between_remote_and_step_decorator():
from sagemaker.workflow.function_step import step

remote_args_to_ignore = [
"_remote",
Comment thread
qidewenwhen marked this conversation as resolved.
"include_local_workdir",
"custom_file_filter",
"s3_kms_key",
Expand All@@ -1508,7 +1508,7 @@ def test_consistency_between_remote_and_step_decorator():

step_args_to_ignore = ["_step", "name", "display_name", "description", "retry_policies"]

remote_decorator_args = remote.__code__.co_varnames
remote_decorator_args = inspect.signature(remote).parameters.keys()
common_remote_decorator_args = set(remote_args_to_ignore) ^ set(remote_decorator_args)

step_decorator_args = step.__code__.co_varnames
Expand All@@ -1522,8 +1522,7 @@ def test_consistency_between_remote_and_executor():
executor_arg_list.remove("self")
executor_arg_list.remove("max_parallel_jobs")

remote_args_list = list(remote.__code__.co_varnames)
remote_args_list.remove("_remote")
remote_args_list = list(inspect.signature(remote).parameters.keys())
remote_args_list.remove("_func")

assert executor_arg_list == remote_args_list
18 changes: 18 additions & 0 deletions tests/unit/sagemaker/telemetry/test_telemetry_logging.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,6 +15,7 @@
import pytest
import requests
from unittest.mock import Mock, patch, MagicMock
import boto3
import sagemaker
from sagemaker.telemetry.constants import Feature
from sagemaker.telemetry.telemetry_logging import (
Expand All@@ -24,6 +25,7 @@
_get_accountId,
_requests_helper,
_get_region_or_default,
_get_default_sagemaker_session,
OS_NAME_VERSION,
PYTHON_VERSION,
)
Expand DownExpand Up@@ -282,3 +284,19 @@ def test_get_region_or_default_exception(self):
region = _get_region_or_default(mock_session)
assert region == "us-west-2"
assert "Error creating boto session" in str(exception)

@patch.object(boto3.Session, "region_name", "us-west-2")
def test_get_default_sagemaker_session(self):
sagemaker_session = _get_default_sagemaker_session()

assert isinstance(sagemaker_session, sagemaker.Session) is True
assert sagemaker_session.boto_session.region_name == "us-west-2"

@patch.object(boto3.Session, "region_name", None)
def test_get_default_sagemaker_session_with_no_region(self):
with self.assertRaises(ValueError) as context:
_get_default_sagemaker_session()

assert "Must setup local AWS configuration with a region supported by SageMaker." in str(
context.exception
)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions src/sagemaker/remote_function/client.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -40,6 +40,8 @@
from sagemaker.utils import name_from_base, base_from_name
from sagemaker.remote_function.spark_config import SparkConfig
from sagemaker.remote_function.custom_file_filter import CustomFileFilter
from sagemaker.telemetry.telemetry_logging import _telemetry_emitter
from sagemaker.telemetry.constants import Feature

_API_CALL_LIMIT = {
"SubmittingIntervalInSecs": 1,
Expand All@@ -57,6 +59,7 @@
logger = logging_config.get_logger()


@_telemetry_emitter(feature=Feature.REMOTE_FUNCTION, func_name="remote_function.remote")
Comment thread
qidewenwhen marked this conversation as resolved.
def remote(
_func=None,
*,
Expand Down
1 change: 1 addition & 0 deletions src/sagemaker/telemetry/constants.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,6 +24,7 @@ class Feature(Enum):

SDK_DEFAULTS = 1
LOCAL_MODE = 2
REMOTE_FUNCTION = 3

def __str__(self): # pylint: disable=E0307
"""Return the feature name."""
Expand Down
177 changes: 102 additions & 75 deletions src/sagemaker/telemetry/telemetry_logging.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,7 +17,11 @@
import sys
from time import perf_counter
from typing import List
import functools
import requests

import boto3
from sagemaker.session import Session
from sagemaker.utils import resolve_value_from_config
from sagemaker.config.config_schema import TELEMETRY_OPT_OUT_PATH
from sagemaker.telemetry.constants import (
Expand DownExpand Up@@ -47,6 +51,7 @@
FEATURE_TO_CODE = {
str(Feature.SDK_DEFAULTS): 1,
str(Feature.LOCAL_MODE): 2,
str(Feature.REMOTE_FUNCTION): 3,
}

STATUS_TO_CODE = {
Expand All@@ -59,86 +64,103 @@ def _telemetry_emitter(feature: str, func_name: str):
"""Decorator to emit telemetry logs for SageMaker Python SDK functions"""

def decorator(func):
def wrapper(self, *args, **kwargs):
logger.info(TELEMETRY_OPT_OUT_MESSAGING)
response = None
caught_ex = None
studio_app_type = process_studio_metadata_file()

# Check if telemetry is opted out
telemetry_opt_out_flag = resolve_value_from_config(
direct_input=None,
config_path=TELEMETRY_OPT_OUT_PATH,
default_value=False,
sagemaker_session=self.sagemaker_session,
)
logger.debug("TelemetryOptOut flag is set to: %s", telemetry_opt_out_flag)

# Construct the feature list to track feature combinations
feature_list: List[int] = [FEATURE_TO_CODE[str(feature)]]
if self.sagemaker_session:
if self.sagemaker_session.sagemaker_config and feature != Feature.SDK_DEFAULTS:
@functools.wraps(func)
def wrapper(*args, **kwargs):
sagemaker_session = None
if len(args) > 0 and hasattr(args[0], "sagemaker_session"):
# Get the sagemaker_session from the instance method args
sagemaker_session = args[0].sagemaker_session
elif feature == Feature.REMOTE_FUNCTION:
# Get the sagemaker_session from the function keyword arguments for remote function
sagemaker_session = kwargs.get(
"sagemaker_session", _get_default_sagemaker_session()
)

if sagemaker_session:
logger.debug("sagemaker_session found, preparing to emit telemetry...")
logger.info(TELEMETRY_OPT_OUT_MESSAGING)
response = None
caught_ex = None
studio_app_type = process_studio_metadata_file()

# Check if telemetry is opted out
telemetry_opt_out_flag = resolve_value_from_config(
direct_input=None,
config_path=TELEMETRY_OPT_OUT_PATH,
default_value=False,
sagemaker_session=sagemaker_session,
)
logger.debug("TelemetryOptOut flag is set to: %s", telemetry_opt_out_flag)

# Construct the feature list to track feature combinations
feature_list: List[int] = [FEATURE_TO_CODE[str(feature)]]

if sagemaker_session.sagemaker_config and feature != Feature.SDK_DEFAULTS:
feature_list.append(FEATURE_TO_CODE[str(Feature.SDK_DEFAULTS)])

if self.sagemaker_session.local_mode and feature != Feature.LOCAL_MODE:
if sagemaker_session.local_mode and feature != Feature.LOCAL_MODE:
feature_list.append(FEATURE_TO_CODE[str(Feature.LOCAL_MODE)])

# Construct the extra info to track platform and environment usage metadata
extra = (
f"{func_name}"
f"&x-sdkVersion={SDK_VERSION}"
f"&x-env={PYTHON_VERSION}"
f"&x-sys={OS_NAME_VERSION}"
f"&x-platform={studio_app_type}"
)

# Add endpoint ARN to the extra info if available
if self.sagemaker_session and self.sagemaker_session.endpoint_arn:
extra += f"&x-endpointArn={self.sagemaker_session.endpoint_arn}"

start_timer = perf_counter()
try:
# Call the original function
response = func(self, *args, **kwargs)
stop_timer = perf_counter()
elapsed = stop_timer - start_timer
extra += f"&x-latency={round(elapsed, 2)}"
if not telemetry_opt_out_flag:
_send_telemetry_request(
STATUS_TO_CODE[str(Status.SUCCESS)],
feature_list,
self.sagemaker_session,
None,
None,
extra,
)
except Exception as e: # pylint: disable=W0703
stop_timer = perf_counter()
elapsed = stop_timer - start_timer
extra += f"&x-latency={round(elapsed, 2)}"
if not telemetry_opt_out_flag:
_send_telemetry_request(
STATUS_TO_CODE[str(Status.FAILURE)],
feature_list,
self.sagemaker_session,
str(e),
e.__class__.__name__,
extra,
)
caught_ex = e
finally:
if caught_ex:
raise caught_ex
return response # pylint: disable=W0150
# Construct the extra info to track platform and environment usage metadata
extra = (
f"{func_name}"
f"&x-sdkVersion={SDK_VERSION}"
f"&x-env={PYTHON_VERSION}"
f"&x-sys={OS_NAME_VERSION}"
f"&x-platform={studio_app_type}"
)

# Add endpoint ARN to the extra info if available
if sagemaker_session.endpoint_arn:
extra += f"&x-endpointArn={sagemaker_session.endpoint_arn}"

start_timer = perf_counter()
try:
# Call the original function
response = func(*args, **kwargs)
stop_timer = perf_counter()
elapsed = stop_timer - start_timer
extra += f"&x-latency={round(elapsed, 2)}"
if not telemetry_opt_out_flag:
_send_telemetry_request(
STATUS_TO_CODE[str(Status.SUCCESS)],
feature_list,
sagemaker_session,
None,
None,
extra,
)
except Exception as e: # pylint: disable=W0703
stop_timer = perf_counter()
elapsed = stop_timer - start_timer
extra += f"&x-latency={round(elapsed, 2)}"
if not telemetry_opt_out_flag:
_send_telemetry_request(
STATUS_TO_CODE[str(Status.FAILURE)],
feature_list,
sagemaker_session,
str(e),
e.__class__.__name__,
extra,
)
caught_ex = e
finally:
if caught_ex:
raise caught_ex
return response # pylint: disable=W0150
else:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: we can move the else branch on top to reduce to clean the code a little bit. For example:

if not sagemaker:
logger.debug(...)
return ...
logger.debug("sagemaker_session found, preparing to emit telemetry...")
...

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ack, Will do this in the follow-up PR for telemetry.

logger.debug(
"Unable to send telemetry for function %s. "
"sagemaker_session is not provided or not valid.",
func_name,
)
return func(*args, **kwargs)

return wrapper

return decorator


from sagemaker.session import Session # noqa: E402 pylint: disable=C0413


def _send_telemetry_request(
status: int,
feature_list: List[int],
Expand All@@ -165,9 +187,9 @@ def _send_telemetry_request(
# Send the telemetry request
logger.debug("Sending telemetry request to [%s]", url)
_requests_helper(url, 2)
logger.debug("SageMaker Python SDK telemetry successfully emitted!")
logger.debug("SageMaker Python SDK telemetry successfully emitted.")
except Exception: # pylint: disable=W0703
logger.debug("SageMaker Python SDK telemetry not emitted!!")
logger.debug("SageMaker Python SDK telemetry not emitted!")


def _construct_url(
Expand DownExpand Up@@ -196,9 +218,6 @@ def _construct_url(
return base_url


import requests # noqa: E402 pylint: disable=C0413,C0411


def _requests_helper(url, timeout):
"""Make a GET request to the given URL"""

Expand DownExpand Up@@ -227,3 +246,11 @@ def _get_region_or_default(session):
return session.boto_session.region_name
except Exception: # pylint: disable=W0703
return DEFAULT_AWS_REGION


def _get_default_sagemaker_session():
"""Return the default sagemaker session"""
boto_session = boto3.Session(region_name=DEFAULT_AWS_REGION)
sagemaker_session = Session(boto_session=boto_session)

return sagemaker_session
7 changes: 3 additions & 4 deletions tests/unit/sagemaker/remote_function/test_client.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,6 +15,7 @@
import os
import threading
import time
import inspect

import pytest
from mock import MagicMock, patch, Mock, ANY, call
Expand DownExpand Up@@ -1498,7 +1499,6 @@ def test_consistency_between_remote_and_step_decorator():
from sagemaker.workflow.function_step import step

remote_args_to_ignore = [
"_remote",
Comment thread
qidewenwhen marked this conversation as resolved.
"include_local_workdir",
"custom_file_filter",
"s3_kms_key",
Expand All@@ -1508,7 +1508,7 @@ def test_consistency_between_remote_and_step_decorator():

step_args_to_ignore = ["_step", "name", "display_name", "description", "retry_policies"]

remote_decorator_args = remote.__code__.co_varnames
remote_decorator_args = inspect.signature(remote).parameters.keys()
common_remote_decorator_args = set(remote_args_to_ignore) ^ set(remote_decorator_args)

step_decorator_args = step.__code__.co_varnames
Expand All@@ -1522,8 +1522,7 @@ def test_consistency_between_remote_and_executor():
executor_arg_list.remove("self")
executor_arg_list.remove("max_parallel_jobs")

remote_args_list = list(remote.__code__.co_varnames)
remote_args_list.remove("_remote")
remote_args_list = list(inspect.signature(remote).parameters.keys())
remote_args_list.remove("_func")

assert executor_arg_list == remote_args_list
18 changes: 18 additions & 0 deletions tests/unit/sagemaker/telemetry/test_telemetry_logging.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,6 +15,7 @@
import pytest
import requests
from unittest.mock import Mock, patch, MagicMock
import boto3
import sagemaker
from sagemaker.telemetry.constants import Feature
from sagemaker.telemetry.telemetry_logging import (
Expand All@@ -24,6 +25,7 @@
_get_accountId,
_requests_helper,
_get_region_or_default,
_get_default_sagemaker_session,
OS_NAME_VERSION,
PYTHON_VERSION,
)
Expand DownExpand Up@@ -282,3 +284,19 @@ def test_get_region_or_default_exception(self):
region = _get_region_or_default(mock_session)
assert region == "us-west-2"
assert "Error creating boto session" in str(exception)

@patch.object(boto3.Session, "region_name", "us-west-2")
def test_get_default_sagemaker_session(self):
sagemaker_session = _get_default_sagemaker_session()

assert isinstance(sagemaker_session, sagemaker.Session) is True
assert sagemaker_session.boto_session.region_name == "us-west-2"

@patch.object(boto3.Session, "region_name", None)
def test_get_default_sagemaker_session_with_no_region(self):
with self.assertRaises(ValueError) as context:
_get_default_sagemaker_session()

assert "Must setup local AWS configuration with a region supported by SageMaker." in str(
context.exception
)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions src/sagemaker/remote_function/client.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -40,6 +40,8 @@
from sagemaker.utils import name_from_base, base_from_name
from sagemaker.remote_function.spark_config import SparkConfig
from sagemaker.remote_function.custom_file_filter import CustomFileFilter
from sagemaker.telemetry.telemetry_logging import _telemetry_emitter
from sagemaker.telemetry.constants import Feature

_API_CALL_LIMIT = {
"SubmittingIntervalInSecs": 1,
Expand All@@ -57,6 +59,7 @@
logger = logging_config.get_logger()


@_telemetry_emitter(feature=Feature.REMOTE_FUNCTION, func_name="remote_function.remote")
Comment thread
qidewenwhen marked this conversation as resolved.
def remote(
_func=None,
*,
Expand Down
1 change: 1 addition & 0 deletions src/sagemaker/telemetry/constants.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,6 +24,7 @@ class Feature(Enum):

SDK_DEFAULTS = 1
LOCAL_MODE = 2
REMOTE_FUNCTION = 3

def __str__(self): # pylint: disable=E0307
"""Return the feature name."""
Expand Down
177 changes: 102 additions & 75 deletions src/sagemaker/telemetry/telemetry_logging.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,7 +17,11 @@
import sys
from time import perf_counter
from typing import List
import functools
import requests

import boto3
from sagemaker.session import Session
from sagemaker.utils import resolve_value_from_config
from sagemaker.config.config_schema import TELEMETRY_OPT_OUT_PATH
from sagemaker.telemetry.constants import (
Expand DownExpand Up@@ -47,6 +51,7 @@
FEATURE_TO_CODE = {
str(Feature.SDK_DEFAULTS): 1,
str(Feature.LOCAL_MODE): 2,
str(Feature.REMOTE_FUNCTION): 3,
}

STATUS_TO_CODE = {
Expand All@@ -59,86 +64,103 @@ def _telemetry_emitter(feature: str, func_name: str):
"""Decorator to emit telemetry logs for SageMaker Python SDK functions"""

def decorator(func):
def wrapper(self, *args, **kwargs):
logger.info(TELEMETRY_OPT_OUT_MESSAGING)
response = None
caught_ex = None
studio_app_type = process_studio_metadata_file()

# Check if telemetry is opted out
telemetry_opt_out_flag = resolve_value_from_config(
direct_input=None,
config_path=TELEMETRY_OPT_OUT_PATH,
default_value=False,
sagemaker_session=self.sagemaker_session,
)
logger.debug("TelemetryOptOut flag is set to: %s", telemetry_opt_out_flag)

# Construct the feature list to track feature combinations
feature_list: List[int] = [FEATURE_TO_CODE[str(feature)]]
if self.sagemaker_session:
if self.sagemaker_session.sagemaker_config and feature != Feature.SDK_DEFAULTS:
@functools.wraps(func)
def wrapper(*args, **kwargs):
sagemaker_session = None
if len(args) > 0 and hasattr(args[0], "sagemaker_session"):
# Get the sagemaker_session from the instance method args
sagemaker_session = args[0].sagemaker_session
elif feature == Feature.REMOTE_FUNCTION:
# Get the sagemaker_session from the function keyword arguments for remote function
sagemaker_session = kwargs.get(
"sagemaker_session", _get_default_sagemaker_session()
)

if sagemaker_session:
logger.debug("sagemaker_session found, preparing to emit telemetry...")
logger.info(TELEMETRY_OPT_OUT_MESSAGING)
response = None
caught_ex = None
studio_app_type = process_studio_metadata_file()

# Check if telemetry is opted out
telemetry_opt_out_flag = resolve_value_from_config(
direct_input=None,
config_path=TELEMETRY_OPT_OUT_PATH,
default_value=False,
sagemaker_session=sagemaker_session,
)
logger.debug("TelemetryOptOut flag is set to: %s", telemetry_opt_out_flag)

# Construct the feature list to track feature combinations
feature_list: List[int] = [FEATURE_TO_CODE[str(feature)]]

if sagemaker_session.sagemaker_config and feature != Feature.SDK_DEFAULTS:
feature_list.append(FEATURE_TO_CODE[str(Feature.SDK_DEFAULTS)])

if self.sagemaker_session.local_mode and feature != Feature.LOCAL_MODE:
if sagemaker_session.local_mode and feature != Feature.LOCAL_MODE:
feature_list.append(FEATURE_TO_CODE[str(Feature.LOCAL_MODE)])

# Construct the extra info to track platform and environment usage metadata
extra = (
f"{func_name}"
f"&x-sdkVersion={SDK_VERSION}"
f"&x-env={PYTHON_VERSION}"
f"&x-sys={OS_NAME_VERSION}"
f"&x-platform={studio_app_type}"
)

# Add endpoint ARN to the extra info if available
if self.sagemaker_session and self.sagemaker_session.endpoint_arn:
extra += f"&x-endpointArn={self.sagemaker_session.endpoint_arn}"

start_timer = perf_counter()
try:
# Call the original function
response = func(self, *args, **kwargs)
stop_timer = perf_counter()
elapsed = stop_timer - start_timer
extra += f"&x-latency={round(elapsed, 2)}"
if not telemetry_opt_out_flag:
_send_telemetry_request(
STATUS_TO_CODE[str(Status.SUCCESS)],
feature_list,
self.sagemaker_session,
None,
None,
extra,
)
except Exception as e: # pylint: disable=W0703
stop_timer = perf_counter()
elapsed = stop_timer - start_timer
extra += f"&x-latency={round(elapsed, 2)}"
if not telemetry_opt_out_flag:
_send_telemetry_request(
STATUS_TO_CODE[str(Status.FAILURE)],
feature_list,
self.sagemaker_session,
str(e),
e.__class__.__name__,
extra,
)
caught_ex = e
finally:
if caught_ex:
raise caught_ex
return response # pylint: disable=W0150
# Construct the extra info to track platform and environment usage metadata
extra = (
f"{func_name}"
f"&x-sdkVersion={SDK_VERSION}"
f"&x-env={PYTHON_VERSION}"
f"&x-sys={OS_NAME_VERSION}"
f"&x-platform={studio_app_type}"
)

# Add endpoint ARN to the extra info if available
if sagemaker_session.endpoint_arn:
extra += f"&x-endpointArn={sagemaker_session.endpoint_arn}"

start_timer = perf_counter()
try:
# Call the original function
response = func(*args, **kwargs)
stop_timer = perf_counter()
elapsed = stop_timer - start_timer
extra += f"&x-latency={round(elapsed, 2)}"
if not telemetry_opt_out_flag:
_send_telemetry_request(
STATUS_TO_CODE[str(Status.SUCCESS)],
feature_list,
sagemaker_session,
None,
None,
extra,
)
except Exception as e: # pylint: disable=W0703
stop_timer = perf_counter()
elapsed = stop_timer - start_timer
extra += f"&x-latency={round(elapsed, 2)}"
if not telemetry_opt_out_flag:
_send_telemetry_request(
STATUS_TO_CODE[str(Status.FAILURE)],
feature_list,
sagemaker_session,
str(e),
e.__class__.__name__,
extra,
)
caught_ex = e
finally:
if caught_ex:
raise caught_ex
return response # pylint: disable=W0150
else:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: we can move the else branch on top to reduce to clean the code a little bit. For example:

if not sagemaker:
logger.debug(...)
return ...
logger.debug("sagemaker_session found, preparing to emit telemetry...")
...

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ack, Will do this in the follow-up PR for telemetry.

logger.debug(
"Unable to send telemetry for function %s. "
"sagemaker_session is not provided or not valid.",
func_name,
)
return func(*args, **kwargs)

return wrapper

return decorator


from sagemaker.session import Session # noqa: E402 pylint: disable=C0413


def _send_telemetry_request(
status: int,
feature_list: List[int],
Expand All@@ -165,9 +187,9 @@ def _send_telemetry_request(
# Send the telemetry request
logger.debug("Sending telemetry request to [%s]", url)
_requests_helper(url, 2)
logger.debug("SageMaker Python SDK telemetry successfully emitted!")
logger.debug("SageMaker Python SDK telemetry successfully emitted.")
except Exception: # pylint: disable=W0703
logger.debug("SageMaker Python SDK telemetry not emitted!!")
logger.debug("SageMaker Python SDK telemetry not emitted!")


def _construct_url(
Expand DownExpand Up@@ -196,9 +218,6 @@ def _construct_url(
return base_url


import requests # noqa: E402 pylint: disable=C0413,C0411


def _requests_helper(url, timeout):
"""Make a GET request to the given URL"""

Expand DownExpand Up@@ -227,3 +246,11 @@ def _get_region_or_default(session):
return session.boto_session.region_name
except Exception: # pylint: disable=W0703
return DEFAULT_AWS_REGION


def _get_default_sagemaker_session():
"""Return the default sagemaker session"""
boto_session = boto3.Session(region_name=DEFAULT_AWS_REGION)
sagemaker_session = Session(boto_session=boto_session)

return sagemaker_session
7 changes: 3 additions & 4 deletions tests/unit/sagemaker/remote_function/test_client.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,6 +15,7 @@
import os
import threading
import time
import inspect

import pytest
from mock import MagicMock, patch, Mock, ANY, call
Expand DownExpand Up@@ -1498,7 +1499,6 @@ def test_consistency_between_remote_and_step_decorator():
from sagemaker.workflow.function_step import step

remote_args_to_ignore = [
"_remote",
Comment thread
qidewenwhen marked this conversation as resolved.
"include_local_workdir",
"custom_file_filter",
"s3_kms_key",
Expand All@@ -1508,7 +1508,7 @@ def test_consistency_between_remote_and_step_decorator():

step_args_to_ignore = ["_step", "name", "display_name", "description", "retry_policies"]

remote_decorator_args = remote.__code__.co_varnames
remote_decorator_args = inspect.signature(remote).parameters.keys()
common_remote_decorator_args = set(remote_args_to_ignore) ^ set(remote_decorator_args)

step_decorator_args = step.__code__.co_varnames
Expand All@@ -1522,8 +1522,7 @@ def test_consistency_between_remote_and_executor():
executor_arg_list.remove("self")
executor_arg_list.remove("max_parallel_jobs")

remote_args_list = list(remote.__code__.co_varnames)
remote_args_list.remove("_remote")
remote_args_list = list(inspect.signature(remote).parameters.keys())
remote_args_list.remove("_func")

assert executor_arg_list == remote_args_list
18 changes: 18 additions & 0 deletions tests/unit/sagemaker/telemetry/test_telemetry_logging.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,6 +15,7 @@
import pytest
import requests
from unittest.mock import Mock, patch, MagicMock
import boto3
import sagemaker
from sagemaker.telemetry.constants import Feature
from sagemaker.telemetry.telemetry_logging import (
Expand All@@ -24,6 +25,7 @@
_get_accountId,
_requests_helper,
_get_region_or_default,
_get_default_sagemaker_session,
OS_NAME_VERSION,
PYTHON_VERSION,
)
Expand DownExpand Up@@ -282,3 +284,19 @@ def test_get_region_or_default_exception(self):
region = _get_region_or_default(mock_session)
assert region == "us-west-2"
assert "Error creating boto session" in str(exception)

@patch.object(boto3.Session, "region_name", "us-west-2")
def test_get_default_sagemaker_session(self):
sagemaker_session = _get_default_sagemaker_session()

assert isinstance(sagemaker_session, sagemaker.Session) is True
assert sagemaker_session.boto_session.region_name == "us-west-2"

@patch.object(boto3.Session, "region_name", None)
def test_get_default_sagemaker_session_with_no_region(self):
with self.assertRaises(ValueError) as context:
_get_default_sagemaker_session()

assert "Must setup local AWS configuration with a region supported by SageMaker." in str(
context.exception
)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions src/sagemaker/remote_function/client.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -40,6 +40,8 @@
from sagemaker.utils import name_from_base, base_from_name
from sagemaker.remote_function.spark_config import SparkConfig
from sagemaker.remote_function.custom_file_filter import CustomFileFilter
from sagemaker.telemetry.telemetry_logging import _telemetry_emitter
from sagemaker.telemetry.constants import Feature

_API_CALL_LIMIT = {
"SubmittingIntervalInSecs": 1,
Expand All@@ -57,6 +59,7 @@
logger = logging_config.get_logger()


@_telemetry_emitter(feature=Feature.REMOTE_FUNCTION, func_name="remote_function.remote")
Comment thread
qidewenwhen marked this conversation as resolved.
def remote(
_func=None,
*,
Expand Down
1 change: 1 addition & 0 deletions src/sagemaker/telemetry/constants.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,6 +24,7 @@ class Feature(Enum):

SDK_DEFAULTS = 1
LOCAL_MODE = 2
REMOTE_FUNCTION = 3

def __str__(self): # pylint: disable=E0307
"""Return the feature name."""
Expand Down
177 changes: 102 additions & 75 deletions src/sagemaker/telemetry/telemetry_logging.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,7 +17,11 @@
import sys
from time import perf_counter
from typing import List
import functools
import requests

import boto3
from sagemaker.session import Session
from sagemaker.utils import resolve_value_from_config
from sagemaker.config.config_schema import TELEMETRY_OPT_OUT_PATH
from sagemaker.telemetry.constants import (
Expand DownExpand Up@@ -47,6 +51,7 @@
FEATURE_TO_CODE = {
str(Feature.SDK_DEFAULTS): 1,
str(Feature.LOCAL_MODE): 2,
str(Feature.REMOTE_FUNCTION): 3,
}

STATUS_TO_CODE = {
Expand All@@ -59,86 +64,103 @@ def _telemetry_emitter(feature: str, func_name: str):
"""Decorator to emit telemetry logs for SageMaker Python SDK functions"""

def decorator(func):
def wrapper(self, *args, **kwargs):
logger.info(TELEMETRY_OPT_OUT_MESSAGING)
response = None
caught_ex = None
studio_app_type = process_studio_metadata_file()

# Check if telemetry is opted out
telemetry_opt_out_flag = resolve_value_from_config(
direct_input=None,
config_path=TELEMETRY_OPT_OUT_PATH,
default_value=False,
sagemaker_session=self.sagemaker_session,
)
logger.debug("TelemetryOptOut flag is set to: %s", telemetry_opt_out_flag)

# Construct the feature list to track feature combinations
feature_list: List[int] = [FEATURE_TO_CODE[str(feature)]]
if self.sagemaker_session:
if self.sagemaker_session.sagemaker_config and feature != Feature.SDK_DEFAULTS:
@functools.wraps(func)
def wrapper(*args, **kwargs):
sagemaker_session = None
if len(args) > 0 and hasattr(args[0], "sagemaker_session"):
# Get the sagemaker_session from the instance method args
sagemaker_session = args[0].sagemaker_session
elif feature == Feature.REMOTE_FUNCTION:
# Get the sagemaker_session from the function keyword arguments for remote function
sagemaker_session = kwargs.get(
"sagemaker_session", _get_default_sagemaker_session()
)

if sagemaker_session:
logger.debug("sagemaker_session found, preparing to emit telemetry...")
logger.info(TELEMETRY_OPT_OUT_MESSAGING)
response = None
caught_ex = None
studio_app_type = process_studio_metadata_file()

# Check if telemetry is opted out
telemetry_opt_out_flag = resolve_value_from_config(
direct_input=None,
config_path=TELEMETRY_OPT_OUT_PATH,
default_value=False,
sagemaker_session=sagemaker_session,
)
logger.debug("TelemetryOptOut flag is set to: %s", telemetry_opt_out_flag)

# Construct the feature list to track feature combinations
feature_list: List[int] = [FEATURE_TO_CODE[str(feature)]]

if sagemaker_session.sagemaker_config and feature != Feature.SDK_DEFAULTS:
feature_list.append(FEATURE_TO_CODE[str(Feature.SDK_DEFAULTS)])

if self.sagemaker_session.local_mode and feature != Feature.LOCAL_MODE:
if sagemaker_session.local_mode and feature != Feature.LOCAL_MODE:
feature_list.append(FEATURE_TO_CODE[str(Feature.LOCAL_MODE)])

# Construct the extra info to track platform and environment usage metadata
extra = (
f"{func_name}"
f"&x-sdkVersion={SDK_VERSION}"
f"&x-env={PYTHON_VERSION}"
f"&x-sys={OS_NAME_VERSION}"
f"&x-platform={studio_app_type}"
)

# Add endpoint ARN to the extra info if available
if self.sagemaker_session and self.sagemaker_session.endpoint_arn:
extra += f"&x-endpointArn={self.sagemaker_session.endpoint_arn}"

start_timer = perf_counter()
try:
# Call the original function
response = func(self, *args, **kwargs)
stop_timer = perf_counter()
elapsed = stop_timer - start_timer
extra += f"&x-latency={round(elapsed, 2)}"
if not telemetry_opt_out_flag:
_send_telemetry_request(
STATUS_TO_CODE[str(Status.SUCCESS)],
feature_list,
self.sagemaker_session,
None,
None,
extra,
)
except Exception as e: # pylint: disable=W0703
stop_timer = perf_counter()
elapsed = stop_timer - start_timer
extra += f"&x-latency={round(elapsed, 2)}"
if not telemetry_opt_out_flag:
_send_telemetry_request(
STATUS_TO_CODE[str(Status.FAILURE)],
feature_list,
self.sagemaker_session,
str(e),
e.__class__.__name__,
extra,
)
caught_ex = e
finally:
if caught_ex:
raise caught_ex
return response # pylint: disable=W0150
# Construct the extra info to track platform and environment usage metadata
extra = (
f"{func_name}"
f"&x-sdkVersion={SDK_VERSION}"
f"&x-env={PYTHON_VERSION}"
f"&x-sys={OS_NAME_VERSION}"
f"&x-platform={studio_app_type}"
)

# Add endpoint ARN to the extra info if available
if sagemaker_session.endpoint_arn:
extra += f"&x-endpointArn={sagemaker_session.endpoint_arn}"

start_timer = perf_counter()
try:
# Call the original function
response = func(*args, **kwargs)
stop_timer = perf_counter()
elapsed = stop_timer - start_timer
extra += f"&x-latency={round(elapsed, 2)}"
if not telemetry_opt_out_flag:
_send_telemetry_request(
STATUS_TO_CODE[str(Status.SUCCESS)],
feature_list,
sagemaker_session,
None,
None,
extra,
)
except Exception as e: # pylint: disable=W0703
stop_timer = perf_counter()
elapsed = stop_timer - start_timer
extra += f"&x-latency={round(elapsed, 2)}"
if not telemetry_opt_out_flag:
_send_telemetry_request(
STATUS_TO_CODE[str(Status.FAILURE)],
feature_list,
sagemaker_session,
str(e),
e.__class__.__name__,
extra,
)
caught_ex = e
finally:
if caught_ex:
raise caught_ex
return response # pylint: disable=W0150
else:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: we can move the else branch on top to reduce to clean the code a little bit. For example:

if not sagemaker:
logger.debug(...)
return ...
logger.debug("sagemaker_session found, preparing to emit telemetry...")
...

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ack, Will do this in the follow-up PR for telemetry.

logger.debug(
"Unable to send telemetry for function %s. "
"sagemaker_session is not provided or not valid.",
func_name,
)
return func(*args, **kwargs)

return wrapper

return decorator


from sagemaker.session import Session # noqa: E402 pylint: disable=C0413


def _send_telemetry_request(
status: int,
feature_list: List[int],
Expand All@@ -165,9 +187,9 @@ def _send_telemetry_request(
# Send the telemetry request
logger.debug("Sending telemetry request to [%s]", url)
_requests_helper(url, 2)
logger.debug("SageMaker Python SDK telemetry successfully emitted!")
logger.debug("SageMaker Python SDK telemetry successfully emitted.")
except Exception: # pylint: disable=W0703
logger.debug("SageMaker Python SDK telemetry not emitted!!")
logger.debug("SageMaker Python SDK telemetry not emitted!")


def _construct_url(
Expand DownExpand Up@@ -196,9 +218,6 @@ def _construct_url(
return base_url


import requests # noqa: E402 pylint: disable=C0413,C0411


def _requests_helper(url, timeout):
"""Make a GET request to the given URL"""

Expand DownExpand Up@@ -227,3 +246,11 @@ def _get_region_or_default(session):
return session.boto_session.region_name
except Exception: # pylint: disable=W0703
return DEFAULT_AWS_REGION


def _get_default_sagemaker_session():
"""Return the default sagemaker session"""
boto_session = boto3.Session(region_name=DEFAULT_AWS_REGION)
sagemaker_session = Session(boto_session=boto_session)

return sagemaker_session
7 changes: 3 additions & 4 deletions tests/unit/sagemaker/remote_function/test_client.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,6 +15,7 @@
import os
import threading
import time
import inspect

import pytest
from mock import MagicMock, patch, Mock, ANY, call
Expand DownExpand Up@@ -1498,7 +1499,6 @@ def test_consistency_between_remote_and_step_decorator():
from sagemaker.workflow.function_step import step

remote_args_to_ignore = [
"_remote",
Comment thread
qidewenwhen marked this conversation as resolved.
"include_local_workdir",
"custom_file_filter",
"s3_kms_key",
Expand All@@ -1508,7 +1508,7 @@ def test_consistency_between_remote_and_step_decorator():

step_args_to_ignore = ["_step", "name", "display_name", "description", "retry_policies"]

remote_decorator_args = remote.__code__.co_varnames
remote_decorator_args = inspect.signature(remote).parameters.keys()
common_remote_decorator_args = set(remote_args_to_ignore) ^ set(remote_decorator_args)

step_decorator_args = step.__code__.co_varnames
Expand All@@ -1522,8 +1522,7 @@ def test_consistency_between_remote_and_executor():
executor_arg_list.remove("self")
executor_arg_list.remove("max_parallel_jobs")

remote_args_list = list(remote.__code__.co_varnames)
remote_args_list.remove("_remote")
remote_args_list = list(inspect.signature(remote).parameters.keys())
remote_args_list.remove("_func")

assert executor_arg_list == remote_args_list
18 changes: 18 additions & 0 deletions tests/unit/sagemaker/telemetry/test_telemetry_logging.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,6 +15,7 @@
import pytest
import requests
from unittest.mock import Mock, patch, MagicMock
import boto3
import sagemaker
from sagemaker.telemetry.constants import Feature
from sagemaker.telemetry.telemetry_logging import (
Expand All@@ -24,6 +25,7 @@
_get_accountId,
_requests_helper,
_get_region_or_default,
_get_default_sagemaker_session,
OS_NAME_VERSION,
PYTHON_VERSION,
)
Expand DownExpand Up@@ -282,3 +284,19 @@ def test_get_region_or_default_exception(self):
region = _get_region_or_default(mock_session)
assert region == "us-west-2"
assert "Error creating boto session" in str(exception)

@patch.object(boto3.Session, "region_name", "us-west-2")
def test_get_default_sagemaker_session(self):
sagemaker_session = _get_default_sagemaker_session()

assert isinstance(sagemaker_session, sagemaker.Session) is True
assert sagemaker_session.boto_session.region_name == "us-west-2"

@patch.object(boto3.Session, "region_name", None)
def test_get_default_sagemaker_session_with_no_region(self):
with self.assertRaises(ValueError) as context:
_get_default_sagemaker_session()

assert "Must setup local AWS configuration with a region supported by SageMaker." in str(
context.exception
)