catalog/system_schema/information_schema/
cluster_info.rs1use std::sync::{Arc, Weak};
16use std::time::Duration;
17
18use arrow_schema::SchemaRef as ArrowSchemaRef;
19use common_catalog::consts::INFORMATION_SCHEMA_CLUSTER_INFO_TABLE_ID;
20use common_error::ext::BoxedError;
21use common_meta::cluster::{DatanodeStatus, NodeInfo, NodeStatus};
22use common_recordbatch::adapter::RecordBatchStreamAdapter;
23use common_recordbatch::{RecordBatch, SendableRecordBatchStream};
24use common_time::timestamp::Timestamp;
25use common_workload::DatanodeWorkloadType;
26use datafusion::execution::TaskContext;
27use datafusion::physical_plan::SendableRecordBatchStream as DfSendableRecordBatchStream;
28use datafusion::physical_plan::stream::RecordBatchStreamAdapter as DfRecordBatchStreamAdapter;
29use datafusion::physical_plan::streaming::PartitionStream as DfPartitionStream;
30use datatypes::prelude::{ConcreteDataType, ScalarVectorBuilder, VectorRef};
31use datatypes::schema::{ColumnSchema, Schema, SchemaRef};
32use datatypes::timestamp::TimestampMillisecond;
33use datatypes::value::Value;
34use datatypes::vectors::{
35 Int64VectorBuilder, StringVectorBuilder, TimestampMillisecondVectorBuilder,
36};
37use serde::Serialize;
38use snafu::ResultExt;
39use store_api::storage::{ScanRequest, TableId};
40
41use crate::CatalogManager;
42use crate::error::{CreateRecordBatchSnafu, InternalSnafu, Result};
43use crate::system_schema::SystemTable;
44use crate::system_schema::information_schema::{
45 CLUSTER_INFO, InformationSchemaTableScope, InformationTable, Predicates,
46};
47use crate::system_schema::utils;
48
49const PEER_TYPE_FRONTEND: &str = "FRONTEND";
50const PEER_TYPE_METASRV: &str = "METASRV";
51
52const PEER_ID: &str = "peer_id";
53const PEER_TYPE: &str = "peer_type";
54const PEER_ADDR: &str = "peer_addr";
55const PEER_HOSTNAME: &str = "peer_hostname";
56const TOTAL_CPU_MILLICORES: &str = "total_cpu_millicores";
57const TOTAL_MEMORY_BYTES: &str = "total_memory_bytes";
58const CPU_USAGE_MILLICORES: &str = "cpu_usage_millicores";
59const MEMORY_USAGE_BYTES: &str = "memory_usage_bytes";
60const VERSION: &str = "version";
61const GIT_COMMIT: &str = "git_commit";
62const START_TIME: &str = "start_time";
63const UPTIME: &str = "uptime";
64const ACTIVE_TIME: &str = "active_time";
65const NODE_STATUS: &str = "node_status";
66
67const INIT_CAPACITY: usize = 42;
68
69#[derive(Debug)]
87pub(super) struct InformationSchemaClusterInfo {
88 schema: SchemaRef,
89 catalog_manager: Weak<dyn CatalogManager>,
90}
91
92impl InformationSchemaClusterInfo {
93 pub(super) fn new(catalog_manager: Weak<dyn CatalogManager>) -> Self {
94 Self {
95 schema: Self::schema(),
96 catalog_manager,
97 }
98 }
99
100 pub(crate) fn schema() -> SchemaRef {
101 Arc::new(Schema::new(vec![
102 ColumnSchema::new(PEER_ID, ConcreteDataType::int64_datatype(), false),
103 ColumnSchema::new(PEER_TYPE, ConcreteDataType::string_datatype(), false),
104 ColumnSchema::new(PEER_ADDR, ConcreteDataType::string_datatype(), true),
105 ColumnSchema::new(PEER_HOSTNAME, ConcreteDataType::string_datatype(), true),
106 ColumnSchema::new(
107 TOTAL_CPU_MILLICORES,
108 ConcreteDataType::int64_datatype(),
109 false,
110 ),
111 ColumnSchema::new(
112 TOTAL_MEMORY_BYTES,
113 ConcreteDataType::int64_datatype(),
114 false,
115 ),
116 ColumnSchema::new(
117 CPU_USAGE_MILLICORES,
118 ConcreteDataType::int64_datatype(),
119 false,
120 ),
121 ColumnSchema::new(
122 MEMORY_USAGE_BYTES,
123 ConcreteDataType::int64_datatype(),
124 false,
125 ),
126 ColumnSchema::new(VERSION, ConcreteDataType::string_datatype(), false),
127 ColumnSchema::new(GIT_COMMIT, ConcreteDataType::string_datatype(), false),
128 ColumnSchema::new(
129 START_TIME,
130 ConcreteDataType::timestamp_millisecond_datatype(),
131 true,
132 ),
133 ColumnSchema::new(UPTIME, ConcreteDataType::string_datatype(), true),
134 ColumnSchema::new(ACTIVE_TIME, ConcreteDataType::string_datatype(), true),
135 ColumnSchema::new(NODE_STATUS, ConcreteDataType::string_datatype(), true),
136 ]))
137 }
138
139 fn builder(&self) -> InformationSchemaClusterInfoBuilder {
140 InformationSchemaClusterInfoBuilder::new(self.schema.clone(), self.catalog_manager.clone())
141 }
142}
143
144impl InformationTable for InformationSchemaClusterInfo {
145 fn scope(&self) -> InformationSchemaTableScope {
146 InformationSchemaTableScope::Global
147 }
148}
149
150impl SystemTable for InformationSchemaClusterInfo {
151 fn table_id(&self) -> TableId {
152 INFORMATION_SCHEMA_CLUSTER_INFO_TABLE_ID
153 }
154
155 fn table_name(&self) -> &'static str {
156 CLUSTER_INFO
157 }
158
159 fn schema(&self) -> SchemaRef {
160 self.schema.clone()
161 }
162
163 fn to_stream(&self, request: ScanRequest) -> Result<SendableRecordBatchStream> {
164 let schema = self.schema.arrow_schema().clone();
165 let mut builder = self.builder();
166 let stream = Box::pin(DfRecordBatchStreamAdapter::new(
167 schema,
168 futures::stream::once(async move {
169 builder
170 .make_cluster_info(Some(request))
171 .await
172 .map(|x| x.into_df_record_batch())
173 .map_err(Into::into)
174 }),
175 ));
176 Ok(Box::pin(
177 RecordBatchStreamAdapter::try_new(stream)
178 .map_err(BoxedError::new)
179 .context(InternalSnafu)?,
180 ))
181 }
182}
183
184struct InformationSchemaClusterInfoBuilder {
185 schema: SchemaRef,
186 catalog_manager: Weak<dyn CatalogManager>,
187
188 peer_ids: Int64VectorBuilder,
189 peer_types: StringVectorBuilder,
190 peer_addrs: StringVectorBuilder,
191 peer_hostnames: StringVectorBuilder,
192 total_cpu_millicores: Int64VectorBuilder,
193 total_memory_bytes: Int64VectorBuilder,
194 cpu_usage_millicores: Int64VectorBuilder,
195 memory_usage_bytes: Int64VectorBuilder,
196 versions: StringVectorBuilder,
197 git_commits: StringVectorBuilder,
198 start_times: TimestampMillisecondVectorBuilder,
199 uptimes: StringVectorBuilder,
200 active_times: StringVectorBuilder,
201 node_status: StringVectorBuilder,
202}
203
204impl InformationSchemaClusterInfoBuilder {
205 fn new(schema: SchemaRef, catalog_manager: Weak<dyn CatalogManager>) -> Self {
206 Self {
207 schema,
208 catalog_manager,
209 peer_ids: Int64VectorBuilder::with_capacity(INIT_CAPACITY),
210 peer_types: StringVectorBuilder::with_capacity(INIT_CAPACITY),
211 peer_addrs: StringVectorBuilder::with_capacity(INIT_CAPACITY),
212 peer_hostnames: StringVectorBuilder::with_capacity(INIT_CAPACITY),
213 total_cpu_millicores: Int64VectorBuilder::with_capacity(INIT_CAPACITY),
214 total_memory_bytes: Int64VectorBuilder::with_capacity(INIT_CAPACITY),
215 cpu_usage_millicores: Int64VectorBuilder::with_capacity(INIT_CAPACITY),
216 memory_usage_bytes: Int64VectorBuilder::with_capacity(INIT_CAPACITY),
217 versions: StringVectorBuilder::with_capacity(INIT_CAPACITY),
218 git_commits: StringVectorBuilder::with_capacity(INIT_CAPACITY),
219 start_times: TimestampMillisecondVectorBuilder::with_capacity(INIT_CAPACITY),
220 uptimes: StringVectorBuilder::with_capacity(INIT_CAPACITY),
221 active_times: StringVectorBuilder::with_capacity(INIT_CAPACITY),
222 node_status: StringVectorBuilder::with_capacity(INIT_CAPACITY),
223 }
224 }
225
226 async fn make_cluster_info(&mut self, request: Option<ScanRequest>) -> Result<RecordBatch> {
228 let predicates = Predicates::from_scan_request(&request);
229 let information_extension = utils::information_extension(&self.catalog_manager)?;
230 let node_infos = information_extension.nodes().await?;
231 for node_info in node_infos {
232 self.add_node_info(&predicates, node_info);
233 }
234 self.finish()
235 }
236
237 fn add_node_info(&mut self, predicates: &Predicates, node_info: NodeInfo) {
238 let peer_type = node_info.status.role_name();
239 let peer_id = peer_id(peer_type, node_info.peer.id);
240
241 let row = [
242 (PEER_ID, &Value::from(peer_id)),
243 (PEER_TYPE, &Value::from(peer_type)),
244 (PEER_ADDR, &Value::from(node_info.peer.addr.as_str())),
245 (PEER_HOSTNAME, &Value::from(node_info.hostname.as_str())),
246 (VERSION, &Value::from(node_info.version.as_str())),
247 (GIT_COMMIT, &Value::from(node_info.git_commit.as_str())),
248 ];
249
250 if !predicates.eval(&row) {
251 return;
252 }
253
254 self.peer_ids.push(Some(peer_id));
255 self.peer_types.push(Some(peer_type));
256 self.peer_addrs.push(Some(&node_info.peer.addr));
257 self.peer_hostnames.push(Some(&node_info.hostname));
258 self.versions.push(Some(&node_info.version));
259 self.git_commits.push(Some(&node_info.git_commit));
260 if node_info.start_time_ms > 0 {
261 self.start_times
262 .push(Some(TimestampMillisecond(Timestamp::new_millisecond(
263 node_info.start_time_ms as i64,
264 ))));
265 self.uptimes.push(Some(
266 Self::format_duration_since(node_info.start_time_ms).as_str(),
267 ));
268 } else {
269 self.start_times.push(None);
270 self.uptimes.push(None);
271 }
272 self.total_cpu_millicores
273 .push(Some(node_info.total_cpu_millicores));
274 self.total_memory_bytes
275 .push(Some(node_info.total_memory_bytes));
276 self.cpu_usage_millicores
277 .push(Some(node_info.cpu_usage_millicores));
278 self.memory_usage_bytes
279 .push(Some(node_info.memory_usage_bytes));
280
281 if node_info.last_activity_ts > 0 {
282 self.active_times.push(Some(
283 Self::format_duration_since(node_info.last_activity_ts as u64).as_str(),
284 ));
285 } else {
286 self.active_times.push(None);
287 }
288 self.node_status
289 .push(format_node_status(&node_info).as_deref());
290 }
291
292 fn format_duration_since(ts: u64) -> String {
293 let now = common_time::util::current_time_millis() as u64;
294 let duration_since = now - ts;
295 humantime::format_duration(Duration::from_millis(duration_since)).to_string()
296 }
297
298 fn finish(&mut self) -> Result<RecordBatch> {
299 let columns: Vec<VectorRef> = vec![
300 Arc::new(self.peer_ids.finish()),
301 Arc::new(self.peer_types.finish()),
302 Arc::new(self.peer_addrs.finish()),
303 Arc::new(self.peer_hostnames.finish()),
304 Arc::new(self.total_cpu_millicores.finish()),
305 Arc::new(self.total_memory_bytes.finish()),
306 Arc::new(self.cpu_usage_millicores.finish()),
307 Arc::new(self.memory_usage_bytes.finish()),
308 Arc::new(self.versions.finish()),
309 Arc::new(self.git_commits.finish()),
310 Arc::new(self.start_times.finish()),
311 Arc::new(self.uptimes.finish()),
312 Arc::new(self.active_times.finish()),
313 Arc::new(self.node_status.finish()),
314 ];
315 RecordBatch::new(self.schema.clone(), columns).context(CreateRecordBatchSnafu)
316 }
317}
318
319impl DfPartitionStream for InformationSchemaClusterInfo {
320 fn schema(&self) -> &ArrowSchemaRef {
321 self.schema.arrow_schema()
322 }
323
324 fn execute(&self, _: Arc<TaskContext>) -> DfSendableRecordBatchStream {
325 let schema = self.schema.arrow_schema().clone();
326 let mut builder = self.builder();
327 Box::pin(DfRecordBatchStreamAdapter::new(
328 schema,
329 futures::stream::once(async move {
330 builder
331 .make_cluster_info(None)
332 .await
333 .map(|x| x.into_df_record_batch())
334 .map_err(Into::into)
335 }),
336 ))
337 }
338}
339
340fn peer_id(peer_type: &str, peer_id: u64) -> i64 {
341 if peer_type == PEER_TYPE_FRONTEND || peer_type == PEER_TYPE_METASRV {
342 -1
343 } else {
344 peer_id as i64
345 }
346}
347
348#[derive(Serialize)]
349struct DisplayMetasrvStatus {
350 is_leader: bool,
351}
352
353#[derive(Serialize)]
354struct DisplayDatanodeStatus {
355 workloads: Vec<DatanodeWorkloadType>,
356 leader_regions: usize,
357 follower_regions: usize,
358}
359
360impl From<&DatanodeStatus> for DisplayDatanodeStatus {
361 fn from(status: &DatanodeStatus) -> Self {
362 Self {
363 workloads: status
364 .workloads
365 .types
366 .iter()
367 .flat_map(|w| DatanodeWorkloadType::from_i32(*w))
368 .collect(),
369 leader_regions: status.leader_regions,
370 follower_regions: status.follower_regions,
371 }
372 }
373}
374
375fn format_node_status(node_info: &NodeInfo) -> Option<String> {
376 match &node_info.status {
377 NodeStatus::Datanode(datanode_status) => {
378 serde_json::to_string(&DisplayDatanodeStatus::from(datanode_status)).ok()
379 }
380 NodeStatus::Frontend(_) => None,
381 NodeStatus::Flownode(_) => None,
382 NodeStatus::Metasrv(metasrv_status) => {
383 if metasrv_status.is_leader {
384 serde_json::to_string(&DisplayMetasrvStatus { is_leader: true }).ok()
385 } else {
386 None
387 }
388 }
389 NodeStatus::Standalone => None,
390 }
391}