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.

Files changed (40) hide show
  1. pyglove/core/geno/base.py +7 -3
  2. pyglove/core/io/file_system.py +452 -2
  3. pyglove/core/io/file_system_test.py +442 -0
  4. pyglove/core/monitoring.py +213 -90
  5. pyglove/core/monitoring_test.py +82 -29
  6. pyglove/core/symbolic/__init__.py +7 -0
  7. pyglove/core/symbolic/base.py +89 -35
  8. pyglove/core/symbolic/base_test.py +3 -3
  9. pyglove/core/symbolic/dict.py +31 -12
  10. pyglove/core/symbolic/dict_test.py +49 -0
  11. pyglove/core/symbolic/list.py +17 -3
  12. pyglove/core/symbolic/list_test.py +24 -2
  13. pyglove/core/symbolic/object.py +3 -1
  14. pyglove/core/symbolic/object_test.py +13 -10
  15. pyglove/core/symbolic/ref.py +19 -7
  16. pyglove/core/symbolic/ref_test.py +94 -7
  17. pyglove/core/symbolic/unknown_symbols.py +147 -0
  18. pyglove/core/symbolic/unknown_symbols_test.py +100 -0
  19. pyglove/core/typing/annotation_conversion.py +8 -1
  20. pyglove/core/typing/annotation_conversion_test.py +14 -19
  21. pyglove/core/typing/class_schema.py +24 -1
  22. pyglove/core/typing/json_schema.py +221 -8
  23. pyglove/core/typing/json_schema_test.py +508 -12
  24. pyglove/core/typing/type_conversion.py +17 -3
  25. pyglove/core/typing/type_conversion_test.py +7 -2
  26. pyglove/core/typing/value_specs.py +5 -1
  27. pyglove/core/typing/value_specs_test.py +5 -0
  28. pyglove/core/utils/__init__.py +1 -0
  29. pyglove/core/utils/contextual.py +9 -4
  30. pyglove/core/utils/contextual_test.py +10 -0
  31. pyglove/core/utils/json_conversion.py +360 -63
  32. pyglove/core/utils/json_conversion_test.py +146 -13
  33. pyglove/core/views/html/controls/tab.py +33 -0
  34. pyglove/core/views/html/controls/tab_test.py +37 -0
  35. pyglove/ext/evolution/base_test.py +1 -1
  36. {pyglove-0.5.0.dev202510020810.dist-info → pyglove-0.5.0.dev202512280810.dist-info}/METADATA +8 -1
  37. {pyglove-0.5.0.dev202510020810.dist-info → pyglove-0.5.0.dev202512280810.dist-info}/RECORD +40 -38
  38. {pyglove-0.5.0.dev202510020810.dist-info → pyglove-0.5.0.dev202512280810.dist-info}/WHEEL +0 -0
  39. {pyglove-0.5.0.dev202510020810.dist-info → pyglove-0.5.0.dev202512280810.dist-info}/licenses/LICENSE +0 -0
  40. {pyglove-0.5.0.dev202510020810.dist-info → pyglove-0.5.0.dev202512280810.dist-info}/top_level.txt +0 -0
@@ -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, Callable, Dict, Iterator, List, Optional, Tuple, Type, Union
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
- class Metric(metaclass=abc.ABCMeta):
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
- class Counter(Metric):
101
- """Base class for counters."""
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 value(self, **parameters) -> int:
117
- """Returns the value of the counter for the given parameters.
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 counter.
189
+ The new value of the metric.
124
190
  """
125
191
 
126
192
 
127
- class Distribution(metaclass=abc.ABCMeta):
128
- """Distribution of scalar values."""
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 Scalar(Metric):
177
- """Base class for scalar values."""
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: int, **parameters) -> None:
181
- """Records a value to the scalar.
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 scalars.
255
+ **parameters: Parameters for parameterized distributions.
186
256
  """
187
257
 
188
- @abc.abstractmethod
189
- def distribution(self, **parameters) -> Distribution:
190
- """Returns the distribution of the scalar.
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
- **parameters: Parameters for parameterized scalars.
194
-
195
- Returns:
196
- The distribution of the scalar.
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.record(int(duration), **parameters)
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 = create_metric_fn(name, description, parameter_definitions)
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
- **kwargs
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
- **kwargs: Additional arguments for creating the counter.
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, self._get_or_create_metric(
302
- Counter, self._create_counter, name, description, parameters,
303
- **kwargs
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
- **kwargs
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
- **kwargs: Additional arguments for creating the scalar.
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
- Scalar, self._create_scalar, name, description, parameters, **kwargs
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
- @abc.abstractmethod
346
- def _create_scalar(
436
+ def get_distribution(
347
437
  self,
348
438
  name: str,
349
439
  description: str,
350
- parameter_definitions: Dict[str, Type[Union[int, str, bool]]],
351
- **kwargs
352
- ) -> Scalar:
353
- """Creates a counter with the given name."""
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: _InMemoryDistribution(self._window_size)
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 distribution(self, **parameters) -> Distribution:
543
+ def value(self, **parameters) -> DistributionValue:
399
544
  """Returns the distribution of the scalar."""
400
- parameters_key = self._parameters_key(**parameters)
401
- return self._distributions[parameters_key]
545
+ return self._distributions[self._parameters_key(**parameters)]
402
546
 
403
547
 
404
- class _InMemoryDistribution(Distribution):
405
- """In memory distribution of scalar values."""
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
- def _create_counter(
493
- self,
494
- name: str,
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
@@ -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.get_scalar('counter', 'counter description')
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 InMemoryDistributionTest(unittest.TestCase):
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._InMemoryDistribution()
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._InMemoryDistribution()
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._InMemoryDistribution()
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._InMemoryDistribution(window_size=3)
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
- dist = scalar.distribution()
202
- self.assertEqual(dist.count, 0)
203
- scalar.record(1)
204
- scalar.record(2)
205
- scalar.record(3)
206
- scalar.distribution()
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
- dist = scalar.distribution(field1='foo')
225
- self.assertEqual(dist.count, 0)
226
- scalar.record(1, field1='foo')
227
- scalar.record(2, field1='foo')
228
- scalar.record(3, field1='bar')
229
- dist = scalar.distribution(field1='foo')
230
- self.assertEqual(dist.count, 2)
231
- dist = scalar.distribution(field1='bar')
232
- self.assertEqual(dist.count, 1)
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