From c0337414c7ed0a726ee98ab6354ff371c04d8e1d Mon Sep 17 00:00:00 2001 From: arsibo Date: Fri, 10 Oct 2025 13:42:56 +0000 Subject: [PATCH 1/8] logging: Add puml diagrams - add puml diagrams converted from uxf files by AI - next steps see https://github.com/eclipse-score/score/issues/1863 --- ...r_buffer_allocator_acquireslottowrite.puml | 13 + design/configuration_sequence.puml | 112 ++++++++ design/configuration_static.puml | 242 +++++++++++++++++ design/configuration_use_cases.puml | 50 ++++ design/datarouter_backend/class_diagram.puml | 196 ++++++++++++++ .../datarouter_backend_datarouterbackend.puml | 7 + .../inter_process_communication.puml | 13 + ...ssage_client_impl_connecttodatarouter.puml | 16 ++ design/frontend_dependency_graph.puml | 64 +++++ design/mw_log_datarouter_recorder.puml | 59 +++++ design/mw_log_file_backend.puml | 162 ++++++++++++ design/mw_log_recorders.puml | 177 +++++++++++++ design/mw_log_shared_memory_reader.puml | 122 +++++++++ design/non_verbose_logging_static.puml | 124 +++++++++ design/shared_memory_reader_read.puml | 93 +++++++ .../shared_memory_writer_allocandwrite.puml | 18 ++ .../slot_drainer_action_diagram_design.puml | 6 + design/slot_drainer_sequence_design.puml | 61 +++++ .../verbose_logging_ara_log_interaction.puml | 42 +++ design/verbose_logging_sequence.puml | 74 ++++++ design/verbose_logging_static.puml | 247 ++++++++++++++++++ design/wait_free_stack_trypush.puml | 13 + .../design/class_diagram.puml | 85 ++++++ .../design/wait_free_alternating_buffers.puml | 71 +++++ .../design/wait_free_linear_buffer.puml | 69 +++++ 25 files changed, 2136 insertions(+) create mode 100644 design/circular_buffer_allocator_acquireslottowrite.puml create mode 100644 design/configuration_sequence.puml create mode 100644 design/configuration_static.puml create mode 100644 design/configuration_use_cases.puml create mode 100644 design/datarouter_backend/class_diagram.puml create mode 100644 design/datarouter_backend/datarouter_backend_datarouterbackend.puml create mode 100644 design/datarouter_backend/inter_process_communication.puml create mode 100644 design/datarouter_message_client_impl_connecttodatarouter.puml create mode 100644 design/frontend_dependency_graph.puml create mode 100644 design/mw_log_datarouter_recorder.puml create mode 100644 design/mw_log_file_backend.puml create mode 100644 design/mw_log_recorders.puml create mode 100644 design/mw_log_shared_memory_reader.puml create mode 100644 design/non_verbose_logging_static.puml create mode 100644 design/shared_memory_reader_read.puml create mode 100644 design/shared_memory_writer_allocandwrite.puml create mode 100644 design/slot_drainer_action_diagram_design.puml create mode 100644 design/slot_drainer_sequence_design.puml create mode 100644 design/verbose_logging_ara_log_interaction.puml create mode 100644 design/verbose_logging_sequence.puml create mode 100644 design/verbose_logging_static.puml create mode 100644 design/wait_free_stack_trypush.puml create mode 100644 detail/wait_free_producer_queue/design/class_diagram.puml create mode 100644 detail/wait_free_producer_queue/design/wait_free_alternating_buffers.puml create mode 100644 detail/wait_free_producer_queue/design/wait_free_linear_buffer.puml diff --git a/design/circular_buffer_allocator_acquireslottowrite.puml b/design/circular_buffer_allocator_acquireslottowrite.puml new file mode 100644 index 0000000..427b150 --- /dev/null +++ b/design/circular_buffer_allocator_acquireslottowrite.puml @@ -0,0 +1,13 @@ +@startuml +start +:Increment loop counter; +if (Loop counter > circular buffer size) then (yes) +:Increment claimed slot; +stop +:Loop counter = 0; +:Slot index = claimed slot % circular +buffer capacity; +if (Circular buffer at Slot index in use?) then (yes) +:Mark Slot as in use; +:Return Slot index; +@enduml \ No newline at end of file diff --git a/design/configuration_sequence.puml b/design/configuration_sequence.puml new file mode 100644 index 0000000..f4b69a9 --- /dev/null +++ b/design/configuration_sequence.puml @@ -0,0 +1,112 @@ +@startuml configuration_sequence + +participant "App" as App +participant "FreeFunctions" as FF +participant "LogStreamFactory" as LSF +participant "Runtime" as RT +participant "RecorderFactory" as RF +participant "TargetConfigReader" as TCR +participant "ConfigurationFileDiscoverer" as CFD +participant "JSON" as JSON +participant "OS" as OS +participant "DatarouterRecorder" as DR +participant "TextRecorder" as TR +participant "CompositeRecorder" as CR + +App -> FF: Error() +activate FF + +FF -> LSF: GetStream(LogLevel) +activate LSF + +LSF -> RT: GetRecorder() +activate RT + +RT -> RF: CreateForTarget() +activate RF + +RF -> TCR: ReadConfig() +activate TCR + +TCR -> CFD: FindConfigurationFiles() +activate CFD + +CFD -> OS: access(/etc/ecu_logging_config.json) +CFD -> OS: access(MW_LOG_CONFIG_FILE) +CFD -> OS: access(/etc/logging.json) +CFD -> OS: access(/logging.json) + +CFD -> TCR: Return config file paths +deactivate CFD + +TCR -> JSON: FromFile(global_file_path) +activate JSON +JSON -> TCR: +deactivate JSON + +alt MW_LOG_CONFIG_FILE is defined + TCR -> JSON: FromFile(environmental_file_path) + activate JSON + JSON -> TCR: + deactivate JSON +else MW_LOG_CONFIG_FILE is undefined + TCR -> JSON: FromFile(app_file_path) + activate JSON + JSON -> TCR: + deactivate JSON +end + +TCR -> RF: Return Configuration instance +deactivate TCR + +RF -> DR: Create instance with configuration +activate DR +DR -> RF: +deactivate DR + +RF -> TR: Create instance with configuration +activate TR +TR -> RF: +deactivate TR + +RF -> CR: Create with Datarouter and StandardOut recorders +activate CR +CR -> RF: +deactivate CR + +RF -> RT: Store returned instance +deactivate RF + +RT -> LSF: Return Recorder instance +deactivate RT + +LSF -> FF: +deactivate LSF + +FF -> App: +deactivate FF + +note left of App + If a user wants to avoid the latency + on the first log message, we provide + mw::log::Initialize() that shall + take care of initializing the runtime. +end note + +note right of CFD + Find and return the existing + config files. +end note + +note right of CR + Composite Recorder will + forward the logs to DatarouterRecorder + and TextRecorder +end note + +note right of RT + If no recorder exists yet, + create one using the factory. +end note + +@enduml \ No newline at end of file diff --git a/design/configuration_static.puml b/design/configuration_static.puml new file mode 100644 index 0000000..9e3ab6c --- /dev/null +++ b/design/configuration_static.puml @@ -0,0 +1,242 @@ +@startuml configuration_static + +package "bmw::json" { + class JsonParser { + +FromFile(path) : Result + } +} + +package "bmw::os" { + class Fcntl + class Mman + class Unistd { + +access() + } + class Stat + class Stdlib { + +getenv() + } + class Pthread + class Signal + + package "utils" { + class Path { + +get_exec_path() : Result + +get_parent_dir() : string + } + } +} + +interface Recorder { + +StartRecord(context_id, LogLevel) : optional + +StopRecord(SlotHandle) : void + +Log(SlotHandle, uint8_t) : void + +Log(SlotHandle, int8_t) : void +} + +class TextRecorder +class DataRouterRecorder { + -configuration : Configuration +} +class FileRecorder +class EmptyRecorder +class CompositeRecorder { + -config_ : Configuration +} + +class RecorderComposite { + +RecorderComposite(recorders) + -recorders_ : vector +} + +class Configuration { + -ecu_id_ : LoggingIdentifier + -app_id_ : LoggingIdentifier + -app_description_ : string + -log_mode_ : unordered_set + -log_file_path_ : string + -default_log_level_ : LogLevel + -default_console_log_level_ : LogLevel + -context_log_level_ : ContextLogLevelMap + -stack_buffer_size_ : size_t + -ring_buffer_size_ : size_t + -ring_buffer_overwrite_on_full_ : bool + -number_of_slots_ : size_t + -slot_size_bytes_ : size_t + -data_router_uid_ : size_t + -dynamic_datarouter_identifiers_ : bool + -- + +Configuration() + +GetEcuId() : string_view + +SetEcuId(value) : void + +GetAppId() : string_view + +SetAppId(value) : void + +GetAppDescription() : string_view + +SetAppDescription(value) : void + +GetLogMode() : unordered_set + +SetLogMode(value) : void + +GetLogFilePath() : string_view + +SetLogFilePath(value) : void + +GetDefaultLogLevel() : LogLevel + +SetDefaultLogLevel(value) : void + +GetDefaultConsoleLogLevel() : LogLevel + +SetDefaultConsoleLogLevel(value) : void + +GetContextLogLevel() : ContextLogLevelMap + +SetContextLogLevel(value) : void + +GetStackBufferSize() : size_t + +SetStackBufferSize(value) : void + +GetRingBufferSize() : size_t + +SetRingBufferSize(value) : void + +GetRingBufferOverwriteOnFull() : bool + +SetRingBufferOverwriteOnFull(value) : void + +GetNumberOfSlots() : size_t + +SetNumberOfSlots(value) : void + +GetSlotSizeInBytes() : size_t + +SetSlotSizeInBytes(value) : void + +SetDataRouterUid(value) : void + +GetDataRouterUid() : size_t + +GetDynamicDatarouterIdentifiers() : bool + +SetDynamicDatarouterIdentifiers(value) : void + +IsLogLevelEnabled(log_level, context, check_for_console) : bool +} + +interface ITargetConfigReader { + +ReadConfig() : Result +} + +class TargetConfigReader { + +TargetConfigReader(discoverer) + +ReadConfig() : Result + -discoverer_ : unique_ptr +} + +class TargetConfigReaderMock + +interface IConfigurationFileDiscoverer { + +FindConfigurationFiles() : vector +} + +class ConfigurationFileDiscoverer { + -path_ : unique_ptr + -stdlib_ : unique_ptr + -unistd_ : unique_ptr + -- + +ConfigurationFileDiscoverer(path, stdlib, unistd) + +FindConfigurationFiles() : vector +} + +class ConfigurationFileDiscovererMock + +class RecorderFactory { + -GetRemoteRecorder(config) : unique_ptr + -GetFileRecorder(config, fcntl) : unique_ptr + -GetConsoleRecorder(config) : unique_ptr + -- + +CreateFromConfiguration() : unique_ptr + +CreateFromConfiguration(reader) : unique_ptr + +CreateWithConsoleLoggingOnly() : unique_ptr + +CreateStub() : unique_ptr + +CreateRecorderFromLogMode(mode, config, fcntl) : unique_ptr +} + +interface IRecorderFactory { + +CreateFromConfiguration() : unique_ptr + +CreateWithConsoleLoggingOnly() : unique_ptr + +CreateStub() : unique_ptr +} + +class Runtime + +class LoggingIdentifier { + +LoggingIdentifier(value) + +GetStringView() : string_view + +operator_equal(lhs, rhs) : bool + +operator_not_equal(lhs, rhs) : bool + +kMaxLength : size_t + +data_ : array +} + +' Inheritance relationships +Recorder <|-- TextRecorder +Recorder <|-- DataRouterRecorder +Recorder <|-- FileRecorder +Recorder <|-- EmptyRecorder +Recorder <|-- CompositeRecorder +Recorder <|.. RecorderComposite + +ITargetConfigReader <|.. TargetConfigReader +ITargetConfigReader <|.. TargetConfigReaderMock + +IConfigurationFileDiscoverer <|-- ConfigurationFileDiscoverer +IConfigurationFileDiscoverer <|-- ConfigurationFileDiscovererMock + +IRecorderFactory <|.. RecorderFactory + +' Composition and aggregation relationships +DataRouterRecorder *-- Configuration : owns +CompositeRecorder *-- Configuration : owns +RecorderComposite *-- Recorder : owns +TargetConfigReader *-- IConfigurationFileDiscoverer : owns +ConfigurationFileDiscoverer *-- Path : uses +ConfigurationFileDiscoverer *-- Stdlib : uses +ConfigurationFileDiscoverer *-- Unistd : uses +Configuration *-- LoggingIdentifier : owns + +' Usage relationships +RecorderFactory ..> Configuration : uses +RecorderFactory ..> ITargetConfigReader : uses +RecorderFactory ..> Recorder : creates +TargetConfigReader ..> Configuration : creates +TargetConfigReader ..> JsonParser : uses +Runtime *-- IRecorderFactory : uses + +note top of RecorderComposite + Forwards the logs to all recorders + in the list passed in the constructor. +end note + +note right of RecorderFactory + CreateForTarget(): Loads the configuration + and instantiates the requested recorder types. + + CreateForUnitTests(): Prepares a recorder + that puts logs in standard output stream + for unit testing. + + CreateStub(): Returns Empty recorder that + discards all the logs. +end note + +note right of TargetConfigReader + Reads config from + /opt//etc/logging.json + and /ecu/logging.json and creates the + Configuration object. +end note + +note bottom of TargetConfigReaderMock + Used for unit testing the RecorderFactory. +end note + +note right of ConfigurationFileDiscoverer + Finds the file paths to the global, environmental and + application configuration files and returns all that exists. + + Global configuration: + 1. /etc/ecu_logging_config.json if it exists. + + Environmental configuration: + 1. path under the MW_LOG_CONFIG_FILE environmental + variable if it defined + + Application configuration: + 1. /etc/logging.json + 2. /logging.json + 3. /../etc/logging.json +end note + +note bottom of IConfigurationFileDiscoverer + Dependency injection for unit testing. +end note + +@enduml \ No newline at end of file diff --git a/design/configuration_use_cases.puml b/design/configuration_use_cases.puml new file mode 100644 index 0000000..78ace6f --- /dev/null +++ b/design/configuration_use_cases.puml @@ -0,0 +1,50 @@ +@startuml configuration_use_cases + +left to right direction + +actor "Developer,\nDebug Application\ndeployed on target" as Dev1 +actor "Developer,\nUnit testing" as Dev2 +actor "Performance\nEngineer" as PerfEng + +usecase "ECU wide configuration\n--\nECUID = MPP1\nLogLevel = kError" as UC1_ECU +usecase "Application configuration\n--\nAPPID = Para\nLogMode = kRemote | kConsole\nLogLevel = kDebug" as UC1_App + +usecase "Print the logs on the console for\nanalysis of unit test failures." as UC2_Console +usecase "No logging.json files,\nit should just work (tm)." as UC2_Default + +usecase "Disable logging completely\nMeasure performance impact of logging" as UC3_Performance + +Dev1 --> UC1_ECU : "Use case 1" +Dev1 --> UC1_App : "Use case 1" + +Dev2 --> UC2_Console : "Use case 2" +Dev2 --> UC2_Default : "Use case 2" + +PerfEng --> UC3_Performance : "Use case 3" + +note top of UC1_ECU + ECU-wide configuration affects + all applications on the target +end note + +note top of UC1_App + Application-specific configuration + overrides ECU-wide settings +end note + +note right of UC2_Console + For development and debugging, + console output is essential +end note + +note right of UC2_Default + Default behavior should work + without configuration files +end note + +note right of UC3_Performance + Performance testing requires + ability to disable logging +end note + +@enduml \ No newline at end of file diff --git a/design/datarouter_backend/class_diagram.puml b/design/datarouter_backend/class_diagram.puml new file mode 100644 index 0000000..6bcc06d --- /dev/null +++ b/design/datarouter_backend/class_diagram.puml @@ -0,0 +1,196 @@ +@startuml datarouter_backend_class_diagram + +package "DataRouter Backend" { + +class DataRouterBackend { + - buffer_: CircularAllocator + - message_client_: DatarouterMessageClient + -- + + DataRouterBackend(const std::size_t, const LogRecord&, DatarouterMessageClientFactory&, const Configuration&, WriterFactory) + + ReserveSlot(): amp::optional + + FlushSlot(const SlotHandle&): void + + GetLogRecord(const SlotHandle&): LogRecord& +} + +interface Backend { + + ReserveSlot(): amp::optional + + FlushSlot(const SlotHandle&): void + + GetLogRecord(const SlotHandle&): LogRecord& +} + +interface DatarouterMessageClient { + + Run(): void + + Shutdown(): void +} + +class DatarouterMessageClientImpl { + - run_started_: bool + - msg_client_ids_: MsgClientIdentifiers + - use_dynamic_datarouter_ids_: bool + - first_message_received_: std::atomic_bool + - shared_memory_writer_: SharedMemoryWriter& + - writer_file_name_: std::string + - message_passing_factory_: std::unique_ptr + - stop_source_: amp::stop_source + - thread_pool_: bmw::concurrency::ThreadPool + -- + + DatarouterMessageClientImpl(const MsgClientIdentifiers&, MsgClientBackend, MsgClientUtils, const amp::stop_source) + + Run(): void + + Shutdown(): void + + ConnectToDatarouter(): void + + SendConnectMessage(): void +} + +interface DatarouterMessageClientFactory { + + CreateOnce(const std::string&, const std::string&): std::unique_ptr +} + +class DatarouterMessageClientFactoryImpl { + - created_once_: bool + - config_: Configuration + - message_passing_factory_: MessagePassingFactory + -- + + DatarouterMessageClientFactoryImpl(const Configuration&, std::unique_ptr, std::unique_ptr, std::unique_ptr) + + CreateOnce(const std::string&, const std::string&): std::unique_ptr +} + +class SharedMemoryWriter { + - shared_data_: SharedData& + - alternating_writer_: WaitFreeAlternatingWriter + - alternating_reader_: AlternatingReader + - type_identifier_: atomic + -- + + SharedMemoryWriter(SharedData&, UnmapCallback) + + GetMaxPayloadSize(): Length + + AllocAndWrite(const TimePoint, const TypeIdentifier, const Length, WriteCallback): void + + TryRegisterType(info: Typeinfo): optional + + DetachWriter(): void +} + +class "<>\nSharedData" as SharedData { + + control_block: AlternatingControlBlock + + linear_buffer_1_offset: Length + + linear_buffer_2_offset: Length + + number_of_drops_buffer_full: std::atomic + + number_of_drops_invalid_size: std::atomic + + writer_detached: std::atomic + + producer_pid: pid_t +} + +class WriterFactory { + - osal_: OsalInstances + - mmap_result_: amp::expected + - unmap_callback_: UnmapCallback + -- + + WriterFactory(OsalInstances osal) + + Create(const std::size_t ring_size, const bool dynamic_mode, const std::string_view app_id): amp::optional + + GetIdentifier(): std::string + + GetFileName(): std::string +} + +class WaitFreeAlternatingWriter { + + Acquire(const Length length): amp::optional + + Release(const AlternatingAcquiredData&): void +} + +class AlternatingReader { + + Read(): amp::optional> + + Switch(): void +} + +class AlternatingControlBlock { + + control_block_even: LinearControlBlock + + control_block_odd: LinearControlBlock + + switch_count_points_active_for_writing: std::atomic +} + +class LinearControlBlock { + + data: amp::span + + acquired_index: std::atomic + + written_index: std::atomic + + number_of_writers: std::atomic +} + +class MsgClientIdentifiers { + - receiver_id_: std::string + - this_process_id_: pid_t + - app_id_: LoggingIdentifier + - datarouter_uid_: uid_t + - uid_: uid_t + -- + + MsgClientIdentifiers(const std::string&, const pid_t, const LoggingIdentifier&, const uid_t, const uid_t) + + GetReceiverID(): std::string& + + GetThisProcID(): const pid_t& + + GetAppID(): const LoggingIdentifier& +} + +class "CircularAllocator" as CircularAllocator { + + Allocate(size): T* + + Deallocate(ptr): void +} + +interface MessagePassingFactory { + + CreateReceiver(): amp::pmr::unique_ptr + + CreateSender(): amp::pmr::unique_ptr +} + +class "<>\nbmw::platform::logger" as Logger { + - writer: SharedMemoryWriter +} + +} + +package "OSAL" { + class OsalInstances { + + fcntl_osal: std::unique_ptr + + unistd: std::unique_ptr + + mman: std::unique_ptr + + stat_osal: std::unique_ptr + } +} + +package "Message Passing" { + interface ISender { + } + + interface IReceiver { + } +} + +' Relationships +Backend <|.. DataRouterBackend +DatarouterMessageClient <|.. DatarouterMessageClientImpl +DatarouterMessageClientFactory <|.. DatarouterMessageClientFactoryImpl + +DataRouterBackend *-- CircularAllocator +DataRouterBackend *-- DatarouterMessageClient +DatarouterMessageClientFactoryImpl ..> DatarouterMessageClientImpl : "creates" + +SharedMemoryWriter *-- SharedData +SharedMemoryWriter *-- WaitFreeAlternatingWriter +SharedMemoryWriter *-- AlternatingReader + +SharedData *-- AlternatingControlBlock +AlternatingControlBlock *-- LinearControlBlock + +WriterFactory ..> SharedMemoryWriter : "creates" +WriterFactory *-- OsalInstances + +DatarouterMessageClientImpl *-- MsgClientIdentifiers +DatarouterMessageClientImpl *-- SharedMemoryWriter +DatarouterMessageClientImpl *-- MessagePassingFactory + +MessagePassingFactory ..> ISender : "creates" +MessagePassingFactory ..> IReceiver : "creates" + +Logger *-- SharedMemoryWriter + +note bottom + using Length = std::uint64_t; + + The DataRouter Backend provides a logging backend + that communicates with external DataRouter services + via message passing and shared memory. +end note + +@enduml \ No newline at end of file diff --git a/design/datarouter_backend/datarouter_backend_datarouterbackend.puml b/design/datarouter_backend/datarouter_backend_datarouterbackend.puml new file mode 100644 index 0000000..e752b78 --- /dev/null +++ b/design/datarouter_backend/datarouter_backend_datarouterbackend.puml @@ -0,0 +1,7 @@ +@startuml +start +:Create shared memory writer; +:Create message client; +:Run message client; +stop +@enduml \ No newline at end of file diff --git a/design/datarouter_backend/inter_process_communication.puml b/design/datarouter_backend/inter_process_communication.puml new file mode 100644 index 0000000..f67aa0d --- /dev/null +++ b/design/datarouter_backend/inter_process_communication.puml @@ -0,0 +1,13 @@ +@startuml +participant "Logging Client" as Client +participant "Datarouter Process" as Datarouter +participant "DatarouterBackend" as Backend + +Client -> Backend : Write log messages via shared memory +Backend -> Datarouter : Forward messages + +note over Client : Creates and writes to shared memory buffer +note over Backend : Read-only access to client buffers +note over Backend : Creates a Sender for each client +note over Backend : One receiver for all clients +@enduml diff --git a/design/datarouter_message_client_impl_connecttodatarouter.puml b/design/datarouter_message_client_impl_connecttodatarouter.puml new file mode 100644 index 0000000..7e01351 --- /dev/null +++ b/design/datarouter_message_client_impl_connecttodatarouter.puml @@ -0,0 +1,16 @@ +@startuml +start +:Block termination Signal; +:Create sender; +:Start receiver; +stop +if (Is exit requsted?) then (yes) +if (Receiver started?) then (yes) +:Send connect message; +:Unlink shared memory file; +:Request stop for connection; +:Shutdown thread pool; +if (merge + +layer=-2) then (yes) +@enduml \ No newline at end of file diff --git a/design/frontend_dependency_graph.puml b/design/frontend_dependency_graph.puml new file mode 100644 index 0000000..28ab4df --- /dev/null +++ b/design/frontend_dependency_graph.puml @@ -0,0 +1,64 @@ +@startuml frontend_dependency_graph + +package "mw::log frontend" as frontend { + class Logger { + +Includes classes and free functions + } + class LogStreamFactory + class LogStream + class Runtime + class LoggerContainer + class LogTypes + interface IRecorder + interface IRecorderFactory +} + +package "mw::log implementation details" as details { + class Configuration + class TargetConfigReader + class DataRouterRecorder + class ConsoleRecorder + class FileRecorder + class CompositeRecorder + class RecorderFactory +} + +Logger <-- LogStreamFactory +Logger <-- LoggerContainer +LogStreamFactory <-- LogStream +LogStream <-- LogTypes +Runtime <-- LogStream +Runtime <-- LoggerContainer +Runtime <-- IRecorder +Runtime <-- IRecorderFactory + +IRecorder <|.. DataRouterRecorder +IRecorder <|.. ConsoleRecorder +IRecorder <|.. FileRecorder +IRecorder <|.. CompositeRecorder + +IRecorderFactory <|.. RecorderFactory + +RecorderFactory <-- DataRouterRecorder +RecorderFactory <-- ConsoleRecorder +RecorderFactory <-- FileRecorder +RecorderFactory <-- CompositeRecorder + +Configuration <-- RecorderFactory +Configuration <-- TargetConfigReader +TargetConfigReader <-- RecorderFactory + +Runtime ..> RecorderFactory : "decoupled dependency" + +note top of frontend + Frontend contains the public user API, + and the necessary classes to interface + with the backend and implementation details. +end note + +note bottom of details + Instable components shall + depend on stable classes. +end note + +@enduml \ No newline at end of file diff --git a/design/mw_log_datarouter_recorder.puml b/design/mw_log_datarouter_recorder.puml new file mode 100644 index 0000000..25d648b --- /dev/null +++ b/design/mw_log_datarouter_recorder.puml @@ -0,0 +1,59 @@ +@startuml mw_log_datarouter_recorder + +class DataRouterRecorder { + - LogData(const SlotHandle&, const T data): void + - SetApplicationId(LogRecord&): void + - backend_: std::unique_ptr + - config_: Configuration + - statistics_reporter_: StatisticsReporter + -- + + DataRouterRecorder(std::unique_ptr&&, const Configuration&) + + StartRecord(const std::string_view, const LogLevel): amp::optional + + StopRecord(const SlotHandle&): void + + Log(const SlotHandle&, const T): void + + IsLogEnabled(const LogLevel&, const std::string_view): bool +} + +class "<>\nmw::log::Runtime" as Runtime { +} + +class StatisticsReporter { + - recorder_: Recorder& + - report_interval_: std::chrono::seconds + - number_of_slots_: std::size_t + - slot_size_bytes_: std::size_t + - no_slot_available_counter_: std::atomic + - message_too_long_counter_: std::atomic + - last_report_time_point_nanoseconds_: std::atomic + - currently_reporting_: std::atomic_bool + -- + + StatisticsReporter(Recorder&, const std::chrono::seconds, const std::size_t, const std::size_t) + + IncrementNoSlotAvailable(): void + + IncrementMessageTooLong(): void + + Update(const std::chrono::steady_clock::time_point&): void +} + +interface IStatisticsReporter { + + IncrementNoSlotAvailable(): void + + IncrementMessageTooLong(): void + + Update(const std::chrono::steady_clock::time_point&): void +} + +class Configuration { +} + +class DataRouterBackend { +} + +class Recorder { +} + +' Relationships +Runtime ||--|| DataRouterRecorder : "is owned by" +DataRouterRecorder ||--|| StatisticsReporter : "composition" +DataRouterRecorder ||--|| Configuration : "composition" +DataRouterRecorder ||--|| DataRouterBackend : "composition" +StatisticsReporter ..|> IStatisticsReporter : "implements" +StatisticsReporter --> Recorder : "uses" + +@enduml \ No newline at end of file diff --git a/design/mw_log_file_backend.puml b/design/mw_log_file_backend.puml new file mode 100644 index 0000000..259aa5b --- /dev/null +++ b/design/mw_log_file_backend.puml @@ -0,0 +1,162 @@ +@startuml mw_log_file_backend + +package "mw::log::detail" { + +class SlotDrainer { + -TryFlushSlots(): amp::expected + -TryFlushSpans(): amp::expected + -MoreSlotsAvailableAndLoaded(): bool + -MoreSpansAvailableAndLoaded(): bool + -allocator_: std::unique_ptr>& + -message_builder_: std::unique_ptr + -context_mutex_: std::mutex + -circular_buffer_: amp::circular_buffer + -current_slot_: amp::optional> + -non_blocking_writer_: NonBlockingWriter + -limit_slots_in_one_cycle_: const std::size_t + -- + +SlotDrainer(std::unique_ptr, std::unique_ptr>&, const std::int32_t, const std::size_t, std::unique_ptr) + +PushBack(const SlotHandle&): void + +Flush(): void +} + +interface Backend { + +ReserveSlot(): amp::optional + +GetLogRecord(const SlotHandle&): LogRecord& + +FlushSlot(const SlotHandle&): void +} + +class FileOutputBackend { + -buffer_allocator_: std::unique_ptr> + -slot_drainer_: SlotDrainer + -- + +FileOutputBackend(std::unique_ptr, const std::int32_t, std::unique_ptr>, std::unique_ptr) + +ReserveSlot(): amp::optional + +FlushSlot(const SlotHandle&): void + +GetLogRecord(const SlotHandle&): LogRecord& +} + +class "Slot" as Slot { + +data: T + +in_use: std::atomic +} + +class NonBlockingWriter { + -InternalFlush(const uint64_t): amp::expected + -unistd_: std::unique_ptr + -file_handle_: std::int32_t + -number_of_flushed_bytes_: uint64_t + -buffer_: amp::span + -buffer_flushed_: Result + -max_chunk_size_: std::size_t + -- + +NonBlockingWriter(const std::int32_t, std::size_t, std::unique_ptr) + +FlushIntoFile(): amp::expected + +SetSpan(const amp::span&): void + +GetMaxChunkSize(): std::size_t +} + +class "amp::circular_buffer" as CircularBuffer { +} + +interface IMessageBuilder { + +SetNextMessage(LogRecord&): void + +GetNextSpan(): amp::optional> +} + +class DltMessageBuilder { + +SetNextMessage(LogRecord&): void + +GetNextSpan(): amp::optional> + -- + +DltMessageBuilder(ecu_id) +} + +class TextMessageBuilder { + +SetNextMessage(log_record: LogRecord&): void + +GetNextSpan(): amp::optional> + -- + +TextMessageBuilder(app_id, ecu_id) +} + +class LogRecord { + -logEntry_: LogEntry + -verbosePayload_: VerbosePayload + -- + +LogRecord(const std::size_t) + +getLogEntry(): LogEntry& + +getLogEntry(): const LogEntry& + +getVerbosePayload(): VerbosePayload& + +getVerbosePayload(): const VerbosePayload& +} + +class BackendLogMock { + +ReserveSlot(): amp::optional + +GetLogRecord(const SlotHandle&): LogRecord& + +FlushSlot(const SlotHandle&): void +} + +} + +package "mw::log" { +class Recorder { + +StartRecord(ctx: std::string_view, ll: LogLevel): amp::optional + +StopRecord(slot: SlotHandle&): void + +Log(T data): void + -- + -backend: unique_ptr +} +} + +package "OSAL" { +class Unistd { +} +class Fcntl { +} +} + +' Relationships +Backend <|.. FileOutputBackend +Backend <|.. BackendLogMock +IMessageBuilder <|.. DltMessageBuilder +IMessageBuilder <|.. TextMessageBuilder + +FileOutputBackend *-- SlotDrainer +SlotDrainer *-- NonBlockingWriter +SlotDrainer *-- CircularBuffer +SlotDrainer *-- IMessageBuilder +SlotDrainer ..> Slot : "uses" + +NonBlockingWriter ..> Unistd : "write/close" +FileOutputBackend ..> Fcntl : "SetNonBlocking" + +Recorder *-- Backend +LogRecord *-- VerbosePayload +LogRecord *-- LogEntry + +note top of SlotDrainer + Responsibilities: + - Drains slots - empties circular_buffer + when subsequent data has been flushed + - Returns status if data was flushed or stalled +end note + +note top of FileOutputBackend + Test strategy: + - Inject SlotDrainer with mocked OSAL + - Use MessageBuilderMock +end note + +note top of NonBlockingWriter + Test strategy: + - Inject OSAL mock by constructor argument + with default value +end note + +note right of CircularBuffer + By design we exclude possibility of dropped + messages in CircularBuffer because the + size of SlotAllocator is smaller or equal + than CircularBuffer size. +end note + +@enduml \ No newline at end of file diff --git a/design/mw_log_recorders.puml b/design/mw_log_recorders.puml new file mode 100644 index 0000000..4b6a096 --- /dev/null +++ b/design/mw_log_recorders.puml @@ -0,0 +1,177 @@ +@startuml mw_log_recorders + +interface "mw::log::Recorder" as Recorder { + + StartRecord(ctx: std::string_view, log_level: LogLevel): amp::optional + + StopRecord(slot: SlotHandle&): void + + Log(const SlotHandle&, const T data): void + + IsLogEnabled(const LogLevel&, const std::string_view): bool +} + +interface "mw::log::detail::Backend" as Backend { + + ReserveSlot(): amp::optional + + FlushSlot(slot: const SlotHandle&): void + + GetLogRecord(slot: const SlotHandle&): LogRecord& +} + +class "mw::log::detail::TextRecorder" as TextRecorder { + - backend_: std::unique_ptr + - config_: Configuration + - check_log_level_for_console_: bool + -- + + TextRecorder(const detail::Configuration&, std::unique_ptr, const bool) + + StartRecord(ctx: std::string_view, const LogLevel): amp::optional + + StopRecord(const SlotHandle&): void + + IsLogEnabled(const LogLevel&, const std::string_view): bool + + Log(const SlotHandle&, T data): void +} + +class "mw::log::detail::FileRecorder" as FileRecorder { + - backend_: std::unique_ptr + - config_: Configuration + -- + + FileRecorder(const detail::Configuration&, std::unique_ptr) + + StartRecord(ctx: std::string_view, const LogLevel): amp::optional + + StopRecord(const SlotHandle&): void + + IsLogEnabled(const LogLevel&, const std::string_view): bool + + Log(const SlotHandle&, T data): void +} + +class "mw::log::detail::DataRouterRecorder" as DataRouterRecorder { + - backend_: std::unique_ptr + - config_: Configuration + -- + + DataRouterRecorder(const detail::Configuration&, std::unique_ptr) + + StartRecord(ctx: std::string_view, const LogLevel): amp::optional + + StopRecord(const SlotHandle&): void + + IsLogEnabled(const LogLevel&, const std::string_view): bool + + Log(const SlotHandle&, T data): void +} + +class "mw::log::detail::EmptyRecorder" as EmptyRecorder { + + StartRecord(const std::string_view, const LogLevel): amp::optional + + StopRecord(const SlotHandle&): void + + IsLogEnabled(const LogLevel&, const std::string_view): bool + + Log(const SlotHandle&, T data): void +} + +class "mw::log::detail::CompositeRecorder" as CompositeRecorder { + - recorders_: std::vector> + -- + + CompositeRecorder(std::vector> recorders) + + StartRecord(const std::string_view, const LogLevel): amp::optional + + StopRecord(slot: SlotHandle&): void + + GetRecorders(): std::vector>& + + IsLogEnabled(const LogLevel&, const std::string_view): bool + + Log(const SlotHandle&, const T): void +} + +class "mw::log::detail::FileOutputBackend" as FileOutputBackend { +} + +class "mw::log::detail::DataRouterBackend" as DataRouterBackend { +} + +class "mw::log::detail::SlogBackend" as SlogBackend { + - app_id_: std::string + - buffer_: CircularAllocator + - slog_buffer_: slog2_buffer_t + - slog_buffer_config_: slog2_buffer_set_config_t + - slog2_instance_: std::unique_ptr + -- + + SlogBackend(const std::size_t, const LogRecord&, const std::string_view, std::unique_ptr) + + ReserveSlot(): amp::optional + + FlushSlot(const SlotHandle&): void + + GetLogRecord(const SlotHandle&): LogRecord& + - Init(verbosity: std::uint8_t): void +} + +class "mw::log::detail::TextFormat" as TextFormat { + + PutFormattedTime(VerbosePayload&): void + + TerminateLog(VerbosePayload&): void + + Log(VerbosePayload&, const T, const IntegerRepresentation): void + + Log(VerbosePayload&, const T): void +} + +class "mw::log::detail::DLTFormat" as DLTFormat { + + Log(VerbosePayload&, const bool): AddArgumentResult + + Log(VerbosePayload&, T, const IntegerRepresentation): AddArgumentResult +} + +class "DltArgumentCounter" as DltArgumentCounter { + - counter_: std::uint8_t& + -- + + DltArgumentCounter(std::uint8_t&) + + TryAddArgument(add_argument_callback): AddArgumentResult +} + +class "mw::log::detail::CircularAllocator" as CircularAllocator { +} + +class "mw::log::detail::LogRecord" as LogRecord { +} + +class "mw::log::detail::RecorderMock" as RecorderMock { +} + +class "mw::log::detail::BackendMock" as BackendMock { +} + +package "OSAL" { + class Unistd { + } + class Fcntl { + } +} + +' Interface implementations +Recorder <|.. TextRecorder +Recorder <|.. FileRecorder +Recorder <|.. DataRouterRecorder +Recorder <|.. EmptyRecorder +Recorder <|.. CompositeRecorder +Recorder <|.. RecorderMock + +Backend <|.. FileOutputBackend +Backend <|.. DataRouterBackend +Backend <|.. SlogBackend +Backend <|.. BackendMock + +' Composition relationships +TextRecorder *-- Backend +FileRecorder *-- Backend +DataRouterRecorder *-- Backend + +CompositeRecorder o-- Recorder : "contains multiple" + +' Usage relationships +TextRecorder ..> TextFormat : "uses" +DataRouterRecorder ..> DLTFormat : "uses" +DLTFormat ..> DltArgumentCounter : "uses" + +Backend ..> LogRecord : "creates" +Backend ..> CircularAllocator : "uses" + +FileOutputBackend ..> Unistd : "close" +FileOutputBackend ..> Fcntl : "SetNonBlocking" + +note top of CompositeRecorder + Composite pattern implementation + that can contain multiple recorders +end note + +note right of TextFormat + Refer text_format.h for all the Log(...) + - family of functions +end note + +note right of Recorder + Refer Recorder.h for all the Log(const SlotHandle&, const T data) + - family of functions +end note + +note bottom of EmptyRecorder + No-op implementation for + performance testing scenarios +end note + +@enduml \ No newline at end of file diff --git a/design/mw_log_shared_memory_reader.puml b/design/mw_log_shared_memory_reader.puml new file mode 100644 index 0000000..209b8f4 --- /dev/null +++ b/design/mw_log_shared_memory_reader.puml @@ -0,0 +1,122 @@ +@startuml mw_log_shared_memory_reader + +interface ReaderFactory { + + Create(const std::int32_t, const pid_t): amp::optional + + Default(): std::unique_ptr +} + +class ReaderFactoryImpl { + - mman_: amp::pmr::unique_ptr + - stat_: amp::pmr::unique_ptr + -- + + Create(const std::int32_t, const pid_t): amp::optional +} + +class SharedMemoryReader { + - shared_data_: SharedData& + - unmap_callback_: UnmapCallback + - linear_reader_: std::optional + - acquired_data_: std::optional + - number_of_acquired_bytes_: Length + - finished_reading_after_detach_: bool + - buffer_expected_to_read_next_: std::uint32_t + - is_writer_detached_: bool + - alternating_read_only_reader_: AlternatingReadOnlyReader + - DetachWriter(): void + - IsWriterDetached(): bool + -- + + SharedMemoryReader(const SharedData&, const amp::span, const amp::span, UnmapCallback) + + Read(const TypeRegistrationCallback&, const NewRecordCallback&): std::optional + + PeekNumberOfBytesAcquiredInBuffer(const std::uint32_t): std::optional + + ReadDetached(const TypeRegistrationCallback&, const NewRecordCallback&): std::optional + + GetNumberOfDropsWithBufferFull(): Length + + GetNumberOfDropsWithInvalidSize(): Length + + GetRingBufferSizeBytes(): Length + + GetNumberOfAcquiredBytes(): Length + + IsBlockReleasedByWriters(): bool + + NotifyAcquisitionSetReader(const ReadAcquireResult&): std::optional +} + +class SharedData <> { +} + +class LinearReader { + - data_: amp::span + - read_index_: Length + -- + + LinearReader(const amp::span&) + + Read(): amp::optional> +} + +class AlternatingReadOnlyReader { + - buffer_a_: amp::span + - buffer_b_: amp::span + - current_buffer_: std::uint32_t + -- + + AlternatingReadOnlyReader(const amp::span&, const amp::span&) + + Read(): amp::optional> + + SwitchBuffer(): void +} + +class DataRouter { + - subscriberDatabase_: SubscriberDatabase + - databaseCallback_: WriteSubscriberDatabaseCallback + -- + + new_source_session(): SessionPtr + + new_subscriber_session(): SessionPtr +} + +package "bmw::os" { + class Mman { + + mmap(): void* + + munmap(): int + } + + class Stat { + + fstat(): int + + get_size(): size_t + } +} + +' Relationships +ReaderFactory <|.. ReaderFactoryImpl +ReaderFactoryImpl ..> SharedMemoryReader : "<>" +ReaderFactoryImpl ..> Mman : "uses" +ReaderFactoryImpl ..> Stat : "uses" + +SharedMemoryReader *-- SharedData +SharedMemoryReader *-- LinearReader +SharedMemoryReader *-- AlternatingReadOnlyReader + +SharedMemoryReader ..> DataRouter : "interacts with" + +note top of SharedMemoryReader + Reads log data from shared memory + buffers created by writers. + Supports both attached and detached + reading modes. +end note + +note right of ReaderFactory + Factory pattern for creating + SharedMemoryReader instances + with proper OS abstractions +end note + +note bottom of SharedData + Shared memory segment containing + ring buffers and metadata shared + between writer and reader processes +end note + +note left of LinearReader + Reads data sequentially from + a single linear buffer +end note + +note left of AlternatingReadOnlyReader + Reads from alternating buffers + to support wait-free operations +end note + +@enduml \ No newline at end of file diff --git a/design/non_verbose_logging_static.puml b/design/non_verbose_logging_static.puml new file mode 100644 index 0000000..82ae7ae --- /dev/null +++ b/design/non_verbose_logging_static.puml @@ -0,0 +1,124 @@ +@startuml non_verbose_logging_static + +class "<>\nbmw::platform::logger" as Logger { + - config_: Configuration + - nvconfig_: NvConfig + - shared_memory_writer_: SharedMemoryWriter + - log_entry: log_entry + -- + + instance(const amp::optional&, const amp::optional&, amp::optional) + + RegisterType(): amp::optional + + get_type_level(): LogLevel + + get_type_threshold(): LogLevel + + get_config(): const Configuration& + + get_non_verbose_config(): const NvConfig& + + GetSharedMemoryWriter(): SharedMemoryWriter& + + InjectTestInstance(logger* const logger_ptr): void {static} +} + +class log_entry { + - default_enabled_: bool + - shared_memory_id_: std::atomic + -- + + instance(): log_entry& {static} + + RegisterTypeGetId(): amp::optional + + TrySerializeIntoSharedMemory(T): void + + TryWriteIntoSharedMemory(const T&): void + + log_at_time(timestamp_t, const T&): void + + log_serialized(const char*, const msgsize_t): void + + enabled(): bool + + enabled_at(LogLevel): bool +} + +class Configuration { + + app_id: LoggingIdentifier + + log_level: LogLevel + + buffer_size: size_t +} + +class NvConfig { + - json_path_: const std::string + - typemap_: bool + -- + + NvConfig(const std::string&) + + parseFromJson(): ReadResult + + getDltMsgDesc(const std::string&): const config::NvMsgDescriptor* +} + +class SharedMemoryWriter { + + WriteMessage(const TypeIdentifier&, const void*, size_t): bool + + GetBuffer(): void* + + IsConnected(): bool +} + +class NvMsgDescriptor { + + id_msg_descriptor_: uint32_t + + appid_: LoggingIdentifier + + ctxid_: LoggingIdentifier + + logLevel_: uint8_t +} + +class LoggingIdentifier { + + value: uint32_t + -- + + LoggingIdentifier(const char*) + + toString(): std::string +} + +class "Free Functions" as FreeFunctions <> { + + TRACE() + + TRACE_ERROR() + + TRACE_WARN() + + TRACE_INFO() + + TRACE_DEBUG() + + TRACE_LEVEL() + + LOG_ENTRY() +} + +' Relationships +Logger *-- Configuration : "owns" +Logger *-- NvConfig : "owns" +Logger *-- SharedMemoryWriter : "owns" +Logger ..> log_entry : "<>" + +NvConfig *-- NvMsgDescriptor : "contains" +NvMsgDescriptor *-- LoggingIdentifier : "uses" + +log_entry ..> SharedMemoryWriter : "writes to" +log_entry ..> Logger : "accesses" + +FreeFunctions ..> log_entry : "uses" +FreeFunctions ..> Logger : "uses" + +note top of Logger + Singleton pattern implementation + for non-verbose logging system. + Manages configuration and shared + memory communication. +end note + +note right of log_entry + Template class that handles + type-specific logging operations + and shared memory serialization. +end note + +note bottom of SharedMemoryWriter + Handles communication with + external log consumers via + shared memory segments. +end note + +note left of FreeFunctions + Global convenience functions + that provide easy access to + logging functionality. +end note + +note right of NvConfig + Configuration parser for + non-verbose logging settings + loaded from JSON files. +end note + +@enduml \ No newline at end of file diff --git a/design/shared_memory_reader_read.puml b/design/shared_memory_reader_read.puml new file mode 100644 index 0000000..60e1744 --- /dev/null +++ b/design/shared_memory_reader_read.puml @@ -0,0 +1,93 @@ +@startuml SharedMemoryReader_Read +title SharedMemoryReader::Read + +start +if (Finished reading after detach?) then (no) + :ReadRemainingDataIfWriterIsDetached; +else (yes) + if (Is Data acquired?) then (yes) + :ReadAquiredData; + else (no) + :ReadAquiredData; + endif +endif +stop + +@enduml + +@startuml ReadAcquiredData +title ReadAcquiredData + +start +:CreateReaderForBuffer; +:ReadLinearBuffer; +:Set flag to indicate if buffer1 is expected to read next is already acquired or not based on acquired data; +:Reset acquired data; +stop + +@enduml + +@startuml CreateReaderForBuffer +title CreateReaderForBuffer + +start +if (Check flag that indicates what buffer1 has acquired) then (Acquired Buffer1) + :control_block=control_block_2; +else (Not Acquired Buffer1) + :control_block=control_block_1; +endif +:Choose the relevant buffer; +:length = written index to respective control block; +:Number of acquired bytes = length; +:CreateLinearReaderFromDataAndLength; +stop + +@enduml + +@startuml ReadLinearBuffer +title ReadLinearBuffer + +start +if (Read successful?) then (no) + stop +else (yes) + if (Length of Read result < buffer entry header size) then (yes) + stop + else (no) + :Initialize header span from Read result; + :Copy header span to buffer entry header; + :Initialize payload span from Read result; + if (Header type identifier == register type token) then (yes) + if (Payload span length < type registration ID) then (no) + :Copy memory from payload span to type registration ID; + :Type ID size = size of type registration ID; + :Type registration data = subspan of type ID size from payload span; + :Trigger type registration callback with created type registration; + else (yes) + :Init shared memory record with existing header and payload span; + :Trigger new message callback with shared memory record; + endif + else (no) + :Init shared memory record with existing header and payload span; + :Trigger new message callback with shared memory record; + endif + endif +endif +stop + +@enduml + +@startuml ReadRemainingDataIfWriterIsDetached +title ReadRemainingDataIfWriterIsDetached + +start +if (Is writer detached?) then (no) + stop +else (yes) + :Reader = CreateReaderForBuffer; + :ReadLinearBuffer; + :Finished reading after detach = true; +endif +stop + +@enduml \ No newline at end of file diff --git a/design/shared_memory_writer_allocandwrite.puml b/design/shared_memory_writer_allocandwrite.puml new file mode 100644 index 0000000..605b461 --- /dev/null +++ b/design/shared_memory_writer_allocandwrite.puml @@ -0,0 +1,18 @@ +@startuml +start +if (Payload size > max payload size) then (yes) +:Acquire data; +:Write header; +:access memory; +:Write payload; +:Release acquired data; +stop +if (Is data aquired?) then (yes) +:Increment number of drops counter due to invalid size +style=wordwrap; +:Increment number of drops counter due to buffer full +style=wordwrap; +if (merge + +layer=1) then (yes) +@enduml \ No newline at end of file diff --git a/design/slot_drainer_action_diagram_design.puml b/design/slot_drainer_action_diagram_design.puml new file mode 100644 index 0000000..0be22b0 --- /dev/null +++ b/design/slot_drainer_action_diagram_design.puml @@ -0,0 +1,6 @@ +@startuml +package SlotDrainer { +} +package NonBlockingWriter { +} +@enduml \ No newline at end of file diff --git a/design/slot_drainer_sequence_design.puml b/design/slot_drainer_sequence_design.puml new file mode 100644 index 0000000..022d79f --- /dev/null +++ b/design/slot_drainer_sequence_design.puml @@ -0,0 +1,61 @@ +@startuml slot_drainer_sequence_design + +participant ":Backend" as Backend +participant ":SlotDrainer" as SlotDrainer +participant ":NonBlockingWriter" as NonBlockingWriter +participant ":CircularBuffer" as CircularBuffer +participant ":MessageBuilder" as MessageBuilder +participant ":SlotAllocator" as SlotAllocator + +note over SlotDrainer : Slot Drainer Flush Sequence + +Backend -> SlotDrainer : flush(ctx_id, log_level) +activate SlotDrainer + +SlotDrainer -> CircularBuffer : push() +SlotDrainer -> CircularBuffer : pop() +SlotDrainer -> SlotAllocator : GetUnderlyingBufferFor(slot) +SlotDrainer -> MessageBuilder : SetNextMessage(log_record) + +loop while more data available + SlotDrainer -> MessageBuilder : GetNextSpan() + SlotDrainer -> NonBlockingWriter : flush() +end + +SlotDrainer -> CircularBuffer : pop() +SlotDrainer -> SlotAllocator : GetUnderlyingBufferFor(slot) +SlotDrainer -> MessageBuilder : SetNextMessage(log_record) + +loop while more spans available + SlotDrainer -> MessageBuilder : GetNextSpan() + SlotDrainer -> NonBlockingWriter : flush() +end + +SlotDrainer -> CircularBuffer : pop() +note right of CircularBuffer : + +Backend <-- SlotDrainer +deactivate SlotDrainer + +note over SlotDrainer, NonBlockingWriter + The SlotDrainer processes slots from the circular buffer, + converts them to message spans via MessageBuilder, + and flushes them using NonBlockingWriter. +end note + +note over CircularBuffer + Circular buffer stores slot handles + that need to be drained. +end note + +note over MessageBuilder + Converts log records to serialized + message spans for output. +end note + +note over NonBlockingWriter + Writes message spans to output + (file, network, etc.) without blocking. +end note + +@enduml \ No newline at end of file diff --git a/design/verbose_logging_ara_log_interaction.puml b/design/verbose_logging_ara_log_interaction.puml new file mode 100644 index 0000000..02eee33 --- /dev/null +++ b/design/verbose_logging_ara_log_interaction.puml @@ -0,0 +1,42 @@ +@startuml verbose_logging_ara_log_interaction + +package "ara::log API" { + class "Free Functions" as AraLogFunctions { + + ara::log::LogError(): ara::log::LogStream + + ara::log::LogWarn(): ara::log::LogStream + + ara::log::LogInfo(): ara::log::LogStream + + ara::log::LogDebug(): ara::log::LogStream + } +} + +package "bmw::mw Log Implementation" { + class "Free Functions" as BmwMwLogFunctions { + + bmw::mw::LogError(): bmw::mw::LogStream + + bmw::mw::LogWarn(): bmw::mw::LogStream + + bmw::mw::LogInfo(): bmw::mw::LogStream + + bmw::mw::LogDebug(): bmw::mw::LogStream + } +} + +AraLogFunctions --> BmwMwLogFunctions : facade + +note top of AraLogFunctions + AUTOSAR Adaptive Platform + Logging API - standardized + interface for automotive + applications +end note + +note bottom of BmwMwLogFunctions + BMW Middleware logging + implementation providing + the actual logging functionality +end note + +note on link + ara::log functions act as + a facade that delegates to + bmw::mw logging implementation +end note + +@enduml \ No newline at end of file diff --git a/design/verbose_logging_sequence.puml b/design/verbose_logging_sequence.puml new file mode 100644 index 0000000..cc63323 --- /dev/null +++ b/design/verbose_logging_sequence.puml @@ -0,0 +1,74 @@ +@startuml verbose_logging_sequence + +participant ":App" as App +participant "FreeFunctions" as FreeFunctions +participant "LogStreamFactory" as LogStreamFactory +participant ":LogStream" as LogStream +participant ":Runtime" as Runtime +participant ":DataRouterRecorder" as DataRouterRecorder +participant "DLTFormater" as DLTFormater +participant ":DataRouterBackend" as DataRouterBackend +participant "TRACE" as TRACE + +App -> FreeFunctions : bmw::mw::log::Error() +activate FreeFunctions + +FreeFunctions -> LogStreamFactory : GetStream(LogLevel) +activate LogStreamFactory + +LogStreamFactory -> Runtime : GetRecorder() +Runtime -> LogStreamFactory : Recorder +LogStreamFactory -> DataRouterRecorder : creates +activate DataRouterRecorder + +LogStreamFactory -> LogStream : construct +activate LogStream + +DataRouterRecorder -> DataRouterBackend : StartRecord(ctx, LogLevel) +activate DataRouterBackend + +DataRouterBackend -> DataRouterRecorder : ReserveSlot() +DataRouterRecorder -> LogStream : amp::optional + +LogStream -> LogStreamFactory : LogStream +LogStreamFactory -> FreeFunctions : LogStream +FreeFunctions -> App : LogStream + +App -> LogStream : << Some Data +LogStream -> DataRouterRecorder : log(SlotHandle, Data) + +loop while logging data + DataRouterRecorder -> DLTFormater : write formatted data\ninto stream + note right : This part will be removed in future,\nonce we can directly stream into MrWriter\nthis will then remove any\ndynamic memory allocation. +end + +App -> LogStream : Destruct +LogStream -> DataRouterRecorder : StopRecord(SlotHandle) + +DataRouterRecorder -> TRACE : TRACE(LogEntry) +activate TRACE +TRACE -> DataRouterRecorder : +deactivate TRACE + +DataRouterRecorder -> LogStream : +deactivate DataRouterRecorder + +LogStream -> App : +deactivate LogStream + +deactivate DataRouterBackend +deactivate LogStreamFactory +deactivate FreeFunctions + +note over App, TRACE + Verbose logging sequence showing the complete flow + from application call to final trace output. + + Key points: + - LogStreamFactory creates LogStream with DataRouterRecorder + - DataRouterRecorder manages slot allocation and data formatting + - DLTFormater handles message formatting (to be removed) + - TRACE provides final log output +end note + +@enduml \ No newline at end of file diff --git a/design/verbose_logging_static.puml b/design/verbose_logging_static.puml new file mode 100644 index 0000000..641f812 --- /dev/null +++ b/design/verbose_logging_static.puml @@ -0,0 +1,247 @@ +@startuml verbose_logging_static + +enum LogLevel { + kOff = 0x00 + kFatal = 0x01 + kError = 0x02 + kWarn = 0x03 + kInfo = 0x04 + kDebug = 0x05 + kVerbose = 0x06 +} + +class Logger { + -context_ : LoggingIdentifier + -- + +Logger(ctxId) + +LogFatal() : LogStream + +LogError() : LogStream + +LogWarn() : LogStream + +LogInfo() : LogStream + +LogDebug() : LogStream + +LogVerbose() : LogStream + +WithLevel(level) : LogStream + +IsLogEnabled(level) : bool + +IsEnabled(level) : bool + +GetContext() : string_view +} + +class FreeFunctions { + +LogFatal() : LogStream + +LogError() : LogStream + +LogWarn() : LogStream + +LogInfo() : LogStream + +LogDebug() : LogStream + +LogVerbose() : LogStream + +LogFatal(context) : LogStream + +LogError(context) : LogStream + +LogWarn(context) : LogStream + +LogInfo(context) : LogStream + +LogDebug(context) : LogStream + +LogVerbose(context) : LogStream + +GetDefaultLogRecorder() : Recorder + +SetLogRecorder(recorder) : void +} + +class LogStreamFactory { + +GetStream(LogLevel, ctxId) : LogStream {static} +} + +class LogStream { + -recorder_ : Recorder + -slot_handle_ : optional + -context_id_ : LoggingIdentifier + -log_level_ : LogLevel + -- + +LogStream(recorder, level, context) + +LogStream(other) + +Flush() : void +} + +class Runtime { + -logger_container_instance_ : LoggerContainer + -recorder_instance_ : Recorder + -default_recorder_ : unique_ptr + -- + +GetRecorder() : Recorder {static} + +SetRecorder(recorder) : void {static} + +GetLoggerContainer() : LoggerContainer {static} +} + +interface Recorder { + +StartRecord(context_id, LogLevel) : optional + +StopRecord(handle) : void + +Log(handle, value1) : void + +Log(handle, value2) : void +} + +class DataRouterRecorder +class TextRecorder +class FileRecorder +class EmptyRecorder + +class Configuration + +interface Backend { + +ReserveSlot() : optional + +FlushSlot(handle) : void + +GetLogRecord(handle) : LogRecord +} + +class DataRouterBackend { + +ReserveSlot() : optional + +FlushSlot(handle) : void + +GetLogRecord(handle) : LogRecord + -- + -buffer_ : CircularAllocator + -message_client_ : unique_ptr +} + +class SlotHandle { + -recorder_to_slot_ : array + -recorder_slot_available_ : bitset + -selected_recorder_ : RecorderIdentifier + -- + +SlotHandle() + +SlotHandle(index) + +GetSlotOfSelectedRecorder() : SlotIndex + +GetSlot(identifier) : SlotIndex + +SetSlot(index, identifier) : void + +GetSelectedRecorder() : RecorderIdentifier + +SetSelectedRecorder(identifier) : void + +IsRecorderActive(identifier) : bool + +operator_equal(lhs, rhs) : bool + +operator_not_equal(lhs, rhs) : bool + +kMaxRecorders : size_t {static} +} + +class LogRecord { + -logEntry_ : LogEntry + -verbosePayload_ : VerbosePayload + -- + +getLogEntry() : LogEntry + +getLogEntry() : LogEntry + +getVerbosePayload() : VerbosePayload + +getVerbosePayload() : VerbosePayload +} + +class LogEntry { + +appId : dltid_t + +ctxId : dltid_t + +sessionId : dltid_t + +logLevel : uint8_t + +num_of_args : int8_t + +payload : string +} + +class VerbosePayload { + -buffer_ : reference_wrapper + -- + +VerbosePayload(size, str) + +Put(bytes, size) : void + +Put(callback, size) : size_t + +GetSpan() : span + +Reset() : void + +WillOverflow(size) : bool + +RemainingCapacity() : size_t + +SetBuffer(buffer) : void +} + +class TextFormat + +class StatisticsReporter + +class LoggerContainer { + -stack_ : WaitFreeStack + -default_logger_ : Logger + -- + +GetLogger(context) : Logger + +GetCapacity() : size_t + +GetDefaultLogger() : Logger +} + +class WaitFreeStack { + -elements_ : vector + -elements_written_ : vector + -write_index_ : AtomicIndex + -capacity_full_ : AtomicBool + -- + +WaitFreeStack(size) + +TryPush(element) : optional + +Find(predicate) : optional +} + +class LoggingIdentifier + +class RecorderIdentifier { + +value : size_t +} + +class TRACE_MACRO + +' Relationships +Logger ..> LogStreamFactory : uses +FreeFunctions ..> LogStreamFactory : uses +LogStreamFactory ..> LogStream : create +LogStream *-- Recorder : records data +LogStream *-- SlotHandle : owns + +Recorder <|-- DataRouterRecorder +Recorder <|-- TextRecorder +Recorder <|-- FileRecorder +Recorder <|-- EmptyRecorder + +Backend <|.. DataRouterBackend +DataRouterRecorder *-- DataRouterBackend : reserve and flush slots +DataRouterRecorder *-- Configuration : get application specific config +DataRouterRecorder *-- StatisticsReporter + +Runtime *-- LoggerContainer : owns +LoggerContainer *-- WaitFreeStack : owns +LoggerContainer *-- Logger : owns +Logger *-- LoggingIdentifier : owns + +DataRouterBackend ..> LogRecord : creates and fills +LogRecord *-- LogEntry : creates and fills +LogRecord *-- VerbosePayload : creates and fills + +TextRecorder ..> TextFormat : Log + +Runtime ..> Recorder : get current recorder + +TRACE_MACRO ..> Runtime : on sync + +note top of DataRouterBackend + For the time being, + DataRouterRecorder will not only be a facade + but contain DLT specific logic. + In this case calling + StartRecord() and StopRecord() +end note + +note bottom of LogEntry + In future this will be removed + and we will stream into MwsrWriter +end note + +note bottom of TRACE_MACRO + In future this will be removed + and we will stream into MwsrWriter +end note + +note bottom of VerbosePayload + In future this will be removed + and we will stream into MwsrWriter +end note + +note right of Recorder + A user is allowed to use the Recorder interface + for its abstraction. But its not recommended due + to its more complicated usage. +end note + +note top + This diagram only illustrates the use-case of verbose logging. +end note + +@enduml \ No newline at end of file diff --git a/design/wait_free_stack_trypush.puml b/design/wait_free_stack_trypush.puml new file mode 100644 index 0000000..aa848f1 --- /dev/null +++ b/design/wait_free_stack_trypush.puml @@ -0,0 +1,13 @@ +@startuml +start +if (Stack capacity is full?) then (yes) +:Current write index = write index; +if (Current write index >= number +of elements in stack) then (yes) +:Increment write index; +:Return null value; +:Push element to stack; +:Return element value; +stop +:Stack capacity full = true; +@enduml \ No newline at end of file diff --git a/detail/wait_free_producer_queue/design/class_diagram.puml b/detail/wait_free_producer_queue/design/class_diagram.puml new file mode 100644 index 0000000..47eac54 --- /dev/null +++ b/detail/wait_free_producer_queue/design/class_diagram.puml @@ -0,0 +1,85 @@ +@startuml class_diagram + +class "<>\nLinearControlBlock" as LinearControlBlock { + + data: span + + acquired_index: atomic_int + + written_index: atomic_int + + number_of_writers: atomic_int +} + +class WaitFreeLinearWriter { + + WaitFreeLinearWriter(control_block: LinearControlBlock&) + + Acquire(length): optional + + Release(AcquiredData): void +} + +class LinearReader { + + LinearReader(span& data) + + Read(): optional> +} + +class "<>\nAlternatingControlBlock" as AlternatingControlBlock { + + linear_control_block_1: LinearControlBlock + + linear_control_block_2: LinearControlBlock +} + +class WaitFreeAlternatingWriter { + + WaitFreeAlternatingWriter(control_block: AlternatingControlBlock&) + + Acquire(length): optional + + Release(AcquiredData): void +} + +class AlternatingReader { + + AlternatingReader(AlternatingControlBlock& data) + + Read(): optional> + + Switch(): void +} + +' Relationships +WaitFreeLinearWriter --> LinearControlBlock : "uses" +LinearReader --> LinearControlBlock : "uses" + +AlternatingControlBlock *-- LinearControlBlock : "composition" + +WaitFreeAlternatingWriter --> AlternatingControlBlock : "uses" +AlternatingReader --> AlternatingControlBlock : "uses" + +note top of LinearControlBlock + Plain Old Data (POD) structure + containing shared memory control + information for linear buffer. +end note + +note bottom of AlternatingControlBlock + POD structure containing two + linear control blocks for + alternating buffer strategy. +end note + +note left of WaitFreeLinearWriter + Wait-free writer implementation + for single linear buffer. + Multiple writers can operate + concurrently without blocking. +end note + +note right of LinearReader + Single-threaded reader for + linear buffer. Reads data + sequentially from buffer. +end note + +note left of WaitFreeAlternatingWriter + Wait-free writer for alternating + buffers. Switches between two + linear buffers to avoid + reader-writer conflicts. +end note + +note right of AlternatingReader + Reader for alternating buffers. + Can switch between buffers + when current buffer is full. +end note + +@enduml \ No newline at end of file diff --git a/detail/wait_free_producer_queue/design/wait_free_alternating_buffers.puml b/detail/wait_free_producer_queue/design/wait_free_alternating_buffers.puml new file mode 100644 index 0000000..98d4fcc --- /dev/null +++ b/detail/wait_free_producer_queue/design/wait_free_alternating_buffers.puml @@ -0,0 +1,71 @@ +@startuml wait_free_alternating_buffers + +participant "Producer Thread\n(WaitFreeAlternatingWriter class)" as Producer +participant "Consumer Thread\n(AlternatingReader class)" as Consumer +participant "AlternatingControlBlock" as ControlBlock + +note over ControlBlock + **Initial State:** + acquired_index_1 == 0, written_index_1 == 0, number_of_writers_1 == 0 + acquired_index_2 == 0, written_index_2 == 0, number_of_writers_2 == 0 + active_for_writing == 1 +end note + +Producer -> ControlBlock : Acquire(64 bytes) +note right + number_of_writers_1.increment() + acquired_index_1 == 72 + number_of_writers_1 == 1 +end note + +Producer -> Producer : WriteDataProducer1(acquired_data) +note right + Writing on buffer 1, range [8:72) +end note + +Consumer -> ControlBlock : Switch() +note right + **Reset buffer 2 for writing:** + acquired_index_2.store(0) + written_index_2.store(0) + + **Toggle future writers to use buffer 2:** + active_for_writing.store(2) + + **Block until pending writers finished on buffer 1.** +end note + +Producer -> ControlBlock : Release(64 bytes) +note right + written_index_1 == (8 + 64) + number_of_writers_1 == 0 +end note + +note over ControlBlock + **Final State:** + acquired_index_1 == 72, written_index_1 == 72, number_of_writers_1 == 0 + acquired_index_2 == 24, written_index_2 == 24, number_of_writers_2 == 0 + active_for_writing == 2 +end note + +note left of Producer + **Linear Buffer 1 Content:** + + **[0:8)** - Length of Packet 1 + Data: 64 + + **[8:72)** - Producer 1, Packet 1 + Data: WriteDataProducer1() +end note + +note right of Consumer + **Linear Buffer 2 Content:** + + **[0:8)** - Length of Packet 2 + Data: 16 + + **[8:24)** - Producer1, Packet 2 + Data: Packet 2 +end note + +@enduml \ No newline at end of file diff --git a/detail/wait_free_producer_queue/design/wait_free_linear_buffer.puml b/detail/wait_free_producer_queue/design/wait_free_linear_buffer.puml new file mode 100644 index 0000000..dd69ce6 --- /dev/null +++ b/detail/wait_free_producer_queue/design/wait_free_linear_buffer.puml @@ -0,0 +1,69 @@ +@startuml wait_free_linear_buffer + +participant "Producer Thread 1\n(WaitFreeLinearWriter class)" as Producer1 +participant "Producer Thread 2\n(WaitFreeLinearWriter class)" as Producer2 +participant "LinearControlBlock" as ControlBlock + +note over ControlBlock + **Initial State:** + acquired_index == 0, written_index == 0 +end note + +Producer1 -> ControlBlock : Acquire(64 bytes) +note right + length_index = acquired_index.fetch_add(8 + 64) + acquired_index == 72 + length_index == 0 +end note + +Producer1 -> Producer1 : WriteDataProducer1(acquired_data) +note right : Writing on range [8:72) + +Producer2 -> ControlBlock : Acquire(16 bytes) +note right + acquired_index == 96 + length_index == 72 +end note + +note over Producer1, Producer2 : **happens before** + +Producer2 -> Producer2 : WriteDataProducer2(acquired_data) +note right : Writing on range [80:96) + +note over Producer1, Producer2 : **Concurrent Writing** + +Producer2 -> ControlBlock : Release(16 bytes) +note right + written_index.fetch_add(8 + 16) + written_index == 8 + 16 +end note + +note over Producer2, Producer1 : **happens before** + +Producer1 -> ControlBlock : Release(64 bytes) +note right + written_index == 24 + (8 + 64) +end note + +note over ControlBlock + **Final State:** + acquired_index == written_index == 96 +end note + +note left of Producer1 + **Linear Buffer Content:** + + **[0:8)** - Length of Producer1 + Data: 64 + + **[8:72)** - Payload of Producer1 + Data: WriteDataProducer1() + + **[72:80)** - Length of Payload 2 + Data: 16 + + **[80:96)** - Payload of Producer2 + Data: WriteDataProducer2() +end note + +@enduml \ No newline at end of file From 2a31389964bf0645e1f819b9864b38afa422e3a5 Mon Sep 17 00:00:00 2001 From: MaciejKaszynski Date: Wed, 15 Oct 2025 08:39:07 +0100 Subject: [PATCH 2/8] Fixing some diagrams --- .gitignore | 1 + ...r_buffer_allocator_acquireslottowrite.puml | 21 ++-- design/configuration_sequence.puml | 113 +++++++++--------- ...ssage_client_impl_connecttodatarouter.puml | 21 ++-- design/frontend_dependency_graph.puml | 27 ++--- design/mw_log_datarouter_recorder.puml | 8 +- design/shared_memory_reader_read.puml | 42 +++---- .../shared_memory_writer_allocandwrite.puml | 27 ++--- design/wait_free_stack_trypush.puml | 21 ++-- 9 files changed, 142 insertions(+), 139 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e33609d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.png diff --git a/design/circular_buffer_allocator_acquireslottowrite.puml b/design/circular_buffer_allocator_acquireslottowrite.puml index 427b150..e455b4f 100644 --- a/design/circular_buffer_allocator_acquireslottowrite.puml +++ b/design/circular_buffer_allocator_acquireslottowrite.puml @@ -1,13 +1,16 @@ @startuml +title Circular Buffer Allocator Acquire Slot To Write start -:Increment loop counter; -if (Loop counter > circular buffer size) then (yes) -:Increment claimed slot; -stop -:Loop counter = 0; -:Slot index = claimed slot % circular -buffer capacity; -if (Circular buffer at Slot index in use?) then (yes) +:Loop Counter = 0; +repeat :Increment loop counter; + :Increment claimed slot; + if (Loop counter > circular buffer size) then (Yes) + stop + else (no) + :Slot index = claimed slot % circular buffer capacity; + endif +repeat while (Circular buffer at Slot index in use?) is (Yes) not (No); :Mark Slot as in use; :Return Slot index; -@enduml \ No newline at end of file +stop +@enduml diff --git a/design/configuration_sequence.puml b/design/configuration_sequence.puml index f4b69a9..7121ba3 100644 --- a/design/configuration_sequence.puml +++ b/design/configuration_sequence.puml @@ -1,4 +1,5 @@ @startuml configuration_sequence +title Configuration Sequence participant "App" as App participant "FreeFunctions" as FF @@ -9,104 +10,102 @@ participant "TargetConfigReader" as TCR participant "ConfigurationFileDiscoverer" as CFD participant "JSON" as JSON participant "OS" as OS -participant "DatarouterRecorder" as DR -participant "TextRecorder" as TR -participant "CompositeRecorder" as CR App -> FF: Error() -activate FF +note right of App + If a user wants to avoid the latency + on the first log message, we provide + mw::log::Initialize() that shall + take care of initializing the runtime. + If this method is called, the first + log message will directly use the + initialized records. +end note FF -> LSF: GetStream(LogLevel) activate LSF LSF -> RT: GetRecorder() -activate RT + +note left of RT + If no recorder exists yet, + create one using the factory. +end note RT -> RF: CreateForTarget() activate RF RF -> TCR: ReadConfig() -activate TCR TCR -> CFD: FindConfigurationFiles() -activate CFD + +note right of CFD + Find and return the existing + config files. +end note CFD -> OS: access(/etc/ecu_logging_config.json) -CFD -> OS: access(MW_LOG_CONFIG_FILE) -CFD -> OS: access(/etc/logging.json) -CFD -> OS: access(/logging.json) +alt MW_LOG_CONFIG_FILE is defined + CFD -> OS: access(MW_LOG_CONFIG_FILE) +else MW_LOG_CONFIG_FILE is undefined + CFD -> OS: access(/etc/logging.json) + CFD -> OS: access(/logging.json) + CFD -> OS: access(/../etc/logging.json) +end -CFD -> TCR: Return config file paths -deactivate CFD +CFD -> TCR: Return global and environmental or application config file paths. TCR -> JSON: FromFile(global_file_path) -activate JSON JSON -> TCR: -deactivate JSON alt MW_LOG_CONFIG_FILE is defined - TCR -> JSON: FromFile(environmental_file_path) - activate JSON + TCR -> JSON: FromFile() JSON -> TCR: - deactivate JSON else MW_LOG_CONFIG_FILE is undefined - TCR -> JSON: FromFile(app_file_path) - activate JSON + TCR -> JSON: FromFile() JSON -> TCR: - deactivate JSON end -TCR -> RF: Return Configuration instance -deactivate TCR - -RF -> DR: Create instance with configuration -activate DR -DR -> RF: -deactivate DR +note right of TCR + Overwrite global configiuration + with environmental or application config file paths. +end note -RF -> TR: Create instance with configuration -activate TR -TR -> RF: -deactivate TR +TCR -> RF: Return Configuration instance -RF -> CR: Create with Datarouter and StandardOut recorders -activate CR -CR -> RF: -deactivate CR +group Possibilities -RF -> RT: Store returned instance -deactivate RF +note right of RF + For exmple, kRemote and kConsole + requested in configuration. +end note -RT -> LSF: Return Recorder instance -deactivate RT +Create "DatarouterRecorder" as DR -LSF -> FF: -deactivate LSF +RF -> DR: Create instance with configuration -FF -> App: -deactivate FF +Create "TextRecorder" as TR -note left of App - If a user wants to avoid the latency - on the first log message, we provide - mw::log::Initialize() that shall - take care of initializing the runtime. -end note +RF -> TR: Create instance with configuration -note right of CFD - Find and return the existing - config files. -end note +Create "CompositeRecorder" as CR +RF -> CR: Create with Datarouter and StandardOut recorders note right of CR Composite Recorder will forward the logs to DatarouterRecorder and TextRecorder end note -note right of RT - If no recorder exists yet, - create one using the factory. -end note +end + + + +RF -> RT: Store returned instance +deactivate RF + +RT -> LSF: Return Recorder instance + +deactivate LSF @enduml \ No newline at end of file diff --git a/design/datarouter_message_client_impl_connecttodatarouter.puml b/design/datarouter_message_client_impl_connecttodatarouter.puml index 7e01351..69ded53 100644 --- a/design/datarouter_message_client_impl_connecttodatarouter.puml +++ b/design/datarouter_message_client_impl_connecttodatarouter.puml @@ -3,14 +3,17 @@ start :Block termination Signal; :Create sender; :Start receiver; +if (Receiver started?) then (Yes) + if (Is exit requsted?) then (No) + :Send connect message; + stop + else (Yes) + endif +else (No) + :Unlink shared memory file; + :Request stop for connection; + :Shutdown thread pool; +endif +:Merge; stop -if (Is exit requsted?) then (yes) -if (Receiver started?) then (yes) -:Send connect message; -:Unlink shared memory file; -:Request stop for connection; -:Shutdown thread pool; -if (merge - -layer=-2) then (yes) @enduml \ No newline at end of file diff --git a/design/frontend_dependency_graph.puml b/design/frontend_dependency_graph.puml index 28ab4df..e3a9174 100644 --- a/design/frontend_dependency_graph.puml +++ b/design/frontend_dependency_graph.puml @@ -23,26 +23,25 @@ package "mw::log implementation details" as details { class RecorderFactory } -Logger <-- LogStreamFactory -Logger <-- LoggerContainer -LogStreamFactory <-- LogStream -LogStream <-- LogTypes -Runtime <-- LogStream -Runtime <-- LoggerContainer -Runtime <-- IRecorder -Runtime <-- IRecorderFactory +Logger --> LogStreamFactory +LoggerContainer --> Logger +Logger --> LogStream +LogStreamFactory --> LogStream +LogStreamFactory --> Runtime +LogStream --> LogTypes +Runtime --> LoggerContainer +Runtime --> IRecorder +Runtime --> IRecorderFactory IRecorder <|.. DataRouterRecorder IRecorder <|.. ConsoleRecorder IRecorder <|.. FileRecorder IRecorder <|.. CompositeRecorder -IRecorderFactory <|.. RecorderFactory - -RecorderFactory <-- DataRouterRecorder -RecorderFactory <-- ConsoleRecorder -RecorderFactory <-- FileRecorder -RecorderFactory <-- CompositeRecorder +RecorderFactory --> DataRouterRecorder +RecorderFactory --> ConsoleRecorder +RecorderFactory --> FileRecorder +RecorderFactory --> CompositeRecorder Configuration <-- RecorderFactory Configuration <-- TargetConfigReader diff --git a/design/mw_log_datarouter_recorder.puml b/design/mw_log_datarouter_recorder.puml index 25d648b..321ad39 100644 --- a/design/mw_log_datarouter_recorder.puml +++ b/design/mw_log_datarouter_recorder.puml @@ -49,10 +49,10 @@ class Recorder { } ' Relationships -Runtime ||--|| DataRouterRecorder : "is owned by" -DataRouterRecorder ||--|| StatisticsReporter : "composition" -DataRouterRecorder ||--|| Configuration : "composition" -DataRouterRecorder ||--|| DataRouterBackend : "composition" +Runtime <-- DataRouterRecorder : "is owned by" +DataRouterRecorder *-- StatisticsReporter : "composition" +DataRouterRecorder *-- Configuration : "composition" +DataRouterRecorder *-- DataRouterBackend : "composition" StatisticsReporter ..|> IStatisticsReporter : "implements" StatisticsReporter --> Recorder : "uses" diff --git a/design/shared_memory_reader_read.puml b/design/shared_memory_reader_read.puml index 60e1744..63a9f33 100644 --- a/design/shared_memory_reader_read.puml +++ b/design/shared_memory_reader_read.puml @@ -2,14 +2,10 @@ title SharedMemoryReader::Read start -if (Finished reading after detach?) then (no) +if (Finished reading after detach?) then (No) + :ReadAquiredData; :ReadRemainingDataIfWriterIsDetached; -else (yes) - if (Is Data acquired?) then (yes) - :ReadAquiredData; - else (no) - :ReadAquiredData; - endif +else (Yes) endif stop @@ -19,10 +15,14 @@ stop title ReadAcquiredData start -:CreateReaderForBuffer; -:ReadLinearBuffer; -:Set flag to indicate if buffer1 is expected to read next is already acquired or not based on acquired data; -:Reset acquired data; +if (Is Data acquired) then (Yes) + :CreateReaderForBuffer; + :ReadLinearBuffer; + :Set flag to indicate if buffer1 is expected to read next is already acquired + or not based on acquired data; + :Reset acquired data; +else (No) +endif stop @enduml @@ -48,31 +48,26 @@ stop title ReadLinearBuffer start -if (Read successful?) then (no) - stop -else (yes) +while (Read succesfull?) is (Yes) if (Length of Read result < buffer entry header size) then (yes) - stop - else (no) + else (No) :Initialize header span from Read result; :Copy header span to buffer entry header; :Initialize payload span from Read result; - if (Header type identifier == register type token) then (yes) - if (Payload span length < type registration ID) then (no) + if (Header type identifier == register type token) then (Yes) + if (Payload span length < type registration ID) then (No) :Copy memory from payload span to type registration ID; :Type ID size = size of type registration ID; :Type registration data = subspan of type ID size from payload span; :Trigger type registration callback with created type registration; - else (yes) - :Init shared memory record with existing header and payload span; - :Trigger new message callback with shared memory record; + else(Yes) endif - else (no) + else (No) :Init shared memory record with existing header and payload span; :Trigger new message callback with shared memory record; endif endif -endif +endwhile (No) stop @enduml @@ -82,7 +77,6 @@ title ReadRemainingDataIfWriterIsDetached start if (Is writer detached?) then (no) - stop else (yes) :Reader = CreateReaderForBuffer; :ReadLinearBuffer; diff --git a/design/shared_memory_writer_allocandwrite.puml b/design/shared_memory_writer_allocandwrite.puml index 605b461..a9c0cf7 100644 --- a/design/shared_memory_writer_allocandwrite.puml +++ b/design/shared_memory_writer_allocandwrite.puml @@ -1,18 +1,17 @@ @startuml start -if (Payload size > max payload size) then (yes) -:Acquire data; -:Write header; -:access memory; -:Write payload; -:Release acquired data; +if (Payload size > max payload size) then (Yes) + :Increment number of drops; +else(No) + :Acquire data; + if (Is data aquired?) then (Yes) + :Write header; + :Access memory; + :Write payload; + :Release acquired data; + else(No) + :Increment number of drops; + endif +endif stop -if (Is data aquired?) then (yes) -:Increment number of drops counter due to invalid size -style=wordwrap; -:Increment number of drops counter due to buffer full -style=wordwrap; -if (merge - -layer=1) then (yes) @enduml \ No newline at end of file diff --git a/design/wait_free_stack_trypush.puml b/design/wait_free_stack_trypush.puml index aa848f1..c982496 100644 --- a/design/wait_free_stack_trypush.puml +++ b/design/wait_free_stack_trypush.puml @@ -1,13 +1,18 @@ @startuml +title Wait Free Stack TryPush start if (Stack capacity is full?) then (yes) -:Current write index = write index; -if (Current write index >= number -of elements in stack) then (yes) -:Increment write index; -:Return null value; -:Push element to stack; -:Return element value; +else (No) + :Current write index = write index; + :Increment write index; + if (Current write index >= number of elements in stack) then (yes) + :Stack capacity full = true; + else (No) + :Push element to stack; + :Return element value; + stop + endif +endif + :Return null value; stop -:Stack capacity full = true; @enduml \ No newline at end of file From ad287f815cec53039c8feb6c3d29df5b5896ffc4 Mon Sep 17 00:00:00 2001 From: MaciejKaszynski Date: Mon, 20 Oct 2025 13:13:42 +0100 Subject: [PATCH 3/8] next set of diagrams --- .../inter_process_communication.puml | 40 +++++++--- design/non_verbose_logging_static.puml | 76 +++++-------------- .../slot_drainer_action_diagram_design.puml | 28 ++++++- design/slot_drainer_sequence_design.puml | 37 +++------ design/verbose_logging_sequence.puml | 72 +++++++++++------- .../design/wait_free_linear_buffer.puml | 7 +- 6 files changed, 130 insertions(+), 130 deletions(-) diff --git a/design/datarouter_backend/inter_process_communication.puml b/design/datarouter_backend/inter_process_communication.puml index f67aa0d..65dde68 100644 --- a/design/datarouter_backend/inter_process_communication.puml +++ b/design/datarouter_backend/inter_process_communication.puml @@ -1,13 +1,35 @@ @startuml -participant "Logging Client" as Client -participant "Datarouter Process" as Datarouter -participant "DatarouterBackend" as Backend +!$shmem_path="/tmp/logging...shmem\nwith permissions 0664" +!$log_recv_path="/mw_com/message_passing/logging.." +!$data_router_recv_path="/mw_com/message_passing/logging.datarouter_recv" + +component "<>\nLogging Client" as log_client { + component "mv::log" as log_client_log_lib{ + portin "mv::com::Receiver" as log_client_receiver_port + portout "mv::com::Sender" as log_client_sender_port + } +} + +component "<>\nDatarouter" as data_router{ + portout "mv::com:::Sender" as data_router_sender_port + portin "mv::com:::Receiver" as data_router_receiver_port +} + +rectangle "Shared Memory" as shmem + +note left of data_router_receiver_port + One receiver for all clients $ +end note + +note left of log_client_receiver_port + Instantiates side channel to receive messages from <$log_recv_path> +end note + +data_router_sender_port --> log_client_receiver_port : "Creates a Sender for each client to send requests to the client" +log_client_sender_port --> data_router_receiver_port : "Creates a Sender to reply to requests from Datarouter" + +data_router --> shmem: "Read-only access" +shmem --* log_client_log_lib: "Creates and write to\n<$shmem_path>" -Client -> Backend : Write log messages via shared memory -Backend -> Datarouter : Forward messages -note over Client : Creates and writes to shared memory buffer -note over Backend : Read-only access to client buffers -note over Backend : Creates a Sender for each client -note over Backend : One receiver for all clients @enduml diff --git a/design/non_verbose_logging_static.puml b/design/non_verbose_logging_static.puml index 82ae7ae..51b7109 100644 --- a/design/non_verbose_logging_static.puml +++ b/design/non_verbose_logging_static.puml @@ -13,14 +13,14 @@ class "<>\nbmw::platform::logger" as Logger { + get_config(): const Configuration& + get_non_verbose_config(): const NvConfig& + GetSharedMemoryWriter(): SharedMemoryWriter& - + InjectTestInstance(logger* const logger_ptr): void {static} + + {static} InjectTestInstance(logger* const logger_ptr): void } -class log_entry { +class "log_entry" as log_entry <