reactor-runtime 2.3.1__tar.gz → 2.3.2__tar.gz

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.
Files changed (132) hide show
  1. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/PKG-INFO +1 -1
  2. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/pyproject.toml +1 -1
  3. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/interface/model/reactor_model.py +30 -3
  4. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/interface/pipeline/reactor_pipeline.py +17 -4
  5. reactor_runtime-2.3.2/src/reactor_runtime/transports/gstreamer/probes/__init__.py +5 -0
  6. reactor_runtime-2.3.2/src/reactor_runtime/transports/gstreamer/probes/fps_probe.py +119 -0
  7. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime.egg-info/PKG-INFO +1 -1
  8. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime.egg-info/SOURCES.txt +2 -0
  9. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/README.md +0 -0
  10. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/setup.cfg +0 -0
  11. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/api/__init__.py +0 -0
  12. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_cli/commands/__init__.py +0 -0
  13. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_cli/commands/init.py +0 -0
  14. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_cli/commands/run.py +0 -0
  15. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_cli/commands/schema.py +0 -0
  16. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_cli/main.py +0 -0
  17. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_cli/utils/__init__.py +0 -0
  18. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_cli/utils/config.py +0 -0
  19. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_cli/utils/runtime.py +0 -0
  20. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_cli/utils/version.py +0 -0
  21. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/__init__.py +0 -0
  22. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/config.py +0 -0
  23. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/interface/__init__.py +0 -0
  24. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/interface/defaults.py +0 -0
  25. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/interface/driver/__init__.py +0 -0
  26. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/interface/driver/pipeline_executor.py +0 -0
  27. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/interface/driver/step_result.py +0 -0
  28. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/interface/events/__init__.py +0 -0
  29. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/interface/events/connected.py +0 -0
  30. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/interface/events/event.py +0 -0
  31. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/interface/events/messages.py +0 -0
  32. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/interface/events/upload.py +0 -0
  33. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/interface/internal/__init__.py +0 -0
  34. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/interface/internal/input_buffer.py +0 -0
  35. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/interface/internal/output_buffer.py +0 -0
  36. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/interface/internal/reactor_core.py +0 -0
  37. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/interface/model/__init__.py +0 -0
  38. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/interface/model/decorators.py +0 -0
  39. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/interface/model/handlers.py +0 -0
  40. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/interface/pipeline/__init__.py +0 -0
  41. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/interface/pipeline/idle.py +0 -0
  42. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/interface/pipeline/input_state.py +0 -0
  43. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/interface/tracks/__init__.py +0 -0
  44. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/interface/tracks/descriptors.py +0 -0
  45. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/interface/tracks/input.py +0 -0
  46. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/interface/tracks/output.py +0 -0
  47. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/interface/upload.py +0 -0
  48. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/model_state.py +0 -0
  49. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/profiling/__init__.py +0 -0
  50. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/profiling/backends/__init__.py +0 -0
  51. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/profiling/backends/base.py +0 -0
  52. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/profiling/backends/file.py +0 -0
  53. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/profiling/backends/otlp.py +0 -0
  54. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/profiling/helpers.py +0 -0
  55. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/profiling/plotting/__init__.py +0 -0
  56. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/profiling/plotting/plot_profiling.py +0 -0
  57. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/profiling/profiler.py +0 -0
  58. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/profiling/singleton.py +0 -0
  59. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/runtime_api.py +0 -0
  60. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/runtimes/headless/config.py +0 -0
  61. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/runtimes/headless/headless_runtime.py +0 -0
  62. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/runtimes/headless/input_feeder.py +0 -0
  63. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/runtimes/http/config.py +0 -0
  64. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/runtimes/http/http_runtime.py +0 -0
  65. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/runtimes/http/types.py +0 -0
  66. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/schema.py +0 -0
  67. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/schema_validator.py +0 -0
  68. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/__init__.py +0 -0
  69. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/aiortc/__init__.py +0 -0
  70. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/aiortc/audio_track.py +0 -0
  71. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/aiortc/client.py +0 -0
  72. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/aiortc/frame_conversion.py +0 -0
  73. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/aiortc/ice_connection.py +0 -0
  74. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/aiortc/video_track.py +0 -0
  75. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/config.py +0 -0
  76. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/events.py +0 -0
  77. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/gstreamer/__init__.py +0 -0
  78. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/gstreamer/client.py +0 -0
  79. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/gstreamer/decoders/__init__.py +0 -0
  80. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/gstreamer/decoders/av1.py +0 -0
  81. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/gstreamer/decoders/base.py +0 -0
  82. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/gstreamer/decoders/factory.py +0 -0
  83. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/gstreamer/decoders/h264.py +0 -0
  84. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/gstreamer/decoders/h265.py +0 -0
  85. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/gstreamer/decoders/vp8.py +0 -0
  86. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/gstreamer/decoders/vp9.py +0 -0
  87. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/gstreamer/encoders/__init__.py +0 -0
  88. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/gstreamer/encoders/av1.py +0 -0
  89. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/gstreamer/encoders/base.py +0 -0
  90. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/gstreamer/encoders/factory.py +0 -0
  91. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/gstreamer/encoders/h264.py +0 -0
  92. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/gstreamer/encoders/h265.py +0 -0
  93. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/gstreamer/encoders/opus.py +0 -0
  94. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/gstreamer/encoders/vp8.py +0 -0
  95. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/gstreamer/encoders/vp9.py +0 -0
  96. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/gstreamer/gst.py +0 -0
  97. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/gstreamer/gst_helpers.py +0 -0
  98. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/gstreamer/receiver/__init__.py +0 -0
  99. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/gstreamer/receiver/audio.py +0 -0
  100. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/gstreamer/receiver/base.py +0 -0
  101. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/gstreamer/receiver/video.py +0 -0
  102. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/gstreamer/sdp/__init__.py +0 -0
  103. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/gstreamer/sdp/bundle.py +0 -0
  104. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/gstreamer/sdp/codec.py +0 -0
  105. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/gstreamer/sdp/extmap.py +0 -0
  106. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/gstreamer/sdp/ice.py +0 -0
  107. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/gstreamer/sender/__init__.py +0 -0
  108. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/gstreamer/sender/audio.py +0 -0
  109. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/gstreamer/sender/base.py +0 -0
  110. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/gstreamer/sender/video.py +0 -0
  111. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/gstreamer/settings.py +0 -0
  112. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/gstreamer/signals.py +0 -0
  113. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/ice_uris.py +0 -0
  114. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/interface.py +0 -0
  115. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/media.py +0 -0
  116. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/transports/types.py +0 -0
  117. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/utils/launch.py +0 -0
  118. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/utils/loader.py +0 -0
  119. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/utils/log.py +0 -0
  120. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/utils/messages.py +0 -0
  121. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime/utils/typing.py +0 -0
  122. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime.egg-info/dependency_links.txt +0 -0
  123. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime.egg-info/entry_points.txt +0 -0
  124. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime.egg-info/requires.txt +0 -0
  125. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/reactor_runtime.egg-info/top_level.txt +0 -0
  126. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/template/README.md +0 -0
  127. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/template/__init__.py +0 -0
  128. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/template/config.yml +0 -0
  129. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/template/model.py +0 -0
  130. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/template/pipeline.py +0 -0
  131. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/template/reactor.yaml +0 -0
  132. {reactor_runtime-2.3.1 → reactor_runtime-2.3.2}/src/template/requirements.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: reactor_runtime
3
- Version: 2.3.1
3
+ Version: 2.3.2
4
4
  Summary: Reactor runtime with public model API
5
5
  Author-email: Reactor <team@reactor.inc>
6
6
  Requires-Python: >=3.9
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "reactor_runtime"
7
- version = "2.3.1"
7
+ version = "2.3.2"
8
8
  description = "Reactor runtime with public model API"
9
9
  authors = [
10
10
  { name = "Reactor", email = "team@reactor.inc" }
@@ -12,7 +12,8 @@ from __future__ import annotations
12
12
 
13
13
  import asyncio
14
14
  import dataclasses
15
- from typing import Any, Callable, List, Tuple, Type
15
+ import os
16
+ from typing import Any, Callable, List, Optional, Tuple, Type
16
17
 
17
18
  from reactor_runtime.interface.events.connected import Connected, Disconnected
18
19
  from reactor_runtime.interface.events.event import Event
@@ -96,12 +97,22 @@ class ReactorModel(ReactorCore):
96
97
  dispatch_table = build_dispatch_table(handlers)
97
98
 
98
99
  dispatcher = asyncio.create_task(self._dispatcher(handlers, dispatch_table))
100
+ crash: Optional[BaseException] = None
99
101
  try:
100
102
  await self.run()
101
103
  except asyncio.CancelledError:
102
104
  logger.info("Model cancelled")
103
- except Exception:
104
- logger.exception("run() crashed")
105
+ except Exception as exc:
106
+ # Don't swallow-and-return: log, clean up via finally, then
107
+ # fail-fast (REA-1740). ``ReactorPipeline.run()`` contains
108
+ # its own per-session crashes, so this only fires for custom
109
+ # ``run()`` overrides.
110
+ crash = exc
111
+ logger.exception(
112
+ "run() crashed — terminating the process",
113
+ model=type(self).__name__,
114
+ exc_type=type(exc).__name__,
115
+ )
105
116
  finally:
106
117
  dispatcher.cancel()
107
118
  try:
@@ -109,6 +120,22 @@ class ReactorModel(ReactorCore):
109
120
  except asyncio.CancelledError:
110
121
  pass
111
122
 
123
+ if crash is not None:
124
+ self._fail_fast(crash)
125
+
126
+ def _fail_fast(self, exc: BaseException) -> None:
127
+ """Terminate the process after an unrecoverable ``run()`` error.
128
+
129
+ Uses :func:`os._exit` (not ``sys.exit``) so the whole process
130
+ goes away regardless of thread. Override in subclasses or
131
+ tests.
132
+ """
133
+ logger.critical(
134
+ "Terminating process after unhandled run() exception",
135
+ exc_type=type(exc).__name__,
136
+ )
137
+ os._exit(1)
138
+
112
139
  # ------------------------------------------------------------------
113
140
  # run() — override with your generation loop
114
141
  # ------------------------------------------------------------------
@@ -241,6 +241,10 @@ class ReactorPipeline(ReactorModel):
241
241
  except BufferClosed:
242
242
  logger.debug("Input buffer closed, ending session")
243
243
  break
244
+ # Any other exception propagates up through the outer
245
+ # try/finally (gen close + state reset) and out of
246
+ # ``run()`` — ``_lifecycle`` then fails-fast via
247
+ # ``_fail_fast`` (REA-1740).
244
248
 
245
249
  if output is Idle:
246
250
  await asyncio.sleep(0.005)
@@ -251,10 +255,19 @@ class ReactorPipeline(ReactorModel):
251
255
  else:
252
256
  await self.emit(output)
253
257
  finally:
254
- if is_async:
255
- await gen.aclose()
256
- else:
257
- gen.close()
258
+ # Swallow exceptions from ``gen.close()`` only so a buggy
259
+ # ``inference()`` ``finally`` cannot mask the original
260
+ # exception on its way up to ``_lifecycle``.
261
+ try:
262
+ if is_async:
263
+ await gen.aclose()
264
+ else:
265
+ gen.close()
266
+ except Exception:
267
+ logger.exception(
268
+ "generator.close() raised during teardown",
269
+ model=type(self).__name__,
270
+ )
258
271
  self.state = None
259
272
  for buf in self._input_buffers.values():
260
273
  buf.reset()
@@ -0,0 +1,5 @@
1
+ # Copyright (c) 2026 Reactor Technologies, Inc. All rights reserved.
2
+
3
+ from reactor_runtime.transports.gstreamer.probes.fps_probe import FpsProbe
4
+
5
+ __all__ = ["FpsProbe"]
@@ -0,0 +1,119 @@
1
+ # Copyright (c) 2026 Reactor Technologies, Inc. All rights reserved.
2
+
3
+ """
4
+ Buffer-count FPS probes on GStreamer pads.
5
+ Each :class:`FpsProbe` instance is independent: attach one probe per pad /
6
+ measurement site (e.g. after ``videoconvert``, after an encoder, on a queue src pad).
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import time
12
+ from typing import Any, Optional
13
+
14
+ from reactor_runtime.transports.gstreamer.gst import Gst
15
+ from reactor_runtime.utils.log import get_logger
16
+
17
+ logger = get_logger(__name__)
18
+
19
+
20
+ class FpsProbe:
21
+ """
22
+ Install a ``Gst.PadProbeType.BUFFER`` probe that reports buffers per second
23
+ over a rolling wall-clock window (default 1s of monotonic time).
24
+ Instances do not share state. Typical usage::
25
+ probe_videoconvert = FpsProbe("video_after_videoconvert")
26
+ probe_videoconvert.attach(videoconvert.get_static_pad("src"))
27
+ probe_encoder = FpsProbe("video_after_encoder")
28
+ probe_encoder.attach(encoder_element.get_static_pad("src"))
29
+ ``last_fps`` is updated at the end of each completed window. Each completed
30
+ window is logged at INFO.
31
+ """
32
+
33
+ def __init__(
34
+ self,
35
+ name: str,
36
+ *,
37
+ window_ns: Optional[int] = None,
38
+ ) -> None:
39
+ self._name = name
40
+ self._window_ns = int(Gst.SECOND if window_ns is None else window_ns)
41
+ if self._window_ns <= 0:
42
+ raise ValueError("window_ns must be positive")
43
+
44
+ self._pad: Optional[Gst.Pad] = None
45
+ self._probe_id: Optional[int] = None
46
+ self._window_start_ns: Optional[int] = None
47
+ self._count = 0
48
+ self._last_fps: Optional[float] = None
49
+
50
+ @property
51
+ def name(self) -> str:
52
+ return self._name
53
+
54
+ @property
55
+ def last_fps(self) -> Optional[float]:
56
+ """Buffers per second from the last completed ``window_ns`` interval."""
57
+ return self._last_fps
58
+
59
+ def attach(self, pad: Gst.Pad) -> None:
60
+ """
61
+ Add this probe on *pad* (usually a src pad, downstream of the element under test).
62
+ Raises:
63
+ RuntimeError: if this instance already has a probe installed.
64
+ """
65
+ if self._probe_id:
66
+ raise RuntimeError(
67
+ f"FpsProbe({self._name!r}) is already attached; call detach() first"
68
+ )
69
+ self._pad = pad
70
+ self._window_start_ns = None
71
+ self._count = 0
72
+ self._probe_id = pad.add_probe(
73
+ Gst.PadProbeType.BUFFER,
74
+ self._buffer_probe,
75
+ None, # user_data (PyGObject passes this as the 3rd callback arg)
76
+ )
77
+ if not self._probe_id:
78
+ self._pad = None
79
+ raise RuntimeError(f"FpsProbe({self._name!r}): pad.add_probe() failed")
80
+
81
+ def detach(self) -> None:
82
+ """Remove the probe from the pad, if installed."""
83
+ if self._pad is not None and self._probe_id is not None:
84
+ self._pad.remove_probe(self._probe_id)
85
+ self._pad = None
86
+ self._probe_id = None
87
+ self._window_start_ns = None
88
+ self._count = 0
89
+ self._last_fps = None
90
+
91
+ def _buffer_probe(
92
+ self,
93
+ _pad: Gst.Pad,
94
+ info: Gst.PadProbeInfo,
95
+ _user_data: Any,
96
+ ) -> Gst.PadProbeReturn:
97
+ buf = info.get_buffer()
98
+ if buf is None:
99
+ return Gst.PadProbeReturn.OK
100
+
101
+ now = time.monotonic_ns()
102
+ window_start = self._window_start_ns
103
+ if window_start is None:
104
+ self._window_start_ns = now
105
+ return Gst.PadProbeReturn.OK
106
+
107
+ self._count += 1
108
+ elapsed = now - window_start
109
+ if elapsed >= self._window_ns:
110
+ # buffers / second for the window that just ended
111
+ fps = self._count * 1_000_000_000 / elapsed
112
+ self._last_fps = fps
113
+ logger.info(
114
+ f"gstreamer_fps_probe name={self._name} fps={fps:.2f} buffers={self._count} window_s={elapsed / 1e9:.3f}",
115
+ )
116
+ self._window_start_ns = now
117
+ self._count = 0
118
+
119
+ return Gst.PadProbeReturn.OK
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: reactor_runtime
3
- Version: 2.3.1
3
+ Version: 2.3.2
4
4
  Summary: Reactor runtime with public model API
5
5
  Author-email: Reactor <team@reactor.inc>
6
6
  Requires-Python: >=3.9
@@ -101,6 +101,8 @@ src/reactor_runtime/transports/gstreamer/encoders/h265.py
101
101
  src/reactor_runtime/transports/gstreamer/encoders/opus.py
102
102
  src/reactor_runtime/transports/gstreamer/encoders/vp8.py
103
103
  src/reactor_runtime/transports/gstreamer/encoders/vp9.py
104
+ src/reactor_runtime/transports/gstreamer/probes/__init__.py
105
+ src/reactor_runtime/transports/gstreamer/probes/fps_probe.py
104
106
  src/reactor_runtime/transports/gstreamer/receiver/__init__.py
105
107
  src/reactor_runtime/transports/gstreamer/receiver/audio.py
106
108
  src/reactor_runtime/transports/gstreamer/receiver/base.py