Skip to content

Latency#16

Open
DouglasAdler wants to merge 18 commits intomainfrom
latency
Open

Latency#16
DouglasAdler wants to merge 18 commits intomainfrom
latency

Conversation

@DouglasAdler
Copy link
Copy Markdown
Contributor

No description provided.

DouglasAdler and others added 7 commits March 29, 2023 19:54
Signed-off-by: Douglas Adler <douglas.adler@yahoo.com>
Signed-off-by: Douglas Adler <douglas.adler@yahoo.com>
* Development (#10)

* Test for crash on exit

Signed-off-by: Douglas Adler <douglas.adler@yahoo.com>

* Test for crash on exit

Signed-off-by: Douglas Adler <douglas.adler@yahoo.com>

---------

Signed-off-by: Douglas Adler <douglas.adler@yahoo.com>

* Initial latency metrics

Signed-off-by: Douglas Adler <douglas.adler@yahoo.com>

---------

Signed-off-by: Douglas Adler <douglas.adler@yahoo.com>
and elapsed time metrics.

Signed-off-by: Douglas Adler <douglas_adler2@comcast.com>
Signed-off-by: Douglas Adler <douglas_adler2@comcast.com>
* Framework for memory tracking

Signed-off-by: Adler, Douglas <Douglas_Adler2+comcast@comcast.com>
* Development (#10)

* Test for crash on exit

Signed-off-by: Douglas Adler <douglas.adler@yahoo.com>

* Test for crash on exit

Signed-off-by: Douglas Adler <douglas.adler@yahoo.com>

---------

Signed-off-by: Douglas Adler <douglas.adler@yahoo.com>

* Deploy cla action

* Deploy cla action

---------

Signed-off-by: Douglas Adler <douglas.adler@yahoo.com>
Co-authored-by: rdkcmf <github@code.rdkcentral.com>
Co-authored-by: Alan Ryan <20208488+Alan-Ryan@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 11, 2026 19:57
Copy link
Copy Markdown
Contributor Author

@DouglasAdler DouglasAdler left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add latency to rdkperf for tracking an event across processes.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new cross-scope “latency sequence” measurement capability (recording ordered locations and reporting aggregated timings), backed by shared memory, and adds perf-test / unit-test hooks to exercise it.

Changes:

  • Added latency tracking infrastructure: shared-memory block, sequence/location model, and a circular buffer for timestamps.
  • Added a new public C API for latency (RDKLatency*) and a perf test scenario that exercises nested calls + fork.
  • Updated a few existing components (msgqueue name buffer sizing, logging env var handling, timer shutdown behavior, minor test tweaks).

Reviewed changes

Copilot reviewed 23 out of 23 changed files in this pull request and generated 36 comments.

Show a summary per file
File Description
test/unit_tests.cpp Adds new perf timing subtask + node timing loops; adjusts threshold units.
test/perftest.cpp Adds latency test sequence (incl. fork/wait) and enables latency tests via compile-time flag.
src/rdk_perf_shm_block.h Introduces shared-memory block header/metadata definition and API.
src/rdk_perf_shm_block.cpp Implements shared-memory creation/attach, mapping, and semaphore locking.
src/rdk_perf_sequence.h Declares the shared-memory-backed sequence manager API.
src/rdk_perf_sequence.cpp Implements sequence creation/lookup, location chaining, and recording.
src/rdk_perf_location.h Declares PerfLocation and exposes timestamp/record APIs.
src/rdk_perf_location.cpp Implements timestamp storage, aggregation, parent/child diff calculation.
src/rdk_perf_latency_data.h Adds shared-memory data structures for sequences/locations/circular buffers.
src/rdk_perf_circularbuffer.h Declares a circular buffer used for timestamp storage.
src/rdk_perf_circularbuffer.cpp Implements circular buffer operations and logging.
src/rdk_perf_msgqueue.h Renames message name length macro to MAX_MSG_NAME_LEN.
src/rdk_perf_msgqueue.cpp Updates buffer-size usage in message send paths.
src/rdk_perf_logging.cpp Changes env var name for verbose logging and adds debug prints.
src/rdk_perf_clock.cpp Suppresses a log line when getrusage returns zeros.
rdkperf/rdk_perf.h Exposes latency API through the main public header.
rdkperf/rdk_perf.cpp Adjusts shutdown order for the timer thread/task and disables exit reporting.
rdkperf/rdk_perf_latency.h Adds the public C latency API header.
rdkperf/rdk_latency.cpp Implements RDKLatency* calls using PerfSequence.
rdkperf/rdk_mem_tracker.h Adds a memory tracker public header.
rdkperf/rdk_mem_tracker.cpp Adds memory tracker implementation + C wrapper functions.
rdkperf/Makefile Adds an $ORIGIN rpath for runtime library resolution.
.github/workflows/cla.yml Adds a CLA workflow using rdkcentral/cmf-actions.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread test/unit_tests.cpp Outdated
Comment thread test/unit_tests.cpp
Comment thread test/unit_tests.cpp
Comment thread test/unit_tests.cpp Outdated
Comment thread test/perftest.cpp
Comment thread src/rdk_perf_shm_block.cpp Outdated
Comment thread src/rdk_perf_shm_block.cpp
Comment thread src/rdk_perf_sequence.cpp
Comment thread src/rdk_perf_sequence.cpp Outdated
Comment thread src/rdk_perf_location.cpp
DouglasAdler and others added 2 commits April 11, 2026 20:25
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 11, 2026 20:28
DouglasAdler and others added 2 commits April 11, 2026 15:30
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated 25 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/rdk_perf_shm_block.h
Comment thread src/rdk_perf_shm_block.cpp
Comment on lines +149 to +157
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;
}
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SharedMemoryBlock is managed as a process-wide singleton (_shared_memory_block), but the destructor does not reset _shared_memory_block to nullptr. If any code deletes the singleton (e.g., via a owning class destructor) and later calls get_instance(), it will return a dangling pointer. Consider either (a) making get_instance() return a non-owning pointer that is never deleted, or (b) adding explicit lifetime management + nulling the static on destruction.

Copilot uses AI. Check for mistakes.
Comment on lines +39 to +47
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;
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CircBufferObject::records is declared as records[sizeof(CircBufferRecord) * MAX_TIME_STAMPS], which makes the array length a byte-count multiple (e.g., 400 elements) instead of MAX_TIME_STAMPS elements. This inflates CircBufferObject (and therefore Sequence/Location) unexpectedly and breaks assumptions in code that treats maxRecords as the array capacity. It should be sized as records[MAX_TIME_STAMPS].

Copilot uses AI. Check for mistakes.
Comment on lines +39 to +56
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;
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CircBufferObject includes char* name, and Location/Sequence structs (and their circular buffers) are stored in shared memory. Persisting a process-local pointer in shared memory is unsafe across processes and will become invalid when another process maps the segment at a different address. Consider removing the pointer from the shared-memory layout (or storing a fixed-size name or an offset within the SHM region).

Copilot uses AI. Check for mistakes.
Comment thread test/unit_tests.cpp
Comment thread src/rdk_perf_sequence.cpp
Comment on lines +58 to +63
typedef struct Location_s
{
char name[MAX_NAME_LEN];
Sequence* sequence;
uint8_t timeStampCirBuffer[sizeof(CircBufferObject)];
uint32_t count;
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Location stores Sequence* sequence, but Location/Sequence are placed in shared memory. A raw pointer to a SHM-mapped object is not stable across processes (different mapping base addresses), so sequence will be invalid when read from another process. Consider storing a sequence index/offset instead of a pointer, and resolve it relative to the mapped base in each process.

Copilot uses AI. Check for mistakes.
Comment thread src/rdk_perf_sequence.cpp Outdated
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);
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

strncpy(..., MAX_NAME_LEN) does not guarantee NUL-termination when the input is length >= MAX_NAME_LEN. Since these names are later treated as C strings (e.g., logged/compared), this can lead to unterminated strings. Consider using snprintf or explicitly setting name[MAX_NAME_LEN-1] = '\0' after strncpy.

Suggested change
strncpy(_sequences[i].name, sequenceName, MAX_NAME_LEN);
strncpy(_sequences[i].name, sequenceName, MAX_NAME_LEN - 1);
_sequences[i].name[MAX_NAME_LEN - 1] = '\0';

Copilot uses AI. Check for mistakes.
Comment thread src/rdk_perf_sequence.cpp Outdated
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 12, 2026 18:24
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated 9 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


typedef struct CircBufferObject_s
{
CircBufferRecord records[sizeof(CircBufferRecord) * MAX_TIME_STAMPS];
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CircBufferObject::records is declared as CircBufferRecord records[sizeof(CircBufferRecord) * MAX_TIME_STAMPS], which allocates (sizeof(record)*MAX) records rather than MAX records. This mismatches how the buffer is indexed (0..maxRecords-1) and inflates the shared-memory layout. It should likely be records[MAX_TIME_STAMPS] (or change the type to a byte array if you intended raw storage).

Suggested change
CircBufferRecord records[sizeof(CircBufferRecord) * MAX_TIME_STAMPS];
CircBufferRecord records[MAX_TIME_STAMPS];

Copilot uses AI. Check for mistakes.
Comment thread src/rdk_perf_sequence.cpp Outdated
Comment on lines +185 to +190
Location* location = _locations + _sequences[i].location_offset;
while(location != nullptr) {
Location* next = _locations + location->child_offset;
LOG(eTrace, "Removing location %s", location->name);
location->name[0] = '\0';
location = next;
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RemoveSequence() can form invalid pointers when location_offset / child_offset is INVALID_OFFSET (-1): _locations + (-1) is UB. Add INVALID_OFFSET guards before computing location and before stepping to next.

Suggested change
Location* location = _locations + _sequences[i].location_offset;
while(location != nullptr) {
Location* next = _locations + location->child_offset;
LOG(eTrace, "Removing location %s", location->name);
location->name[0] = '\0';
location = next;
auto location_offset = _sequences[i].location_offset;
while(location_offset != INVALID_OFFSET) {
Location* location = _locations + location_offset;
auto next_offset = location->child_offset;
LOG(eTrace, "Removing location %s", location->name);
location->name[0] = '\0';
location_offset = next_offset;

Copilot uses AI. Check for mistakes.
Comment thread src/rdk_perf_sequence.cpp Outdated
// Unlock the shared memory block
_shared_memory_block->unlock();

LOG(eError, "Failed to find sequence %s ro remove\n", sequenceName);
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This error log runs unconditionally, even when the sequence was successfully removed, which will mislead operators and tests. Only log this message when retVal is false (and fix the typo "ro remove" -> "to remove").

Suggested change
LOG(eError, "Failed to find sequence %s ro remove\n", sequenceName);
if(!retVal) {
LOG(eError, "Failed to find sequence %s to remove\n", sequenceName);
}

Copilot uses AI. Check for mistakes.
Comment thread src/rdk_perf_sequence.cpp
Comment on lines +301 to +305
_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
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When creating a new location, the circular buffer backing storage (timeStampCirBuffer) isn’t reset, and the CircularBuffer constructor currently does not clear head/tail/currentSize/records. This can leave new locations with garbage circular-buffer state. Ensure new buffers are zeroed and initialized (use CircularBuffer::initialize() for new allocations).

Copilot uses AI. Check for mistakes.
Comment thread src/rdk_perf_sequence.cpp
Comment on lines +388 to +392
// Count the locations
Location* location = _locations + _current_sequence->location_offset;
while(location != nullptr) {
count++;
if(location->child_offset == INVALID_OFFSET) {
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetLocationsDepth() does not guard against _current_sequence->location_offset == INVALID_OFFSET, so it can compute _locations + (-1) and dereference it. Return 0 early when location_offset is invalid.

Copilot uses AI. Check for mistakes.
Comment thread src/rdk_perf_sequence.cpp
LOG(eError, "No current sequence to get data record\n");
return nullptr;
}

Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetDataRecord() constructs PerfLocation from _current_sequence->location_offset without checking INVALID_OFFSET, which can dereference out-of-bounds memory for a sequence with no locations. Guard and return nullptr (or an empty record) when location_offset is invalid.

Suggested change
if(_current_sequence->location_offset == INVALID_OFFSET) {
LOG(eError, "No valid location to get data record\n");
return nullptr;
}

Copilot uses AI. Check for mistakes.
Comment thread rdkperf/rdk_latency.cpp
Comment on lines +95 to +99
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;
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RDKLatencyReport() prints a heap-allocated DataRecord chain returned by GetDataRecord() but never frees it (and advances pData so the original pointer is lost). This leaks on every report. Keep the root pointer and delete/free the chain after printing (or return an owning smart pointer/value type).

Copilot uses AI. Check for mistakes.
Comment on lines +72 to +75
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;
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Several log statements use [%s] with circBuffer->name (e.g., in the FULL-buffer log). If name hasn’t been set (or is an invalid SHM pointer), passing it to %s is undefined and can crash. Guard against null/invalid names (or avoid storing pointers in SHM).

Copilot uses AI. Check for mistakes.
Comment thread src/rdk_perf_location.cpp
Comment on lines +384 to +387
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;
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rootDiff is a uint64_t, but it’s logged with %lf (double) in LOG(eTrace, "Root timestamp ... root diff %lf"), which is undefined behavior. Use the correct integer format (or cast to double if that’s the intent).

Copilot uses AI. Check for mistakes.
DouglasAdler and others added 2 commits April 12, 2026 19:16
and semaphore management in `PerfSequence` and `SharedMemoryBlock`.

Signed-off-by: Douglas Adler <douglas_adler2@comcast.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 12, 2026 19:24
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated 7 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/rdk_perf_location.cpp
Comment on lines +461 to +462
uint64_t timeStamp = 0;
return _timeStamps.pop(count, timeStamp);
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RemoveTimeStamp(count) calls _timeStamps.pop(...), but pop() removes the oldest (tail) entry and does not remove a specific key. This breaks the logic in AddTimeStamp() that tries to remove the matching parent timestamp for the same count. Add an erase-by-key API (or change storage) so the timestamp for the requested count is removed deterministically.

Suggested change
uint64_t timeStamp = 0;
return _timeStamps.pop(count, timeStamp);
CircBufferObject tempBufferMemory = {};
CircularBuffer retainedTimeStamps(&tempBufferMemory, MAX_TIME_STAMPS);
bool removed = false;
uint32_t poppedCount = 0;
uint64_t timeStamp = 0;
while(_timeStamps.pop(poppedCount, timeStamp)) {
if(!removed && poppedCount == count) {
removed = true;
LOG(eTrace, "Removed timestamp %lu for count %u\n", timeStamp, count);
continue;
}
if(!retainedTimeStamps.push(poppedCount, timeStamp)) {
LOG(eError, "Failed to retain timestamp %lu for count %u while removing count %u\n", timeStamp, poppedCount, count);
return false;
}
}
while(retainedTimeStamps.pop(poppedCount, timeStamp)) {
if(!_timeStamps.push(poppedCount, timeStamp)) {
LOG(eError, "Failed to restore timestamp %lu for count %u after removing count %u\n", timeStamp, poppedCount, count);
return false;
}
}
if(!removed) {
LOG(eError, "Failed to find timestamp for count %u\n", count);
}
return removed;

Copilot uses AI. Check for mistakes.
Comment thread src/rdk_perf_sequence.cpp
Comment on lines +33 to +52
// 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()
{
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These functions are declared static with constructor/destructor attributes, but the definitions are non-static. That’s a linkage mismatch and will typically fail to compile (“static declaration follows non-static” / multiple linkage). Make the definitions static to match the forward declarations (or drop static from both).

Copilot uses AI. Check for mistakes.
Comment on lines +121 to +136
// 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;
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The attached_intances decrement and == 0 decision are protected by the semaphore, but the actual cleanup (sem_destroy() / shm_unlink()) happens after unlocking. Another process can attach/detach in between, leading to premature destruction/unlink. Keep the lock held across decrement+check+final cleanup (or use a more robust refcounting strategy).

Suggested change
// 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;
// Decrement the number of attached instances and, if this is the
// last attached process, perform final cleanup while still holding
// the semaphore so no other process can attach in between.
lock();
_shared_block->attached_intances--;
if(_shared_block->attached_intances == 0) {
bDelete = true;
// Destroy the semaphore before unmapping the shared memory.
// Do not unlock after this, because the semaphore is no longer valid.
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;
} else {
unlock();

Copilot uses AI. Check for mistakes.
Comment on lines +94 to +105
// 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;
}
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The wait loop uses static int nWaitCount = 0; inside SharedMemoryBlock::initialize(). Because it’s static, a previous initialization attempt can leave nWaitCount high and cause subsequent attempts to fail immediately. Make this a normal local counter (and consider logging less aggressively inside the spin loop to avoid flooding logs).

Copilot uses AI. Check for mistakes.
Comment thread src/rdk_perf_msgqueue.cpp
Comment on lines 112 to 123
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)));
memcpy((void*)msg.msg_data.entry.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.entry.szName, (void*)szName, MIN((size_t)(MAX_MSG_NAME_LEN - 1), strlen(szName)));
break;
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the eThreshold and eExit cases, the code writes the name into msg.msg_data.entry.szName, but the active union members are threshold and exit. This will populate the wrong struct and can corrupt the message contents. Write into msg.msg_data.threshold.szName / msg.msg_data.exit.szName respectively.

Copilot uses AI. Check for mistakes.
Comment thread src/rdk_perf_logging.cpp
Comment on lines 61 to 71
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");
fprintf(stdout, "[DEBUG] 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) {
fprintf(stdout, "[DEBUG] Setting s_VerboseLog to true\n");
s_VerboseLog = true;
LOG(eWarning, "Enabling RDKPERF extended logging %d", s_VerboseLog);
} else if(env_log_level != NULL) {
fprintf(stdout, "[DEBUG] env_log_level='%s' did not match 'true'\n", env_log_level);
}
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LogModuleInit() unconditionally prints [DEBUG] ... messages to stdout during library initialization. This is noisy for consumers and bypasses the project’s logging controls. Prefer using the existing LOG(...) macro (respecting verbosity) or guard these prints behind the env var / a compile-time debug flag.

Copilot uses AI. Check for mistakes.
Comment on lines +41 to +48
SharedMemoryBlock::SharedMemoryBlock(uint64_t maxDataSize)
: _initialized(false)
, _shm_fd(-1)
, _shared_block(nullptr)
{
_data_size = sizeof(GlobalStorage) + maxDataSize;
_initialized = initialize();
}
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SharedMemoryBlock::get_instance() always returns the singleton even if SharedMemoryBlock::initialize() failed (constructor sets _initialized=false but callers aren’t prevented from using _shared_block==nullptr). This can lead to null dereferences in lock()/get_data() downstream. Consider making get_instance() return nullptr on initialization failure (and/or delete the partially-constructed singleton).

Copilot uses AI. Check for mistakes.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 12, 2026 19:36
Signed-off-by: Douglas Adler <douglas_adler2@comcast.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated 11 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/rdk_perf_msgqueue.cpp
Comment on lines 119 to 123
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;
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the eExit case the name is copied into msg.msg_data.entry.szName rather than msg.msg_data.exit.szName. That leaves exit.szName empty and overwrites unrelated union storage. Copy into the exit member for exit messages.

Copilot uses AI. Check for mistakes.
Comment thread src/rdk_perf_location.cpp
Comment on lines +295 to +299
const char* retVal = "\n";

if(_location->child_offset == 0) {
return retVal;
}
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetChildName() treats child_offset == 0 as “no child”, but offsets use INVALID_OFFSET (-1) as the sentinel elsewhere. A valid first child at index 0 would be incorrectly hidden. Check for INVALID_OFFSET instead of 0 (and consider returning an empty string rather than a newline).

Copilot uses AI. Check for mistakes.
Comment thread src/rdk_perf_location.cpp
Comment on lines +305 to +308
if(_location->child_offset != 0) {
Location* child = (Location*)((uint8_t*)_locations + _location->child_offset);
retVal = child->name;
}
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetChildName() computes the child pointer using byte arithmetic ((uint8_t*)_locations + child_offset), but child_offset is used as an index everywhere else (e.g., it’s assigned i in PerfSequence::AddLocation). This will point at the wrong address. Use _locations + child_offset (or multiply by sizeof(Location) if the offset is intended to be bytes).

Copilot uses AI. Check for mistakes.
Comment thread src/rdk_perf_location.cpp
Comment on lines +319 to +328
// 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;
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetDataRecord() divides by _location->count when computing average and average_elapsed. When count == 0 this is a divide-by-zero (NaN/inf) and can propagate into reporting. Guard the division (e.g., return 0 averages when count is 0).

Suggested change
// 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;
// Copy the count
retVal->count = _location->count;
// Copy the total time
if(retVal->count > 0) {
retVal->average = (double)(_location->total / (double)(retVal->count));
// Copy the total elapsed time
retVal->average_elapsed = (double)(_location->total_elapsed / (double)(retVal->count));
} else {
retVal->average = 0.0;
retVal->average_elapsed = 0.0;
}
// Copy the min time
retVal->min = (double)_location->min;
// Copy the max time
retVal->max = (double)_location->max;

Copilot uses AI. Check for mistakes.
Comment thread src/rdk_perf_sequence.cpp
Comment on lines +92 to +97
// 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;
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PerfSequence::~PerfSequence() does not delete _current_location, which is allocated with new in SetCurrentLocation(...). This leaks per-instance memory (and may leak repeatedly if sequences are recreated). Ensure _current_location is deleted (or convert it to a value type / smart pointer).

Suggested change
// 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;
if(_current_location != nullptr) {
delete _current_location;
_current_location = nullptr;
}
// 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;

Copilot uses AI. Check for mistakes.
Comment thread rdkperf/rdk_latency.cpp
Comment on lines +96 to +100
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) {
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RDKLatencyReport() walks a DataRecord chain returned by PerfSequence::GetDataRecord(), but that chain is allocated with new (recursively) and is never freed. Repeated reporting will leak memory. Add an explicit cleanup/free routine for the DataRecord tree (or return an owning smart pointer / value type) after printing.

Copilot uses AI. Check for mistakes.
Comment thread test/perftest.cpp
Comment on lines +239 to +243
for(int idx = 0; idx < 499; idx++) {
// if(idx % 250 == 0) {
// LOG(eWarning, "Running latency test %d\n", idx);
// }
test_latency();
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The latency test loop runs 499 iterations and each iteration can fork() (via test_latency_2). This can make the test binary very slow and resource-intensive by default. Consider lowering the default iterations / removing the fork from the hot loop, and making the iteration count configurable via argv/env.

Copilot uses AI. Check for mistakes.
Comment on lines +70 to +74
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);
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CircularBuffer::push/pop/peek/find emit eWarning logs for normal control-flow states like full/empty/not-found (e.g., buffer full in push() will happen routinely once more than MAX_TIME_STAMPS are recorded, especially for root timestamps that are never popped). Since warnings are not gated by s_VerboseLog, this can flood logs and significantly slow down recording. Consider downgrading these to eTrace or gating them behind the extended-logging flag.

Copilot uses AI. Check for mistakes.
Comment thread src/rdk_perf_sequence.cpp
Comment on lines +81 to +84
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);
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PerfSequence logs the shared-memory addresses using LOG(eError, ...) during normal construction. eError messages are always emitted and may alarm users / spam logs even in healthy runs. Consider lowering these to eTrace/eWarning (or removing) unless this indicates an actual error condition.

Suggested change
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);
LOG(eTrace, "Sequence array at %p\n", _sequences);
// Get the pointer to the location array
_locations = (Location*) ((uint8_t*) _sequences + sizeof(Sequence) * MAX_SEQUENCE);
LOG(eTrace, "Location array at %p\n", _locations);

Copilot uses AI. Check for mistakes.
Comment on lines +55 to +61
// 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) {
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When shm_open(..., O_CREAT | O_EXCL, ...) fails, the code logs ERR_LOG("shm already created") at error level before checking errno. For the expected EEXIST case this will produce an error log on every non-first attach. Consider only logging at eTrace/eWarning for EEXIST, and reserve eError for unexpected errno values.

Suggested change
// 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) {
// Create the shared memory segment exclusively. If it already exists, open the existing segment.
_shm_fd = shm_open(SHARED_MEMORY_NAME, O_CREAT | O_EXCL | O_RDWR, 0666);
if (_shm_fd == -1) {
if (errno == EEXIST) {
LOG(eTrace, "Shared memory segment already exists, opening existing segment\n");
// 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 {

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants