mirror of
https://github.com/openmv/openmv.git
synced 2025-09-26 23:09:13 +08:00
Merge pull request #2816 from openmv/profiling_on_steroids
Some checks are pending
🔥 Firmware Build / build-firmware (0, ARDUINO_GIGA) (push) Waiting to run
🔥 Firmware Build / build-firmware (0, ARDUINO_NANO_33_BLE_SENSE) (push) Waiting to run
🔥 Firmware Build / build-firmware (0, ARDUINO_NANO_RP2040_CONNECT) (push) Waiting to run
🔥 Firmware Build / build-firmware (0, ARDUINO_NICLA_VISION) (push) Waiting to run
🔥 Firmware Build / build-firmware (0, ARDUINO_PORTENTA_H7) (push) Waiting to run
🔥 Firmware Build / build-firmware (0, DOCKER) (push) Waiting to run
🔥 Firmware Build / build-firmware (0, OPENMV2) (push) Waiting to run
🔥 Firmware Build / build-firmware (0, OPENMV3) (push) Waiting to run
🔥 Firmware Build / build-firmware (0, OPENMV4) (push) Waiting to run
🔥 Firmware Build / build-firmware (0, OPENMV4P) (push) Waiting to run
🔥 Firmware Build / build-firmware (0, OPENMVPT) (push) Waiting to run
🔥 Firmware Build / build-firmware (0, OPENMV_AE3) (push) Waiting to run
🔥 Firmware Build / build-firmware (0, OPENMV_N6) (push) Waiting to run
🔥 Firmware Build / build-firmware (0, OPENMV_RT1060) (push) Waiting to run
🔥 Firmware Build / build-firmware (1, OPENMV_N6) (push) Waiting to run
🔥 Firmware Build / code-size-report (push) Blocked by required conditions
🔥 Firmware Build / stable-release (push) Blocked by required conditions
🔥 Firmware Build / development-release (push) Blocked by required conditions
Some checks are pending
🔥 Firmware Build / build-firmware (0, ARDUINO_GIGA) (push) Waiting to run
🔥 Firmware Build / build-firmware (0, ARDUINO_NANO_33_BLE_SENSE) (push) Waiting to run
🔥 Firmware Build / build-firmware (0, ARDUINO_NANO_RP2040_CONNECT) (push) Waiting to run
🔥 Firmware Build / build-firmware (0, ARDUINO_NICLA_VISION) (push) Waiting to run
🔥 Firmware Build / build-firmware (0, ARDUINO_PORTENTA_H7) (push) Waiting to run
🔥 Firmware Build / build-firmware (0, DOCKER) (push) Waiting to run
🔥 Firmware Build / build-firmware (0, OPENMV2) (push) Waiting to run
🔥 Firmware Build / build-firmware (0, OPENMV3) (push) Waiting to run
🔥 Firmware Build / build-firmware (0, OPENMV4) (push) Waiting to run
🔥 Firmware Build / build-firmware (0, OPENMV4P) (push) Waiting to run
🔥 Firmware Build / build-firmware (0, OPENMVPT) (push) Waiting to run
🔥 Firmware Build / build-firmware (0, OPENMV_AE3) (push) Waiting to run
🔥 Firmware Build / build-firmware (0, OPENMV_N6) (push) Waiting to run
🔥 Firmware Build / build-firmware (0, OPENMV_RT1060) (push) Waiting to run
🔥 Firmware Build / build-firmware (1, OPENMV_N6) (push) Waiting to run
🔥 Firmware Build / code-size-report (push) Blocked by required conditions
🔥 Firmware Build / stable-release (push) Blocked by required conditions
🔥 Firmware Build / development-release (push) Blocked by required conditions
common: Add code profiler.
This commit is contained in:
commit
0dbb5c8aa7
8
.github/workflows/firmware.yml
vendored
8
.github/workflows/firmware.yml
vendored
@ -49,6 +49,10 @@ jobs:
|
||||
- ARDUINO_NANO_RP2040_CONNECT
|
||||
- ARDUINO_NANO_33_BLE_SENSE
|
||||
- DOCKER
|
||||
profile: [0] # default profile
|
||||
include:
|
||||
- target: OPENMV_N6
|
||||
profile: 1
|
||||
fail-fast: false
|
||||
steps:
|
||||
- name: '⏳ Checkout repository'
|
||||
@ -100,10 +104,10 @@ jobs:
|
||||
run: source tools/ci.sh && ci_install_stedgeai ${HOME}/cache/stedgeai
|
||||
|
||||
- name: '🏗 Build firmware'
|
||||
run: source tools/ci.sh && ci_build_target ${{ matrix.target }}
|
||||
run: source tools/ci.sh && ci_build_target ${{ matrix.target }} ${{ matrix.profile }}
|
||||
|
||||
- name: '⬆ Upload artifacts'
|
||||
if: matrix.target != 'DOCKER'
|
||||
if: matrix.target != 'DOCKER' && matrix.profile == 0
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.target }}
|
||||
|
18
Makefile
18
Makefile
@ -107,10 +107,24 @@ CFLAGS += -DFB_ALLOC_STATS
|
||||
endif
|
||||
|
||||
# Enable timing for some functions.
|
||||
ifeq ($(PROFILE), 1)
|
||||
CFLAGS += -DOMV_PROFILE_ENABLE=1
|
||||
ifeq ($(PROFILE_ENABLE), 1)
|
||||
$(info ===================================)
|
||||
$(info ======= Profiling Enabled =======)
|
||||
$(info ===================================)
|
||||
|
||||
# Enable profiling in IRQ context (default: enabled)
|
||||
PROFILE_IRQ ?= 0
|
||||
|
||||
# Default profiler hash table size (must be power of 2)
|
||||
ifeq ($(PROFILE_HASH),)
|
||||
PROFILE_HASH=256
|
||||
endif
|
||||
|
||||
CFLAGS += -DOMV_PROFILER_ENABLE=1
|
||||
CFLAGS += -DOMV_PROFILER_HASH_SIZE=$(PROFILE_HASH)
|
||||
CFLAGS += -DOMV_PROFILER_IRQ_ENABLE=$(PROFILE_IRQ)
|
||||
CFLAGS += -finstrument-functions-exclude-file-list=lib/cmsis,lib/stm32,/lib/mimxrt,lib/alif,simd.h
|
||||
endif
|
||||
|
||||
# Include OpenMV board config first to set the port.
|
||||
include $(OMV_BOARD_CONFIG_DIR)/omv_boardconfig.mk
|
||||
|
@ -39,6 +39,7 @@ COMMON_SRC_C += \
|
||||
usbdbg.c \
|
||||
vospi.c \
|
||||
queue.c \
|
||||
omv_profiler.c \
|
||||
|
||||
CFLAGS += -I$(TOP_DIR)/common
|
||||
OMV_FIRM_OBJ += $(addprefix $(BUILD)/common/, $(COMMON_SRC_C:.c=.o))
|
||||
|
@ -31,6 +31,7 @@
|
||||
#define OMV_ATTR_ALWAYS_INLINE inline __attribute__((always_inline))
|
||||
#define OMV_ATTR_OPTIMIZE(o) __attribute__((optimize(o)))
|
||||
#define OMV_ATTR_SEC_ALIGN(x, s, a) x __attribute__((section(s), aligned(a)))
|
||||
#define OMV_ATTR_NO_INSTRUMENT __attribute__((no_instrument_function))
|
||||
#define OMV_DEBUG_BREAKPOINT() __asm__ volatile ("BKPT")
|
||||
|
||||
#ifndef __DCACHE_PRESENT
|
||||
@ -76,17 +77,11 @@
|
||||
|
||||
#define OMV_ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
|
||||
|
||||
#if OMV_PROFILE_ENABLE
|
||||
#include <stdio.h>
|
||||
#include "py/mphal.h"
|
||||
#define OMV_CONCATENATE_DETAIL(x, y) x##y
|
||||
#define OMV_CONCATENATE(x, y) OMV_CONCATENATE_DETAIL(x, y)
|
||||
#define OMV_PROFILE_START(F) mp_uint_t OMV_CONCATENATE(_ticks_start_, F) = mp_hal_ticks_us()
|
||||
#define OMV_PROFILE_PRINT(F) printf("%s:%s %u us\n", __FUNCTION__, #F, mp_hal_ticks_us() - OMV_CONCATENATE(_ticks_start_, F))
|
||||
#else
|
||||
#define OMV_PROFILE_START(F)
|
||||
#define OMV_PROFILE_PRINT(F)
|
||||
#endif
|
||||
// Token concatenation macros
|
||||
// OMV_CONCAT expands its arguments before pasting, while
|
||||
// OMV_CONCAT_HELPER performs the raw token pasting (x##y).
|
||||
#define OMV_CONCAT_HELPER(x, y) x##y
|
||||
#define OMV_CONCAT_STR(x, y) OMV_CONCAT_HELPER(x, y)
|
||||
|
||||
// Returns a pointer to the containing structure
|
||||
// ptr: Pointer to the member within the structure
|
||||
|
365
common/omv_profiler.c
Normal file
365
common/omv_profiler.c
Normal file
@ -0,0 +1,365 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2025 OpenMV, LLC.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* OpenMV code profiler.
|
||||
*/
|
||||
#if OMV_PROFILER_ENABLE
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "py/mphal.h"
|
||||
#include "omv_profiler.h"
|
||||
|
||||
#if __PMU_PRESENT
|
||||
#define OMV_GET_CYCLE_COUNT() ARM_PMU_Get_CCNTR()
|
||||
#define OMV_GET_EVENT_COUNT(i) ARM_PMU_Get_EVCNTR(i)
|
||||
#else
|
||||
#define OMV_GET_CYCLE_COUNT() 0
|
||||
#define OMV_GET_EVENT_COUNT(i) 0
|
||||
#endif // __PMU_PRESENT
|
||||
#define OMV_GET_TICKS_COUNT() ticks_us_monotonic()
|
||||
|
||||
// Call stack entry for tracking nested calls
|
||||
typedef struct {
|
||||
void *func_addr; // Function address
|
||||
void *call_addr; // Call site address
|
||||
|
||||
uint32_t enter_ticks; // Timestamp on entry
|
||||
uint32_t enter_cycles; // Cycle count on entry
|
||||
|
||||
uint64_t child_ticks; // Total child execution time
|
||||
uint64_t child_cycles; // Total child cycle count
|
||||
|
||||
#if __PMU_PRESENT
|
||||
uint16_t enter_events[__PMU_NUM_EVENTCNT]; // PMU events on entry
|
||||
uint64_t child_events[__PMU_NUM_EVENTCNT]; // Total child PMU events
|
||||
#endif
|
||||
} omv_stack_entry_t;
|
||||
|
||||
typedef struct {
|
||||
bool initialized; // Profiler state.
|
||||
mutex_t mutex; // Protects profile data
|
||||
bool reset_pending; // Reset requested flag
|
||||
omv_profiler_mode_t mode; // Inclusive/exclusive mode
|
||||
|
||||
uint32_t collisions; // Hash table collision count
|
||||
omv_profiler_data_t *hash[OMV_PROFILER_HASH_SIZE]; // Hash table buckets
|
||||
|
||||
uint32_t pool_index; // Next free pool entry
|
||||
omv_profiler_data_t pool[OMV_PROFILER_HASH_SIZE]; // Entry pool
|
||||
|
||||
int32_t stack_top; // Current stack position
|
||||
uint32_t stack_depth; // Max stack depth reached
|
||||
omv_stack_entry_t stack[OMV_PROFILER_STACK_DEPTH]; // Call stack
|
||||
} omv_profiler_state_t;
|
||||
|
||||
static omv_profiler_state_t profiler;
|
||||
|
||||
#define profiler_stack_top(x) profiler.stack[profiler.stack_top].x
|
||||
#define profiler_stack_prv(x) profiler.stack[profiler.stack_top - 1].x
|
||||
|
||||
static OMV_ATTR_NO_INSTRUMENT mp_uint_t ticks_us_monotonic(void) {
|
||||
static mp_uint_t last_timestamp = 0;
|
||||
mp_uint_t current = mp_hal_ticks_us();
|
||||
|
||||
// Ensure monotonic behavior
|
||||
if (current > last_timestamp) {
|
||||
last_timestamp = current;
|
||||
}
|
||||
|
||||
return last_timestamp;
|
||||
}
|
||||
|
||||
static inline uint32_t OMV_ATTR_NO_INSTRUMENT hash_address(void *addr) {
|
||||
uint32_t x = (uint32_t) addr >> 2; // drop thumb bit + alignment bits
|
||||
// mix (Murmur finalizer style)
|
||||
x ^= x >> 16;
|
||||
x *= 0x7feb352dU;
|
||||
x ^= x >> 15;
|
||||
x *= 0x846ca68bU;
|
||||
x ^= x >> 16;
|
||||
return x & (OMV_PROFILER_HASH_SIZE - 1);
|
||||
}
|
||||
|
||||
// Initialize profiling system
|
||||
void omv_profiler_init(void) {
|
||||
if (!profiler.initialized) {
|
||||
// Reset state
|
||||
memset(&profiler, 0, sizeof(profiler));
|
||||
#if __PMU_PRESENT
|
||||
// Enable PMU
|
||||
ARM_PMU_Enable();
|
||||
// Disable all event counters
|
||||
ARM_PMU_CNTR_Disable(((1U << __PMU_NUM_EVENTCNT) - 1));
|
||||
// Enable cycles counter.
|
||||
ARM_PMU_CNTR_Enable(PMU_CNTENSET_CCNTR_ENABLE_Msk);
|
||||
// Enable Trace
|
||||
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Initialize mutex
|
||||
mutex_init0(&profiler.mutex);
|
||||
profiler.initialized = true;
|
||||
}
|
||||
|
||||
void omv_profiler_reset(void) {
|
||||
if (!mutex_try_lock(&profiler.mutex, MUTEX_TID_OMV)) {
|
||||
// Set a pending reset if the data is locked.
|
||||
profiler.reset_pending = true;
|
||||
} else {
|
||||
// Reset state
|
||||
profiler.pool_index = 0;
|
||||
profiler.stack_top = -1;
|
||||
profiler.stack_depth = 0;
|
||||
profiler.collisions = 0;
|
||||
profiler.reset_pending = false;
|
||||
|
||||
// Clear hash table
|
||||
memset(profiler.hash, 0, sizeof(profiler.hash));
|
||||
memset(profiler.pool, 0, sizeof(profiler.pool));
|
||||
memset(profiler.stack, 0, sizeof(profiler.stack));
|
||||
|
||||
mutex_unlock(&profiler.mutex, MUTEX_TID_OMV);
|
||||
}
|
||||
}
|
||||
|
||||
mutex_t *omv_profiler_lock(void) {
|
||||
return &profiler.mutex;
|
||||
}
|
||||
|
||||
void *omv_profiler_get_data(size_t *count) {
|
||||
*count = profiler.pool_index;
|
||||
return profiler.pool;
|
||||
}
|
||||
|
||||
void omv_profiler_set_mode(uint32_t mode) {
|
||||
profiler.mode = mode;
|
||||
profiler.reset_pending = true;
|
||||
}
|
||||
|
||||
void omv_profiler_set_event(uint32_t num, uint32_t type) {
|
||||
#if __PMU_PRESENT
|
||||
if (num < __PMU_NUM_EVENTCNT) {
|
||||
ARM_PMU_Disable();
|
||||
|
||||
// Configure and enable event counter.
|
||||
ARM_PMU_Set_EVTYPER(num, type);
|
||||
ARM_PMU_CNTR_Enable(1 << num);
|
||||
|
||||
// Reset all event counters
|
||||
ARM_PMU_EVCNTR_ALL_Reset();
|
||||
|
||||
// Enable PMU
|
||||
ARM_PMU_Enable();
|
||||
}
|
||||
#endif
|
||||
profiler.reset_pending = true;
|
||||
}
|
||||
|
||||
static omv_profiler_data_t *omv_profiler_get_entry(void *func_addr) {
|
||||
uint32_t hash = hash_address(func_addr);
|
||||
omv_profiler_data_t *entry = profiler.hash[hash];
|
||||
|
||||
// Search existing entries in collision chain
|
||||
while (entry != NULL) {
|
||||
if (entry->func_addr == func_addr) {
|
||||
return entry;
|
||||
}
|
||||
entry = entry->next;
|
||||
}
|
||||
|
||||
// Create new entry if not found
|
||||
if (profiler.pool_index >= OMV_PROFILER_HASH_SIZE) {
|
||||
// Pool exhausted
|
||||
return NULL;
|
||||
}
|
||||
|
||||
entry = &profiler.pool[profiler.pool_index++];
|
||||
|
||||
memset(entry, 0, sizeof(omv_profiler_data_t));
|
||||
entry->min_ticks = UINT32_MAX;
|
||||
|
||||
// Insert at head of collision chain
|
||||
if (profiler.hash[hash] != NULL) {
|
||||
profiler.collisions++;
|
||||
}
|
||||
|
||||
entry->next = profiler.hash[hash];
|
||||
profiler.hash[hash] = entry;
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
void OMV_ATTR_NO_INSTRUMENT __cyg_profile_func_enter(void *func_addr, void *call_addr) {
|
||||
uint32_t enter_ticks = OMV_GET_TICKS_COUNT();
|
||||
uint32_t enter_cycles = OMV_GET_CYCLE_COUNT();
|
||||
|
||||
if (func_addr == NULL || call_addr == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip profiling if called from IRQ context
|
||||
#if !OMV_PROFILER_IRQ_ENABLE
|
||||
if ((SCB->ICSR & SCB_ICSR_VECTACTIVE_Msk)) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Restart profiler if needed
|
||||
if (profiler.reset_pending) {
|
||||
return omv_profiler_reset();
|
||||
}
|
||||
|
||||
// Update call stack
|
||||
profiler.stack_top++;
|
||||
if (profiler.stack_top >= OMV_PROFILER_STACK_DEPTH) {
|
||||
profiler.stack_top = OMV_PROFILER_STACK_DEPTH - 1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (profiler.stack_top > (int32_t)profiler.stack_depth) {
|
||||
profiler.stack_depth = profiler.stack_top;
|
||||
}
|
||||
|
||||
profiler_stack_top(func_addr) = func_addr;
|
||||
profiler_stack_top(call_addr) = call_addr;
|
||||
|
||||
profiler_stack_top(child_ticks) = 0;
|
||||
profiler_stack_top(enter_ticks) = enter_ticks;
|
||||
|
||||
profiler_stack_top(child_cycles) = 0;
|
||||
profiler_stack_top(enter_cycles) = enter_cycles;
|
||||
|
||||
#if __PMU_PRESENT
|
||||
for (size_t i = 0; i < __PMU_NUM_EVENTCNT; i++) {
|
||||
profiler_stack_top(child_events[i]) = 0;
|
||||
profiler_stack_top(enter_events[i]) = OMV_GET_EVENT_COUNT(i);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void OMV_ATTR_NO_INSTRUMENT __cyg_profile_func_exit(void *func_addr, void *call_addr) {
|
||||
uint32_t exit_ticks = OMV_GET_TICKS_COUNT();
|
||||
uint32_t exit_cycles = OMV_GET_CYCLE_COUNT();
|
||||
|
||||
if (func_addr == NULL || call_addr == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip profiling if called from IRQ context
|
||||
#if !OMV_PROFILER_IRQ_ENABLE
|
||||
if ((SCB->ICSR & SCB_ICSR_VECTACTIVE_Msk)) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Restart profiler if needed
|
||||
if (profiler.reset_pending) {
|
||||
return omv_profiler_reset();
|
||||
}
|
||||
|
||||
// Update events
|
||||
#if __PMU_PRESENT
|
||||
uint16_t exit_events[__PMU_NUM_EVENTCNT];
|
||||
for (size_t i = 0; i < __PMU_NUM_EVENTCNT; i++) {
|
||||
exit_events[i] = OMV_GET_EVENT_COUNT(i);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (profiler.stack_top < 0 || profiler.stack_top >= OMV_PROFILER_STACK_DEPTH) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Function address should match the stack's top.
|
||||
if (func_addr != profiler_stack_top(func_addr) ||
|
||||
call_addr != profiler_stack_top(call_addr)) {
|
||||
goto exit_cleanup;
|
||||
}
|
||||
|
||||
// Protect profile data update via mutex.
|
||||
if (!mutex_try_lock(&profiler.mutex, MUTEX_TID_OMV)) {
|
||||
goto exit_cleanup;
|
||||
}
|
||||
|
||||
// Calculate inclusive ticks and cycles
|
||||
uint32_t incl_ticks = exit_ticks - profiler_stack_top(enter_ticks);
|
||||
uint32_t incl_cycles = exit_cycles - profiler_stack_top(enter_cycles);
|
||||
|
||||
// Calculate exclusive ticks and cycles
|
||||
uint32_t excl_ticks = incl_ticks - profiler_stack_top(child_ticks);
|
||||
uint32_t excl_cycles = incl_cycles - profiler_stack_top(child_cycles);
|
||||
|
||||
// Check if this entry makes sense.
|
||||
if (incl_ticks < excl_ticks || incl_cycles < excl_cycles) {
|
||||
goto mutex_unlock;
|
||||
}
|
||||
|
||||
// Get or create a new entry
|
||||
omv_profiler_data_t *entry = omv_profiler_get_entry(func_addr);
|
||||
if (entry == NULL) {
|
||||
goto mutex_unlock;
|
||||
}
|
||||
|
||||
entry->call_count++;
|
||||
entry->func_addr = func_addr;
|
||||
entry->call_addr = call_addr;
|
||||
|
||||
entry->total_ticks += excl_ticks;
|
||||
entry->total_cycles += excl_cycles;
|
||||
|
||||
if (excl_ticks > entry->max_ticks) {
|
||||
entry->max_ticks = excl_ticks;
|
||||
}
|
||||
|
||||
if (excl_ticks < entry->min_ticks) {
|
||||
entry->min_ticks = excl_ticks;
|
||||
}
|
||||
|
||||
// In exclusive mode, updated the parent's child counters.
|
||||
if (profiler.stack_top && profiler.mode == OMV_PROFILER_EXCLUSIVE) {
|
||||
profiler_stack_prv(child_ticks) += incl_ticks;
|
||||
profiler_stack_prv(child_cycles) += incl_cycles;
|
||||
}
|
||||
|
||||
#if __PMU_PRESENT
|
||||
for (size_t i = 0; i < __PMU_NUM_EVENTCNT; i++) {
|
||||
uint16_t incl_events = exit_events[i] - profiler_stack_top(enter_events[i]);
|
||||
uint16_t excl_events = incl_events - profiler_stack_top(child_events[i]);
|
||||
|
||||
entry->total_events[i] += excl_events;
|
||||
|
||||
// In exclusive mode, updated the parent's child counters.
|
||||
if (profiler.stack_top && profiler.mode == OMV_PROFILER_EXCLUSIVE) {
|
||||
profiler_stack_prv(child_events[i]) += incl_events;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
mutex_unlock:
|
||||
mutex_unlock(&profiler.mutex, MUTEX_TID_OMV);
|
||||
exit_cleanup:
|
||||
profiler.stack_top--;
|
||||
}
|
||||
#endif // OMV_PROFILER_ENABLE
|
102
common/omv_profiler.h
Normal file
102
common/omv_profiler.h
Normal file
@ -0,0 +1,102 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2025 OpenMV, LLC.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* OpenMV code profiler.
|
||||
*/
|
||||
#ifndef __OMV_PROFILER_H__
|
||||
#define __OMV_PROFILER_H__
|
||||
|
||||
#if OMV_PROFILER_ENABLE
|
||||
#include <stdio.h>
|
||||
#include "py/mphal.h"
|
||||
#if __PMU_PRESENT
|
||||
#include "pmu_armv8.h"
|
||||
#endif
|
||||
#ifndef __cplusplus
|
||||
#include "common/mutex.h"
|
||||
#endif
|
||||
#include "common/omv_common.h"
|
||||
|
||||
#ifndef __PMU_NUM_EVENTCNT
|
||||
#define __PMU_NUM_EVENTCNT 0
|
||||
#endif
|
||||
|
||||
// Must be a power of 2
|
||||
#ifndef OMV_PROFILER_HASH_SIZE
|
||||
#define OMV_PROFILER_HASH_SIZE 256
|
||||
#endif
|
||||
|
||||
#ifndef OMV_PROFILER_STACK_DEPTH
|
||||
#define OMV_PROFILER_STACK_DEPTH 32
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
OMV_PROFILER_INCLUSIVE = 0,
|
||||
OMV_PROFILER_EXCLUSIVE = 1,
|
||||
} omv_profiler_mode_t;
|
||||
|
||||
// Profile record structure
|
||||
typedef struct __attribute__((packed)) _prof_data {
|
||||
void *func_addr; // Function address
|
||||
void *call_addr; // Caller address
|
||||
uint32_t call_count; // Number of times called
|
||||
uint32_t min_ticks; // Minimum execution time
|
||||
uint32_t max_ticks; // Maximum execution time
|
||||
uint64_t total_ticks; // Total time in ticks
|
||||
uint64_t total_cycles; // Total CPU cycles
|
||||
#if __PMU_PRESENT
|
||||
uint64_t total_events[__PMU_NUM_EVENTCNT];
|
||||
#endif
|
||||
struct _prof_data *next; // Next in hash collision chain
|
||||
} omv_profiler_data_t;
|
||||
|
||||
OMV_ATTR_NO_INSTRUMENT void omv_profiler_init(void);
|
||||
OMV_ATTR_NO_INSTRUMENT void omv_profiler_reset(void);
|
||||
OMV_ATTR_NO_INSTRUMENT void *omv_profiler_get_data(size_t *count);
|
||||
OMV_ATTR_NO_INSTRUMENT void omv_profiler_set_mode(uint32_t mode);
|
||||
OMV_ATTR_NO_INSTRUMENT void omv_profiler_set_event(uint32_t num, uint32_t type);
|
||||
#ifndef __cplusplus
|
||||
OMV_ATTR_NO_INSTRUMENT mutex_t *omv_profiler_lock(void);
|
||||
#endif
|
||||
|
||||
// Manual instrumentation macros
|
||||
#define OMV_PROFILER_ENTER(func) \
|
||||
do { \
|
||||
void *func_addr = (void*)(func); \
|
||||
void *call_addr = __builtin_return_address(0); \
|
||||
__cyg_profile_func_enter(func_addr, call_addr); \
|
||||
} while(0)
|
||||
|
||||
#define OMV_PROFILER_EXIT(func) \
|
||||
do { \
|
||||
void *func_addr = (void*)(func); \
|
||||
void *call_addr = __builtin_return_address(0); \
|
||||
__cyg_profile_func_exit(func_addr, call_addr); \
|
||||
} while(0)
|
||||
|
||||
#else
|
||||
// Disabled - empty macros
|
||||
#define OMV_PROFILER_ENTER(func) do {} while(0)
|
||||
#define OMV_PROFILER_EXIT(func) do {} while(0)
|
||||
#endif // OMV_PROFILER_ENABLE
|
||||
#endif // __OMV_PROFILER_H__
|
@ -57,7 +57,6 @@ extern uint32_t usb_cdc_buf_len();
|
||||
extern uint32_t usb_cdc_get_buf(uint8_t *buf, uint32_t len);
|
||||
extern void usb_cdc_reset_buffers(void);
|
||||
|
||||
|
||||
void usbdbg_init() {
|
||||
cmd = USBDBG_NONE;
|
||||
script_ready = false;
|
||||
@ -67,6 +66,9 @@ void usbdbg_init() {
|
||||
#if OMV_TUSBDBG_ENABLE
|
||||
tinyusb_debug_init();
|
||||
#endif
|
||||
#if OMV_PROFILER_ENABLE
|
||||
omv_profiler_init();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool usbdbg_script_ready() {
|
||||
@ -166,7 +168,7 @@ void usbdbg_data_in(uint32_t size, usbdbg_write_callback_t write_callback) {
|
||||
// Return 0 if FB is locked or not ready.
|
||||
uint32_t buffer[3] = { 0 };
|
||||
// Try to lock FB. If header size == 0 frame is not ready
|
||||
if (mutex_try_lock_fair(&JPEG_FB()->lock, MUTEX_TID_IDE)) {
|
||||
if (mutex_try_lock(&JPEG_FB()->lock, MUTEX_TID_IDE)) {
|
||||
// If header size == 0 frame is not ready
|
||||
if (JPEG_FB()->size == 0) {
|
||||
// unlock FB
|
||||
@ -231,15 +233,23 @@ void usbdbg_data_in(uint32_t size, usbdbg_write_callback_t write_callback) {
|
||||
|
||||
// Set script running flag
|
||||
if (script_running) {
|
||||
buffer[0] |= USBDBG_STATE_FLAGS_SCRIPT;
|
||||
buffer[0] |= USBDBG_FLAG_SCRIPT_RUNNING;
|
||||
}
|
||||
|
||||
// Set text buf valid flag.
|
||||
uint32_t tx_buf_len = usb_cdc_buf_len();
|
||||
if (tx_buf_len) {
|
||||
buffer[0] |= USBDBG_STATE_FLAGS_TEXT;
|
||||
buffer[0] |= USBDBG_FLAG_TEXTBUF_NOTEMPTY;
|
||||
}
|
||||
|
||||
// Set code profiling flags
|
||||
#if OMV_PROFILER_ENABLE
|
||||
buffer[0] |= USBDBG_FLAG_PROFILE_ENABLED;
|
||||
#if __PMU_PRESENT
|
||||
buffer[0] |= USBDBG_FLAG_PROFILE_HAS_PMU;
|
||||
#endif // __PMU_PRESENT
|
||||
#endif // OMV_PROFILER_ENABLE
|
||||
|
||||
// Limit the frames sent over USB to 20Hz.
|
||||
if (check_timeout_ms(last_update_ms, 50) &&
|
||||
mutex_try_lock_fair(&JPEG_FB()->lock, MUTEX_TID_IDE)) {
|
||||
@ -249,7 +259,7 @@ void usbdbg_data_in(uint32_t size, usbdbg_write_callback_t write_callback) {
|
||||
mutex_unlock(&JPEG_FB()->lock, MUTEX_TID_IDE);
|
||||
} else {
|
||||
// Set valid frame flag.
|
||||
buffer[0] |= USBDBG_STATE_FLAGS_FRAME;
|
||||
buffer[0] |= USBDBG_FLAG_FRAMEBUF_LOCKED;
|
||||
|
||||
// Set frame width, height and size/bpp
|
||||
buffer[1] = JPEG_FB()->w;
|
||||
@ -272,6 +282,40 @@ void usbdbg_data_in(uint32_t size, usbdbg_write_callback_t write_callback) {
|
||||
break;
|
||||
}
|
||||
|
||||
case USBDBG_PROFILE_SIZE: {
|
||||
// Return 0 if the profiling data is locked.
|
||||
uint32_t buffer[3] = { 0 };
|
||||
#if OMV_PROFILER_ENABLE
|
||||
if (mutex_try_lock(omv_profiler_lock(), MUTEX_TID_IDE)) {
|
||||
size_t count = 0;
|
||||
(void) omv_profiler_get_data(&count);
|
||||
|
||||
buffer[0] = count;
|
||||
buffer[1] = sizeof(omv_profiler_data_t);
|
||||
buffer[2] = __PMU_NUM_EVENTCNT;
|
||||
}
|
||||
#endif
|
||||
cmd = USBDBG_NONE;
|
||||
write_callback(&buffer, sizeof(buffer));
|
||||
break;
|
||||
}
|
||||
|
||||
#if OMV_PROFILER_ENABLE
|
||||
case USBDBG_PROFILE_DUMP:
|
||||
if (xfer_offs < xfer_size) {
|
||||
size_t count = 0;
|
||||
char *data = omv_profiler_get_data(&count);
|
||||
|
||||
write_callback(data + xfer_offs, size);
|
||||
xfer_offs += size;
|
||||
if (xfer_offs == xfer_size) {
|
||||
cmd = USBDBG_NONE;
|
||||
mutex_unlock(omv_profiler_lock(), MUTEX_TID_IDE);
|
||||
}
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
|
||||
default: /* error */
|
||||
break;
|
||||
}
|
||||
@ -310,6 +354,26 @@ void usbdbg_data_out(uint32_t size, usbdbg_read_callback_t read_callback) {
|
||||
}
|
||||
break;
|
||||
|
||||
case USBDBG_PROFILE_MODE: {
|
||||
uint32_t buffer[1];
|
||||
read_callback(&buffer, sizeof(buffer));
|
||||
#if OMV_PROFILER_ENABLE
|
||||
omv_profiler_set_mode(buffer[0]);
|
||||
#endif
|
||||
cmd = USBDBG_NONE;
|
||||
break;
|
||||
}
|
||||
|
||||
case USBDBG_PROFILE_EVENT: {
|
||||
uint32_t buffer[2];
|
||||
read_callback(&buffer, sizeof(buffer));
|
||||
#if OMV_PROFILER_ENABLE
|
||||
omv_profiler_set_event(buffer[0], buffer[1]);
|
||||
#endif
|
||||
cmd = USBDBG_NONE;
|
||||
break;
|
||||
}
|
||||
|
||||
default: /* error */
|
||||
break;
|
||||
}
|
||||
@ -327,17 +391,29 @@ void usbdbg_control(void *buffer, uint8_t request, uint32_t size) {
|
||||
case USBDBG_TX_BUF_LEN:
|
||||
case USBDBG_SENSOR_ID:
|
||||
case USBDBG_GET_STATE:
|
||||
xfer_offs = 0;
|
||||
xfer_size = size;
|
||||
break;
|
||||
|
||||
case USBDBG_SCRIPT_RUNNING:
|
||||
vstr_reset(&script_buf);
|
||||
case USBDBG_PROFILE_SIZE:
|
||||
case USBDBG_PROFILE_DUMP:
|
||||
case USBDBG_PROFILE_MODE:
|
||||
case USBDBG_PROFILE_EVENT:
|
||||
case USBDBG_SCRIPT_EXEC:
|
||||
xfer_offs = 0;
|
||||
xfer_size = size;
|
||||
break;
|
||||
|
||||
case USBDBG_PROFILE_RESET: {
|
||||
#if OMV_PROFILER_ENABLE
|
||||
omv_profiler_reset();
|
||||
#endif
|
||||
cmd = USBDBG_NONE;
|
||||
break;
|
||||
}
|
||||
|
||||
case USBDBG_SCRIPT_RUNNING:
|
||||
vstr_reset(&script_buf);
|
||||
xfer_offs = 0;
|
||||
xfer_size = size;
|
||||
break;
|
||||
|
||||
case USBDBG_SCRIPT_STOP:
|
||||
if (script_running) {
|
||||
// Reset CDC buffers.
|
||||
|
@ -61,13 +61,20 @@ enum usbdbg_cmd {
|
||||
USBDBG_TX_BUF = 0x8F,
|
||||
USBDBG_SENSOR_ID = 0x90,
|
||||
USBDBG_GET_STATE = 0x93,
|
||||
USBDBG_PROFILE_SIZE = 0x94,
|
||||
USBDBG_PROFILE_DUMP = 0x95,
|
||||
USBDBG_PROFILE_MODE = 0x16,
|
||||
USBDBG_PROFILE_EVENT = 0x17,
|
||||
USBDBG_PROFILE_RESET = 0x18,
|
||||
};
|
||||
|
||||
enum usbdbg_state_flags {
|
||||
USBDBG_STATE_FLAGS_SCRIPT = (1 << 0),
|
||||
USBDBG_STATE_FLAGS_TEXT = (1 << 1),
|
||||
USBDBG_STATE_FLAGS_FRAME = (1 << 2),
|
||||
};
|
||||
typedef enum {
|
||||
USBDBG_FLAG_SCRIPT_RUNNING = (1 << 0),
|
||||
USBDBG_FLAG_TEXTBUF_NOTEMPTY = (1 << 1),
|
||||
USBDBG_FLAG_FRAMEBUF_LOCKED = (1 << 2),
|
||||
USBDBG_FLAG_PROFILE_ENABLED = (1 << 3),
|
||||
USBDBG_FLAG_PROFILE_HAS_PMU = (1 << 4),
|
||||
} usbdbg_flags_t;
|
||||
|
||||
typedef uint32_t (*usbdbg_read_callback_t) (void *buf, uint32_t len);
|
||||
typedef uint32_t (*usbdbg_write_callback_t) (const void *buf, uint32_t len);
|
||||
|
@ -1160,7 +1160,6 @@ void imlib_debayer_line(int x_start, int x_end, int y_row, void *dst_row_ptr, pi
|
||||
// assumes dst->h == src->h
|
||||
// src and dst may not overlap, but, faster than imlib_debayer_image_awb
|
||||
void imlib_debayer_image(image_t *dst, image_t *src) {
|
||||
OMV_PROFILE_START();
|
||||
rectangle_t roi = {
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
@ -1168,7 +1167,6 @@ void imlib_debayer_image(image_t *dst, image_t *src) {
|
||||
.h = src->h,
|
||||
};
|
||||
vdebayer(src, &roi, 0, dst);
|
||||
OMV_PROFILE_PRINT();
|
||||
}
|
||||
|
||||
#if defined(IMLIB_ENABLE_DEBAYER_OPTIMIZATION)
|
||||
@ -2294,8 +2292,6 @@ static void vdebayer_rggb_to_rgb565_awb_quarter(image_t *src, image_t *dst, uint
|
||||
// RGB565: src->data == dst->data + image_size(src)
|
||||
// YUV422: Not supported
|
||||
void imlib_debayer_image_awb(image_t *dst, image_t *src, bool fast, uint32_t r_out, uint32_t g_out, uint32_t b_out) {
|
||||
OMV_PROFILE_START();
|
||||
|
||||
uint32_t red_gain = IM_DIV(g_out * 32, r_out);
|
||||
red_gain = IM_MIN(red_gain, 128U);
|
||||
|
||||
@ -2459,6 +2455,4 @@ void imlib_debayer_image_awb(image_t *dst, image_t *src, bool fast, uint32_t r_o
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OMV_PROFILE_PRINT();
|
||||
}
|
||||
|
@ -2838,7 +2838,6 @@ void imlib_draw_image(image_t *dst_img,
|
||||
imlib_draw_row_callback_t callback,
|
||||
void *callback_arg,
|
||||
void *dst_row_override) {
|
||||
OMV_PROFILE_START();
|
||||
int dst_delta_x = 1; // positive direction
|
||||
if (x_scale < 0.f) {
|
||||
// flip X
|
||||
@ -5404,7 +5403,6 @@ exit_cleanup:
|
||||
if (&new_src_img == src_img) {
|
||||
fb_free();
|
||||
}
|
||||
OMV_PROFILE_PRINT();
|
||||
}
|
||||
|
||||
#ifdef IMLIB_ENABLE_FLOOD_FILL
|
||||
|
@ -44,6 +44,7 @@
|
||||
#include "imlib_config.h"
|
||||
#include "omv_boardconfig.h"
|
||||
#include "omv_common.h"
|
||||
#include "omv_profiler.h"
|
||||
#include "py/runtime.h"
|
||||
|
||||
// Enables 38 TensorFlow Lite operators.
|
||||
|
@ -85,4 +85,11 @@ ifeq ($(CLANG_ENABLE),1)
|
||||
OMV_CLANG_OBJ = $(BUILD)/lib/imlib/bayer.o
|
||||
endif
|
||||
|
||||
# Enable instrumentation.
|
||||
ifeq ($(PROFILE_ENABLE), 1)
|
||||
$(BUILD)/lib/imlib/%.o: override CFLAGS += -finstrument-functions
|
||||
# Clang does not support -finstrument-functions-exclude-file-list.
|
||||
$(OMV_CLANG_OBJ): override CFLAGS := $(filter-out -finstrument-functions-exclude-file-list=%,$(CFLAGS))
|
||||
endif
|
||||
|
||||
OMV_FIRM_OBJ += $(addprefix $(BUILD)/lib/imlib/, $(IMLIB_SRC_C:.c=.o))
|
||||
|
@ -2858,7 +2858,6 @@ static int DecodeJPEG(JPEGIMAGE *pJPEG) {
|
||||
}
|
||||
|
||||
void jpeg_decompress(image_t *dst, image_t *src) {
|
||||
OMV_PROFILE_START();
|
||||
JPEGIMAGE jpg;
|
||||
|
||||
// Supports decoding baseline JPEGs only.
|
||||
@ -2898,7 +2897,5 @@ void jpeg_decompress(image_t *dst, image_t *src) {
|
||||
if (JPEG_decode(&jpg, 0, 0, 0) == 0) {
|
||||
mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("JPEG decoder failed."));
|
||||
}
|
||||
|
||||
OMV_PROFILE_PRINT();
|
||||
}
|
||||
#endif
|
||||
|
@ -931,8 +931,6 @@ static void jpeg_write_headers(jpeg_buf_t *jpeg_buf, int w, int h, int bpp, jpeg
|
||||
}
|
||||
|
||||
bool jpeg_compress(image_t *src, image_t *dst, int quality, bool realloc, jpeg_subsampling_t subsampling) {
|
||||
OMV_PROFILE_START();
|
||||
|
||||
if (!dst->data) {
|
||||
uint32_t size = 0;
|
||||
dst->data = fb_alloc_all(&size, FB_ALLOC_PREFER_SIZE | FB_ALLOC_CACHE_ALIGN);
|
||||
@ -1261,8 +1259,6 @@ bool jpeg_compress(image_t *src, image_t *dst, int quality, bool realloc, jpeg_s
|
||||
|
||||
dst->size = jpeg_buf.idx;
|
||||
dst->data = jpeg_buf.buf;
|
||||
|
||||
OMV_PROFILE_PRINT();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -120,8 +120,6 @@ unsigned lodepng_convert_cb(unsigned char *out, const unsigned char *in,
|
||||
|
||||
#if defined(IMLIB_ENABLE_PNG_ENCODER)
|
||||
bool png_compress(image_t *src, image_t *dst) {
|
||||
OMV_PROFILE_START();
|
||||
|
||||
if (src->is_compressed) {
|
||||
return true;
|
||||
}
|
||||
@ -192,14 +190,12 @@ bool png_compress(image_t *src, image_t *dst) {
|
||||
// free fb_alloc() memory used for umm_init_x().
|
||||
fb_free(); // umm_init_x();
|
||||
}
|
||||
OMV_PROFILE_PRINT();
|
||||
return false;
|
||||
}
|
||||
#endif // IMLIB_ENABLE_PNG_ENCODER
|
||||
|
||||
#if defined(IMLIB_ENABLE_PNG_DECODER)
|
||||
void png_decompress(image_t *dst, image_t *src) {
|
||||
OMV_PROFILE_START();
|
||||
umm_init_x(fb_avail());
|
||||
|
||||
LodePNGState state;
|
||||
@ -241,7 +237,6 @@ void png_decompress(image_t *dst, image_t *src) {
|
||||
|
||||
// free fb_alloc() memory used for umm_init_x().
|
||||
fb_free(); // umm_init_x();
|
||||
OMV_PROFILE_PRINT();
|
||||
}
|
||||
#endif // IMLIB_ENABLE_PNG_DECODER
|
||||
#endif // IMLIB_ENABLE_PNG_ENCODER || IMLIB_ENABLE_PNG_DECODER
|
||||
|
@ -78,6 +78,11 @@ $(BUILD)/lib/stai/libstai/ll_aton/%.o: override CFLAGS += \
|
||||
-Wno-double-promotion \
|
||||
$(STAI_CFLAGS) \
|
||||
|
||||
# Enable instrumentation.
|
||||
ifeq ($(PROFILE_ENABLE), 1)
|
||||
$(BUILD)/lib/stai/stai_backend.o: override CFLAGS += -finstrument-functions
|
||||
endif
|
||||
|
||||
OMV_CFLAGS += -I$(TOP_DIR)/lib/stai/libstai/include
|
||||
OMV_FIRM_OBJ += $(addprefix $(BUILD)/lib/stai/, $(STAI_SRC_C:.c=.o))
|
||||
endif
|
||||
|
@ -50,6 +50,5 @@ $(BUILD)/lib/tflm/tflm_backend.o: CXXFLAGS = \
|
||||
|
||||
OMV_CFLAGS += -I$(TOP_DIR)/lib/tflm/libtflm/include
|
||||
OMV_CFLAGS += -I$(TOP_DIR)/lib/tflm/libtflm/include/third_party/ethos_u_core_driver/include
|
||||
|
||||
OMV_FIRM_OBJ += $(addprefix $(BUILD)/lib/tflm/, $(TFLM_SRC_CC:.cc=.o))
|
||||
endif
|
||||
|
@ -51,6 +51,7 @@ extern "C" {
|
||||
#include "py/binary.h"
|
||||
#include "py/gc.h"
|
||||
#include "py_ml.h"
|
||||
#include "common/omv_profiler.h"
|
||||
|
||||
using namespace tflite;
|
||||
#define TF_ARENA_EXTRA (512)
|
||||
@ -319,6 +320,8 @@ int ml_backend_init_model(py_ml_model_obj_t *model) {
|
||||
}
|
||||
|
||||
int ml_backend_run_inference(py_ml_model_obj_t *model) {
|
||||
OMV_PROFILER_ENTER(ml_backend_run_inference);
|
||||
|
||||
RegisterDebugLogCallback(ml_backend_log_handler);
|
||||
ml_backend_state_t *state = (ml_backend_state_t *) model->state;
|
||||
|
||||
@ -326,6 +329,7 @@ int ml_backend_run_inference(py_ml_model_obj_t *model) {
|
||||
mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Invoke failed"));
|
||||
}
|
||||
|
||||
OMV_PROFILER_EXIT(ml_backend_run_inference);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -66,3 +66,10 @@ ifeq ($(DEBUG), 0)
|
||||
# Use a higher optimization level for user C modules.
|
||||
$(BUILD)/modules/%.o: override CFLAGS += $(USERMOD_OPT)
|
||||
endif
|
||||
|
||||
ifeq ($(PROFILE_ENABLE), 1)
|
||||
$(BUILD)/modules/py_ml.o: override CFLAGS += -finstrument-functions
|
||||
$(BUILD)/modules/py_image.o: override CFLAGS += -finstrument-functions
|
||||
$(BUILD)/modules/ulab/%.o: override CFLAGS += -finstrument-functions
|
||||
endif
|
||||
|
||||
|
@ -731,7 +731,6 @@ static MP_DEFINE_CONST_FUN_OBJ_1(py_image_bytearray_obj, py_image_bytearray);
|
||||
|
||||
#if defined(MODULE_ULAB_ENABLED) && (ULAB_MAX_DIMS == 4)
|
||||
static mp_obj_t py_image_to_ndarray(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
|
||||
OMV_PROFILE_START();
|
||||
enum { ARG_dtype, ARG_buffer };
|
||||
static const mp_arg_t allowed_args[] = {
|
||||
{ MP_QSTR_dtype, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_rom_obj = MP_ROM_NONE } },
|
||||
@ -906,8 +905,6 @@ static mp_obj_t py_image_to_ndarray(size_t n_args, const mp_obj_t *pos_args, mp_
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OMV_PROFILE_PRINT();
|
||||
return MP_OBJ_FROM_PTR(ndarray);
|
||||
}
|
||||
static MP_DEFINE_CONST_FUN_OBJ_KW(py_image_to_ndarray_obj, 1, py_image_to_ndarray);
|
||||
|
@ -254,14 +254,8 @@ static mp_obj_t py_ml_model_predict(size_t n_args, const mp_obj_t *pos_args, mp_
|
||||
mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Unsupported input type. Expected a list"));
|
||||
}
|
||||
|
||||
OMV_PROFILE_START(preprocess);
|
||||
py_ml_process_input(model, pos_args[1]);
|
||||
OMV_PROFILE_PRINT(preprocess);
|
||||
|
||||
OMV_PROFILE_START(inference);
|
||||
ml_backend_run_inference(model);
|
||||
OMV_PROFILE_PRINT(inference);
|
||||
|
||||
mp_obj_t output = py_ml_process_output(model);
|
||||
|
||||
if (args[ARG_callback].u_obj != mp_const_none) {
|
||||
|
@ -135,7 +135,6 @@ int omv_gpu_draw_image(image_t *src_img,
|
||||
if (color_palette || alpha_palette || transform) {
|
||||
return -1;
|
||||
}
|
||||
OMV_PROFILE_START();
|
||||
d2_s32 err;
|
||||
d2_u32 blit_flags = 0;
|
||||
|
||||
@ -183,7 +182,6 @@ int omv_gpu_draw_image(image_t *src_img,
|
||||
|
||||
// Invalidate the framebuffer image.
|
||||
SCB_InvalidateDCache_by_Addr(dst_img->data, image_size(dst_img));
|
||||
OMV_PROFILE_PRINT();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -27,3 +27,9 @@ CFLAGS += -I$(TOP_DIR)/ports/$(PORT)/modules
|
||||
|
||||
PORT_SRC_C = $(wildcard ports/$(PORT)/*.c)
|
||||
OMV_FIRM_OBJ += $(addprefix $(BUILD)/, $(PORT_SRC_C:.c=.o))
|
||||
|
||||
# Enable instrumentation.
|
||||
ifeq ($(PROFILE_ENABLE), 1)
|
||||
$(BUILD)/ports/alif/%.o: override CFLAGS += -finstrument-functions
|
||||
$(BUILD)/ports/stm32/%.o: override CFLAGS += -finstrument-functions
|
||||
endif
|
||||
|
@ -95,8 +95,6 @@ int omv_gpu_draw_image(image_t *src_img,
|
||||
const uint8_t *alpha_palette,
|
||||
image_hint_t hint,
|
||||
float *transform) {
|
||||
OMV_PROFILE_START();
|
||||
|
||||
// GPU2D can only draw on RGB565/GRAYSCALE buffers.
|
||||
if ((dst_img->pixfmt != PIXFORMAT_RGB565) && (dst_img->pixfmt != PIXFORMAT_GRAYSCALE)) {
|
||||
return -1;
|
||||
@ -197,8 +195,6 @@ int omv_gpu_draw_image(image_t *src_img,
|
||||
HAL_ICACHE_Invalidate_IT();
|
||||
|
||||
SCB_InvalidateDCache_by_Addr(dst_img->data, image_size(dst_img));
|
||||
|
||||
OMV_PROFILE_PRINT();
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
@ -211,8 +207,6 @@ int omv_gpu_draw_image(image_t *src_img,
|
||||
const uint8_t *alpha_palette,
|
||||
image_hint_t hint,
|
||||
float *transform) {
|
||||
OMV_PROFILE_START();
|
||||
|
||||
// DMA2D can only draw on RGB565 buffers and the destination/source buffers must be accessible by DMA.
|
||||
if ((dst_img->pixfmt != PIXFORMAT_RGB565) || (!DMA_BUFFER(dst_img->data)) || (!DMA_BUFFER(src_img->data))) {
|
||||
return -1;
|
||||
@ -401,8 +395,6 @@ int omv_gpu_draw_image(image_t *src_img,
|
||||
#endif
|
||||
|
||||
HAL_DMA2D_DeInit(&dma2d);
|
||||
|
||||
OMV_PROFILE_PRINT();
|
||||
return 0;
|
||||
}
|
||||
#endif // OMV_GPU_NEMA
|
||||
|
@ -129,7 +129,6 @@ static void jpeg_compress_data_ready(JPEG_HandleTypeDef *hjpeg, uint8_t *pDataOu
|
||||
}
|
||||
|
||||
bool jpeg_compress(image_t *src, image_t *dst, int quality, bool realloc, jpeg_subsampling_t subsampling) {
|
||||
OMV_PROFILE_START();
|
||||
HAL_JPEG_RegisterGetDataCallback(&JPEG_state.jpeg_descr, jpeg_compress_get_data);
|
||||
HAL_JPEG_RegisterDataReadyCallback(&JPEG_state.jpeg_descr, jpeg_compress_data_ready);
|
||||
|
||||
@ -394,8 +393,6 @@ exit_cleanup:
|
||||
HAL_JPEG_UnRegisterGetDataCallback(&JPEG_state.jpeg_descr);
|
||||
|
||||
fb_free(); // mcu_row_buffer (after DMA is aborted)
|
||||
|
||||
OMV_PROFILE_PRINT();
|
||||
return jpeg_overflow;
|
||||
}
|
||||
|
||||
@ -427,8 +424,6 @@ static void jpeg_decompress_data_ready(JPEG_HandleTypeDef *hjpeg, uint8_t *pData
|
||||
}
|
||||
|
||||
void jpeg_decompress(image_t *dst, image_t *src) {
|
||||
OMV_PROFILE_START();
|
||||
|
||||
// Verify the jpeg image is not a non-baseline jpeg image and check that is has
|
||||
// valid headers up to the start-of-scan header (which cannot be trivially walked).
|
||||
if (!jpeg_is_valid(src)) {
|
||||
@ -795,8 +790,6 @@ exit_cleanup:
|
||||
if (((uint32_t) src->data) % __SCB_DCACHE_LINE_SIZE) {
|
||||
fb_free(); // JPEG_state.jpeg_descr.pJpegInBuffPtr (after DMA is aborted)
|
||||
}
|
||||
|
||||
OMV_PROFILE_PRINT();
|
||||
}
|
||||
|
||||
void imlib_hardware_jpeg_init() {
|
||||
|
@ -54,8 +54,11 @@ ci_build_target() {
|
||||
make -j$(nproc) -C docker TARGET=${BOARD}
|
||||
else
|
||||
make -j$(nproc) -C lib/micropython/mpy-cross
|
||||
make -j$(nproc) TARGET=${1}
|
||||
mv build/bin ${1}
|
||||
make -j$(nproc) TARGET=${1} PROFILE_ENABLE=${2}
|
||||
# Don't copy artifacts for profiling builds
|
||||
if [ "$2" = 0 ]; then
|
||||
mv build/bin ${1}
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python
|
||||
# This file is part of the OpenMV project.
|
||||
#
|
||||
# Copyright (c) 2013-2021 Ibrahim Abdelkader <iabdalkader@openmv.io>
|
||||
@ -16,7 +16,7 @@ import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
__serial = None
|
||||
__FB_HDR_SIZE =12
|
||||
__FB_HDR_SIZE = 12
|
||||
|
||||
# USB Debug commands
|
||||
__USBDBG_CMD = 48
|
||||
@ -38,20 +38,27 @@ __USBDBG_FB_ENABLE = 0x0D
|
||||
__USBDBG_TX_BUF_LEN = 0x8E
|
||||
__USBDBG_TX_BUF = 0x8F
|
||||
__USBDBG_GET_STATE = 0x93
|
||||
__USBDBG_PROFILE_SIZE = 0x94
|
||||
__USBDBG_PROFILE_DUMP = 0x95
|
||||
__USBDBG_PROFILE_MODE = 0x16
|
||||
__USBDBG_PROFILE_EVENT = 0x17
|
||||
__USBDBG_PROFILE_RESET = 0x18
|
||||
|
||||
__USBDBG_STATE_FLAGS_SCRIPT = (1 << 0)
|
||||
__USBDBG_STATE_FLAGS_TEXT = (1 << 1)
|
||||
__USBDBG_STATE_FLAGS_FRAME = (1 << 2)
|
||||
__USBDBG_FLAG_SCRIPT_RUNNING = (1 << 0)
|
||||
__USBDBG_FLAG_TEXTBUF_NOTEMPTY = (1 << 1)
|
||||
__USBDBG_FLAG_FRAMEBUF_LOCKED = (1 << 2)
|
||||
__USBDBG_FLAG_PROFILE_ENABLED = (1 << 3)
|
||||
__USBDBG_FLAG_PROFILE_HAS_PMU = (1 << 4)
|
||||
|
||||
ATTR_CONTRAST =0
|
||||
ATTR_BRIGHTNESS =1
|
||||
ATTR_SATURATION =2
|
||||
ATTR_GAINCEILING=3
|
||||
ATTR_CONTRAST = 0
|
||||
ATTR_BRIGHTNESS = 1
|
||||
ATTR_SATURATION = 2
|
||||
ATTR_GAINCEILING = 3
|
||||
|
||||
__BOOTLDR_START = 0xABCD0001
|
||||
__BOOTLDR_RESET = 0xABCD0002
|
||||
__BOOTLDR_ERASE = 0xABCD0004
|
||||
__BOOTLDR_WRITE = 0xABCD0008
|
||||
__BOOTLDR_START = 0xABCD0001
|
||||
__BOOTLDR_RESET = 0xABCD0002
|
||||
__BOOTLDR_ERASE = 0xABCD0004
|
||||
__BOOTLDR_WRITE = 0xABCD0008
|
||||
|
||||
def init(port, baudrate=921600, timeout=0.3):
|
||||
global __serial
|
||||
@ -67,29 +74,40 @@ def disconnect():
|
||||
except:
|
||||
pass
|
||||
|
||||
def write_pack(fmt, *values):
|
||||
__serial.write(struct.pack(fmt, *values))
|
||||
|
||||
def read_unpack(fmt):
|
||||
return struct.unpack(fmt, __serial.read(struct.calcsize(fmt)))
|
||||
|
||||
def set_timeout(timeout):
|
||||
__serial.timeout = timeout
|
||||
|
||||
def fb_size():
|
||||
# read fb header
|
||||
__serial.write(struct.pack("<BBI", __USBDBG_CMD, __USBDBG_FRAME_SIZE, __FB_HDR_SIZE))
|
||||
return struct.unpack("III", __serial.read(12))
|
||||
write_pack("<BBI", __USBDBG_CMD, __USBDBG_FRAME_SIZE, __FB_HDR_SIZE)
|
||||
return read_unpack("III")
|
||||
|
||||
def read_state():
|
||||
__serial.write(struct.pack("<BBI", __USBDBG_CMD, __USBDBG_GET_STATE, 64))
|
||||
flags, w, h, size, text_buf = struct.unpack("IIII48s", __serial.read(64))
|
||||
|
||||
write_pack("<BBI", __USBDBG_CMD, __USBDBG_GET_STATE, 64)
|
||||
flags, w, h, size, text_buf = read_unpack("IIII48s")
|
||||
|
||||
text = None
|
||||
if flags & __USBDBG_STATE_FLAGS_TEXT:
|
||||
profile = False
|
||||
|
||||
if flags & __USBDBG_FLAG_PROFILE_ENABLED:
|
||||
profile = True
|
||||
|
||||
if flags & __USBDBG_FLAG_TEXTBUF_NOTEMPTY:
|
||||
text = text_buf.split(b'\0', 1)[0].decode()
|
||||
|
||||
if flags & __USBDBG_STATE_FLAGS_FRAME == 0:
|
||||
return 0, 0, None, 0, text, ""
|
||||
if flags & __USBDBG_FLAG_FRAMEBUF_LOCKED == 0:
|
||||
return 0, 0, None, 0, text, "", profile
|
||||
|
||||
num_bytes = size if size > 2 else (w * h * size)
|
||||
|
||||
# read fb data
|
||||
__serial.write(struct.pack("<BBI", __USBDBG_CMD, __USBDBG_FRAME_DUMP, num_bytes))
|
||||
write_pack("<BBI", __USBDBG_CMD, __USBDBG_FRAME_DUMP, num_bytes)
|
||||
buff = __serial.read(num_bytes)
|
||||
|
||||
if size == 1: # Grayscale
|
||||
@ -113,7 +131,7 @@ def read_state():
|
||||
if (buff.size != (w*h*3)):
|
||||
raise ValueError(f"Unexpected frame size. Expected: {w*h*3} received: {buff.size}")
|
||||
|
||||
return w, h, buff.reshape((h, w, 3)), num_bytes, text, fmt
|
||||
return w, h, buff.reshape((h, w, 3)), num_bytes, text, fmt, profile
|
||||
|
||||
|
||||
def fb_dump():
|
||||
@ -129,7 +147,7 @@ def fb_dump():
|
||||
num_bytes = size[0]*size[1]*size[2]
|
||||
|
||||
# read fb data
|
||||
__serial.write(struct.pack("<BBI", __USBDBG_CMD, __USBDBG_FRAME_DUMP, num_bytes))
|
||||
write_pack("<BBI", __USBDBG_CMD, __USBDBG_FRAME_DUMP, num_bytes)
|
||||
buff = __serial.read(num_bytes)
|
||||
|
||||
if size[2] == 1: # Grayscale
|
||||
@ -153,72 +171,119 @@ def fb_dump():
|
||||
|
||||
return (size[0], size[1], buff.reshape((size[1], size[0], 3)))
|
||||
|
||||
def read_profile():
|
||||
records = []
|
||||
|
||||
# Read and unpack profiling data size
|
||||
write_pack("<BBI", __USBDBG_CMD, __USBDBG_PROFILE_SIZE, 12)
|
||||
record_count, record_size, event_count = read_unpack("<III")
|
||||
|
||||
if record_count:
|
||||
offset = 0
|
||||
record_format = f"<5I2Q{event_count}QI"
|
||||
profile_size = record_count * record_size
|
||||
|
||||
# Read profiling data
|
||||
write_pack("<BBI", __USBDBG_CMD, __USBDBG_PROFILE_DUMP, profile_size)
|
||||
profile_data = __serial.read(profile_size)
|
||||
|
||||
for i in range(record_count):
|
||||
# Unpack the record
|
||||
profile = struct.unpack(record_format, profile_data[offset:offset + record_size])
|
||||
|
||||
# Parse the profile data
|
||||
records.append({
|
||||
'address': profile[0],
|
||||
'caller': profile[1],
|
||||
'call_count': profile[2],
|
||||
'min_ticks': profile[3],
|
||||
'max_ticks': profile[4],
|
||||
'total_ticks': profile[5],
|
||||
'total_cycles': profile[6],
|
||||
'events': profile[7:7 + event_count] if event_count > 0 else []
|
||||
})
|
||||
|
||||
offset += record_size
|
||||
|
||||
return records
|
||||
|
||||
def set_profile_mode(mode):
|
||||
write_pack("<BBI", __USBDBG_CMD, __USBDBG_PROFILE_MODE, 4)
|
||||
write_pack("<I", mode)
|
||||
|
||||
def set_event_counter(event_num, event_type):
|
||||
write_pack("<BBI", __USBDBG_CMD, __USBDBG_PROFILE_EVENT, 8)
|
||||
write_pack("<II", event_num, event_type)
|
||||
|
||||
def reset_profiler():
|
||||
write_pack("<BBI", __USBDBG_CMD, __USBDBG_PROFILE_RESET, 0)
|
||||
|
||||
def exec_script(buf):
|
||||
__serial.write(struct.pack("<BBI", __USBDBG_CMD, __USBDBG_SCRIPT_EXEC, len(buf)))
|
||||
write_pack("<BBI", __USBDBG_CMD, __USBDBG_SCRIPT_EXEC, len(buf))
|
||||
__serial.write(buf.encode())
|
||||
|
||||
def stop_script():
|
||||
__serial.write(struct.pack("<BBI", __USBDBG_CMD, __USBDBG_SCRIPT_STOP, 0))
|
||||
write_pack("<BBI", __USBDBG_CMD, __USBDBG_SCRIPT_STOP, 0)
|
||||
|
||||
def script_running():
|
||||
__serial.write(struct.pack("<BBI", __USBDBG_CMD, __USBDBG_SCRIPT_RUNNING, 4))
|
||||
return struct.unpack("I", __serial.read(4))[0]
|
||||
write_pack("<BBI", __USBDBG_CMD, __USBDBG_SCRIPT_RUNNING, 4)
|
||||
return read_unpack("I")[0]
|
||||
|
||||
def save_template(x, y, w, h, path):
|
||||
buf = struct.pack("IIII", x, y, w, h) + path
|
||||
__serial.write(struct.pack("<BBI", __USBDBG_CMD, __USBDBG_TEMPLATE_SAVE, len(buf)))
|
||||
write_pack("<BBI", __USBDBG_CMD, __USBDBG_TEMPLATE_SAVE, len(buf))
|
||||
__serial.write(buf)
|
||||
|
||||
def save_descriptor(x, y, w, h, path):
|
||||
buf = struct.pack("HHHH", x, y, w, h) + path
|
||||
__serial.write(struct.pack("<BBI", __USBDBG_CMD, __USBDBG_DESCRIPTOR_SAVE, len(buf)))
|
||||
write_pack("<BBI", __USBDBG_CMD, __USBDBG_DESCRIPTOR_SAVE, len(buf))
|
||||
__serial.write(buf)
|
||||
|
||||
def set_attr(attr, value):
|
||||
__serial.write(struct.pack("<BBI", __USBDBG_CMD, __USBDBG_ATTR_WRITE, 8))
|
||||
__serial.write(struct.pack("<II", attr, value))
|
||||
write_pack("<BBI", __USBDBG_CMD, __USBDBG_ATTR_WRITE, 8)
|
||||
write_pack("<II", attr, value)
|
||||
|
||||
def get_attr(attr):
|
||||
__serial.write(struct.pack("<BBIh", __USBDBG_CMD, __USBDBG_ATTR_READ, 1, attr))
|
||||
write_pack("<BBIh", __USBDBG_CMD, __USBDBG_ATTR_READ, 1, attr)
|
||||
return __serial.read(1)
|
||||
|
||||
def reset():
|
||||
__serial.write(struct.pack("<BBI", __USBDBG_CMD, __USBDBG_SYS_RESET, 0))
|
||||
write_pack("<BBI", __USBDBG_CMD, __USBDBG_SYS_RESET, 0)
|
||||
|
||||
def reset_to_bl():
|
||||
__serial.write(struct.pack("<BBI", __USBDBG_CMD, __USBDBG_SYS_RESET_TO_BL, 0))
|
||||
write_pack("<BBI", __USBDBG_CMD, __USBDBG_SYS_RESET_TO_BL, 0)
|
||||
|
||||
def bootloader_start():
|
||||
__serial.write(struct.pack("<I", __BOOTLDR_START))
|
||||
return struct.unpack("I", __serial.read(4))[0] == __BOOTLDR_START
|
||||
write_pack("<I", __BOOTLDR_START)
|
||||
return read_unpack("I")[0] == __BOOTLDR_START
|
||||
|
||||
def bootloader_reset():
|
||||
__serial.write(struct.pack("<I", __BOOTLDR_RESET))
|
||||
write_pack("<I", __BOOTLDR_RESET)
|
||||
|
||||
def flash_erase(sector):
|
||||
__serial.write(struct.pack("<II", __BOOTLDR_ERASE, sector))
|
||||
write_pack("<II", __BOOTLDR_ERASE, sector)
|
||||
|
||||
def flash_write(buf):
|
||||
__serial.write(struct.pack("<I", __BOOTLDR_WRITE) + buf)
|
||||
write_pack("<I", __BOOTLDR_WRITE) + buf
|
||||
|
||||
def tx_buf_len():
|
||||
__serial.write(struct.pack("<BBI", __USBDBG_CMD, __USBDBG_TX_BUF_LEN, 4))
|
||||
return struct.unpack("I", __serial.read(4))[0]
|
||||
write_pack("<BBI", __USBDBG_CMD, __USBDBG_TX_BUF_LEN, 4)
|
||||
return read_unpack("I")[0]
|
||||
|
||||
def tx_buf(bytes):
|
||||
__serial.write(struct.pack("<BBI", __USBDBG_CMD, __USBDBG_TX_BUF, bytes))
|
||||
write_pack("<BBI", __USBDBG_CMD, __USBDBG_TX_BUF, bytes)
|
||||
return __serial.read(bytes)
|
||||
|
||||
def fw_version():
|
||||
__serial.write(struct.pack("<BBI", __USBDBG_CMD, __USBDBG_FW_VERSION, 12))
|
||||
return struct.unpack("III", __serial.read(12))
|
||||
write_pack("<BBI", __USBDBG_CMD, __USBDBG_FW_VERSION, 12)
|
||||
return read_unpack("III")
|
||||
|
||||
def enable_fb(enable):
|
||||
__serial.write(struct.pack("<BBI", __USBDBG_CMD, __USBDBG_FB_ENABLE, 4))
|
||||
__serial.write(struct.pack("<I", enable))
|
||||
write_pack("<BBI", __USBDBG_CMD, __USBDBG_FB_ENABLE, 4)
|
||||
write_pack("<I", enable)
|
||||
|
||||
def arch_str():
|
||||
__serial.write(struct.pack("<BBI", __USBDBG_CMD, __USBDBG_ARCH_STR, 64))
|
||||
write_pack("<BBI", __USBDBG_CMD, __USBDBG_ARCH_STR, 64)
|
||||
return __serial.read(64).split(b'\0', 1)[0]
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python
|
||||
# This file is part of the OpenMV project.
|
||||
#
|
||||
# Copyright (c) 2013-2021 Ibrahim Abdelkader <iabdalkader@openmv.io>
|
||||
# Copyright (c) 2013-2021 Kwabena W. Agyeman <kwagyeman@openmv.io>
|
||||
# Copyright (c) 2013-2025 Ibrahim Abdelkader <iabdalkader@openmv.io>
|
||||
# Copyright (c) 2013-2025 Kwabena W. Agyeman <kwagyeman@openmv.io>
|
||||
#
|
||||
# This work is licensed under the MIT license, see the file LICENSE for details.
|
||||
#
|
||||
@ -39,7 +39,355 @@ while(True):
|
||||
img.flush()
|
||||
"""
|
||||
|
||||
def pygame_test(port, poll_rate, scale, benchmark):
|
||||
def addr_to_symbol(symbols, address):
|
||||
# Binary search for speed
|
||||
lo, hi = 0, len(symbols) - 1
|
||||
while lo <= hi:
|
||||
mid = (lo + hi) // 2
|
||||
start, end, name = symbols[mid]
|
||||
if start <= address < end:
|
||||
return name
|
||||
elif address < start:
|
||||
hi = mid - 1
|
||||
else:
|
||||
lo = mid + 1
|
||||
return None
|
||||
|
||||
def get_color_by_percentage(percentage, base_color=(220, 220, 220)):
|
||||
"""
|
||||
Return a color based on percentage with fine-grained intensity levels.
|
||||
"""
|
||||
def clamp(value):
|
||||
return max(0, min(255, int(value)))
|
||||
|
||||
if percentage >= 50:
|
||||
# Very high - bright red
|
||||
intensity = min(1.0, (percentage - 50) / 50)
|
||||
return (255, clamp(120 - 120 * intensity), clamp(120 - 120 * intensity))
|
||||
elif percentage >= 30:
|
||||
# High - red-orange
|
||||
intensity = (percentage - 30) / 20
|
||||
return (255, clamp(160 + 40 * intensity), clamp(160 - 40 * intensity))
|
||||
elif percentage >= 20:
|
||||
# Medium-high - orange
|
||||
intensity = (percentage - 20) / 10
|
||||
return (255, clamp(200 + 55 * intensity), clamp(180 - 20 * intensity))
|
||||
elif percentage >= 15:
|
||||
# Medium - yellow-orange
|
||||
intensity = (percentage - 15) / 5
|
||||
return (255, clamp(220 + 35 * intensity), clamp(180 + 20 * intensity))
|
||||
elif percentage >= 10:
|
||||
# Medium-low - yellow
|
||||
intensity = (percentage - 10) / 5
|
||||
return (clamp(255 - 75 * intensity), 255, clamp(180 + 75 * intensity))
|
||||
elif percentage >= 5:
|
||||
# Low - light green
|
||||
intensity = (percentage - 5) / 5
|
||||
return (clamp(180 + 75 * intensity), 255, clamp(180 + 75 * intensity))
|
||||
elif percentage >= 2:
|
||||
# Very low - green
|
||||
intensity = (percentage - 2) / 3
|
||||
return (clamp(160 + 95 * intensity), clamp(255 - 55 * intensity), clamp(160 + 95 * intensity))
|
||||
elif percentage >= 1:
|
||||
# Minimal - light blue-green
|
||||
intensity = (percentage - 1) / 1
|
||||
return (clamp(140 + 120 * intensity), clamp(200 + 55 * intensity), clamp(255 - 95 * intensity))
|
||||
else:
|
||||
# Zero or negligible - base color
|
||||
return base_color
|
||||
|
||||
def draw_rounded_rect(surface, color, rect, radius=5):
|
||||
x, y, w, h = rect
|
||||
if w <= 0 or h <= 0:
|
||||
return
|
||||
pygame.draw.rect(surface, color, (x + radius, y, w - 2*radius, h))
|
||||
pygame.draw.rect(surface, color, (x, y + radius, w, h - 2*radius))
|
||||
pygame.draw.circle(surface, color, (x + radius, y + radius), radius)
|
||||
pygame.draw.circle(surface, color, (x + w - radius, y + radius), radius)
|
||||
pygame.draw.circle(surface, color, (x + radius, y + h - radius), radius)
|
||||
pygame.draw.circle(surface, color, (x + w - radius, y + h - radius), radius)
|
||||
|
||||
|
||||
def draw_table(overlay_surface, config, title, headers, col_widths):
|
||||
"""Draw the common table background, title, and header."""
|
||||
# Draw main table background
|
||||
table_rect = (0, 0, config['width'], config['height'])
|
||||
draw_rounded_rect(overlay_surface, config['colors']['bg'], table_rect, int(8 * config['scale_factor']))
|
||||
pygame.draw.rect(overlay_surface, config['colors']['border'], table_rect, max(1, int(2 * config['scale_factor'])))
|
||||
|
||||
# Table title
|
||||
title_text = config['fonts']['title'].render(title, True, config['colors']['header_text'])
|
||||
title_rect = title_text.get_rect()
|
||||
title_x = (config['width'] - title_rect.width) // 2
|
||||
overlay_surface.blit(title_text, (title_x, int(12 * config['scale_factor'])))
|
||||
|
||||
# Header
|
||||
header_y = int(50 * config['scale_factor'])
|
||||
header_height = int(40 * config['scale_factor'])
|
||||
|
||||
# Draw header background
|
||||
header_rect = (int(5 * config['scale_factor']), header_y,
|
||||
config['width'] - int(10 * config['scale_factor']), header_height)
|
||||
draw_rounded_rect(overlay_surface, config['colors']['header_bg'], header_rect, int(4 * config['scale_factor']))
|
||||
|
||||
# Draw header text and separators
|
||||
current_x = int(10 * config['scale_factor'])
|
||||
for i, (header, width) in enumerate(zip(headers, col_widths)):
|
||||
header_surface = config['fonts']['header'].render(header, True, config['colors']['header_text'])
|
||||
overlay_surface.blit(header_surface, (current_x, header_y + int(6 * config['scale_factor'])))
|
||||
|
||||
if i < len(headers) - 1:
|
||||
sep_x = current_x + width - int(5 * config['scale_factor'])
|
||||
pygame.draw.line(overlay_surface, config['colors']['border'],
|
||||
(sep_x, header_y + 2), (sep_x, header_y + header_height - 2), 1)
|
||||
current_x += width
|
||||
|
||||
|
||||
def draw_event_table(overlay_surface, config, profile_data, profile_mode, symbols):
|
||||
"""Draw the event counter mode table."""
|
||||
|
||||
# Prepare data
|
||||
num_events = len(profile_data[0]['events']) if profile_data else 0
|
||||
if not num_events:
|
||||
sorted_data = sorted(profile_data, key=lambda x: x['address'])
|
||||
else:
|
||||
sort_func = lambda x: x['events'][0] // max(1, x['call_count'])
|
||||
sorted_data = sorted(profile_data, key=sort_func, reverse=True)
|
||||
|
||||
headers = ["Function"] + [f"E{i}" for i in range(num_events)]
|
||||
proportions = [0.30] + [0.70/num_events] * num_events
|
||||
col_widths = [config['width'] * prop for prop in proportions]
|
||||
profile_mode = "Exclusive" if profile_mode else "Inclusive"
|
||||
|
||||
# Calculate event totals for percentage calculation
|
||||
event_totals = [0] * num_events
|
||||
for record in sorted_data:
|
||||
for i, event_count in enumerate(record['events']):
|
||||
event_totals[i] += event_count // max(1, record['call_count'])
|
||||
|
||||
# Draw table structure
|
||||
draw_table(overlay_surface, config, f"Event Counters ({profile_mode})", headers, col_widths)
|
||||
|
||||
# Draw data rows
|
||||
row_height = int(30 * config['scale_factor'])
|
||||
data_start_y = int(50 * config['scale_factor'] + 40 * config['scale_factor'] + 8 * config['scale_factor'])
|
||||
available_height = config['height'] - data_start_y - int(60 * config['scale_factor'])
|
||||
visible_rows = min(len(sorted_data), available_height // row_height)
|
||||
|
||||
for i in range(visible_rows):
|
||||
record = sorted_data[i]
|
||||
row_y = data_start_y + i * row_height
|
||||
|
||||
# Draw row background
|
||||
row_color = config['colors']['row_alt'] if i % 2 == 0 else config['colors']['row_normal']
|
||||
row_rect = (int(5 * config['scale_factor']), row_y,
|
||||
config['width'] - int(10 * config['scale_factor']), row_height)
|
||||
pygame.draw.rect(overlay_surface, row_color, row_rect)
|
||||
|
||||
# Function name
|
||||
name = addr_to_symbol(symbols, record['address']) if symbols else "<no symbols>"
|
||||
max_name_chars = int(col_widths[0] // (11 * config['scale_factor']))
|
||||
display_name = name if len(name) <= max_name_chars else name[:max_name_chars - 3] + "..."
|
||||
|
||||
row_data = [display_name]
|
||||
|
||||
# Event data
|
||||
for j, event_count in enumerate(record['events']):
|
||||
event_scale = ""
|
||||
event_count //= max(1, record['call_count'])
|
||||
if event_count > 1_000_000_000:
|
||||
event_count //= 1_000_000_000
|
||||
event_scale = "B"
|
||||
elif event_count > 1_000_000:
|
||||
event_count //= 1_000_000
|
||||
event_scale = "M"
|
||||
row_data.append(f"{event_count:,}{event_scale}")
|
||||
|
||||
# Determine row color based on sorting key (event 0)
|
||||
if len(record['events']) > 0 and event_totals[0] > 0:
|
||||
sort_key_value = record['events'][0] // max(1, record['call_count'])
|
||||
percentage = (sort_key_value / event_totals[0] * 100)
|
||||
row_text_color = get_color_by_percentage(percentage, config['colors']['content_text'])
|
||||
else:
|
||||
row_text_color = config['colors']['content_text']
|
||||
|
||||
# Draw row data with uniform color
|
||||
current_x = 10
|
||||
for j, (data, width) in enumerate(zip(row_data, col_widths)):
|
||||
text_surface = config['fonts']['content'].render(str(data), True, row_text_color)
|
||||
overlay_surface.blit(text_surface, (current_x, row_y + int(8 * config['scale_factor'])))
|
||||
|
||||
if j < len(row_data) - 1:
|
||||
sep_x = current_x + width - 8
|
||||
pygame.draw.line(overlay_surface, (60, 70, 85),
|
||||
(sep_x, row_y), (sep_x, row_y + row_height), 1)
|
||||
current_x += width
|
||||
|
||||
# Draw summary
|
||||
summary_y = config['height'] - int(50 * config['scale_factor'])
|
||||
total_functions = len(profile_data)
|
||||
grand_total = sum(event_totals)
|
||||
summary_text = (
|
||||
f"Profiles: {total_functions} | "
|
||||
f"Events: {num_events} | "
|
||||
f"Total Events: {grand_total:,}"
|
||||
)
|
||||
|
||||
summary_surface = config['fonts']['summary'].render(summary_text, True, config['colors']['content_text'])
|
||||
summary_rect = summary_surface.get_rect()
|
||||
summary_x = (config['width'] - summary_rect.width) // 2
|
||||
overlay_surface.blit(summary_surface, (summary_x, summary_y))
|
||||
|
||||
# Instructions
|
||||
instruction_str = "Press 'P' to toggle event counter overlay"
|
||||
instruction_text = config['fonts']['instruction'].render(instruction_str, True, (180, 180, 180))
|
||||
overlay_surface.blit(instruction_text, (0, summary_y + int(20 * config['scale_factor'])))
|
||||
|
||||
|
||||
def draw_profile_table(overlay_surface, config, profile_data, profile_mode, symbols):
|
||||
"""Draw the profile mode table."""
|
||||
|
||||
# Prepare data
|
||||
sort_func = lambda x: x['total_ticks']
|
||||
sorted_data = sorted(profile_data, key=sort_func, reverse=True)
|
||||
total_ticks_all = sum(record['total_ticks'] for record in profile_data)
|
||||
profile_mode = "Exclusive" if profile_mode else "Inclusive"
|
||||
|
||||
headers = ["Function", "Calls", "Min", "Max", "Total", "Avg", "Cycles", "%"]
|
||||
proportions = [0.30, 0.08, 0.10, 0.10, 0.13, 0.10, 0.13, 0.05]
|
||||
col_widths = [config['width'] * prop for prop in proportions]
|
||||
|
||||
# Draw table structure
|
||||
draw_table(overlay_surface, config, f"Performance Profile ({profile_mode})", headers, col_widths)
|
||||
|
||||
# Draw data rows
|
||||
row_height = int(30 * config['scale_factor'])
|
||||
data_start_y = int(50 * config['scale_factor'] + 40 * config['scale_factor'] + 8 * config['scale_factor'])
|
||||
available_height = config['height'] - data_start_y - int(60 * config['scale_factor'])
|
||||
visible_rows = min(len(sorted_data), available_height // row_height)
|
||||
|
||||
for i in range(visible_rows):
|
||||
record = sorted_data[i]
|
||||
row_y = data_start_y + i * row_height
|
||||
|
||||
# Draw row background
|
||||
row_color = config['colors']['row_alt'] if i % 2 == 0 else config['colors']['row_normal']
|
||||
row_rect = (int(5 * config['scale_factor']), row_y,
|
||||
config['width'] - int(10 * config['scale_factor']), row_height)
|
||||
pygame.draw.rect(overlay_surface, row_color, row_rect)
|
||||
|
||||
# Function name
|
||||
name = addr_to_symbol(symbols, record['address']) if symbols else "<no symbols>"
|
||||
max_name_chars = int(col_widths[0] // (11 * config['scale_factor']))
|
||||
display_name = name if len(name) <= max_name_chars else name[:max_name_chars - 3] + "..."
|
||||
|
||||
# Calculate values
|
||||
call_count = record['call_count']
|
||||
min_ticks = record['min_ticks'] if call_count else 0
|
||||
max_ticks = record['max_ticks'] if call_count else 0
|
||||
total_ticks = record['total_ticks']
|
||||
avg_cycles = record['total_cycles'] // max(1, call_count)
|
||||
avg_ticks = total_ticks // max(1, call_count)
|
||||
percentage = (total_ticks / total_ticks_all * 100)
|
||||
|
||||
ticks_scale = ""
|
||||
if total_ticks > 1_000_000_000:
|
||||
total_ticks //= 1_000_000
|
||||
ticks_scale = "M"
|
||||
|
||||
row_data = [
|
||||
display_name,
|
||||
f"{call_count:,}",
|
||||
f"{min_ticks:,}",
|
||||
f"{max_ticks:,}",
|
||||
f"{total_ticks:,}{ticks_scale}",
|
||||
f"{avg_ticks:,}",
|
||||
f"{avg_cycles:,}",
|
||||
f"{percentage:.1f}%"
|
||||
]
|
||||
|
||||
# Determine row color based on percentage
|
||||
text_color = get_color_by_percentage(percentage, config['colors']['content_text'])
|
||||
|
||||
# Draw row data
|
||||
current_x = int(10 * config['scale_factor'])
|
||||
for j, (data, width) in enumerate(zip(row_data, col_widths)):
|
||||
text_surface = config['fonts']['content'].render(str(data), True, text_color)
|
||||
overlay_surface.blit(text_surface, (current_x, row_y + int(8 * config['scale_factor'])))
|
||||
|
||||
if j < len(row_data) - 1:
|
||||
sep_x = current_x + width - int(8 * config['scale_factor'])
|
||||
pygame.draw.line(overlay_surface, (60, 70, 85),
|
||||
(sep_x, row_y), (sep_x, row_y + row_height), 1)
|
||||
current_x += width
|
||||
|
||||
# Draw summary
|
||||
summary_y = config['height'] - int(50 * config['scale_factor'])
|
||||
total_calls = sum(record['call_count'] for record in profile_data)
|
||||
total_cycles = sum(record['total_cycles'] for record in profile_data)
|
||||
total_ticks_summary = sum(record['total_ticks'] for record in profile_data)
|
||||
|
||||
summary_text = (
|
||||
f"Profiles: {len(profile_data)} | "
|
||||
f"Total Calls: {total_calls:,} | "
|
||||
f"Total Ticks: {total_ticks_summary:,} | "
|
||||
f"Total Cycles: {total_cycles:,}"
|
||||
)
|
||||
|
||||
summary_surface = config['fonts']['summary'].render(summary_text, True, config['colors']['content_text'])
|
||||
summary_rect = summary_surface.get_rect()
|
||||
summary_x = (config['width'] - summary_rect.width) // 2
|
||||
overlay_surface.blit(summary_surface, (summary_x, summary_y))
|
||||
|
||||
# Instructions
|
||||
instruction_str = "Press 'P' to toggle event counter overlay"
|
||||
instruction_text = config['fonts']['instruction'].render(instruction_str, True, (180, 180, 180))
|
||||
overlay_surface.blit(instruction_text, (0, summary_y + int(20 * config['scale_factor'])))
|
||||
|
||||
def draw_profile_overlay(screen, screen_width, screen_height, profile_data,
|
||||
profile_mode, profile_type, scale, symbols, alpha=250):
|
||||
"""Main entry point for drawing the profile overlay."""
|
||||
# Calculate dimensions and create surface
|
||||
base_width, base_height = 800, 800
|
||||
screen_width *= scale
|
||||
screen_height *= scale
|
||||
scale_factor = min(screen_width / base_width, screen_height / base_height)
|
||||
|
||||
overlay_surface = pygame.Surface((screen_width, screen_height), pygame.SRCALPHA)
|
||||
overlay_surface.set_alpha(alpha)
|
||||
|
||||
# Setup common configuration
|
||||
config = {
|
||||
'width': screen_width,
|
||||
'height': screen_height,
|
||||
'scale_factor': scale_factor,
|
||||
'colors': {
|
||||
'bg': (40, 50, 65),
|
||||
'border': (70, 80, 100),
|
||||
'header_bg': (60, 80, 120),
|
||||
'header_text': (255, 255, 255),
|
||||
'content_text': (220, 220, 220),
|
||||
'row_alt': (35, 45, 60),
|
||||
'row_normal': (45, 55, 70)
|
||||
},
|
||||
'fonts': {
|
||||
'title': pygame.font.SysFont("arial", int(28 * scale_factor), bold=True),
|
||||
'header': pygame.font.SysFont("monospace", int(20 * scale_factor), bold=True),
|
||||
'content': pygame.font.SysFont("monospace", int(18 * scale_factor)),
|
||||
'summary': pygame.font.SysFont("arial", int(20 * scale_factor)),
|
||||
'instruction': pygame.font.SysFont("arial", int(22 * scale_factor))
|
||||
}
|
||||
}
|
||||
|
||||
# Draw based on mode
|
||||
if profile_type == 1:
|
||||
draw_profile_table(overlay_surface, config, profile_data, profile_mode, symbols)
|
||||
elif profile_type == 2:
|
||||
draw_event_table(overlay_surface, config, profile_data, profile_mode, symbols)
|
||||
|
||||
screen.blit(overlay_surface, (0, 0))
|
||||
|
||||
def pygame_test(port, script, poll_rate, scale, benchmark, symbols):
|
||||
# init pygame
|
||||
pygame.init()
|
||||
pyopenmv.disconnect()
|
||||
@ -67,27 +415,63 @@ def pygame_test(port, poll_rate, scale, benchmark):
|
||||
pyopenmv.set_timeout(1*2) # SD Cards can cause big hicups.
|
||||
pyopenmv.stop_script()
|
||||
pyopenmv.enable_fb(True)
|
||||
pyopenmv.exec_script(bench_script if benchmark else test_script)
|
||||
pyopenmv.reset_profiler()
|
||||
|
||||
# Configure some event counters.
|
||||
pyopenmv.set_event_counter(0, 0x0039)
|
||||
pyopenmv.set_event_counter(1, 0x0023)
|
||||
pyopenmv.set_event_counter(2, 0x0024)
|
||||
pyopenmv.set_event_counter(3, 0x0001)
|
||||
pyopenmv.set_event_counter(4, 0x0003)
|
||||
pyopenmv.set_event_counter(5, 0xC102)
|
||||
pyopenmv.set_event_counter(6, 0x02CC)
|
||||
pyopenmv.set_event_counter(7, 0xC303)
|
||||
|
||||
pyopenmv.exec_script(script)
|
||||
|
||||
# init screen
|
||||
running = True
|
||||
screen = None
|
||||
|
||||
# Profiling control
|
||||
profile_type = 0
|
||||
profile_mode = 0
|
||||
profile_data = []
|
||||
last_profile_read = 0
|
||||
|
||||
clock = pygame.time.Clock()
|
||||
fps_clock = pygame.time.Clock()
|
||||
font = pygame.font.SysFont("monospace", 30)
|
||||
|
||||
if benchmark:
|
||||
if not benchmark:
|
||||
pygame.display.set_caption("OpenMV Camera")
|
||||
else:
|
||||
pygame.display.set_caption("OpenMV Camera (Benchmark)")
|
||||
screen = pygame.display.set_mode((640, 120), pygame.DOUBLEBUF, 32)
|
||||
|
||||
try:
|
||||
while running:
|
||||
# Read state
|
||||
w, h, data, size, text, fmt = pyopenmv.read_state()
|
||||
w, h, data, size, text, fmt, profiling = pyopenmv.read_state()
|
||||
|
||||
if text is not None:
|
||||
print(text, end="")
|
||||
|
||||
# Read profiling data (maximum 10Hz)
|
||||
if profiling and profile_type:
|
||||
current_time = time.time()
|
||||
if current_time - last_profile_read >= 0.1: # 10Hz = 0.1s interval
|
||||
tmp_data = pyopenmv.read_profile()
|
||||
if tmp_data:
|
||||
profile_data = tmp_data
|
||||
last_profile_read = current_time
|
||||
|
||||
#if profile_data:
|
||||
# for r in profile_data:
|
||||
# print(f"Func: {addr_to_symbol(symbols, r['address'])}@0x{r['address']:x} ")
|
||||
# print(f"Call: {addr_to_symbol(symbols, r['caller'])}@0x{r['caller']:x}")
|
||||
# sys.exit(0)
|
||||
|
||||
if data is not None:
|
||||
fps = fps_clock.get_fps()
|
||||
|
||||
@ -104,7 +488,14 @@ def pygame_test(port, poll_rate, scale, benchmark):
|
||||
screen.fill((0, 0, 0))
|
||||
else:
|
||||
screen.blit(image, (0, 0))
|
||||
screen.blit(font.render(f"{fps:.2f} FPS {fps * size / 1024**2:.2f} MB/s {w}x{h} {fmt}", 5, (255, 0, 0)), (0, 0))
|
||||
|
||||
# FPS text
|
||||
fps_text = f"{fps:.2f} FPS {fps * size / 1024**2:.2f} MB/s {w}x{h} {fmt}"
|
||||
screen.blit(font.render(fps_text, 5, (255, 0, 0)), (0, 0))
|
||||
|
||||
# Draw profile overlay if enabled
|
||||
if profile_type and profile_data:
|
||||
draw_profile_overlay(screen, w, h, profile_data, profile_mode, profile_type, scale, symbols)
|
||||
|
||||
# update display
|
||||
pygame.display.flip()
|
||||
@ -116,10 +507,18 @@ def pygame_test(port, poll_rate, scale, benchmark):
|
||||
elif event.type == pygame.KEYDOWN:
|
||||
if event.key == pygame.K_ESCAPE:
|
||||
running = False
|
||||
if event.key == pygame.K_c:
|
||||
elif event.key == pygame.K_c:
|
||||
pygame.image.save(image, "capture.png")
|
||||
|
||||
elif event.key == pygame.K_p:
|
||||
profile_type = (profile_type + 1) % 3
|
||||
elif event.key == pygame.K_m:
|
||||
profile_mode = not profile_mode
|
||||
pyopenmv.set_profile_mode(profile_mode)
|
||||
elif event.key == pygame.K_r:
|
||||
pyopenmv.reset_profiler()
|
||||
|
||||
clock.tick(1000//poll_rate)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
@ -128,9 +527,38 @@ def pygame_test(port, poll_rate, scale, benchmark):
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description='pyopenmv module')
|
||||
parser.add_argument('--port', action = 'store', help='OpenMV camera port (default /dev/ttyACM0)', default='/dev/ttyACM0', )
|
||||
parser.add_argument('--port', action = 'store', help='Serial port (dev/ttyACM0)', default='/dev/ttyACM0')
|
||||
parser.add_argument("--script", action = "store", default=None, help = "Script file")
|
||||
parser.add_argument('--poll', action = 'store', help='Poll rate (default 4ms)', default=4, type=int)
|
||||
parser.add_argument('--bench', action = 'store_true', help='Run throughput benchmark.', default=False)
|
||||
parser.add_argument('--scale', action = 'store', help='Set frame scaling factor (default 4x).', default=4, type=int)
|
||||
parser.add_argument('--firmware', action = 'store', help='Firmware for address to symbol', default=None)
|
||||
|
||||
args = parser.parse_args()
|
||||
pygame_test(args.port, args.poll, args.scale, args.bench)
|
||||
if args.script is not None:
|
||||
with open(args.script) as f:
|
||||
args.script = f.read()
|
||||
else:
|
||||
args.script = bench_script if args.bench else test_script
|
||||
|
||||
symbols = []
|
||||
|
||||
if args.firmware:
|
||||
from elftools.elf.elffile import ELFFile
|
||||
|
||||
with open(args.firmware, 'rb') as f:
|
||||
elf = ELFFile(f)
|
||||
symtab = elf.get_section_by_name('.symtab')
|
||||
if not symtab:
|
||||
raise ValueError("No symbol table found in ELF.")
|
||||
|
||||
for sym in symtab.iter_symbols():
|
||||
addr = sym['st_value']
|
||||
size = sym['st_size']
|
||||
name = sym.name
|
||||
if name and size > 0: # ignore empty symbols
|
||||
symbols.append((addr, addr + size, name))
|
||||
|
||||
symbols.sort()
|
||||
|
||||
pygame_test(args.port, args.script, args.poll, args.scale, args.bench, symbols)
|
||||
|
Loading…
Reference in New Issue
Block a user