Skip to content

Commit d664105

Browse files
authored
Add telemetry support (#144)
1 parent 4a4811e commit d664105

4 files changed

Lines changed: 580 additions & 479 deletions

File tree

pyproject.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "kup"
7-
version = "0.2.5"
7+
version = "0.2.6"
88
description = "kup is a tool for managing installations of the K framework along with the different available semantics"
99
readme = "README.md"
1010
requires-python = ">=3.10,<4"
@@ -15,6 +15,8 @@ dependencies = [
1515
"rich>=12.6.0,<13",
1616
"terminaltables>=3.1.10,<4",
1717
"tinynetrc>=1.3.1,<2",
18+
"tomli>=2.4.0",
19+
"tomli-w>=1.2.0",
1820
]
1921

2022
[[project.authors]]

src/kup/__main__.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
PackageName,
5353
PackageVersion,
5454
)
55+
from .telemetry import emit_event
5556

5657
if TYPE_CHECKING:
5758
from argparse import Namespace
@@ -199,7 +200,7 @@ def package_metadata_tree(p: PackageMetadata | Follows, lbl: str | None = None,
199200
follows = (' - follows [green]' + '/'.join(p.follows)) if type(p) == Follows else ''
200201
status = ''
201202
if show_status and type(p) == PackageMetadata:
202-
auth = {'Authorization': f'Bearer {os.getenv("GH_TOKEN")}'} if os.getenv('GH_TOKEN') else {}
203+
auth = {'Authorization': f"Bearer {os.getenv('GH_TOKEN')}"} if os.getenv('GH_TOKEN') else {}
203204
commits = requests.get(f'https://api.github.com/repos/{p.org}/{p.repo}/commits', headers=auth)
204205
if commits.ok:
205206
commits_list = [c['sha'] for c in commits.json()]
@@ -261,8 +262,8 @@ def reload_packages(load_versions: bool = True) -> None:
261262
if pinned.ok:
262263
pinned_package_cache = {r['name']: r['lastRevision']['storePath'] for r in pinned.json()}
263264

264-
if os.path.exists(f'{os.getenv("HOME")}/.nix-profile/manifest.json'):
265-
manifest_file = open(f'{os.getenv("HOME")}/.nix-profile/manifest.json')
265+
if os.path.exists(f"{os.getenv('HOME')}/.nix-profile/manifest.json"):
266+
manifest_file = open(f"{os.getenv('HOME')}/.nix-profile/manifest.json")
266267
manifest = json.loads(manifest_file.read())['elements']
267268
if type(manifest) is list:
268269
manifest = dict(enumerate(manifest))
@@ -360,7 +361,7 @@ def list_package(
360361
c['commit']['message'],
361362
tagged_releases[c['sha']]['name'] if c['sha'] in tagged_releases else None,
362363
c['commit']['committer']['date'],
363-
f'github:{listed_package.org}/{listed_package.repo}/{c["sha"]}#{listed_package.package_name}'
364+
f"github:{listed_package.org}/{listed_package.repo}/{c['sha']}#{listed_package.package_name}"
364365
in pinned_package_cache.keys(),
365366
)
366367
for c in commits.json()
@@ -487,6 +488,15 @@ def install_package(
487488
_, git_token_options = package.concrete_repo_path_with_access
488489
overrides = mk_override_args(package, package_overrides)
489490

491+
emit_event(
492+
'kup_install_start',
493+
{
494+
'package': package_name.base,
495+
'version': package_version,
496+
'has_overrides': len(package_overrides) > 0 if package_overrides else False,
497+
},
498+
)
499+
490500
if not overrides and package.uri in pinned_package_cache:
491501
rich.print(f" ⌛ Fetching cached version of '[green]{package_name.pretty_name}[/]' ...")
492502
nix(
@@ -531,6 +541,16 @@ def install_package(
531541
display_version = None
532542
display_version = f' ({display_version})' if display_version is not None else ''
533543

544+
emit_event(
545+
'kup_install_complete',
546+
{
547+
'package': package_name.base,
548+
'version': package_version or 'latest',
549+
'was_update': verb == 'updated',
550+
'from_cache': package.uri in pinned_package_cache and not overrides,
551+
},
552+
)
553+
534554
rich.print(
535555
f" ✅ Successfully {verb} '[green]{package_name.base}[/]' version [blue]{package.uri}{display_version}[/]."
536556
)

src/kup/telemetry.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
from __future__ import annotations
2+
3+
import logging
4+
import os
5+
import uuid
6+
from pathlib import Path
7+
from typing import Final
8+
9+
import requests
10+
import tomli
11+
import tomli_w
12+
13+
_LOGGER: Final = logging.getLogger(__name__)
14+
15+
KPROFILE_CONFIG_DIR: Final = Path.home() / '.config' / 'kprofile'
16+
KPROFILE_CONFIG_FILE: Final = KPROFILE_CONFIG_DIR / 'config.toml'
17+
TELEMETRY_MESSAGE: Final = (
18+
f'Telemetry: sending anonymous usage data. You can opt out by setting KPROFILE_TELEMETRY_DISABLED=true or consent=false in {KPROFILE_CONFIG_FILE}'
19+
)
20+
21+
22+
def _get_user_id() -> str:
23+
"""Get or create persistent anonymous user ID"""
24+
if not KPROFILE_CONFIG_FILE.exists():
25+
KPROFILE_CONFIG_FILE.parent.mkdir(parents=True, exist_ok=True)
26+
config = {'user': {'user_id': str(uuid.uuid4()), 'consent': True}}
27+
with open(KPROFILE_CONFIG_FILE, 'wb') as f:
28+
tomli_w.dump(config, f)
29+
return str(config['user']['user_id'])
30+
31+
with open(KPROFILE_CONFIG_FILE, 'rb') as f:
32+
config = tomli.load(f)
33+
34+
return str(config['user']['user_id'])
35+
36+
37+
def _has_permission() -> bool:
38+
"""Check if telemetry is enabled"""
39+
if os.getenv('KPROFILE_TELEMETRY_DISABLED', '').lower() == 'true':
40+
return False
41+
42+
_get_user_id()
43+
44+
with open(KPROFILE_CONFIG_FILE, 'rb') as f:
45+
config = tomli.load(f)
46+
47+
return config.get('user', {}).get('consent', True)
48+
49+
50+
def emit_event(event: str, properties: dict | None = None) -> None:
51+
"""Send telemetry event to proxy server"""
52+
if not _has_permission():
53+
return
54+
55+
_LOGGER.info(TELEMETRY_MESSAGE)
56+
57+
try:
58+
requests.post(
59+
'https://ojlk1fzi13.execute-api.us-east-1.amazonaws.com/dev/track',
60+
json={'user_id': _get_user_id(), 'event': event, 'properties': properties},
61+
timeout=2,
62+
)
63+
except requests.exceptions.ReadTimeout:
64+
_LOGGER.debug(f'Telemetry event timed out: {event}')
65+
except Exception as e:
66+
_LOGGER.warning(f'Telemetry event failed: {event}', exc_info=e)

0 commit comments

Comments
 (0)