Skip to content

Commit e1ae4d4

Browse files
authored
feat(dashboard): show table data size (#22288)
Signed-off-by: xxchan <[email protected]>
1 parent a2e9711 commit e1ae4d4

File tree

4 files changed

+118
-14
lines changed

4 files changed

+118
-14
lines changed

dashboard/components/Relations.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,23 @@ export const vnodeCountColumn: Column<RwTable> = {
110110
content: (r) => r.maybeVnodeCount ?? "?",
111111
}
112112

113-
export const tableColumns = [primaryKeyColumn, vnodeCountColumn]
113+
// Helper function to format bytes into human readable format
114+
function formatBytes(bytes: number | undefined): string {
115+
if (bytes === undefined) return "unknown"
116+
if (bytes === 0) return "0 B"
117+
const k = 1024
118+
const sizes = ["B", "KB", "MB", "GB", "TB"]
119+
const i = Math.floor(Math.log(bytes) / Math.log(k))
120+
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + " " + sizes[i]
121+
}
122+
123+
export const dataSizeColumn: Column<Relation> = {
124+
name: "Data Size",
125+
width: 2,
126+
content: (r) => formatBytes(r.totalSizeBytes),
127+
}
128+
129+
export const tableColumns = [primaryKeyColumn, vnodeCountColumn, dataSizeColumn]
114130

115131
export const connectorColumnSource: Column<RwSource> = {
116132
name: "Connector",

dashboard/lib/api/streaming.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ export interface Relation {
6767
ownerName?: string
6868
schemaName?: string
6969
databaseName?: string
70+
totalSizeBytes?: number
7071
}
7172

7273
export class StreamingJob {
@@ -166,10 +167,23 @@ export async function getFragmentToRelationMap() {
166167
return fragmentVertexToRelationMap
167168
}
168169

170+
interface ExtendedTable extends Table {
171+
totalSizeBytes?: number
172+
}
173+
174+
// Extended conversion function for Table with extra fields
175+
function extendedTableFromJSON(json: any): ExtendedTable {
176+
const table = Table.fromJSON(json)
177+
return {
178+
...table,
179+
totalSizeBytes: json.total_size_bytes,
180+
}
181+
}
182+
169183
async function getTableCatalogsInner(
170184
path: "tables" | "materialized_views" | "indexes" | "internal_tables"
171-
) {
172-
let list: Table[] = (await api.get(`/${path}`)).map(Table.fromJSON)
185+
): Promise<ExtendedTable[]> {
186+
let list = (await api.get(`/${path}`)).map(extendedTableFromJSON)
173187
list = sortBy(list, (x) => x.id)
174188
return list
175189
}

src/meta/node/src/server.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,7 @@ pub async fn start_service_as_election_leader(
422422
prometheus_client,
423423
prometheus_selector,
424424
metadata_manager: metadata_manager.clone(),
425+
hummock_manager: hummock_manager.clone(),
425426
compute_clients: ComputeClientPool::new(1, env.opts.compute_client_config.clone()), /* typically no need for plural clients */
426427
diagnose_command,
427428
trace_state,

src/meta/src/dashboard/mod.rs

Lines changed: 84 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ use tower_http::add_extension::AddExtensionLayer;
3131
use tower_http::compression::CompressionLayer;
3232
use tower_http::cors::{self, CorsLayer};
3333

34+
use crate::hummock::HummockManagerRef;
3435
use crate::manager::MetadataManager;
3536
use crate::manager::diagnose::DiagnoseCommandRef;
3637

@@ -41,6 +42,7 @@ pub struct DashboardService {
4142
pub prometheus_client: Option<prometheus_http_query::Client>,
4243
pub prometheus_selector: String,
4344
pub metadata_manager: MetadataManager,
45+
pub hummock_manager: HummockManagerRef,
4446
pub compute_clients: ComputeClientPool,
4547
pub diagnose_command: DiagnoseCommandRef,
4648
pub trace_state: otlp_embedded::StateRef,
@@ -65,6 +67,7 @@ pub(super) mod handlers {
6567
Index, PbDatabase, PbSchema, Sink, Source, Subscription, Table, View,
6668
};
6769
use risingwave_pb::common::{WorkerNode, WorkerType};
70+
use risingwave_pb::hummock::TableStats;
6871
use risingwave_pb::meta::list_object_dependencies_response::PbObjectDependencies;
6972
use risingwave_pb::meta::{
7073
ActorIds, FragmentIdToActorIdMap, FragmentToRelationMap, PbTableFragments, RelationIdInfos,
@@ -76,14 +79,48 @@ pub(super) mod handlers {
7679
};
7780
use risingwave_pb::stream_plan::FragmentTypeFlag;
7881
use risingwave_pb::user::PbUserInfo;
79-
use serde::Deserialize;
82+
use serde::{Deserialize, Serialize};
8083
use serde_json::json;
8184
use thiserror_ext::AsReport;
8285

8386
use super::*;
8487
use crate::controller::fragment::StreamingJobInfo;
8588
use crate::rpc::await_tree::{dump_cluster_await_tree, dump_worker_node_await_tree};
8689

90+
#[derive(Serialize)]
91+
pub struct TableWithStats {
92+
#[serde(flatten)]
93+
pub table: Table,
94+
pub total_size_bytes: i64,
95+
pub total_key_count: i64,
96+
pub total_key_size: i64,
97+
pub total_value_size: i64,
98+
pub compressed_size: u64,
99+
}
100+
101+
impl TableWithStats {
102+
pub fn from_table_and_stats(table: Table, stats: Option<&TableStats>) -> Self {
103+
match stats {
104+
Some(stats) => Self {
105+
total_size_bytes: stats.total_key_size + stats.total_value_size,
106+
total_key_count: stats.total_key_count,
107+
total_key_size: stats.total_key_size,
108+
total_value_size: stats.total_value_size,
109+
compressed_size: stats.total_compressed_size,
110+
table,
111+
},
112+
None => Self {
113+
total_size_bytes: 0,
114+
total_key_count: 0,
115+
total_key_size: 0,
116+
total_value_size: 0,
117+
compressed_size: 0,
118+
table,
119+
},
120+
}
121+
}
122+
}
123+
87124
pub struct DashboardError(anyhow::Error);
88125
pub type Result<T> = std::result::Result<T, DashboardError>;
89126

@@ -126,29 +163,60 @@ pub(super) mod handlers {
126163

127164
async fn list_table_catalogs_inner(
128165
metadata_manager: &MetadataManager,
166+
hummock_manager: &HummockManagerRef,
129167
table_type: TableType,
130-
) -> Result<Json<Vec<Table>>> {
168+
) -> Result<Json<Vec<TableWithStats>>> {
131169
let tables = metadata_manager
132170
.catalog_controller
133171
.list_tables_by_type(table_type.into())
134172
.await
135173
.map_err(err)?;
136174

137-
Ok(Json(tables))
175+
// Get table statistics from hummock manager
176+
let version_stats = hummock_manager.get_version_stats().await;
177+
178+
let tables_with_stats = tables
179+
.into_iter()
180+
.map(|table| {
181+
let stats = version_stats.table_stats.get(&table.id);
182+
TableWithStats::from_table_and_stats(table, stats)
183+
})
184+
.collect();
185+
186+
Ok(Json(tables_with_stats))
138187
}
139188

140189
pub async fn list_materialized_views(
141190
Extension(srv): Extension<Service>,
142-
) -> Result<Json<Vec<Table>>> {
143-
list_table_catalogs_inner(&srv.metadata_manager, TableType::MaterializedView).await
191+
) -> Result<Json<Vec<TableWithStats>>> {
192+
list_table_catalogs_inner(
193+
&srv.metadata_manager,
194+
&srv.hummock_manager,
195+
TableType::MaterializedView,
196+
)
197+
.await
144198
}
145199

146-
pub async fn list_tables(Extension(srv): Extension<Service>) -> Result<Json<Vec<Table>>> {
147-
list_table_catalogs_inner(&srv.metadata_manager, TableType::Table).await
200+
pub async fn list_tables(
201+
Extension(srv): Extension<Service>,
202+
) -> Result<Json<Vec<TableWithStats>>> {
203+
list_table_catalogs_inner(
204+
&srv.metadata_manager,
205+
&srv.hummock_manager,
206+
TableType::Table,
207+
)
208+
.await
148209
}
149210

150-
pub async fn list_index_tables(Extension(srv): Extension<Service>) -> Result<Json<Vec<Table>>> {
151-
list_table_catalogs_inner(&srv.metadata_manager, TableType::Index).await
211+
pub async fn list_index_tables(
212+
Extension(srv): Extension<Service>,
213+
) -> Result<Json<Vec<TableWithStats>>> {
214+
list_table_catalogs_inner(
215+
&srv.metadata_manager,
216+
&srv.hummock_manager,
217+
TableType::Index,
218+
)
219+
.await
152220
}
153221

154222
pub async fn list_indexes(Extension(srv): Extension<Service>) -> Result<Json<Vec<Index>>> {
@@ -177,8 +245,13 @@ pub(super) mod handlers {
177245

178246
pub async fn list_internal_tables(
179247
Extension(srv): Extension<Service>,
180-
) -> Result<Json<Vec<Table>>> {
181-
list_table_catalogs_inner(&srv.metadata_manager, TableType::Internal).await
248+
) -> Result<Json<Vec<TableWithStats>>> {
249+
list_table_catalogs_inner(
250+
&srv.metadata_manager,
251+
&srv.hummock_manager,
252+
TableType::Internal,
253+
)
254+
.await
182255
}
183256

184257
pub async fn list_sources(Extension(srv): Extension<Service>) -> Result<Json<Vec<Source>>> {

0 commit comments

Comments
 (0)