Technical Overview
Clock Implementations
PhoenixClock provides two main types of clock backends:
- Mock Backend: Implemented by the
PClockMockclass. This backend simulates clock behavior by reading time values from a mock file. The mock file can be generated by recording real clock values or created manually, making it ideal for testing and reproducibility. - Real Backend: Implemented by classes such as
PClockNs. This backend interacts with the actual system or hardware clock to provide real time measurements. You can also implement your own backend for specific clock types, such asClockMicrosecond.
Developing Your Own Backend
To create a custom clock backend, you can use the architecture of PClockNs as a template. Your backend should implement at least the following methods:
now(): Returns the current time value according to your backend's logic.sleep(EllapsedTime sleepTime): Pauses execution for the specified duration.
This ensures compatibility with the generic clock manager and allows seamless integration with the rest of the PhoenixClock system.
Double Backend Design
PhoenixClock is built around the concept of a double backend design. This architecture allows you to switch easily between real and mock clock backends, providing flexibility for both production and testing environments. A generic clock manager has to instantiate the two backends, the real one and the mock one and then, switch between them using the available modes.
///@brief Generic clock which can use several backends (real clock backend and a mock of clock)
template<typename _TBackend, typename _TMockBackend>
class PGenericClock
You can, for example, use the PClockMock backend provided by PhoenixClock for the mock backend and the PCLockNs as the real backend.
If so, you can instatiate your clock manager as the following :
///Definition of a clock using ClockNs and ClockMock as backends
typedef PGenericClock<PClockNs, PClockMock> ClockNanoSecond;
How It Works
- The generic clock manager (
PGenericClock) holds both a real backend and a mock backend. - Depending on the selected mode, it delegates time queries and sleep operations to either the real or mock backend.
- Modes include:
- NO_MOCK: Only the real backend is used for all operations.
- MOCK: Only the mock backend is used, simulating time operations via file I/O.
- MOCK_RECORD: The real backend is used for time measurement, but all time values are also recorded using the mock backend for later replay or analysis.
Switching between different modes
typedef PGenericClock<PClockNs, PClockMock> ClockNanoSecond;// Definition of the test clock for more precise time
phoenix_createClockMock("", 3lu); // Fill a mock fil with 3 values for testing (0, 1, 2)
ClockNanosecond clock; // Create the clock
clock.setMockPrefix(""); // Setting the mock prefix
clock.setMode(PClockMode::NO_MOCK); // Switching to NO_MOCK mode to call real clock
data_stream_assert(clock.now() != 0l); // Check that the real clock return the real time (should be different from 0)
clock.setMode(PClockMode::MOCK); // Switching to MOCK mode to evaluate that the mock file contains the proper values
data_stream_assert(clock.now() == 0l); // First value should be 0
data_stream_assert(clock.now() == 1l); // Second value should be 1
data_stream_assert(clock.now() == 2l); // Third value should be 2
data_stream_assert(clock.now() == 0l); // 4th value should be 0. Once all mock values have been played, the sequence loops back at beginning.
clock.sleep(1000l); // Sleep for 1 microsecond in MOCK mode should not do anything
clock.setMode(PClockMode::MOCK_RECORD); // Switching to MOCK_RECORD mode to revord a real clock and the replay it
clock.sleep(1000l); // Sleep for 1 microsecond in MOCK_RECORD mode should sleep but not registrer it in the mock file
time_t nowValue = clock.now(); // Register current time
data_stream_assert(nowValue != 0l); // Check that it is different from 0
clock.setMode(PClockMode::MOCK); // Switch to MOCK mode to replay the clock
clock.now(); // Skip second value (first one has already loop)
clock.now(); // Skip third value
data_stream_assert(clock.now() == nowValue); // Check that the mock has a correct value comparing to the real clock recorded
Benefits
- Testing: Run unit tests or integration tests without needing real hardware clocks.
- Reproducibility: Replay recorded time sequences for debugging or validation.
- Extensibility: Add new clock types or backends easily by following the established convention.
- Consistent API: All backends expose the same interface, so switching is seamless.
Summary
By following this double backend design, PhoenixClock enables robust, flexible, and maintainable time management for both real-world and simulated scenarios. This is especially useful for scientific, embedded, or time-sensitive applications where reproducibility and testing are critical.