[Python] Migrate datetime metric to UniFFI

This commit is contained in:
Jan-Erik Rediger 2022-02-14 11:52:13 +01:00 коммит произвёл Jan-Erik Rediger
Родитель 58e0774e9f
Коммит 8a7bd23dc6
3 изменённых файлов: 80 добавлений и 145 удалений

Просмотреть файл

@ -4,23 +4,20 @@
import datetime
import sys
from typing import List, Optional
from typing import Optional
from .. import _ffi
from .._dispatcher import Dispatcher
from .._uniffi import DatetimeMetric
from .._uniffi import CommonMetricData, TimeUnit, Datetime
from ..testing import ErrorType
from .lifetime import Lifetime
from .timeunit import TimeUnit
if sys.version_info < (3, 7):
import iso8601 # type: ignore
else:
iso8601 = None
def tzoffset(offset):
"""
Create a timezone from the given offset (in seconds) from UTC.
"""
delta = datetime.timedelta(seconds=offset)
return datetime.timezone(delta)
class DatetimeMetricType:
@ -34,31 +31,8 @@ class DatetimeMetricType:
The datetime API only exposes the `DatetimeMetricType.set` method.
"""
def __init__(
self,
disabled: bool,
category: str,
lifetime: Lifetime,
name: str,
send_in_pings: List[str],
time_unit: TimeUnit,
):
self._disabled = disabled
self._send_in_pings = send_in_pings
self._handle = _ffi.lib.glean_new_datetime_metric(
_ffi.ffi_encode_string(category),
_ffi.ffi_encode_string(name),
_ffi.ffi_encode_vec_string(send_in_pings),
len(send_in_pings),
lifetime.value,
disabled,
time_unit.value,
)
def __del__(self):
if getattr(self, "_handle", 0) != 0:
_ffi.lib.glean_destroy_datetime_metric(self._handle)
def __init__(self, common_metric_data: CommonMetricData, time_unit: TimeUnit):
self._inner = DatetimeMetric(common_metric_data, time_unit)
def set(self, value: Optional[datetime.datetime] = None) -> None:
"""
@ -68,53 +42,34 @@ class DatetimeMetricType:
value (datetime.datetime): (default: now) The `datetime.datetime`
value to set. If not provided, will record the current time.
"""
if self._disabled:
return
if value is None:
value = datetime.datetime.now()
# now at UTC -> astimezone gives us a time with the local timezone.
value = datetime.datetime.now(datetime.timezone.utc).astimezone()
@Dispatcher.launch
def set():
tzinfo = value.tzinfo
if tzinfo is not None:
offset = tzinfo.utcoffset(value).seconds
tzinfo = value.tzinfo
if tzinfo is not None:
utcoff = tzinfo.utcoffset(value)
if utcoff is not None:
offset = utcoff.seconds
else:
offset = 0
_ffi.lib.glean_datetime_set(
self._handle,
value.year,
value.month,
value.day,
value.hour,
value.minute,
value.second,
value.microsecond * 1000,
offset,
)
else:
offset = 0
def test_has_value(self, ping_name: Optional[str] = None) -> bool:
"""
Tests whether a value is stored for the metric for testing purposes
only.
Args:
ping_name (str): (default: first value in send_in_pings) The name
of the ping to retrieve the metric for.
Returns:
has_value (bool): True if the metric value exists.
"""
if ping_name is None:
ping_name = self._send_in_pings[0]
return bool(
_ffi.lib.glean_datetime_test_has_value(
self._handle, _ffi.ffi_encode_string(ping_name)
)
dt = Datetime(
year=value.year,
month=value.month,
day=value.day,
hour=value.hour,
minute=value.minute,
second=value.second,
nanosecond=value.microsecond * 1000,
offset_seconds=offset,
)
def test_get_value_as_str(self, ping_name: Optional[str] = None) -> str:
self._inner.set(dt)
def test_get_value_as_str(self, ping_name: Optional[str] = None) -> Optional[str]:
"""
Returns the stored value for testing purposes only, as an ISO8601 string.
@ -125,19 +80,15 @@ class DatetimeMetricType:
Returns:
value (str): value of the stored metric.
"""
if ping_name is None:
ping_name = self._send_in_pings[0]
dt = self.test_get_value(ping_name)
if not dt:
return None
if not self.test_has_value(ping_name):
raise ValueError("metric has no value")
return dt.isoformat()
return _ffi.ffi_decode_string(
_ffi.lib.glean_datetime_test_get_value_as_string(
self._handle, _ffi.ffi_encode_string(ping_name)
)
)
def test_get_value(self, ping_name: Optional[str] = None) -> datetime.datetime:
def test_get_value(
self, ping_name: Optional[str] = None
) -> Optional[datetime.datetime]:
"""
Returns the stored value for testing purposes only.
@ -148,36 +99,27 @@ class DatetimeMetricType:
Returns:
value (datetime.datetime): value of the stored metric.
"""
if sys.version_info < (3, 7):
return iso8601.parse_date(self.test_get_value_as_str(ping_name))
else:
return datetime.datetime.fromisoformat(
self.test_get_value_as_str(ping_name)
)
dt = self._inner.test_get_value(ping_name)
if not dt:
return None
tz = tzoffset(dt.offset_seconds)
dt = datetime.datetime(
year=dt.year,
month=dt.month,
day=dt.day,
hour=dt.hour,
minute=dt.minute,
second=dt.second,
microsecond=round(dt.nanosecond / 1000),
tzinfo=tz,
)
return dt
def test_get_num_recorded_errors(
self, error_type: ErrorType, ping_name: Optional[str] = None
) -> int:
"""
Returns the number of errors recorded for the given metric.
Args:
error_type (ErrorType): The type of error recorded.
ping_name (str): (default: first value in send_in_pings) The name
of the ping to retrieve the metric for.
Returns:
num_errors (int): The number of errors recorded for the metric for
the given error type.
"""
if ping_name is None:
ping_name = self._send_in_pings[0]
return _ffi.lib.glean_datetime_test_get_num_recorded_errors(
self._handle,
error_type.value,
_ffi.ffi_encode_string(ping_name),
)
return self._inner.test_get_num_recorded_errors(error_type, ping_name)
__all__ = ["DatetimeMetricType"]

Просмотреть файл

@ -61,7 +61,6 @@ version = "41.1.1"
requirements = [
"cffi>=1.13.0",
"glean_parser==4.0.0",
"iso8601>=0.1.10; python_version<='3.6'",
]
setup_requirements = ["cffi>=1.13.0"]

Просмотреть файл

@ -2,74 +2,68 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import datetime
import pytest
from glean import metrics
from glean.metrics import Lifetime
from glean.metrics import Lifetime, CommonMetricData
def test_the_api_saves_to_its_storage_engine():
# Define a datetime metric, which will be stored in "store1"
datetime_metric = metrics.DatetimeMetricType(
disabled=False,
category="telemetry",
lifetime=Lifetime.APPLICATION,
name="datetime_metric",
send_in_pings=["store1"],
CommonMetricData(
disabled=False,
category="telemetry",
lifetime=Lifetime.APPLICATION,
name="datetime_metric",
send_in_pings=["store1"],
dynamic_label=None,
),
time_unit=metrics.TimeUnit.MINUTE,
)
assert datetime_metric.test_has_value() is False
assert datetime_metric.test_get_value() is None
datetime_metric.set()
assert datetime_metric.test_has_value() is True
assert datetime_metric.test_get_value()
value = datetime.datetime(
2004, 12, 9, 8, 3, 29, tzinfo=datetime.timezone(datetime.timedelta(hours=16))
)
datetime_metric.set(value)
assert datetime_metric.test_has_value() is True
assert "2004-12-09T08:03+16:00" == datetime_metric.test_get_value_as_str()
assert "2004-12-09T08:03:00+16:00" == datetime_metric.test_get_value_as_str()
assert value.replace(second=0) == datetime_metric.test_get_value()
value2 = datetime.datetime(1993, 2, 23, 5, 43, tzinfo=datetime.timezone.utc)
datetime_metric.set(value2)
assert datetime_metric.test_has_value() is True
assert "1993-02-23T05:43+00:00" == datetime_metric.test_get_value_as_str()
assert "1993-02-23T05:43:00+00:00" == datetime_metric.test_get_value_as_str()
assert value2.replace(second=0) == datetime_metric.test_get_value()
# A date prior to the UNIX epoch
value3 = datetime.datetime(1969, 8, 20, 17, 3, tzinfo=datetime.timezone.utc)
datetime_metric.set(value3)
assert datetime_metric.test_has_value() is True
assert "1969-08-20T17:03+00:00" == datetime_metric.test_get_value_as_str()
assert "1969-08-20T17:03:00+00:00" == datetime_metric.test_get_value_as_str()
assert value3.replace(second=0) == datetime_metric.test_get_value()
# A date following 2038 (the extent of 32-bits after the UNIX epoch
value4 = datetime.datetime(2039, 7, 20, 20, 17, 3, tzinfo=datetime.timezone.utc)
datetime_metric.set(value4)
assert datetime_metric.test_has_value() is True
assert "2039-07-20T20:17+00:00" == datetime_metric.test_get_value_as_str()
assert "2039-07-20T20:17:00+00:00" == datetime_metric.test_get_value_as_str()
assert value4.replace(second=0) == datetime_metric.test_get_value()
def test_disabled_datetimes_must_not_record_data():
datetime_metric = metrics.DatetimeMetricType(
disabled=True,
category="telemetry",
lifetime=Lifetime.APPLICATION,
name="datetime_metric",
send_in_pings=["store1"],
CommonMetricData(
disabled=True,
category="telemetry",
lifetime=Lifetime.APPLICATION,
name="datetime_metric",
send_in_pings=["store1"],
dynamic_label=None,
),
time_unit=metrics.TimeUnit.MINUTE,
)
datetime_metric.set()
assert False is datetime_metric.test_has_value()
with pytest.raises(ValueError):
datetime_metric.test_get_value()
assert datetime_metric.test_get_value() is None