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
2 changes: 1 addition & 1 deletion src/xpk/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ def _save_configs(self, config_yaml: dict) -> None:
with open(self._config, encoding='utf-8', mode='w') as stream:
yaml.dump(config_yaml, stream)

def set(self, key: str, value: str) -> None:
def set(self, key: str, value: str | None) -> None:
if key not in self._allowed_keys:
xpk_print(f'Key {key} is not an allowed xpk config key.')
return
Expand Down
42 changes: 30 additions & 12 deletions src/xpk/core/telemetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,17 +105,6 @@ def _clearcut_flush(file_path: str) -> None:
os.remove(file_path)


def ensure_client_id() -> str:
"""Generates Client ID and stores in configuration if not already present."""
current_client_id = xpk_config.get(CLIENT_ID_KEY)
if current_client_id is not None:
return current_client_id

new_client_id = str(uuid.uuid4())
xpk_config.set(CLIENT_ID_KEY, new_client_id)
return new_client_id


class MetricsEventMetadataKey(Enum):
SESSION_ID = "XPK_SESSION_ID"
DRY_RUN = "XPK_DRY_RUN"
Expand All @@ -125,6 +114,8 @@ class MetricsEventMetadataKey(Enum):
PROVISIONING_MODE = "XPK_PROVISIONING_MODE"
COMMAND = "XPK_COMMAND"
EXIT_CODE = "XPK_EXIT_CODE"
RUNNING_AS_PIP = "XPK_RUNNING_AS_PIP"
RUNNING_FROM_SOURCE = "XPK_RUNNING_FROM_SOURCE"


@dataclass
Expand Down Expand Up @@ -222,16 +213,43 @@ def _get_base_event_metadata() -> dict[MetricsEventMetadataKey, str]:
MetricsEventMetadataKey.SESSION_ID: _get_session_id(),
MetricsEventMetadataKey.DRY_RUN: str(is_dry_run()).lower(),
MetricsEventMetadataKey.PYTHON_VERSION: platform.python_version(),
MetricsEventMetadataKey.RUNNING_AS_PIP: str(_is_running_as_pip()).lower(),
MetricsEventMetadataKey.RUNNING_FROM_SOURCE: str(
_is_running_from_source()
).lower(),
}


def _get_base_concord_event() -> dict[str, str]:
return {
"release_version": xpk_version,
"console_type": "XPK",
"client_install_id": ensure_client_id(),
"client_install_id": _ensure_client_id(),
}


def _is_running_as_pip() -> bool:
return os.path.basename(sys.argv[0]) == "xpk"


def _is_running_from_source() -> bool:
current_path = os.path.abspath(os.path.realpath(__file__))
return (
"site-packages" not in current_path
and "dist-packages" not in current_path
)


def _get_session_id() -> str:
return str(uuid.uuid4())


def _ensure_client_id() -> str:
"""Generates Client ID and stores in configuration if not already present."""
current_client_id = xpk_config.get(CLIENT_ID_KEY)
if current_client_id is not None:
return current_client_id

new_client_id = str(uuid.uuid4())
xpk_config.set(CLIENT_ID_KEY, new_client_id)
return new_client_id
95 changes: 76 additions & 19 deletions src/xpk/core/telemetry_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,33 +17,24 @@
import pytest
import json
from .config import xpk_config, CLIENT_ID_KEY
from .telemetry import ensure_client_id, MetricsCollector, MetricsEventMetadataKey
from .telemetry import MetricsCollector, MetricsEventMetadataKey
from ..utils.execution_context import set_dry_run
from pytest_mock import MockerFixture


@pytest.fixture(autouse=True)
def setup_mocks(mocker):
def setup_mocks(mocker: MockerFixture):
mocker.patch('xpk.core.telemetry._get_session_id', return_value='321231')
mocker.patch('time.time', return_value=0)
mocker.patch('platform.python_version', return_value='99.99.99')
mocker.patch('os.path.basename', return_value='xpk.py')
mocker.patch('os.path.abspath', return_value='/home/xpk_user')
set_dry_run(False)
xpk_config.set(CLIENT_ID_KEY, 'client_id')
yield
xpk_config.set(CLIENT_ID_KEY, None)


def test_ensure_client_id_generates_client_id_when_its_not_present():
xpk_config.set(CLIENT_ID_KEY, None)
ensure_client_id()
assert xpk_config.get(CLIENT_ID_KEY) is not None


def test_ensure_client_id_does_not_regenerate_id_when_its_present():
client_id = '1337'
xpk_config.set(CLIENT_ID_KEY, client_id)
ensure_client_id()
assert xpk_config.get(CLIENT_ID_KEY) == client_id


def test_metrics_collector_generates_client_id_if_not_present():
xpk_config.set(CLIENT_ID_KEY, None)
MetricsCollector.log_start(command='test')
Expand All @@ -54,7 +45,6 @@ def test_metrics_collector_generates_client_id_if_not_present():


def test_metrics_collector_logs_start_event_correctly():
set_dry_run(False)
MetricsCollector.log_start(command='test')
payload = json.loads(MetricsCollector.flush())
extension_json = json.loads(payload['log_event'][0]['source_extension_json'])
Expand All @@ -65,6 +55,8 @@ def test_metrics_collector_logs_start_event_correctly():
{'key': 'XPK_SESSION_ID', 'value': '321231'},
{'key': 'XPK_DRY_RUN', 'value': 'false'},
{'key': 'XPK_PYTHON_VERSION', 'value': '99.99.99'},
{'key': 'XPK_RUNNING_AS_PIP', 'value': 'false'},
{'key': 'XPK_RUNNING_FROM_SOURCE', 'value': 'true'},
{'key': 'XPK_COMMAND', 'value': 'test'},
],
'event_name': 'start',
Expand All @@ -73,8 +65,16 @@ def test_metrics_collector_logs_start_event_correctly():
}


def test_metrics_collector_generates_client_id_when_not_present():
xpk_config.set(CLIENT_ID_KEY, None)
MetricsCollector.log_start(command='test')
payload = json.loads(MetricsCollector.flush())
extension_json = json.loads(payload['log_event'][0]['source_extension_json'])
assert extension_json['client_install_id'] is not None
assert len(extension_json['client_install_id']) > 0


def test_metrics_collector_logs_complete_event_correctly():
set_dry_run(True)
MetricsCollector.log_complete(exit_code=2)
payload = json.loads(MetricsCollector.flush())
extension_json = json.loads(payload['log_event'][0]['source_extension_json'])
Expand All @@ -83,8 +83,10 @@ def test_metrics_collector_logs_complete_event_correctly():
'console_type': 'XPK',
'event_metadata': [
{'key': 'XPK_SESSION_ID', 'value': '321231'},
{'key': 'XPK_DRY_RUN', 'value': 'true'},
{'key': 'XPK_DRY_RUN', 'value': 'false'},
{'key': 'XPK_PYTHON_VERSION', 'value': '99.99.99'},
{'key': 'XPK_RUNNING_AS_PIP', 'value': 'false'},
{'key': 'XPK_RUNNING_FROM_SOURCE', 'value': 'true'},
{'key': 'XPK_EXIT_CODE', 'value': '2'},
],
'event_name': 'complete',
Expand All @@ -94,7 +96,6 @@ def test_metrics_collector_logs_complete_event_correctly():


def test_metrics_collector_logs_custom_event_correctly():
set_dry_run(False)
MetricsCollector.log_custom(
name='test', metadata={MetricsEventMetadataKey.PROVISIONING_MODE: 'flex'}
)
Expand All @@ -107,6 +108,8 @@ def test_metrics_collector_logs_custom_event_correctly():
{'key': 'XPK_SESSION_ID', 'value': '321231'},
{'key': 'XPK_DRY_RUN', 'value': 'false'},
{'key': 'XPK_PYTHON_VERSION', 'value': '99.99.99'},
{'key': 'XPK_RUNNING_AS_PIP', 'value': 'false'},
{'key': 'XPK_RUNNING_FROM_SOURCE', 'value': 'true'},
{'key': 'XPK_PROVISIONING_MODE', 'value': 'flex'},
],
'event_name': 'test',
Expand Down Expand Up @@ -134,3 +137,57 @@ def test_metrics_collector_does_not_flush_event_twice():
MetricsCollector.log_start(command='version')
payload = json.loads(MetricsCollector.flush())
assert len(payload['log_event']) == 1


@pytest.mark.parametrize(
argnames='dry_run,expected', argvalues=[(False, 'false'), (True, 'true')]
)
def test_metrics_collector_logs_correct_dry_run_value(
dry_run: bool, expected: str
):
set_dry_run(dry_run)
MetricsCollector.log_start(command='test')
payload = MetricsCollector.flush()
assert _get_metadata_value(payload, 'XPK_DRY_RUN') == expected


@pytest.mark.parametrize(
argnames='basename,expected',
argvalues=[
('xpk', 'true'),
('xpk.py', 'false'),
],
)
def test_metrics_collectors_logs_correct_running_as_pip_value(
basename: str, expected: str, mocker: MockerFixture
):
mocker.patch('os.path.basename', return_value=basename)
MetricsCollector.log_start(command='test')
payload = MetricsCollector.flush()
assert _get_metadata_value(payload, 'XPK_RUNNING_AS_PIP') == expected


@pytest.mark.parametrize(
argnames='abspath,expected',
argvalues=[
('/site-packages/', 'false'),
('/dist-packages/', 'false'),
('/home/xpk_user', 'true'),
],
)
def test_metrics_collectors_logs_correct_running_from_source_value(
abspath: str, expected: str, mocker: MockerFixture
):
mocker.patch('os.path.abspath', return_value=abspath)
MetricsCollector.log_start(command='test')
payload = MetricsCollector.flush()
assert _get_metadata_value(payload, 'XPK_RUNNING_FROM_SOURCE') == expected


def _get_metadata_value(payload_str: str, key: str) -> str | None:
payload = json.loads(payload_str)
metadata = json.loads(payload['log_event'][0]['source_extension_json'])[
'event_metadata'
]
matching = (item['value'] for item in metadata if item['key'] == key)
return next(matching, None)
Loading