Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python: Implement Datetime metric type #732

Merged
merged 3 commits into from
Feb 25, 2020
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 .dictionary
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
personal_ws-1.1 en 151 utf-8
personal_ws-1.1 en 154 utf-8
AAR
AARs
APIs
Expand Down
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
[Full changelog](https://github.com/mozilla/glean/compare/v25.0.0...master)

* Python:
* The boolean metric type is now supported in Python.
* The Boolean and Datetime metric types are now supported in Python.

# v25.0.0 (2020-02-17)

Expand Down
33 changes: 33 additions & 0 deletions docs/user/metrics/datetime.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,38 @@ XCTAssertEqual(1, Install.firstRun.getNumRecordedErrors(.invalidValue))

</div>

<div data-lang="Python" class="tab">

```Python
import datetime

from glean import load_metrics
metrics = load_metrics("metrics.yaml")

# Records "now"
metrics.install.first_run.set()
# Records a custom datetime
metrics.install.first_run.set(datetime.datetime(2019, 3, 25))
```

There are test APIs available too.

```Python
# Was anything recorded?
assert metrics.install.first_run.test_has_value()

# Was it the expected value?
# NOTE: Datetimes always include a timezone offset from UTC, hence the
# "-05:00" suffix.
assert "2019-03-25-05:00" == metrics.install.first_run.test_get_value_as_str()
# Was the value invalid?
assert 1 == metrics.install.test_get_num_recorded_errors(
ErrorType.INVALID_VALUE
)
```

</div>

{{#include ../../tab_footer.md}}

## Limits
Expand All @@ -134,3 +166,4 @@ XCTAssertEqual(1, Install.firstRun.getNumRecordedErrors(.invalidValue))

* [Kotlin API docs](../../../javadoc/glean/mozilla.telemetry.glean.private/-datetime-metric-type/index.html)
* [Swift API docs](../../../swift/Classes/DatetimeMetricType.html)
* [Python API docs](../../../python/glean/metrics/datetime.html)
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,35 @@ package mozilla.telemetry.glean.private

/**
* Enumeration of different resolutions supported by
* the Timespan and TimingDistribution metric types.
* the Timespan and DateTime metric types.
*/
enum class TimeUnit {
/**
* Represents a nanosecond precision.
* Represents nanosecond precision.
*/
Nanosecond,
/**
* Represents a microsecond precision.
* Represents microsecond precision.
*/
Microsecond,
/**
* Represents a millisecond precision.
* Represents millisecond precision.
*/
Millisecond,
/**
* Represents a second precision.
* Represents second precision.
*/
Second,
/**
* Represents a minute precision.
* Represents minute precision.
*/
Minute,
/**
* Represents a hour precision.
* Represents hour precision.
*/
Hour,
/**
* Represents a day precision.
* Represents day precision.
*/
Day,
}
14 changes: 7 additions & 7 deletions glean-core/ios/Glean/Metrics/TimeUnit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,24 @@ import Foundation
/// Enumeration of different resolutions supported by
/// the `TimespanMetricType` and `TimingDistributionMetricType`.
public enum TimeUnit: Int32 {
/// Represents a nanosecond precision.
/// Represents nanosecond precision.
case nanosecond = 0

/// Represents a microsecond precision.
/// Represents microsecond precision.
case microsecond = 1

/// Represents a millisecond precision.
/// Represents millisecond precision.
case millisecond = 2

/// Represents a second precision.
/// Represents second precision.
case second = 3

/// Represents a minute precision.
/// Represents minute precision.
case minute = 4

/// Represents a hour precision.
/// Represents hour precision.
case hour = 5

/// Represents a day precision.
/// Represents day precision.
case day = 6
}
1 change: 1 addition & 0 deletions glean-core/python/glean/_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
_TYPE_MAPPING = {
"boolean": metrics.BooleanMetricType,
"counter": metrics.CounterMetricType,
"datetime": metrics.DatetimeMetricType,
"event": metrics.EventMetricType,
"labeled_counter": metrics.LabeledCounterMetricType,
"ping": metrics.PingType,
Expand Down
4 changes: 4 additions & 0 deletions glean-core/python/glean/metrics/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,28 @@

from .boolean import BooleanMetricType
from .counter import CounterMetricType
from .datetime import DatetimeMetricType
from .event import EventMetricType, RecordedEventData
from .experiment import RecordedExperimentData
from .labeled import LabeledCounterMetricType
from .lifetime import Lifetime
from .ping import PingType
from .string import StringMetricType
from .timeunit import TimeUnit
from .uuid import UuidMetricType


__all__ = [
"BooleanMetricType",
"CounterMetricType",
"DatetimeMetricType",
"EventMetricType",
"LabeledCounterMetricType",
"Lifetime",
"PingType",
"RecordedEventData",
"RecordedExperimentData",
"StringMetricType",
"TimeUnit",
"UuidMetricType",
]
172 changes: 172 additions & 0 deletions glean-core/python/glean/metrics/datetime.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# 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
from typing import List, Optional


import iso8601 # type: ignore


from .. import _ffi
from .._dispatcher import Dispatcher
from ..testing import ErrorType


from .lifetime import Lifetime
from .timeunit import TimeUnit


class DatetimeMetricType:
"""
This implements the developer facing API for recording datetime metrics.

Instances of this class type are automatically generated by
`glean.load_metrics`, allowing developers to record values that were
previously registered in the metrics.yaml file.

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 set(self, value: Optional[datetime.datetime] = None):
"""
Set a datetime value, truncating it to the metric's resolution.

Args:
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()

@Dispatcher.launch
def set():
tzinfo = value.tzinfo
if tzinfo is not None:
offset = tzinfo.utcoffset(value).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,
)

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)
)
)

def test_get_value_as_str(self, ping_name: Optional[str] = None) -> str:
"""
Returns the stored value for testing purposes only, as an ISO8601 string.

Args:
ping_name (str): (default: first value in send_in_pings) The name
of the ping to retrieve the metric for.

Returns:
value (str): value of the stored metric.
"""
if ping_name is None:
ping_name = self._send_in_pings[0]

if not self.test_has_value(ping_name):
raise ValueError("metric has no value")

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:
"""
Returns the stored value 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:
value (datetime.datetime): value of the stored metric.
"""
return iso8601.parse_date(self.test_get_value_as_str(ping_name))

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),
)


__all__ = ["DatetimeMetricType"]
47 changes: 47 additions & 0 deletions glean-core/python/glean/metrics/timeunit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# 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/.


from enum import IntEnum


class TimeUnit(IntEnum):
"""
An enumeration of different resolutions supported by the `glean.metrics.DateTime` metric type.
"""

NANOSECOND = 0
"""
Represents nanosecond precision.
"""

MICROSECOND = 1
"""
Represents microsecond precision.
"""

MILLISECOND = 2
"""
Represents millisecond precision.
"""

SECOND = 3
"""
Represents second precision.
"""

MINUTE = 4
"""
Represents minute precision.
"""

HOUR = 5
"""
Represents hour precision.
"""

DAY = 6
"""
Represents day precision.
"""
Loading