setOrgFilter(e.target.value)}
+ className="h-8 w-64"
+ placeholder="Search org, principal, SQL or query id…"
+ value={search}
+ onChange={(e) => setSearch(e.target.value)}
/>
@@ -180,10 +367,10 @@ export function TrinoQueries() {
} />
0 ? "warning" : "default"}
- hint="waiting on a resource group"
+ hint="queued, waiting for resources, planning or starting"
icon={}
/>
}
/>
+ {byOrg.length > 0 && (
+
+ (all.some((q) => q.org === key) ? : {key})}
+ selected={orgPick}
+ onSelect={setOrgPick}
+ />
+ {hasInstances && (
+ {key}}
+ selected={instancePick}
+ onSelect={setInstancePick}
+ />
+ )}
+
+ )}
+
-
+
{activeOnly ? "Active queries" : "Queries held by the coordinator"}
+
+ {BUCKETS.filter((b) => !(activeOnly && b.activeOnly === false)).map((b) => (
+
+ ))}
+ {(orgPick !== null || instancePick !== null) && (
+
+ )}
+
{queries.isLoading ? (
-
+
) : queries.isError ? (
void queries.refetch()} />
) : rows.length === 0 ? (
@@ -225,13 +462,16 @@ export function TrinoQueries() {
- State
- Org
+
+
+
Query
- Elapsed
- CPU
- Scanned
- Peak mem
+ {hasInstances && Instance}
+
+
+
+
+
@@ -239,62 +479,113 @@ export function TrinoQueries() {
{rows.map((q) => {
const flag = trinoQueryFlag(q);
const perRow = trinoScanEfficiency(q);
+ const sql = trinoSqlSummary(q.query);
+ const open = expanded.has(q.query_id);
return (
-
-
- {q.state}
- {flag && (
-
- {FLAG_LABEL[flag]}
-
- )}
-
-
- {q.org ? (
-
- ) : (
- // No org means a control-plane principal: the
- // reconcile loop's DDL or this console's own
- // reads, both tagged by X-Trino-Source.
-
- {q.source || q.principal || "—"}
+
+ toggle(q.query_id)}
+ >
+
+ {open ? : }
+
+
+ {q.state}
+ {flag && (
+
+ {FLAG_LABEL[flag]}
+
+ )}
+
+
+ {q.org ? (
+
+ ) : (
+ // No org means a control-plane principal: the
+ // reconcile loop's DDL or this console's own
+ // reads, both tagged by X-Trino-Source.
+
+ {q.source || q.principal || "—"}
+
+ )}
+ {q.org && q.principal && (
+ {q.principal}
+ )}
+
+
+ {sql.verb && (
+
+ {sql.verb}
+ {sql.target && {sql.target}}
+
+ )}
+
+ {sql.text || q.query}
+
+ {hasInstances && (
+ {q.instance || "—"}
)}
-
-
-
- {q.query}
-
-
- {q.resource_group || "unassigned"}
- {q.progress_percentage !== null
- ? ` · ${q.progress_percentage.toFixed(0)}%`
- : ""}
- {perRow !== null ? ` · ${fmtBytes(perRow)}/row` : ""}
-
-
-
- {fmtDurationMs(q.elapsed_ms)}
-
-
- {fmtDurationMs(q.cpu_ms)}
-
-
- {fmtBytes(q.physical_input_bytes)}
-
-
- {fmtBytes(q.peak_memory_bytes)}
-
-
- {isActiveTrinoQuery(q) && (
-
-
-
- )}
-
-
+
+ {fmtDurationMs(q.elapsed_ms)}
+ {q.queued_ms > 1000 && (
+ {fmtDurationMs(q.queued_ms)} queued
+ )}
+
+
+
+
+ {fmtDurationMs(q.cpu_ms)}
+
+ {fmtBytes(q.physical_input_bytes)}
+ {perRow !== null && (
+ {fmtBytes(perRow)}/row
+ )}
+
+ {fmtBytes(q.peak_memory_bytes)}
+ e.stopPropagation()}>
+ {isActiveTrinoQuery(q) && (
+
+
+
+ )}
+
+
+ {open && (
+
+
+
+
+ {q.query_id}
+ {q.principal || "—"}
+ {q.source || "—"}
+ {q.resource_group || "unassigned"}
+
+ {q.running_drivers} running · {q.queued_drivers} queued · {q.completed_drivers}/{q.total_drivers} done
+
+ {fmtCompact(q.processed_input_rows)}
+ {fmtBytes(q.internal_network_bytes)}
+ {fmtBytes(q.spilled_bytes)}
+ {fmtDurationMs(q.queued_ms)}
+ {sql.tag && {sql.tag}}
+ {q.error_type && (
+
+ {q.error_type}
+ {q.error_code ? ` · ${q.error_code}` : ""}
+
+ )}
+
+
+ {q.query}
+
+
+
+ )}
+
);
})}
diff --git a/controlplane/admin/ui/src/types/api.ts b/controlplane/admin/ui/src/types/api.ts
index 55411c9c..4b16c08c 100644
--- a/controlplane/admin/ui/src/types/api.ts
+++ b/controlplane/admin/ui/src/types/api.ts
@@ -774,6 +774,9 @@ export interface TrinoQuery {
source: string;
resource_group: string;
query: string;
+ // instance is the shared-pool instance whose coordinator holds the query.
+ // Absent on a cell with a single coordinator.
+ instance?: string;
created?: string;
elapsed_ms: number;
queued_ms: number;
diff --git a/controlplane/trino_pool_observer.go b/controlplane/trino_pool_observer.go
index 1bc94cf5..ec3fb67c 100644
--- a/controlplane/trino_pool_observer.go
+++ b/controlplane/trino_pool_observer.go
@@ -139,6 +139,9 @@ func (o *trinoPoolObserver) Queries(ctx context.Context) ([]admin.TrinoQuery, er
continue
}
succeeded++
+ for i := range queries {
+ queries[i].Instance = member.instanceID
+ }
all = append(all, queries...)
}
if succeeded == 0 {
@@ -158,6 +161,9 @@ func (o *trinoPoolObserver) Query(ctx context.Context, queryID string) (*admin.T
for _, member := range members {
query, err := member.client.Query(ctx, queryID)
if err == nil {
+ if query != nil {
+ query.Instance = member.instanceID
+ }
return query, nil
}
lastErr = err
diff --git a/controlplane/trino_pool_observer_test.go b/controlplane/trino_pool_observer_test.go
index 929acfbc..182241b8 100644
--- a/controlplane/trino_pool_observer_test.go
+++ b/controlplane/trino_pool_observer_test.go
@@ -115,6 +115,14 @@ func TestTrinoPoolObserverUnionsLiveMembers(t *testing.T) {
if lister.poolIDs[0] != "registered:cell-001" {
t.Fatalf("listed pool %q, want the stored pool id", lister.poolIDs[0])
}
+ // Each query names the instance whose coordinator holds it, so the
+ // console can show one instance saturated while the others idle.
+ for _, q := range queries {
+ want := map[string]string{"qa": "cell-a", "qb": "cell-b"}[q.QueryID]
+ if q.Instance != want {
+ t.Fatalf("query %s instance = %q, want %q", q.QueryID, q.Instance, want)
+ }
+ }
}
// One unreachable member must not hide the rest; only a pool where every
@@ -175,6 +183,24 @@ func TestTrinoPoolObserverKillsOnOwningMember(t *testing.T) {
}
}
+func TestTrinoPoolObserverQueryDetailNamesItsInstance(t *testing.T) {
+ members := map[string]*fakePoolMember{
+ "cell-a": {queries: []admin.TrinoQuery{{QueryID: "qa"}}},
+ "cell-b": {queries: []admin.TrinoQuery{{QueryID: "qb"}}},
+ }
+ o, _, _ := poolObserverFixture([]configstore.TrinoPoolInstance{
+ instance("cell-a", trinopool.PhaseServing),
+ instance("cell-b", trinopool.PhaseServing),
+ }, members)
+ q, err := o.Query(context.Background(), "qb")
+ if err != nil {
+ t.Fatalf("Query: %v", err)
+ }
+ if q.Instance != "cell-b" {
+ t.Fatalf("Query(qb).Instance = %q, want cell-b", q.Instance)
+ }
+}
+
// A retired instance's client is dropped, so the cache cannot grow without
// bound as the pool replaces members.
func TestTrinoPoolObserverDropsRetiredClients(t *testing.T) {
diff --git a/tests/mw-dev/e2e/trino.sh b/tests/mw-dev/e2e/trino.sh
index 3c54d934..9a391795 100644
--- a/tests/mw-dev/e2e/trino.sh
+++ b/tests/mw-dev/e2e/trino.sh
@@ -410,6 +410,15 @@ done
api "$API/api/v1/trino/queries/$query_id?cell=pool-test" | jq -e --arg q "$query_id" --arg org "$ORG_A" \
'.query_id == $q and .org == $org' >/dev/null \
|| fail "admin Trino query detail did not identify tenant A query $query_id"
+# A pooled cell names the instance whose coordinator holds each query (the
+# console's per-instance breakdown). It must be one of the pool's own
+# instances, in both the list and the detail.
+pool_instances="$(api "$API/api/v1/trino/instances?cell=pool-test" | jq -c '[.instances[].instance_id]')"
+for view in "$(api "$API/api/v1/trino/queries?cell=pool-test&org=$ORG_A&active=1" | jq -c --arg q "$query_id" '.queries[] | select(.query_id == $q)')" \
+ "$(api "$API/api/v1/trino/queries/$query_id?cell=pool-test")"; do
+ printf %s "$view" | jq -e --argjson ids "$pool_instances" '(.instance // "") as $i | $i != "" and ($ids | index($i)) != null' >/dev/null \
+ || fail "pooled Trino query $query_id does not name a pool instance (instances $pool_instances): $view"
+done
code="$(curl --cacert "$CA" -sS -o /tmp/trino-cross-query -w '%{http_code}' \
--user "$DB_B:$pw_b" -H "X-Trino-User: $DB_B" "$TRINO/v1/query/$query_id")"
[ "$code" = 403 ] || fail "tenant B query detail for tenant A returned HTTP $code, want 403: $(cat /tmp/trino-cross-query)"