> ## Documentation Index
> Fetch the complete documentation index at: https://docs.apsio.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Performance

> App start, screen loads, network requests, hangs and ANRs, slow and frozen frames, and traces, and how Apsio measures each.

The SDKs measure how fast your app starts, how fast each screen shows its content, how its
requests behave, when the main thread stops answering, and how smooth each screen is. Apsio adds
these up per release, screen, endpoint and device, in the console's Performance pages, the
[read API](/read-api#endpoints) and the MCP tool `query_vitals`.

## Vitals

The vitals of an app and release, in one view (`GET /v1/projects/{projectId}/vitals`):

| Vital | What it is |
| - | - |
| App start | p50, p90 and p95 of cold, warm and hot starts |
| Screen load | p50, p90 and p95 of TTID, and of TTFD where the app reports it |
| Hang rate (iOS) | The share of sessions with at least one hang |
| ANR rate (Android) | The share of sessions with an ANR |

`GET /v1/projects/{projectId}/performance/series` follows one of them per day or per release:
`cold_start_p90`, `ttid_p90`, `network_p90`, `network_error_rate`, `hang_rate`, `anr_rate`,
`slow_frames_per_minute` or `frozen_frames_per_minute`. A performance view covers at most 30
days.

Performance comes from sampled detail: spans and frame counts are sent only from
[kept sessions](/concepts/sampling), and counts and percentiles are reweighted by each session's
sampling rate, so a span from a session kept at a rate of 1/4 counts four times.

## App start

Each launch is one `app.start` span, from the start of the process (cold) or of the relaunch
(warm, hot) to the first frame of the first screen.

| Type | When |
| - | - |
| `cold` | The process starts |
| `warm` | The process exists, but the activity or scene is created again |
| `hot` | The app comes back with its UI intact (Android) |

iOS can start an app's process before the user opens it (prewarming). A prewarmed launch is
marked `app.start.prewarmed` and measured from when the SDK starts, not from the process start,
which can be minutes earlier. Start Apsio as early as possible so the measure covers your own
launch work.

`GET /v1/projects/{projectId}/performance/start` breaks app start down by release, OS version,
device model or prewarming, with p50, p90, p95 and a histogram per group.

## Screens

Each screen created is one `app.screen.load` span, from its creation (an activity's `onCreate`,
a view controller loading its view) to its first drawn frame:

* **TTID**, time to initial display, is measured on its own.
* **TTFD**, time to full display, is when the screen shows its real content, such as the data it
  loaded. Only the app knows that moment, so the app reports it: `reportFullyDrawn()` on every
  SDK. A screen that never reports it has no TTFD; Apsio never uses TTID in its place.

UIKit view controllers and Android activities are tracked on their own. In SwiftUI, Compose and
React Native, name the screens yourself (see each SDK's page). A screen shown again without being
created, such as by going back, starts no load; it still counts in funnels and journeys.

`GET /v1/projects/{projectId}/performance/screens` lists every screen with its loads, sessions,
TTID and TTFD percentiles, and the hangs and ANRs that happened on it;
`performance/screens/detail` shows one screen per release and per day, with its 20 slowest loads
and their sessions.

## Network

Each HTTP request is a client span with its method, its URL without the query string, its host,
status, duration and error type. Request and response bodies and query strings are never
recorded.

| Platform | What is recorded |
| - | - |
| iOS | `URLSession`, on its own |
| Android | OkHttp, once you add [the integration](/sdks/android#network) |
| React Native | `fetch` and `XMLHttpRequest`, on its own |

Requests are grouped by **endpoint**: the method, the host and a template of the path. The
template is `url.template` when the app or its HTTP library sends one. Otherwise Apsio derives it
from the path, replacing every segment that looks like an identifier or personal data (numbers,
UUIDs, long hexadecimal, phone numbers, tokens, email addresses, free text) with `{id}`, so
`GET /orders/1001` and `GET /orders/1002` are one endpoint, `GET /orders/{id}`.

A request **failed** when it got a 5xx or no response at all. 4xx responses are counted on their
own, since many are expected. `GET /v1/projects/{projectId}/network/endpoints` lists the
endpoints with their requests, error rates and latency percentiles, and
`network/endpoints/detail` one endpoint's statuses, per day and per release, with sample requests
linked to their sessions. An [alert](/concepts/alerts) can watch an endpoint's failure rate.

## Traces

Requests made while one of your spans is open nest under it, and a span started while another is
open becomes its child. To follow a request into your backend, list your own hosts in
`firstPartyHosts`: the SDK then adds a W3C `traceparent` header to their requests, and only
theirs, never a third party's.

`GET /v1/projects/{projectId}/traces/{traceId}` returns the device spans of one trace. Set the
project's APM link (Settings, or `PATCH /v1/orgs/{orgId}/projects/{projectId}`) to an https URL
with a `{trace_id}` placeholder, such as `https://app.datadoghq.com/apm/trace/{trace_id}`, and
every trace and sample request carries `apm_url`, the same trace in your APM.

## Hangs and ANRs

A **hang** (iOS) is the main thread blocked for more than 250 ms, seen by the SDK while the app
runs in the foreground, without a debugger. It is recorded with the main thread's stack, taken
while it was blocked, and its duration once the thread runs again. An **ANR** (Android) is the
main thread not answering for 5 seconds, recorded with its stack.

Both are [issues](/concepts/issues), grouped by the main thread's frames in your app.

* A hang that the app was killed during (by the watchdog, or a force quit) never ends. The SDK
  writes it to disk while it lasts and sends it at the next launch as unfinished: it counts in
  the hang rate, but its duration is only a lower bound, so duration percentiles leave it out.
* MetricKit reports hangs too, up to a day later, and they may be the same hangs the SDK saw.
  Rates and screens count only the hangs the SDK saw while the app ran, so a hang counts once;
  MetricKit's add to durations and stacks.

`GET /v1/projects/{projectId}/performance/hangs` gives hang and ANR session rates per release
and per day, the screen of each hang, and hang durations.

## Frames

The SDKs count the frames of the current screen while the app is in the foreground:

* a frame is **slow** when it took longer than the display's refresh interval for that frame
  (16.7 ms at 60 Hz, 8.3 ms at 120 Hz);
* it is **frozen** when it took longer than 700 ms. Slow and frozen do not overlap.

Counts are sent per screen visit, never one record per frame. On iOS counting pauses on a screen
nobody touches, 2 seconds after the last touch, screen change or keyboard event, so an animation
nobody touched is not counted. A hang longer than 700 ms is also a frozen frame when the screen
draws again at its end.

Slow and frozen frames **per minute** in the foreground compare across platforms. The frame
total, and the share of slow frames in it, compare within one platform only: iOS counts every
refresh of the display, also when nothing changes, and Android only the frames it drew.

`GET /v1/projects/{projectId}/performance/frames` gives slow and frozen frames per release and per
screen.

## MetricKit

On iOS, MetricKit's daily reports add what the SDK cannot see from inside the app: launch and
hang histograms, memory, CPU and disk, from every user's day rather than from sampled sessions.
`GET /v1/projects/{projectId}/metrics/metrickit` adds up one app's histograms per release, with
estimated percentiles.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.