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