1use std::time::Duration;
19
20use jemalloc_pprof::JemallocProfCtl;
21use mz_ore::cast::CastFrom;
22use mz_ore::metric;
23use mz_ore::metrics::{MetricsRegistry, UIntGauge};
24use pprof_util::ProfStartTime;
25use tikv_jemalloc_ctl::{epoch, raw, stats};
26
27#[allow(non_upper_case_globals)]
28#[unsafe(export_name = "malloc_conf")]
29pub static malloc_conf: &[u8] = b"prof:true,prof_active:true,lg_prof_sample:19\0";
30
31#[derive(Copy, Clone, Debug)]
32pub struct JemallocProfMetadata {
33 pub start_time: Option<ProfStartTime>,
34}
35
36pub struct JemallocStats {
38 pub active: usize,
39 pub allocated: usize,
40 pub metadata: usize,
41 pub resident: usize,
42 pub retained: usize,
43}
44
45pub trait JemallocProfCtlExt {
46 fn dump_stats(&mut self, json_format: bool) -> anyhow::Result<String>;
47 fn stats(&self) -> anyhow::Result<JemallocStats>;
48
49 fn pause(&self) -> anyhow::Result<()>;
62
63 fn resume(&self) -> anyhow::Result<()>;
68}
69
70impl JemallocProfCtlExt for JemallocProfCtl {
71 fn dump_stats(&mut self, json_format: bool) -> anyhow::Result<String> {
72 let mut buf = Vec::with_capacity(1 << 22);
74 let mut options = tikv_jemalloc_ctl::stats_print::Options::default();
75 options.json_format = json_format;
76 tikv_jemalloc_ctl::stats_print::stats_print(&mut buf, options)?;
77 Ok(String::from_utf8(buf)?)
78 }
79
80 fn stats(&self) -> anyhow::Result<JemallocStats> {
81 JemallocStats::get()
82 }
83
84 fn pause(&self) -> anyhow::Result<()> {
85 unsafe { raw::write(b"prof.active\0", false) }?;
88 Ok(())
89 }
90
91 fn resume(&self) -> anyhow::Result<()> {
92 unsafe { raw::write(b"prof.active\0", true) }?;
95 Ok(())
96 }
97}
98
99impl JemallocStats {
100 pub fn get() -> anyhow::Result<JemallocStats> {
101 epoch::advance()?;
102 Ok(JemallocStats {
103 active: stats::active::read()?,
104 allocated: stats::allocated::read()?,
105 metadata: stats::metadata::read()?,
106 resident: stats::resident::read()?,
107 retained: stats::retained::read()?,
108 })
109 }
110}
111
112pub struct JemallocMetrics {
114 pub active: UIntGauge,
115 pub allocated: UIntGauge,
116 pub metadata: UIntGauge,
117 pub resident: UIntGauge,
118 pub retained: UIntGauge,
119}
120
121impl JemallocMetrics {
122 #[allow(clippy::unused_async)]
126 pub async fn register_into(registry: &MetricsRegistry) {
127 let m = JemallocMetrics {
128 active: registry.register(metric!(
129 name: "jemalloc_active",
130 help: "Total number of bytes in active pages allocated by the application",
131 )),
132 allocated: registry.register(metric!(
133 name: "jemalloc_allocated",
134 help: "Total number of bytes allocated by the application",
135 )),
136 metadata: registry.register(metric!(
137 name: "jemalloc_metadata",
138 help: "Total number of bytes dedicated to metadata.",
139 )),
140 resident: registry.register(metric!(
141 name: "jemalloc_resident",
142 help: "Maximum number of bytes in physically resident data pages mapped",
143 )),
144 retained: registry.register(metric!(
145 name: "jemalloc_retained",
146 help: "Total number of bytes in virtual memory mappings",
147 )),
148 };
149
150 mz_ore::task::spawn(|| "jemalloc_stats_update", async move {
151 let mut interval = tokio::time::interval(Duration::from_secs(10));
152 interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
153 loop {
154 interval.tick().await;
155 if let Err(e) = m.update() {
156 tracing::warn!("Error while updating jemalloc stats: {}", e);
157 }
158 }
159 });
160 }
161
162 fn update(&self) -> anyhow::Result<()> {
163 let s = JemallocStats::get()?;
164 self.active.set(u64::cast_from(s.active));
165 self.allocated.set(u64::cast_from(s.allocated));
166 self.metadata.set(u64::cast_from(s.metadata));
167 self.resident.set(u64::cast_from(s.resident));
168 self.retained.set(u64::cast_from(s.retained));
169 Ok(())
170 }
171}