diff --git a/awscrt/mqtt.py b/awscrt/mqtt.py index 7529168ea..675f57688 100644 --- a/awscrt/mqtt.py +++ b/awscrt/mqtt.py @@ -15,7 +15,7 @@ from awscrt.http import HttpProxyOptions, HttpRequest from awscrt.io import ClientBootstrap, ClientTlsContext, SocketOptions from dataclasses import dataclass -from awscrt.mqtt5 import Client as Mqtt5Client +from awscrt.mqtt5 import Client as Mqtt5Client, _get_awsiot_metrics_str class QoS(IntEnum): @@ -330,6 +330,8 @@ class Connection(NativeResource): proxy_options (Optional[awscrt.http.HttpProxyOptions]): Optional proxy options for all connections. + + enable_aws_metrics (bool): If true, append AWS IoT metrics to the username. (Default to true) """ def __init__(self, @@ -355,7 +357,8 @@ def __init__(self, proxy_options=None, on_connection_success=None, on_connection_failure=None, - on_connection_closed=None + on_connection_closed=None, + enable_aws_metrics=True ): assert isinstance(client, Client) or isinstance(client, Mqtt5Client) @@ -404,6 +407,11 @@ def __init__(self, self.ping_timeout_ms = ping_timeout_ms self.protocol_operation_timeout_ms = protocol_operation_timeout_ms self.will = will + + if enable_aws_metrics: + username = username if username else "" + username += _get_awsiot_metrics_str(username) + self.username = username self.password = password self.socket_options = socket_options if socket_options else SocketOptions() diff --git a/awscrt/mqtt5.py b/awscrt/mqtt5.py index 7c5e4f31f..5d1eba7f4 100644 --- a/awscrt/mqtt5.py +++ b/awscrt/mqtt5.py @@ -16,6 +16,40 @@ from collections.abc import Sequence from inspect import signature +# Global variable to cache metrics string +_metrics_str = None + + +def _get_awsiot_metrics_str(current_username=""): + global _metrics_str + + username_has_query = False + if current_username.find("?") != -1: + username_has_query = True + # The SDK query is already set, skip adding it again + if username_has_query and current_username.find("SDK=") != -1: + return "" + + if _metrics_str is None: + try: + import importlib.metadata + try: + version = importlib.metadata.version("awscrt") + _metrics_str = "SDK=CRTPython&Version={}&Platform={}".format( + version, _awscrt.get_platform_build_os_string()) + except importlib.metadata.PackageNotFoundError: + _metrics_str = "SDK=CRTPython&Version=dev&Platform={}".format(_awscrt.get_platform_build_os_string()) + except BaseException: + _metrics_str = "" + + if not _metrics_str == "": + if username_has_query: + return "&" + _metrics_str + else: + return "?" + _metrics_str + else: + return "" + class QoS(IntEnum): """MQTT message delivery quality of service. @@ -1338,6 +1372,7 @@ class ClientOptions: on_lifecycle_event_connection_success_fn (Callable[[LifecycleConnectSuccessData],]): Callback for Lifecycle Event Connection Success. on_lifecycle_event_connection_failure_fn (Callable[[LifecycleConnectFailureData],]): Callback for Lifecycle Event Connection Failure. on_lifecycle_event_disconnection_fn (Callable[[LifecycleDisconnectData],]): Callback for Lifecycle Event Disconnection. + enable_aws_metrics (bool): Whether to append AWS IoT metrics to the username field during CONNECT. Default: True """ host_name: str port: int = None @@ -1364,6 +1399,7 @@ class ClientOptions: on_lifecycle_event_connection_success_fn: Callable[[LifecycleConnectSuccessData], None] = None on_lifecycle_event_connection_failure_fn: Callable[[LifecycleConnectFailureData], None] = None on_lifecycle_event_disconnection_fn: Callable[[LifecycleDisconnectData], None] = None + enable_aws_metrics: bool = True def _check_callback(callback): @@ -1753,6 +1789,11 @@ def __init__(self, client_options: ClientOptions): is_will_none = False will = connect_options.will + username = connect_options.username + if client_options.enable_aws_metrics: + username = username if username else "" + username += _get_awsiot_metrics_str(username) + connect_options.username = username websocket_is_none = client_options.websocket_handshake_transform is None self.tls_ctx = client_options.tls_ctx self._binding = _awscrt.mqtt5_client_new(self, diff --git a/crt/aws-c-common b/crt/aws-c-common index 31578beb2..7a67e7748 160000 --- a/crt/aws-c-common +++ b/crt/aws-c-common @@ -1 +1 @@ -Subproject commit 31578beb2309330fece3fb3a66035a568a2641e7 +Subproject commit 7a67e7748644ea01161215dc457165f96259dd8e diff --git a/source/common.c b/source/common.c index cd1b19ef6..236fc2945 100644 --- a/source/common.c +++ b/source/common.c @@ -26,6 +26,14 @@ PyObject *aws_py_get_cpu_count_for_group(PyObject *self, PyObject *args) { return PyLong_FromSize_t(count); } +PyObject *aws_py_get_platform_build_os_string(PyObject *self, PyObject *args) { + (void)self; + (void)args; + + struct aws_byte_cursor os_string = aws_get_platform_build_os_string(); + return PyUnicode_FromAwsByteCursor(&os_string); +} + PyObject *aws_py_thread_join_all_managed(PyObject *self, PyObject *args) { (void)self; diff --git a/source/common.h b/source/common.h index 48df0cf6f..45a23d09b 100644 --- a/source/common.h +++ b/source/common.h @@ -13,6 +13,7 @@ PyObject *aws_py_get_cpu_group_count(PyObject *self, PyObject *args); PyObject *aws_py_get_cpu_count_for_group(PyObject *self, PyObject *args); +PyObject *aws_py_get_platform_build_os_string(PyObject *self, PyObject *args); PyObject *aws_py_thread_join_all_managed(PyObject *self, PyObject *args); diff --git a/source/module.c b/source/module.c index be4afdca1..b8919064c 100644 --- a/source/module.c +++ b/source/module.c @@ -767,6 +767,7 @@ static PyMethodDef s_module_methods[] = { AWS_PY_METHOD_DEF(get_corresponding_builtin_exception, METH_VARARGS), AWS_PY_METHOD_DEF(get_cpu_group_count, METH_VARARGS), AWS_PY_METHOD_DEF(get_cpu_count_for_group, METH_VARARGS), + AWS_PY_METHOD_DEF(get_platform_build_os_string, METH_VARARGS), AWS_PY_METHOD_DEF(native_memory_usage, METH_NOARGS), AWS_PY_METHOD_DEF(native_memory_dump, METH_NOARGS), AWS_PY_METHOD_DEF(thread_join_all_managed, METH_VARARGS), diff --git a/test/test_mqtt.py b/test/test_mqtt.py index f8435316e..ef54b6d94 100644 --- a/test/test_mqtt.py +++ b/test/test_mqtt.py @@ -629,7 +629,8 @@ def _test_mqtt311_direct_connect_basic_auth(self): host_name=input_host_name, port=input_port, username=input_username, - password=input_password) + password=input_password, + enable_aws_metrics=False) # Disable AWS metrics for basic auth on non-AWS broker connection.connect().result(TIMEOUT) connection.disconnect().result(TIMEOUT) @@ -760,7 +761,8 @@ def sign_function(transform_args, **kwargs): username=input_username, password=input_password, use_websockets=True, - websocket_handshake_transform=sign_function) + websocket_handshake_transform=sign_function, + enable_aws_metrics=False) # Disable AWS metrics for basic auth on non-AWS broker connection.connect().result(TIMEOUT) connection.disconnect().result(TIMEOUT) diff --git a/test/test_mqtt5.py b/test/test_mqtt5.py index 83ee5928a..db0a5f73b 100644 --- a/test/test_mqtt5.py +++ b/test/test_mqtt5.py @@ -229,7 +229,8 @@ def _test_direct_connect_basic_auth(self): client_options = mqtt5.ClientOptions( host_name=input_host_name, port=input_port, - connect_options=connect_options + connect_options=connect_options, + enable_aws_metrics=False # Disable AWS metrics for basic auth on non-AWS broker ) callbacks = Mqtt5TestCallbacks() client = self._create_client(client_options=client_options, callbacks=callbacks) @@ -416,7 +417,8 @@ def _test_websocket_connect_basic_auth(self): client_options = mqtt5.ClientOptions( host_name=input_host_name, port=input_port, - connect_options=connect_options + connect_options=connect_options, + enable_aws_metrics=False # Disable AWS metrics for basic auth on non-AWS broker ) callbacks = Mqtt5TestCallbacks() client_options.websocket_handshake_transform = callbacks.ws_handshake_transform @@ -615,7 +617,8 @@ def test_connect_with_incorrect_basic_authentication_credentials(self): client_options = mqtt5.ClientOptions( host_name=input_host_name, port=input_port, - connect_options=connect_options + connect_options=connect_options, + enable_aws_metrics=False # Disable AWS metrics for basic auth on non-AWS broker ) callbacks = Mqtt5TestCallbacks() client = self._create_client(client_options=client_options, callbacks=callbacks)