#include #include #include #include #include #include #include #include #include #include namespace smo { namespace stim_buff { namespace { long computeTimesliceResidueMs( long productionDurationMs, long periodMs) { if (productionDurationMs >= periodMs) { return 0; } return periodMs - productionDurationMs; } void logProductionOverrunIfNeeded( const char *daemonName, long productionDurationMs, long periodMs, size_t &nTimesliceOverruns) { if (productionDurationMs <= periodMs) { return; } ++nTimesliceOverruns; const long overrunByMs = productionDurationMs - periodMs; std::cerr << daemonName << ": production overrun: actual=" << productionDurationMs << "ms budget=" << periodMs << "ms overrunBy=" << overrunByMs << "ms nOverruns=" << nTimesliceOverruns << std::endl; } long durationMsSince( const std::chrono::high_resolution_clock::time_point &startStamp, const std::chrono::high_resolution_clock::time_point &endStamp) { const auto duration = endStamp - startStamp; return std::chrono::duration_cast( duration).count(); } void logDaemonDurationsIfVerbose( const char *daemonName, long productionDurationMs, long timesliceDurationMs, long periodMs) { if (!OptionParser::getOptions().verbose) { return; } std::cerr << daemonName << ": daemon durations: production=" << productionDurationMs << "ms timeslice=" << timesliceDurationMs << "ms period=" << periodMs << "ms" << std::endl; } } // namespace std::shared_ptr StimulusProducer::getAttachedStimulusBuffer( const std::shared_ptr& spec) const { for (const auto& buffer : attachedStimulusBuffers) { if (buffer && buffer->deviceAttachmentSpec && *buffer->deviceAttachmentSpec == *spec) { return buffer; } } return nullptr; } std::shared_ptr StimulusProducer::getAttachedStimulusBufferByAttachIdentity( const std::string& deviceIdentifier, const std::string& qualeIfaceApi) const { for (const auto& buffer : attachedStimulusBuffers) { if (!buffer || !buffer->deviceAttachmentSpec) { throw std::runtime_error( "StimulusProducer::getAttachedStimulusBufferByAttachIdentity: " "encountered null buffer or null deviceAttachmentSpec in " "attachedStimulusBuffers (should never happen)"); } if (buffer->deviceAttachmentSpec->deviceIdentifier != deviceIdentifier) { continue; } if (buffer->deviceAttachmentSpec->qualeIfaceApi != qualeIfaceApi) { continue; } return buffer; } return nullptr; } bool StimulusProducer::hasBufferWithQualeIfaceApi( const std::string& qualeIfaceApi) const { for (const auto& buffer : attachedStimulusBuffers) { if (!buffer || !buffer->deviceAttachmentSpec) { throw std::runtime_error( "StimulusProducer::hasBufferWithQualeIfaceApi: encountered " "null buffer or null deviceAttachmentSpec in " "attachedStimulusBuffers (should never happen)"); } if (buffer->deviceAttachmentSpec->qualeIfaceApi != qualeIfaceApi) { continue; } return true; } return false; } void StimulusProducer::ensureNoDuplicateQualeIface( const std::string& qualeIfaceApi) const { if (!hasBufferWithQualeIfaceApi(qualeIfaceApi)) { return; } throw std::runtime_error( "duplicate qualeIface '" + qualeIfaceApi + "' for this producer session"); } bool StimulusProducer::addAttachedStimulusBufferIfNotExists( const std::shared_ptr& buffer) { if (!buffer) { return false; } auto it = std::find_if( attachedStimulusBuffers.begin(), attachedStimulusBuffers.end(), [&](const std::shared_ptr& buf) { return buf && buffer && buf->deviceAttachmentSpec && buffer->deviceAttachmentSpec && *(buf->deviceAttachmentSpec) == *(buffer->deviceAttachmentSpec); }); if (it != attachedStimulusBuffers.end()) { return false; } attachedStimulusBuffers.push_back(buffer); return true; } void StimulusProducer::destroyAttachedStimulusBuffer( const std::shared_ptr& buffer) { if (!buffer) { return; } auto it = std::find( attachedStimulusBuffers.begin(), attachedStimulusBuffers.end(), buffer); if (it != attachedStimulusBuffers.end()) { attachedStimulusBuffers.erase(it); } } sscl::co::NonViralNonPostingInvoker StimulusProducer::productionCDaemon( std::exception_ptr &, std::function, sscl::SyncCancelerForAsyncWork &canceler) { const long framePeriodMs = CONFIG_STIMBUFF_FRAME_PERIOD_MS; do { if (canceler.isCancellationRequested()) { break; } const auto timesliceStartStamp = std::chrono::high_resolution_clock::now(); const auto productionStartStamp = std::chrono::high_resolution_clock::now(); co_await stimFrameProductionTimesliceCInd(canceler); const auto productionEndStamp = std::chrono::high_resolution_clock::now(); const long productionDurationMs = durationMsSince( productionStartStamp, productionEndStamp); logProductionOverrunIfNeeded( "productionCDaemon", productionDurationMs, framePeriodMs, nTimesliceOverruns); const long residueMs = computeTimesliceResidueMs( productionDurationMs, framePeriodMs); // Schedule the next timeout based on timeslice remaining time. const bool expiredNormally = co_await adapters::boostAsio::getDeadlineTimerAReqAwaiter( ioContext, daemonTimer, boost::posix_time::milliseconds(residueMs)); if (!expiredNormally) { // Timer was cancelled, which is expected when stopping break; } const auto timesliceEndStamp = std::chrono::high_resolution_clock::now(); const long timesliceDurationMs = durationMsSince( timesliceStartStamp, timesliceEndStamp); logDaemonDurationsIfVerbose( "productionCDaemon", productionDurationMs, timesliceDurationMs, framePeriodMs); } while (!canceler.isCancellationRequested()); co_return; } void StimulusProducer::start() { std::cout << __func__ << ": Starting stimulus producer for device " << deviceAttachmentSpec->deviceSelector << std::endl; nTimesliceOverruns = 0; taskNursery.openAdmission(); taskNursery.launch( [this](sscl::co::NonViralTaskNursery::Slot::Lease &lease) { return productionCDaemon( lease.getExceptionStorage(), lease.getCallerLambda(), lease.getSyncCanceler()); }); } void StimulusProducer::stop() { // Cancel timer immediately daemonTimer.cancel(); taskNursery.requestCancelOnAll(); taskNursery.closeAdmission(); taskNursery.syncAwaitAllSettlements( sscl::ComponentThread::getSelf()->getIoContext()); std::cout << __func__ << ": Stopped stimulus producer for device " << deviceAttachmentSpec->deviceSelector << std::endl; } } // namespace stim_buff } // namespace smo