pyglove 0.5.0.dev202510020810__py3-none-any.whl → 0.5.0.dev202512280810__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pyglove might be problematic. Click here for more details.
- pyglove/core/geno/base.py +7 -3
- pyglove/core/io/file_system.py +452 -2
- pyglove/core/io/file_system_test.py +442 -0
- pyglove/core/monitoring.py +213 -90
- pyglove/core/monitoring_test.py +82 -29
- pyglove/core/symbolic/__init__.py +7 -0
- pyglove/core/symbolic/base.py +89 -35
- pyglove/core/symbolic/base_test.py +3 -3
- pyglove/core/symbolic/dict.py +31 -12
- pyglove/core/symbolic/dict_test.py +49 -0
- pyglove/core/symbolic/list.py +17 -3
- pyglove/core/symbolic/list_test.py +24 -2
- pyglove/core/symbolic/object.py +3 -1
- pyglove/core/symbolic/object_test.py +13 -10
- pyglove/core/symbolic/ref.py +19 -7
- pyglove/core/symbolic/ref_test.py +94 -7
- pyglove/core/symbolic/unknown_symbols.py +147 -0
- pyglove/core/symbolic/unknown_symbols_test.py +100 -0
- pyglove/core/typing/annotation_conversion.py +8 -1
- pyglove/core/typing/annotation_conversion_test.py +14 -19
- pyglove/core/typing/class_schema.py +24 -1
- pyglove/core/typing/json_schema.py +221 -8
- pyglove/core/typing/json_schema_test.py +508 -12
- pyglove/core/typing/type_conversion.py +17 -3
- pyglove/core/typing/type_conversion_test.py +7 -2
- pyglove/core/typing/value_specs.py +5 -1
- pyglove/core/typing/value_specs_test.py +5 -0
- pyglove/core/utils/__init__.py +1 -0
- pyglove/core/utils/contextual.py +9 -4
- pyglove/core/utils/contextual_test.py +10 -0
- pyglove/core/utils/json_conversion.py +360 -63
- pyglove/core/utils/json_conversion_test.py +146 -13
- pyglove/core/views/html/controls/tab.py +33 -0
- pyglove/core/views/html/controls/tab_test.py +37 -0
- pyglove/ext/evolution/base_test.py +1 -1
- {pyglove-0.5.0.dev202510020810.dist-info → pyglove-0.5.0.dev202512280810.dist-info}/METADATA +8 -1
- {pyglove-0.5.0.dev202510020810.dist-info → pyglove-0.5.0.dev202512280810.dist-info}/RECORD +40 -38
- {pyglove-0.5.0.dev202510020810.dist-info → pyglove-0.5.0.dev202512280810.dist-info}/WHEEL +0 -0
- {pyglove-0.5.0.dev202510020810.dist-info → pyglove-0.5.0.dev202512280810.dist-info}/licenses/LICENSE +0 -0
- {pyglove-0.5.0.dev202510020810.dist-info → pyglove-0.5.0.dev202512280810.dist-info}/top_level.txt +0 -0
pyglove/core/monitoring.py
CHANGED
|
@@ -14,7 +14,17 @@
|
|
|
14
14
|
"""Pluggable metric systems for monitoring.
|
|
15
15
|
|
|
16
16
|
This module allows PyGlove to plugin metrics to monitor the execution of
|
|
17
|
-
programs.
|
|
17
|
+
programs. There are three common metrics for monitoring: counters, scalars, and
|
|
18
|
+
distributions.
|
|
19
|
+
|
|
20
|
+
* Counters are metrics that track the number of times an event occurs. It's
|
|
21
|
+
monotonically increasing over time.
|
|
22
|
+
|
|
23
|
+
* Scalars are metrics that track a single value at a given time, for example,
|
|
24
|
+
available memory size. It does not accumulate over time like counters.
|
|
25
|
+
|
|
26
|
+
* Distributions are metrics that track the distribution of a numerical value.
|
|
27
|
+
For example, the latency of an operation.
|
|
18
28
|
"""
|
|
19
29
|
|
|
20
30
|
import abc
|
|
@@ -24,8 +34,8 @@ import math
|
|
|
24
34
|
import threading
|
|
25
35
|
import time
|
|
26
36
|
import typing
|
|
27
|
-
from typing import Any,
|
|
28
|
-
|
|
37
|
+
from typing import Any, Dict, Generic, Iterator, List, Optional, Tuple, Type, TypeVar, Union
|
|
38
|
+
from pyglove.core.utils import error_utils
|
|
29
39
|
|
|
30
40
|
try:
|
|
31
41
|
import numpy # pylint: disable=g-import-not-at-top
|
|
@@ -33,7 +43,10 @@ except ImportError:
|
|
|
33
43
|
numpy = None
|
|
34
44
|
|
|
35
45
|
|
|
36
|
-
|
|
46
|
+
MetricValueType = TypeVar('MetricValueType')
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class Metric(Generic[MetricValueType], metaclass=abc.ABCMeta):
|
|
37
50
|
"""Base class for metrics."""
|
|
38
51
|
|
|
39
52
|
def __init__(
|
|
@@ -41,12 +54,23 @@ class Metric(metaclass=abc.ABCMeta):
|
|
|
41
54
|
namespace: str,
|
|
42
55
|
name: str,
|
|
43
56
|
description: str,
|
|
44
|
-
parameter_definitions: Dict[str, Type[Union[int, str, bool]]]
|
|
57
|
+
parameter_definitions: Dict[str, Type[Union[int, str, bool]]],
|
|
58
|
+
**additional_flags,
|
|
45
59
|
) -> None:
|
|
60
|
+
"""Initializes the metric.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
namespace: The namespace of the metric.
|
|
64
|
+
name: The name of the metric.
|
|
65
|
+
description: The description of the metric.
|
|
66
|
+
parameter_definitions: The definitions of the parameters for the metric.
|
|
67
|
+
**additional_flags: Additional flags for the metric.
|
|
68
|
+
"""
|
|
46
69
|
self._namespace = namespace
|
|
47
70
|
self._name = name
|
|
48
71
|
self._description = description
|
|
49
72
|
self._parameter_definitions = parameter_definitions
|
|
73
|
+
self._flags = additional_flags
|
|
50
74
|
|
|
51
75
|
@property
|
|
52
76
|
def namespace(self) -> str:
|
|
@@ -73,6 +97,11 @@ class Metric(metaclass=abc.ABCMeta):
|
|
|
73
97
|
"""Returns the parameter definitions of the metric."""
|
|
74
98
|
return self._parameter_definitions
|
|
75
99
|
|
|
100
|
+
@property
|
|
101
|
+
def flags(self) -> Dict[str, Any]:
|
|
102
|
+
"""Returns the flags of the metric."""
|
|
103
|
+
return self._flags
|
|
104
|
+
|
|
76
105
|
def _parameters_key(self, **parameters) -> Tuple[Any, ...]:
|
|
77
106
|
"""Returns the parameters tuple for the metric."""
|
|
78
107
|
for k, t in self._parameter_definitions.items():
|
|
@@ -96,9 +125,24 @@ class Metric(metaclass=abc.ABCMeta):
|
|
|
96
125
|
)
|
|
97
126
|
return tuple(parameters[k] for k in self._parameter_definitions)
|
|
98
127
|
|
|
128
|
+
@abc.abstractmethod
|
|
129
|
+
def value(self, **parameters) -> MetricValueType:
|
|
130
|
+
"""Returns the value of the metric for the given parameters.
|
|
99
131
|
|
|
100
|
-
|
|
101
|
-
|
|
132
|
+
Args:
|
|
133
|
+
**parameters: Parameters for parameterized counters.
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
The value of the metric.
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class Counter(Metric[int]):
|
|
141
|
+
"""Base class for counters.
|
|
142
|
+
|
|
143
|
+
Counters are metrics that track the number of times an event occurs. It's
|
|
144
|
+
monotonically increasing over time.
|
|
145
|
+
"""
|
|
102
146
|
|
|
103
147
|
@abc.abstractmethod
|
|
104
148
|
def increment(self, delta: int = 1, **parameters) -> int:
|
|
@@ -112,20 +156,42 @@ class Counter(Metric):
|
|
|
112
156
|
The new value of the counter.
|
|
113
157
|
"""
|
|
114
158
|
|
|
159
|
+
|
|
160
|
+
class Scalar(Metric[MetricValueType]):
|
|
161
|
+
"""Base class for scalar values.
|
|
162
|
+
|
|
163
|
+
Scalar values are metrics that track a single value at a given time, for
|
|
164
|
+
example, available memory size. It does not accumulate over time like
|
|
165
|
+
counters.
|
|
166
|
+
"""
|
|
167
|
+
|
|
115
168
|
@abc.abstractmethod
|
|
116
|
-
def
|
|
117
|
-
"""
|
|
169
|
+
def set(self, value: MetricValueType, **parameters) -> None:
|
|
170
|
+
"""Sets the value of the scalar.
|
|
118
171
|
|
|
119
172
|
Args:
|
|
173
|
+
value: The value to record.
|
|
174
|
+
**parameters: Parameters for parameterized scalars.
|
|
175
|
+
"""
|
|
176
|
+
|
|
177
|
+
@abc.abstractmethod
|
|
178
|
+
def increment(
|
|
179
|
+
self,
|
|
180
|
+
delta: MetricValueType = 1, **parameters
|
|
181
|
+
) -> MetricValueType:
|
|
182
|
+
"""Increments the scalar by delta and returns the new value.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
delta: The amount to increment the counter by.
|
|
120
186
|
**parameters: Parameters for parameterized counters.
|
|
121
187
|
|
|
122
188
|
Returns:
|
|
123
|
-
The value of the
|
|
189
|
+
The new value of the metric.
|
|
124
190
|
"""
|
|
125
191
|
|
|
126
192
|
|
|
127
|
-
class
|
|
128
|
-
"""
|
|
193
|
+
class DistributionValue(metaclass=abc.ABCMeta):
|
|
194
|
+
"""Base for distribution value."""
|
|
129
195
|
|
|
130
196
|
@property
|
|
131
197
|
@abc.abstractmethod
|
|
@@ -173,43 +239,62 @@ class Distribution(metaclass=abc.ABCMeta):
|
|
|
173
239
|
"""Returns the fraction of values in the distribution less than value."""
|
|
174
240
|
|
|
175
241
|
|
|
176
|
-
class
|
|
177
|
-
"""Base class for
|
|
242
|
+
class Distribution(Metric[DistributionValue]):
|
|
243
|
+
"""Base class for distributional metrics.
|
|
244
|
+
|
|
245
|
+
Distributions are metrics that track the distribution of a numerical value.
|
|
246
|
+
For example, the latency of an operation.
|
|
247
|
+
"""
|
|
178
248
|
|
|
179
249
|
@abc.abstractmethod
|
|
180
|
-
def record(self, value:
|
|
181
|
-
"""Records a value to the
|
|
250
|
+
def record(self, value: float, **parameters) -> None:
|
|
251
|
+
"""Records a value to the distribution.
|
|
182
252
|
|
|
183
253
|
Args:
|
|
184
254
|
value: The value to record.
|
|
185
|
-
**parameters: Parameters for parameterized
|
|
255
|
+
**parameters: Parameters for parameterized distributions.
|
|
186
256
|
"""
|
|
187
257
|
|
|
188
|
-
@
|
|
189
|
-
def
|
|
190
|
-
|
|
258
|
+
@contextlib.contextmanager
|
|
259
|
+
def record_duration(
|
|
260
|
+
self,
|
|
261
|
+
*,
|
|
262
|
+
scale: int = 1000,
|
|
263
|
+
error_parameter: str = 'error',
|
|
264
|
+
**parameters) -> Iterator[None]:
|
|
265
|
+
"""Context manager that records the duration of code block.
|
|
191
266
|
|
|
192
267
|
Args:
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
268
|
+
scale: The scale of the duration.
|
|
269
|
+
error_parameter: The parameter name for recording the error. If the name
|
|
270
|
+
is not defined as a parameter for the distribution, the error tag will
|
|
271
|
+
not be recorded.
|
|
272
|
+
**parameters: Parameters for parameterized distributions.
|
|
197
273
|
"""
|
|
198
|
-
|
|
199
|
-
@contextlib.contextmanager
|
|
200
|
-
def record_duration(self, scale: int = 1000, **parameters) -> Iterator[None]:
|
|
201
|
-
"""Context manager that records the duration of code block to the scalar."""
|
|
202
274
|
start_time = time.time()
|
|
275
|
+
error = None
|
|
203
276
|
try:
|
|
204
277
|
yield
|
|
278
|
+
except BaseException as e:
|
|
279
|
+
error = e
|
|
280
|
+
raise e
|
|
205
281
|
finally:
|
|
206
282
|
duration = (time.time() - start_time) * scale
|
|
207
|
-
self.
|
|
283
|
+
if error_parameter in self._parameter_definitions:
|
|
284
|
+
parameters[error_parameter] = (
|
|
285
|
+
error_utils.ErrorInfo.from_exception(error).tag
|
|
286
|
+
if error is not None else ''
|
|
287
|
+
)
|
|
288
|
+
self.record(duration, **parameters)
|
|
208
289
|
|
|
209
290
|
|
|
210
291
|
class MetricCollection(metaclass=abc.ABCMeta):
|
|
211
292
|
"""Base class for counter collections."""
|
|
212
293
|
|
|
294
|
+
_COUNTER_CLASS = Counter
|
|
295
|
+
_SCALAR_CLASS = Scalar
|
|
296
|
+
_DISTRIBUTION_CLASS = Distribution
|
|
297
|
+
|
|
213
298
|
def __init__(
|
|
214
299
|
self,
|
|
215
300
|
namespace: str,
|
|
@@ -244,13 +329,10 @@ class MetricCollection(metaclass=abc.ABCMeta):
|
|
|
244
329
|
def _get_or_create_metric(
|
|
245
330
|
self,
|
|
246
331
|
metric_cls: Type[Metric],
|
|
247
|
-
create_metric_fn: Callable[
|
|
248
|
-
[str, str, Dict[str, Type[Union[int, str, bool]]]],
|
|
249
|
-
Metric
|
|
250
|
-
],
|
|
251
332
|
name: str,
|
|
252
333
|
description: str,
|
|
253
|
-
parameter_definitions: Dict[str, Type[Union[int, str, bool]]]
|
|
334
|
+
parameter_definitions: Dict[str, Type[Union[int, str, bool]]],
|
|
335
|
+
**additional_flags,
|
|
254
336
|
) -> Metric:
|
|
255
337
|
"""Gets or creates a metric with the given name."""
|
|
256
338
|
full_name = f'{self._namespace}/{name}'
|
|
@@ -272,7 +354,13 @@ class MetricCollection(metaclass=abc.ABCMeta):
|
|
|
272
354
|
f'definitions ({metric.parameter_definitions!r}).'
|
|
273
355
|
)
|
|
274
356
|
else:
|
|
275
|
-
metric =
|
|
357
|
+
metric = metric_cls(
|
|
358
|
+
self.namespace,
|
|
359
|
+
name,
|
|
360
|
+
description,
|
|
361
|
+
parameter_definitions,
|
|
362
|
+
**additional_flags
|
|
363
|
+
)
|
|
276
364
|
self._metrics[full_name] = metric
|
|
277
365
|
return metric
|
|
278
366
|
|
|
@@ -281,7 +369,7 @@ class MetricCollection(metaclass=abc.ABCMeta):
|
|
|
281
369
|
name: str,
|
|
282
370
|
description: str,
|
|
283
371
|
parameters: Optional[Dict[str, Type[Union[int, str, bool]]]] = None,
|
|
284
|
-
**
|
|
372
|
+
**additional_flags
|
|
285
373
|
) -> Counter:
|
|
286
374
|
"""Gets or creates a counter with the given name.
|
|
287
375
|
|
|
@@ -290,7 +378,9 @@ class MetricCollection(metaclass=abc.ABCMeta):
|
|
|
290
378
|
description: The description of the counter.
|
|
291
379
|
parameters: The definitions of the parameters for the counter.
|
|
292
380
|
`default_parameters` from the collection will be used if not specified.
|
|
293
|
-
**
|
|
381
|
+
**additional_flags: Additional arguments for creating the counter.
|
|
382
|
+
Subclasses can use these arguments to provide additional information for
|
|
383
|
+
creating the counter.
|
|
294
384
|
|
|
295
385
|
Returns:
|
|
296
386
|
The counter with the given name.
|
|
@@ -298,28 +388,23 @@ class MetricCollection(metaclass=abc.ABCMeta):
|
|
|
298
388
|
if parameters is None:
|
|
299
389
|
parameters = self._default_parameter_definitions
|
|
300
390
|
return typing.cast(
|
|
301
|
-
Counter,
|
|
302
|
-
|
|
303
|
-
|
|
391
|
+
Counter,
|
|
392
|
+
self._get_or_create_metric(
|
|
393
|
+
self._COUNTER_CLASS,
|
|
394
|
+
name,
|
|
395
|
+
description,
|
|
396
|
+
parameters,
|
|
397
|
+
**additional_flags
|
|
304
398
|
)
|
|
305
399
|
)
|
|
306
400
|
|
|
307
|
-
@abc.abstractmethod
|
|
308
|
-
def _create_counter(
|
|
309
|
-
self,
|
|
310
|
-
name: str,
|
|
311
|
-
description: str,
|
|
312
|
-
parameter_definitions: Dict[str, Type[Union[int, str, bool]]],
|
|
313
|
-
**kwargs
|
|
314
|
-
) -> Counter:
|
|
315
|
-
"""Creates a counter with the given name."""
|
|
316
|
-
|
|
317
401
|
def get_scalar(
|
|
318
402
|
self,
|
|
319
403
|
name: str,
|
|
320
404
|
description: str,
|
|
321
405
|
parameters: Optional[Dict[str, Type[Union[int, str, bool]]]] = None,
|
|
322
|
-
|
|
406
|
+
value_type: Type[Union[int, float]] = int,
|
|
407
|
+
**additional_flags
|
|
323
408
|
) -> Scalar:
|
|
324
409
|
"""Gets or creates a scalar with the given name.
|
|
325
410
|
|
|
@@ -328,7 +413,8 @@ class MetricCollection(metaclass=abc.ABCMeta):
|
|
|
328
413
|
description: The description of the counter.
|
|
329
414
|
parameters: The definitions of the parameters for the counter.
|
|
330
415
|
`default_parameters` from the collection will be used if not specified.
|
|
331
|
-
|
|
416
|
+
value_type: The type of the value for the scalar.
|
|
417
|
+
**additional_flags: Additional arguments for creating the scalar.
|
|
332
418
|
|
|
333
419
|
Returns:
|
|
334
420
|
The counter with the given name.
|
|
@@ -338,19 +424,46 @@ class MetricCollection(metaclass=abc.ABCMeta):
|
|
|
338
424
|
return typing.cast(
|
|
339
425
|
Scalar,
|
|
340
426
|
self._get_or_create_metric(
|
|
341
|
-
|
|
427
|
+
self._SCALAR_CLASS,
|
|
428
|
+
name,
|
|
429
|
+
description,
|
|
430
|
+
parameters,
|
|
431
|
+
value_type=value_type,
|
|
432
|
+
**additional_flags
|
|
342
433
|
)
|
|
343
434
|
)
|
|
344
435
|
|
|
345
|
-
|
|
346
|
-
def _create_scalar(
|
|
436
|
+
def get_distribution(
|
|
347
437
|
self,
|
|
348
438
|
name: str,
|
|
349
439
|
description: str,
|
|
350
|
-
|
|
351
|
-
**
|
|
352
|
-
) ->
|
|
353
|
-
"""
|
|
440
|
+
parameters: Optional[Dict[str, Type[Union[int, str, bool]]]] = None,
|
|
441
|
+
**additional_flags
|
|
442
|
+
) -> Distribution:
|
|
443
|
+
"""Gets or creates a distribution with the given name.
|
|
444
|
+
|
|
445
|
+
Args:
|
|
446
|
+
name: The name of the distribution.
|
|
447
|
+
description: The description of the distribution.
|
|
448
|
+
parameters: The definitions of the parameters for the distribution.
|
|
449
|
+
`default_parameters` from the collection will be used if not specified.
|
|
450
|
+
**additional_flags: Additional arguments for creating the distribution.
|
|
451
|
+
|
|
452
|
+
Returns:
|
|
453
|
+
The distribution with the given name.
|
|
454
|
+
"""
|
|
455
|
+
if parameters is None:
|
|
456
|
+
parameters = self._default_parameter_definitions
|
|
457
|
+
return typing.cast(
|
|
458
|
+
Distribution,
|
|
459
|
+
self._get_or_create_metric(
|
|
460
|
+
self._DISTRIBUTION_CLASS,
|
|
461
|
+
name,
|
|
462
|
+
description,
|
|
463
|
+
parameters,
|
|
464
|
+
**additional_flags
|
|
465
|
+
)
|
|
466
|
+
)
|
|
354
467
|
|
|
355
468
|
#
|
|
356
469
|
# InMemoryMetricCollection.
|
|
@@ -379,14 +492,46 @@ class _InMemoryCounter(Counter):
|
|
|
379
492
|
return self._counter[self._parameters_key(**parameters)]
|
|
380
493
|
|
|
381
494
|
|
|
382
|
-
class _InMemoryScalar(Scalar):
|
|
495
|
+
class _InMemoryScalar(Scalar[MetricValueType]):
|
|
383
496
|
"""In-memory scalar."""
|
|
384
497
|
|
|
498
|
+
def __init__(self, *args, **kwargs):
|
|
499
|
+
super().__init__(*args, **kwargs)
|
|
500
|
+
self._values = collections.defaultdict(self.flags['value_type'])
|
|
501
|
+
self._lock = threading.Lock()
|
|
502
|
+
|
|
503
|
+
def increment(
|
|
504
|
+
self,
|
|
505
|
+
delta: MetricValueType = 1,
|
|
506
|
+
**parameters
|
|
507
|
+
) -> MetricValueType:
|
|
508
|
+
"""Increments the scalar by delta and returns the new value."""
|
|
509
|
+
parameters_key = self._parameters_key(**parameters)
|
|
510
|
+
with self._lock:
|
|
511
|
+
value = self._values[parameters_key]
|
|
512
|
+
value += delta
|
|
513
|
+
self._values[parameters_key] = value
|
|
514
|
+
return value
|
|
515
|
+
|
|
516
|
+
def set(self, value: MetricValueType, **parameters) -> None:
|
|
517
|
+
"""Sets the value of the scalar."""
|
|
518
|
+
parameters_key = self._parameters_key(**parameters)
|
|
519
|
+
with self._lock:
|
|
520
|
+
self._values[parameters_key] = value
|
|
521
|
+
|
|
522
|
+
def value(self, **parameters) -> MetricValueType:
|
|
523
|
+
"""Returns the distribution of the scalar."""
|
|
524
|
+
return self._values[self._parameters_key(**parameters)]
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
class _InMemoryDistribution(Distribution):
|
|
528
|
+
"""In-memory distribution."""
|
|
529
|
+
|
|
385
530
|
def __init__(self, *args, window_size: int = 1024 * 1024, **kwargs):
|
|
386
531
|
super().__init__(*args, **kwargs)
|
|
387
532
|
self._window_size = window_size
|
|
388
533
|
self._distributions = collections.defaultdict(
|
|
389
|
-
lambda:
|
|
534
|
+
lambda: _InMemoryDistributionValue(self._window_size)
|
|
390
535
|
)
|
|
391
536
|
self._lock = threading.Lock()
|
|
392
537
|
|
|
@@ -395,14 +540,13 @@ class _InMemoryScalar(Scalar):
|
|
|
395
540
|
parameters_key = self._parameters_key(**parameters)
|
|
396
541
|
self._distributions[parameters_key].add(value)
|
|
397
542
|
|
|
398
|
-
def
|
|
543
|
+
def value(self, **parameters) -> DistributionValue:
|
|
399
544
|
"""Returns the distribution of the scalar."""
|
|
400
|
-
|
|
401
|
-
return self._distributions[parameters_key]
|
|
545
|
+
return self._distributions[self._parameters_key(**parameters)]
|
|
402
546
|
|
|
403
547
|
|
|
404
|
-
class
|
|
405
|
-
"""In memory distribution
|
|
548
|
+
class _InMemoryDistributionValue(DistributionValue):
|
|
549
|
+
"""In memory distribution value."""
|
|
406
550
|
|
|
407
551
|
def __init__(self, window_size: int = 1024 * 1024):
|
|
408
552
|
self._window_size = window_size
|
|
@@ -489,30 +633,9 @@ class _InMemoryDistribution(Distribution):
|
|
|
489
633
|
class InMemoryMetricCollection(MetricCollection):
|
|
490
634
|
"""In-memory counter."""
|
|
491
635
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
description: str,
|
|
496
|
-
parameter_definitions: Dict[str, Type[Union[int, str, bool]]],
|
|
497
|
-
**kwargs
|
|
498
|
-
) -> Counter:
|
|
499
|
-
return _InMemoryCounter(
|
|
500
|
-
self._namespace, name, description, parameter_definitions
|
|
501
|
-
)
|
|
502
|
-
|
|
503
|
-
def _create_scalar(
|
|
504
|
-
self,
|
|
505
|
-
name: str,
|
|
506
|
-
description: str,
|
|
507
|
-
parameter_definitions: Dict[str, Type[Union[int, str, bool]]],
|
|
508
|
-
*,
|
|
509
|
-
window_size: int = 1024 * 1024,
|
|
510
|
-
**kwargs
|
|
511
|
-
) -> Scalar:
|
|
512
|
-
return _InMemoryScalar(
|
|
513
|
-
self._namespace, name, description, parameter_definitions,
|
|
514
|
-
window_size=window_size, **kwargs
|
|
515
|
-
)
|
|
636
|
+
_COUNTER_CLASS = _InMemoryCounter
|
|
637
|
+
_SCALAR_CLASS = _InMemoryScalar
|
|
638
|
+
_DISTRIBUTION_CLASS = _InMemoryDistribution
|
|
516
639
|
|
|
517
640
|
|
|
518
641
|
_METRIC_COLLECTION_CLS = InMemoryMetricCollection # pylint: disable=invalid-name
|
pyglove/core/monitoring_test.py
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import time
|
|
16
16
|
import unittest
|
|
17
17
|
from pyglove.core import monitoring
|
|
18
|
+
from pyglove.core.symbolic import error_info # pylint: disable=unused-import
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
class MetricCollectionTest(unittest.TestCase):
|
|
@@ -54,7 +55,7 @@ class MetricCollectionTest(unittest.TestCase):
|
|
|
54
55
|
with self.assertRaisesRegex(
|
|
55
56
|
ValueError, 'Metric .* already exists with a different type'
|
|
56
57
|
):
|
|
57
|
-
collection.
|
|
58
|
+
collection.get_distribution('counter', 'counter description')
|
|
58
59
|
|
|
59
60
|
with self.assertRaisesRegex(
|
|
60
61
|
ValueError, 'Metric .* already exists with a different description'
|
|
@@ -70,11 +71,11 @@ class MetricCollectionTest(unittest.TestCase):
|
|
|
70
71
|
)
|
|
71
72
|
|
|
72
73
|
|
|
73
|
-
class
|
|
74
|
-
"""Tests for in memory distribution."""
|
|
74
|
+
class InMemoryDistributionValueTest(unittest.TestCase):
|
|
75
|
+
"""Tests for in memory distribution value."""
|
|
75
76
|
|
|
76
77
|
def test_empty_distribution(self):
|
|
77
|
-
dist = monitoring.
|
|
78
|
+
dist = monitoring._InMemoryDistributionValue()
|
|
78
79
|
self.assertEqual(dist.count, 0)
|
|
79
80
|
self.assertEqual(dist.sum, 0.0)
|
|
80
81
|
self.assertEqual(dist.mean, 0.0)
|
|
@@ -85,7 +86,7 @@ class InMemoryDistributionTest(unittest.TestCase):
|
|
|
85
86
|
self.assertEqual(dist.fraction_less_than(100), 0.0)
|
|
86
87
|
|
|
87
88
|
def test_add_value(self):
|
|
88
|
-
dist = monitoring.
|
|
89
|
+
dist = monitoring._InMemoryDistributionValue()
|
|
89
90
|
dist.add(1)
|
|
90
91
|
dist.add(3)
|
|
91
92
|
dist.add(10)
|
|
@@ -105,7 +106,7 @@ class InMemoryDistributionTest(unittest.TestCase):
|
|
|
105
106
|
def test_add_value_no_numpy(self):
|
|
106
107
|
numpy = monitoring.numpy
|
|
107
108
|
monitoring.numpy = None
|
|
108
|
-
dist = monitoring.
|
|
109
|
+
dist = monitoring._InMemoryDistributionValue()
|
|
109
110
|
dist.add(1)
|
|
110
111
|
dist.add(3)
|
|
111
112
|
dist.add(10)
|
|
@@ -124,7 +125,7 @@ class InMemoryDistributionTest(unittest.TestCase):
|
|
|
124
125
|
monitoring.numpy = numpy
|
|
125
126
|
|
|
126
127
|
def test_window_size(self):
|
|
127
|
-
dist = monitoring.
|
|
128
|
+
dist = monitoring._InMemoryDistributionValue(window_size=3)
|
|
128
129
|
dist.add(1)
|
|
129
130
|
dist.add(3)
|
|
130
131
|
dist.add(10)
|
|
@@ -198,38 +199,90 @@ class InMemoryScalarTest(unittest.TestCase):
|
|
|
198
199
|
self.assertEqual(scalar.description, 'scalar description')
|
|
199
200
|
self.assertEqual(scalar.parameter_definitions, {})
|
|
200
201
|
self.assertEqual(scalar.full_name, '/test/scalar')
|
|
201
|
-
|
|
202
|
-
self.assertEqual(
|
|
203
|
-
scalar.
|
|
204
|
-
scalar.
|
|
205
|
-
scalar.
|
|
206
|
-
scalar.
|
|
207
|
-
self.assertEqual(dist.count, 3)
|
|
208
|
-
|
|
209
|
-
scalar = collection.get_scalar('scalar2', 'scalar description')
|
|
210
|
-
with scalar.record_duration():
|
|
211
|
-
time.sleep(0.1)
|
|
212
|
-
self.assertGreaterEqual(scalar.distribution().mean, 100)
|
|
202
|
+
self.assertEqual(scalar.value(), 0)
|
|
203
|
+
self.assertEqual(scalar.increment(), 1)
|
|
204
|
+
self.assertEqual(scalar.value(), 1)
|
|
205
|
+
scalar.set(3)
|
|
206
|
+
self.assertEqual(scalar.increment(2), 5)
|
|
207
|
+
self.assertEqual(scalar.value(), 5)
|
|
213
208
|
|
|
214
209
|
def test_scalar_with_parameters(self):
|
|
215
210
|
collection = monitoring.InMemoryMetricCollection('/test')
|
|
216
211
|
scalar = collection.get_scalar(
|
|
217
|
-
'scalar', 'scalar description', {'field1': str}
|
|
212
|
+
'scalar', 'scalar description', {'field1': str}, float
|
|
218
213
|
)
|
|
219
214
|
self.assertEqual(scalar.namespace, '/test')
|
|
220
215
|
self.assertEqual(scalar.name, 'scalar')
|
|
221
216
|
self.assertEqual(scalar.description, 'scalar description')
|
|
222
217
|
self.assertEqual(scalar.parameter_definitions, {'field1': str})
|
|
223
218
|
self.assertEqual(scalar.full_name, '/test/scalar')
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
scalar.
|
|
227
|
-
scalar.
|
|
228
|
-
scalar.
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
219
|
+
self.assertEqual(scalar.value(field1='foo'), 0.0)
|
|
220
|
+
scalar.set(2.5, field1='bar')
|
|
221
|
+
self.assertEqual(scalar.value(field1='bar'), 2.5)
|
|
222
|
+
self.assertEqual(scalar.increment(1.1, field1='bar'), 3.6)
|
|
223
|
+
self.assertEqual(scalar.value(field1='bar'), 3.6)
|
|
224
|
+
self.assertEqual(scalar.value(field1='foo'), 0.0)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class InMemoryDistributionTest(unittest.TestCase):
|
|
228
|
+
"""Tests for in memory distribution."""
|
|
229
|
+
|
|
230
|
+
def test_distribution_without_parameters(self):
|
|
231
|
+
collection = monitoring.InMemoryMetricCollection('/test')
|
|
232
|
+
dist = collection.get_distribution(
|
|
233
|
+
'distribution', 'distribution description'
|
|
234
|
+
)
|
|
235
|
+
self.assertEqual(dist.namespace, '/test')
|
|
236
|
+
self.assertEqual(dist.name, 'distribution')
|
|
237
|
+
self.assertEqual(dist.description, 'distribution description')
|
|
238
|
+
self.assertEqual(dist.parameter_definitions, {})
|
|
239
|
+
self.assertEqual(dist.full_name, '/test/distribution')
|
|
240
|
+
v = dist.value()
|
|
241
|
+
self.assertEqual(v.count, 0)
|
|
242
|
+
dist.record(1)
|
|
243
|
+
dist.record(2)
|
|
244
|
+
dist.record(3)
|
|
245
|
+
v = dist.value()
|
|
246
|
+
self.assertEqual(v.count, 3)
|
|
247
|
+
|
|
248
|
+
dist = collection.get_distribution(
|
|
249
|
+
'distribution2', 'distribution description'
|
|
250
|
+
)
|
|
251
|
+
with dist.record_duration():
|
|
252
|
+
time.sleep(0.1)
|
|
253
|
+
self.assertGreaterEqual(dist.value().mean, 100)
|
|
254
|
+
|
|
255
|
+
def test_distribution_with_parameters(self):
|
|
256
|
+
collection = monitoring.InMemoryMetricCollection('/test')
|
|
257
|
+
dist = collection.get_distribution(
|
|
258
|
+
'distribution', 'distribution description', {'field1': str}
|
|
259
|
+
)
|
|
260
|
+
self.assertEqual(dist.namespace, '/test')
|
|
261
|
+
self.assertEqual(dist.name, 'distribution')
|
|
262
|
+
self.assertEqual(dist.description, 'distribution description')
|
|
263
|
+
self.assertEqual(dist.parameter_definitions, {'field1': str})
|
|
264
|
+
self.assertEqual(dist.full_name, '/test/distribution')
|
|
265
|
+
value = dist.value(field1='foo')
|
|
266
|
+
self.assertEqual(value.count, 0)
|
|
267
|
+
dist.record(1, field1='foo')
|
|
268
|
+
dist.record(2, field1='foo')
|
|
269
|
+
dist.record(3, field1='bar')
|
|
270
|
+
value = dist.value(field1='foo')
|
|
271
|
+
self.assertEqual(value.count, 2)
|
|
272
|
+
value = dist.value(field1='bar')
|
|
273
|
+
self.assertEqual(value.count, 1)
|
|
274
|
+
|
|
275
|
+
dist = collection.get_distribution(
|
|
276
|
+
'distribution2', 'distribution description', {'error': str}
|
|
277
|
+
)
|
|
278
|
+
with self.assertRaises(ValueError):
|
|
279
|
+
with dist.record_duration():
|
|
280
|
+
time.sleep(0.1)
|
|
281
|
+
raise ValueError()
|
|
282
|
+
self.assertGreaterEqual(dist.value(error='ValueError').mean, 100)
|
|
283
|
+
with dist.record_duration():
|
|
284
|
+
time.sleep(0.1)
|
|
285
|
+
self.assertGreaterEqual(dist.value(error='').mean, 100)
|
|
233
286
|
|
|
234
287
|
|
|
235
288
|
if __name__ == '__main__':
|
|
@@ -147,4 +147,11 @@ from pyglove.core.symbolic.list import mark_as_insertion
|
|
|
147
147
|
from pyglove.core.symbolic.base import WritePermissionError
|
|
148
148
|
from pyglove.core.symbolic.error_info import ErrorInfo
|
|
149
149
|
|
|
150
|
+
# Unknown symbols.
|
|
151
|
+
from pyglove.core.symbolic.unknown_symbols import UnknownSymbol
|
|
152
|
+
from pyglove.core.symbolic.unknown_symbols import UnknownType
|
|
153
|
+
from pyglove.core.symbolic.unknown_symbols import UnknownFunction
|
|
154
|
+
from pyglove.core.symbolic.unknown_symbols import UnknownMethod
|
|
155
|
+
from pyglove.core.symbolic.unknown_symbols import UnknownTypedObject
|
|
156
|
+
|
|
150
157
|
# pylint: enable=g-bad-import-order
|