Skip to content

Commit 2dab97d

Browse files
committed
Add usage count info.
1 parent 4a7e3b6 commit 2dab97d

File tree

5 files changed

+182
-1
lines changed

5 files changed

+182
-1
lines changed

src/backend/backend.py

+6
Original file line numberDiff line numberDiff line change
@@ -100,3 +100,9 @@ def cid_file_initialized(self):
100100
101101
:return: True if client ID file is initialized, otherwise False.
102102
"""
103+
104+
@abc.abstractmethod
105+
def set_stats(self, data: dict):
106+
"""
107+
Pass additional statistic, which will be added to telemetry messages
108+
"""

src/backend/backend_ga4.py

+5
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ def __init__(self, tid: str = None, app_name: str = None, app_version: str = Non
4040
'app_name': self.app_name,
4141
'app_version': self.app_version,
4242
}
43+
self.stats = {}
4344

4445
def send(self, message: dict):
4546
if message is None:
@@ -90,6 +91,7 @@ def build_event_message(self, event_category: str, event_action: str, event_labe
9091
"event_count": event_value,
9192
"session_id": self.session_id,
9293
**default_args,
94+
**self.stats
9395
}
9496
}
9597
]
@@ -124,6 +126,9 @@ def remove_cid_file(self):
124126
remove_cid_file(self.cid_filename)
125127
remove_cid_file(self.old_cid_filename)
126128

129+
def set_stats(self, data: dict):
130+
self.stats = data
131+
127132

128133
def is_valid_cid(cid: str):
129134
try:

src/main.py

+25-1
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@
77
from enum import Enum
88

99
from .backend.backend import BackendRegistry
10-
from .utils.opt_in_checker import OptInChecker, ConsentCheckResult, DialogResult
1110
from .utils.sender import TelemetrySender
11+
from .utils.opt_in_checker import OptInChecker, ConsentCheckResult, DialogResult
12+
from .utils.stats_processor import StatsProcessor
1213

1314

1415
class OptInStatus(Enum):
@@ -76,6 +77,9 @@ def init(self, app_name: str = None, app_version: str = None, tid: str = None,
7677
if self.consent and not self.backend.cid_file_initialized():
7778
self.backend.generate_new_cid_file()
7879

80+
if self.consent:
81+
self.backend.set_stats(self.get_stats())
82+
7983
if not enable_opt_in_dialog and self.consent:
8084
# Try to create directory for client ID if it does not exist
8185
if not opt_in_checker.create_or_check_consent_dir():
@@ -264,6 +268,8 @@ def _update_opt_in_status(tid: str, new_opt_in_status: bool):
264268
if prev_status != OptInStatus.DECLINED:
265269
telemetry.send_opt_in_event(OptInStatus.DECLINED, prev_status, force_send=True)
266270
telemetry.backend.remove_cid_file()
271+
from .utils.stats_processor import StatsProcessor
272+
StatsProcessor().remove_stats_file()
267273
print("You have successfully opted out to send the telemetry data.")
268274

269275
def send_opt_in_event(self, new_state: OptInStatus, prev_state: OptInStatus = OptInStatus.UNDEFINED,
@@ -283,6 +289,24 @@ def send_opt_in_event(self, new_state: OptInStatus, prev_state: OptInStatus = Op
283289
label = "{{prev_state:{}, new_state: {}}}".format(prev_state.value, new_state.value)
284290
self.send_event("opt_in", new_state.value, label, force_send=force_send)
285291

292+
def get_stats(self):
293+
stats = StatsProcessor()
294+
file_exists, data = stats.get_stats()
295+
if not file_exists:
296+
created = stats.create_new_stats_file()
297+
data = {}
298+
if not created:
299+
return None
300+
if "usage_count" in data:
301+
usage_count = data["usage_count"]
302+
if usage_count < sys.maxsize:
303+
usage_count += 1
304+
else:
305+
usage_count = 1
306+
data["usage_count"] = usage_count
307+
stats.update_stats(data)
308+
return data
309+
286310
@staticmethod
287311
def opt_in(tid: str):
288312
"""

src/utils/stats_processor.py

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# Copyright (C) 2018-2024 Intel Corporation
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
from .opt_in_checker import OptInChecker
5+
import logging as log
6+
import os
7+
import json
8+
9+
10+
class StatsProcessor:
11+
def __init__(self):
12+
self.opt_in_checker = OptInChecker()
13+
14+
def stats_file(self):
15+
"""
16+
Returns the statistics file path.
17+
"""
18+
return os.path.join(self.opt_in_checker.consent_file_base_dir(), self.opt_in_checker.consent_file_subdirectory(), "stats")
19+
20+
def create_new_stats_file(self):
21+
"""
22+
Creates a new statistics file.
23+
:return: True if the file is created successfully, otherwise False
24+
"""
25+
if not self.opt_in_checker.create_or_check_consent_dir():
26+
return False
27+
try:
28+
open(self.stats_file(), 'w').close()
29+
except Exception:
30+
return False
31+
return True
32+
33+
def update_stats(self, stats: dict):
34+
"""
35+
Updates the statistics in the statistics file.
36+
:param stats: the dictionary with statistics.
37+
:return: False if the statistics file is not writable, otherwise True
38+
"""
39+
if self.opt_in_checker.consent_file_base_dir() is None or self.opt_in_checker.consent_file_subdirectory() is None:
40+
return False
41+
if not os.path.exists(self.stats_file()):
42+
if not self.create_new_stats_file():
43+
return False
44+
if not os.access(self.stats_file(), os.W_OK):
45+
log.warning("Failed to usage statistics. "
46+
"Please allow write access to the following file: {}".format(self.stats_file()))
47+
return False
48+
try:
49+
str_data = json.dumps(stats, indent=4)
50+
with open(self.stats_file(), 'w') as file:
51+
file.write(str_data)
52+
except Exception:
53+
return False
54+
return True
55+
56+
def get_stats(self):
57+
"""
58+
Gets information from statistics file.
59+
:return: the tuple, where the first element is True if the file is read successfully, otherwise False
60+
and the second element is the content of the statistics file.
61+
"""
62+
if not os.access(self.stats_file(), os.R_OK):
63+
return False, {}
64+
try:
65+
with open(self.stats_file(), 'r') as file:
66+
data = json.load(file)
67+
except Exception:
68+
return False, {}
69+
return True, data
70+
71+
def remove_stats_file(self):
72+
"""
73+
Removes statistics file.
74+
:return: None
75+
"""
76+
stats_file = self.stats_file()
77+
if os.path.exists(stats_file):
78+
if not os.access(stats_file, os.W_OK):
79+
log.warning("Failed to remove statistics file {}.".format(stats_file))
80+
return
81+
os.remove(stats_file)

src/utils/stats_processor_test.py

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Copyright (C) 2018-2024 Intel Corporation
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
import os
5+
import unittest
6+
from tempfile import TemporaryDirectory
7+
from unittest.mock import MagicMock, patch
8+
9+
from .stats_processor import StatsProcessor
10+
from .opt_in_checker import OptInChecker
11+
12+
13+
class StatsProcessorTest(unittest.TestCase):
14+
test_directory = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'test_stats')
15+
test_subdir = 'test_stats_subdir'
16+
stats_processor = StatsProcessor()
17+
18+
def init_stats_processor(self, test_directory):
19+
self.stats_processor.consent_file_base_dir = MagicMock(return_value=test_directory)
20+
self.stats_processor.consent_file_subdirectory = MagicMock(return_value=self.test_subdir)
21+
22+
def test_stats_usage(self):
23+
with TemporaryDirectory(prefix=self.test_directory) as test_dir:
24+
with patch.object(OptInChecker, 'consent_file_base_dir', return_value=test_dir):
25+
with patch.object(OptInChecker, 'consent_file_subdirectory', return_value=self.test_subdir):
26+
if not os.path.exists(test_dir):
27+
os.mkdir(test_dir)
28+
stats_filename = os.path.join(test_dir, self.test_subdir, 'stats')
29+
test_data1 = {"value1": 12, "value2": 7, "value3": 8}
30+
31+
# Test first creation of statistics file
32+
self.stats_processor.update_stats(test_data1)
33+
assert os.path.exists(stats_filename)
34+
with open(stats_filename, 'r') as file:
35+
assert file.readlines() == ['{\n', ' "value1": 12,\n', ' "value2": 7,\n', ' "value3": 8\n', '}']
36+
37+
status, res = self.stats_processor.get_stats()
38+
assert status
39+
assert res == test_data1
40+
41+
# Test updating of statistics file
42+
test_data2 = {"value1": 15, "a": "abs"}
43+
self.stats_processor.update_stats(test_data2)
44+
assert os.path.exists(stats_filename)
45+
with open(stats_filename, 'r') as file:
46+
assert file.readlines() == ['{\n', ' "value1": 15,\n', ' "a": "abs"\n', '}']
47+
48+
status, res = self.stats_processor.get_stats()
49+
assert status
50+
assert res == test_data2
51+
52+
# Test removing of statistics file
53+
self.stats_processor.remove_stats_file()
54+
assert not os.path.exists(stats_filename)
55+
56+
status, res = self.stats_processor.get_stats()
57+
assert not status
58+
assert res == {}
59+
60+
# Test attempt to read incorrect statistics file
61+
with open(stats_filename, 'w') as file:
62+
file.write("{ abc")
63+
status, res = self.stats_processor.get_stats()
64+
assert not status
65+
assert res == {}

0 commit comments

Comments
 (0)