Skip to content
Draft
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
59 changes: 59 additions & 0 deletions docs/components/datarouter/detailed_design/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
..
# *******************************************************************************
# Copyright (c) 2026 Contributors to the Eclipse Foundation
#
# See the NOTICE file(s) distributed with this work for additional
# information regarding copyright ownership.
#
# This program and the accompanying materials are made available under the
# terms of the Apache License Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************

Detailed Design
###############

.. document:: Data Router Detailed Design
:id: doc__data_router_detailed_design
:status: valid
:safety: ASIL_B

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

datarouter is a QM component

:security: YES
:realizes: wp__sw_implementation


Static View
-----------

.. comp_arc_sta:: Data Router Static View
:id: comp_arc_sta__data_router__static_view
:security: YES
:safety: ASIL_B
:status: valid
:fulfils: comp_req__data_router__dlt_server
:belongs_to: comp__data_router

.. uml:: datarouter_class_diagram.puml

.. uml:: inter_process_communication.puml

.. uml:: mw_log_shared_memory_reader.puml


Dynamic View
------------

.. comp_arc_dyn:: Data Router Dynamic View
:id: comp_arc_dyn__data_router__dynamic_view
:security: YES
:safety: ASIL_B
:status: valid
:fulfils: comp_req__data_router__dlt_server
:belongs_to: comp__data_router

.. uml:: datarouter_backend_datarouterbackend.puml

.. uml:: shared_memory_reader_read.puml

.. uml:: datarouter_message_client_impl_connecttodatarouter.puml
1 change: 1 addition & 0 deletions docs/components/datarouter/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@ This section is reserved for data router-specific documentation.
:glob:

*
detailed_design/index
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
@startuml component_diagram

skinparam componentStyle rectangle
skinparam backgroundColor #FEFEFE
skinparam component {
BackgroundColor<<always>> #D5E8D4
BorderColor<<always>> #82B366
BackgroundColor<<additive>> #DAE8FC
BorderColor<<additive>> #6C8EBF
BackgroundColor<<core>> #FFF2CC
BorderColor<<core>> #D6B656
BackgroundColor<<recorder>> #E1D5E7
BorderColor<<recorder>> #9673A6
BackgroundColor<<app>> #F8CECC
BorderColor<<app>> #B85450
}
skinparam note {
BackgroundColor #FFFFCC
BorderColor #999999
}

title **mw::log — Lightweight Selectable Backend Architecture**\nComponent Overview

' === Application Layer ===
package "Application Binary" <<app>> {
component "Application Code" as app <<app>> {
note right of app
#include "score/mw/log/logging.h"
LOG_INFO() << "message";
end note
}
}

' === Frontend Layer (public API) ===
package "mw/log:frontend" <<always>> {
component "Logger" as logger <<always>>
component "LoggerContainer" as container <<always>>
component "LogStream" as stream <<always>>
component "Runtime\n(Meyers' Singleton)" as runtime <<always>>
component "IRecorderFactory\n(pure virtual)" as irecorder_factory <<core>>
}

' === Backend Infrastructure (always linked) ===
package "mw/log:console [alwayslink=True]" <<always>> {
component "gBackendCreators\n(std::array<FnPtr, 6>)\n---\nConstant-initialized\nZero-init at load time\nNo constructor / No destructor" as backend_table <<core>>
component "RegistryAwareRecorderFactory\n: IRecorderFactory" as registry_factory <<always>>
component "ConsoleRegistrant\n(static const)" as console_reg <<always>>
component "CreateRecorderFactory()\n[link-time injection]" as create_factory <<always>>
}

' === Additive Backend Plugins (optional, each alwayslink=True) ===
package "Additive Backend Plugins" <<additive>> {
component "backend:file\nFileRegistrant" as file_reg <<additive>>
component "backend:remote\nRemoteRegistrant" as remote_reg <<additive>>
component "backend:slog\nSlogRegistrant\n(QNX only)" as slog_reg <<additive>>
component "backend:custom\nCustomRegistrant" as custom_reg <<additive>>
}

' === Recorder Implementations ===
package "Recorder Implementations" <<recorder>> {
component "TextRecorder\n(Console)" as text_recorder <<always>>
component "FileRecorder\n(DLT File)" as file_recorder <<recorder>>
component "DataRouterRecorder\n(Remote DLT)" as dr_recorder <<recorder>>
component "SlogRecorder\n(QNX slog2)" as slog_recorder <<recorder>>
component "EmptyRecorder\n(No-op stub)" as empty_recorder <<recorder>>
component "CompositeRecorder\n(Multiplexer)" as composite <<recorder>>
}

' === Configuration ===
package "Configuration" <<core>> {
component "TargetConfigReader" as config_reader <<core>>
component "Configuration" as config <<core>>
}

' === Relationships ===

' App -> Frontend
app --> logger : "LOG_xxx()"
app --> stream
logger --> runtime : "GetRecorder()"

' Frontend -> Factory
runtime --> create_factory : "CreateRecorderFactory()\n(link-time injection)"
create_factory --> registry_factory : "returns"
registry_factory --|> irecorder_factory : "implements"

' Factory -> Backend Table
registry_factory --> backend_table : "queries\ngBackendCreators[mode]"
registry_factory --> config_reader : "reads config files"
config_reader --> config : "produces"

' Registrants -> Backend Table
console_reg --> backend_table : "writes slot[0]\n(kConsole)"
file_reg --> backend_table : "writes slot[1]\n(kFile)"
remote_reg --> backend_table : "writes slot[2]\n(kRemote)"
slog_reg --> backend_table : "writes slot[3]\n(kSystem)"
custom_reg --> backend_table : "writes slot[4]\n(kCustom)"

' Registrants -> Recorder Factories -> Recorders
console_reg ..> text_recorder : "creates via\nConsoleRecorderFactory"
file_reg ..> file_recorder : "creates via\nFileRecorderFactory"
remote_reg ..> dr_recorder : "creates via\nRemoteDltRecorderFactory"
slog_reg ..> slog_recorder : "creates via\nSlogRecorderFactory"

' Factory -> Recorders
registry_factory ..> composite : "wraps multiple\nrecorders"
registry_factory ..> empty_recorder : "last resort\nfallback"

' Legend
legend bottom
|= Color |= Meaning |
| <back:#D5E8D4> green </back> | **Always linked** — part of mw/log:console |
| <back:#DAE8FC> blue </back> | **Additive plugin** — opt-in via Bazel deps |
| <back:#FFF2CC> yellow </back> | **Core infrastructure** — backend table, config, interfaces |
| <back:#E1D5E7> purple </back> | **Recorder implementations** — linked only when needed |
| <back:#F8CECC> red </back> | **Application code** — user binary |
end legend

@enduml
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
@startuml sequence_diagram
' Sequence Diagram: Backend Registration and Recorder Creation Flow

skinparam backgroundColor #FEFEFE
skinparam sequenceArrowThickness 2
skinparam sequenceParticipantBorderColor #666666
skinparam sequenceLifeLineBorderColor #999999
skinparam noteBackgroundColor #FFFFCC
skinparam noteBorderColor #999999
skinparam sequenceGroupBackgroundColor #F5F5F5

title **mw::log — Backend Registration & Recorder Creation Sequence**

' === Participants ===
participant "OS Loader" as loader #F8CECC
participant "gBackendCreators\n[std::array<FnPtr,6>]" as table #FFF2CC
participant "ConsoleRegistrant\n(static const)" as console_reg #D5E8D4
participant "FileRegistrant\n(static const)" as file_reg #DAE8FC
participant "RemoteRegistrant\n(static const)" as remote_reg #DAE8FC
participant "Application\nmain()" as app #F8CECC
participant "Runtime\n(Meyers' Singleton)" as runtime #D5E8D4
participant "RegistryAware\nRecorderFactory" as factory #D5E8D4
participant "TargetConfigReader" as config_reader #FFF2CC
participant "CompositeRecorder" as composite #E1D5E7

' === Phase 1: Constant Initialization (load time) ===
autonumber
group #E8F5E9 Phase 1: Constant Initialization (before main)
loader -> table : Zero-initialize gBackendCreators
note over table
gBackendCreators = {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}
end note
end

' === Phase 2: Dynamic Initialization (static constructors, before main) ===
group #E3F2FD Phase 2: Dynamic Initialization (static constructors, TU order unspecified)
console_reg -> table : RegisterBackend(kConsole, &CreateConsoleRecorder)
note right : gBackendCreators[0] = &CreateConsoleRecorder
activate table
deactivate table

file_reg -> table : RegisterBackend(kFile, &CreateFileRecorder)
note right : gBackendCreators[1] = &CreateFileRecorder
activate table
deactivate table

note over table
Assuming the user has linked the respective backends (E.g.: mw/log/backend:file)*
- If a backend is not linked, its slot remains nullptr.
mw/log:console is always linked, so kConsole is guaranteed to be registered.
end note
end

' === Phase 3: main() starts ===
group #FFF3E0 Phase 3: Application Runtime (after main)
app -> runtime : First LOG_INFO() call
activate runtime

runtime -> runtime : Instance() — Meyers' singleton created
note right of runtime
Single creation point.
All registrants have completed.
end note

runtime -> factory : CreateRecorderFactory()
activate factory

factory -> config_reader : ReadConfig()
activate config_reader
config_reader --> factory : Result<Configuration>\n{logMode: kConsole | kFile | kRemote}
deactivate config_reader

' --- Iterate over configured log modes ---
loop for each mode in config.GetLogMode()
alt mode = kConsole
factory -> table : IsBackendAvailable(kConsole)
table --> factory : true
factory -> table : CreateRecorderForMode(kConsole, config, memory_resource)
table --> factory : unique_ptr<TextRecorder>
else mode = kFile
factory -> table : IsBackendAvailable(kFile)
table --> factory : true
factory -> table : CreateRecorderForMode(kFile, config, memory_resource)
table --> factory : unique_ptr<FileRecorder>
else mode = kRemote (not linked)
factory -> table : IsBackendAvailable(kRemote)
table --> factory : false (nullptr)
note right
**Fallback:** Backend not linked.
Create console recorder instead.
end note
factory -> table : CreateRecorderForMode(kConsole, config, memory_resource)
table --> factory : unique_ptr<TextRecorder> (fallback)
end
end

alt recorders.size() == 1
factory --> runtime : single Recorder
else recorders.size() > 1
factory -> composite : CompositeRecorder(move(recorders))
activate composite
composite --> factory : unique_ptr<CompositeRecorder>
deactivate composite
factory --> runtime : unique_ptr<CompositeRecorder>
end
deactivate factory

runtime --> app : Recorder& (ready for logging)
deactivate runtime
end

' === Phase 4: Shutdown ===
group #FFEBEE Phase 4: Program Shutdown
app -> runtime : Process exit
note over runtime, table
- Runtime singleton destroyed releasing owned Recorder instances
- BackendRegistrant instances destroyed -> no-op (no destructor body)
- gBackendCreators "destroyed" -> trivially destructible, no dtor runs

end note
end

@enduml
Loading
Loading