risingwave_ctl/cmd_impl/
profile.rs1use std::collections::HashSet;
16use std::path::PathBuf;
17
18use chrono::prelude::Local;
19use clap::ValueEnum;
20use futures::future::try_join_all;
21use risingwave_pb::common::WorkerType;
22use risingwave_pb::monitor_service::ProfilingResponse;
23use risingwave_rpc_client::MonitorClientPool;
24use thiserror_ext::AsReport;
25use tokio::fs::{File, create_dir_all};
26use tokio::io::AsyncWriteExt;
27
28use crate::CtlContext;
29
30#[derive(Clone, Copy, Debug, ValueEnum)]
31#[clap(rename_all = "kebab-case")]
32pub enum ProfileWorkerType {
33 Frontend,
34 ComputeNode,
35 Compactor,
36 Meta,
37}
38
39pub async fn cpu_profile(
40 context: &CtlContext,
41 sleep_s: u64,
42 worker_types: Vec<ProfileWorkerType>,
43) -> anyhow::Result<()> {
44 let meta_client = context.meta_client().await?;
45
46 let workers = meta_client.list_worker_nodes(None).await?;
47 let target_types = selected_worker_types(&worker_types);
48 let target_nodes = workers
49 .into_iter()
50 .filter(|w| target_types.contains(&w.r#type()));
51
52 let clients = MonitorClientPool::adhoc();
53
54 let profile_root_path = std::env::var("PREFIX_PROFILING").unwrap_or_else(|_| {
55 tracing::info!("PREFIX_PROFILING is not set, using current directory");
56 "./".to_owned()
57 });
58 let profile_root_path = PathBuf::from(&profile_root_path);
59 let dir_name = Local::now().format("%Y-%m-%d-%H-%M-%S").to_string();
60 let dir_path = profile_root_path.join(dir_name);
61 create_dir_all(&dir_path).await?;
62
63 let mut profile_futs = vec![];
64
65 for cn in target_nodes {
68 let client = clients.get(&cn).await?;
69
70 let dir_path_ref = &dir_path;
71
72 let fut = async move {
73 let response = client.profile(sleep_s).await;
74 let host_addr = cn.get_host().expect("Should have host address");
75 let node_name = format!(
76 "{}-{}-{}",
77 worker_type_label(cn.r#type()),
78 host_addr.get_host().replace('.', "-"),
79 host_addr.get_port()
80 );
81 let svg_file_name = format!("{}.svg", node_name);
82 match response {
83 Ok(ProfilingResponse { result }) => {
84 let mut file = File::create(dir_path_ref.join(svg_file_name)).await?;
85 file.write_all(&result).await?;
86 file.flush().await?;
87 }
88 Err(err) => {
89 tracing::error!(
90 error = %err.as_report(),
91 node_name,
92 "Failed to get profiling result",
93 );
94 }
95 }
96 Ok::<_, anyhow::Error>(())
97 };
98 profile_futs.push(fut);
99 }
100
101 try_join_all(profile_futs).await?;
102
103 println!("Profiling results are saved at {}", dir_path.display());
104
105 Ok(())
106}
107
108pub async fn heap_profile(
109 context: &CtlContext,
110 dir: Option<String>,
111 worker_types: Vec<ProfileWorkerType>,
112) -> anyhow::Result<()> {
113 let dir = dir.unwrap_or_default();
114 let meta_client = context.meta_client().await?;
115
116 let workers = meta_client.list_worker_nodes(None).await?;
117 let target_types = selected_worker_types(&worker_types);
118 let target_nodes = workers
119 .into_iter()
120 .filter(|w| target_types.contains(&w.r#type()));
121
122 let clients = MonitorClientPool::adhoc();
123
124 let mut profile_futs = vec![];
125
126 for cn in target_nodes {
129 let client = clients.get(&cn).await?;
130 let dir = &dir;
131
132 let fut = async move {
133 let response = client.heap_profile(dir.clone()).await;
134 let host_addr = cn.get_host().expect("Should have host address");
135
136 let node_name = format!(
137 "{}-{}-{}",
138 worker_type_label(cn.r#type()),
139 host_addr.get_host().replace('.', "-"),
140 host_addr.get_port()
141 );
142
143 if let Err(err) = response {
144 tracing::error!(
145 error = %err.as_report(),
146 node_name,
147 "Failed to dump profile",
148 );
149 }
150 Ok::<_, anyhow::Error>(())
151 };
152 profile_futs.push(fut);
153 }
154
155 try_join_all(profile_futs).await?;
156
157 println!(
158 "Profiling results are saved at {} on each target node",
159 PathBuf::from(dir).display()
160 );
161
162 Ok(())
163}
164
165fn selected_worker_types(values: &[ProfileWorkerType]) -> HashSet<WorkerType> {
166 if values.is_empty() {
168 return HashSet::from([
169 WorkerType::Frontend,
170 WorkerType::ComputeNode,
171 WorkerType::Compactor,
172 WorkerType::Meta,
173 ]);
174 }
175 values.iter().map(|value| to_worker_type(*value)).collect()
176}
177
178fn to_worker_type(value: ProfileWorkerType) -> WorkerType {
179 match value {
180 ProfileWorkerType::Frontend => WorkerType::Frontend,
181 ProfileWorkerType::ComputeNode => WorkerType::ComputeNode,
182 ProfileWorkerType::Compactor => WorkerType::Compactor,
183 ProfileWorkerType::Meta => WorkerType::Meta,
184 }
185}
186
187fn worker_type_label(worker_type: WorkerType) -> &'static str {
188 match worker_type {
189 WorkerType::Frontend => "frontend",
190 WorkerType::ComputeNode => "compute-node",
191 WorkerType::Compactor => "compactor",
192 WorkerType::Meta => "meta",
193 _ => "unknown",
194 }
195}