├── Cargo.toml ├── README.rst └── mod_prometheus.rs /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mod_prometheus" 3 | version = "0.1.0" 4 | authors = ["Moises Silva "] 5 | 6 | [dependencies] 7 | lazy_static = "0.1.*" 8 | libc = "*" 9 | prometheus = { git = "https://github.com/moises-silva/prometheus-rs" } 10 | freeswitchrs = { git = "https://github.com/moises-silva/freeswitchrs" } 11 | 12 | #[dependencies.freeswitchrs] 13 | #path = ".." 14 | 15 | #[dependencies.prometheus] 16 | #path = "/home/moy/src/rust/prometheus" 17 | 18 | [lib] 19 | crate-type = ["dylib"] 20 | name = "mod_prometheus" 21 | path = "mod_prometheus.rs" 22 | 23 | [profile.dev] 24 | panic = "abort" 25 | 26 | [profile.release] 27 | panic = "abort" 28 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | FreeSWITCH Prometheus Module 2 | ---------------------------- 3 | 4 | **WARNING**: This module has serious bugs and does not produce reliable metrics at the moment. While I hope I can find the time to fix it soon, I'd rather people not waste their 5 | time trying to install and use it when the results won't be usable in a production environment. 6 | 7 | This module exposes FreeSWITCH metrics for scraping by 8 | `Prometheus 9 | `_. 10 | 11 | mod_prometheus is built upon 12 | `FreeSWITCH Rust bindings 13 | `_. 14 | 15 | 16 | Install 17 | ======= 18 | 19 | Installation instructions:: 20 | 21 | # Check for and install the Rust compiler 22 | $ rustc -V || curl -sSf https://static.rust-lang.org/rustup.sh | sh 23 | 24 | # Clone the project and build it: 25 | $ git clone https://github.com/moises-silva/mod_prometheus.git 26 | $ cd mod_prometheus 27 | $ cargo build 28 | 29 | # Copy the module to your FreeSWITCH modules directory: 30 | $ sudo cp target/debug/libmod_prometheus.so `fs_cli -x 'global_getvar mod_dir'`/mod_prometheus.so 31 | 32 | # Load the module: 33 | $ fs_cli -x 'load mod_prometheus' 34 | 35 | # Make sure it's loaded and listening to TCP port 9282 36 | $ fs_cli -x 'module_exists mod_prometheus' 37 | true 38 | 39 | $ netstat -nl | grep 9282 40 | tcp 0 0 0.0.0.0:9282 0.0.0.0:* LISTEN 41 | 42 | # For auto-load the module add this line at the end of your modules.conf 43 | $ sudo vi /etc/freeswitch/autoload_configs/modules.conf.xml 44 | 45 | 46 | 47 | Now you can access your host at port 9282 to check your metrics:: 48 | 49 | $ curl http://127.0.0.1:9282/metrics 50 | 51 | The /metrics url path is not required but it could be required in the future as it's recommended by the Prometheus guidelines. 52 | 53 | Metrics 54 | ======= 55 | 56 | These are the metrics provided by default:: 57 | 58 | Counters:: 59 | 60 | freeswitch_heartbeats_total 61 | freeswitch_registration_attempts_total 62 | freeswitch_registration_failures_total 63 | freeswitch_registrations_total 64 | freeswitch_sessions_total 65 | freeswitch_sessions_answered_total 66 | freeswitch_sessions_failed_total 67 | freeswitch_sessions_inbound_total 68 | freeswitch_sessions_inbound_answered_total 69 | freeswitch_sessions_inbound_failed_total 70 | freeswitch_sessions_outbound_total 71 | freeswitch_sessions_outbound_answered_total 72 | freeswitch_sessions_outbound_failed_total 73 | 74 | Gauges:: 75 | 76 | freeswitch_sessions_active 77 | freeswitch_sessions_asr 78 | freeswitch_registrations_active 79 | 80 | You can also use FreeSWITCH ESL APIs to create your own counters or gauges:: 81 | 82 | fscli> prom_counter_increment my_counter 83 | 84 | fscli> prom_counter_increment my_counter 100 85 | 86 | fscli> prom_gauge_set my_gauge 500 87 | 88 | fscli> prom_gauge_increment my_gauge 89 | fscli> prom_gauge_decrement my_gauge 2 90 | 91 | As all FreeSWITCH APIs, these functions can be used from the XML dialplan or the command line. 92 | -------------------------------------------------------------------------------- /mod_prometheus.rs: -------------------------------------------------------------------------------- 1 | // TODO: 2 | // - Macro to bind event and store id? Ideally we should have the FS core remember the 3 | // events this module registered and the core can then unregister them, the same way 4 | // it works for module applications and APIs 5 | // - Refactor code to avoid using so many static globals and hide the ugliness 6 | // of Arc>> 7 | // - Make bindaddr configurable 8 | // - Initialize counters/gauges to current values on module load 9 | // using switch_core_session_count(), switch_core_session_ctl() etc 10 | // - Allow configuring metrics that can be later references the dialplan 11 | // - Add dimensions to metrics (e.g inbound per profile) 12 | // - Add error metrics (based on log errors/warnings) 13 | // - Add dialplan app, so if a gauge increased is associated with a session 14 | // it can be auto-decremented when the session is destroyed 15 | // - Add label support 16 | #[macro_use] 17 | extern crate lazy_static; 18 | 19 | #[macro_use] 20 | extern crate freeswitchrs; 21 | extern crate prometheus; 22 | extern crate libc; 23 | 24 | use std::sync::{Arc, Mutex}; 25 | use std::borrow::Cow; 26 | use std::collections::HashMap; 27 | use std::ops::Index; 28 | 29 | use freeswitchrs::raw as fsr; 30 | use freeswitchrs::mods::*; // This will get replaced with a mods prelude 31 | use freeswitchrs::Status; 32 | use freeswitchrs::raw::log_level::{DEBUG, INFO, WARNING, ERROR}; 33 | 34 | use prometheus::{Registry, Counter, Gauge}; 35 | 36 | // Ugh, note that these counter/gauge index values must map to the index 37 | // in the COUNTERS/GAUGES globals. There is probably a less error-prone way 38 | // to do this, but as of today it seems one can't iterate over enums in rust 39 | enum FSCounter { 40 | Heartbeats = 0, 41 | Sessions, 42 | SessionsAnswered, 43 | SessionsFailed, 44 | SessionsInbound, 45 | SessionsInboundAnswered, 46 | SessionsInboundFailed, 47 | SessionsOutbound, 48 | SessionsOutboundAnswered, 49 | SessionsOutboundFailed, 50 | Registrations, 51 | RegistrationAttempts, 52 | RegistrationFailures 53 | } 54 | 55 | enum FSGauge { 56 | SessionsActive, 57 | SessionsASR, 58 | RegistrationsActive 59 | } 60 | 61 | static mut REGPTR: *mut Arc> = 0 as *mut Arc>; 62 | 63 | lazy_static! { 64 | static ref USER_COUNTERS: Mutex>>> = { 65 | Mutex::new(HashMap::new()) 66 | }; 67 | static ref USER_GAUGES: Mutex>>> = { 68 | Mutex::new(HashMap::new()) 69 | }; 70 | static ref COUNTERS: [Arc>;13] = {[ 71 | Arc::new(Mutex::new(Counter::new("freeswitch_heartbeats_total".to_string(), 72 | "FreeSWITCH heartbeat count".to_string()))), 73 | 74 | Arc::new(Mutex::new(prometheus::Counter::new("freeswitch_sessions_total".to_string(), 75 | "FreeSWITCH Session Count".to_string()))), 76 | 77 | Arc::new(Mutex::new(prometheus::Counter::new("freeswitch_sessions_answered_total".to_string(), 78 | "FreeSWITCH Answered Sessions Count".to_string()))), 79 | 80 | Arc::new(Mutex::new(prometheus::Counter::new("freeswitch_sessions_failed_total".to_string(), 81 | "FreeSWITCH Failed Sessions Count".to_string()))), 82 | 83 | Arc::new(Mutex::new(prometheus::Counter::new("freeswitch_sessions_inbound_total".to_string(), 84 | "FreeSWITCH Inbound Sessions Count".to_string()))), 85 | 86 | Arc::new(Mutex::new(prometheus::Counter::new("freeswitch_sessions_inbound_answered_total".to_string(), 87 | "FreeSWITCH Answered Inbound Sessions Count".to_string()))), 88 | 89 | Arc::new(Mutex::new(prometheus::Counter::new("freeswitch_sessions_inbound_failed_total".to_string(), 90 | "FreeSWITCH Failed Inbound Sessions Count".to_string()))), 91 | 92 | Arc::new(Mutex::new(prometheus::Counter::new("freeswitch_sessions_outbound_total".to_string(), 93 | "FreeSWITCH Outbound Sessions Count".to_string()))), 94 | 95 | Arc::new(Mutex::new(prometheus::Counter::new("freeswitch_sessions_outbound_answered_total".to_string(), 96 | "FreeSWITCH Answered Outbound Sessions Count".to_string()))), 97 | 98 | Arc::new(Mutex::new(prometheus::Counter::new("freeswitch_sessions_outbound_failed_total".to_string(), 99 | "FreeSWITCH Failed Outbound Sessions Count".to_string()))), 100 | 101 | // Registration Metrics 102 | Arc::new(Mutex::new(prometheus::Counter::new("freeswitch_registrations_total".to_string(), 103 | "FreeSWITCH Registration Count".to_string()))), 104 | 105 | Arc::new(Mutex::new(prometheus::Counter::new("freeswitch_registration_attempts_total".to_string(), 106 | "FreeSWITCH Registration Attempts".to_string()))), 107 | 108 | Arc::new(Mutex::new(prometheus::Counter::new("freeswitch_registration_failures_total".to_string(), 109 | "FreeSWITCH Registration Failures".to_string()))) 110 | ]}; 111 | static ref GAUGES: [Arc>;3] = {[ 112 | Arc::new(Mutex::new(prometheus::Gauge::new("freeswitch_sessions_active".to_string(), 113 | "FreeSWITCH Active Sessions".to_string()))), 114 | 115 | Arc::new(Mutex::new(prometheus::Gauge::new("freeswitch_sessions_asr".to_string(), 116 | "FreeSWITCH Sessions Answer Seizure Ratio".to_string()))), 117 | 118 | Arc::new(Mutex::new(prometheus::Gauge::new("freeswitch_registrations_active".to_string(), 119 | "FreeSWITCH Active Registrations".to_string()))) 120 | ]}; 121 | static ref EVENT_NODE_IDS: Mutex> = { 122 | Mutex::new(Vec::new()) 123 | }; 124 | } 125 | 126 | impl Index for [Arc>] { 127 | type Output = Arc>; 128 | fn index(&self, idx: FSCounter) -> &Arc> { 129 | &self[idx as usize] 130 | } 131 | } 132 | 133 | impl Index for [Arc>] { 134 | type Output = Arc>; 135 | fn index(&self, idx: FSGauge) -> &Arc> { 136 | &self[idx as usize] 137 | } 138 | } 139 | 140 | fn prometheus_load(mod_int: &ModInterface) -> Status { 141 | unsafe { 142 | // FIXME: use config api to fetch the port from a config file 143 | let reg = Box::new(Arc::new(Mutex::new(Registry::new("0.0.0.0".to_string(), 9282)))); 144 | REGPTR = Box::into_raw(reg); 145 | }; 146 | let reg = unsafe { &*REGPTR }; 147 | // At some point we'll have to configure things ... 148 | //let xml = fsr::xml_open_cfg(); 149 | Registry::start(®); 150 | { 151 | let mut r = reg.lock().unwrap(); 152 | for c in COUNTERS.iter() { 153 | r.register_counter(c.clone()); 154 | } 155 | for g in GAUGES.iter() { 156 | r.register_gauge(g.clone()); 157 | } 158 | } 159 | // Heartbeat counts 160 | let mut id = freeswitchrs::event_bind("mod_prometheus", fsr::event_types::HEARTBEAT, None, |_| { 161 | COUNTERS[FSCounter::Heartbeats].lock().unwrap().increment(); 162 | }); 163 | EVENT_NODE_IDS.lock().unwrap().push(id); 164 | 165 | // New channel created 166 | id = freeswitchrs::event_bind("mod_prometheus", fsr::event_types::CHANNEL_CREATE, None, |e| { 167 | COUNTERS[FSCounter::Sessions].lock().unwrap().increment(); 168 | GAUGES[FSGauge::SessionsActive].lock().unwrap().increment(); 169 | if let Some(direction) = e.header("Call-Direction") { 170 | if direction == "inbound" { 171 | COUNTERS[FSCounter::SessionsInbound].lock().unwrap().increment(); 172 | } else { 173 | let outbound = COUNTERS[FSCounter::SessionsOutbound].lock().unwrap().increment(); 174 | let asr = COUNTERS[FSCounter::SessionsOutboundAnswered].lock().unwrap().value() / outbound; 175 | GAUGES[FSGauge::SessionsASR].lock().unwrap().set(asr); 176 | } 177 | } else { 178 | let b = e.body().unwrap_or(Cow::Borrowed("")); 179 | fslog!(WARNING, "Received channel create event with no call direction: {:?}\n", b); 180 | } 181 | }); 182 | EVENT_NODE_IDS.lock().unwrap().push(id); 183 | 184 | // Channel answered 185 | id = freeswitchrs::event_bind("mod_prometheus", fsr::event_types::CHANNEL_ANSWER, None, |e| { 186 | COUNTERS[FSCounter::SessionsAnswered].lock().unwrap().increment(); 187 | if let Some(direction) = e.header("Call-Direction") { 188 | if direction == "inbound" { 189 | COUNTERS[FSCounter::SessionsInboundAnswered].lock().unwrap().increment(); 190 | } else { 191 | let answered = COUNTERS[FSCounter::SessionsOutboundAnswered].lock().unwrap().increment(); 192 | let asr = answered / COUNTERS[FSCounter::SessionsOutbound].lock().unwrap().value(); 193 | GAUGES[FSGauge::SessionsASR].lock().unwrap().set(asr); 194 | } 195 | } else { 196 | let b = e.body().unwrap_or(Cow::Borrowed("")); 197 | fslog!(WARNING, "Received channel answer event with no call direction: {:?}\n", b); 198 | } 199 | }); 200 | EVENT_NODE_IDS.lock().unwrap().push(id); 201 | 202 | // Channel hangup 203 | id = freeswitchrs::event_bind("mod_prometheus", fsr::event_types::CHANNEL_HANGUP, None, |e| { 204 | if let Some(answer) = e.header("Caller-Channel-Answered-Time") { 205 | let parsed_time = answer.parse::(); 206 | if parsed_time.is_ok() && parsed_time.unwrap() == 0 as i64 { 207 | if let Some(direction) = e.header("Call-Direction") { 208 | if direction == "inbound" { 209 | COUNTERS[FSCounter::SessionsInboundFailed].lock().unwrap().increment(); 210 | } else { 211 | COUNTERS[FSCounter::SessionsOutboundFailed].lock().unwrap().increment(); 212 | } 213 | COUNTERS[FSCounter::SessionsFailed].lock().unwrap().increment(); 214 | } else { 215 | let b = e.body().unwrap_or(Cow::Borrowed("")); 216 | fslog!(WARNING, "Received channel hangup event with no call direction: {:?}\n", b); 217 | } 218 | } 219 | } else { 220 | let b = e.body().unwrap_or(Cow::Borrowed("")); 221 | fslog!(WARNING, "Received channel hangup event with no call answer time information: {:?}\n", b); 222 | } 223 | }); 224 | EVENT_NODE_IDS.lock().unwrap().push(id); 225 | 226 | // Channel destroyed 227 | id = freeswitchrs::event_bind("mod_prometheus", fsr::event_types::CHANNEL_DESTROY, None, |_| { 228 | GAUGES[FSGauge::SessionsActive].lock().unwrap().decrement(); 229 | }); 230 | EVENT_NODE_IDS.lock().unwrap().push(id); 231 | 232 | // FIXME: Registrations are bound to be outdated on restart (registrations are in the db) 233 | // so we should fetch them on module load to get the counters initialized 234 | 235 | // Registration attempts 236 | id = freeswitchrs::event_bind("mod_prometheus", fsr::event_types::CUSTOM, Some("sofia::register_attempt"), |_| { 237 | COUNTERS[FSCounter::RegistrationAttempts].lock().unwrap().increment(); 238 | }); 239 | EVENT_NODE_IDS.lock().unwrap().push(id); 240 | 241 | // Registration failures 242 | id = freeswitchrs::event_bind("mod_prometheus", fsr::event_types::CUSTOM, Some("sofia::register_failure"), |_| { 243 | COUNTERS[FSCounter::RegistrationFailures].lock().unwrap().increment(); 244 | }); 245 | EVENT_NODE_IDS.lock().unwrap().push(id); 246 | 247 | // Registration counters 248 | id = freeswitchrs::event_bind("mod_prometheus", fsr::event_types::CUSTOM, Some("sofia::register"), |_| { 249 | COUNTERS[FSCounter::Registrations].lock().unwrap().increment(); 250 | GAUGES[FSGauge::RegistrationsActive].lock().unwrap().increment(); 251 | }); 252 | EVENT_NODE_IDS.lock().unwrap().push(id); 253 | 254 | id = freeswitchrs::event_bind("mod_prometheus", fsr::event_types::CUSTOM, Some("sofia::unregister"), |_| { 255 | GAUGES[FSGauge::RegistrationsActive].lock().unwrap().decrement(); 256 | }); 257 | EVENT_NODE_IDS.lock().unwrap().push(id); 258 | 259 | id = freeswitchrs::event_bind("mod_prometheus", fsr::event_types::CUSTOM, Some("sofia::expire"), |_| { 260 | GAUGES[FSGauge::RegistrationsActive].lock().unwrap().decrement(); 261 | }); 262 | EVENT_NODE_IDS.lock().unwrap().push(id); 263 | 264 | /* APIs */ 265 | mod_int.add_raw_api("prom_counter_increment", "Increment Counter", "Increment Counter", counter_increment_api); 266 | mod_int.add_raw_api("prom_gauge_set", "Set Gauge Value", "Set Gauge Value", gauge_set_api); 267 | mod_int.add_raw_api("prom_gauge_increment", "Increase Gauge Value", "Increase Gauge Value", gauge_increment_api); 268 | mod_int.add_raw_api("prom_gauge_decrement", "Decrement Gauge Value", "Decrement Gauge Value", gauge_decrement_api); 269 | 270 | /* Applications */ 271 | mod_int.add_raw_application("prom_gauge_increment", 272 | "Increment Gauge", "Increment Gauge", 273 | "prom_gauge_increment []", 274 | gauge_increment_app, 275 | fsr::application_flag_enum::SUPPORT_NOMEDIA); 276 | 277 | fslog!(INFO, "Loaded Prometheus Metrics Module"); 278 | Ok(()) 279 | } 280 | 281 | fn parse_metric_api_args(cmd: *const std::os::raw::c_char, 282 | stream: Option<*mut fsr::stream_handle>) 283 | -> Option<(String, f64)> { 284 | let cmdopt = unsafe { fsr::ptr_to_str(cmd) }; 285 | if !cmdopt.is_some() { 286 | if let Some(s) = stream { 287 | unsafe { (*s).write_function.unwrap()(s, fsr::str_to_ptr("Invalid arguments")); } 288 | } else { 289 | fslog!(ERROR, "Invalid metric arguments"); 290 | } 291 | return None; 292 | } 293 | let cmdstr = cmdopt.unwrap(); 294 | let args: Vec<&str> = cmdstr.split(' ').collect(); 295 | let name = args[0]; 296 | let val = if args.len() > 1 { 297 | let r = args[1].parse::(); 298 | if r.is_ok() { 299 | r.unwrap() 300 | } else { 301 | if let Some(s) = stream { 302 | unsafe { (*s).write_function.unwrap()(s, fsr::str_to_ptr("Invalid metric value")); } 303 | } else { 304 | fslog!(ERROR, "Invalid metric value"); 305 | } 306 | return None; 307 | } 308 | } else { 1 as f64 }; 309 | Some((name.to_string(), val)) 310 | } 311 | 312 | #[allow(unused_variables)] 313 | unsafe extern "C" fn counter_increment_api(cmd: *const std::os::raw::c_char, 314 | session: *mut fsr::core_session, 315 | stream: *mut fsr::stream_handle) 316 | -> fsr::status { 317 | let argsopt = parse_metric_api_args(cmd, Some(stream)); 318 | if !argsopt.is_some() { 319 | return fsr::status::FALSE; 320 | } 321 | let v: f64; 322 | let (name, val) = argsopt.unwrap(); 323 | { 324 | let mut counters = USER_COUNTERS.lock().unwrap(); 325 | if !counters.contains_key(&name) { 326 | let counter = Arc::new(Mutex::new(Counter::new(name.clone(), name.clone()))); 327 | counters.insert(name.clone(), counter.clone()); 328 | let reg = &*REGPTR; 329 | reg.lock().unwrap().register_counter(counter); 330 | } 331 | v = counters[&name].lock().unwrap().increment_by(val); 332 | } 333 | let out = format!("+OK {}", v); 334 | (*stream).write_function.unwrap()(stream, fsr::str_to_ptr(&out)); 335 | fsr::status::SUCCESS 336 | } 337 | 338 | fn gauge_get(name: &str) -> Arc> { 339 | let mut gauges = USER_GAUGES.lock().unwrap(); 340 | if gauges.contains_key(name) { 341 | gauges[name].clone() 342 | } else { 343 | let gauge = Arc::new(Mutex::new(Gauge::new(name.to_string(), name.to_string()))); 344 | gauges.insert(name.to_string(), gauge.clone()); 345 | let reg = unsafe { &*REGPTR }; 346 | reg.lock().unwrap().register_gauge(gauge.clone()); 347 | gauge 348 | } 349 | } 350 | 351 | #[allow(unused_variables)] 352 | unsafe extern "C" fn gauge_set_api(cmd: *const std::os::raw::c_char, 353 | session: *mut fsr::core_session, 354 | stream: *mut fsr::stream_handle) 355 | -> fsr::status { 356 | let argsopt = parse_metric_api_args(cmd, Some(stream)); 357 | if !argsopt.is_some() { 358 | return fsr::status::FALSE; 359 | } 360 | let (name, val) = argsopt.unwrap(); 361 | let gauge = gauge_get(&name); 362 | let v = gauge.lock().unwrap().set(val); 363 | let out = format!("+OK {}", v); 364 | (*stream).write_function.unwrap()(stream, fsr::str_to_ptr(&out)); 365 | fsr::status::SUCCESS 366 | } 367 | 368 | #[allow(unused_variables)] 369 | unsafe extern "C" fn gauge_increment_api(cmd: *const std::os::raw::c_char, 370 | session: *mut fsr::core_session, 371 | stream: *mut fsr::stream_handle) 372 | -> fsr::status { 373 | let argsopt = parse_metric_api_args(cmd, Some(stream)); 374 | if !argsopt.is_some() { 375 | return fsr::status::FALSE; 376 | } 377 | let (name, val) = argsopt.unwrap(); 378 | let gauge = gauge_get(&name); 379 | let v = gauge.lock().unwrap().increment_by(val); 380 | let out = format!("+OK {}", v); 381 | (*stream).write_function.unwrap()(stream, fsr::str_to_ptr(&out)); 382 | fsr::status::SUCCESS 383 | } 384 | 385 | #[allow(unused_variables)] 386 | unsafe extern "C" fn gauge_decrement_api(cmd: *const std::os::raw::c_char, 387 | session: *mut fsr::core_session, 388 | stream: *mut fsr::stream_handle) 389 | -> fsr::status { 390 | let argsopt = parse_metric_api_args(cmd, Some(stream)); 391 | if !argsopt.is_some() { 392 | return fsr::status::FALSE; 393 | } 394 | let (name, val) = argsopt.unwrap(); 395 | let gauge = gauge_get(&name); 396 | let v = gauge.lock().unwrap().decrement_by(val); 397 | let out = format!("+OK {}", v); 398 | (*stream).write_function.unwrap()(stream, fsr::str_to_ptr(&out)); 399 | fsr::status::SUCCESS 400 | } 401 | 402 | #[allow(unused_variables)] 403 | unsafe extern "C" fn gauge_increment_app(session: *mut fsr::core_session, 404 | data: *const std::os::raw::c_char) { 405 | let argsopt = parse_metric_api_args(data, None); 406 | if argsopt.is_some() { 407 | let (name, val) = argsopt.unwrap(); 408 | let gauge = gauge_get(&name); 409 | let v = gauge.lock().unwrap().increment_by(val); 410 | fslog!(INFO, "Incremented gauge {} to {}", name, v); 411 | } 412 | } 413 | 414 | fn prometheus_unload() -> Status { 415 | let reg = unsafe { &*REGPTR }; 416 | USER_GAUGES.lock().unwrap().clear(); 417 | USER_COUNTERS.lock().unwrap().clear(); 418 | { 419 | let mut event_ids = EVENT_NODE_IDS.lock().unwrap(); 420 | for e in event_ids.iter() { 421 | freeswitchrs::event_unbind(*e); 422 | } 423 | event_ids.clear(); 424 | } 425 | fslog!(DEBUG, "Stopping metric registry"); 426 | Registry::stop(®); 427 | std::mem::drop(reg); 428 | unsafe { 429 | REGPTR = 0 as *mut Arc>; 430 | } 431 | fslog!(DEBUG, "Metric registry destroyed"); 432 | Ok(()) 433 | } 434 | 435 | static MOD_PROMETHEUS_DEF: ModDefinition = ModDefinition { 436 | name: "mod_prometheus", 437 | load: prometheus_load, 438 | runtime: None, 439 | shutdown: Some(prometheus_unload) 440 | }; 441 | 442 | freeswitch_export_mod!(mod_prometheus_module_interface, MOD_PROMETHEUS_DEF); 443 | --------------------------------------------------------------------------------