Skip to content

Commit 80924e2

Browse files
docs(strictmode): add StrictMode policy documentation (#1244)
* docs(strictmode): add StrictMode policy documentation * docs(strictmode): apply PR 1244 feedback
1 parent a530c9c commit 80924e2

2 files changed

Lines changed: 155 additions & 0 deletions

File tree

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
* [Getting Started](#getting-started)
1212
* [Features](#contributing)
1313
* [Contributing](#contributing)
14+
* [StrictMode Policy](./docs/STRICTMODE.md)
15+
* [Exporter Chain](./docs/EXPORTER_CHAIN.md)
1416

1517
# About
1618

@@ -71,6 +73,12 @@ Some of the additional features provided include:
7173

7274
Note: Use of these features is not yet well documented.
7375

76+
## StrictMode
77+
78+
For guidance on Android StrictMode violations (disk / network I/O warnings) triggered by SDK initialization
79+
and instrumentation, expected one-time operations, plus available mitigations and workarounds, see
80+
[StrictMode Policy](./docs/STRICTMODE.md).
81+
7482
## Exporter Chain
7583

7684
The SDK performs asynchronous exporter initialization with an in-memory buffering layer and optional

docs/STRICTMODE.md

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
# StrictMode Policy and OpenTelemetry Android
2+
3+
This document explains the current policy, known sources, and mitigation options for Android StrictMode
4+
violations when using the OpenTelemetry Android SDK ("android-agent") and underlying OpenTelemetry Java SDK.
5+
6+
StrictMode is a development tool that surfaces potentially expensive operations (e.g. disk/network I/O) on
7+
threads where they could cause UI jank or ANRs. The OpenTelemetry Android SDK aims to minimize undesirable
8+
impact to startup and frame rendering. However:
9+
10+
- Some one‑time initialization I/O is currently unavoidable.
11+
- Completely eliminating all StrictMode disk read signals would add significant complexity or overhead.
12+
13+
We group known violations into three categories:
14+
15+
1. Avoidable – we intend to fix or have open issues / PRs.
16+
2. Optional / Avoidable by app choice – can be prevented by disabling an optional feature OR by applying an early, low‑cost mitigation (e.g. setting a property very early).
17+
3. Acceptable / Unavoidable – one‑time, low‑latency initialization that cannot practically be prevented without disproportionate complexity, performance cost, or brittle ordering requirements.
18+
19+
> IMPORTANT: StrictMode reports *any* disk read/write on the main thread, regardless of cost. A reported
20+
> violation does not automatically mean a user‑visible freeze. Always measure real device startup / frame
21+
> metrics before drawing conclusions.
22+
23+
## Summary Table
24+
25+
| Source / Stack (Representative) | Category | Description | Mitigation / Workaround |
26+
|---------------------------------|----------|-------------|--------------------------|
27+
| `io.opentelemetry.context.LazyStorage.<clinit>` SPI resource scan (ServiceLoader) | 2 | One‑time classpath resource + service index scan across DEX/JAR elements to initialize context storage in OpenTelemetry Java | Pre‑seed the default context storage via system property to avoid ServiceLoader disk scan during first span; see below. |
28+
| Offline persistence initialization (creating / listing storage dir) | 2 | Disk reads/writes to enable offline batching of telemetry (creates cache subdir, may stat existing files) | Disable offline buffering (`DiskBufferingConfig.enabled = false` when configuring, or omit enabling the feature) if startup StrictMode cleanliness is critical – see `core/src/main/java/io/opentelemetry/android/features/diskbuffering/DiskBufferingConfig.kt` |
29+
| Crash / ANR signal handlers registration (proc / system reads) | 3 | Light, one‑time metadata collection (may read `/proc/self/` entries, system traces) | No direct workaround; expected to be minimal on typical hardware—measure on low‑end devices |
30+
| Reading network state / registering listeners | 2 | Accessing system services (e.g. `ConnectivityManager`, possibly reading system settings) | Disable specific instrumentation modules (network, slow/frozen frame) or remove their Gradle dependency |
31+
32+
We will expand this table as additional sources are identified. Contributions welcome.
33+
34+
## Known Primary Violation: Context Storage Lazy Initialization
35+
36+
Stack traces similar to:
37+
38+
```text
39+
StrictMode policy violation; ~duration=168 ms: android.os.strictmode.DiskReadViolation
40+
at java.util.ServiceLoader$LazyClassPathLookupIterator.hasNext(ServiceLoader.java:...)
41+
at io.opentelemetry.context.LazyStorage.createStorage(LazyStorage.java:107)
42+
at io.opentelemetry.context.LazyStorage.<clinit>(LazyStorage.java:80)
43+
at io.opentelemetry.context.ContextStorage.get(ContextStorage.java:72)
44+
at io.opentelemetry.context.Context.current(Context.java:92)
45+
at io.opentelemetry.sdk.trace.SdkSpanBuilder.startSpan(...)
46+
```
47+
48+
Cause: The OpenTelemetry Java SDK lazily resolves a `ContextStorage` implementation using `ServiceLoader`. The
49+
first time a span is started (or context accessed) on the main thread, a disk read can occur while the runtime
50+
scans Class-Path / DEX resource indices (service descriptors) to locate an implementation.
51+
52+
References:
53+
54+
- OTel Java issue: <https://github.com/open-telemetry/opentelemetry-java/issues/7600>
55+
- `LazyStorage` source (may move across versions): <https://github.com/open-telemetry/opentelemetry-java/blob/main/context/src/main/java/io/opentelemetry/context/LazyStorage.java>
56+
57+
### Workaround: Pre‑seed the Default Context Storage
58+
59+
If you do not provide a custom context storage provider, you can set a system property **as early as possible** (before
60+
any OpenTelemetry API, agent, or auto-initialization code runs) to short‑circuit the `ServiceLoader` scan.
61+
62+
Important details:
63+
64+
- Set it before any OpenTelemetry API usage.
65+
- ContentProvider-based auto-initialization may run earlier; if that happens and can't be disabled, accept the one-time scan.
66+
- Applies per process.
67+
- Do not set it if you use a custom ContextStorage implementation.
68+
69+
Kotlin (e.g. in a custom `Application` `attachBaseContext` or very early in `onCreate`):
70+
71+
```kotlin
72+
override fun attachBaseContext(base: Context?) {
73+
// Prevent lazy ServiceLoader disk scan on first span creation
74+
System.setProperty("io.opentelemetry.context.contextStorageProvider", "default")
75+
super.attachBaseContext(base)
76+
}
77+
```
78+
79+
Java equivalent:
80+
81+
```java
82+
public class MyApplication extends Application {
83+
@Override public void attachBaseContext(Context base) {
84+
System.setProperty("io.opentelemetry.context.contextStorageProvider", "default");
85+
super.attachBaseContext(base);
86+
}
87+
}
88+
```
89+
90+
Notes:
91+
92+
- Must run before any OpenTelemetry API call (including automatic instrumentation) to be effective.
93+
- Does nothing if you actually supply a custom provider (omit it in that case).
94+
- Eliminates the specific `LazyStorage` triggered disk read StrictMode warning in most scenarios.
95+
96+
### Alternative: Initialize Off Main Thread
97+
98+
If your app architecture supports delaying the first span until after a background initialization phase has run,
99+
you can trigger a dummy context access / span start in a background thread:
100+
101+
```kotlin
102+
Executors.newSingleThreadExecutor().execute {
103+
val tracer = GlobalOpenTelemetry.getTracer("warmup")
104+
tracer.spanBuilder("otel.warmup").startSpan().end()
105+
}
106+
```
107+
108+
This forces the one‑time initialization off the UI thread. Be sure any feature relying on early spans (e.g.
109+
startup telemetry) still meets your requirements.
110+
111+
## Feature-Specific Violations and How To Bypass
112+
113+
| Feature | Potential Main Thread I/O (Examples) | Bypass / Disable (Current State) | Trade‑offs |
114+
|---------|--------------------------------------|----------------------------------|------------|
115+
| Offline buffering / storage | Create cache dir, list existing batch files, stat file sizes | Disable via `DiskBufferingConfig(enabled = false)` (or omit enabling); if no runtime toggle exposed, remove related dependency | No offline durability; data loss when offline |
116+
| Crash reporting | Read `/proc/self/stat`, signal handler setup, collect app/package metadata | Remove crash instrumentation dependency (runtime toggle TBD) | No automatic crash spans / attributes |
117+
| ANR detection | Install handlers / read traces or binder timeouts (light reads) | Remove ANR instrumentation dependency | Lose ANR diagnostics |
118+
| Slow / frozen frame detection | Access frame metrics / choreographer callbacks (minimal disk I/O) | Remove slowrendering instrumentation dependency | Lose frame performance telemetry |
119+
| Network change detection | Query `ConnectivityManager`, register network callbacks (system service I/O) | Remove network instrumentation dependency | Lose network state enrichment |
120+
| Context storage init | ServiceLoader resource scan | Pre‑seed property OR off-main-thread warmup | None (once-only cost) |
121+
122+
(Exact configuration flags / Gradle coordinates to disable each module should be documented here as those features become generally available. PRs welcome.)
123+
124+
## Policy
125+
126+
1. One‑time, low‑latency (< ~5ms guideline) main‑thread disk reads during initialization are acceptable when they occur exactly once per process (concurrent first access still counts as one logical init) or are realistically unavoidable due to platform/JVM constraints.
127+
2. Where feasible we expose mitigations (early system property, background warmup, feature toggles) so apps can eliminate or shift this work.
128+
3. StrictMode violations > ~16ms that are easily/consistently reproducible and attributable to OpenTelemetry main‑thread I/O may be filed as issues. Please include stack trace, timing, reproduction steps, and enabled modules so we can prioritize actual user impact.
129+
4. The list of sources will evolve; new SDK versions, Android platform changes, or dependency updates can surface new one‑time reads. We’ll adjust documentation as patterns emerge.
130+
5. Always validate real performance signals (startup time, frame timing, ANR rate) before optimizing purely for zero StrictMode entries; benign one‑time reads without user impact are lower priority than sustained latency.
131+
132+
## Reporting Additional Violations
133+
134+
If you find a StrictMode violation that appears frequently or adds noticeable startup time:
135+
136+
1. Capture the full StrictMode stack trace with timestamps and (if possible) duration.
137+
2. Note which OpenTelemetry modules are enabled and your SDK version.
138+
3. Open an issue (or comment on an existing one) in `opentelemetry-android` with the above information and steps to reproduce.
139+
4. Provide device class info (API level, CPU/RAM tier).
140+
141+
## References
142+
143+
- OTel Android issue (policy): <https://github.com/open-telemetry/opentelemetry-android/issues/1188>
144+
- OTel Java issue (LazyStorage SPI scan): <https://github.com/open-telemetry/opentelemetry-java/issues/7600>
145+
- OTel Java `LazyStorage` source: <https://github.com/open-telemetry/opentelemetry-java/blob/main/context/src/main/java/io/opentelemetry/context/LazyStorage.java>
146+
- Disk buffering configuration (`DiskBufferingConfig`): `core/src/main/java/io/opentelemetry/android/features/diskbuffering/DiskBufferingConfig.kt`
147+
- Exporter delegate chain overview: `docs/EXPORTER_CHAIN.md`

0 commit comments

Comments
 (0)