Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ If you need runtime logs, `mise install-emulator --logs` runs it in an emulator
## Debugging

- C: `APP_LOG(APP_LOG_LEVEL_DEBUG, "msg", args)`
- Heap probes: use `MEMORY_LOG_HEAP("tag")` for dev-only `MEM|...` logs around lifecycle and redraw checkpoints.
- JS: `console.log("msg")`

## JavaScript Conventions
Expand All @@ -16,4 +17,4 @@ If you need runtime logs, `mise install-emulator --logs` runs it in an emulator

## Supabase migrations

Never write `migrations/` files manually. Edit declarative `schemas/` and generate migrations as-needed before commits with `supabase db diff -f <label>`
Never write `migrations/` files manually. Edit declarative `schemas/` and generate migrations as-needed before commits with `supabase db diff -f <label>`
8 changes: 8 additions & 0 deletions src/c/appendix/app_message.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "c/layers/calendar_layer.h"
#include "c/layers/calendar_status_layer.h"
#include "c/windows/main_window.h"
#include "memory_log.h"

static void inbox_received_callback(DictionaryIterator *iterator, void *context) {
APP_LOG(APP_LOG_LEVEL_INFO, "Message received!");
Expand Down Expand Up @@ -44,6 +45,12 @@ static void inbox_received_callback(DictionaryIterator *iterator, void *context)
persist_set_forecast_start((time_t)forecast_start_tuple->value->int32);
const int num_entries = ((int)num_entries_tuple->value->int32);
persist_set_num_entries(num_entries);
#ifdef FCW2_ENABLE_MEMORY_LOGGING
APP_LOG(APP_LOG_LEVEL_DEBUG, "MEM|forecast_payload|entries=%d|free=%lu|used=%lu",
num_entries,
(unsigned long)heap_bytes_free(),
(unsigned long)heap_bytes_used());
#endif
int16_t *temp_data = (int16_t*) temp_trend_tuple->value->data;
persist_set_temp_trend(temp_data, num_entries);
uint8_t *precip_data = (uint8_t*) precip_trend_tuple->value->data;
Expand Down Expand Up @@ -126,4 +133,5 @@ void app_message_init() {
const int inbox_size = 256;
const int outbox_size = 0;
app_message_open(inbox_size, outbox_size);
MEMORY_LOG_HEAP("after_app_message_open");
}
3 changes: 3 additions & 0 deletions src/c/appendix/config.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "config.h"
#include "persist.h"
#include "math.h"
#include "memory_log.h"

// NOTE: g_config is a global config variable

Expand Down Expand Up @@ -38,12 +39,14 @@ static void config_read_or_default(Config *config) {
void config_load() {
g_config = (Config*) malloc(sizeof(Config));
config_read_or_default(g_config);
MEMORY_LOG_HEAP("after_config_load");
}

void config_refresh() {
free(g_config); // Clear out the old config
g_config = (Config*) malloc(sizeof(Config));
config_read_or_default(g_config); // Then reload
MEMORY_LOG_HEAP("after_config_refresh");
}

void config_unload() {
Expand Down
105 changes: 105 additions & 0 deletions src/c/appendix/memory_log.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#pragma once

#include <pebble.h>

typedef struct
{
const char *scope_tag;
unsigned long min_free;
unsigned long used_at_min;
} MemoryHeapProbe;

/*
* MEMORY_LOG_HEAP(tag) records a one-off heap snapshot.
*
* Use probe helpers when you want to track heap behavior across one chunk of work.
*
* MemoryHeapProbe probe = MEMORY_HEAP_PROBE_START("some_work");
* MEMORY_HEAP_PROBE_SAMPLE("step_1", &probe);
* MEMORY_HEAP_PROBE_SAMPLE("step_2", &probe);
* MEMORY_HEAP_PROBE_SAMPLE("step_3", &probe);
* MEMORY_HEAP_PROBE_LOG_MIN(&probe);
*
* Meaning:
* - START: capture the starting heap snapshot for this work.
* - SAMPLE: capture checkpoint snapshots while work is running.
* - LOG_MIN: print the lowest free-heap point seen across this probe.
*
* The scope name is provided once at START and reused for the summary log.
* SAMPLE logs are emitted as "scope:checkpoint", so checkpoint strings should
* describe where the sample was taken.
*
* In release builds these probe macros compile to no-ops.
*/

#ifdef FCW2_ENABLE_MEMORY_LOGGING
static inline MemoryHeapProbe memory_heap_probe_start_impl(const char *scope_tag)
{
MemoryHeapProbe probe = {
.scope_tag = scope_tag,
.min_free = (unsigned long)heap_bytes_free(),
.used_at_min = (unsigned long)heap_bytes_used()};

APP_LOG(APP_LOG_LEVEL_DEBUG, "MEM|%s:start|free=%lu|used=%lu",
scope_tag,
probe.min_free,
probe.used_at_min);

return probe;
}

static inline void memory_heap_probe_sample_impl(const char *checkpoint_tag, MemoryHeapProbe *probe)
{
const unsigned long free_now = (unsigned long)heap_bytes_free();
const unsigned long used_now = (unsigned long)heap_bytes_used();
if (free_now < probe->min_free)
{
probe->min_free = free_now;
probe->used_at_min = used_now;
}

APP_LOG(APP_LOG_LEVEL_DEBUG, "MEM|%s:%s|free=%lu|used=%lu",
probe->scope_tag,
checkpoint_tag,
free_now,
used_now);
}

static inline void memory_heap_probe_log_min_impl(const MemoryHeapProbe *probe)
{
APP_LOG(APP_LOG_LEVEL_DEBUG, "MEM|%s:min_free|free=%lu|used=%lu",
probe->scope_tag,
probe->min_free,
probe->used_at_min);
}

#define MEMORY_LOG_HEAP(tag) \
APP_LOG(APP_LOG_LEVEL_DEBUG, "MEM|%s|free=%lu|used=%lu", \
tag, \
(unsigned long)heap_bytes_free(), \
(unsigned long)heap_bytes_used())
#define MEMORY_HEAP_PROBE_START(scope_tag) \
memory_heap_probe_start_impl(scope_tag)
#define MEMORY_HEAP_PROBE_SAMPLE(tag, probe_ptr) \
memory_heap_probe_sample_impl(tag, probe_ptr)
#define MEMORY_HEAP_PROBE_LOG_MIN(probe_ptr) \
memory_heap_probe_log_min_impl(probe_ptr)
#else
#define MEMORY_LOG_HEAP(tag) \
do \
{ \
} while (0)
#define MEMORY_HEAP_PROBE_START(scope_tag) \
((void)(scope_tag), (MemoryHeapProbe){0, 0, 0})
#define MEMORY_HEAP_PROBE_SAMPLE(tag, probe_ptr) \
do \
{ \
(void)(tag); \
(void)(probe_ptr); \
} while (0)
#define MEMORY_HEAP_PROBE_LOG_MIN(probe_ptr) \
do \
{ \
(void)(probe_ptr); \
} while (0)
#endif
4 changes: 4 additions & 0 deletions src/c/layers/battery_layer.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "battery_layer.h"
#include "c/appendix/persist.h"
#include "c/appendix/memory_log.h"

#define BATTERY_NUB_W 2
#define BATTERY_NUB_H 6
Expand Down Expand Up @@ -87,15 +88,18 @@ void battery_layer_create(Layer* parent_layer, GRect frame) {
layer_set_update_proc(s_battery_layer, battery_update_proc);
battery_state_service_subscribe(battery_state_handler);
layer_add_child(parent_layer, s_battery_layer);
MEMORY_LOG_HEAP("after_battery_layer_create");
}

void battery_layer_refresh() {
layer_mark_dirty(s_battery_layer);
}

void battery_layer_destroy() {
MEMORY_LOG_HEAP("battery_layer_destroy:before");
battery_state_service_unsubscribe();
free(s_battery_palette);
gbitmap_destroy(s_battery_power_bitmap);
layer_destroy(s_battery_layer);
MEMORY_LOG_HEAP("battery_layer_destroy:after");
}
4 changes: 4 additions & 0 deletions src/c/layers/calendar_layer.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "calendar_layer.h"
#include "c/appendix/config.h"
#include "c/appendix/memory_log.h"
#include <time.h>

#define NUM_WEEKS 3
Expand Down Expand Up @@ -128,6 +129,7 @@ void calendar_layer_create(Layer* parent_layer, GRect frame) {
layer_set_update_proc(s_calendar_layer, calendar_update_proc);
calendar_layer_refresh();
layer_add_child(parent_layer, s_calendar_layer);
MEMORY_LOG_HEAP("after_calendar_layer_create");
}


Expand Down Expand Up @@ -168,8 +170,10 @@ void calendar_layer_refresh() {
}

void calendar_layer_destroy() {
MEMORY_LOG_HEAP("calendar_layer_destroy:before");
for (int i = 0; i < NUM_WEEKS * DAYS_PER_WEEK; ++i) {
text_layer_destroy(s_calendar_text_layers[i]);
}
layer_destroy(s_calendar_layer);
MEMORY_LOG_HEAP("calendar_layer_destroy:after");
}
4 changes: 4 additions & 0 deletions src/c/layers/calendar_status_layer.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "calendar_status_layer.h"
#include "battery_layer.h"
#include "c/appendix/config.h"
#include "c/appendix/memory_log.h"

#define MONTH_FONT_OFFSET 7
#define BATTERY_W 29
Expand Down Expand Up @@ -86,6 +87,7 @@ void calendar_status_layer_create(Layer* parent_layer, GRect frame) {
layer_add_child(s_calendar_status_layer, text_layer_get_layer(s_calendar_month_layer));
battery_layer_create(s_calendar_status_layer, GRect(w - BATTERY_W - PADDING, 1, BATTERY_W, BATTERY_H));
layer_add_child(parent_layer, s_calendar_status_layer);
MEMORY_LOG_HEAP("after_calendar_status_layer_create");
}

void bluetooth_icons_refresh(bool connected) {
Expand Down Expand Up @@ -126,6 +128,7 @@ void calendar_status_layer_refresh() {
}

void calendar_status_layer_destroy() {
MEMORY_LOG_HEAP("calendar_status_layer_destroy:before");
battery_layer_destroy();
free(s_bt_palette);
free(s_bt_disconnect_palette);
Expand All @@ -137,4 +140,5 @@ void calendar_status_layer_destroy() {
bitmap_layer_destroy(s_bt_bitmap_layer);
bitmap_layer_destroy(s_bt_disconnect_bitmap_layer);
layer_destroy(s_calendar_status_layer);
MEMORY_LOG_HEAP("calendar_status_layer_destroy:after");
}
27 changes: 27 additions & 0 deletions src/c/layers/forecast_layer.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "c/appendix/persist.h"
#include "c/appendix/math.h"
#include "c/appendix/config.h"
#include "c/appendix/memory_log.h"

#define LEFT_AXIS_LABEL_STRIP_MIN_W 15
#define LEFT_AXIS_LABEL_TO_GRAPH_GAP 2
Expand Down Expand Up @@ -440,6 +441,7 @@ static void draw_night_boundaries_over_precip(GContext *ctx, GRect graph_plot_re

static void forecast_update_proc(Layer *layer, GContext *ctx)
{
MEMORY_LOG_HEAP("forecast_update:enter");
GRect bounds = layer_get_bounds(layer);
RenderSpec render_spec = make_render_spec();
ForecastLayout layout = compute_layout(bounds);
Expand All @@ -450,10 +452,12 @@ static void forecast_update_proc(Layer *layer, GContext *ctx)

// Load data from storage
const int num_entries = persist_get_num_entries();
MemoryHeapProbe redraw_probe = MEMORY_HEAP_PROBE_START("forecast_update");
if (num_entries < 2)
{
graphics_context_set_fill_color(ctx, GColorBlack);
graphics_fill_rect(ctx, bounds, 0, GCornerNone);
MEMORY_LOG_HEAP("forecast_update:exit");
return;
}

Expand Down Expand Up @@ -538,10 +542,14 @@ static void forecast_update_proc(Layer *layer, GContext *ctx)
GPathInfo path_info_precip = {
.num_points = num_entries + 2,
.points = points_precip};
MEMORY_HEAP_PROBE_SAMPLE("before_precip_path_create", &redraw_probe);
GPath *path_precip_area_under = gpath_create(&path_info_precip);
MEMORY_HEAP_PROBE_SAMPLE("after_precip_path_create", &redraw_probe);
graphics_context_set_fill_color(ctx, PRECIP_FILL_COLOR);
gpath_draw_filled(ctx, path_precip_area_under);
MEMORY_HEAP_PROBE_SAMPLE("before_precip_path_destroy", &redraw_probe);
gpath_destroy(path_precip_area_under);
MEMORY_HEAP_PROBE_SAMPLE("after_precip_path_destroy", &redraw_probe);

if (render_spec.draw_night_overlay)
{
Expand All @@ -553,21 +561,29 @@ static void forecast_update_proc(Layer *layer, GContext *ctx)

// Draw the precipitation line
path_info_precip.num_points = num_entries;
MEMORY_HEAP_PROBE_SAMPLE("before_precip_top_create", &redraw_probe);
GPath *path_precip_top = gpath_create(&path_info_precip);
MEMORY_HEAP_PROBE_SAMPLE("after_precip_top_create", &redraw_probe);
graphics_context_set_stroke_color(ctx, GColorPictonBlue);
graphics_context_set_stroke_width(ctx, 1);
gpath_draw_outline_open(ctx, path_precip_top);
MEMORY_HEAP_PROBE_SAMPLE("before_precip_top_destroy", &redraw_probe);
gpath_destroy(path_precip_top);
MEMORY_HEAP_PROBE_SAMPLE("after_precip_top_destroy", &redraw_probe);

// Draw the temperature line
GPathInfo path_info_temp = {
.num_points = num_entries,
.points = points_temp};
MEMORY_HEAP_PROBE_SAMPLE("before_temp_path_create", &redraw_probe);
GPath *path_temp = gpath_create(&path_info_temp);
MEMORY_HEAP_PROBE_SAMPLE("after_temp_path_create", &redraw_probe);
graphics_context_set_stroke_color(ctx, PBL_IF_COLOR_ELSE(GColorRed, GColorWhite));
graphics_context_set_stroke_width(ctx, 3); // Only odd stroke width values supported
gpath_draw_outline_open(ctx, path_temp);
MEMORY_HEAP_PROBE_SAMPLE("before_temp_path_destroy", &redraw_probe);
gpath_destroy(path_temp);
MEMORY_HEAP_PROBE_SAMPLE("after_temp_path_destroy", &redraw_probe);

// Draw a line for the bottom axis
graphics_context_set_stroke_color(ctx, render_spec.axis_color);
Expand All @@ -578,6 +594,8 @@ static void forecast_update_proc(Layer *layer, GContext *ctx)
graphics_context_set_fill_color(ctx, GColorBlack);
graphics_fill_rect(ctx, GRect(0, 0, s_axis_left_w, h - BOTTOM_AXIS_H), 0, GCornerNone); // Paint over plot bleeding
graphics_draw_line(ctx, GPoint(graph_bounds.origin.x, 0), GPoint(graph_bounds.origin.x, axis_y));
MEMORY_HEAP_PROBE_LOG_MIN(&redraw_probe);
MEMORY_LOG_HEAP("forecast_update:exit");
}

static int temp_label_string_width(const char *text)
Expand Down Expand Up @@ -650,17 +668,26 @@ void forecast_layer_create(Layer *parent_layer, GRect frame)

// Add it as a child layer to the Window's root layer
layer_add_child(parent_layer, s_forecast_layer);
MEMORY_LOG_HEAP("after_forecast_layer_create");
}

void forecast_layer_refresh()
{
text_layers_refresh();
layer_mark_dirty(s_forecast_layer);
#ifdef FCW2_ENABLE_MEMORY_LOGGING
APP_LOG(APP_LOG_LEVEL_DEBUG, "MEM|forecast_refresh|entries=%d|free=%lu|used=%lu",
persist_get_num_entries(),
(unsigned long)heap_bytes_free(),
(unsigned long)heap_bytes_used());
#endif
}

void forecast_layer_destroy()
{
MEMORY_LOG_HEAP("forecast_layer_destroy:before");
text_layer_destroy(s_hi_layer);
text_layer_destroy(s_lo_layer);
layer_destroy(s_forecast_layer);
MEMORY_LOG_HEAP("forecast_layer_destroy:after");
}
8 changes: 6 additions & 2 deletions src/c/layers/loading_layer.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "loading_layer.h"
#include "c/appendix/persist.h"
#include "c/appendix/memory_log.h"

static Layer *s_loading_layer;
static TextLayer *s_loading_text_layer;
Expand Down Expand Up @@ -29,6 +30,7 @@ void loading_layer_create(Layer* parent_layer, GRect frame) {
layer_set_update_proc(s_loading_layer, loading_update_proc);
layer_add_child(s_loading_layer, text_layer_get_layer(s_loading_text_layer));
layer_add_child(parent_layer, s_loading_layer);
MEMORY_LOG_HEAP("after_loading_layer_create");
}

void loading_layer_refresh() {
Expand All @@ -41,6 +43,8 @@ void loading_layer_refresh() {
}

void loading_layer_destroy() {
layer_destroy(s_loading_layer);
MEMORY_LOG_HEAP("loading_layer_destroy:before");
text_layer_destroy(s_loading_text_layer);
}
layer_destroy(s_loading_layer);
MEMORY_LOG_HEAP("loading_layer_destroy:after");
}
Loading