|
1 |
| -use std::{sync::Arc, time::Duration}; |
| 1 | +use std::{any::Any, sync::Arc, time::Duration}; |
2 | 2 |
|
3 | 3 | use magnus::{
|
4 |
| - class, function, method, |
| 4 | + class, function, |
| 5 | + gc::register_mark_object, |
| 6 | + method, |
5 | 7 | prelude::*,
|
6 | 8 | r_hash::ForEach,
|
7 |
| - value::{IntoId, Qfalse, Qtrue}, |
8 |
| - DataTypeFunctions, Error, Float, Integer, RHash, RString, Ruby, Symbol, TryConvert, TypedData, |
9 |
| - Value, |
| 9 | + value::{IntoId, Lazy, Qfalse, Qtrue}, |
| 10 | + DataTypeFunctions, Error, Float, Integer, RClass, RHash, RModule, RString, Ruby, StaticSymbol, |
| 11 | + Symbol, TryConvert, TypedData, Value, |
| 12 | +}; |
| 13 | +use temporal_sdk_core_api::telemetry::metrics::{ |
| 14 | + self, BufferInstrumentRef, CustomMetricAttributes, MetricEvent, |
10 | 15 | };
|
11 |
| -use temporal_sdk_core_api::telemetry::metrics; |
12 | 16 |
|
13 |
| -use crate::{error, id, runtime::Runtime, ROOT_MOD}; |
| 17 | +use crate::{error, id, runtime::Runtime, util::SendSyncBoxValue, ROOT_MOD}; |
14 | 18 |
|
15 | 19 | pub fn init(ruby: &Ruby) -> Result<(), Error> {
|
16 | 20 | let root_mod = ruby.get_inner(&ROOT_MOD);
|
@@ -268,3 +272,221 @@ fn metric_key_value(k: Value, v: Value) -> Result<metrics::MetricKeyValue, Error
|
268 | 272 | };
|
269 | 273 | Ok(metrics::MetricKeyValue::new(key, val))
|
270 | 274 | }
|
| 275 | + |
| 276 | +#[derive(Clone, Debug)] |
| 277 | +pub struct BufferedMetricRef { |
| 278 | + value: Arc<SendSyncBoxValue<Value>>, |
| 279 | +} |
| 280 | + |
| 281 | +impl BufferInstrumentRef for BufferedMetricRef {} |
| 282 | + |
| 283 | +#[derive(Debug)] |
| 284 | +struct BufferedMetricAttributes { |
| 285 | + value: SendSyncBoxValue<RHash>, |
| 286 | +} |
| 287 | + |
| 288 | +impl CustomMetricAttributes for BufferedMetricAttributes { |
| 289 | + fn as_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync> { |
| 290 | + self as Arc<dyn Any + Send + Sync> |
| 291 | + } |
| 292 | +} |
| 293 | + |
| 294 | +static METRIC_BUFFER_UPDATE: Lazy<RClass> = Lazy::new(|ruby| { |
| 295 | + let cls = ruby |
| 296 | + .class_object() |
| 297 | + .const_get::<_, RModule>("Temporalio") |
| 298 | + .unwrap() |
| 299 | + .const_get::<_, RClass>("Runtime") |
| 300 | + .unwrap() |
| 301 | + .const_get::<_, RClass>("MetricBuffer") |
| 302 | + .unwrap() |
| 303 | + .const_get("Update") |
| 304 | + .unwrap(); |
| 305 | + // Make sure class is not GC'd |
| 306 | + register_mark_object(cls); |
| 307 | + cls |
| 308 | +}); |
| 309 | + |
| 310 | +static METRIC_BUFFER_METRIC: Lazy<RClass> = Lazy::new(|ruby| { |
| 311 | + let cls = ruby |
| 312 | + .class_object() |
| 313 | + .const_get::<_, RModule>("Temporalio") |
| 314 | + .unwrap() |
| 315 | + .const_get::<_, RClass>("Runtime") |
| 316 | + .unwrap() |
| 317 | + .const_get::<_, RClass>("MetricBuffer") |
| 318 | + .unwrap() |
| 319 | + .const_get("Metric") |
| 320 | + .unwrap(); |
| 321 | + // Make sure class is not GC'd |
| 322 | + register_mark_object(cls); |
| 323 | + cls |
| 324 | +}); |
| 325 | + |
| 326 | +static METRIC_KIND_COUNTER: Lazy<StaticSymbol> = Lazy::new(|ruby| ruby.sym_new("counter")); |
| 327 | +static METRIC_KIND_GAUGE: Lazy<StaticSymbol> = Lazy::new(|ruby| ruby.sym_new("gauge")); |
| 328 | +static METRIC_KIND_HISTOGRAM: Lazy<StaticSymbol> = Lazy::new(|ruby| ruby.sym_new("histogram")); |
| 329 | + |
| 330 | +pub fn convert_metric_events( |
| 331 | + ruby: &Ruby, |
| 332 | + events: Vec<MetricEvent<BufferedMetricRef>>, |
| 333 | + durations_as_seconds: bool, |
| 334 | +) -> Result<Vec<Value>, Error> { |
| 335 | + let temp: Result<Vec<Option<Value>>, Error> = events |
| 336 | + .into_iter() |
| 337 | + .map(|e| convert_metric_event(ruby, e, durations_as_seconds)) |
| 338 | + .collect(); |
| 339 | + Ok(temp?.into_iter().flatten().collect()) |
| 340 | +} |
| 341 | + |
| 342 | +fn convert_metric_event( |
| 343 | + ruby: &Ruby, |
| 344 | + event: MetricEvent<BufferedMetricRef>, |
| 345 | + durations_as_seconds: bool, |
| 346 | +) -> Result<Option<Value>, Error> { |
| 347 | + match event { |
| 348 | + // Create the metric and put it on the lazy ref |
| 349 | + MetricEvent::Create { |
| 350 | + params, |
| 351 | + populate_into, |
| 352 | + kind, |
| 353 | + } => { |
| 354 | + let cls = ruby.get_inner(&METRIC_BUFFER_METRIC); |
| 355 | + let val: Value = cls.funcall( |
| 356 | + "new", |
| 357 | + ( |
| 358 | + // Name |
| 359 | + params.name.to_string(), |
| 360 | + // Description |
| 361 | + Some(params.description) |
| 362 | + .filter(|s| !s.is_empty()) |
| 363 | + .map(|s| s.to_string()), |
| 364 | + // Unit |
| 365 | + if matches!(kind, metrics::MetricKind::HistogramDuration) |
| 366 | + && params.unit == "duration" |
| 367 | + { |
| 368 | + if durations_as_seconds { |
| 369 | + Some("s".to_owned()) |
| 370 | + } else { |
| 371 | + Some("ms".to_owned()) |
| 372 | + } |
| 373 | + } else if params.unit.is_empty() { |
| 374 | + None |
| 375 | + } else { |
| 376 | + Some(params.unit.to_string()) |
| 377 | + }, |
| 378 | + // Kind |
| 379 | + match kind { |
| 380 | + metrics::MetricKind::Counter => ruby.get_inner(&METRIC_KIND_COUNTER), |
| 381 | + metrics::MetricKind::Gauge | metrics::MetricKind::GaugeF64 => { |
| 382 | + ruby.get_inner(&METRIC_KIND_GAUGE) |
| 383 | + } |
| 384 | + metrics::MetricKind::Histogram |
| 385 | + | metrics::MetricKind::HistogramF64 |
| 386 | + | metrics::MetricKind::HistogramDuration => { |
| 387 | + ruby.get_inner(&METRIC_KIND_HISTOGRAM) |
| 388 | + } |
| 389 | + }, |
| 390 | + ), |
| 391 | + )?; |
| 392 | + // Put on lazy ref |
| 393 | + populate_into |
| 394 | + .set(Arc::new(BufferedMetricRef { |
| 395 | + value: Arc::new(SendSyncBoxValue::new(val)), |
| 396 | + })) |
| 397 | + .map_err(|_| error!("Failed setting metric ref"))?; |
| 398 | + Ok(None) |
| 399 | + } |
| 400 | + // Create the attributes and put it on the lazy ref |
| 401 | + MetricEvent::CreateAttributes { |
| 402 | + populate_into, |
| 403 | + append_from, |
| 404 | + attributes, |
| 405 | + } => { |
| 406 | + // Create a hash (from existing or new) |
| 407 | + let hash: RHash = match append_from { |
| 408 | + Some(existing) => { |
| 409 | + let attrs = existing |
| 410 | + .get() |
| 411 | + .clone() |
| 412 | + .as_any() |
| 413 | + .downcast::<BufferedMetricAttributes>() |
| 414 | + .map_err(|_| { |
| 415 | + error!("Unable to downcast to expected buffered metric attributes") |
| 416 | + })? |
| 417 | + .value |
| 418 | + .value(ruby); |
| 419 | + attrs.funcall("dup", ())? |
| 420 | + } |
| 421 | + None => ruby.hash_new_capa(attributes.len()), |
| 422 | + }; |
| 423 | + // Add attributes |
| 424 | + for kv in attributes.into_iter() { |
| 425 | + match kv.value { |
| 426 | + metrics::MetricValue::String(v) => hash.aset(kv.key, v)?, |
| 427 | + metrics::MetricValue::Int(v) => hash.aset(kv.key, v)?, |
| 428 | + metrics::MetricValue::Float(v) => hash.aset(kv.key, v)?, |
| 429 | + metrics::MetricValue::Bool(v) => hash.aset(kv.key, v)?, |
| 430 | + }; |
| 431 | + } |
| 432 | + hash.freeze(); |
| 433 | + // Put on lazy ref |
| 434 | + populate_into |
| 435 | + .set(Arc::new(BufferedMetricAttributes { |
| 436 | + value: SendSyncBoxValue::new(hash), |
| 437 | + })) |
| 438 | + .map_err(|_| error!("Failed setting metric attrs"))?; |
| 439 | + Ok(None) |
| 440 | + } |
| 441 | + // Convert to Ruby metric update |
| 442 | + MetricEvent::Update { |
| 443 | + instrument, |
| 444 | + attributes, |
| 445 | + update, |
| 446 | + } => { |
| 447 | + let cls = ruby.get_inner(&METRIC_BUFFER_UPDATE); |
| 448 | + Ok(Some( |
| 449 | + cls.funcall( |
| 450 | + "new", |
| 451 | + ( |
| 452 | + // Metric |
| 453 | + instrument.get().clone().value.clone().value(ruby), |
| 454 | + // Value |
| 455 | + match update { |
| 456 | + metrics::MetricUpdateVal::Duration(v) if durations_as_seconds => { |
| 457 | + ruby.into_value(v.as_secs_f64()) |
| 458 | + } |
| 459 | + metrics::MetricUpdateVal::Duration(v) => { |
| 460 | + // As of this writing, https://github.com/matsadler/magnus/pull/136 not released, so we will do |
| 461 | + // the logic ourselves |
| 462 | + let val = v.as_millis(); |
| 463 | + if val <= u64::MAX as u128 { |
| 464 | + ruby.into_value(val as u64) |
| 465 | + } else { |
| 466 | + ruby.module_kernel() |
| 467 | + .funcall("Integer", (val.to_string(),)) |
| 468 | + .unwrap() |
| 469 | + } |
| 470 | + } |
| 471 | + metrics::MetricUpdateVal::Delta(v) => ruby.into_value(v), |
| 472 | + metrics::MetricUpdateVal::DeltaF64(v) => ruby.into_value(v), |
| 473 | + metrics::MetricUpdateVal::Value(v) => ruby.into_value(v), |
| 474 | + metrics::MetricUpdateVal::ValueF64(v) => ruby.into_value(v), |
| 475 | + }, |
| 476 | + // Attributes |
| 477 | + attributes |
| 478 | + .get() |
| 479 | + .clone() |
| 480 | + .as_any() |
| 481 | + .downcast::<BufferedMetricAttributes>() |
| 482 | + .map_err(|_| { |
| 483 | + error!("Unable to downcast to expected buffered metric attributes") |
| 484 | + })? |
| 485 | + .value |
| 486 | + .value(ruby), |
| 487 | + ), |
| 488 | + )?, |
| 489 | + )) |
| 490 | + } |
| 491 | + } |
| 492 | +} |
0 commit comments