-
Notifications
You must be signed in to change notification settings - Fork 0
Feature/ab#2263 register config message #4
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
Merged
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
c7d3f81
Add initial implementation
flonix8 b69a000
Add tests
flonix8 6c9f559
Remove instance_id from frame_forwarder output key (aligned with exis…
flonix8 6ecc581
Add readme
flonix8 9024bf5
use newer image for pr-build
witchpou a23285c
used latest checkout action for pr build
witchpou 945a6a8
used checkout action 7 for pr build
witchpou 952d292
used latest versions for release build
witchpou 7750682
use latest python image
witchpou 9eac856
Potential fix for pull request finding
flonix8 a72209b
Potential fix for pull request finding
flonix8 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,48 @@ | ||
| # SAE sae-api | ||
|
|
||
| The API layer between a SAE instance and the outside world (e.g. a cloud backend | ||
| system). It publishes information about the local SAE instance to a backend Valkey, | ||
| and is the place where inbound communication from the backend will be handled in the | ||
| future. | ||
|
|
||
| ## What it does | ||
|
|
||
| - **Reports lifecycle events** — on startup and shutdown it publishes an | ||
| `EventMessage` describing the instance to the backend. | ||
| - **Forwards frames** — periodically pulls the latest frame from each local video | ||
| source stream and forwards a frame-only `SaeMessage` to the backend (one output | ||
| stream per source). | ||
|
|
||
| ## Interfaces | ||
|
|
||
| It reads from a local **source Valkey** and writes to a remote **backend Valkey**. | ||
| Stream keys are built from the prefixes and IDs in the configuration: | ||
|
|
||
| | Direction | Valkey | Stream key | Payload | | ||
| | --------- | -------- | --------------------------------------------------- | -------------- | | ||
| | Input | source | `{video_source_stream_prefix}:{source_id}` | `SaeMessage` | | ||
| | Output | backend | `{frame_forwarding.output_stream_prefix}:{source_id}` | frame-only `SaeMessage` | | ||
| | Output | backend | `{event_reporting.output_stream_prefix}:{instance_id}` | `EventMessage` | | ||
|
|
||
| Source streams are discovered by scanning the source Valkey for keys matching | ||
| `{video_source_stream_prefix}:*`. | ||
|
|
||
| ## Running it | ||
|
|
||
| Configuration is read from `settings.yaml` (see `settings.template.yaml` for all | ||
| options); every value can be overridden via environment variables, using `__` as the | ||
| nesting delimiter (e.g. `BACKEND_VALKEY__HOST`). Point `SETTINGS_FILE` at a different | ||
| file to use another location. | ||
|
|
||
| ```sh | ||
| poetry install | ||
| poetry run python main.py | ||
| ``` | ||
|
|
||
| A Prometheus metrics endpoint is served on `prometheus_port` (8000 by default). | ||
|
|
||
| ## Tests | ||
|
|
||
| ```sh | ||
| poetry run pytest | ||
| ``` |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| import logging | ||
| import time | ||
|
|
||
| from prometheus_client import Counter | ||
| from visionapi.sae_pb2 import EventMessage | ||
| from visionapi.common_pb2 import MessageType | ||
| from visionlib.pipeline import ValkeyPublisher | ||
|
|
||
| from .config import SaeApiConfig | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
| EVENT_MESSAGE_COUNTER = Counter('sae_api_event_message_counter', 'How many event messages have been published', | ||
| labelnames=('event',)) | ||
|
|
||
|
|
||
| class EventReporter: | ||
| '''Publishes lifecycle EventMessages about this SAE instance to the backend Valkey.''' | ||
|
|
||
| def __init__(self, config: SaeApiConfig) -> None: | ||
| self._config = config | ||
| self._stream_key = f'{config.event_reporting.output_stream_prefix}:{config.instance_id}' | ||
| self._publish_ctx = ValkeyPublisher(config.backend_valkey.host, config.backend_valkey.port) | ||
| self._publish = None | ||
|
|
||
| def __enter__(self): | ||
| self._publish = self._publish_ctx.__enter__() | ||
| return self | ||
|
|
||
| def __exit__(self, exc_type, exc_value, traceback): | ||
| return self._publish_ctx.__exit__(exc_type, exc_value, traceback) | ||
|
|
||
| def report_startup(self): | ||
| self.report(EventMessage.EventType.STARTUP) | ||
|
|
||
| def report_shutdown(self): | ||
| self.report(EventMessage.EventType.SHUTDOWN) | ||
|
|
||
| def report(self, event_type: EventMessage.EventType): | ||
| msg = EventMessage() | ||
| msg.instance_id = self._config.instance_id | ||
| msg.timestamp_utc_ms = int(time.time() * 1000) | ||
| msg.event_type = event_type | ||
| msg.type = MessageType.SAE_EVENT | ||
|
|
||
| self._publish(self._stream_key, msg.SerializeToString()) | ||
|
|
||
| event_name = EventMessage.EventType.Name(event_type) | ||
| EVENT_MESSAGE_COUNTER.labels(event=event_name).inc() | ||
| logger.info(f'Published event message ({event_name}) to {self._stream_key}') |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| import logging | ||
|
|
||
| import pybase64 | ||
| import valkey | ||
| from prometheus_client import Counter, Histogram | ||
| from visionapi.common_pb2 import MessageType | ||
| from visionapi.sae_pb2 import SaeMessage | ||
| from visionlib.pipeline import ValkeyPublisher | ||
|
|
||
| from .config import SaeApiConfig | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
| FRAMES_FORWARDED = Counter('sae_api_frames_forwarded', 'How many frames have been forwarded to the backend') | ||
| FORWARD_CYCLE_DURATION = Histogram('sae_api_forward_cycle_duration', 'The time it takes to run one full frame forwarding cycle') | ||
|
|
||
|
|
||
| class FrameForwarder: | ||
| '''Periodically pulls the latest frame from every video source stream on the source Valkey | ||
| and forwards a frame-only SaeMessage to the backend Valkey (one output stream per source).''' | ||
|
|
||
| def __init__(self, config: SaeApiConfig) -> None: | ||
| self._config = config | ||
| self._scan_pattern = f'{config.frame_forwarding.video_source_stream_prefix}:*' | ||
| self._output_prefix = config.frame_forwarding.output_stream_prefix | ||
|
|
||
| self._source_client = None | ||
| self._publish_ctx = ValkeyPublisher(config.backend_valkey.host, config.backend_valkey.port) | ||
| self._publish = None | ||
|
|
||
| def __enter__(self): | ||
| self._source_client = valkey.Valkey(self._config.source_valkey.host, self._config.source_valkey.port) | ||
| self._publish = self._publish_ctx.__enter__() | ||
| return self | ||
|
|
||
| def __exit__(self, exc_type, exc_value, traceback): | ||
| try: | ||
| self._source_client.close() | ||
| except Exception as e: | ||
| logger.warning('Error while closing source valkey client', exc_info=e) | ||
| return self._publish_ctx.__exit__(exc_type, exc_value, traceback) | ||
|
|
||
| @FORWARD_CYCLE_DURATION.time() | ||
| def forward_once(self): | ||
| stream_keys = list(self._source_client.scan_iter(match=self._scan_pattern, _type='STREAM')) | ||
| logger.debug(f'Discovered {len(stream_keys)} source stream(s) matching {self._scan_pattern}') | ||
|
|
||
| for stream_key in stream_keys: | ||
| try: | ||
| self._forward_stream(stream_key) | ||
| except Exception as e: | ||
| logger.warning(f'Error while forwarding latest frame from {stream_key!r}', exc_info=e) | ||
|
|
||
|
flonix8 marked this conversation as resolved.
|
||
| def _forward_stream(self, stream_key: bytes): | ||
| entries = self._source_client.xrevrange(stream_key, count=1) | ||
| if not entries: | ||
| return | ||
|
|
||
| _, fields = entries[0] | ||
| proto_data = fields.get(b'proto_data_b64') | ||
| if proto_data is not None: | ||
| proto_data = pybase64.b64decode(proto_data, validate=True) | ||
| else: | ||
| proto_data = fields.get(b'proto_data') | ||
| if proto_data is None: | ||
| logger.warning(f'No proto data field found in latest entry of {stream_key!r}') | ||
| return | ||
|
|
||
| sae_msg = SaeMessage() | ||
| sae_msg.ParseFromString(proto_data) | ||
|
|
||
| if not sae_msg.HasField('frame'): | ||
| logger.debug(f'Latest message in {stream_key!r} has no frame; skipping') | ||
| return | ||
|
|
||
| out_msg = SaeMessage() | ||
| out_msg.frame.CopyFrom(sae_msg.frame) | ||
| out_msg.type = MessageType.SAE | ||
|
|
||
| source_id = stream_key.decode('utf-8').split(':', 1)[1] | ||
| output_key = f'{self._output_prefix}:{source_id}' | ||
| self._publish(output_key, out_msg.SerializeToString()) | ||
|
|
||
| FRAMES_FORWARDED.inc() | ||
| logger.debug(f'Forwarded latest frame from {stream_key!r} to {output_key}') | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.