# Query the data

Everything Squiggle records is in one SQLite file in your project. When the screens and reports don’t answer your question, ask it in SQL.

## Run a query

The slowest network requests, on average, across every session:

```shell
squiggle perf sql "
  select name, count(*) as n, avg(dur_us) / 1000.0 as avg_ms
  from spans where kind = 1
  group by name order by avg_ms desc limit 3"
```

Output:

```text
name                                              n   avg_ms
GET https://jsonplaceholder.typicode.com/todos/1  62  138.24
  1 row
```

Queries are read-only. The file is `.squiggle/perf/perf.db`, and you can also open it in any SQLite tool. In Studio, the Performance screen’s **Data** section has the same query box, with starter queries until you save your own.

## The tables

Twenty-four tables hold data: `actions`, `baselines`, `errors`, `escalations`, `gestures`, `heals`, `incidents`, `insight_revisions`, `insights`, `intent_runs`, `logs`, `memo_misses`, `metrics`, `metrics_1s`, `metrics_60s`, `navigations`, `probe_fires`, `profiles`, `recordings`, `renders`, `scrolls`, `sessions`, `spans` and `state_features`. The ones you will use most:

| Table | One row per |
| --- | --- |
| `sessions` | Session: when it started (started_wall), the app, and the user and traits from identify(). |
| `renders` | Render: the component name, its file and line, why it rendered and how long it took. |
| `spans` | Timed piece of work. kind says which: 1 is an HTTP request, 10 a SQLite query, 15 a screen transition, 18 a long JS task. |
| `metrics` | Sample of one metric. metrics_1s and metrics_60s hold the same series rolled up. |
| `navigations` | Screen change. |
| `gestures · scrolls` | Touch with its track, and scroll with its offsets. |
| `actions` | Call to a defineAction, with its duration and result. |
| `insights` | Finding, with its detector, severity and title. |
| `state_features` | State path in a state sample: `path`, such as `zustand:useCart.items#len`, and its value in `num`, or in `str` for text such as `#kind` and `#value`. Empty unless your app sends [state samples](https://squiggle.sh/docs/product#state-paths). |
| `errors · logs` | JS error with its stack, and console line. |
| `probe_fires` | Time a probe’s rules started or stopped holding, with the inputs it read in `inputs_json`. Empty unless you run [probes](https://squiggle.sh/docs/performance/profiling#watch-for-a-condition-inside-the-app). |

> **Units**
>
> Times inside a session (`ts_us`, `dur_us`) are microseconds on that session’s own clock. Wall-clock times (`started_wall`, `triggered_wall`) are milliseconds since 1970, so `datetime(started_wall / 1000, 'unixepoch')` gives a date. Tables that belong to a session have a `session_id` to join on.

## Save a query

```shell
squiggle perf saved save slow-requests "select name, avg(dur_us) from spans where kind = 1 group by name"
squiggle perf saved run slow-requests
squiggle perf saved list
squiggle perf saved rm slow-requests
```

Saved queries are shared: one you save in the terminal appears in Studio’s query box, and the other way round. Your agent runs them with the `perf_saved_query_run` tool.

## Use the result in a script

```shell
squiggle perf sql "select title from insights where severity = 'error'" --json | jq -r '.[].title'
```

With `--json`, the rows go to standard output as a JSON array and nothing else does, so you can pipe them on. See the [CLI reference](https://squiggle.sh/docs/reference/cli).
