1use std::collections::{HashMap, HashSet};
16use std::str::FromStr;
17
18use api::v1::meta::{DatanodeWorkloads, HeartbeatRequest, RequestHeader};
19use common_time::util as time_util;
20use lazy_static::lazy_static;
21use regex::Regex;
22use serde::{Deserialize, Serialize};
23use snafu::{OptionExt, ResultExt, ensure};
24use store_api::region_engine::{RegionRole, RegionStatistic};
25use store_api::storage::RegionId;
26use table::metadata::TableId;
27
28use crate::error::{self, DeserializeFromJsonSnafu, Result};
29use crate::heartbeat::utils::get_datanode_workloads;
30
31const DATANODE_STAT_PREFIX: &str = "__meta_datanode_stat";
32
33pub const REGION_STATISTIC_KEY: &str = "__region_statistic";
34
35lazy_static! {
36 pub(crate) static ref DATANODE_LEASE_KEY_PATTERN: Regex =
37 Regex::new("^__meta_datanode_lease-([0-9]+)-([0-9]+)$").unwrap();
38 static ref DATANODE_STAT_KEY_PATTERN: Regex =
39 Regex::new(&format!("^{DATANODE_STAT_PREFIX}-([0-9]+)-([0-9]+)$")).unwrap();
40 static ref INACTIVE_REGION_KEY_PATTERN: Regex =
41 Regex::new("^__meta_inactive_region-([0-9]+)-([0-9]+)-([0-9]+)$").unwrap();
42}
43
44#[derive(Debug, Clone, Default, Serialize, Deserialize)]
48pub struct Stat {
49 pub timestamp_millis: i64,
50 pub id: u64,
52 pub addr: String,
54 pub rcus: i64,
56 pub wcus: i64,
58 pub region_num: u64,
60 pub region_stats: Vec<RegionStat>,
62 pub topic_stats: Vec<TopicStat>,
64 pub node_epoch: u64,
66 pub datanode_workloads: DatanodeWorkloads,
68 pub gc_stat: Option<GcStat>,
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
74pub struct RegionStat {
75 pub id: RegionId,
77 pub rcus: i64,
79 pub wcus: i64,
81 pub approximate_bytes: u64,
83 pub engine: String,
85 pub role: RegionRole,
87 pub num_rows: u64,
89 pub memtable_size: u64,
91 pub manifest_size: u64,
93 pub sst_size: u64,
95 pub sst_num: u64,
97 pub index_size: u64,
99 pub region_manifest: RegionManifestInfo,
101 pub written_bytes: u64,
103 #[serde(default)]
107 pub query_cpu_time: u64,
108 #[serde(default)]
110 pub query_scanned_bytes: u64,
111 pub data_topic_latest_entry_id: u64,
114 pub metadata_topic_latest_entry_id: u64,
118}
119
120#[derive(Debug, Clone, Serialize, Deserialize)]
121pub struct TopicStat {
122 pub topic: String,
124 pub latest_entry_id: u64,
126 pub record_size: u64,
128 pub record_num: u64,
130}
131
132pub trait TopicStatsReporter: Send + Sync {
134 fn reportable_topics(&mut self) -> Vec<TopicStat>;
136}
137
138#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
139pub enum RegionManifestInfo {
140 Mito {
141 manifest_version: u64,
142 flushed_entry_id: u64,
143 file_removed_cnt: u64,
145 },
146 Metric {
147 data_manifest_version: u64,
148 data_flushed_entry_id: u64,
149 metadata_manifest_version: u64,
150 metadata_flushed_entry_id: u64,
151 },
152}
153
154impl Stat {
155 #[inline]
156 pub fn is_empty(&self) -> bool {
157 self.region_stats.is_empty()
158 }
159
160 pub fn stat_key(&self) -> DatanodeStatKey {
161 DatanodeStatKey { node_id: self.id }
162 }
163
164 pub fn regions(&self) -> Vec<(RegionId, RegionRole)> {
166 self.region_stats.iter().map(|s| (s.id, s.role)).collect()
167 }
168
169 pub fn table_ids(&self) -> HashSet<TableId> {
171 self.region_stats.iter().map(|s| s.id.table_id()).collect()
172 }
173
174 pub fn retain_active_region_stats(&mut self, inactive_region_ids: &HashSet<RegionId>) {
176 if inactive_region_ids.is_empty() {
177 return;
178 }
179
180 self.region_stats
181 .retain(|r| !inactive_region_ids.contains(&r.id));
182 self.rcus = self.region_stats.iter().map(|s| s.rcus).sum();
183 self.wcus = self.region_stats.iter().map(|s| s.wcus).sum();
184 self.region_num = self.region_stats.len() as u64;
185 }
186
187 pub fn memory_size(&self) -> usize {
188 std::mem::size_of::<i64>() * 3 +
190 std::mem::size_of::<u64>() * 3 +
192 std::mem::size_of::<String>() + self.addr.capacity() +
194 self.region_stats.iter().map(|s| s.memory_size()).sum::<usize>()
196 }
197}
198
199impl RegionStat {
200 pub fn memory_size(&self) -> usize {
201 std::mem::size_of::<RegionRole>() +
203 std::mem::size_of::<RegionId>() +
205 std::mem::size_of::<i64>() * 4 +
207 std::mem::size_of::<u64>() * 5 +
209 std::mem::size_of::<String>() + self.engine.capacity() +
211 self.region_manifest.memory_size()
213 }
214}
215
216impl RegionManifestInfo {
217 pub fn memory_size(&self) -> usize {
218 match self {
219 RegionManifestInfo::Mito { .. } => std::mem::size_of::<u64>() * 2,
220 RegionManifestInfo::Metric { .. } => std::mem::size_of::<u64>() * 4,
221 }
222 }
223}
224
225impl TryFrom<&HeartbeatRequest> for Stat {
226 type Error = Option<RequestHeader>;
227
228 fn try_from(value: &HeartbeatRequest) -> std::result::Result<Self, Self::Error> {
229 let HeartbeatRequest {
230 header,
231 peer,
232 region_stats,
233 node_epoch,
234 node_workloads,
235 topic_stats,
236 extensions,
237 ..
238 } = value;
239
240 match (header, peer) {
241 (Some(header), Some(peer)) => {
242 let region_stats = region_stats
243 .iter()
244 .map(RegionStat::from)
245 .collect::<Vec<_>>();
246 let topic_stats = topic_stats.iter().map(TopicStat::from).collect::<Vec<_>>();
247
248 let datanode_workloads = get_datanode_workloads(node_workloads.as_ref());
249
250 let gc_stat = GcStat::from_extensions(extensions).map_err(|err| {
251 common_telemetry::error!(
252 "Failed to deserialize GcStat from extensions: {}",
253 err
254 );
255 header.clone()
256 })?;
257 Ok(Self {
258 timestamp_millis: time_util::current_time_millis(),
259 id: peer.id,
261 addr: peer.addr.clone(),
263 rcus: region_stats.iter().map(|s| s.rcus).sum(),
264 wcus: region_stats.iter().map(|s| s.wcus).sum(),
265 region_num: region_stats.len() as u64,
266 region_stats,
267 topic_stats,
268 node_epoch: *node_epoch,
269 datanode_workloads,
270 gc_stat,
271 })
272 }
273 (header, _) => Err(header.clone()),
274 }
275 }
276}
277
278impl From<store_api::region_engine::RegionManifestInfo> for RegionManifestInfo {
279 fn from(value: store_api::region_engine::RegionManifestInfo) -> Self {
280 match value {
281 store_api::region_engine::RegionManifestInfo::Mito {
282 manifest_version,
283 flushed_entry_id,
284 file_removed_cnt,
285 } => RegionManifestInfo::Mito {
286 manifest_version,
287 flushed_entry_id,
288 file_removed_cnt,
289 },
290 store_api::region_engine::RegionManifestInfo::Metric {
291 data_manifest_version,
292 data_flushed_entry_id,
293 metadata_manifest_version,
294 metadata_flushed_entry_id,
295 } => RegionManifestInfo::Metric {
296 data_manifest_version,
297 data_flushed_entry_id,
298 metadata_manifest_version,
299 metadata_flushed_entry_id,
300 },
301 }
302 }
303}
304
305impl From<&api::v1::meta::RegionStat> for RegionStat {
306 fn from(value: &api::v1::meta::RegionStat) -> Self {
307 let region_stat = value
308 .extensions
309 .get(REGION_STATISTIC_KEY)
310 .and_then(|value| RegionStatistic::deserialize_from_slice(value))
311 .unwrap_or_default();
312
313 Self {
314 id: RegionId::from_u64(value.region_id),
315 rcus: value.rcus,
316 wcus: value.wcus,
317 approximate_bytes: value.approximate_bytes as u64,
318 engine: value.engine.clone(),
319 role: RegionRole::from(value.role()),
320 num_rows: region_stat.num_rows,
321 memtable_size: region_stat.memtable_size,
322 manifest_size: region_stat.manifest_size,
323 sst_size: region_stat.sst_size,
324 sst_num: region_stat.sst_num,
325 index_size: region_stat.index_size,
326 region_manifest: region_stat.manifest.into(),
327 written_bytes: region_stat.written_bytes,
328 query_cpu_time: region_stat.query_cpu_time,
329 query_scanned_bytes: region_stat.query_scanned_bytes,
330 data_topic_latest_entry_id: region_stat.data_topic_latest_entry_id,
331 metadata_topic_latest_entry_id: region_stat.metadata_topic_latest_entry_id,
332 }
333 }
334}
335
336impl From<&api::v1::meta::TopicStat> for TopicStat {
337 fn from(value: &api::v1::meta::TopicStat) -> Self {
338 Self {
339 topic: value.topic_name.clone(),
340 latest_entry_id: value.latest_entry_id,
341 record_size: value.record_size,
342 record_num: value.record_num,
343 }
344 }
345}
346
347#[derive(Debug, Clone, Serialize, Deserialize, Default)]
348pub struct GcStat {
349 pub running_gc_tasks: u32,
351 pub gc_concurrency: u32,
353}
354
355impl GcStat {
356 pub const GC_STAT_KEY: &str = "__gc_stat";
357
358 pub fn new(running_gc_tasks: u32, gc_concurrency: u32) -> Self {
359 Self {
360 running_gc_tasks,
361 gc_concurrency,
362 }
363 }
364
365 pub fn into_extensions(&self, extensions: &mut std::collections::HashMap<String, Vec<u8>>) {
366 let bytes = serde_json::to_vec(self).unwrap_or_default();
367 extensions.insert(Self::GC_STAT_KEY.to_string(), bytes);
368 }
369
370 pub fn from_extensions(
371 extensions: &std::collections::HashMap<String, Vec<u8>>,
372 ) -> Result<Option<Self>> {
373 extensions
374 .get(Self::GC_STAT_KEY)
375 .map(|bytes| {
376 serde_json::from_slice(bytes).with_context(|_| DeserializeFromJsonSnafu {
377 input: String::from_utf8_lossy(bytes).to_string(),
378 })
379 })
380 .transpose()
381 }
382}
383
384#[derive(Debug, Clone, Serialize, Deserialize, Default)]
386pub struct EnvVars {
387 pub vars: HashMap<String, String>,
388}
389
390impl EnvVars {
391 pub const ENV_VARS_KEY: &str = "__env_vars";
392
393 pub fn new(vars: HashMap<String, String>) -> Self {
394 Self { vars }
395 }
396
397 pub fn from_config(keys: &[String]) -> Self {
399 let vars = keys
400 .iter()
401 .filter_map(|key| std::env::var(key).ok().map(|value| (key.clone(), value)))
402 .collect();
403 Self { vars }
404 }
405
406 pub fn into_extensions(&self, extensions: &mut HashMap<String, Vec<u8>>) {
407 if self.vars.is_empty() {
408 return;
409 }
410 let bytes = serde_json::to_vec(self).unwrap_or_default();
411 extensions.insert(Self::ENV_VARS_KEY.to_string(), bytes);
412 }
413
414 pub fn from_extensions(extensions: &HashMap<String, Vec<u8>>) -> Result<Option<Self>> {
415 extensions
416 .get(Self::ENV_VARS_KEY)
417 .map(|bytes| {
418 serde_json::from_slice(bytes).with_context(|_| DeserializeFromJsonSnafu {
419 input: String::from_utf8_lossy(bytes).to_string(),
420 })
421 })
422 .transpose()
423 }
424}
425
426#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
430pub struct DatanodeStatKey {
431 pub node_id: u64,
432}
433
434impl DatanodeStatKey {
435 pub fn prefix_key() -> Vec<u8> {
437 format!("{DATANODE_STAT_PREFIX}-0-").into_bytes()
439 }
440}
441
442impl From<DatanodeStatKey> for Vec<u8> {
443 fn from(value: DatanodeStatKey) -> Self {
444 format!("{}-0-{}", DATANODE_STAT_PREFIX, value.node_id).into_bytes()
446 }
447}
448
449impl FromStr for DatanodeStatKey {
450 type Err = error::Error;
451
452 fn from_str(key: &str) -> Result<Self> {
453 let caps = DATANODE_STAT_KEY_PATTERN
454 .captures(key)
455 .context(error::InvalidStatKeySnafu { key })?;
456
457 ensure!(caps.len() == 3, error::InvalidStatKeySnafu { key });
458 let node_id = caps[2].to_string();
459 let node_id: u64 = node_id.parse().context(error::ParseNumSnafu {
460 err_msg: format!("invalid node_id: {node_id}"),
461 })?;
462
463 Ok(Self { node_id })
464 }
465}
466
467impl TryFrom<Vec<u8>> for DatanodeStatKey {
468 type Error = error::Error;
469
470 fn try_from(bytes: Vec<u8>) -> Result<Self> {
471 String::from_utf8(bytes)
472 .context(error::FromUtf8Snafu {
473 name: "DatanodeStatKey",
474 })
475 .map(|x| x.parse())?
476 }
477}
478
479#[derive(Debug, Clone, Serialize, Deserialize)]
481#[serde(transparent)]
482pub struct DatanodeStatValue {
483 pub stats: Vec<Stat>,
484}
485
486impl DatanodeStatValue {
487 pub fn region_num(&self) -> Option<u64> {
489 self.stats.last().map(|x| x.region_num)
490 }
491
492 pub fn node_addr(&self) -> Option<String> {
494 self.stats.last().map(|x| x.addr.clone())
495 }
496}
497
498impl TryFrom<DatanodeStatValue> for Vec<u8> {
499 type Error = error::Error;
500
501 fn try_from(stats: DatanodeStatValue) -> Result<Self> {
502 Ok(serde_json::to_string(&stats)
503 .context(error::SerializeToJsonSnafu {
504 input: format!("{stats:?}"),
505 })?
506 .into_bytes())
507 }
508}
509
510impl FromStr for DatanodeStatValue {
511 type Err = error::Error;
512
513 fn from_str(value: &str) -> Result<Self> {
514 serde_json::from_str(value).context(error::DeserializeFromJsonSnafu { input: value })
515 }
516}
517
518impl TryFrom<Vec<u8>> for DatanodeStatValue {
519 type Error = error::Error;
520
521 fn try_from(value: Vec<u8>) -> Result<Self> {
522 String::from_utf8(value)
523 .context(error::FromUtf8Snafu {
524 name: "DatanodeStatValue",
525 })
526 .map(|x| x.parse())?
527 }
528}
529
530#[cfg(test)]
531mod tests {
532 use super::*;
533
534 #[test]
535 fn test_stat_key() {
536 let stat = Stat {
537 id: 101,
538 region_num: 10,
539 ..Default::default()
540 };
541
542 let stat_key = stat.stat_key();
543
544 assert_eq!(101, stat_key.node_id);
545 }
546
547 #[test]
548 fn test_stat_val_round_trip() {
549 let stat = Stat {
550 id: 101,
551 region_num: 100,
552 ..Default::default()
553 };
554
555 let stat_val = DatanodeStatValue { stats: vec![stat] };
556
557 let bytes: Vec<u8> = stat_val.try_into().unwrap();
558 let stat_val: DatanodeStatValue = bytes.try_into().unwrap();
559 let stats = stat_val.stats;
560
561 assert_eq!(1, stats.len());
562
563 let stat = stats.first().unwrap();
564 assert_eq!(101, stat.id);
565 assert_eq!(100, stat.region_num);
566 }
567
568 #[test]
569 fn test_stat_val_deserializes_without_query_stats() {
570 let stat = Stat {
571 region_stats: vec![RegionStat {
572 id: RegionId::new(1024, 1),
573 rcus: 0,
574 wcus: 0,
575 approximate_bytes: 0,
576 engine: "mito".to_string(),
577 role: RegionRole::Leader,
578 num_rows: 0,
579 memtable_size: 0,
580 manifest_size: 0,
581 sst_size: 0,
582 sst_num: 0,
583 index_size: 0,
584 region_manifest: RegionManifestInfo::Mito {
585 manifest_version: 0,
586 flushed_entry_id: 0,
587 file_removed_cnt: 0,
588 },
589 written_bytes: 0,
590 query_cpu_time: 10,
591 query_scanned_bytes: 20,
592 data_topic_latest_entry_id: 0,
593 metadata_topic_latest_entry_id: 0,
594 }],
595 ..Default::default()
596 };
597 let stat_val = DatanodeStatValue { stats: vec![stat] };
598 let mut value = serde_json::to_value(stat_val).unwrap();
599 let region_stat = value[0]["region_stats"][0].as_object_mut().unwrap();
600 region_stat.remove("query_cpu_time");
601 region_stat.remove("query_scanned_bytes");
602
603 let stat_val: DatanodeStatValue = serde_json::from_value(value).unwrap();
604 let region_stat = &stat_val.stats[0].region_stats[0];
605
606 assert_eq!(region_stat.query_cpu_time, 0);
607 assert_eq!(region_stat.query_scanned_bytes, 0);
608 }
609
610 #[test]
611 fn test_get_addr_from_stat_val() {
612 let empty = DatanodeStatValue { stats: vec![] };
613 let addr = empty.node_addr();
614 assert!(addr.is_none());
615
616 let stat_val = DatanodeStatValue {
617 stats: vec![
618 Stat {
619 addr: "1".to_string(),
620 ..Default::default()
621 },
622 Stat {
623 addr: "2".to_string(),
624 ..Default::default()
625 },
626 Stat {
627 addr: "3".to_string(),
628 ..Default::default()
629 },
630 ],
631 };
632 let addr = stat_val.node_addr().unwrap();
633 assert_eq!("3", addr);
634 }
635
636 #[test]
637 fn test_get_region_num_from_stat_val() {
638 let empty = DatanodeStatValue { stats: vec![] };
639 let region_num = empty.region_num();
640 assert!(region_num.is_none());
641
642 let wrong = DatanodeStatValue {
643 stats: vec![Stat {
644 region_num: 0,
645 ..Default::default()
646 }],
647 };
648 let right = wrong.region_num();
649 assert_eq!(Some(0), right);
650
651 let stat_val = DatanodeStatValue {
652 stats: vec![
653 Stat {
654 region_num: 1,
655 ..Default::default()
656 },
657 Stat {
658 region_num: 0,
659 ..Default::default()
660 },
661 Stat {
662 region_num: 2,
663 ..Default::default()
664 },
665 ],
666 };
667 let region_num = stat_val.region_num().unwrap();
668 assert_eq!(2, region_num);
669 }
670
671 #[test]
672 fn test_region_stat_from_heartbeat_preserves_staging_leader_role() {
673 let request = HeartbeatRequest {
674 header: Some(RequestHeader::default()),
675 peer: Some(api::v1::meta::Peer {
676 id: 1,
677 addr: "127.0.0.1:3001".to_string(),
678 }),
679 region_stats: vec![api::v1::meta::RegionStat {
680 region_id: RegionId::new(1024, 1).as_u64(),
681 engine: "mito".to_string(),
682 role: api::v1::meta::RegionRole::StagingLeader.into(),
683 ..Default::default()
684 }],
685 ..Default::default()
686 };
687
688 let stat = Stat::try_from(&request).unwrap();
689
690 assert_eq!(stat.region_stats.len(), 1);
691 assert_eq!(stat.region_stats[0].role, RegionRole::StagingLeader);
692 }
693
694 #[test]
695 fn test_env_vars_round_trip() {
696 let mut vars = HashMap::new();
697 vars.insert("AZ".to_string(), "us-east-1a".to_string());
698 vars.insert("REGION".to_string(), "us-east-1".to_string());
699 let env_vars = EnvVars::new(vars);
700
701 let mut extensions = HashMap::new();
702 env_vars.into_extensions(&mut extensions);
703
704 let extracted = EnvVars::from_extensions(&extensions).unwrap().unwrap();
705 assert_eq!(extracted.vars.get("AZ").unwrap(), "us-east-1a");
706 assert_eq!(extracted.vars.get("REGION").unwrap(), "us-east-1");
707 }
708
709 #[test]
710 fn test_env_vars_empty_not_written() {
711 let env_vars = EnvVars::default();
712 let mut extensions = HashMap::new();
713 env_vars.into_extensions(&mut extensions);
714 assert!(extensions.is_empty());
715 }
716
717 #[test]
718 fn test_env_vars_from_extensions_missing() {
719 let extensions = HashMap::new();
720 let result = EnvVars::from_extensions(&extensions).unwrap();
721 assert!(result.is_none());
722 }
723}