Skip to main content

catalog/system_schema/information_schema/
cluster_info.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use 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/// The `CLUSTER_INFO` table provides information about the current topology information of the cluster.
70///
71/// - `peer_id`: the peer server id.
72/// - `peer_type`: the peer type, such as `datanode`, `frontend`, `metasrv` etc.
73/// - `peer_addr`: the peer gRPC address.
74/// - `peer_hostname`: the hostname of the peer.
75/// - `total_cpu_millicores`: the total CPU millicores of the peer.
76/// - `total_memory_bytes`: the total memory bytes of the peer.
77/// - `cpu_usage_millicores`: the CPU usage millicores of the peer.
78/// - `memory_usage_bytes`: the memory usage bytes of the peer.
79/// - `version`: the build package version of the peer.
80/// - `git_commit`: the build git commit hash of the peer.
81/// - `start_time`: the starting time of the peer.
82/// - `uptime`: the uptime of the peer.
83/// - `active_time`: the time since the last activity of the peer.
84/// - `node_status`: the status info of the peer.
85///
86#[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    /// Construct the `information_schema.cluster_info` virtual table
227    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}