bqtop 0.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,18 @@
1
+ name: ci
2
+ on:
3
+ push:
4
+ branches: [main]
5
+ pull_request:
6
+ jobs:
7
+ test:
8
+ runs-on: ubuntu-latest
9
+ strategy:
10
+ matrix:
11
+ python: ["3.12", "3.13"]
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - uses: astral-sh/setup-uv@v5
15
+ - run: uv python install ${{ matrix.python }}
16
+ - run: uv sync --python ${{ matrix.python }} --extra dev
17
+ - run: uv run ruff check src tests
18
+ - run: uv run pytest -q
@@ -0,0 +1,69 @@
1
+ name: release
2
+ on:
3
+ push:
4
+ tags: ["v*"]
5
+
6
+ permissions:
7
+ contents: write
8
+
9
+ jobs:
10
+ binaries:
11
+ strategy:
12
+ fail-fast: false
13
+ matrix:
14
+ include:
15
+ - { os: macos-latest, name: macos-arm64 }
16
+ - { os: macos-15-intel, name: macos-x86_64 }
17
+ - { os: ubuntu-latest, name: linux-x86_64 }
18
+ - { os: ubuntu-24.04-arm, name: linux-arm64 }
19
+ runs-on: ${{ matrix.os }}
20
+ steps:
21
+ - uses: actions/checkout@v4
22
+ - uses: astral-sh/setup-uv@v5
23
+ - run: uv python install 3.13
24
+ - run: uv sync --python 3.13
25
+ - run: scripts/build_binary.sh
26
+ - uses: actions/upload-artifact@v4
27
+ with:
28
+ name: ${{ matrix.name }}
29
+ path: |
30
+ dist/*.tar.gz
31
+ dist/*.sha256
32
+
33
+ release:
34
+ needs: binaries
35
+ runs-on: ubuntu-latest
36
+ steps:
37
+ - uses: actions/checkout@v4
38
+ - uses: actions/download-artifact@v4
39
+ with: { path: assets }
40
+ - name: version
41
+ id: v
42
+ run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
43
+ - name: formula
44
+ run: python3 scripts/formula.py "${{ steps.v.outputs.version }}" assets > assets/bqtop.rb
45
+ - name: github release
46
+ env: { GH_TOKEN: "${{ github.token }}" }
47
+ run: |
48
+ gh release create "$GITHUB_REF_NAME" --title "bqtop ${{ steps.v.outputs.version }}" --generate-notes \
49
+ assets/*/*.tar.gz assets/*/*.sha256 assets/bqtop.rb
50
+ - name: update homebrew tap
51
+ if: env.TAP_TOKEN != ''
52
+ env: { TAP_TOKEN: "${{ secrets.TAP_GITHUB_TOKEN }}" }
53
+ run: |
54
+ git clone "https://x-access-token:${TAP_TOKEN}@github.com/dadadima/homebrew-tap.git" tap
55
+ mkdir -p tap/Formula && cp assets/bqtop.rb tap/Formula/bqtop.rb
56
+ cd tap && git config user.name "bqtop-release" && git config user.email "bqtop-release@users.noreply.github.com"
57
+ git add Formula/bqtop.rb && git commit -m "bqtop ${{ steps.v.outputs.version }}" && git push
58
+
59
+ pypi:
60
+ needs: binaries
61
+ runs-on: ubuntu-latest
62
+ environment: pypi
63
+ permissions: { id-token: write, contents: read }
64
+ continue-on-error: true # until the PyPI trusted publisher is configured
65
+ steps:
66
+ - uses: actions/checkout@v4
67
+ - uses: astral-sh/setup-uv@v5
68
+ - run: uv build
69
+ - uses: pypa/gh-action-pypi-publish@release/v1
bqtop-0.3.1/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ dist/
5
+ build/
6
+ *.egg-info/
7
+ uv.lock
8
+ bqtop.toml
9
+ .DS_Store
10
+ *.spec
@@ -0,0 +1,32 @@
1
+ # Changelog
2
+
3
+ ## 0.3.1 (2026-09-30)
4
+
5
+ - `[source].credentials_file`: run bqtop as a service account instead of Application Default
6
+ Credentials, for setups where the user login expires.
7
+ - `--check` reports the identity actually used and the coverage: jobs and projects seen in the last 24h,
8
+ with a hint when `JOBS_BY_FOLDER` only sees the billing project itself.
9
+
10
+ ## 0.3.0 (2026-09-30)
11
+
12
+ - `bqtop --init` is an interactive setup: detects the gcloud project and timezone, asks source, scope,
13
+ pricing and refresh, writes the config and runs `--check`. `--init -y` writes the example silently.
14
+ - dbt models panel: `d` swaps hot tables for cost per dbt model, parsed from dbt's query comment
15
+ (`node_id`). Also in `--once` output and `--json` (`by_model`).
16
+ - Single-file binaries for macOS (arm64, x86_64) and Linux (arm64, x86_64) on every `v*` tag, via
17
+ PyInstaller, attached to the GitHub release together with a Homebrew formula.
18
+ - Homebrew: `brew install dadadima/tap/bqtop`.
19
+ - Release workflow also publishes to PyPI once a trusted publisher is configured.
20
+
21
+ ## 0.2.0 (2026-09-30)
22
+
23
+ - Local job store with incremental refresh; sort, filter and window changes never hit BigQuery.
24
+ - Pricing model: `auto` / `on_demand` / `slots`, per-project overrides, reservation-aware.
25
+ - `[budgets]` in USD/day for principals and projects, next to `[quotas]`.
26
+ - Cost sparkline, `/` filter, enter to drill down, job detail with full query, `?` help, `p` pause.
27
+ - `--demo`, `--check` with permission hints, `--watch`, `--json`, `-f`, multi-region.
28
+ - Tests, ruff, CI, screenshot from demo data.
29
+
30
+ ## 0.1.0 (2026-09-30)
31
+
32
+ - First cut: INFORMATION_SCHEMA and audit-log sources, four panels, `--once`.
bqtop-0.3.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Davide Di Matteo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
bqtop-0.3.1/PKG-INFO ADDED
@@ -0,0 +1,189 @@
1
+ Metadata-Version: 2.5
2
+ Name: bqtop
3
+ Version: 0.3.1
4
+ Summary: htop for BigQuery: live jobs, principals, projects, hot tables and cost in your terminal
5
+ Project-URL: Homepage, https://github.com/dadadima/bqtop
6
+ Author: Davide Di Matteo
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Keywords: bigquery,cost,gcp,monitoring,tui
10
+ Requires-Python: >=3.12
11
+ Requires-Dist: google-cloud-bigquery>=3.25
12
+ Requires-Dist: rich>=13
13
+ Requires-Dist: textual>=0.80
14
+ Provides-Extra: dev
15
+ Requires-Dist: pytest>=8; extra == 'dev'
16
+ Requires-Dist: ruff>=0.6; extra == 'dev'
17
+ Description-Content-Type: text/markdown
18
+
19
+ # bqtop
20
+
21
+ htop for BigQuery. A terminal dashboard that shows who is running what on your BigQuery projects
22
+ right now, what it costs, which tables are hot, and where today's spend stands against quotas and
23
+ budgets.
24
+
25
+ ![bqtop on demo data](docs/screenshot.svg)
26
+
27
+ It reads the metadata BigQuery already keeps, backfills once, then refreshes incrementally like `htop`.
28
+ No agents, no sinks to build, no dashboards to click through.
29
+
30
+ ```bash
31
+ brew install dadadima/tap/bqtop # macOS / Linux binary, no Python needed
32
+ # or: uv tool install git+https://github.com/dadadima/bqtop (pipx works too)
33
+ # or: grab a binary from https://github.com/dadadima/bqtop/releases
34
+
35
+ bqtop --demo # look around on synthetic data, no GCP needed
36
+ bqtop --init # interactive setup: detects your project, writes the config
37
+ bqtop # the TUI
38
+ ```
39
+
40
+ `bqtop --init` asks five questions (source, project, scope, pricing, timezone), writes
41
+ `~/.config/bqtop/config.toml` and runs `bqtop --check`, which tells you who you are, what the source
42
+ scan costs, and the exact role to grant if something is missing.
43
+
44
+ ## What you see
45
+
46
+ | panel | what |
47
+ | -------------- | --------------------------------------------------------------------------------------------------------- |
48
+ | summary | jobs, running, errors, cache-hit ratio, bytes billed and cost in the window, cost today, slot-hours |
49
+ | timeline | cost per time bucket across the window, so a spike is visible before the bill is |
50
+ | principals | who: jobs, running, errors, billed, cost, cost today, slot-hours, daily budget % |
51
+ | projects | where: same, plus today's `QueryUsagePerDay` quota % or budget % |
52
+ | hot tables | which tables the money goes to (a job touching three tables counts against all three: an upper bound) |
53
+ | dbt models | `d` swaps in cost per dbt model, parsed from dbt's query comment (`node_id`) |
54
+ | jobs | live stream, running first, then newest: state, principal, project, type, duration, billed, cost, query |
55
+
56
+ Press `enter` on a principal, project or table to drill down (everything filters to it), on a job to see
57
+ its details and full query text.
58
+
59
+ ## Keys
60
+
61
+ | key | action |
62
+ | ------- | ------------------------------------------------------------------- |
63
+ | `q` | quit |
64
+ | `r` | refresh now |
65
+ | `w` | wider window: 1h → 6h → 24h → 72h → 168h |
66
+ | `s` | cycle sort: cost, bytes, jobs, errors, slots |
67
+ | `j` | hide/show the tables panel (widens the job stream) |
68
+ | `d` | tables panel: hot tables ↔ dbt models |
69
+ | `/` | filter on principal, project, table, query text or error message |
70
+ | `esc` | clear the filter / close a dialog |
71
+ | `p` | pause auto-refresh |
72
+ | `enter` | drill down on a row; job details on a job |
73
+ | `?` | help |
74
+
75
+ Sorting, filtering and drill-down never touch BigQuery: they re-aggregate the local store. Widening the
76
+ window backfills the missing range once.
77
+
78
+ ## Sources
79
+
80
+ Pick one with `[source].kind`. Both are standard GCP surfaces.
81
+
82
+ | kind | reads | running jobs | permissions |
83
+ | -------------------- | ---------------------------------------------------------------- | ------------ | -------------------------------------------------------------------------------------------------------- |
84
+ | `information_schema` | `INFORMATION_SCHEMA.JOBS_BY_{PROJECT,FOLDER,ORGANIZATION,USER}` | yes | `bigquery.jobs.listAll` at that level, e.g. `roles/bigquery.resourceViewer`; nothing extra for `user` |
85
+ | `audit_log` | a routed `cloudaudit_googleapis_com_data_access` table | no | `dataViewer` on the sink table, `jobUser` on the billing project |
86
+
87
+ `information_schema` is the default: real time, includes `RUNNING` jobs, nothing to set up.
88
+ `scope = "folder"` gives one view over every project under the folder that directly contains
89
+ `billing_project`, sub-folders included. To watch a whole tree, run bqtop from a project that sits right
90
+ under the top folder; `bqtop --check` prints how many projects the source actually sees.
91
+ `audit_log` is for setups that already route BigQuery audit logs to a table (a folder- or org-level
92
+ sink), which also works when you cannot get `jobs.listAll` everywhere.
93
+
94
+ Authentication is whatever `google-cloud-bigquery` finds: Application Default Credentials
95
+ (`gcloud auth application-default login`), a service-account key via `GOOGLE_APPLICATION_CREDENTIALS`,
96
+ or the metadata server. Set `[source].credentials_file` to a service-account key to pin the identity
97
+ instead, useful when your user login expires daily. `bqtop --check` tells you who you are and what is missing.
98
+
99
+ ## Configuration
100
+
101
+ `~/.config/bqtop/config.toml`, or `./bqtop.toml` in the current directory. Full example with comments in
102
+ [`examples/config.example.toml`](examples/config.example.toml).
103
+
104
+ ```toml
105
+ [source]
106
+ kind = "information_schema"
107
+ billing_project = "my-admin-project" # runs and pays for bqtop's own queries
108
+ regions = ["us"]
109
+ scope = "folder" # project | folder | organization | user
110
+
111
+ [pricing]
112
+ mode = "auto" # auto | on_demand | slots
113
+ on_demand_usd_per_tib = 6.25
114
+ slot_usd_per_hour = 0.06 # Editions pay-as-you-go: standard 0.04, enterprise 0.06, plus 0.10
115
+
116
+ [pricing.projects]
117
+ "my-dbt-project" = { mode = "slots", slot_usd_per_hour = 0.04 }
118
+
119
+ [quotas] # daily QueryUsagePerDay caps, shown as % used today
120
+ "my-agents-project" = "5 TiB"
121
+
122
+ [budgets] # daily spend budgets, USD, by principal or project
123
+ "svc-airflow@my-ingest.iam.gserviceaccount.com" = 50
124
+ "my-agents-project" = "$10"
125
+
126
+ [ui]
127
+ refresh_seconds = 60
128
+ window_hours = 24
129
+ max_window_hours = 168
130
+ timezone = "Europe/Brussels"
131
+ top_n = 15
132
+ stream_rows = 40
133
+ ```
134
+
135
+ ### Pricing
136
+
137
+ BigQuery bills analysis either on demand (bytes) or through slots (Editions). `mode = "auto"` prices
138
+ each job by how it actually ran: in a reservation, slot-hours × `slot_usd_per_hour`; otherwise bytes
139
+ billed × `on_demand_usd_per_tib`. Override per project when a project is on a different edition. The
140
+ result is an estimate, not the invoice: storage, streaming inserts, Storage Read API and egress are not
141
+ in job metadata.
142
+
143
+ ### Quotas and budgets
144
+
145
+ `[quotas]` holds `QueryUsagePerDay` caps in bytes per project. `[budgets]` holds dollars per day for a
146
+ principal or a project. Both compare against **today**: bytes billed or cost since local midnight in
147
+ `[ui].timezone`, independent of the window you are looking at. Cells turn yellow at 60 % and red at 90 %.
148
+
149
+ ## Scripting
150
+
151
+ ```bash
152
+ bqtop --once # Rich tables, one snapshot
153
+ bqtop --once -w 6 --sort errors # 6 hours, worst offenders first
154
+ bqtop --once -f svc-airflow # only jobs matching a text
155
+ bqtop --once --json | jq '.by_principal[0]'
156
+ bqtop --watch 30 # plain-text refresh every 30 s, for tmux panes
157
+ bqtop --check # exits non-zero with hints if something is missing
158
+ ```
159
+
160
+ ## What it costs to run
161
+
162
+ bqtop bills its own queries to `billing_project`. On `information_schema` each refresh is one query with
163
+ a 10 MiB minimum, so a 60 s refresh is under a dollar a month. On `audit_log` the sink table is
164
+ day-partitioned, so each incremental refresh rescans the current day's partition; the summary line shows
165
+ what the last refresh billed and the session total. `bqtop --check` prints what a 24 h backfill scans.
166
+
167
+ ## Not covered
168
+
169
+ Storage Read API sessions, network egress, storage and streaming costs do not appear in job metadata.
170
+ If you stream tables out with Spark, DuckDB or Arrow clients, that spend is invisible here; Cloud Billing
171
+ export is the place for it. A billing-export source is the natural next step.
172
+
173
+ ## Releases
174
+
175
+ Every `v*` tag builds single-file binaries (PyInstaller) for macOS arm64 / x86_64 and Linux arm64 /
176
+ x86_64, attaches them to a GitHub release with their sha256 and a generated Homebrew formula, and
177
+ updates [`dadadima/homebrew-tap`](https://github.com/dadadima/homebrew-tap). `scripts/build_binary.sh`
178
+ does the same locally.
179
+
180
+ ## Development
181
+
182
+ ```bash
183
+ uv sync --extra dev
184
+ uv run pytest
185
+ uv run ruff check src tests && uv run ruff format src tests
186
+ uv run python scripts/screenshot.py # regenerate docs/screenshot.svg from the demo source
187
+ ```
188
+
189
+ MIT.
bqtop-0.3.1/README.md ADDED
@@ -0,0 +1,171 @@
1
+ # bqtop
2
+
3
+ htop for BigQuery. A terminal dashboard that shows who is running what on your BigQuery projects
4
+ right now, what it costs, which tables are hot, and where today's spend stands against quotas and
5
+ budgets.
6
+
7
+ ![bqtop on demo data](docs/screenshot.svg)
8
+
9
+ It reads the metadata BigQuery already keeps, backfills once, then refreshes incrementally like `htop`.
10
+ No agents, no sinks to build, no dashboards to click through.
11
+
12
+ ```bash
13
+ brew install dadadima/tap/bqtop # macOS / Linux binary, no Python needed
14
+ # or: uv tool install git+https://github.com/dadadima/bqtop (pipx works too)
15
+ # or: grab a binary from https://github.com/dadadima/bqtop/releases
16
+
17
+ bqtop --demo # look around on synthetic data, no GCP needed
18
+ bqtop --init # interactive setup: detects your project, writes the config
19
+ bqtop # the TUI
20
+ ```
21
+
22
+ `bqtop --init` asks five questions (source, project, scope, pricing, timezone), writes
23
+ `~/.config/bqtop/config.toml` and runs `bqtop --check`, which tells you who you are, what the source
24
+ scan costs, and the exact role to grant if something is missing.
25
+
26
+ ## What you see
27
+
28
+ | panel | what |
29
+ | -------------- | --------------------------------------------------------------------------------------------------------- |
30
+ | summary | jobs, running, errors, cache-hit ratio, bytes billed and cost in the window, cost today, slot-hours |
31
+ | timeline | cost per time bucket across the window, so a spike is visible before the bill is |
32
+ | principals | who: jobs, running, errors, billed, cost, cost today, slot-hours, daily budget % |
33
+ | projects | where: same, plus today's `QueryUsagePerDay` quota % or budget % |
34
+ | hot tables | which tables the money goes to (a job touching three tables counts against all three: an upper bound) |
35
+ | dbt models | `d` swaps in cost per dbt model, parsed from dbt's query comment (`node_id`) |
36
+ | jobs | live stream, running first, then newest: state, principal, project, type, duration, billed, cost, query |
37
+
38
+ Press `enter` on a principal, project or table to drill down (everything filters to it), on a job to see
39
+ its details and full query text.
40
+
41
+ ## Keys
42
+
43
+ | key | action |
44
+ | ------- | ------------------------------------------------------------------- |
45
+ | `q` | quit |
46
+ | `r` | refresh now |
47
+ | `w` | wider window: 1h → 6h → 24h → 72h → 168h |
48
+ | `s` | cycle sort: cost, bytes, jobs, errors, slots |
49
+ | `j` | hide/show the tables panel (widens the job stream) |
50
+ | `d` | tables panel: hot tables ↔ dbt models |
51
+ | `/` | filter on principal, project, table, query text or error message |
52
+ | `esc` | clear the filter / close a dialog |
53
+ | `p` | pause auto-refresh |
54
+ | `enter` | drill down on a row; job details on a job |
55
+ | `?` | help |
56
+
57
+ Sorting, filtering and drill-down never touch BigQuery: they re-aggregate the local store. Widening the
58
+ window backfills the missing range once.
59
+
60
+ ## Sources
61
+
62
+ Pick one with `[source].kind`. Both are standard GCP surfaces.
63
+
64
+ | kind | reads | running jobs | permissions |
65
+ | -------------------- | ---------------------------------------------------------------- | ------------ | -------------------------------------------------------------------------------------------------------- |
66
+ | `information_schema` | `INFORMATION_SCHEMA.JOBS_BY_{PROJECT,FOLDER,ORGANIZATION,USER}` | yes | `bigquery.jobs.listAll` at that level, e.g. `roles/bigquery.resourceViewer`; nothing extra for `user` |
67
+ | `audit_log` | a routed `cloudaudit_googleapis_com_data_access` table | no | `dataViewer` on the sink table, `jobUser` on the billing project |
68
+
69
+ `information_schema` is the default: real time, includes `RUNNING` jobs, nothing to set up.
70
+ `scope = "folder"` gives one view over every project under the folder that directly contains
71
+ `billing_project`, sub-folders included. To watch a whole tree, run bqtop from a project that sits right
72
+ under the top folder; `bqtop --check` prints how many projects the source actually sees.
73
+ `audit_log` is for setups that already route BigQuery audit logs to a table (a folder- or org-level
74
+ sink), which also works when you cannot get `jobs.listAll` everywhere.
75
+
76
+ Authentication is whatever `google-cloud-bigquery` finds: Application Default Credentials
77
+ (`gcloud auth application-default login`), a service-account key via `GOOGLE_APPLICATION_CREDENTIALS`,
78
+ or the metadata server. Set `[source].credentials_file` to a service-account key to pin the identity
79
+ instead, useful when your user login expires daily. `bqtop --check` tells you who you are and what is missing.
80
+
81
+ ## Configuration
82
+
83
+ `~/.config/bqtop/config.toml`, or `./bqtop.toml` in the current directory. Full example with comments in
84
+ [`examples/config.example.toml`](examples/config.example.toml).
85
+
86
+ ```toml
87
+ [source]
88
+ kind = "information_schema"
89
+ billing_project = "my-admin-project" # runs and pays for bqtop's own queries
90
+ regions = ["us"]
91
+ scope = "folder" # project | folder | organization | user
92
+
93
+ [pricing]
94
+ mode = "auto" # auto | on_demand | slots
95
+ on_demand_usd_per_tib = 6.25
96
+ slot_usd_per_hour = 0.06 # Editions pay-as-you-go: standard 0.04, enterprise 0.06, plus 0.10
97
+
98
+ [pricing.projects]
99
+ "my-dbt-project" = { mode = "slots", slot_usd_per_hour = 0.04 }
100
+
101
+ [quotas] # daily QueryUsagePerDay caps, shown as % used today
102
+ "my-agents-project" = "5 TiB"
103
+
104
+ [budgets] # daily spend budgets, USD, by principal or project
105
+ "svc-airflow@my-ingest.iam.gserviceaccount.com" = 50
106
+ "my-agents-project" = "$10"
107
+
108
+ [ui]
109
+ refresh_seconds = 60
110
+ window_hours = 24
111
+ max_window_hours = 168
112
+ timezone = "Europe/Brussels"
113
+ top_n = 15
114
+ stream_rows = 40
115
+ ```
116
+
117
+ ### Pricing
118
+
119
+ BigQuery bills analysis either on demand (bytes) or through slots (Editions). `mode = "auto"` prices
120
+ each job by how it actually ran: in a reservation, slot-hours × `slot_usd_per_hour`; otherwise bytes
121
+ billed × `on_demand_usd_per_tib`. Override per project when a project is on a different edition. The
122
+ result is an estimate, not the invoice: storage, streaming inserts, Storage Read API and egress are not
123
+ in job metadata.
124
+
125
+ ### Quotas and budgets
126
+
127
+ `[quotas]` holds `QueryUsagePerDay` caps in bytes per project. `[budgets]` holds dollars per day for a
128
+ principal or a project. Both compare against **today**: bytes billed or cost since local midnight in
129
+ `[ui].timezone`, independent of the window you are looking at. Cells turn yellow at 60 % and red at 90 %.
130
+
131
+ ## Scripting
132
+
133
+ ```bash
134
+ bqtop --once # Rich tables, one snapshot
135
+ bqtop --once -w 6 --sort errors # 6 hours, worst offenders first
136
+ bqtop --once -f svc-airflow # only jobs matching a text
137
+ bqtop --once --json | jq '.by_principal[0]'
138
+ bqtop --watch 30 # plain-text refresh every 30 s, for tmux panes
139
+ bqtop --check # exits non-zero with hints if something is missing
140
+ ```
141
+
142
+ ## What it costs to run
143
+
144
+ bqtop bills its own queries to `billing_project`. On `information_schema` each refresh is one query with
145
+ a 10 MiB minimum, so a 60 s refresh is under a dollar a month. On `audit_log` the sink table is
146
+ day-partitioned, so each incremental refresh rescans the current day's partition; the summary line shows
147
+ what the last refresh billed and the session total. `bqtop --check` prints what a 24 h backfill scans.
148
+
149
+ ## Not covered
150
+
151
+ Storage Read API sessions, network egress, storage and streaming costs do not appear in job metadata.
152
+ If you stream tables out with Spark, DuckDB or Arrow clients, that spend is invisible here; Cloud Billing
153
+ export is the place for it. A billing-export source is the natural next step.
154
+
155
+ ## Releases
156
+
157
+ Every `v*` tag builds single-file binaries (PyInstaller) for macOS arm64 / x86_64 and Linux arm64 /
158
+ x86_64, attaches them to a GitHub release with their sha256 and a generated Homebrew formula, and
159
+ updates [`dadadima/homebrew-tap`](https://github.com/dadadima/homebrew-tap). `scripts/build_binary.sh`
160
+ does the same locally.
161
+
162
+ ## Development
163
+
164
+ ```bash
165
+ uv sync --extra dev
166
+ uv run pytest
167
+ uv run ruff check src tests && uv run ruff format src tests
168
+ uv run python scripts/screenshot.py # regenerate docs/screenshot.svg from the demo source
169
+ ```
170
+
171
+ MIT.