Skip to main content

risingwave_compute/rpc/service/
monitor_service.rs

1// Copyright 2022 RisingWave Labs
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::collections::{BTreeMap, HashMap, HashSet};
16use std::sync::Arc;
17use std::time::Duration;
18
19use foyer::{HybridCache, TracingOptions};
20use prometheus::core::Collector;
21use prometheus::proto::Metric;
22use risingwave_batch::task::BatchManager;
23use risingwave_batch::task::await_tree_key::BatchTask;
24use risingwave_common::bitmap::Bitmap;
25use risingwave_common::config::{MetricLevel, ServerConfig};
26use risingwave_common_heap_profiling::ProfileServiceImpl;
27use risingwave_hummock_sdk::HummockSstableObjectId;
28use risingwave_jni_core::jvm_runtime::dump_jvm_stack_traces;
29use risingwave_pb::monitor_service::monitor_service_server::MonitorService;
30use risingwave_pb::monitor_service::stack_trace_request::ActorTracesFormat;
31use risingwave_pb::monitor_service::{
32    AnalyzeHeapRequest, AnalyzeHeapResponse, ChannelStats, FragmentStats, GetProfileStatsRequest,
33    GetProfileStatsResponse, GetStreamingStatsRequest, GetStreamingStatsResponse,
34    GetTableCacheRefillStatsRequest, GetTableCacheRefillStatsResponse, HeapProfilingRequest,
35    HeapProfilingResponse, ListHeapProfilingRequest, ListHeapProfilingResponse, ProfilingRequest,
36    ProfilingResponse, RelationStats, StackTraceRequest, StackTraceResponse,
37    TieredCacheTracingRequest, TieredCacheTracingResponse,
38};
39use risingwave_storage::hummock::compactor::await_tree_key::Compaction;
40use risingwave_storage::hummock::event_handler::refiller::{
41    TableCacheRefillContext, TableCacheRefillMonitorSnapshot,
42};
43use risingwave_storage::hummock::store::HummockStorage;
44use risingwave_storage::hummock::{Block, Sstable, SstableBlockIndex};
45use risingwave_stream::executor::monitor::global_streaming_metrics;
46use risingwave_stream::task::LocalStreamManager;
47use risingwave_stream::task::await_tree_key::{Actor, BarrierAwait};
48use thiserror_ext::AsReport;
49use tonic::{Request, Response, Status};
50
51type MetaCache = HybridCache<HummockSstableObjectId, Box<Sstable>>;
52type BlockCache = HybridCache<SstableBlockIndex, Box<Block>>;
53
54#[derive(Clone)]
55pub struct MonitorServiceImpl {
56    stream_mgr: LocalStreamManager,
57    batch_mgr: Arc<BatchManager>,
58    profile_service: ProfileServiceImpl,
59    meta_cache: Option<MetaCache>,
60    block_cache: Option<BlockCache>,
61    hummock_storage: Option<HummockStorage>,
62}
63
64impl MonitorServiceImpl {
65    pub fn new(
66        stream_mgr: LocalStreamManager,
67        batch_mgr: Arc<BatchManager>,
68        server_config: ServerConfig,
69        meta_cache: Option<MetaCache>,
70        block_cache: Option<BlockCache>,
71        hummock_storage: Option<HummockStorage>,
72    ) -> Self {
73        Self {
74            stream_mgr,
75            batch_mgr,
76            profile_service: ProfileServiceImpl::new(server_config),
77            meta_cache,
78            block_cache,
79            hummock_storage,
80        }
81    }
82}
83
84#[async_trait::async_trait]
85impl MonitorService for MonitorServiceImpl {
86    async fn stack_trace(
87        &self,
88        request: Request<StackTraceRequest>,
89    ) -> Result<Response<StackTraceResponse>, Status> {
90        let req = request.into_inner();
91
92        let actor_traces = if let Some(reg) = self.stream_mgr.await_tree_reg() {
93            reg.collect::<Actor>()
94                .into_iter()
95                .map(|(k, v)| {
96                    (
97                        k.0.as_raw_id(),
98                        if req.actor_traces_format == ActorTracesFormat::Text as i32 {
99                            v.to_string()
100                        } else {
101                            serde_json::to_string(&v).unwrap()
102                        },
103                    )
104                })
105                .collect()
106        } else {
107            Default::default()
108        };
109
110        let barrier_traces = if let Some(reg) = self.stream_mgr.await_tree_reg() {
111            reg.collect::<BarrierAwait>()
112                .into_iter()
113                .map(|(k, v)| (k.prev_epoch, v.to_string()))
114                .collect()
115        } else {
116            Default::default()
117        };
118
119        let mut rpc_traces: BTreeMap<_, _> = if let Some(reg) = self.stream_mgr.await_tree_reg() {
120            reg.collect::<GrpcCall>()
121                .into_iter()
122                .map(|(k, v)| (k.desc, v.to_string()))
123                .collect()
124        } else {
125            Default::default()
126        };
127
128        if let Some(reg) = self.batch_mgr.await_tree_reg() {
129            rpc_traces.extend(
130                reg.collect::<GrpcCall>()
131                    .into_iter()
132                    .map(|(k, v)| (k.desc, v.to_string())),
133            );
134        }
135
136        let batch_traces = if let Some(reg) = self.batch_mgr.await_tree_reg() {
137            reg.collect::<BatchTask>()
138                .into_iter()
139                .map(|(k, v)| {
140                    let id = &k.0;
141                    (
142                        format!("{}-{}-{}", id.query_id, id.stage_id, id.task_id),
143                        v.to_string(),
144                    )
145                })
146                .collect()
147        } else {
148            Default::default()
149        };
150
151        let compaction_task_traces = if let Some(hummock) =
152            self.stream_mgr.env.state_store().as_hummock()
153            && let Some(m) = hummock.compaction_await_tree_reg()
154        {
155            m.collect::<Compaction>()
156                .into_iter()
157                .map(|(k, v)| (format!("{k:?}"), v.to_string()))
158                .collect()
159        } else {
160            Default::default()
161        };
162
163        let barrier_worker_state = self.stream_mgr.inspect_barrier_state().await?;
164
165        let jvm_stack_traces = match dump_jvm_stack_traces() {
166            Ok(None) => None,
167            Err(err) => Some(err.as_report().to_string()),
168            Ok(Some(stack_traces)) => Some(stack_traces),
169        };
170
171        Ok(Response::new(StackTraceResponse {
172            actor_traces,
173            rpc_traces,
174            compaction_task_traces,
175            inflight_barrier_traces: barrier_traces,
176            barrier_worker_state: BTreeMap::from_iter([(
177                self.stream_mgr.env.worker_id(),
178                barrier_worker_state,
179            )]),
180            jvm_stack_traces: match jvm_stack_traces {
181                Some(stack_traces) => {
182                    BTreeMap::from_iter([(self.stream_mgr.env.worker_id(), stack_traces)])
183                }
184                None => BTreeMap::new(),
185            },
186            meta_traces: Default::default(),
187            node_errors: Default::default(),
188            batch_traces,
189        }))
190    }
191
192    async fn profiling(
193        &self,
194        request: Request<ProfilingRequest>,
195    ) -> Result<Response<ProfilingResponse>, Status> {
196        self.profile_service.profiling(request).await
197    }
198
199    async fn heap_profiling(
200        &self,
201        request: Request<HeapProfilingRequest>,
202    ) -> Result<Response<HeapProfilingResponse>, Status> {
203        self.profile_service.heap_profiling(request)
204    }
205
206    async fn list_heap_profiling(
207        &self,
208        _request: Request<ListHeapProfilingRequest>,
209    ) -> Result<Response<ListHeapProfilingResponse>, Status> {
210        self.profile_service.list_heap_profiling(_request)
211    }
212
213    async fn analyze_heap(
214        &self,
215        request: Request<AnalyzeHeapRequest>,
216    ) -> Result<Response<AnalyzeHeapResponse>, Status> {
217        self.profile_service.analyze_heap(request).await
218    }
219
220    async fn get_profile_stats(
221        &self,
222        request: Request<GetProfileStatsRequest>,
223    ) -> Result<Response<GetProfileStatsResponse>, Status> {
224        let metrics = global_streaming_metrics(MetricLevel::Info);
225        let inner = request.into_inner();
226        let executor_ids = &inner.executor_ids;
227        let fragment_ids = HashSet::from_iter(inner.dispatcher_fragment_ids);
228        let stream_node_output_row_count = metrics
229            .mem_stream_node_output_row_count
230            .collect(executor_ids);
231        let stream_node_output_blocking_duration_ns = metrics
232            .mem_stream_node_output_blocking_duration_ns
233            .collect(executor_ids);
234
235        // Collect count metrics by fragment_ids
236        fn collect_by_fragment_ids<T: Collector>(
237            m: &T,
238            fragment_ids: &HashSet<FragmentId>,
239        ) -> HashMap<FragmentId, u64> {
240            let mut metrics = HashMap::new();
241            for mut metric_family in m.collect() {
242                for metric in metric_family.take_metric() {
243                    let fragment_id = get_label_infallible(&metric, "fragment_id");
244                    if fragment_ids.contains(&fragment_id) {
245                        let entry = metrics.entry(fragment_id).or_insert(0);
246                        *entry += metric.get_counter().value() as u64;
247                    }
248                }
249            }
250            metrics
251        }
252
253        let dispatch_fragment_output_row_count =
254            collect_by_fragment_ids(&metrics.actor_out_record_cnt, &fragment_ids);
255        let dispatch_fragment_output_blocking_duration_ns = collect_by_fragment_ids(
256            &metrics.actor_output_buffer_blocking_duration_ns,
257            &fragment_ids,
258        );
259        Ok(Response::new(GetProfileStatsResponse {
260            stream_node_output_row_count,
261            stream_node_output_blocking_duration_ns,
262            dispatch_fragment_output_row_count,
263            dispatch_fragment_output_blocking_duration_ns,
264        }))
265    }
266
267    async fn get_streaming_stats(
268        &self,
269        _request: Request<GetStreamingStatsRequest>,
270    ) -> Result<Response<GetStreamingStatsResponse>, Status> {
271        let metrics = global_streaming_metrics(MetricLevel::Info);
272
273        fn collect<T: Collector>(m: &T) -> Vec<Metric> {
274            m.collect().into_iter().next().unwrap().take_metric()
275        }
276
277        let actor_output_buffer_blocking_duration_ns =
278            collect(&metrics.actor_output_buffer_blocking_duration_ns);
279        let actor_count = collect(&metrics.actor_count);
280
281        let actor_count: HashMap<_, _> = actor_count
282            .iter()
283            .map(|m| {
284                let fragment_id: u32 = get_label_infallible(m, "fragment_id");
285                let count = m.get_gauge().value() as u32;
286                (fragment_id, count)
287            })
288            .collect();
289
290        let mut fragment_stats: HashMap<u32, FragmentStats> = HashMap::new();
291        for (&fragment_id, &actor_count) in &actor_count {
292            fragment_stats.insert(
293                fragment_id,
294                FragmentStats {
295                    actor_count,
296                    current_epoch: 0,
297                },
298            );
299        }
300
301        let actor_current_epoch = collect(&metrics.actor_current_epoch);
302        for m in &actor_current_epoch {
303            let fragment_id: u32 = get_label_infallible(m, "fragment_id");
304            let epoch = m.get_gauge().value() as u64;
305            if let Some(s) = fragment_stats.get_mut(&fragment_id) {
306                s.current_epoch = if s.current_epoch == 0 {
307                    epoch
308                } else {
309                    u64::min(s.current_epoch, epoch)
310                }
311            } else {
312                warn!(
313                    fragment_id = fragment_id,
314                    "Miss corresponding actor count metrics"
315                );
316            }
317        }
318
319        let mut relation_stats: HashMap<u32, RelationStats> = HashMap::new();
320        let mview_current_epoch = collect(&metrics.materialize_current_epoch);
321        for m in &mview_current_epoch {
322            let table_id: u32 = get_label_infallible(m, "table_id");
323            let epoch = m.get_gauge().value() as u64;
324            if let Some(s) = relation_stats.get_mut(&table_id) {
325                s.current_epoch = if s.current_epoch == 0 {
326                    epoch
327                } else {
328                    u64::min(s.current_epoch, epoch)
329                };
330                s.actor_count += 1;
331            } else {
332                relation_stats.insert(
333                    table_id,
334                    RelationStats {
335                        actor_count: 1,
336                        current_epoch: epoch,
337                    },
338                );
339            }
340        }
341
342        let mut channel_stats: BTreeMap<String, ChannelStats> = BTreeMap::new();
343
344        for metric in actor_output_buffer_blocking_duration_ns {
345            let fragment_id: u32 = get_label_infallible(&metric, "fragment_id");
346            let downstream_fragment_id: u32 =
347                get_label_infallible(&metric, "downstream_fragment_id");
348
349            let actor_count_to_add =
350                if get_label_infallible::<String>(&metric, "actor_id").is_empty() {
351                    match actor_count.get(&fragment_id) {
352                        Some(&count) => count,
353                        None => {
354                            // Metrics can be momentarily inconsistent (or stale) across families.
355                            // Skip this channel instead of crashing the compute node.
356                            warn!(
357                                fragment_id = fragment_id,
358                                downstream_fragment_id = downstream_fragment_id,
359                                "Miss corresponding actor count metrics"
360                            );
361                            continue;
362                        }
363                    }
364                } else {
365                    1
366                };
367
368            let key = format!("{}_{}", fragment_id, downstream_fragment_id);
369            let channel_stat = channel_stats.entry(key).or_insert_with(|| ChannelStats {
370                actor_count: 0,
371                output_blocking_duration: 0.,
372                recv_row_count: 0,
373                send_row_count: 0,
374            });
375
376            // When metrics level is Debug, `actor_id` will be removed to reduce metrics.
377            // See `src/common/metrics/src/relabeled_metric.rs`
378            channel_stat.actor_count += actor_count_to_add;
379            channel_stat.output_blocking_duration += metric.get_counter().value();
380        }
381
382        let actor_output_row_count = collect(&metrics.actor_out_record_cnt);
383        for metric in actor_output_row_count {
384            let fragment_id: u32 = get_label_infallible(&metric, "fragment_id");
385
386            // Find out and write to all downstream channels
387            let key_prefix = format!("{}_", fragment_id);
388            let key_range_end = format!("{}`", fragment_id); // '`' is next to `_`
389            for (_, s) in channel_stats.range_mut(key_prefix..key_range_end) {
390                s.send_row_count += metric.get_counter().value() as u64;
391            }
392        }
393
394        let actor_input_row_count = collect(&metrics.actor_in_record_cnt);
395        for metric in actor_input_row_count {
396            let upstream_fragment_id: u32 = get_label_infallible(&metric, "upstream_fragment_id");
397            let fragment_id: u32 = get_label_infallible(&metric, "fragment_id");
398
399            let key = format!("{}_{}", upstream_fragment_id, fragment_id);
400            if let Some(s) = channel_stats.get_mut(&key) {
401                s.recv_row_count += metric.get_counter().value() as u64;
402            }
403        }
404
405        let channel_stats = channel_stats.into_iter().collect();
406        Ok(Response::new(GetStreamingStatsResponse {
407            channel_stats,
408            fragment_stats,
409            relation_stats,
410        }))
411    }
412
413    async fn tiered_cache_tracing(
414        &self,
415        request: Request<TieredCacheTracingRequest>,
416    ) -> Result<Response<TieredCacheTracingResponse>, Status> {
417        let req = request.into_inner();
418
419        tracing::info!("Update tiered cache tracing config: {req:?}");
420
421        if let Some(cache) = &self.meta_cache {
422            if req.enable {
423                cache.enable_tracing();
424            } else {
425                cache.disable_tracing();
426            }
427            let mut options = TracingOptions::new();
428            if let Some(threshold) = req.record_hybrid_insert_threshold_ms {
429                options = options
430                    .with_record_hybrid_insert_threshold(Duration::from_millis(threshold as _));
431            }
432            if let Some(threshold) = req.record_hybrid_get_threshold_ms {
433                options =
434                    options.with_record_hybrid_get_threshold(Duration::from_millis(threshold as _));
435            }
436            if let Some(threshold) = req.record_hybrid_remove_threshold_ms {
437                options = options
438                    .with_record_hybrid_remove_threshold(Duration::from_millis(threshold as _));
439            }
440            if let Some(threshold) = req.record_hybrid_fetch_threshold_ms {
441                options = options.with_record_hybrid_get_or_fetch_threshold(Duration::from_millis(
442                    threshold as _,
443                ));
444            }
445            cache.update_tracing_options(options);
446        }
447
448        if let Some(cache) = &self.block_cache {
449            if req.enable {
450                cache.enable_tracing();
451            } else {
452                cache.disable_tracing();
453            }
454            let mut options = TracingOptions::new();
455            if let Some(threshold) = req.record_hybrid_insert_threshold_ms {
456                options = options
457                    .with_record_hybrid_insert_threshold(Duration::from_millis(threshold as _));
458            }
459            if let Some(threshold) = req.record_hybrid_get_threshold_ms {
460                options =
461                    options.with_record_hybrid_get_threshold(Duration::from_millis(threshold as _));
462            }
463            if let Some(threshold) = req.record_hybrid_remove_threshold_ms {
464                options = options
465                    .with_record_hybrid_remove_threshold(Duration::from_millis(threshold as _));
466            }
467            if let Some(threshold) = req.record_hybrid_fetch_threshold_ms {
468                options = options.with_record_hybrid_get_or_fetch_threshold(Duration::from_millis(
469                    threshold as _,
470                ));
471            }
472            cache.update_tracing_options(options);
473        }
474
475        Ok(Response::new(TieredCacheTracingResponse::default()))
476    }
477
478    async fn get_table_cache_refill_stats(
479        &self,
480        _request: Request<GetTableCacheRefillStatsRequest>,
481    ) -> Result<Response<GetTableCacheRefillStatsResponse>, Status> {
482        let Some(hummock_storage) = &self.hummock_storage else {
483            return Ok(Response::new(GetTableCacheRefillStatsResponse {
484                stats: "{}".to_owned(),
485            }));
486        };
487
488        let monitor_snapshot = hummock_storage
489            .table_cache_refill_monitor_snapshot()
490            .await
491            .map_err(|err| {
492                Status::internal(format!(
493                    "failed to get table cache refill monitor snapshot: {e}",
494                    e = err.as_report()
495                ))
496            })?;
497        let stats = TableCacheRefillStats::from(&monitor_snapshot);
498        let json_value = serde_json::to_value(stats).map_err(|err| {
499            Status::internal(format!(
500                "failed to serialize stats: {e}",
501                e = err.as_report()
502            ))
503        })?;
504        let json_string_pretty = serde_json::to_string_pretty(&json_value).map_err(|err| {
505            Status::internal(format!(
506                "failed to serialize stats: {e}",
507                e = err.as_report()
508            ))
509        })?;
510        Ok(Response::new(GetTableCacheRefillStatsResponse {
511            stats: json_string_pretty,
512        }))
513    }
514}
515
516#[derive(Debug, serde::Serialize, serde::Deserialize)]
517struct TableCacheRefillStats {
518    contexts: HashMap<u32, TableCacheRefillTableStats>,
519    streaming: HashMap<u32, Vec<u16>>,
520    serving: HashMap<u32, Vec<u16>>,
521    policies: HashMap<u32, String>,
522    default_policy: String,
523    internal: TableCacheRefillInternalStats,
524}
525
526#[derive(Debug, serde::Serialize, serde::Deserialize)]
527struct TableCacheRefillTableStats {
528    streaming: Option<Vec<u16>>,
529    serving: Option<Vec<u16>>,
530    policy: String,
531}
532
533#[derive(Debug, serde::Serialize, serde::Deserialize)]
534struct TableCacheRefillInternalStats {
535    streaming: HashMap<u32, Vec<Vec<u16>>>,
536    serving: HashMap<u32, Vec<u16>>,
537}
538
539impl From<&TableCacheRefillMonitorSnapshot> for TableCacheRefillStats {
540    fn from(snapshot: &TableCacheRefillMonitorSnapshot) -> Self {
541        let contexts = snapshot
542            .contexts
543            .iter()
544            .map(|(table_id, context)| {
545                (
546                    table_id.as_raw_id(),
547                    TableCacheRefillTableStats::from(context.clone()),
548                )
549            })
550            .collect::<HashMap<_, _>>();
551        let streaming = contexts
552            .iter()
553            .filter_map(|(table_id, context)| {
554                context
555                    .streaming
556                    .as_ref()
557                    .map(|vnodes| (*table_id, vnodes.clone()))
558            })
559            .collect();
560        let serving = contexts
561            .iter()
562            .filter_map(|(table_id, context)| {
563                context
564                    .serving
565                    .as_ref()
566                    .map(|vnodes| (*table_id, vnodes.clone()))
567            })
568            .collect();
569        let policies = snapshot
570            .policies
571            .iter()
572            .map(|(table_id, policy)| (table_id.as_raw_id(), policy.to_string()))
573            .collect();
574        let internal_streaming = snapshot
575            .streaming_table_vnode_mapping
576            .iter()
577            // Keep the historical list-of-lists JSON shape. The refiller stores one
578            // table-level union bitmap per table.
579            .map(|(table_id, bitmap)| (table_id.as_raw_id(), vec![bitmap_to_vnodes(bitmap)]))
580            .collect();
581        let internal_serving = snapshot
582            .serving_table_vnode_mapping
583            .iter()
584            .map(|(table_id, bitmap)| (table_id.as_raw_id(), bitmap_to_vnodes(bitmap)))
585            .collect();
586
587        Self {
588            contexts,
589            streaming,
590            serving,
591            policies,
592            default_policy: snapshot.default_policy.to_string(),
593            internal: TableCacheRefillInternalStats {
594                streaming: internal_streaming,
595                serving: internal_serving,
596            },
597        }
598    }
599}
600
601impl From<TableCacheRefillContext> for TableCacheRefillTableStats {
602    fn from(context: TableCacheRefillContext) -> Self {
603        Self {
604            streaming: context
605                .streaming_vnode_bitmap
606                .as_ref()
607                .map(bitmap_to_vnodes),
608            serving: context.serving_vnode_bitmap.as_ref().map(bitmap_to_vnodes),
609            policy: context.policy.to_string(),
610        }
611    }
612}
613
614fn bitmap_to_vnodes(bitmap: &Bitmap) -> Vec<u16> {
615    bitmap.iter_ones().map(|idx| idx as u16).collect()
616}
617
618pub use grpc_middleware::*;
619use risingwave_common::metrics::get_label_infallible;
620use risingwave_pb::id::FragmentId;
621
622pub mod grpc_middleware {
623    pub use risingwave_common_service::{
624        AwaitTreeMiddleware, AwaitTreeMiddlewareLayer, AwaitTreeRegistryRef, GrpcCall,
625    };
626}