mirror of
https://github.com/esphome/esphome.git
synced 2026-09-15 00:58:40 +00:00
Merge remote-tracking branch 'upstream/millis64-zephyr-native' into integration
This commit is contained in:
+39
-10
@@ -686,7 +686,7 @@ jobs:
|
||||
ram_usage: ${{ steps.extract.outputs.ram_usage }}
|
||||
flash_usage: ${{ steps.extract.outputs.flash_usage }}
|
||||
cache_hit: ${{ steps.cache-memory-analysis.outputs.cache-hit }}
|
||||
skip: ${{ steps.check-script.outputs.skip }}
|
||||
skip: ${{ steps.check-script.outputs.skip || steps.check-tests.outputs.skip }}
|
||||
steps:
|
||||
- name: Check out target branch
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
@@ -705,10 +705,39 @@ jobs:
|
||||
echo "::warning::ci_memory_impact_extract.py not found on target branch, skipping memory impact analysis"
|
||||
fi
|
||||
|
||||
# All remaining steps only run if script exists
|
||||
# Check if test files exist on the target branch for the requested
|
||||
# components and platform. When a PR adds new test files for a platform,
|
||||
# the target branch won't have them yet, so skip instead of failing.
|
||||
# This check must be done here (not in determine-jobs.py) because
|
||||
# determine-jobs runs on the PR branch and cannot see what the target
|
||||
# branch has.
|
||||
- name: Check for test files on target branch
|
||||
id: check-tests
|
||||
if: steps.check-script.outputs.skip != 'true'
|
||||
run: |
|
||||
components='${{ toJSON(fromJSON(needs.determine-jobs.outputs.memory_impact).components) }}'
|
||||
platform="${{ fromJSON(needs.determine-jobs.outputs.memory_impact).platform }}"
|
||||
found=false
|
||||
for component in $(echo "$components" | jq -r '.[]'); do
|
||||
# Check for test files matching the platform (test.platform.yaml or test-*.platform.yaml)
|
||||
for f in tests/components/${component}/test*.${platform}.yaml; do
|
||||
if [ -f "$f" ]; then
|
||||
found=true
|
||||
break 2
|
||||
fi
|
||||
done
|
||||
done
|
||||
if [ "$found" = false ]; then
|
||||
echo "skip=true" >> $GITHUB_OUTPUT
|
||||
echo "::warning::No test files found on target branch for platform ${platform}, skipping memory impact analysis"
|
||||
else
|
||||
echo "skip=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
# All remaining steps only run if script and tests exist
|
||||
- name: Generate cache key
|
||||
id: cache-key
|
||||
if: steps.check-script.outputs.skip != 'true'
|
||||
if: steps.check-script.outputs.skip != 'true' && steps.check-tests.outputs.skip != 'true'
|
||||
run: |
|
||||
# Get the commit SHA of the target branch
|
||||
target_sha=$(git rev-parse HEAD)
|
||||
@@ -735,14 +764,14 @@ jobs:
|
||||
|
||||
- name: Restore cached memory analysis
|
||||
id: cache-memory-analysis
|
||||
if: steps.check-script.outputs.skip != 'true'
|
||||
if: steps.check-script.outputs.skip != 'true' && steps.check-tests.outputs.skip != 'true'
|
||||
uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||
with:
|
||||
path: memory-analysis-target.json
|
||||
key: ${{ steps.cache-key.outputs.cache-key }}
|
||||
|
||||
- name: Cache status
|
||||
if: steps.check-script.outputs.skip != 'true'
|
||||
if: steps.check-script.outputs.skip != 'true' && steps.check-tests.outputs.skip != 'true'
|
||||
run: |
|
||||
if [ "${{ steps.cache-memory-analysis.outputs.cache-hit }}" == "true" ]; then
|
||||
echo "✓ Cache hit! Using cached memory analysis results."
|
||||
@@ -752,21 +781,21 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Restore Python
|
||||
if: steps.check-script.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true'
|
||||
if: steps.check-script.outputs.skip != 'true' && steps.check-tests.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true'
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
|
||||
- name: Cache platformio
|
||||
if: steps.check-script.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true'
|
||||
if: steps.check-script.outputs.skip != 'true' && steps.check-tests.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true'
|
||||
uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: platformio-memory-${{ fromJSON(needs.determine-jobs.outputs.memory_impact).platform }}-${{ hashFiles('platformio.ini') }}
|
||||
|
||||
- name: Build, compile, and analyze memory
|
||||
if: steps.check-script.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true'
|
||||
if: steps.check-script.outputs.skip != 'true' && steps.check-tests.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true'
|
||||
id: build
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
@@ -800,7 +829,7 @@ jobs:
|
||||
--platform "$platform"
|
||||
|
||||
- name: Save memory analysis to cache
|
||||
if: steps.check-script.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true' && steps.build.outcome == 'success'
|
||||
if: steps.check-script.outputs.skip != 'true' && steps.check-tests.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true' && steps.build.outcome == 'success'
|
||||
uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||
with:
|
||||
path: memory-analysis-target.json
|
||||
@@ -808,7 +837,7 @@ jobs:
|
||||
|
||||
- name: Extract memory usage for outputs
|
||||
id: extract
|
||||
if: steps.check-script.outputs.skip != 'true'
|
||||
if: steps.check-script.outputs.skip != 'true' && steps.check-tests.outputs.skip != 'true'
|
||||
run: |
|
||||
if [ -f memory-analysis-target.json ]; then
|
||||
ram=$(jq -r '.ram_bytes' memory-analysis-target.json)
|
||||
|
||||
@@ -20,7 +20,11 @@ uint32_t IRAM_ATTR HOT millis() {
|
||||
uint32_t ms = round(spec.tv_nsec / 1e6);
|
||||
return ((uint32_t) seconds) * 1000U + ms;
|
||||
}
|
||||
uint64_t millis_64() { return App.scheduler.millis_64_impl_(millis()); }
|
||||
uint64_t millis_64() {
|
||||
struct timespec spec;
|
||||
clock_gettime(CLOCK_MONOTONIC, &spec);
|
||||
return static_cast<uint64_t>(spec.tv_sec) * 1000ULL + static_cast<uint64_t>(spec.tv_nsec) / 1000000ULL;
|
||||
}
|
||||
void HOT delay(uint32_t ms) {
|
||||
struct timespec ts;
|
||||
ts.tv_sec = ms / 1000;
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include <zephyr/drivers/watchdog.h>
|
||||
#include <zephyr/sys/reboot.h>
|
||||
#include <zephyr/random/random.h>
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/defines.h"
|
||||
@@ -17,8 +16,8 @@ static const device *const WDT = DEVICE_DT_GET(DT_ALIAS(watchdog0));
|
||||
#endif
|
||||
|
||||
void yield() { ::k_yield(); }
|
||||
uint32_t millis() { return k_ticks_to_ms_floor32(k_uptime_ticks()); }
|
||||
uint64_t millis_64() { return App.scheduler.millis_64_impl_(millis()); }
|
||||
uint32_t millis() { return static_cast<uint32_t>(millis_64()); }
|
||||
uint64_t millis_64() { return static_cast<uint64_t>(k_uptime_get()); }
|
||||
uint32_t micros() { return k_ticks_to_us_floor32(k_uptime_ticks()); }
|
||||
void delayMicroseconds(uint32_t us) { ::k_usleep(us); }
|
||||
void delay(uint32_t ms) { ::k_msleep(ms); }
|
||||
|
||||
@@ -744,8 +744,17 @@ void Application::yield_with_select_(uint32_t delay_ms) {
|
||||
#error "Application placement new requires Itanium C++ ABI (GCC/Clang)"
|
||||
#endif
|
||||
static_assert(std::is_default_constructible<Application>::value, "Application must be default-constructible");
|
||||
// __USER_LABEL_PREFIX__ is "_" on Mach-O (macOS) and empty on ELF (embedded targets).
|
||||
// String literal concatenation produces the correct platform-specific mangled symbol.
|
||||
// Two-level macro needed: # stringifies before expansion, so the
|
||||
// indirection forces __USER_LABEL_PREFIX__ to expand first.
|
||||
#define ESPHOME_STRINGIFY_IMPL_(x) #x
|
||||
#define ESPHOME_STRINGIFY_(x) ESPHOME_STRINGIFY_IMPL_(x)
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
alignas(Application) char app_storage[sizeof(Application)] asm("_ZN7esphome3AppE");
|
||||
alignas(Application) char app_storage[sizeof(Application)] asm(
|
||||
ESPHOME_STRINGIFY_(__USER_LABEL_PREFIX__) "_ZN7esphome3AppE");
|
||||
#undef ESPHOME_STRINGIFY_
|
||||
#undef ESPHOME_STRINGIFY_IMPL_
|
||||
|
||||
#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE)
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ static constexpr size_t MAX_POOL_SIZE = 5;
|
||||
// Set to 5 to match the pool size - when we have as many cancelled items as our
|
||||
// pool can hold, it's time to clean up and recycle them.
|
||||
static constexpr uint32_t MAX_LOGICALLY_DELETED_ITEMS = 5;
|
||||
#ifndef USE_ESP32
|
||||
#if !defined(USE_ESP32) && !defined(USE_HOST) && !defined(USE_ZEPHYR)
|
||||
// Half the 32-bit range - used to detect rollovers vs normal time progression
|
||||
static constexpr uint32_t HALF_MAX_UINT32 = std::numeric_limits<uint32_t>::max() / 2;
|
||||
#endif
|
||||
@@ -475,12 +475,12 @@ void HOT Scheduler::call(uint32_t now) {
|
||||
if (now_64 - last_print > 2000) {
|
||||
last_print = now_64;
|
||||
std::vector<SchedulerItemPtr> old_items;
|
||||
#if !defined(USE_ESP32) && defined(ESPHOME_THREAD_MULTI_ATOMICS)
|
||||
#if !defined(USE_ESP32) && !defined(USE_HOST) && !defined(USE_ZEPHYR) && defined(ESPHOME_THREAD_MULTI_ATOMICS)
|
||||
const auto last_dbg = this->last_millis_.load(std::memory_order_relaxed);
|
||||
const auto major_dbg = this->millis_major_.load(std::memory_order_relaxed);
|
||||
ESP_LOGD(TAG, "Items: count=%zu, pool=%zu, now=%" PRIu64 " (%" PRIu16 ", %" PRIu32 ")", this->items_.size(),
|
||||
this->scheduler_item_pool_.size(), now_64, major_dbg, last_dbg);
|
||||
#elif !defined(USE_ESP32)
|
||||
#elif !defined(USE_ESP32) && !defined(USE_HOST) && !defined(USE_ZEPHYR)
|
||||
ESP_LOGD(TAG, "Items: count=%zu, pool=%zu, now=%" PRIu64 " (%" PRIu16 ", %" PRIu32 ")", this->items_.size(),
|
||||
this->scheduler_item_pool_.size(), now_64, this->millis_major_, this->last_millis_);
|
||||
#else
|
||||
@@ -714,7 +714,7 @@ bool HOT Scheduler::cancel_item_locked_(Component *component, NameType name_type
|
||||
return total_cancelled > 0;
|
||||
}
|
||||
|
||||
#ifndef USE_ESP32
|
||||
#if !defined(USE_ESP32) && !defined(USE_HOST) && !defined(USE_ZEPHYR)
|
||||
uint64_t Scheduler::millis_64_impl_(uint32_t now) {
|
||||
// THREAD SAFETY NOTE:
|
||||
// This function has three implementations, based on the precompiler flags
|
||||
@@ -872,7 +872,7 @@ uint64_t Scheduler::millis_64_impl_(uint32_t now) {
|
||||
"No platform threading model defined. One of ESPHOME_THREAD_SINGLE, ESPHOME_THREAD_MULTI_NO_ATOMICS, or ESPHOME_THREAD_MULTI_ATOMICS must be defined."
|
||||
#endif
|
||||
}
|
||||
#endif // not USE_ESP32
|
||||
#endif // !USE_ESP32 && !USE_HOST && !USE_ZEPHYR
|
||||
|
||||
bool HOT Scheduler::SchedulerItem::cmp(const SchedulerItemPtr &a, const SchedulerItemPtr &b) {
|
||||
// High bits are almost always equal (change only on 32-bit rollover ~49 days)
|
||||
|
||||
@@ -286,7 +286,7 @@ class Scheduler {
|
||||
// On ESP32, ignores now and uses esp_timer_get_time() directly (native 64-bit).
|
||||
// On non-ESP32, extends now to 64-bit using rollover tracking.
|
||||
uint64_t millis_64_from_(uint32_t now) {
|
||||
#ifdef USE_ESP32
|
||||
#if defined(USE_ESP32) || defined(USE_HOST) || defined(USE_ZEPHYR)
|
||||
(void) now;
|
||||
return millis_64();
|
||||
#else
|
||||
@@ -294,10 +294,9 @@ class Scheduler {
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifndef USE_ESP32
|
||||
// On non-ESP32 platforms, millis_64() HAL function delegates to this method
|
||||
// which tracks 32-bit millis() rollover using millis_major_ and last_millis_.
|
||||
// On ESP32, millis_64() uses esp_timer_get_time() directly.
|
||||
#if !defined(USE_ESP32) && !defined(USE_HOST) && !defined(USE_ZEPHYR)
|
||||
// On platforms without native 64-bit time, millis_64() HAL function delegates to this
|
||||
// method which tracks 32-bit millis() rollover using millis_major_ and last_millis_.
|
||||
friend uint64_t millis_64();
|
||||
uint64_t millis_64_impl_(uint32_t now);
|
||||
#endif
|
||||
@@ -567,8 +566,8 @@ class Scheduler {
|
||||
// to synchronize between tasks (see https://github.com/esphome/backlog/issues/52)
|
||||
std::vector<SchedulerItemPtr> scheduler_item_pool_;
|
||||
|
||||
#ifndef USE_ESP32
|
||||
// On ESP32, millis_64() uses esp_timer_get_time() directly; no rollover tracking needed.
|
||||
#if !defined(USE_ESP32) && !defined(USE_HOST) && !defined(USE_ZEPHYR)
|
||||
// On platforms with native 64-bit time (ESP32, Host, Zephyr), no rollover tracking needed.
|
||||
// On other platforms, these fields track 32-bit millis() rollover for millis_64_impl_().
|
||||
#ifdef ESPHOME_THREAD_MULTI_ATOMICS
|
||||
/*
|
||||
@@ -598,7 +597,7 @@ class Scheduler {
|
||||
#else /* not ESPHOME_THREAD_MULTI_ATOMICS */
|
||||
uint16_t millis_major_{0};
|
||||
#endif /* else ESPHOME_THREAD_MULTI_ATOMICS */
|
||||
#endif /* not USE_ESP32 */
|
||||
#endif /* !USE_ESP32 && !USE_HOST && !USE_ZEPHYR */
|
||||
};
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
Reference in New Issue
Block a user