diff --git a/rdkperf/Makefile b/rdkperf/Makefile index 02fdc03..d8f85a0 100644 --- a/rdkperf/Makefile +++ b/rdkperf/Makefile @@ -30,6 +30,7 @@ INCLUDES += \ # Libraries to load LD_FLAGS = -L$(BUILD_DIR) \ + -Wl,-rpath,'$$ORIGIN' \ -lperftool \ -lrt -lpthread -lstdc++ diff --git a/rdkperf/rdk_latency.cpp b/rdkperf/rdk_latency.cpp new file mode 100644 index 0000000..24ae7c0 --- /dev/null +++ b/rdkperf/rdk_latency.cpp @@ -0,0 +1,118 @@ +/** +* Copyright 2024 Comcast Cable Communications Management, LLC +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +* SPDX-License-Identifier: Apache-2.0 +*/ + +#include "rdk_perf_sequence.h" +#include "rdk_perf_location.h" +#include "rdk_perf_latency.h" +#include "rdk_perf_scopedlock.h" + +#define REPORT_FREQUENCY 500 + +#ifdef USE_RDK_PERF +#include "rdk_perf.h" +#define PERF_FUNC(a) RDKPerf perf(a); +#else +#define PERF_FUNC(a) +#endif // USE_RDK_PERF + +// Prototypes +bool RDKCheckSequenceCount(PerfSequence* pSeq); + +void RDKLatency(const char* sequence, const char* location) +{ + PERF_FUNC(__FUNCTION__); + + SCOPED_LOCK(); + + PerfSequence* pSeq = PerfSequence::GetInstance(); + if(pSeq->GetSequence(sequence)) { + pSeq->RecordLocation(location); + + if(RDKCheckSequenceCount(pSeq)) { + RDKLatencyReport(sequence); + } + } +} + +bool RDKCheckSequenceCount(PerfSequence* pSeq) +{ + PERF_FUNC(__FUNCTION__); + + static uint32_t lastCount = 0; + bool retVal = false; + + uint32_t count = pSeq->GetRecordCount(); + // Report every n-th location + if(count != lastCount && count % REPORT_FREQUENCY == 0) { + // Count will be the same for all the locations + // in the sequence, so only print the report when the + // last location in the sequence is recorded + retVal = true; + } + lastCount = count; + + return retVal; +} +bool RDKLatencyReportAll() +{ + // For all sequences in the PerfSequence instance + PERF_FUNC(__FUNCTION__); + SCOPED_LOCK(); + PerfSequence* pSeq = PerfSequence::GetInstance(); + // Iterate through all sequences + // Note: This requires adding an iterator method to PerfSequence class + // For simplicity, assuming we have a method GetAllSequenceNames that returns a vector of names + std::vector sequenceNames = pSeq->GetAllSequenceNames(); + for(const auto& seqName : sequenceNames) { + RDKLatencyReport(seqName.c_str()); + } + return true; +} +bool RDKLatencyReport(const char* sequence) +{ + PERF_FUNC(__FUNCTION__); + + SCOPED_LOCK(); + FILE* fp = stderr; + //fp = fopen("/tmp/rdkperf_latency.log", "a"); + + PerfSequence* pSeq = PerfSequence::GetInstance(); + if(pSeq->GetSequence(sequence)) { + DataRecord* pData = pSeq->GetDataRecord(); + if(pData != nullptr) { + fprintf(fp, "Sequence %s Depth %d\n", pSeq->GetName(), pSeq->GetLocationsDepth()); + uint8_t depth = 1; + while(pData != nullptr) { + for(uint8_t i = 0; i <= depth; i++) { + fprintf(fp, "--"); + } + fprintf(fp, "| %s >> Elapsed %0.3lf Avg %0.3lf Min %0.3lf Max %0.3lf Count %u \n", + pData->name, + (double)pData->average_elapsed / 1000.0, + (double)pData->average / 1000.0, + (double)pData->min / 1000.0, + (double)pData->max / 1000.0, + pData->count); + pData = pData->child; + depth++; + } + return true; + } + } + return false; +} \ No newline at end of file diff --git a/rdkperf/rdk_mem_tracker.cpp b/rdkperf/rdk_mem_tracker.cpp new file mode 100644 index 0000000..92ff1bd --- /dev/null +++ b/rdkperf/rdk_mem_tracker.cpp @@ -0,0 +1,179 @@ +/** +* Copyright 2021 Comcast Cable Communications Management, LLC +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +* SPDX-License-Identifier: Apache-2.0 +*/ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "rdk_mem_tracker.h" + +RDKMemTracker::RDKMemTracker(LogFunction pLog) +: _sequence(0) +, _lastFreed(0) +, _totalAllocatedSize(0) +, _totalAllocatedElements(0) +, _logFunc(NULL) +{ + if(pLog != NULL) { + _logFunc = pLog; + } + return; +} + +RDKMemTracker::~RDKMemTracker() +{ + Report(); + + auto it = _map.begin(); + while(it != _map.end()) { + delete it->second; + it++; + } + _map.clear(); + + return; +} + +void RDKMemTracker::Allocation(void* memory, uint32_t size, const char* szFunction, const uint32_t nLine) +{ + _sequence++; + AllocationData* pAlloc = (AllocationData*)calloc(1, sizeof(AllocationData)); + pAlloc->size = size; + pAlloc->szFunction = szFunction; + pAlloc->nLine = nLine; + pAlloc->sequence = _sequence; + + _map[memory] = pAlloc; + _totalAllocatedSize += size; + _totalAllocatedElements += 1; + + return; +} + +void RDKMemTracker::Free(void* memory) +{ + auto it = _map.find(memory); + if(it != _map.end()) { + AllocationData* pAlloc = it->second; + if(_lastFreed != pAlloc->sequence - 1) { + // Free out of sequence + Log("Freeing memory %p of size %u out of sequence %u, last freed sequence %u\n", + memory, pAlloc->size, pAlloc->sequence, _lastFreed); + } + _lastFreed = pAlloc->sequence; + _totalAllocatedSize -= pAlloc->size; + _totalAllocatedElements -= 1; + ::free(pAlloc); + _map.erase(it); + } + else { + // Error, allocation not in list + Log("ERROR: Could not find memory item %p in the allocation list\n", memory); + } + + return; +} + +void RDKMemTracker::Log(std::string& buffer) +{ + if(_logFunc != NULL) { + _logFunc(buffer.c_str()); + } + else { + fprintf(stdout, "%s", buffer.c_str()); + } +} + +void RDKMemTracker::Log(const char * format, ...) +{ + char logMessage[LOG_MESSAGE_SIZE]; + + // Generate the log string + va_list ap; + va_start(ap, format); + vsnprintf(logMessage, LOG_MESSAGE_SIZE, format, ap); + va_end(ap); + + std::string buffer(logMessage); + Log(buffer); +} + +void RDKMemTracker::Report(bool bForce) +{ + if(bForce == true || _map.size() > 0) { + // How many allocations in the map + Log("Total active allocations %u (%u)\n", _totalAllocatedElements, _map.size()); + + auto it = _map.begin(); + while(it != _map.end()) { + AllocationData* pAlloc = it->second; + Log("Element sequence %u size %u, allocated here %s, %u\n", + pAlloc->sequence, pAlloc->size, pAlloc->szFunction, pAlloc->nLine); + it++; + } + } +} + + +// C interface +extern "C" { + +RDKMemTrackerHandle CreateMemTrackerWithLogCallback(LogFunction pLog) +{ + return static_cast(new RDKMemTracker(pLog)); +} + +RDKMemTrackerHandle CreateMemTracker() +{ + return static_cast(new RDKMemTracker()); +} + +void TerminateMemTracker(RDKMemTrackerHandle hTracker) +{ + delete static_cast(hTracker); + return; +} + +// Memory tracking +void TrackAllocation(RDKMemTrackerHandle hTracker, void* memory, uint32_t size, const char* szFunction, const uint32_t nLine) +{ + static_cast(hTracker)->Allocation(memory, size, szFunction, nLine); + return; +} + +void TrackFree(RDKMemTrackerHandle hTracker, void* memory) +{ + static_cast(hTracker)->Free(memory); + return; +} + +// Reporting +void MemTrackerReport(RDKMemTrackerHandle hTracker) +{ + static_cast(hTracker)->Report(); + return; +} + + +} // extern "C" diff --git a/rdkperf/rdk_mem_tracker.h b/rdkperf/rdk_mem_tracker.h new file mode 100644 index 0000000..a81fb9d --- /dev/null +++ b/rdkperf/rdk_mem_tracker.h @@ -0,0 +1,98 @@ +/** +* Copyright 2021 Comcast Cable Communications Management, LLC +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +* SPDX-License-Identifier: Apache-2.0 +*/ + +#ifndef __RDK_MEM_TRACKER_H__ +#define __RDK_MEM_TRACKER_H__ + +#ifdef __cplusplus +extern "C" { +#endif +typedef void (*LogFunction)(const char * ptr); +#ifdef __cplusplus +} // extern "C" +#endif + +#ifdef __cplusplus + +#include +#include +#include +#include + +#define LOG_MESSAGE_SIZE 4096 + +class RDKMemTracker +{ +public: + + RDKMemTracker(LogFunction pLog = NULL); + ~RDKMemTracker(); + + void Allocation(void* memory, uint32_t size, const char* szFunction, const uint32_t nLine); + void Free(void* memory); + + void Report(bool bForce = false); + + void Log(std::string& buffer); + void Log(const char * format, ...); + +private: + typedef struct AllocationData_ + { + uint32_t size; + const char* szFunction; + uint32_t nLine; + uint32_t sequence; + } AllocationData; + + std::map _map; + uint32_t _sequence; + uint32_t _lastFreed; + uint32_t _totalAllocatedSize; + uint32_t _totalAllocatedElements; + + LogFunction _logFunc; + +}; + +#endif // #ifdef __cplusplus + +// C interface +#ifdef __cplusplus +extern "C" { +#endif + +typedef void* RDKMemTrackerHandle; + +// Lifecycle +RDKMemTrackerHandle CreateMemTracker(); +RDKMemTrackerHandle CreateMemTrackerWithLogCallback(LogFunction pLog); +void TerminateMemTracker(RDKMemTrackerHandle hTracker); + +// Memory tracking +void TrackAllocation(RDKMemTrackerHandle hTracker, void* memory, uint32_t size, const char* szFunction, const uint32_t nLine); +void TrackFree(RDKMemTrackerHandle hTracker, void* memory); + +// Reporting +void MemTrackerReport(RDKMemTrackerHandle hTracker); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // __RDK_MEM_TRACKER_H__ diff --git a/rdkperf/rdk_perf.cpp b/rdkperf/rdk_perf.cpp index 37bb00e..4e1fda9 100644 --- a/rdkperf/rdk_perf.cpp +++ b/rdkperf/rdk_perf.cpp @@ -247,6 +247,8 @@ static void PerfModuleTerminate() #endif // PERF_REMOTE RDKPerf_DeleteMap(); + + return; } #ifdef NO_PERF diff --git a/rdkperf/rdk_perf.h b/rdkperf/rdk_perf.h index ae2daac..6a6706e 100644 --- a/rdkperf/rdk_perf.h +++ b/rdkperf/rdk_perf.h @@ -32,6 +32,7 @@ #include #include +#include "rdk_perf_latency.h" #include "rdk_perf_record.h" //#include "rdk_perf_node.h" diff --git a/rdkperf/rdk_perf_latency.h b/rdkperf/rdk_perf_latency.h new file mode 100644 index 0000000..a54efaf --- /dev/null +++ b/rdkperf/rdk_perf_latency.h @@ -0,0 +1,38 @@ +/** +* Copyright 2024 Comcast Cable Communications Management, LLC +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +* SPDX-License-Identifier: Apache-2.0 +*/ + +#ifndef __RDK_PERF_LATENCY_H__ +#define __RDK_PERF_LATENCY_H__ + +#include +#ifndef __cplusplus +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +void RDKLatency(const char* sequence, const char* location); +bool RDKLatencyReport(const char* sequence); +bool RDKLatencyReportAll(); + +#ifdef __cplusplus +} // extern "C" +#endif +#endif // __RDK_PERF_LATENCY_H__ diff --git a/src/rdk_perf_circularbuffer.cpp b/src/rdk_perf_circularbuffer.cpp new file mode 100644 index 0000000..a868acf --- /dev/null +++ b/src/rdk_perf_circularbuffer.cpp @@ -0,0 +1,183 @@ +/** +* Copyright 2025 Comcast Cable Communications Management, LLC +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +* SPDX-License-Identifier: Apache-2.0 +*/ +#include "rdk_perf_circularbuffer.h" +#include "rdk_perf_logging.h" +#include +#include +#include "rdk_perf_latency_data.h" + +// Circular Buffer (CoPilot Implementation) +CircularBuffer::CircularBuffer() +: circBuffer(nullptr) +{ +} + +CircularBuffer::CircularBuffer(void* preallocatedMemory, size_t maxRecords) + +{ + // circBuffer = static_cast(preallocatedMemory); + // circBuffer->maxRecords = maxRecords; + // circBuffer->head = 0; + // circBuffer->tail = 0; + // circBuffer->currentSize = 0; + // std::memset(circBuffer->records, 0, circBuffer->maxRecords * sizeof(CircBufferRecord)); + initialize_with_exiting_memory(preallocatedMemory, maxRecords); +} + +CircularBuffer::~CircularBuffer() { + // No need to explicitly destruct records + // as they are part of the location array +} + +void CircularBuffer::initialize(void* preallocatedMemory, size_t maxRecords) +{ + circBuffer = static_cast(preallocatedMemory); + circBuffer->maxRecords = maxRecords; + circBuffer->head = 0; + circBuffer->tail = 0; + circBuffer->currentSize = 0; + std::memset(circBuffer->records, 0, circBuffer->maxRecords * sizeof(CircBufferRecord)); +} + +void CircularBuffer::initialize_with_exiting_memory(void* preallocatedMemory, size_t maxRecords) +{ + circBuffer = static_cast(preallocatedMemory); + circBuffer->maxRecords = maxRecords; +} + +bool CircularBuffer::push(uint32_t key, uint64_t value) +{ + // if(circBuffer->records == nullptr) { + // LOG(eError, "[%s] Records array not set\n", circBuffer->name); + // return false; + // } + + if (full()) { + // Remove the oldest record + LOG(eWarning, "[%s] Buffer FULL (size=%u, max=%u) - removing oldest key %u at tail=%u\n", + circBuffer->name, circBuffer->currentSize, circBuffer->maxRecords, + circBuffer->records[circBuffer->tail].key, circBuffer->tail); + circBuffer->tail = (circBuffer->tail + 1) % circBuffer->maxRecords; + --circBuffer->currentSize; + } + circBuffer->records[circBuffer->head].key = key; + circBuffer->records[circBuffer->head].value = value; + LOG(eTrace, "[%s] PUSH key %u at head=%u, buffer size now %u/%u\n", + circBuffer->name, key, circBuffer->head, circBuffer->currentSize + 1, circBuffer->maxRecords); + circBuffer->head = (circBuffer->head + 1) % circBuffer->maxRecords; + ++circBuffer->currentSize; + return true; +} + +bool CircularBuffer::pop(uint32_t& key, uint64_t& value) +{ + // if(circBuffer->records == nullptr) { + // LOG(eError, "[%s] Records array not set\n", circBuffer->name); + // return false; + // } + + if (empty()) { + LOG(eWarning, "[%s] POP attempted on EMPTY buffer\n", circBuffer->name); + return false; // Buffer is empty + } + key = circBuffer->records[circBuffer->tail].key; + value = circBuffer->records[circBuffer->tail].value; + LOG(eTrace, "[%s] POP key %u from tail=%u, buffer size %u->%u\n", + circBuffer->name, key, circBuffer->tail, circBuffer->currentSize, circBuffer->currentSize - 1); + circBuffer->tail = (circBuffer->tail + 1) % circBuffer->maxRecords; + --circBuffer->currentSize; + + LOG(eTrace, "[%s] Popped key %u value %lu\n", circBuffer->name, key, value); + return true; +} + +bool CircularBuffer::peek(uint32_t& key, uint64_t& value) const +{ + // if(circBuffer->records == nullptr) { + // LOG(eError, "[%s] Records array not set\n", circBuffer->name); + // return false; + // } + + if (empty()) { + LOG(eWarning, "[%s] Buffer is empty\n", circBuffer->name); + return false; // Buffer is empty + } + + key = circBuffer->records[circBuffer->tail].key; + value = circBuffer->records[circBuffer->tail].value; + return true; +} + +bool CircularBuffer::find(uint32_t key, uint64_t& value) const +{ + // if(circBuffer->records == nullptr) { + // LOG(eError, "[%s] Records array not set\n", circBuffer->name); + // return false; + // } + + if (empty()) { + LOG(eWarning, "[%s] FIND key %u: Buffer is EMPTY\n", circBuffer->name, key); + return false; // Buffer is empty + } + + LOG(eTrace, "[%s] FIND key %u: searching in buffer (size=%u, head=%u, tail=%u)\n", + circBuffer->name, key, circBuffer->currentSize, circBuffer->head, circBuffer->tail); + + size_t index = circBuffer->tail; + for (size_t i = 0; i < circBuffer->currentSize; ++i) { + LOG(eTrace, "[%s] FIND key %u: checking index %u, found key %u\n", + circBuffer->name, key, index, circBuffer->records[index].key); + if (circBuffer->records[index].key == key) { + value = circBuffer->records[index].value; + LOG(eTrace, "[%s] FIND key %u: FOUND at index %u\n", circBuffer->name, key, index); + return true; + } + index = (index + 1) % circBuffer->maxRecords; + } + LOG(eWarning, "[%s] FIND key %u: Key NOT found in buffer (size=%u, contains keys: tail->head)\n", + circBuffer->name, key, circBuffer->currentSize); + + // Log all keys currently in buffer + size_t idx = circBuffer->tail; + for (size_t i = 0; i < circBuffer->currentSize; ++i) { + LOG(eWarning, "[%s] Buffer[%u] = key %u\n", circBuffer->name, i, circBuffer->records[idx].key); + idx = (idx + 1) % circBuffer->maxRecords; + } + + return false; // Key not found +} + +size_t CircularBuffer::size() const +{ + return circBuffer->currentSize; +} + +bool CircularBuffer::empty() const +{ + return circBuffer->currentSize == 0; +} + +bool CircularBuffer::full() const +{ + return circBuffer->currentSize == circBuffer->maxRecords; +} + +size_t CircularBuffer::recordSize() +{ + return sizeof(CircBufferRecord); +} diff --git a/src/rdk_perf_circularbuffer.h b/src/rdk_perf_circularbuffer.h new file mode 100644 index 0000000..afecb03 --- /dev/null +++ b/src/rdk_perf_circularbuffer.h @@ -0,0 +1,58 @@ +/** +* Copyright 2025 Comcast Cable Communications Management, LLC +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +* SPDX-License-Identifier: Apache-2.0 +*/ + + +#ifndef __RDK_PERF_CIRCULARBUFFER_H__ +#define __RDK_PERF_CIRCULARBUFFER_H__ + +#include +#include +#include + +#include "rdk_perf_latency_data.h" + +// Circular Buffer (CoPilot Implementation) +class CircularBuffer { +public: + CircularBuffer(); + CircularBuffer(void* preallocatedMemory, size_t maxRecords); + ~CircularBuffer(); + + void initialize(void* preallocatedMemory, size_t maxRecords); + void initialize_with_exiting_memory(void* preallocatedMemory, size_t maxRecords); + void set_name(const char* name) { + if(circBuffer != nullptr) { + circBuffer->name = const_cast(name); + } + } + + bool push(uint32_t key, uint64_t value); + bool pop(uint32_t& key, uint64_t& value); + bool peek(uint32_t& key, uint64_t& value) const; + bool find(uint32_t key, uint64_t& value) const; + size_t size() const; + bool empty() const; + bool full() const; + // Static method to get the size of a record + static size_t recordSize(); + +private: + CircBufferObject* circBuffer; +}; + +#endif // __RDK_PERF_CIRCULARBUFFER_H__ diff --git a/src/rdk_perf_clock.cpp b/src/rdk_perf_clock.cpp index fd8008a..f96d9b1 100644 --- a/src/rdk_perf_clock.cpp +++ b/src/rdk_perf_clock.cpp @@ -93,7 +93,7 @@ void PerfClock::SetCPU() } if(data.ru_stime.tv_sec == 0 && data.ru_stime.tv_usec == 0) { - LOG(eError, "getrusage failed values = 0\n"); + //LOG(eError, "getrusage failed values = 0\n"); m_timeStamp.systemCPU = 0; m_timeStamp.userCPU = 0; } diff --git a/src/rdk_perf_latency_data.h b/src/rdk_perf_latency_data.h new file mode 100644 index 0000000..c7629f1 --- /dev/null +++ b/src/rdk_perf_latency_data.h @@ -0,0 +1,84 @@ +/** +* Copyright 2024 Comcast Cable Communications Management, LLC +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +* SPDX-License-Identifier: Apache-2.0 +*/ + +#ifndef __RDK_PERF_LATENCY_DATA_H__ +#define __RDK_PERF_LATENCY_DATA_H__ + + +#include +#include + +#define MAX_SEQUENCE 10 +#define MAX_LOCATIONS 100 +#define MAX_TIME_STAMPS 25 +#define MAX_NAME_LEN 64 +#define INVALID_TIMESTAMP (uint64_t)0 +#define INVALID_OFFSET (int32_t)-1 + +typedef struct CircBufferRecord_s +{ + uint32_t key; + uint64_t value; +} CircBufferRecord; + +typedef struct CircBufferObject_s +{ + CircBufferRecord records[sizeof(CircBufferRecord) * MAX_TIME_STAMPS]; + uint32_t maxRecords; + uint32_t head; + uint32_t tail; + uint32_t currentSize; + char* name; // pointer to the name of the location or sequence, stored in parent structure +} CircBufferObject; + + +typedef struct Sequence_s +{ + char name[MAX_NAME_LEN]; + int32_t location_offset; + uint8_t timeStampCirBuffer[sizeof(CircBufferObject)]; // Array of root timestamps for the sequence + //uint64_t rootTimeStamp; // root timestamp in microseconds, updated each time the first location is recorded +} Sequence; + +typedef struct Location_s +{ + char name[MAX_NAME_LEN]; + Sequence* sequence; + uint8_t timeStampCirBuffer[sizeof(CircBufferObject)]; + uint32_t count; + int32_t parent_offset; + int32_t child_offset; + uint64_t total; // total time in microseconds from parent location + uint64_t total_elapsed; // total elapsed time in microseconds from root location + uint64_t min; // minimum time in microseconds + uint64_t max; // maximum time in microseconds +} Location; + +typedef struct DataRecord_s +{ + char name[MAX_NAME_LEN]; + double average; // average time in microseconds + double average_elapsed; // average time in microseconds from root location + double min; // minimum time in microseconds + double max; // maximum time in microseconds + uint32_t count; + DataRecord_s* child; +} DataRecord; + + +#endif // __RDK_PERF_LATENCY_DATA_H__ diff --git a/src/rdk_perf_location.cpp b/src/rdk_perf_location.cpp new file mode 100644 index 0000000..7a29726 --- /dev/null +++ b/src/rdk_perf_location.cpp @@ -0,0 +1,463 @@ +/** +* Copyright 2024 Comcast Cable Communications Management, LLC +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +* SPDX-License-Identifier: Apache-2.0 +*/ + +#include "rdk_perf_location.h" +#include "rdk_perf_logging.h" +#include "rdk_perf_circularbuffer.h" + +#include +#include + +#ifdef USE_RDK_PERF +#include "rdk_perf.h" +#define PERF_FUNC(a) RDKPerf perf(a); +#else +#define PERF_FUNC(a) +#endif // USE_RDK_PERF + +static void* _locations = nullptr; + +#if USE_CIRCULAR_BUFFER +// Circular Buffer (CoPilot Implementation) +CircularBuffer::CircularBuffer() +: circBuffer(nullptr) +{ +} + +CircularBuffer::CircularBuffer(void* preallocatedMemory, size_t maxRecords) + +{ + circBuffer = static_cast(preallocatedMemory); + circBuffer->maxRecords = maxRecords; + circBuffer->head = 0; + circBuffer->tail = 0; + circBuffer->currentSize = 0; + std::memset(circBuffer->records, 0, circBuffer->maxRecords * sizeof(CircBufferRecord));} + +CircularBuffer::~CircularBuffer() { + // No need to explicitly destruct records + // as they are part of the location array +} + +void CircularBuffer::initialize(void* preallocatedMemory, size_t maxRecords) +{ + circBuffer = static_cast(preallocatedMemory); + circBuffer->maxRecords = maxRecords; + circBuffer->head = 0; + circBuffer->tail = 0; + circBuffer->currentSize = 0; + std::memset(circBuffer->records, 0, circBuffer->maxRecords * sizeof(CircBufferRecord)); +} + +void CircularBuffer::initialize_with_exiting_memory(void* preallocatedMemory, size_t maxRecords) +{ + circBuffer = static_cast(preallocatedMemory); +} + +bool CircularBuffer::push(uint32_t key, uint64_t value) +{ + if(circBuffer->records == nullptr) { + LOG(eError, "Records array not set\n"); + return false; + } + + if (full()) { + // Remove the oldest record + circBuffer->tail = (circBuffer->tail + 1) % circBuffer->maxRecords; + --circBuffer->currentSize; + } + circBuffer->records[circBuffer->head].key = key; + circBuffer->records[circBuffer->head].value = value; + circBuffer->head = (circBuffer->head + 1) % circBuffer->maxRecords; + ++circBuffer->currentSize; + return true; +} + +bool CircularBuffer::pop(uint32_t& key, uint64_t& value) +{ + if(circBuffer->records == nullptr) { + LOG(eError, "Records array not set\n"); + return false; + } + + if (empty()) { + return false; // Buffer is empty + } + key = circBuffer->records[circBuffer->tail].key; + value = circBuffer->records[circBuffer->tail].value; + circBuffer->tail = (circBuffer->tail + 1) % circBuffer->maxRecords; + --circBuffer->currentSize; + return true; +} + +bool CircularBuffer::peek(uint32_t& key, uint64_t& value) const +{ + if(circBuffer->records == nullptr) { + LOG(eError, "Records array not set\n"); + return false; + } + + if (empty()) { + LOG(eWarning, "Buffer is empty\n"); + return false; // Buffer is empty + } + + key = circBuffer->records[circBuffer->tail].key; + value = circBuffer->records[circBuffer->tail].value; + return true; +} + +bool CircularBuffer::find(uint32_t key, uint64_t& value) const +{ + if(circBuffer->records == nullptr) { + LOG(eError, "Records array not set\n"); + return false; + } + + if (empty()) { + return false; // Buffer is empty + } + size_t index = circBuffer->tail; + for (size_t i = 0; i < circBuffer->currentSize; ++i) { + if (circBuffer->records[index].key == key) { + value = circBuffer->records[index].value; + return true; + } + index = (index + 1) % circBuffer->maxRecords; + } + return false; // Key not found +} + +size_t CircularBuffer::size() const +{ + return circBuffer->currentSize; +} + +bool CircularBuffer::empty() const +{ + return circBuffer->currentSize == 0; +} + +bool CircularBuffer::full() const +{ + return circBuffer->currentSize == circBuffer->maxRecords; +} + +size_t CircularBuffer::recordSize() +{ + return sizeof(CircBufferRecord); +} + +#endif // USE_CIRCULAR_BUFFER + +#ifdef NEED_PREALLOCATED_MAP +// PreAllocated Map +PreallocatedMap::PreallocatedMap(void* preallocatedMemory, size_t maxEntries) +: _entries(static_cast(preallocatedMemory)) +, _maxEntries(maxEntries) +, _currentSize(0) +{ + // Do not clear the memory as it is expected to be pre-allocated + // and might contain existing data + + // Calculate the current size + for (size_t i = 0; i < _maxEntries; ++i) { + if (_entries[i].inUse) { + _currentSize++; + } + } +} + +PreallocatedMap::~PreallocatedMap() +{ + // No need to explicitly destruct entries as they are part of the location array +} + +bool PreallocatedMap::insert(uint32_t key, uint64_t value) +{ + for (size_t i = 0; i < _maxEntries; ++i) { + if (!_entries[i].inUse) { + _entries[i].key = key; + _entries[i].value = value; + _entries[i].inUse = true; + _currentSize++; + return true; + } + } + return false; // No available slot +} + +bool PreallocatedMap::erase(uint32_t key) +{ + for (size_t i = 0; i < _maxEntries; ++i) { + if (_entries[i].inUse && _entries[i].key == key) { + _entries[i].inUse = false; + _currentSize--; + return true; + } + } + return false; // Key not found +} + +bool PreallocatedMap::find(uint32_t key, uint64_t& value) +{ + for (size_t i = 0; i < _maxEntries; ++i) { + if (_entries[i].inUse && _entries[i].key == key) { + value = _entries[i].value; + return true; + } + } + value = INVALID_TIMESTAMP; + return false; // Key not found +} + +bool PreallocatedMap::sort() +{ + std::sort(_entries, _entries + _maxEntries, [](const Entry& a, const Entry& b) { + if (!a.inUse) return false; + if (!b.inUse) return true; + return a.key < b.key; + }); + return true; +} + +size_t PreallocatedMap::size() const +{ + return _currentSize; +} + +bool PreallocatedMap::empty() const +{ + return _currentSize == 0; +} +#endif // NEED_PREALLOCATED_MAP + +// Static method to set the locations array +void PerfLocation::SetLocationsArray(Location* locations) +{ + _locations = locations; +} + +PerfLocation::PerfLocation(Location * location) +: _location(location) +, _location_offset(0) +, _timeStamps(&location->timeStampCirBuffer[0], MAX_TIME_STAMPS) +{ + // Set Location offset + if(_locations != nullptr) { + _location_offset = (uint32_t)(((uint8_t*)location - (uint8_t*)_locations)/sizeof(Location)); + } + else { + _location_offset = INVALID_OFFSET; + LOG(eError, "Locations array not set\n"); + } +} + +PerfLocation::PerfLocation(uint32_t locationOffset) +: _location(nullptr) +, _location_offset(locationOffset) +{ + if(_locations != nullptr) { + _location = ((Location*)_locations) + locationOffset; + _timeStamps.initialize_with_exiting_memory(&_location->timeStampCirBuffer[0], MAX_TIME_STAMPS); + } + else { + LOG(eError, "Locations array not set\n"); + } +} + +PerfLocation::~PerfLocation() +{ +} + +const char* PerfLocation::GetName() +{ + return _location->name; +} + +const char* PerfLocation::GetChildName() +{ + const char* retVal = "\n"; + + if(_location->child_offset == 0) { + return retVal; + } + + if(_locations == nullptr) { + return retVal; + } + + if(_location->child_offset != 0) { + Location* child = (Location*)((uint8_t*)_locations + _location->child_offset); + retVal = child->name; + } + return retVal; +} + +DataRecord* PerfLocation::GetDataRecord() +{ + DataRecord* retVal = new DataRecord(); + std::memset(retVal, 0, sizeof(DataRecord)); + + // Copy the name + std::strncpy(retVal->name, _location->name, MAX_NAME_LEN); + // Copy the total time + retVal->average = (double)(_location->total / (double)(_location->count)); + // Copy the total elapsed time + retVal->average_elapsed = (double)(_location->total_elapsed / (double)(_location->count)); + // Copy the min time + retVal->min = (double)_location->min; + // Copy the max time + retVal->max = (double)_location->max; + // Copy the count + retVal->count = _location->count; + + // If there is a child, get its data record + if(_location->child_offset != INVALID_OFFSET) { + PerfLocation child(_location->child_offset); + retVal->child = child.GetDataRecord(); + } + + return retVal; +} + +bool PerfLocation::AddTimeStamp(uint64_t timeStamp) +{ + bool retVal = false; + + LOG(eTrace, "Inserted timestamp %lu for count %ld\n", timeStamp, _location->count); + // Insert the timestamp + retVal = _timeStamps.push(_location->count, timeStamp); + + // Is this the first location in the sequence? + if(_location->parent_offset == INVALID_OFFSET) { + // If so, set the root timestamp in the sequence + if(_location->sequence != nullptr) { + CircularBuffer rootTimeStamps; + rootTimeStamps.initialize_with_exiting_memory(&_location->sequence->timeStampCirBuffer[0], MAX_TIME_STAMPS); + // Add the timestamp to the root timestamps + if(!rootTimeStamps.push(_location->count, timeStamp)) { + LOG(eError, "Failed to add root timestamp for sequence %s\n", _location->sequence->name); + } + else { + LOG(eTrace, "Added root timestamp %lu for sequence %s at count %ld\n", timeStamp, _location->sequence->name, _location->count); + } + } + else { + LOG(eError, "No sequence for this location\n"); + } + } + + // Get the root time stamp + uint64_t rootTimeStamp = GetRootTimeStamp(_location->count); + if(rootTimeStamp == INVALID_TIMESTAMP) { + LOG(eError, "Root timestamp not found\n"); + return false; + } + + // Get the matching parent time stamp + if(_location->parent_offset != INVALID_OFFSET) { + PerfLocation parent(_location->parent_offset); + uint64_t parentTimeStamp = parent.GetTimeStamp(_location->count); + if(parentTimeStamp != INVALID_TIMESTAMP) { + // Calculate the difference + uint64_t diff = (uint64_t)(timeStamp - parentTimeStamp); + LOG(eTrace, "Parent timestamp %lu, diff %lf\n", parentTimeStamp, diff); + // Update the total elapsed time + _location->total += diff; + + uint64_t rootDiff = (uint64_t)(timeStamp - rootTimeStamp); + // Update the total elapsed time + LOG(eTrace, "Root timestamp %lu, root diff %lf\n", rootTimeStamp, rootDiff); + _location->total_elapsed += rootDiff; + + // Update the min + if(diff < _location->min || _location->min == 0) { + _location->min = diff; + } + // Update the max + if(diff > _location->max) { + _location->max = diff; + } + + LOG(eTrace, "[%s] Count %d Total %lu, Avg %lf Min %lu, Max %lu elapsed %lf\n", _location->name, + _location->count, _location->total, (double)(_location->total / (_location->count + 1)), + _location->min, _location->max, (double)(_location->total_elapsed / (_location->count + 1))); + + // Since there is a match remove the parent timestamp from the map + parent.RemoveTimeStamp(_location->count); + } + else { + LOG(eError, "Parent timestamp not found\n"); + } + } + + // Increment the count + _location->count++; + + return retVal; +} + +uint64_t PerfLocation::GetTimeStamp(uint32_t count) +{ + uint64_t timeStamp = 0; + uint32_t index = 0; + if(_timeStamps.peek(index, timeStamp)) { + LOG(eTrace, "Peeked timestamp %lu for count %u at index %u\n", timeStamp, count, index); + if(index != count) { + LOG(eError, "Invalid index %u, expected %u\n", index, count); + timeStamp = INVALID_TIMESTAMP; + } + else { + LOG(eTrace, "Found timestamp %lu for count %u\n", timeStamp, count); + } + } + else { + LOG(eError, "Failed to peek timestamp for count %u\n", count); + timeStamp = INVALID_TIMESTAMP; + } + return timeStamp; +} + +uint64_t PerfLocation::GetRootTimeStamp(uint32_t count) +{ + if(_location->sequence == nullptr) { + return INVALID_TIMESTAMP; + } + + // Get the root timestamps from the sequence + CircularBuffer rootTimeStamps; + rootTimeStamps.initialize_with_exiting_memory(&_location->sequence->timeStampCirBuffer[0], MAX_TIME_STAMPS); + uint64_t rootTimeStamp = 0; + if(!rootTimeStamps.find(count, rootTimeStamp)) { + LOG(eError, "Failed to find root timestamp for count %u\n", count); + return INVALID_TIMESTAMP; + } + LOG(eTrace, "Found root timestamp %lu for count %u\n", rootTimeStamp, count); + return rootTimeStamp; + + // PerfLocation location(_location->sequence->location_offset); + // return location.GetTimeStamp(count); + +} + +bool PerfLocation::RemoveTimeStamp(uint32_t count) +{ + uint64_t timeStamp = 0; + return _timeStamps.pop(count, timeStamp); +} diff --git a/src/rdk_perf_location.h b/src/rdk_perf_location.h new file mode 100644 index 0000000..153b12e --- /dev/null +++ b/src/rdk_perf_location.h @@ -0,0 +1,118 @@ +/** +* Copyright 2024 Comcast Cable Communications Management, LLC +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +* SPDX-License-Identifier: Apache-2.0 +*/ + +#ifndef __RDK_PERF_LOCATION_H__ +#define __RDK_PERF_LOCATION_H__ + +#include +#include +#include +#include + +#include "rdk_perf_latency_data.h" +#include "rdk_perf_circularbuffer.h" + +#ifdef USE_CIRCULAR_BUFFER +// Circular Buffer (CoPilot Implementation) +class CircularBuffer { +public: + CircularBuffer(); + CircularBuffer(void* preallocatedMemory, size_t maxRecords); + ~CircularBuffer(); + + void initialize(void* preallocatedMemory, size_t maxRecords); + void initialize_with_exiting_memory(void* preallocatedMemory, size_t maxRecords); + + bool push(uint32_t key, uint64_t value); + bool pop(uint32_t& key, uint64_t& value); + bool peek(uint32_t& key, uint64_t& value) const; + bool find(uint32_t key, uint64_t& value) const; + size_t size() const; + bool empty() const; + bool full() const; + // Static method to get the size of a record + static size_t recordSize(); + +private: + CircBufferObject* circBuffer; +}; +#endif // USE_CIRCULAR_BUFFER + +#if NEED_PREALLOCATED_MAP +class PreallocatedMap +{ +public: + PreallocatedMap(void* preallocatedMemory, size_t maxEntries); + ~PreallocatedMap(); + + bool insert(uint32_t key, uint64_t value); + bool erase(uint32_t key); + bool find(uint32_t key, uint64_t& value); + bool sort(); + size_t size() const; + bool empty() const; + + struct Entry + { + uint32_t key; + uint64_t value; + bool inUse; + }; + +private: + Entry* _entries; + size_t _maxEntries; + size_t _currentSize; +}; +#endif // NEED_PREALLOCATED_MAP + +class PerfLocation +{ +public: + PerfLocation(uint32_t locationOffset); + PerfLocation(Location* location); + ~PerfLocation(); + + uint32_t GetLocationOffset() { return _location_offset; }; + uint32_t GetLocationParentOffset() { return _location->parent_offset; }; + uint32_t GetLocationChildOffset() { return _location->child_offset; }; + void SetLocationParentOffset(uint32_t parent_offset) { _location->parent_offset = parent_offset; }; + void SetLocationChildOffset(uint32_t child_offset) { _location->child_offset = child_offset; }; + uint32_t GetCount() { return _location->count; }; + + uint64_t GetTimeStamp(uint32_t count); + uint64_t GetRootTimeStamp(uint32_t count); + bool RemoveTimeStamp(uint32_t count); + bool AddTimeStamp(uint64_t timeStampUS); + + const char* GetName(); + const char* GetChildName(); + DataRecord* GetDataRecord(); + + // Static Methods + static uint32_t GetLocationDataSize() { return sizeof(Location); }; + static uint32_t GetMaxLocations() { return MAX_LOCATIONS; }; + static void SetLocationsArray(Location* locations); + +private: + Location* _location; + uint32_t _location_offset; + CircularBuffer _timeStamps; +}; + +#endif // __RDK_PERF_LOCATION_H__ \ No newline at end of file diff --git a/src/rdk_perf_logging.cpp b/src/rdk_perf_logging.cpp index f63a3e6..ea22ff4 100644 --- a/src/rdk_perf_logging.cpp +++ b/src/rdk_perf_logging.cpp @@ -59,11 +59,14 @@ static void __attribute__((destructor)) LogModuleTerminate(); static void LogModuleInit() { LOG(eWarning, "RDK Perf Logging initialize extending logging set to %d\n", s_VerboseLog); - const char *env_log_level = getenv("RDKPER_EXTENDED_LOGGING"); + const char *env_log_level = getenv("RDKPERF_EXTENDED_LOGGING"); + LOG(eWarning, "[WARN] env_log_level=%s\n", env_log_level ? env_log_level : "NULL"); if(env_log_level != NULL && - strncasecmp(env_log_level, "true", strlen("true")) == 0) { - s_VerboseLog = true; - LOG(eWarning, "Enabling RDKPERF extended logging %d", s_VerboseLog); + strncasecmp(env_log_level, "true", strlen("true")) == 0) { + s_VerboseLog = true; + LOG(eWarning, "Enabling RDKPERF extended logging %d", s_VerboseLog); + } else if(env_log_level != NULL) { + LOG(eWarning, "[WARN] env_log_level='%s' did not match 'true'\n", env_log_level); } } // This function is assigned to execute as library unload diff --git a/src/rdk_perf_msgqueue.cpp b/src/rdk_perf_msgqueue.cpp index 71f1691..071ed29 100644 --- a/src/rdk_perf_msgqueue.cpp +++ b/src/rdk_perf_msgqueue.cpp @@ -42,7 +42,7 @@ PerfMsgQueue::PerfMsgQueue(const char* szQueueName, bool bService) mode_t mode = S_IRWXU | S_IRWXG | S_IRWXO; struct mq_attr new_attr = { 0 }; - snprintf(m_szName, MAX_NAME_LEN, "%s", szQueueName); + snprintf(m_szName, MAX_MSG_NAME_LEN, "%s", szQueueName); if(m_bService) { flags = O_RDONLY | O_CREAT; @@ -106,20 +106,20 @@ bool PerfMsgQueue::SendMessage(MessageType type, const char* szName, uint64_t nT msg.msg_data.entry.tID = pthread_self(); msg.msg_data.entry.nTimeStamp = nTimeStamp; msg.msg_data.entry.nThresholdInUS = nThresholdInUS; - pthread_getname_np(msg.msg_data.entry.tID, msg.msg_data.entry.szThreadName, MAX_NAME_LEN); - memcpy((void*)msg.msg_data.entry.szName, (void*)szName, MIN((size_t)(MAX_NAME_LEN - 1), strlen(szName))); + pthread_getname_np(msg.msg_data.entry.tID, msg.msg_data.entry.szThreadName, MAX_MSG_NAME_LEN); + memcpy((void*)msg.msg_data.entry.szName, (void*)szName, MIN((size_t)(MAX_MSG_NAME_LEN - 1), strlen(szName))); break; case eThreshold: - msg.msg_data.entry.pID = getpid(); - msg.msg_data.entry.tID = pthread_self(); - msg.msg_data.entry.nThresholdInUS = nThresholdInUS; - memcpy((void*)msg.msg_data.entry.szName, (void*)szName, MIN((size_t)(MAX_NAME_LEN - 1), strlen(szName))); + msg.msg_data.threshold.pID = getpid(); + msg.msg_data.threshold.tID = pthread_self(); + msg.msg_data.threshold.nThresholdInUS = nThresholdInUS; + memcpy((void*)msg.msg_data.threshold.szName, (void*)szName, MIN((size_t)(MAX_MSG_NAME_LEN - 1), strlen(szName))); break; case eExit: msg.msg_data.exit.pID = getpid(); msg.msg_data.exit.tID = pthread_self(); msg.msg_data.exit.nTimeStamp = nTimeStamp; - memcpy((void*)msg.msg_data.entry.szName, (void*)szName, MIN((size_t)(MAX_NAME_LEN - 1), strlen(szName))); + memcpy((void*)msg.msg_data.exit.szName, (void*)szName, MIN((size_t)(MAX_MSG_NAME_LEN - 1), strlen(szName))); break; case eReportThread: msg.msg_data.report_thread.pID = getpid(); diff --git a/src/rdk_perf_msgqueue.h b/src/rdk_perf_msgqueue.h index 2e314a9..54c2d8b 100644 --- a/src/rdk_perf_msgqueue.h +++ b/src/rdk_perf_msgqueue.h @@ -45,7 +45,7 @@ //#define RDK_PERF_MSG_QUEUE_NAME "/test" #define RDK_PERF_MSG_QUEUE_NAME "/RDKPerfServerQueue" -#define MAX_NAME_LEN 128 +#define MAX_MSG_NAME_LEN 128 typedef enum _MessageType { @@ -65,8 +65,8 @@ typedef struct _EntryMessage { pid_t pID; pthread_t tID; - char szName[MAX_NAME_LEN]; - char szThreadName[MAX_NAME_LEN]; + char szName[MAX_MSG_NAME_LEN]; + char szThreadName[MAX_MSG_NAME_LEN]; uint64_t nTimeStamp; int32_t nThresholdInUS; } EntryMessage; @@ -75,7 +75,7 @@ typedef struct _ExitMessage { pid_t pID; pthread_t tID; - char szName[MAX_NAME_LEN]; + char szName[MAX_MSG_NAME_LEN]; uint64_t nTimeStamp; } ExitMessage; @@ -83,7 +83,7 @@ typedef struct _ThresholdMessage { pid_t pID; pthread_t tID; - char szName[MAX_NAME_LEN]; + char szName[MAX_MSG_NAME_LEN]; int32_t nThresholdInUS; } ThresholdMessage; @@ -147,7 +147,7 @@ class PerfMsgQueue mqd_t m_queue; struct mq_attr m_queue_attr; bool m_bService; - char m_szName[MAX_NAME_LEN]; + char m_szName[MAX_MSG_NAME_LEN]; uint32_t m_RefCount; }; diff --git a/src/rdk_perf_sequence.cpp b/src/rdk_perf_sequence.cpp new file mode 100644 index 0000000..9430e5c --- /dev/null +++ b/src/rdk_perf_sequence.cpp @@ -0,0 +1,469 @@ +/** +* Copyright 2024 Comcast Cable Communications Management, LLC +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +* SPDX-License-Identifier: Apache-2.0 +*/ + +#include "rdk_perf_logging.h" +#include "rdk_perf_location.h" +#include "rdk_perf_sequence.h" + +#ifdef USE_RDK_PERF +#include "rdk_perf.h" +#define PERF_FUNC(a) RDKPerf perf(a); +#else +#define PERF_FUNC(a) +#endif // USE_RDK_PERF + + +static PerfSequence* _perf_sequence = nullptr; + +// Module constructor/destructor functions +static void __attribute__((constructor)) PerfSequenceModuleInit(); +static void __attribute__((destructor)) PerfSequenceModuleTerminate(); + +// This function is assigned to execute as a library init +// using __attribute__((constructor)) +void PerfSequenceModuleInit() +{ + LOG(eWarning, "Initialize\n"); + if(_perf_sequence != nullptr) { + LOG(eWarning, "_perf_sequence already initialized\n"); + delete _perf_sequence; + } + _perf_sequence = new PerfSequence(); +} + +// This function is assigned to execute as a library termination +// using __attribute__((destructor)) +void PerfSequenceModuleTerminate() +{ + LOG(eWarning, "Terminate\n"); + if(_perf_sequence != nullptr) { + delete _perf_sequence; + _perf_sequence = nullptr; + } +} + +PerfSequence::PerfSequence() +: _shared_memory_block(nullptr) +, _memory_block_size(0) +, _sequences(nullptr) +, _locations(nullptr) +, _current_sequence(nullptr) +, _current_location(nullptr) +{ + // Caclulate the size of the memory block + _memory_block_size = PerfLocation::GetLocationDataSize() * PerfLocation::GetMaxLocations(); + _memory_block_size += sizeof(Sequence) * MAX_SEQUENCE; + + // Attach to the Shared Memory Block + _shared_memory_block = SharedMemoryBlock::get_instance(_memory_block_size); + if(_shared_memory_block == nullptr) { + LOG(eError, "Failed to attach to shared memory block\n"); + return; + } + + // Get the pointer to the sequence array + _sequences = (Sequence*) _shared_memory_block->get_data(); + LOG(eError, "Sequence array at %p\n", _sequences); + // Get the pointer to the location array + _locations = (Location*) ((uint8_t*) _sequences + sizeof(Sequence) * MAX_SEQUENCE); + LOG(eError, "Location array at %p\n", _locations); + PerfLocation::SetLocationsArray(_locations); + + return; +} + +PerfSequence::~PerfSequence() +{ + // Clean up the shared memory block + if(_shared_memory_block != nullptr) { + // `_shared_memory_block` is obtained via SharedMemoryBlock::get_instance(), + // so PerfSequence does not own it and must not delete it here. + // Treat the shared memory block as process-lifetime from this file. + _shared_memory_block = nullptr; + } + return; +} + +std::vector PerfSequence::GetAllSequenceNames() +{ + PERF_FUNC(__FUNCTION__); + + std::vector sequenceNames; + + for(uint32_t i = 0; i < MAX_SEQUENCE; i++) { + if(_sequences[i].name[0] != '\0') { + sequenceNames.push_back(std::string(_sequences[i].name)); + } + } + + return sequenceNames; +} + +bool PerfSequence::GetSequence(const char* sequenceName) +{ + PERF_FUNC(__FUNCTION__); + + bool retVal = false; + + // Check if we are already in the sequence + if(_current_sequence != nullptr) { + if(strncmp(_current_sequence->name, sequenceName, MAX_NAME_LEN) == 0) { + return true; + } + } + + // New sequence, reset the current location + SetCurrentLocation(nullptr); + + // Find the sequence + _shared_memory_block->lock(); + for(uint32_t i = 0; i < MAX_SEQUENCE; i++) { + if(strncmp(_sequences[i].name, sequenceName, MAX_NAME_LEN) == 0) { + _current_sequence = &_sequences[i]; + LOG(eTrace, "Found sequence %s\n", sequenceName); + retVal = true; + } + } + + if(!retVal) { + // Sequence not found + LOG(eTrace, "Failed to find sequence %s\n", sequenceName); + // Add the sequence + uint32_t i = 0; + for(i = 0; i < MAX_SEQUENCE; i++) { + if(_sequences[i].name[0] == '\0') { + strncpy(_sequences[i].name, sequenceName, MAX_NAME_LEN - 1); + _sequences[i].name[MAX_NAME_LEN - 1] = '\0'; // Force null termination + + LOG(eTrace, "Added sequence %s\n", _sequences[i].name); + _sequences[i].location_offset = INVALID_OFFSET; + // Initialize the timestamp circular buffer + CircularBuffer rootTimeStamps(&_sequences[i].timeStampCirBuffer[0], MAX_TIME_STAMPS); + rootTimeStamps.set_name(_sequences[i].name); + _current_sequence = &_sequences[i]; + retVal = true; + break; + } + LOG(eTrace, "Sequence %d %s\n", i, _sequences[i].name); + } + if(!retVal) { + // No more room for sequences + LOG(eError, "No more room for sequences, already allocated %d\n", i + 1); + } + } + + // Unlock the shared memory block + _shared_memory_block->unlock(); + + // Failed to find or add the sequence + if(!retVal) { + LOG(eError, "Failed to find or add sequence %s\n", sequenceName); + } + return retVal; +} + +bool PerfSequence::RemoveSequence(const char* sequenceName) +{ + PERF_FUNC(__FUNCTION__); + + bool retVal = false; + // Lock the shared memory block + _shared_memory_block->lock(); + + // Find the sequence + for(uint32_t i = 0; i < MAX_SEQUENCE; i++) { + if(strncmp(_sequences[i].name, sequenceName, MAX_NAME_LEN) == 0) { + _sequences[i].name[0] = '\0'; + + // Remove all locations + int32_t location_offset = _sequences[i].location_offset; + while(location_offset != INVALID_OFFSET) { + Location* location = _locations + location_offset; + int32_t next_offset = location->child_offset; + LOG(eTrace, "Removing location %s", location->name); + location->name[0] = '\0'; + location_offset = next_offset; + } + + _sequences[i].location_offset = INVALID_OFFSET; + + LOG(eTrace, "Removed sequence %s", sequenceName); + retVal = true; + } + } + + // Unlock the shared memory block + _shared_memory_block->unlock(); + + if(!retVal) { + LOG(eError, "Failed to find sequence %s to remove\n", sequenceName); + } + return retVal; +} + +bool PerfSequence::FindLocation(const char* locationName) +{ + PERF_FUNC(__FUNCTION__); + + bool retVal = false; + // Check if we have a sequence + if(_current_sequence == nullptr) { + LOG(eError, "No current sequence to find location %s\n", locationName); + return retVal; + } + + // Is this the child of the current location + if(_current_location != nullptr) { + if (strncmp(_current_location->GetChildName(), locationName, MAX_NAME_LEN) == 0) { + LOG(eTrace, "Found child location %s\n", locationName); + SetCurrentLocation(_current_location->GetLocationChildOffset()); + + retVal = true; + } + } + + if(!retVal) { + // Find the location in the sequence location chain + if(_current_sequence->location_offset != INVALID_OFFSET) { + Location* location = _locations + _current_sequence->location_offset; + while(location->name[0] != '\0') { + LOG(eTrace, "Comparing location %s with %s\n", location->name, locationName); + if(strncmp(location->name, locationName, MAX_NAME_LEN) == 0) { + LOG(eTrace, "Found location %s\n", locationName); + SetCurrentLocation(location); + retVal = true; + } + if(location->child_offset == INVALID_OFFSET) { + break; + } + location = _locations + location->child_offset; + } + } + } + + if(!retVal) { + LOG(eTrace, "Failed to find location %s\n", locationName); + } + return retVal; +} + +void PerfSequence::SetCurrentLocation(Location* location) +{ + if(_current_location != nullptr) { + delete _current_location; + _current_location = nullptr; + } + if(location != nullptr) { + _current_location = new PerfLocation(location); + } +} + +void PerfSequence::SetCurrentLocation(uint32_t locationOffset) +{ + if(_current_location != nullptr) { + delete _current_location; + _current_location = nullptr; + } + if(locationOffset != (uint32_t)INVALID_OFFSET) { + _current_location = new PerfLocation(locationOffset); + } +} + +bool PerfSequence::AddLocation(const char* locationName) +{ + PERF_FUNC(__FUNCTION__); + + bool retVal = false; + + // Check if we have a sequence + if(_current_sequence == nullptr) { + LOG(eError, "No current sequence to add location %s\n", locationName); + return retVal; + } + + // Add a new location + _shared_memory_block->lock(); + uint32_t i = 0; + for(i = 0; i < MAX_LOCATIONS; i++) { + if(_locations[i].name[0] == '\0') { + strncpy(_locations[i].name, locationName, MAX_NAME_LEN - 1); + _locations[i].name[MAX_NAME_LEN - 1] = '\0'; // Force null termination + + LOG(eWarning, "Adding location %s at index %ld\n", locationName, i); + _locations[i].sequence = _current_sequence; + _locations[i].count = 0; + _locations[i].parent_offset = INVALID_OFFSET; + _locations[i].child_offset = INVALID_OFFSET; + _locations[i].total = 0; + _locations[i].total_elapsed = 0; + _locations[i].min = 0; + _locations[i].max = 0; + + CircularBuffer circBuffer(&_locations[i].timeStampCirBuffer[0], MAX_TIME_STAMPS); + circBuffer.set_name(_locations[i].name); + // If this is the first location in the sequence set the starting location offset + if(_current_sequence->location_offset == INVALID_OFFSET) { + _current_sequence->location_offset = i; + LOG(eTrace, "Adding first location %s at offset %lu\n", locationName, _current_sequence->location_offset); + } + else { + // Find the last location in the sequence + Location* endLocation = _locations + _current_sequence->location_offset; + while(endLocation->child_offset != INVALID_OFFSET) { + endLocation = _locations + endLocation->child_offset; + } + _locations[i].parent_offset = (uint32_t)(((uint8_t*)endLocation - (uint8_t*)_locations)/sizeof(Location)); + endLocation->child_offset = i; + } + + SetCurrentLocation(&_locations[i]); + + retVal = true; + break; + } + } + _shared_memory_block->unlock(); + + if(!retVal) { + // No more room for locations + LOG(eError, "No more room for locations, already allocated %d\n", i + 1); + } + return retVal; +} + +uint64_t PerfSequence::GetTimeStampUS() +{ + struct timeval tv; + gettimeofday(&tv, NULL); + return (tv.tv_sec * 1000000) + tv.tv_usec; +} + +bool PerfSequence::RecordLocation(const char* locationName) +{ + PERF_FUNC(__FUNCTION__); + + bool retVal = false; + // Check if we have a sequence + if(_current_sequence == nullptr) { + LOG(eError, "No current sequence to record location %s\n", locationName); + return retVal; + } + + // Find the location + uint64_t timeStampUS = GetTimeStampUS(); + if(FindLocation(locationName)) { + retVal = true; + } + else { + // Add a new location + retVal = AddLocation(locationName); + } + + if(retVal) { + _shared_memory_block->lock(); + _current_location->AddTimeStamp(timeStampUS); + _shared_memory_block->unlock(); + + LOG(eTrace, "Recorded location %s\n", locationName); + } + else { + LOG(eError, "Failed to record location %s\n", locationName); + } + + return retVal; +} + +uint32_t PerfSequence::GetLocationsDepth() +{ + PERF_FUNC(__FUNCTION__); + + uint32_t count = 0; + // Check if we have a sequence + if(_current_sequence == nullptr) { + LOG(eError, "No current sequence to get location count\n"); + return count; + } + + // Count the locations + Location* location = _locations + _current_sequence->location_offset; + while(location != nullptr) { + count++; + if(location->child_offset == INVALID_OFFSET) { + break; + } + location = _locations + location->child_offset; + } + + return count; +} + +DataRecord* PerfSequence::GetDataRecord() +{ + PERF_FUNC(__FUNCTION__); + + // Check if we have a sequence + if(_current_sequence == nullptr) { + LOG(eError, "No current sequence to get data record\n"); + return nullptr; + } + + PerfLocation location(_current_sequence->location_offset); + return location.GetDataRecord(); +} + +uint32_t PerfSequence::GetRecordCount() +{ + PERF_FUNC(__FUNCTION__); + + uint32_t count = 0; + // Check if we have a sequence + if(_current_sequence == nullptr) { + LOG(eError, "No current sequence to get record count\n"); + return count; + } + + // Count the number of records in the sequence + if(_current_sequence->location_offset == INVALID_OFFSET) { + return count; + } + + + Location* location = _locations + _current_sequence->location_offset; + // Find the tail of the location chain + while(location->child_offset != INVALID_OFFSET) { + location = _locations + location->child_offset; + } + + count = location->count; + + return count; +} + +PerfSequence* PerfSequence::GetInstance() +{ + if(_perf_sequence == nullptr) { + LOG(eWarning, "Creating new PerfSequence instance\n"); + _perf_sequence = new PerfSequence(); + } + + if(_perf_sequence == nullptr) { + LOG(eError, "Failed to create PerfSequence instance\n"); + } + + return _perf_sequence; +} \ No newline at end of file diff --git a/src/rdk_perf_sequence.h b/src/rdk_perf_sequence.h new file mode 100644 index 0000000..e759e87 --- /dev/null +++ b/src/rdk_perf_sequence.h @@ -0,0 +1,75 @@ +/** +* Copyright 2024 Comcast Cable Communications Management, LLC +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +* SPDX-License-Identifier: Apache-2.0 +*/ +#ifndef __RDK_PERF_SEQUENCE_H__ +#define __RDK_PERF_SEQUENCE_H__ + +#include +#include +#include +#include + +#include +#include + +#include "rdk_perf_shm_block.h" +#include "rdk_perf_latency_data.h" + + + + +// Forward declarations +class PerfLocation; + +// PerfSequence class defined +#define PERFSEQUENCE 1 +class PerfSequence +{ +public: + PerfSequence(); + ~PerfSequence(); + + bool GetSequence(const char* sequenceName); + const char* GetName() { return _current_sequence != NULL ? _current_sequence->name : ""; }; + bool RemoveSequence(const char* sequenceName); + bool RecordLocation(const char* locationName); + uint32_t GetLocationsDepth(); + + DataRecord* GetDataRecord(); + uint32_t GetRecordCount(); + + std::vector GetAllSequenceNames(); + static PerfSequence* GetInstance(); + +private: + bool FindLocation(const char* locationName); + bool AddLocation(const char* locationName); + void SetCurrentLocation(Location* location); + void SetCurrentLocation(uint32_t locationOffset); + uint64_t GetTimeStampUS(); + + +private: + SharedMemoryBlock* _shared_memory_block; + uint64_t _memory_block_size; + Sequence* _sequences; + Location* _locations; + Sequence* _current_sequence; + PerfLocation* _current_location; +}; + +#endif // __RDK_PERF_SEQUENCE_H__ \ No newline at end of file diff --git a/src/rdk_perf_shm_block.cpp b/src/rdk_perf_shm_block.cpp new file mode 100644 index 0000000..3da5d75 --- /dev/null +++ b/src/rdk_perf_shm_block.cpp @@ -0,0 +1,164 @@ +/** +* Copyright 2024 Comcast Cable Communications Management, LLC +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +* SPDX-License-Identifier: Apache-2.0 +*/ + +// Aided by CoPilot :-) + +#include +#include +#include +#include +#include +#include + +#include "rdk_perf_shm_block.h" +#include "rdk_perf_logging.h" // for LOG macro + +#define ERR_LOG(msg) LOG(eError, "%s - %d: %s\n", msg, errno, strerror(errno)); + +#define INITIALIZATION_UUID "22b90a50-fccc-4fec-8314-fabd059a27e6" +#define INITIALIZATION_UUID_LEN 37 +static uint8_t initialization_uuid_binary[] = {0x22, 0xb9, 0x0a, 0x50, 0xfc, 0xcc, 0x4f, 0xec, 0x83, 0x14, 0xfa, 0xbd, 0x05, 0x9a, 0x27, 0xe6}; +#define INITIALIZATION_UUID_BINARY_LEN sizeof(initialization_uuid_binary) + + +static SharedMemoryBlock* _shared_memory_block = nullptr; + +SharedMemoryBlock::SharedMemoryBlock(uint64_t maxDataSize) +: _initialized(false) +, _shm_fd(-1) +, _shared_block(nullptr) +{ + _data_size = sizeof(GlobalStorage) + maxDataSize; + _initialized = initialize(); +} + + +bool SharedMemoryBlock::initialize() +{ + bool bInitialize_memory = false; + + // Create the shared memory segment exclusively. If it already exists, return an error + _shm_fd = shm_open(SHARED_MEMORY_NAME, O_CREAT | O_EXCL | O_RDWR, 0666); + if (_shm_fd == -1) { + ERR_LOG("shm already created"); + // Try to open the existing shared memory segment + _shm_fd = shm_open(SHARED_MEMORY_NAME, O_RDWR, 0666); + if (_shm_fd == -1) { + ERR_LOG("shm_open"); + return false; + } + } + else { + // We are the first to create the shared memory segment and can now initialize the memory + bInitialize_memory = true; + + // Set the size of the shared memory segment + if (ftruncate(_shm_fd, _data_size) == -1) { + ERR_LOG("ftruncate"); + return false; + } + } + + // Map the shared memory segment into the process's address space + _shared_block = static_cast(mmap(nullptr, _data_size, PROT_READ | PROT_WRITE, MAP_SHARED, _shm_fd, 0)); + if (_shared_block == MAP_FAILED) { + ERR_LOG("mmap"); + return false; + } + + if(bInitialize_memory) { + // Initialize the shared memory segment if it's the first time + memset(_shared_block, 0, _data_size); + if (sem_init(&_shared_block->semaphore, 1, 1) == -1) { + ERR_LOG("sem_init"); + return false; + } + memcpy(_shared_block->initialization_uuid, initialization_uuid_binary, INITIALIZATION_UUID_BINARY_LEN); + } + else { + // Not the first proces in the shared memory segment + while(memcmp(&_shared_block->initialization_uuid[0], initialization_uuid_binary, INITIALIZATION_UUID_BINARY_LEN) != 0) { + ERR_LOG("Shared memory segment not initialized"); + // wait a bit for the shared memory segment to be initialized + usleep(1000); + + static int nWaitCount = 0; + if(nWaitCount++ > 1000) { + // Waited too long for the shared memory segment to be initialized + LOG(eError, "Shared memory segment not initialized and waited too long\n"); + return false; + } + } + } + + LOG(eWarning, "Shared memory segment initialized data size %lu, address %p\n", _data_size, _shared_block); + lock(); + _shared_block->attached_intances++; + unlock(); + + return true; +} + +SharedMemoryBlock::~SharedMemoryBlock() +{ + bool bDelete = false; + + // Decrement the number of attached instances + lock(); + _shared_block->attached_intances--; + if(_shared_block->attached_intances == 0) { + bDelete = true; + } + unlock(); + + if(bDelete) { + // Destroy the semaphore + sem_destroy(&_shared_block->semaphore); + // Last process in the shared memory segment + // Unlink the shared memory segment + shm_unlink(SHARED_MEMORY_NAME); + + _shared_memory_block = nullptr; + } + + // Unmap the shared memory segment + munmap(_shared_block, _data_size); + + // Close the shared memory segment + close(_shm_fd); +} + +bool SharedMemoryBlock::lock() +{ + return sem_wait(&_shared_block->semaphore) == 0; +} + +bool SharedMemoryBlock::unlock() +{ + return sem_post(&_shared_block->semaphore) == 0; +} + +SharedMemoryBlock* SharedMemoryBlock::get_instance(uint64_t maxDataSize) +{ + if(_shared_memory_block == nullptr) { + LOG(eWarning, "Creating new SharedMemoryBlock instance\n"); + _shared_memory_block = new SharedMemoryBlock(maxDataSize); + } + + return _shared_memory_block; +} diff --git a/src/rdk_perf_shm_block.h b/src/rdk_perf_shm_block.h new file mode 100644 index 0000000..8c758bd --- /dev/null +++ b/src/rdk_perf_shm_block.h @@ -0,0 +1,63 @@ +/** +* Copyright 2024 Comcast Cable Communications Management, LLC +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +* SPDX-License-Identifier: Apache-2.0 +*/ + +#ifndef __RDK_PERF_SHARED_MEMORY_BLOCK_H__ +#define __RDK_PERF_SHARED_MEMORY_BLOCK_H__ + +#include +#include +#include +#include +#include + +#define SHARED_MEMORY_NAME "/rdkperf_sharedmemory_block" +#define SEMAPHORE_NAME "/rdkperf_shmblock_sem" + +struct GlobalStorage { + uint8_t initialization_uuid[16]; + uint16_t attached_intances; + sem_t semaphore; + // Data + uint8_t data[0]; +}; + +class SharedMemoryBlock { +public: + SharedMemoryBlock(uint64_t maxDataSize); + ~SharedMemoryBlock(); + + bool lock(); + bool unlock(); + + void* get_data() { return &_shared_block->data[0]; } + + // Static methods + static SharedMemoryBlock* get_instance(uint64_t maxDataSize); + +private: + bool initialize(); + + bool _initialized; + int _shm_fd; + uint64_t _data_size; + GlobalStorage* _shared_block; + +}; + + +#endif // __RDK_PERF_SHARED_MEMORY_BLOCK_H__ \ No newline at end of file diff --git a/test/perftest.cpp b/test/perftest.cpp index c363466..8bedc4c 100644 --- a/test/perftest.cpp +++ b/test/perftest.cpp @@ -28,8 +28,13 @@ #include "rdk_perf.h" #include "rdk_perf_logging.h" +#include -// Uint Tests prototype +//#define DO_UNIT_TESTS +//#define DO_THREAD_TESTS +#define DO_LATENCY_TESTS + +// Unit Tests prototype void unit_tests(); void unit_tests_c(); @@ -114,6 +119,73 @@ void test_inline() return; } +// Declared in unit_tests.cpp +void do_work(uint32_t timeoutMS); + +void test_latency_4() +{ + LOG(eTrace, "END\n"); + RDKLatency("test_sequence", __FUNCTION__); +} + +void test_latency_3() +{ + LOG(eTrace, "\n"); + RDKLatency("test_sequence", __FUNCTION__); + do_work(1); + test_latency_4(); +} + +void test_latency_2() +{ + LOG(eTrace, "\n"); + // fork the process and wait for child to complete + RDKLatency("test_sequence", __FUNCTION__); + +#if 1 + LOG(eTrace, "Forking child process\n"); + pid_t child_pid = fork(); + if(child_pid == 0) { + RDKLatency("test_sequence", "child_start"); + + LOG(eTrace, "Child process %d created\n", getpid()); + /* This is done by the child process. */ + do_work(1); + RDKLatency("test_sequence", "child_exit"); + LOG(eTrace, "Child process %d exiting\n", getpid()); + _exit(0); + } + else { + // Parent process + int status; + LOG(eTrace, "Parent process %d waiting for child %d\n", getpid(), child_pid); + pid_t killed = waitpid(child_pid, &status, 0); + if(killed == -1) { + LOG(eError, "Failed to wait for child process\n"); + } + LOG(eTrace, "Child process %d <-> %d completed\n", killed, child_pid); + } + + test_latency_3(); +#endif +} + +void test_latency_1() +{ + LOG(eTrace, "\n"); + RDKLatency("test_sequence", __FUNCTION__); + do_work(1); + test_latency_2(); +} + +void test_latency() +{ + LOG(eTrace, "\n"); + RDKLatency("test_sequence", __FUNCTION__); + do_work(1); + test_latency_1(); +} + int main(int argc, char *argv[]) { LOG(eWarning, "Enter test app %s\n", __DATE__); @@ -138,9 +210,11 @@ int main(int argc, char *argv[]) // } // sleep(1); #endif +#ifdef DO_UNIT_TESTS // Perform Unit tests unit_tests(); //unit_tests_c(); +#endif // DO_UNIT_TESTS #ifdef DO_THREAD_TESTS pthread_t threadId1; @@ -156,10 +230,21 @@ int main(int argc, char *argv[]) #endif #ifdef DO_INLINE_TESTS - for(int idx = 0; idx < 1000; idx++) { + for(int idx = 0; idx < 10; idx++) { test_inline(); } #endif + +#ifdef DO_LATENCY_TESTS + for(int idx = 0; idx < 499; idx++) { + // if(idx % 250 == 0) { + // LOG(eWarning, "Running latency test %d\n", idx); + // } + test_latency(); + } + RDKLatencyReport("test_sequence"); +#endif // DO_LATENCY_TESTS + // Don't need to make this call as the process terminate handler will // call the RDKPerf_ReportProcess() function // RDKPerf_ReportProcess(getpid()); diff --git a/test/unit_tests.cpp b/test/unit_tests.cpp index 4d1d9e4..06f2e92 100644 --- a/test/unit_tests.cpp +++ b/test/unit_tests.cpp @@ -43,7 +43,15 @@ void timer_sleep(uint32_t timeMS) return; } - +void do_work_subtask() +{ + RDKPerf perf (__FUNCTION__); + int i = 0; + while(i < 10000) { + i++; + } + return; +} void do_work(uint32_t timeMS) { struct timeval timeStamp; @@ -52,6 +60,7 @@ void do_work(uint32_t timeMS) uint64_t inital_time = (uint64_t)(((uint64_t)timeStamp.tv_sec * 1000000) + timeStamp.tv_usec); uint64_t elapsed_time = inital_time; while(elapsed_time - inital_time < (timeMS * 1000)) { + do_work_subtask(); gettimeofday(&timeStamp, NULL); elapsed_time = (uint64_t)(((uint64_t)timeStamp.tv_sec * 1000000) + timeStamp.tv_usec); } @@ -96,7 +105,7 @@ void record_with_threshold(uint32_t timeMS) { int idx = 0; while(idx < 1) { - RDKPerf perf (__FUNCTION__, timeMS/2); + RDKPerf perf (__FUNCTION__, (static_cast(timeMS) * 1000) / 2); do_work(timeMS); @@ -106,6 +115,30 @@ void record_with_threshold(uint32_t timeMS) return; } +int test_no_cpu() +{ + static int nCount = 0; + RDKPerf perf(__FUNCTION__); + nCount++; + return nCount; +} +void perf_node_time_no_cpu() +{ + RDKPerf perf(__FUNCTION__); + + int nIdx = 0; + while(nIdx < 100000) { + nIdx = test_no_cpu(); + } + return; +} + +void perf_node_time_with_cpu() +{ + RDKPerf perf(__FUNCTION__); + return; +} + // Unit Tests entry point #define DELAY_SHORT 2 * 1000 // 2s #define DELAY_LONG 10 * 1000 // 2s @@ -114,6 +147,11 @@ void unit_tests() { LOG(eWarning, "---------------------- Unit Tests START --------------------\n"); + perf_node_time_no_cpu(); + RDKPerf_ReportProcess(getpid()); + + perf_node_time_with_cpu(); + timer_sleep(DELAY_SHORT); timer_work(DELAY_SHORT);