diff --git a/ACT/LibMC.xml b/ACT/LibMC.xml index 3a8ee1dd..edb14ca9 100644 --- a/ACT/LibMC.xml +++ b/ACT/LibMC.xml @@ -740,6 +740,7 @@ + diff --git a/ACT/LibMCData.xml b/ACT/LibMCData.xml index 922fbba6..dc5583b3 100644 --- a/ACT/LibMCData.xml +++ b/ACT/LibMCData.xml @@ -452,6 +452,7 @@ + @@ -2291,8 +2292,16 @@ + + + + + + + + - + diff --git a/Framework/HeadersCore/CppDynamic/libmc_dynamic.hpp b/Framework/HeadersCore/CppDynamic/libmc_dynamic.hpp index 1a480f93..360065b4 100644 --- a/Framework/HeadersCore/CppDynamic/libmc_dynamic.hpp +++ b/Framework/HeadersCore/CppDynamic/libmc_dynamic.hpp @@ -809,6 +809,7 @@ class ELibMCException : public std::exception { case LIBMC_ERROR_MISSINGSESSIONVARIABLENAME: return "MISSINGSESSIONVARIABLENAME"; case LIBMC_ERROR_MISSINGSESSIONVARIABLETYPE: return "MISSINGSESSIONVARIABLETYPE"; case LIBMC_ERROR_INVALIDSESSIONREFERENCE: return "INVALIDSESSIONREFERENCE"; + case LIBMC_ERROR_INVALIDTELEMETRYSETTING: return "INVALIDTELEMETRYSETTING"; } return "UNKNOWN"; } @@ -1452,6 +1453,7 @@ class ELibMCException : public std::exception { case LIBMC_ERROR_MISSINGSESSIONVARIABLENAME: return "Missing session variable name."; case LIBMC_ERROR_MISSINGSESSIONVARIABLETYPE: return "Missing session variable type."; case LIBMC_ERROR_INVALIDSESSIONREFERENCE: return "Invalid session reference in UI expression."; + case LIBMC_ERROR_INVALIDTELEMETRYSETTING: return "Invalid telemetry setting."; } return "unknown error"; } diff --git a/Framework/HeadersCore/CppDynamic/libmc_types.hpp b/Framework/HeadersCore/CppDynamic/libmc_types.hpp index a81eb9ca..de838335 100644 --- a/Framework/HeadersCore/CppDynamic/libmc_types.hpp +++ b/Framework/HeadersCore/CppDynamic/libmc_types.hpp @@ -731,6 +731,7 @@ typedef void * LibMC_pvoid; #define LIBMC_ERROR_MISSINGSESSIONVARIABLENAME 715 /** Missing session variable name. */ #define LIBMC_ERROR_MISSINGSESSIONVARIABLETYPE 716 /** Missing session variable type. */ #define LIBMC_ERROR_INVALIDSESSIONREFERENCE 717 /** Invalid session reference in UI expression. */ +#define LIBMC_ERROR_INVALIDTELEMETRYSETTING 718 /** Invalid telemetry setting. */ /************************************************************************************************************************* Error strings for LibMC @@ -1374,6 +1375,7 @@ inline const char * LIBMC_GETERRORSTRING (LibMCResult nErrorCode) { case LIBMC_ERROR_MISSINGSESSIONVARIABLENAME: return "Missing session variable name."; case LIBMC_ERROR_MISSINGSESSIONVARIABLETYPE: return "Missing session variable type."; case LIBMC_ERROR_INVALIDSESSIONREFERENCE: return "Invalid session reference in UI expression."; + case LIBMC_ERROR_INVALIDTELEMETRYSETTING: return "Invalid telemetry setting."; default: return "unknown error"; } } diff --git a/Framework/HeadersCore/CppDynamic/libmcdata_dynamic.h b/Framework/HeadersCore/CppDynamic/libmcdata_dynamic.h index 09ab4e9d..d8b18fdf 100644 --- a/Framework/HeadersCore/CppDynamic/libmcdata_dynamic.h +++ b/Framework/HeadersCore/CppDynamic/libmcdata_dynamic.h @@ -3420,6 +3420,24 @@ typedef LibMCDataResult (*PLibMCDataInstallationInformation_GetBaseTempDirectory */ typedef LibMCDataResult (*PLibMCDataDataModel_InitialiseDatabasePtr) (LibMCData_DataModel pDataModel, const char * pDataDirectory, LibMCData::eDataBaseType eDataBaseType, const char * pConnectionString); +/** +* enables or disables telemetry recording. Must be called before InitialiseDatabase. +* +* @param[in] pDataModel - DataModel instance. +* @param[in] bTelemetryEnabled - Flag if telemetry recording shall be enabled. +* @return error code or 0 (success) +*/ +typedef LibMCDataResult (*PLibMCDataDataModel_SetTelemetryEnabledPtr) (LibMCData_DataModel pDataModel, bool bTelemetryEnabled); + +/** +* returns if telemetry recording is enabled. +* +* @param[in] pDataModel - DataModel instance. +* @param[out] pTelemetryEnabled - Flag if telemetry recording is enabled. +* @return error code or 0 (success) +*/ +typedef LibMCDataResult (*PLibMCDataDataModel_GetTelemetryEnabledPtr) (LibMCData_DataModel pDataModel, bool * pTelemetryEnabled); + /** * returns the linear data model version. * @@ -4021,6 +4039,8 @@ typedef struct { PLibMCDataInstallationInformation_GetInstallationSecretPtr m_InstallationInformation_GetInstallationSecret; PLibMCDataInstallationInformation_GetBaseTempDirectoryPtr m_InstallationInformation_GetBaseTempDirectory; PLibMCDataDataModel_InitialiseDatabasePtr m_DataModel_InitialiseDatabase; + PLibMCDataDataModel_SetTelemetryEnabledPtr m_DataModel_SetTelemetryEnabled; + PLibMCDataDataModel_GetTelemetryEnabledPtr m_DataModel_GetTelemetryEnabled; PLibMCDataDataModel_GetDataModelVersionPtr m_DataModel_GetDataModelVersion; PLibMCDataDataModel_GetInstallationInformationPtr m_DataModel_GetInstallationInformation; PLibMCDataDataModel_GetInstallationInformationObjectPtr m_DataModel_GetInstallationInformationObject; diff --git a/Framework/HeadersCore/CppDynamic/libmcdata_dynamic.hpp b/Framework/HeadersCore/CppDynamic/libmcdata_dynamic.hpp index 8c70f9b0..3eb1bab7 100644 --- a/Framework/HeadersCore/CppDynamic/libmcdata_dynamic.hpp +++ b/Framework/HeadersCore/CppDynamic/libmcdata_dynamic.hpp @@ -690,6 +690,7 @@ class ELibMCDataException : public std::exception { case LIBMCDATA_ERROR_TELEMETRYCHANNELNOTFOUND: return "TELEMETRYCHANNELNOTFOUND"; case LIBMCDATA_ERROR_JOBNAMETOOLONG: return "JOBNAMETOOLONG"; case LIBMCDATA_ERROR_STRINGPARAMETERTOOLONG: return "STRINGPARAMETERTOOLONG"; + case LIBMCDATA_ERROR_TELEMETRYSETTINGAFTERINITIALISATION: return "TELEMETRYSETTINGAFTERINITIALISATION"; } return "UNKNOWN"; } @@ -1079,6 +1080,7 @@ class ELibMCDataException : public std::exception { case LIBMCDATA_ERROR_TELEMETRYCHANNELNOTFOUND: return "Telemetry channel not found."; case LIBMCDATA_ERROR_JOBNAMETOOLONG: return "Job name exceeds maximum allowed length"; case LIBMCDATA_ERROR_STRINGPARAMETERTOOLONG: return "String parameter exceeds maximum allowed length"; + case LIBMCDATA_ERROR_TELEMETRYSETTINGAFTERINITIALISATION: return "Telemetry setting changed after database initialisation"; } return "unknown error"; } @@ -2197,6 +2199,8 @@ class CDataModel : public CBase { } inline void InitialiseDatabase(const std::string & sDataDirectory, const eDataBaseType eDataBaseType, const std::string & sConnectionString); + inline void SetTelemetryEnabled(const bool bTelemetryEnabled); + inline bool GetTelemetryEnabled(); inline LibMCData_uint32 GetDataModelVersion(); inline void GetInstallationInformation(std::string & sDEPRECIATEDInstallationUUID, std::string & sDEPRECIATEDInstallationSecret); inline PInstallationInformation GetInstallationInformationObject(); @@ -2616,6 +2620,8 @@ class CDataModel : public CBase { pWrapperTable->m_InstallationInformation_GetInstallationSecret = nullptr; pWrapperTable->m_InstallationInformation_GetBaseTempDirectory = nullptr; pWrapperTable->m_DataModel_InitialiseDatabase = nullptr; + pWrapperTable->m_DataModel_SetTelemetryEnabled = nullptr; + pWrapperTable->m_DataModel_GetTelemetryEnabled = nullptr; pWrapperTable->m_DataModel_GetDataModelVersion = nullptr; pWrapperTable->m_DataModel_GetInstallationInformation = nullptr; pWrapperTable->m_DataModel_GetInstallationInformationObject = nullptr; @@ -5361,6 +5367,24 @@ class CDataModel : public CBase { if (pWrapperTable->m_DataModel_InitialiseDatabase == nullptr) return LIBMCDATA_ERROR_COULDNOTFINDLIBRARYEXPORT; + #ifdef _WIN32 + pWrapperTable->m_DataModel_SetTelemetryEnabled = (PLibMCDataDataModel_SetTelemetryEnabledPtr) GetProcAddress(hLibrary, "libmcdata_datamodel_settelemetryenabled"); + #else // _WIN32 + pWrapperTable->m_DataModel_SetTelemetryEnabled = (PLibMCDataDataModel_SetTelemetryEnabledPtr) dlsym(hLibrary, "libmcdata_datamodel_settelemetryenabled"); + dlerror(); + #endif // _WIN32 + if (pWrapperTable->m_DataModel_SetTelemetryEnabled == nullptr) + return LIBMCDATA_ERROR_COULDNOTFINDLIBRARYEXPORT; + + #ifdef _WIN32 + pWrapperTable->m_DataModel_GetTelemetryEnabled = (PLibMCDataDataModel_GetTelemetryEnabledPtr) GetProcAddress(hLibrary, "libmcdata_datamodel_gettelemetryenabled"); + #else // _WIN32 + pWrapperTable->m_DataModel_GetTelemetryEnabled = (PLibMCDataDataModel_GetTelemetryEnabledPtr) dlsym(hLibrary, "libmcdata_datamodel_gettelemetryenabled"); + dlerror(); + #endif // _WIN32 + if (pWrapperTable->m_DataModel_GetTelemetryEnabled == nullptr) + return LIBMCDATA_ERROR_COULDNOTFINDLIBRARYEXPORT; + #ifdef _WIN32 pWrapperTable->m_DataModel_GetDataModelVersion = (PLibMCDataDataModel_GetDataModelVersionPtr) GetProcAddress(hLibrary, "libmcdata_datamodel_getdatamodelversion"); #else // _WIN32 @@ -6840,6 +6864,14 @@ class CDataModel : public CBase { if ( (eLookupError != 0) || (pWrapperTable->m_DataModel_InitialiseDatabase == nullptr) ) return LIBMCDATA_ERROR_COULDNOTFINDLIBRARYEXPORT; + eLookupError = (*pLookup)("libmcdata_datamodel_settelemetryenabled", (void**)&(pWrapperTable->m_DataModel_SetTelemetryEnabled)); + if ( (eLookupError != 0) || (pWrapperTable->m_DataModel_SetTelemetryEnabled == nullptr) ) + return LIBMCDATA_ERROR_COULDNOTFINDLIBRARYEXPORT; + + eLookupError = (*pLookup)("libmcdata_datamodel_gettelemetryenabled", (void**)&(pWrapperTable->m_DataModel_GetTelemetryEnabled)); + if ( (eLookupError != 0) || (pWrapperTable->m_DataModel_GetTelemetryEnabled == nullptr) ) + return LIBMCDATA_ERROR_COULDNOTFINDLIBRARYEXPORT; + eLookupError = (*pLookup)("libmcdata_datamodel_getdatamodelversion", (void**)&(pWrapperTable->m_DataModel_GetDataModelVersion)); if ( (eLookupError != 0) || (pWrapperTable->m_DataModel_GetDataModelVersion == nullptr) ) return LIBMCDATA_ERROR_COULDNOTFINDLIBRARYEXPORT; @@ -11348,6 +11380,27 @@ class CDataModel : public CBase { CheckError(m_pWrapper->m_WrapperTable.m_DataModel_InitialiseDatabase(m_pHandle, sDataDirectory.c_str(), eDataBaseType, sConnectionString.c_str())); } + /** + * CDataModel::SetTelemetryEnabled - enables or disables telemetry recording. Must be called before InitialiseDatabase. + * @param[in] bTelemetryEnabled - Flag if telemetry recording shall be enabled. + */ + void CDataModel::SetTelemetryEnabled(const bool bTelemetryEnabled) + { + CheckError(m_pWrapper->m_WrapperTable.m_DataModel_SetTelemetryEnabled(m_pHandle, bTelemetryEnabled)); + } + + /** + * CDataModel::GetTelemetryEnabled - returns if telemetry recording is enabled. + * @return Flag if telemetry recording is enabled. + */ + bool CDataModel::GetTelemetryEnabled() + { + bool resultTelemetryEnabled = 0; + CheckError(m_pWrapper->m_WrapperTable.m_DataModel_GetTelemetryEnabled(m_pHandle, &resultTelemetryEnabled)); + + return resultTelemetryEnabled; + } + /** * CDataModel::GetDataModelVersion - returns the linear data model version. * @return Data model version. diff --git a/Framework/HeadersCore/CppDynamic/libmcdata_types.hpp b/Framework/HeadersCore/CppDynamic/libmcdata_types.hpp index 19bdf40c..a2d8e0c6 100644 --- a/Framework/HeadersCore/CppDynamic/libmcdata_types.hpp +++ b/Framework/HeadersCore/CppDynamic/libmcdata_types.hpp @@ -477,6 +477,7 @@ typedef void * LibMCData_pvoid; #define LIBMCDATA_ERROR_TELEMETRYCHANNELNOTFOUND 450 /** Telemetry channel not found. */ #define LIBMCDATA_ERROR_JOBNAMETOOLONG 451 /** Job name exceeds maximum allowed length */ #define LIBMCDATA_ERROR_STRINGPARAMETERTOOLONG 452 /** String parameter exceeds maximum allowed length */ +#define LIBMCDATA_ERROR_TELEMETRYSETTINGAFTERINITIALISATION 453 /** Telemetry setting changed after database initialisation */ /************************************************************************************************************************* Error strings for LibMCData @@ -866,6 +867,7 @@ inline const char * LIBMCDATA_GETERRORSTRING (LibMCDataResult nErrorCode) { case LIBMCDATA_ERROR_TELEMETRYCHANNELNOTFOUND: return "Telemetry channel not found."; case LIBMCDATA_ERROR_JOBNAMETOOLONG: return "Job name exceeds maximum allowed length"; case LIBMCDATA_ERROR_STRINGPARAMETERTOOLONG: return "String parameter exceeds maximum allowed length"; + case LIBMCDATA_ERROR_TELEMETRYSETTINGAFTERINITIALISATION: return "Telemetry setting changed after database initialisation"; default: return "unknown error"; } } diff --git a/Framework/InterfacesCore/libmc_types.hpp b/Framework/InterfacesCore/libmc_types.hpp index a81eb9ca..de838335 100644 --- a/Framework/InterfacesCore/libmc_types.hpp +++ b/Framework/InterfacesCore/libmc_types.hpp @@ -731,6 +731,7 @@ typedef void * LibMC_pvoid; #define LIBMC_ERROR_MISSINGSESSIONVARIABLENAME 715 /** Missing session variable name. */ #define LIBMC_ERROR_MISSINGSESSIONVARIABLETYPE 716 /** Missing session variable type. */ #define LIBMC_ERROR_INVALIDSESSIONREFERENCE 717 /** Invalid session reference in UI expression. */ +#define LIBMC_ERROR_INVALIDTELEMETRYSETTING 718 /** Invalid telemetry setting. */ /************************************************************************************************************************* Error strings for LibMC @@ -1374,6 +1375,7 @@ inline const char * LIBMC_GETERRORSTRING (LibMCResult nErrorCode) { case LIBMC_ERROR_MISSINGSESSIONVARIABLENAME: return "Missing session variable name."; case LIBMC_ERROR_MISSINGSESSIONVARIABLETYPE: return "Missing session variable type."; case LIBMC_ERROR_INVALIDSESSIONREFERENCE: return "Invalid session reference in UI expression."; + case LIBMC_ERROR_INVALIDTELEMETRYSETTING: return "Invalid telemetry setting."; default: return "unknown error"; } } diff --git a/Framework/InterfacesCore/libmcdata_abi.hpp b/Framework/InterfacesCore/libmcdata_abi.hpp index 5c81d31f..98e1f41e 100644 --- a/Framework/InterfacesCore/libmcdata_abi.hpp +++ b/Framework/InterfacesCore/libmcdata_abi.hpp @@ -3433,6 +3433,24 @@ LIBMCDATA_DECLSPEC LibMCDataResult libmcdata_installationinformation_getbasetemp */ LIBMCDATA_DECLSPEC LibMCDataResult libmcdata_datamodel_initialisedatabase(LibMCData_DataModel pDataModel, const char * pDataDirectory, LibMCData::eDataBaseType eDataBaseType, const char * pConnectionString); +/** +* enables or disables telemetry recording. Must be called before InitialiseDatabase. +* +* @param[in] pDataModel - DataModel instance. +* @param[in] bTelemetryEnabled - Flag if telemetry recording shall be enabled. +* @return error code or 0 (success) +*/ +LIBMCDATA_DECLSPEC LibMCDataResult libmcdata_datamodel_settelemetryenabled(LibMCData_DataModel pDataModel, bool bTelemetryEnabled); + +/** +* returns if telemetry recording is enabled. +* +* @param[in] pDataModel - DataModel instance. +* @param[out] pTelemetryEnabled - Flag if telemetry recording is enabled. +* @return error code or 0 (success) +*/ +LIBMCDATA_DECLSPEC LibMCDataResult libmcdata_datamodel_gettelemetryenabled(LibMCData_DataModel pDataModel, bool * pTelemetryEnabled); + /** * returns the linear data model version. * diff --git a/Framework/InterfacesCore/libmcdata_interfaces.hpp b/Framework/InterfacesCore/libmcdata_interfaces.hpp index 0b7cdcb0..5d52dca6 100644 --- a/Framework/InterfacesCore/libmcdata_interfaces.hpp +++ b/Framework/InterfacesCore/libmcdata_interfaces.hpp @@ -2854,6 +2854,18 @@ class IDataModel : public virtual IBase { */ virtual void InitialiseDatabase(const std::string & sDataDirectory, const LibMCData::eDataBaseType eDataBaseType, const std::string & sConnectionString) = 0; + /** + * IDataModel::SetTelemetryEnabled - enables or disables telemetry recording. Must be called before InitialiseDatabase. + * @param[in] bTelemetryEnabled - Flag if telemetry recording shall be enabled. + */ + virtual void SetTelemetryEnabled(const bool bTelemetryEnabled) = 0; + + /** + * IDataModel::GetTelemetryEnabled - returns if telemetry recording is enabled. + * @return Flag if telemetry recording is enabled. + */ + virtual bool GetTelemetryEnabled() = 0; + /** * IDataModel::GetDataModelVersion - returns the linear data model version. * @return Data model version. diff --git a/Framework/InterfacesCore/libmcdata_interfacewrapper.cpp b/Framework/InterfacesCore/libmcdata_interfacewrapper.cpp index 59a379cb..92ad92f6 100644 --- a/Framework/InterfacesCore/libmcdata_interfacewrapper.cpp +++ b/Framework/InterfacesCore/libmcdata_interfacewrapper.cpp @@ -10938,6 +10938,56 @@ LibMCDataResult libmcdata_datamodel_initialisedatabase(LibMCData_DataModel pData } } +LibMCDataResult libmcdata_datamodel_settelemetryenabled(LibMCData_DataModel pDataModel, bool bTelemetryEnabled) +{ + IBase* pIBaseClass = (IBase *)pDataModel; + + try { + IDataModel* pIDataModel = dynamic_cast(pIBaseClass); + if (!pIDataModel) + throw ELibMCDataInterfaceException(LIBMCDATA_ERROR_INVALIDCAST); + + pIDataModel->SetTelemetryEnabled(bTelemetryEnabled); + + return LIBMCDATA_SUCCESS; + } + catch (ELibMCDataInterfaceException & Exception) { + return handleLibMCDataException(pIBaseClass, Exception); + } + catch (std::exception & StdException) { + return handleStdException(pIBaseClass, StdException); + } + catch (...) { + return handleUnhandledException(pIBaseClass); + } +} + +LibMCDataResult libmcdata_datamodel_gettelemetryenabled(LibMCData_DataModel pDataModel, bool * pTelemetryEnabled) +{ + IBase* pIBaseClass = (IBase *)pDataModel; + + try { + if (pTelemetryEnabled == nullptr) + throw ELibMCDataInterfaceException (LIBMCDATA_ERROR_INVALIDPARAM); + IDataModel* pIDataModel = dynamic_cast(pIBaseClass); + if (!pIDataModel) + throw ELibMCDataInterfaceException(LIBMCDATA_ERROR_INVALIDCAST); + + *pTelemetryEnabled = pIDataModel->GetTelemetryEnabled(); + + return LIBMCDATA_SUCCESS; + } + catch (ELibMCDataInterfaceException & Exception) { + return handleLibMCDataException(pIBaseClass, Exception); + } + catch (std::exception & StdException) { + return handleStdException(pIBaseClass, StdException); + } + catch (...) { + return handleUnhandledException(pIBaseClass); + } +} + LibMCDataResult libmcdata_datamodel_getdatamodelversion(LibMCData_DataModel pDataModel, LibMCData_uint32 * pVersion) { IBase* pIBaseClass = (IBase *)pDataModel; @@ -12307,6 +12357,10 @@ LibMCDataResult LibMCData::Impl::LibMCData_GetProcAddress (const char * pProcNam *ppProcAddress = (void*) &libmcdata_installationinformation_getbasetempdirectory; if (sProcName == "libmcdata_datamodel_initialisedatabase") *ppProcAddress = (void*) &libmcdata_datamodel_initialisedatabase; + if (sProcName == "libmcdata_datamodel_settelemetryenabled") + *ppProcAddress = (void*) &libmcdata_datamodel_settelemetryenabled; + if (sProcName == "libmcdata_datamodel_gettelemetryenabled") + *ppProcAddress = (void*) &libmcdata_datamodel_gettelemetryenabled; if (sProcName == "libmcdata_datamodel_getdatamodelversion") *ppProcAddress = (void*) &libmcdata_datamodel_getdatamodelversion; if (sProcName == "libmcdata_datamodel_getinstallationinformation") diff --git a/Framework/InterfacesCore/libmcdata_types.hpp b/Framework/InterfacesCore/libmcdata_types.hpp index 19bdf40c..a2d8e0c6 100644 --- a/Framework/InterfacesCore/libmcdata_types.hpp +++ b/Framework/InterfacesCore/libmcdata_types.hpp @@ -477,6 +477,7 @@ typedef void * LibMCData_pvoid; #define LIBMCDATA_ERROR_TELEMETRYCHANNELNOTFOUND 450 /** Telemetry channel not found. */ #define LIBMCDATA_ERROR_JOBNAMETOOLONG 451 /** Job name exceeds maximum allowed length */ #define LIBMCDATA_ERROR_STRINGPARAMETERTOOLONG 452 /** String parameter exceeds maximum allowed length */ +#define LIBMCDATA_ERROR_TELEMETRYSETTINGAFTERINITIALISATION 453 /** Telemetry setting changed after database initialisation */ /************************************************************************************************************************* Error strings for LibMCData @@ -866,6 +867,7 @@ inline const char * LIBMCDATA_GETERRORSTRING (LibMCDataResult nErrorCode) { case LIBMCDATA_ERROR_TELEMETRYCHANNELNOTFOUND: return "Telemetry channel not found."; case LIBMCDATA_ERROR_JOBNAMETOOLONG: return "Job name exceeds maximum allowed length"; case LIBMCDATA_ERROR_STRINGPARAMETERTOOLONG: return "String parameter exceeds maximum allowed length"; + case LIBMCDATA_ERROR_TELEMETRYSETTINGAFTERINITIALISATION: return "Telemetry setting changed after database initialisation"; default: return "unknown error"; } } diff --git a/Implementation/Core/amc_systemstate.cpp b/Implementation/Core/amc_systemstate.cpp index 987d9d61..0c14a539 100644 --- a/Implementation/Core/amc_systemstate.cpp +++ b/Implementation/Core/amc_systemstate.cpp @@ -93,7 +93,7 @@ namespace AMC { m_pAccessControl = std::make_shared (); - auto pTelemetryWriter = std::make_shared (m_pDataModel->CreateTelemetrySession (), m_pGlobalChrono); + auto pTelemetryWriter = std::make_shared (m_pDataModel->CreateTelemetrySession (), m_pGlobalChrono, m_pDataModel->GetTelemetryEnabled ()); m_pTelemetryHandler = std::make_shared (pTelemetryWriter); m_pMeshHandler = std::make_shared(); diff --git a/Implementation/Core/amc_telemetry.cpp b/Implementation/Core/amc_telemetry.cpp index 183cbc5e..bc84c5a2 100644 --- a/Implementation/Core/amc_telemetry.cpp +++ b/Implementation/Core/amc_telemetry.cpp @@ -176,16 +176,18 @@ namespace AMC { - CTelemetryWriter::CTelemetryWriter(LibMCData::PTelemetrySession pTelemetrySession, AMCCommon::PChrono pGlobalChrono) + CTelemetryWriter::CTelemetryWriter(LibMCData::PTelemetrySession pTelemetrySession, AMCCommon::PChrono pGlobalChrono, bool bTelemetryEnabled) : m_pTelemetrySession(pTelemetrySession), m_nNextMarkerID(1), m_pGlobalChrono (pGlobalChrono), - m_nChunkIntervalInMicroseconds (TELEMETRY_DEFAULT_CHUNKINTERVAL_MICROSECONDS) + m_nChunkIntervalInMicroseconds (TELEMETRY_DEFAULT_CHUNKINTERVAL_MICROSECONDS), + m_bTelemetryEnabled (bTelemetryEnabled) { if (pGlobalChrono.get () == nullptr) throw ELibMCInterfaceException(LIBMC_ERROR_INVALIDPARAM); if (pTelemetrySession.get() == nullptr) throw ELibMCInterfaceException(LIBMC_ERROR_INVALIDPARAM); - extendChunksUntil(1); + if (m_bTelemetryEnabled) + extendChunksUntil(1); } CTelemetryWriter::~CTelemetryWriter() @@ -247,6 +249,22 @@ namespace AMC { void CTelemetryWriter::writeEntry(const LibMCData::sTelemetryChunkEntry& entry) { + // Telemetry is dropped HERE, at the writer, and deliberately not earlier. + // + // Do NOT "optimise" this by short-circuiting marker creation (for example with a no-op + // CTelemetryMarker). CStateSignalSlot::updateTimingStatistics feeds the live + // reactiontime_ / finishtime_ / acknowledgetime_ parameters of every signal from + // CTelemetryChannel::getMaxDurationInMicroseconds, which is updated by unregisterMarker + // in CTelemetryMarker::finishMarker - independently of this writer. Killing markers + // would silently zero those three parameters on every device. + // + // Dropping at this level is also symmetric: registerOpenInterval and eraseOpenInterval are + // both called from CTelemetryDataChunk::writeEntry, so neither runs and no open-interval + // state is orphaned. With no chunk ever allocated, archiveOldChunksToDB no-ops and + // CJournal::writeTelemetryChunk is never reached. + if (!m_bTelemetryEnabled) + return; + auto pChunk = getOrCreateChunkByTimestamp(entry.m_TimeStamp); pChunk->writeEntry(entry); diff --git a/Implementation/Core/amc_telemetry.hpp b/Implementation/Core/amc_telemetry.hpp index 930d2714..661d362a 100644 --- a/Implementation/Core/amc_telemetry.hpp +++ b/Implementation/Core/amc_telemetry.hpp @@ -127,11 +127,15 @@ namespace AMC { std::atomic m_nNextMarkerID; + // If false, writeEntry drops every entry, so no chunk is ever allocated and nothing is + // archived to disk. Markers themselves are still created - see writeEntry. + bool m_bTelemetryEnabled; + void extendChunksUntil(uint64_t nMaxChunkIndexOneBased); public: - CTelemetryWriter(LibMCData::PTelemetrySession pTelemetrySession, AMCCommon::PChrono pGlobalChrono); + CTelemetryWriter(LibMCData::PTelemetrySession pTelemetrySession, AMCCommon::PChrono pGlobalChrono, bool bTelemetryEnabled = true); virtual ~CTelemetryWriter(); diff --git a/Implementation/DataModel/amcdata_journal.cpp b/Implementation/DataModel/amcdata_journal.cpp index 6a7112bf..ad32fc4f 100644 --- a/Implementation/DataModel/amcdata_journal.cpp +++ b/Implementation/DataModel/amcdata_journal.cpp @@ -151,9 +151,10 @@ namespace AMCData { return m_nTotalSize; } - CJournal::CJournal(const std::string& sJournalBasePath, const std::string& sJournalName, const std::string& sJournalChunkBaseName, const std::string& sTelemetryChunkBaseName, const std::string& sSessionUUID) + CJournal::CJournal(const std::string& sJournalBasePath, const std::string& sJournalName, const std::string& sJournalChunkBaseName, const std::string& sTelemetryChunkBaseName, const std::string& sSessionUUID, bool bTelemetryEnabled) : m_LogID(1), m_AlertID(1), m_sSessionUUID(AMCCommon::CUtils::normalizeUUIDString(sSessionUUID)), - m_sJournalBasePath(sJournalBasePath), m_sChunkBaseName (sJournalChunkBaseName), m_sTelemetryChunkBaseName(sTelemetryChunkBaseName) + m_sJournalBasePath(sJournalBasePath), m_sChunkBaseName (sJournalChunkBaseName), m_sTelemetryChunkBaseName(sTelemetryChunkBaseName), + m_bTelemetryEnabled (bTelemetryEnabled) { if (sJournalChunkBaseName.empty()) throw ELibMCDataInterfaceException(LIBMCDATA_ERROR_INVALIDPARAM, "Journal chunk base name is empty"); @@ -296,7 +297,12 @@ namespace AMCData { pSessionMetricsStatement = nullptr; m_pCurrentJournalFile = createJournalFile(); - m_pCurrentTelemetryFile = createTelemetryFile(); + + // With telemetry disabled, no data file is created at all. The telemetry_* tables above are + // still created, so the schema stays uniform and a reader can enumerate the registered + // channels of an "off" session. + if (m_bTelemetryEnabled) + m_pCurrentTelemetryFile = createTelemetryFile(); } @@ -1052,6 +1058,14 @@ namespace AMCData { if (nStartTimeStamp > nEndTimeStamp) throw ELibMCDataInterfaceException(LIBMCDATA_ERROR_INVALIDPARAM); + // With telemetry disabled there is no data file to write to. libmc drops entries before they + // ever become chunks, so this should be unreachable, but the two libraries are versioned + // separately and a flag that reaches one and not the other must not be a null dereference. + if (!m_bTelemetryEnabled) + return; + if (m_pCurrentTelemetryFile.get() == nullptr) + return; + if (m_pCurrentTelemetryFile->getTotalSize() > getMaxChunkFileSizeQuotaInBytes()) m_pCurrentTelemetryFile = createTelemetryFile(); diff --git a/Implementation/DataModel/amcdata_journal.hpp b/Implementation/DataModel/amcdata_journal.hpp index 3fef99e0..5d81255d 100644 --- a/Implementation/DataModel/amcdata_journal.hpp +++ b/Implementation/DataModel/amcdata_journal.hpp @@ -103,6 +103,10 @@ namespace AMCData { PActiveJournalFile m_pCurrentTelemetryFile; + // If false, no telemetry data file is created and no telemetry entry is ever written. + // This is an explicit flag on purpose: an empty m_sTelemetryChunkBaseName stays an error. + bool m_bTelemetryEnabled; + PActiveJournalFile createTelemetryFile(); public: @@ -111,7 +115,7 @@ namespace AMCData { static LibMCData::eAlertLevel convertStringToAlertLevel(const std::string & sValue, bool bFailIfUnknown); - CJournal(const std::string& sJournalBasePath, const std::string& sJournalName, const std::string& sJournalChunkBaseName, const std::string& sTelemetryChunkBaseName, const std::string & sSessionUUID); + CJournal(const std::string& sJournalBasePath, const std::string& sJournalName, const std::string& sJournalChunkBaseName, const std::string& sTelemetryChunkBaseName, const std::string & sSessionUUID, bool bTelemetryEnabled = true); virtual ~CJournal(); diff --git a/Implementation/LibMCData/libmcdata_datamodel.cpp b/Implementation/LibMCData/libmcdata_datamodel.cpp index 6c925636..bca018e7 100644 --- a/Implementation/LibMCData/libmcdata_datamodel.cpp +++ b/Implementation/LibMCData/libmcdata_datamodel.cpp @@ -75,7 +75,7 @@ using namespace LibMCData::Impl; **************************************************************************************************************************/ CDataModel::CDataModel() - : m_eDataBaseType(eDataBaseType::Unknown), m_pLogCallback (nullptr), m_pLogUserData (nullptr) + : m_eDataBaseType(eDataBaseType::Unknown), m_pLogCallback (nullptr), m_pLogUserData (nullptr), m_bTelemetryEnabled (true) { m_sSessionUUID = AMCCommon::CUtils::createUUID(); @@ -153,7 +153,7 @@ void CDataModel::InitialiseDatabase(const std::string & sDataDirectory, const Li auto sJournalChunkBaseName = m_pStorageState->getJournalChunkBaseName(m_sTimeFileName); auto sTelemetryChunkBaseName = m_pStorageState->getTelemetryChunkBaseName(m_sTimeFileName);; - m_pJournal = std::make_shared (sJournalBasePath, sJournalName, sJournalChunkBaseName, sTelemetryChunkBaseName, m_sSessionUUID); + m_pJournal = std::make_shared (sJournalBasePath, sJournalName, sJournalChunkBaseName, sTelemetryChunkBaseName, m_sSessionUUID, m_bTelemetryEnabled); auto pStatement = m_pSQLHandler->prepareStatement("INSERT INTO journals (uuid, starttime, logfilename, journalfilename, telemetryfilename, logfilepath, journalfilepath, schemaversion, githash) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"); pStatement->setString(1, m_sSessionUUID); @@ -169,6 +169,22 @@ void CDataModel::InitialiseDatabase(const std::string & sDataDirectory, const Li } +void CDataModel::SetTelemetryEnabled(const bool bTelemetryEnabled) +{ + // The flag is latched by InitialiseDatabase, which is where the telemetry data file is + // created. Changing it afterwards cannot un-create that file, so fail loudly instead of + // silently ignoring the call. + if (m_pJournal.get() != nullptr) + throw ELibMCDataInterfaceException(LIBMCDATA_ERROR_TELEMETRYSETTINGAFTERINITIALISATION); + + m_bTelemetryEnabled = bTelemetryEnabled; +} + +bool CDataModel::GetTelemetryEnabled() +{ + return m_bTelemetryEnabled; +} + LibMCData_uint32 CDataModel::GetDataModelVersion() { return AMCData::CDatabaseMigrator::getCurrentSchemaVersion (); diff --git a/Implementation/LibMCData/libmcdata_datamodel.hpp b/Implementation/LibMCData/libmcdata_datamodel.hpp index 0f38fa08..f74b54b2 100644 --- a/Implementation/LibMCData/libmcdata_datamodel.hpp +++ b/Implementation/LibMCData/libmcdata_datamodel.hpp @@ -76,6 +76,10 @@ class CDataModel : public virtual IDataModel, public virtual CBase { LibMCData::LogCallback m_pLogCallback; LibMCData_pvoid m_pLogUserData; + // Telemetry recording flag. Defaults to true, so that a caller that never calls + // SetTelemetryEnabled gets stock behaviour. Latched in InitialiseDatabase. + bool m_bTelemetryEnabled; + public: CDataModel(); @@ -84,6 +88,10 @@ class CDataModel : public virtual IDataModel, public virtual CBase { void InitialiseDatabase(const std::string & sDataDirectory, const LibMCData::eDataBaseType dataBaseType, const std::string & sConnectionString) override; + void SetTelemetryEnabled(const bool bTelemetryEnabled) override; + + bool GetTelemetryEnabled() override; + LibMCData_uint32 GetDataModelVersion() override; // DEPRECIATED! DO NOT USE. Use GetInstallationInformationObject instead! diff --git a/Implementation/Server/amc_server.cpp b/Implementation/Server/amc_server.cpp index 96d3fa3f..167abaaa 100644 --- a/Implementation/Server/amc_server.cpp +++ b/Implementation/Server/amc_server.cpp @@ -257,6 +257,11 @@ void CServer::executeBlocking(const std::string& sConfigurationFileName) m_pDataModel->SetBaseTempDirectory(m_pServerConfiguration->getBaseTempDirectory ()); + // MUST be set before InitialiseDatabase, which is where the telemetry data file is created. + if (!m_pServerConfiguration->getTelemetryEnabled()) + log("Telemetry recording is disabled."); + m_pDataModel->SetTelemetryEnabled(m_pServerConfiguration->getTelemetryEnabled()); + log("Initialising Database..."); m_pDataModel->InitialiseDatabase(m_pServerConfiguration->getDataDirectory(), m_pServerConfiguration->getDataBaseType(), m_pServerConfiguration->getConnectionString()); diff --git a/Implementation/Server/amc_server_configuration.cpp b/Implementation/Server/amc_server_configuration.cpp index df47579c..10daf809 100644 --- a/Implementation/Server/amc_server_configuration.cpp +++ b/Implementation/Server/amc_server_configuration.cpp @@ -97,7 +97,7 @@ std::string CServerLibrary::getResourcePath() } CServerConfiguration::CServerConfiguration(const std::string& configurationXMLString, PServerIO pServerIO) - : m_nPort(0), m_DataBaseType(LibMCData::eDataBaseType::Unknown), m_bUseSSL (false) + : m_nPort(0), m_DataBaseType(LibMCData::eDataBaseType::Unknown), m_bUseSSL (false), m_bTelemetryEnabled (true) { if (pServerIO.get() == nullptr) @@ -212,6 +212,21 @@ CServerConfiguration::CServerConfiguration(const std::string& configurationXMLSt throw LibMC::ELibMCException(LIBMC_ERROR_INVALIDDATABASETYPE, "Invalid database type: " + sDataBaseType); } + // Optional telemetry off-switch. Absent means "on", which is stock behaviour. + auto telemetryAttrib = dataNode.attribute("telemetry"); + if (!telemetryAttrib.empty()) { + std::string sTelemetrySetting = telemetryAttrib.as_string(); + if (sTelemetrySetting == "on") { + m_bTelemetryEnabled = true; + } + else if (sTelemetrySetting == "off") { + m_bTelemetryEnabled = false; + } + else { + throw LibMC::ELibMCException(LIBMC_ERROR_INVALIDTELEMETRYSETTING, "Invalid telemetry setting: " + sTelemetrySetting + " (must be \"on\" or \"off\")"); + } + } + auto defaultPackageNode = amcNode.child("defaultpackage"); if (defaultPackageNode.empty ()) throw LibMC::ELibMCException(LIBMC_ERROR_DEFAULTPACKAGEMISSING, "Default package missing"); @@ -255,6 +270,11 @@ std::string CServerConfiguration::getConnectionString() return m_sConnectionString; } +bool CServerConfiguration::getTelemetryEnabled() +{ + return m_bTelemetryEnabled; +} + std::string CServerConfiguration::getPackageName() { return m_sPackageName; diff --git a/Implementation/Server/amc_server_configuration.hpp b/Implementation/Server/amc_server_configuration.hpp index a891266c..642e9588 100644 --- a/Implementation/Server/amc_server_configuration.hpp +++ b/Implementation/Server/amc_server_configuration.hpp @@ -67,6 +67,7 @@ namespace AMC { std::string m_sDataDirectory; LibMCData::eDataBaseType m_DataBaseType; std::string m_sConnectionString; + bool m_bTelemetryEnabled; std::string m_sPackageName; std::string m_sPackageCoreClient; @@ -92,6 +93,7 @@ namespace AMC { std::string getDataDirectory (); LibMCData::eDataBaseType getDataBaseType (); std::string getConnectionString (); + bool getTelemetryEnabled (); std::string getPackageName (); std::string getPackageCoreClient (); std::string getPackageAPIDocs (); diff --git a/Implementation/UnitTest/amc_unittests_signalslot.hpp b/Implementation/UnitTest/amc_unittests_signalslot.hpp index 1142b28a..48ea7c1c 100644 --- a/Implementation/UnitTest/amc_unittests_signalslot.hpp +++ b/Implementation/UnitTest/amc_unittests_signalslot.hpp @@ -47,6 +47,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "common_utils.hpp" #include "common_chrono.hpp" #include "amc_telemetry.hpp" +#include "amc_parametergroup.hpp" #include "libmcdata_dynamic.hpp" #include "amc_unittests_libmcdata.hpp" @@ -57,9 +58,9 @@ namespace AMCUnitTest { public: - CDummyRegistry() + CDummyRegistry(bool bTelemetryEnabled = true) { - initializeTelemetry(); + initializeTelemetry(bTelemetryEnabled); } void registerMessage(const std::string& sMessageUUID, AMC::CStateSignalSlot* pSignalSlot) override @@ -82,8 +83,13 @@ namespace AMCUnitTest { return m_pTelemetryHandler->registerChannel(sChannelIdentifier, sChannelDescription, channelType); } + AMCCommon::PChrono getChrono() + { + return m_pChrono; + } + private: - void initializeTelemetry() + void initializeTelemetry(bool bTelemetryEnabled) { std::string sRootPath = "temp"; if (!AMCCommon::CUtils::fileOrPathExistsOnDisk(sRootPath)) @@ -97,11 +103,12 @@ namespace AMCUnitTest { m_pDataWrapper = AMCUnitTest::loadLibMCDataInProcess(); m_pDataModel = m_pDataWrapper->CreateDataModelInstance(); + m_pDataModel->SetTelemetryEnabled(bTelemetryEnabled); m_pDataModel->InitialiseDatabase(sBasePath, LibMCData::eDataBaseType::SqLite, sDatabaseFile); m_pTelemetrySession = m_pDataModel->CreateTelemetrySession(); m_pChrono = std::make_shared(); - m_pTelemetryWriter = std::make_shared(m_pTelemetrySession, m_pChrono); + m_pTelemetryWriter = std::make_shared(m_pTelemetrySession, m_pChrono, bTelemetryEnabled); m_pTelemetryHandler = std::make_shared(m_pTelemetryWriter); } @@ -137,10 +144,60 @@ class CUnitTestGroup_SignalSlot : public CUnitTestGroup { registerTest("ParameterResultAccess", "Tests getting parameter and result JSON", eUnitTestCategory::utMandatoryPass, std::bind(&CUnitTestGroup_SignalSlot::test_ParameterResultAccess, this)); registerTest("ClearQueueWorks", "Clears the queue and marks signals as cleared", eUnitTestCategory::utMandatoryPass, std::bind(&CUnitTestGroup_SignalSlot::test_ClearQueueWorks, this)); registerTest("TimeoutAndOverflowTest", "Simulates queue overflow and timeout scenarios", eUnitTestCategory::utOptionalPass, std::bind(&CUnitTestGroup_SignalSlot::test_TimeoutAndOverflowTest, this)); + registerTest("TimingStatisticsWithTelemetryOn", "Signal timing parameters update with telemetry enabled", eUnitTestCategory::utMandatoryPass, std::bind(&CUnitTestGroup_SignalSlot::test_TimingStatisticsWithTelemetryOn, this)); + registerTest("TimingStatisticsWithTelemetryOff", "Signal timing parameters still update with telemetry disabled", eUnitTestCategory::utMandatoryPass, std::bind(&CUnitTestGroup_SignalSlot::test_TimingStatisticsWithTelemetryOff, this)); } private: + // Drives a signal through InQueue -> InProcess -> Handled and returns the timing parameters + // that CStateSignalSlot::updateTimingStatistics feeds from the telemetry channels. + void driveSignalAndReadTimings(bool bTelemetryEnabled, int64_t& nReactionTime, int64_t& nFinishTime) + { + CDummyRegistry registry(bTelemetryEnabled); + + auto pInformationGroup = std::make_shared("signalinfo", "signal information", registry.getChrono()); + + AMC::CStateSignalSlot slot("instance", "signal", {}, {}, 1000, 5000, 3, pInformationGroup, ®istry); + std::string uuid = "0f0e0d0c-0b0a-0908-0706-050403020100"; + + AMCCommon::CChrono chrono; + chrono.sleepMicroseconds(500); + + auto pMessage = slot.addNewInQueueSignalInternal(uuid, "{}", 500, chrono.getElapsedMicroseconds()); + assertTrue(pMessage != nullptr); + + chrono.sleepMicroseconds(1000); + assertTrue(slot.changeSignalPhaseToInProcessInternal(uuid, chrono.getElapsedMicroseconds())); + + chrono.sleepMicroseconds(1000); + assertTrue(slot.changeSignalPhaseToHandledInternal(uuid, "{\"result\":true}", chrono.getElapsedMicroseconds())); + + nReactionTime = pInformationGroup->getIntParameterValueByName("reactiontime_signal"); + nFinishTime = pInformationGroup->getIntParameterValueByName("finishtime_signal"); + } + + void test_TimingStatisticsWithTelemetryOn() { + int64_t nReactionTime = 0; + int64_t nFinishTime = 0; + driveSignalAndReadTimings(true, nReactionTime, nFinishTime); + + assertTrue(nReactionTime > 0, "reactiontime_ should be updated with telemetry enabled"); + assertTrue(nFinishTime > 0, "finishtime_ should be updated with telemetry enabled"); + } + + void test_TimingStatisticsWithTelemetryOff() { + // This is the acceptance criterion for the telemetry off-switch: dropping telemetry at the + // writer must NOT zero the live signal timing parameters, which are fed from the in-memory + // channel statistics rather than from anything written to disk. + int64_t nReactionTime = 0; + int64_t nFinishTime = 0; + driveSignalAndReadTimings(false, nReactionTime, nFinishTime); + + assertTrue(nReactionTime > 0, "reactiontime_ must still be updated with telemetry disabled"); + assertTrue(nFinishTime > 0, "finishtime_ must still be updated with telemetry disabled"); + } + void test_CreateSignalSlotBasic() { std::vector params; std::vector results; diff --git a/Implementation/UnitTest/amc_unittests_telemetry.hpp b/Implementation/UnitTest/amc_unittests_telemetry.hpp index 7612798d..73b1d72f 100644 --- a/Implementation/UnitTest/amc_unittests_telemetry.hpp +++ b/Implementation/UnitTest/amc_unittests_telemetry.hpp @@ -40,6 +40,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include namespace AMCUnitTest { @@ -64,6 +65,13 @@ namespace AMCUnitTest { registerTest("TypeMappingRoundTrip", "Telemetry channel type mapping round-trip", eUnitTestCategory::utMandatoryPass, std::bind(&CUnitTestGroup_Telemetry::testTypeMappingRoundTrip, this)); registerTest("TypeMappingInvalidThrows", "Telemetry channel type mapping rejects invalid strings", eUnitTestCategory::utMandatoryPass, std::bind(&CUnitTestGroup_Telemetry::testTypeMappingInvalidThrows, this)); registerTest("ChunkArchivePath", "Chunk read-only and archive path works", eUnitTestCategory::utMandatoryPass, std::bind(&CUnitTestGroup_Telemetry::testChunkArchivePath, this)); + registerTest("TelemetryOnCreatesDataFile", "Telemetry enabled creates a telemetry data file", eUnitTestCategory::utMandatoryPass, std::bind(&CUnitTestGroup_Telemetry::testTelemetryOnCreatesDataFile, this)); + registerTest("TelemetryOffCreatesNoDataFile", "Telemetry disabled creates no telemetry data file", eUnitTestCategory::utMandatoryPass, std::bind(&CUnitTestGroup_Telemetry::testTelemetryOffCreatesNoDataFile, this)); + registerTest("TelemetryOffKeepsMarkerStatistics", "Telemetry disabled still updates marker statistics", eUnitTestCategory::utMandatoryPass, std::bind(&CUnitTestGroup_Telemetry::testTelemetryOffKeepsMarkerStatistics, this)); + registerTest("TelemetryOffScopeDoesNotThrow", "Telemetry disabled scopes complete without throwing", eUnitTestCategory::utMandatoryPass, std::bind(&CUnitTestGroup_Telemetry::testTelemetryOffScopeDoesNotThrow, this)); + registerTest("TelemetryOffReaderIsEmpty", "Telemetry reader on a disabled session reports channels but no data", eUnitTestCategory::utMandatoryPass, std::bind(&CUnitTestGroup_Telemetry::testTelemetryOffReaderIsEmpty, this)); + registerTest("TelemetrySettingAfterInitThrows", "Changing the telemetry setting after initialisation fails", eUnitTestCategory::utMandatoryPass, std::bind(&CUnitTestGroup_Telemetry::testTelemetrySettingAfterInitThrows, this)); + registerTest("TelemetryEnabledDefaultsToOn", "Telemetry defaults to enabled when never set", eUnitTestCategory::utMandatoryPass, std::bind(&CUnitTestGroup_Telemetry::testTelemetryEnabledDefaultsToOn, this)); } void initializeTests() override { @@ -78,9 +86,11 @@ namespace AMCUnitTest { AMCCommon::PChrono m_pChrono; AMC::PTelemetryWriter m_pWriter; AMC::PTelemetryHandler m_pHandler; + std::string m_sBasePath; + std::string m_sJournalUUID; } sTelemetryFixture; - sTelemetryFixture createFixture(const std::string& sSuffix) + sTelemetryFixture createFixture(const std::string& sSuffix, bool bTelemetryEnabled = true) { sTelemetryFixture fixture; @@ -94,18 +104,47 @@ namespace AMCUnitTest { std::string sDatabaseFile = sBasePath + "/telemetry.db"; + fixture.m_sBasePath = sBasePath; fixture.m_pDataWrapper = AMCUnitTest::loadLibMCDataInProcess(); fixture.m_pDataModel = fixture.m_pDataWrapper->CreateDataModelInstance(); + + // MUST happen before InitialiseDatabase, which is where the telemetry file is created. + fixture.m_pDataModel->SetTelemetryEnabled(bTelemetryEnabled); + fixture.m_pDataModel->InitialiseDatabase(sBasePath, LibMCData::eDataBaseType::SqLite, sDatabaseFile); fixture.m_pTelemetrySession = fixture.m_pDataModel->CreateTelemetrySession(); + fixture.m_sJournalUUID = fixture.m_pDataModel->CreateJournalSession()->GetSessionUUID(); fixture.m_pChrono = std::make_shared(); - fixture.m_pWriter = std::make_shared(fixture.m_pTelemetrySession, fixture.m_pChrono); + fixture.m_pWriter = std::make_shared(fixture.m_pTelemetrySession, fixture.m_pChrono, bTelemetryEnabled); fixture.m_pHandler = std::make_shared(fixture.m_pWriter); return fixture; } + // Each telemetry-mode test needs a directory of its own, so that a data file left behind by + // an earlier run cannot be mistaken for one created by this one. + sTelemetryFixture createUniqueFixture(const std::string& sSuffix, bool bTelemetryEnabled) + { + return createFixture(sSuffix + "_" + AMCCommon::CUtils::createUUID(), bTelemetryEnabled); + } + + uint32_t countTelemetryDataFiles(const std::string& sBasePath) + { + uint32_t nCount = 0; + for (const auto& entry : std::filesystem::directory_iterator(sBasePath)) { + if (!entry.is_regular_file()) + continue; + + std::string sFileName = entry.path().filename().string(); + if ((sFileName.rfind("telemetry_", 0) == 0) && (sFileName.size() >= 5) + && (sFileName.compare(sFileName.size() - 5, 5, ".data") == 0)) + nCount++; + } + + return nCount; + } + AMC::PTelemetryChannel createChannel(sTelemetryFixture& fixture, const std::string& sIdentifier) { return fixture.m_pHandler->registerChannel(sIdentifier, "channel description", LibMCData::eTelemetryChannelType::CustomMarker); @@ -373,6 +412,110 @@ namespace AMCUnitTest { fixture.m_pHandler->archiveOldChunksToDB(); } + + void testTelemetryOnCreatesDataFile() + { + auto fixture = createUniqueFixture("mode_on", true); + assertTrue(fixture.m_pDataModel->GetTelemetryEnabled(), "Telemetry should report as enabled"); + assertTrue(countTelemetryDataFiles(fixture.m_sBasePath) == 1, "Expected exactly one telemetry data file"); + } + + void testTelemetryOffCreatesNoDataFile() + { + auto fixture = createUniqueFixture("mode_off", false); + assertFalse(fixture.m_pDataModel->GetTelemetryEnabled(), "Telemetry should report as disabled"); + assertTrue(countTelemetryDataFiles(fixture.m_sBasePath) == 0, "Expected no telemetry data file at session start"); + + // Recording activity must not create one later either. + auto pChannel = createChannel(fixture, "off_channel"); + for (uint32_t nIndex = 0; nIndex < 32; nIndex++) { + auto scope = pChannel->startIntervalScope(nIndex); + AMCCommon::CChrono::sleepMicroseconds(10); + } + pChannel->createInstantMarker(7); + fixture.m_pHandler->archiveOldChunksToDB(); + + assertTrue(countTelemetryDataFiles(fixture.m_sBasePath) == 0, "Expected no telemetry data file after recording activity"); + } + + void testTelemetryOffKeepsMarkerStatistics() + { + // This is the behaviour CStateSignalSlot::updateTimingStatistics depends on: the + // reactiontime_ / finishtime_ / acknowledgetime_ signal parameters are fed from + // getMaxDurationInMicroseconds, which must keep working with telemetry disabled. + auto fixture = createUniqueFixture("off_stats", false); + auto pChannel = createChannel(fixture, "off_stats_channel"); + + auto pMarker = pChannel->startIntervalMarker(42); + AMCCommon::CChrono::sleepMicroseconds(500); + pMarker->finishMarker(); + + assertTrue(pMarker->isFinished(), "Marker should be finished with telemetry disabled"); + assertTrue(pMarker->getDuration() > 0, "Marker duration should be recorded with telemetry disabled"); + assertTrue(pChannel->getMaxDurationInMicroseconds() == pMarker->getDuration(), "Channel max duration should track the marker with telemetry disabled"); + assertTrue(pChannel->getTotalMarkersCreated() == 1, "Marker count should be tracked with telemetry disabled"); + } + + void testTelemetryOffScopeDoesNotThrow() + { + auto fixture = createUniqueFixture("off_scope", false); + auto pChannel = createChannel(fixture, "off_scope_channel"); + + bool thrown = false; + try { + for (uint32_t nIndex = 0; nIndex < 16; nIndex++) { + auto scope = pChannel->startIntervalScope(nIndex); + AMCCommon::CChrono::sleepMicroseconds(10); + } + } + catch (...) { + thrown = true; + } + + assertFalse(thrown, "Telemetry scopes must not throw with telemetry disabled"); + assertTrue(pChannel->getTotalMarkersCreated() == 16, "All markers should still be created"); + assertTrue(pChannel->getMaxDurationInMicroseconds() > 0, "Scope durations should still be recorded"); + } + + void testTelemetryOffReaderIsEmpty() + { + auto fixture = createUniqueFixture("off_reader", false); + auto pChannel = createChannel(fixture, "off_reader_channel"); + pChannel->createInstantMarker(1); + fixture.m_pHandler->archiveOldChunksToDB(); + + // Channel registration is deliberately unaffected by the off-switch, so that an "off" + // session is still self-describing. + auto pReader = fixture.m_pDataModel->CreateTelemetryReader(fixture.m_sJournalUUID); + assertTrue(pReader->GetChannelCount() == 1, "Reader should report the registered channel"); + assertTrue(pReader->GetChunkCount() == 0, "Reader should report no chunks for a disabled session"); + + uint32_t nChannelIndex = 0; + assertTrue(pReader->FindChannelByIdentifier("off_reader_channel", nChannelIndex), "Reader should find the registered channel"); + } + + void testTelemetrySettingAfterInitThrows() + { + auto fixture = createUniqueFixture("set_after_init", true); + + bool thrown = false; + try { + fixture.m_pDataModel->SetTelemetryEnabled(false); + } + catch (...) { + thrown = true; + } + + assertTrue(thrown, "Expected SetTelemetryEnabled after InitialiseDatabase to throw"); + } + + void testTelemetryEnabledDefaultsToOn() + { + // A caller that never calls SetTelemetryEnabled must get stock behaviour. + auto pDataWrapper = AMCUnitTest::loadLibMCDataInProcess(); + auto pDataModel = pDataWrapper->CreateDataModelInstance(); + assertTrue(pDataModel->GetTelemetryEnabled(), "Telemetry should default to enabled"); + } }; }