English | 简体中文
A real-time BigQuery observability dashboard. BigLens queries BigQuery's INFORMATION_SCHEMA views to surface storage costs, compute slot usage, per-user spend, and optimization recommendations — all from a single dark-themed web UI.
- Go 1.22+
- Node.js 20+ and npm
- Google Cloud credentials with BigQuery metadata access (
roles/bigquery.resourceViewer)
Copy the template and fill in your GCP project ID:
cp conf.yaml.template conf.yamlEdit conf.yaml:
server:
port: 1983
mode: "debug" # "debug" or "release"
bigquery:
project_id: "your-gcp-project-id"
credentials_path: "" # optional, falls back to GOOGLE_APPLICATION_CREDENTIALS| Field | Description |
|---|---|
server.port |
HTTP port for the dashboard (default 1983) |
server.mode |
debug for verbose logging, release for production |
bigquery.project_id |
Your GCP project ID |
bigquery.credentials_path |
Path to a service account JSON key. Leave empty to use Application Default Credentials (gcloud auth application-default login) |
make serveThis single command:
- Installs frontend dependencies and builds the React app
- Copies the static bundle into the Go server
- Compiles the Go binary
- Starts the server
Open http://localhost:1983 in your browser.
make build-frontend # Build only the React frontend
make build-backend # Build only the Go binary
make build-all # Build both without launching
make clean # Remove build artifactsTo run the frontend with hot reload while the backend serves API requests:
# Terminal 1: start the Go backend
make build-backend && ./bin/biglens-server
# Terminal 2: start Vite dev server (proxies /api/* to port 1983)
cd frontend && npm run devBigLens provides five dashboard views, each powered by INFORMATION_SCHEMA queries:
| Dashboard | Widgets |
|---|---|
| Storage | Logical vs. physical billing simulator, active/long-term donut chart, top 10 heaviest tables |
| Compute | Concurrent slot usage area chart (JOBS_TIMELINE), top slot-consuming jobs |
| Cost | On-demand cost extrapolation ($6.25/TiB), spend-by-user treemap |
| Insights | Active BigQuery recommendations feed |
| IAM | Per-principal job activity and inactive-principal detection (7/30/90-day windows) |
All dashboards share a sidebar filter panel:
- Region — Searchable dropdown for the BQ region (defaults to
us) - Dataset / Table — Scope metrics to a specific dataset or table
- User Email — Isolate metrics to a specific user or service account
- Time Range — 24h, 7d, 30d, or 90d lookback
The Dataplex view turns your data catalog into an interactive 2D/3D graph you can search, explore, and edit. It is built on the Open Knowledge Format (OKF): a git-friendly bundle of markdown files where each file is a concept (a node) and markdown links between them are edges.
- Graph — force-directed, toggle between 2D and 3D. Nodes are
colored by their
type(BigQuery Table / View / Dataset, Glossary Term, Metric, …); edges are relationships. - Bottom tabs — Search (by name, type, or tag), Details (frontmatter, body, and connections of the selected node), and Edit (create, update, or delete concepts — written straight to the markdown bundle).
- Import from Dataplex — pulls live entries from the Dataplex Universal
Catalog (
SearchEntries) into the OKF bundle and wires two kinds of edges:- Containment —
dataset ⊃ table, derived from the entry hierarchy. - Lineage — source → derived table ETL data flow, from the Data Lineage API (best-effort; if the API is disabled or untracked, import still succeeds with containment edges and reports lineage as skipped). Edits stay local in the bundle (reversible via git); they are not written back to Dataplex.
- Containment —
Configure the bundle and import source in conf.yaml:
catalog:
bundle_path: "okf-bundle" # directory holding the OKF markdown bundle
dataplex:
project_id: "" # defaults to bigquery.project_id when empty
location: "global" # Dataplex search location, e.g. "global" or "us"
lineage_location: "us" # Data Lineage API region (regional, not "global")The runtime bundle okf-bundle/ is git-ignored (it may hold imported
metadata). A reference sample ships in okf-bundle.sample/ — copy it in to see
the graph before importing:
cp -r okf-bundle.sample/. okf-bundle/Importing requires roles/dataplex.catalogViewer; lineage edges additionally
require the Data Lineage API enabled and roles/datalineage.viewer.
The BigQuery Open Data view hosts dashboards built on
Google Cloud public datasets.
Queries run in your configured project (billed there) against
bigquery-public-data; every query filters on the partition key to keep scans
small, and results are served through the same 10-minute cache as the other
dashboards.
The first dashboard, powered by bigquery-public-data.google_trends
(international_top_terms / international_top_rising_terms):
| Widget | Description |
|---|---|
| Top Terms Leaderboard | Top 25 terms per country with inline score bars |
| Term Cloud | Tag cloud sized by score, top-5 ranks highlighted |
| Surging Terms | Top 10 rising queries by percent_gain, plus a breakdown table |
| Cross-Country Interest | A term's latest score wherever it charts in the top 25 |
| Interest Over Time | 5-year weekly history, compare up to 5 terms, drag to zoom |
Filters: country, snapshot date (refresh_date partition), and a term search
over the day's charts. Clicking any term focuses the geographic view and adds
it to the comparison chart.
The dashboard surfaces three metrics straight from the dataset — they measure different things, so they don't move together:
- Rank (1–25) — the term's position in the country's daily top chart, ordered by raw search volume for that snapshot. This is what sorts the leaderboard.
- Score (0–100) — Google's relative search-interest index for the latest
week: each term is normalized against its own all-time peak, where 100 means
"this week is (or ties) the term's peak popularity". The dataset reports it
per region, so BigLens averages it across all of a country's regions and
rounds to an integer (
CAST(COALESCE(AVG(score), 0) AS INT64); a NULL score counts as 0). The inline bar next to each leaderboard row visualizes this value. - Gain (%) — for rising terms only: the week-over-week percentage increase
in search volume (
percent_gain). A brand-new breakout query can show gains of several thousand percent.
Because rank reflects absolute daily volume while score reflects interest relative to the term's own history, a #14 term can score 100 (it just hit its all-time high) while the #1 term scores lower (huge volume, but past its peak week). The leaderboard therefore intentionally sorts by rank, not score.
A real-time global news sentiment and geopolitical monitoring dashboard,
powered by the GDELT Project 2.0 tables
gdelt-bq.gdeltv2.events_partitioned and gdelt-bq.gdeltv2.gkg_partitioned.
GDELT machine-reads news media worldwide in 100+ languages and refreshes
every 15 minutes; BigLens queries the partitioned tables directly (no
intermediate tables or views).
| Widget | Description |
|---|---|
| Global Event Hotspots | World map of the top 500 locations — bubble size = event count, color = average tone |
| Sentiment Gauge | Weighted global average tone for the selected range |
| Volume & Tone Trend | Daily event count (bars) vs. daily average tone (line) |
| Cooperation vs Conflict | Donut of the event mix across the four QuadClasses |
| Risk Matrix | Event types plotted by Goldstein score (x) vs. activity (y, log) — lower-right = high-volume destabilizing |
| Conflict Categories | Event counts per CAMEO conflict root code (Protest, Coerce, Assault, Fight, …) |
| Breaking Conflict Reports | Top 50 most-mentioned conflict articles, one row per source URL |
| Trending Themes | Treemap of the top 50 GKG themes by article count |
| Most Covered People / Leading Media Sources | Top 20 people and top 10 outlets (colored by average tone) |
Filters: quick ranges (3 / 7 / 30 days) plus a custom UTC date range. The event panels accept up to 90 days; the theme/entity (GKG) panel up to 30 days and loads independently, so the fast event charts never wait for it.
GDELT is an index of news coverage, not a registry of verified incidents. Each row is one machine-coded "who did what to whom" statement extracted from a news report, so the same real-world incident covered by many outlets produces many rows — counts measure media attention, which is exactly what a news-pulse dashboard should show.
- Date reported — the UTC date GDELT ingested the report
(
_PARTITIONDATE), not the date the underlying event happened. This is the right axis for "what is the news covering right now", and it is also the table's partition key, so every query prunes to only the selected days. - Tone — the average emotional tone of the language in the articles describing an event, from GDELT's sentiment engine. The scale is −100…+100 but real-world values almost always fall in −10…+10; below −2 reads as clearly negative coverage, above +2 as positive.
- Goldstein scale (−10…+10) — a standard political-science score of an event type's theoretical impact on a country's stability (e.g. "Provide aid" is strongly positive, "Fight" strongly negative). It is fixed per CAMEO event type — it rates the kind of action, not the individual article.
- QuadClass (1–4) — GDELT's coarsest event grouping: Verbal Cooperation, Material Cooperation, Verbal Conflict, Material Conflict. Classes 3–4 drive the "Conflict Share" metric and the breaking-reports list.
- CAMEO root codes ('01'–'20') — the 20 top-level event categories of the CAMEO taxonomy (Appeal, Consult, Threaten, Protest, Fight, …). Codes '10'+ are the conflict side. The API returns raw codes; the UI maps them to labels.
- Mentions — how many times an event was mentioned across all monitored
documents (
NumMentions); the prominence signal that ranks the breaking-reports table. - Themes / People (GKG) — from the Global Knowledge Graph, which tags
every article with themes (e.g.
PROTEST,WB_2670_JOBS) and named people. Their weights are article counts: an article mentioning a theme ten times still counts once, so long articles don't dominate the treemap. - Media source tone — the average document tone (first field of the GKG
V2Tonecomposite) across everything an outlet published in the range.
- Weighted averages, never averages of averages. BigQuery returns one
row per (day × QuadClass × event type) group with that group's
AVGandCOUNT; the Go backend combines them asΣ(avg×n)/Σ(n), which is mathematically identical to averaging the raw rows. A plain mean of group averages would let a 10-event group distort the global tone as much as a 100,000-event group. - Hotspots are event coordinates rounded to a 0.1° grid (~11 km) and aggregated per cell; the map shows the 500 busiest cells.
- Breaking reports are deduplicated by
SOURCEURL(keeping each article's highest-mention event row), because GDELT emits several event rows per article and one big story would otherwise flood the top 50. - Cost guardrails: native
DATEparameters against the partition key, hard span caps (90 / 30 days), server-sideGROUP BY + LIMIT, the shared 10-minute cache, and request coalescing (singleflight) so concurrent identical requests trigger a single BigQuery job. A full cache miss on the default 3-day window scans well under 1 GB — a fraction of a cent at on-demand pricing.
Daily land-station observations from NOAA GHCN-Daily
(bigquery-public-data.ghcn_d): ~20,000 stations worldwide, about one day
behind real time.
| Widget | Description |
|---|---|
| KPI strip | Stations reporting, hottest / coldest / wettest station, stations with snow |
| Station Map | World map of the snapshot day's observations (max/min temp, precipitation, snow) |
| Daily Trend | Network-wide temperature/precipitation averages and reporting-station counts |
Filters: snapshot date (back to 1900) and a 7–31 day trailing window. Trend averages are means across reporting stations — a network mean, not a physical global average. The default date skips the newest 1–2 days while GHCN backfill settles.
On-chain financial fundamentals for Bitcoin and Ethereum, powered by
bigquery-public-data.crypto_bitcoin and crypto_ethereum (refreshed daily).
Four lazily-loaded tabs, each behind its own endpoint and the shared
10-minute cache:
| Tab | Widgets |
|---|---|
| Network Pulse | Per-chain KPI strip (latest complete UTC day), daily transactions, value settled, active addresses (approx. distinct senders, ≤90d), block fullness, block production |
| Fee Market | BTC median sat/vB vs ETH avg gwei trend, BTC miner revenue (subsidy vs fees), ETH EIP-1559 burned vs tips, congestion-vs-fee scatter |
| Whales & Flow | Top 50 largest transfers (explorer links), whale-sized tx trend (≥100 BTC / ≥1,000 ETH), top receiving addresses, top-1% value concentration |
| Token Economy | Top 25 ERC-20 tokens by transfer count, token vs native activity, new contract deployments, token movement treemap |
Ranges: 7/30/90 days everywhere, plus 1 year for the slim aggregate trends
(pulse, fees); token queries cap at 30 days. Every query filters the
partition key — crypto_ethereum prunes daily on block_timestamp, while
crypto_bitcoin is month-partitioned on block_timestamp_month, so BTC
queries carry both the month bound and the exact timestamp window.
These datasets contain on-chain data only — no fiat prices. Everything is reported in native units (BTC, ETH, gwei) or counts:
- Value settled (BTC) sums transaction outputs, which include change returning to the sender — an upper bound on economic volume, not a measure of "money changing hands". The chart tooltip carries the same caveat.
- Active addresses are approximate distinct senders per day
(
APPROX_COUNT_DISTINCT, ~1% error) — a network-activity gauge, not a user count (one user can hold many addresses). - Token activity is measured in transfer counts, never summed values:
cross-token value sums are meaningless without prices, and the
decimalsmetadata needed to scale raw amounts is unreliable for long-tail tokens. - Whale thresholds (≥100 BTC, ≥1,000 ETH) are named constants in native units; there is no USD equivalent in the datasets.
- Backend: create
backend/opendata_<name>.go(typed rows +BQClientmethods) andbackend/opendata_<name>_handlers.go, routed under/api/opendata/<name>/*. - Frontend: build the dashboard component in
frontend/src/opendata/and register it infrontend/src/opendata/registry.tsx— the sidebar entry, header, and routing come for free.
frontend/ React 19 + Vite + ECharts + Tailwind CSS v4
catalog/ Dataplex graph view (react-force-graph 2D/3D, three.js)
backend/ Go net/http server
main.go HTTP server, routing, middleware
bigquery.go All INFORMATION_SCHEMA queries
handlers.go Dashboard endpoints with errgroup concurrency
catalog_handlers.go OKF graph/search/concept/import endpoints
okf.go OKF bundle engine (parse, graph, read/write concepts)
catalog_dataplex.go Dataplex SearchEntries -> OKF concept mapping
cache.go In-memory TTL cache (sync.Map, 10-min TTL)
filters.go Global filter parsing & SQL clause builders
config.go YAML config loader
The backend uses errgroup to run all widget queries for a dashboard in parallel, and caches results for 10 minutes to reduce BigQuery API calls.
BigLens is built entirely on BigQuery's INFORMATION_SCHEMA — a set of read-only system views that expose metadata about your BigQuery resources. These views provide storage metrics, job execution history, slot utilization, and optimization recommendations, all queryable with standard SQL.
For full documentation, see the official Google Cloud reference: BigQuery INFORMATION_SCHEMA Introduction