digidem / digidem/comapeo-core-react-native

perf(sentry): compute storage-size metric via StorageStatsManager instead of a recursive walk

Open
#178 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

area: android performance sentry tech-debt
Dominant language
Kotlin
Stars
1
Forks
0
Avg merge
8h 24m
Merged PRs (30d)
9

Description

Background

The backend emits a coarse storage_size bucket metric once at boot (§11.2.a) so we can see, in aggregate, roughly how much data CoMapeo installs hold. Today this is computed by sampleStorageSize in backend/index.js, which recursively walks the private storage directory in the nodejs-mobile backend — readdir + fs.stat on every file — and sums the bytes before bucketing.

That walk is O(number of files): on an install with a large media/blob store it is real disk I/O and battery proportional to the dataset, run on every cold start. As an interim fix (#111) the walk is now skipped entirely when Sentry is disabled (metrics.isEnabled() gate), so opted-out users no longer pay for it. This issue tracks replacing the walk with an OS-provided figure so even diagnostics-on users don't pay for it.

Proposed change

Use the platform's built-in per-app storage accounting instead of walking the tree.

Android — StorageStatsManager (API 26+).
StorageStatsManager.queryStatsForUid(StorageManager.UUID_DEFAULT, Process.myUid()) returns a StorageStats with getDataBytes() / getCacheBytes() / getAppBytes(). The OS already tracks this per-UID, so it is effectively a lookup rather than a walk. Querying our own app's UID needs no permission (PACKAGE_USAGE_STATS is only required to query other apps).

The backend is JS (nodejs-mobile) and can't call Android APIs directly, but we already compute DeviceTags (deviceClass/osMajor) in Kotlin and pass them to the backend via argv (--deviceClass=…). Storage can ride the same channel: compute the bucket in Kotlin and pass --storageBucket=…, or emit the metric straight from the FGS Sentry bridge. Either way the JS walk disappears.

iOS has no equivalent per-sandbox OS counter (URLResourceValues is still per-file), so iOS keeps a gated walk or skips the metric.

Pros

  • Removes an O(files) disk walk from every cold start on Android; the OS figure is ~constant-cost.
  • No new permission required for our own UID.
  • Fits the existing Kotlin→backend argv plumbing used for device tags.
  • Arguably more accurate as a "user footprint" signal: getDataBytes() is the whole app's on-disk data (filesDir + databases + caches + …), a superset of the privateStorageDir subtree we currently sum.

Cons / considerations

  • minSdk is 24; StorageStatsManager is API 26+. API 24–25 needs a fallback (walk, or skip the metric) — a small and shrinking device slice.
  • Metric semantics shift: whole-app data bytes vs. the privateStorageDir subtree. If we want continuity with already-collected data, account for the step change (e.g. note it in the dashboard, or restrict to the data subdir if the API allows — it does not, it's UID-level).
  • queryStatsForUid can itself be non-trivial on some devices/large apps (the OS may compute on demand), though still far cheaper than our walk and the sanctioned API.
  • Cross-platform split: Android gets the native path; iOS keeps a walk or drops the metric. Slightly more code paths than the current single JS implementation.
  • Moves one more piece of telemetry from the backend into the native layer — fine given device tags already live there, but worth being deliberate about where the storage metric is owned.

Rejected alternatives

  • StatFs — only reports whole-filesystem free/total, not this app's usage.
  • du -s <dir> shell-out — still an O(files) walk (in C), and awkward to spawn from nodejs-mobile; marginal win.

Acceptance

  • Android computes storage bytes via StorageStatsManager.queryStatsForUid (API 26+), with a 24–25 fallback decided (walk or skip).
  • Value reaches the metric without a JS recursive walk on Android.
  • iOS path decided (gated walk or drop).
  • Bucket-semantics change documented for whoever reads the dashboard.

Interim gating landed in #111.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with sampleStorageSize in backend/index.js and inspect the existing Kotlin DeviceTags-to-backend argv plumbing. Compare the Android API 26+ path with the required API 24–25 fallback and decide how iOS is handled. Done means Android avoids the JS recursive walk, the value reaches the metric, and the bucket-semantics change is documented.

Written by the indexing model from the issue text.

Assessment

Tech stack
android, javascript, kotlin, node.js
Domain
mobile-dev, observability, performance
Issue type
Refactor
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.