From 521e6d42bb64d96ff66be0854b70823704f3d2ca Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 14 Apr 2026 12:36:42 +0200 Subject: [PATCH 1/2] feat: improve Prometheus metrics API Co-Authored-By: Claude Opus 4.6 --- Cargo.lock | 67 ++++ confidence-resolver/Cargo.toml | 1 + confidence-resolver/src/telemetry.rs | 293 ++++++++++++++++-- .../go/confidence/integration_test.go | 61 +++- .../assets/confidence_resolver.wasm | Bin 481211 -> 482446 bytes .../internal/local_resolver/local_resolver.go | 5 +- .../internal/local_resolver/pool.go | 48 ++- .../internal/local_resolver/recover.go | 4 +- .../internal/local_resolver/recover_test.go | 2 +- .../internal/local_resolver/wasm.go | 6 +- .../internal/proto/admin/resolver.pb.go | 4 +- .../internal/proto/resolver/api.pb.go | 4 +- .../internal/proto/resolver/api_grpc.pb.go | 2 +- .../internal/proto/resolver/types.pb.go | 4 +- .../proto/resolverinternal/internal_api.pb.go | 4 +- .../resolverinternal/internal_api_grpc.pb.go | 2 +- .../internal/proto/types/target.pb.go | 4 +- .../internal/proto/types/types.pb.go | 4 +- .../internal/proto/wasm/messages.pb.go | 34 +- .../internal/proto/wasm/wasm_api.pb.go | 4 +- .../confidence/internal/testutil/helpers.go | 2 +- .../go/confidence/materialization.go | 4 +- .../go/confidence/provider.go | 15 +- .../go/confidence/provider_test.go | 2 +- openfeature-provider/go/go.mod | 7 +- openfeature-provider/go/go.sum | 32 +- openfeature-provider/js/src/WasmResolver.ts | 5 +- .../proto/confidence/wasm/messages.proto | 2 + wasm/proto/messages.proto | 2 + wasm/rust-guest/src/lib.rs | 6 +- 30 files changed, 548 insertions(+), 82 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 701cf64f..28b0f5fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -76,6 +76,12 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "auto_ops" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7460f7dd8e100147b82a63afca1a20eb6c231ee36b90ba7272e14951cb58af59" + [[package]] name = "autocfg" version = "1.5.0" @@ -277,6 +283,7 @@ dependencies = [ "fastrand", "isocountry", "miniz_oxide", + "openmetrics-parser", "papaya", "pbjson", "pbjson-build", @@ -1240,6 +1247,17 @@ dependencies = [ "rust-guest", ] +[[package]] +name = "openmetrics-parser" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e40a68c62e09c5dfec2f6472af3bd5e8ddf506fcf14c78ece23794ffbb874eca" +dependencies = [ + "auto_ops", + "pest", + "pest_derive", +] + [[package]] name = "openssl-probe" version = "0.2.0" @@ -1328,6 +1346,49 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "pest" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" +dependencies = [ + "memchr", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220" +dependencies = [ + "pest", + "sha2", +] + [[package]] name = "petgraph" version = "0.6.5" @@ -2556,6 +2617,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + [[package]] name = "unicode-ident" version = "1.0.18" diff --git a/confidence-resolver/Cargo.toml b/confidence-resolver/Cargo.toml index ec0d72c8..2c451753 100644 --- a/confidence-resolver/Cargo.toml +++ b/confidence-resolver/Cargo.toml @@ -43,6 +43,7 @@ serde_json = { version = "1.0.107", optional = true } isocountry = "0.3.2" [dev-dependencies] +openmetrics-parser = "0.4.4" regex = "1.10.2" [build-dependencies] diff --git a/confidence-resolver/src/telemetry.rs b/confidence-resolver/src/telemetry.rs index 77d33000..f93b6f0f 100644 --- a/confidence-resolver/src/telemetry.rs +++ b/confidence-resolver/src/telemetry.rs @@ -119,6 +119,28 @@ pub struct HistogramSnapshot { pub buckets: Vec, } +/// Configuration for Prometheus text format rendering. +pub struct PrometheusConfig { + /// Number of histogram buckets per decimal order of magnitude. + /// The internal histogram always stores 174 buckets (18 per decade). + /// This controls how many are emitted in the text output by striding. + /// Valid range: 1..=18. Values outside this range are clamped. + /// A value of 0 is treated as the default (18 — no striding). + pub buckets_per_decade: u32, + /// When true, emit OpenMetrics text format instead of Prometheus exposition format. + /// Differences: integer values rendered as floats, `# EOF` appended. + pub openmetrics: bool, +} + +impl Default for PrometheusConfig { + fn default() -> Self { + PrometheusConfig { + buckets_per_decade: 18, + openmetrics: false, + } + } +} + impl TelemetrySnapshot { /// Format the snapshot as Prometheus exposition text. /// @@ -126,70 +148,122 @@ impl TelemetrySnapshot { /// The histogram uses cumulative `le` buckets derived from the exponential /// bucket boundaries. /// - /// `instance` is included as an `instance="..."` label on every metric, + /// `resolver_id` is included as a `resolver_id="..."` label on every metric, /// allowing outputs from multiple WASM instances to be concatenated into /// a single scrape endpoint. - pub fn to_prometheus(&self, instance: &str) -> String { + pub fn to_prometheus(&self, resolver_id: &str, config: &PrometheusConfig) -> String { let mut out = String::new(); // fmt::Write for String is infallible; ignore the Ok. - let _ = self.write_prometheus(&mut out, instance); + let _ = self.write_prometheus(&mut out, resolver_id, config); out } - fn write_prometheus(&self, w: &mut dyn fmt::Write, instance: &str) -> fmt::Result { - self.write_histogram(w, instance)?; - self.write_resolve_rates(w, instance)?; - self.write_memory(w, instance) + fn write_prometheus( + &self, + w: &mut dyn fmt::Write, + resolver_id: &str, + config: &PrometheusConfig, + ) -> fmt::Result { + self.write_histogram(w, resolver_id, config)?; + self.write_resolve_rates(w, resolver_id, config)?; + self.write_memory(w, resolver_id, config)?; + if config.openmetrics { + writeln!(w, "# EOF")?; + } + Ok(()) } - fn write_histogram(&self, w: &mut dyn fmt::Write, instance: &str) -> fmt::Result { + fn write_histogram( + &self, + w: &mut dyn fmt::Write, + resolver_id: &str, + config: &PrometheusConfig, + ) -> fmt::Result { if self.latency.count == 0 { return Ok(()); } + let bpd = if config.buckets_per_decade == 0 { + 18usize + } else { + config.buckets_per_decade.clamp(1, 18) as usize + }; + // bpd is in 1..=18, so checked_div cannot return None. + let stride = 18usize.checked_div(bpd).unwrap_or(1); + + writeln!( + w, + "# HELP confidence_resolve_latency_microseconds Latency of flag resolve operations in microseconds." + )?; writeln!( w, "# TYPE confidence_resolve_latency_microseconds histogram" )?; let mut cumulative: u64 = 0; + // Track the next bucket index to emit (stride-1, stride*2-1, ...). + // Advance next_emit at every stride boundary regardless of data; + // only skip the actual write when cumulative is still zero. + let mut next_emit = stride.wrapping_sub(1); + // OpenMetrics requires integer values to be rendered as floats. + let suffix = if config.openmetrics { ".0" } else { "" }; for (i, &count) in self.latency.buckets.iter().enumerate() { cumulative = cumulative.wrapping_add(count); + if i != next_emit { + continue; + } + next_emit = next_emit.saturating_add(stride); if cumulative == 0 { continue; } let upper = ((i as f64 + 1.0) * LN_RATIO).exp(); writeln!( w, - "confidence_resolve_latency_microseconds_bucket{{instance=\"{instance}\",le=\"{upper:.6e}\"}} {cumulative}" + "confidence_resolve_latency_microseconds_bucket{{resolver_id=\"{resolver_id}\",le=\"{upper:.6e}\"}} {cumulative}{suffix}" )?; } writeln!( w, - "confidence_resolve_latency_microseconds_bucket{{instance=\"{instance}\",le=\"+Inf\"}} {}", + "confidence_resolve_latency_microseconds_bucket{{resolver_id=\"{resolver_id}\",le=\"+Inf\"}} {}{suffix}", self.latency.count )?; writeln!( w, - "confidence_resolve_latency_microseconds_sum{{instance=\"{instance}\"}} {}", + "confidence_resolve_latency_microseconds_sum{{resolver_id=\"{resolver_id}\"}} {}{suffix}", self.latency.sum )?; writeln!( w, - "confidence_resolve_latency_microseconds_count{{instance=\"{instance}\"}} {}", + "confidence_resolve_latency_microseconds_count{{resolver_id=\"{resolver_id}\"}} {}{suffix}", self.latency.count )?; Ok(()) } - fn write_resolve_rates(&self, w: &mut dyn fmt::Write, instance: &str) -> fmt::Result { + fn write_resolve_rates( + &self, + w: &mut dyn fmt::Write, + resolver_id: &str, + config: &PrometheusConfig, + ) -> fmt::Result { let has_any = self.resolve_rates.iter().any(|&c| c > 0); if !has_any { return Ok(()); } - writeln!(w, "# TYPE confidence_resolves_total counter")?; + let suffix = if config.openmetrics { ".0" } else { "" }; + // OpenMetrics: TYPE/HELP use the base name; sample lines keep _total. + let type_name = if config.openmetrics { + "confidence_resolves" + } else { + "confidence_resolves_total" + }; + writeln!( + w, + "# HELP {type_name} Total number of flag resolve operations." + )?; + writeln!(w, "# TYPE {type_name} counter")?; for (i, &count) in self.resolve_rates.iter().enumerate() { if count == 0 { continue; @@ -199,21 +273,31 @@ impl TelemetrySnapshot { .unwrap_or("UNKNOWN"); writeln!( w, - "confidence_resolves_total{{instance=\"{instance}\",reason=\"{label}\"}} {count}" + "confidence_resolves_total{{resolver_id=\"{resolver_id}\",reason=\"{label}\"}} {count}{suffix}" )?; } Ok(()) } - fn write_memory(&self, w: &mut dyn fmt::Write, instance: &str) -> fmt::Result { + fn write_memory( + &self, + w: &mut dyn fmt::Write, + resolver_id: &str, + config: &PrometheusConfig, + ) -> fmt::Result { if self.memory_bytes == 0 { return Ok(()); } + let suffix = if config.openmetrics { ".0" } else { "" }; + writeln!( + w, + "# HELP confidence_memory_bytes Current memory usage of the resolver in bytes." + )?; writeln!(w, "# TYPE confidence_memory_bytes gauge")?; writeln!( w, - "confidence_memory_bytes{{instance=\"{instance}\"}} {}", + "confidence_memory_bytes{{resolver_id=\"{resolver_id}\"}} {}{suffix}", self.memory_bytes ) } @@ -633,7 +717,7 @@ mod tests { #[test] fn prometheus_empty_snapshot() { let snap = TelemetrySnapshot::default(); - assert_eq!(snap.to_prometheus("w0"), ""); + assert_eq!(snap.to_prometheus("w0", &PrometheusConfig::default()), ""); } #[test] @@ -645,25 +729,32 @@ mod tests { tel.mark_resolve(ResolveReason::Match); tel.mark_resolve(ResolveReason::NoSegmentMatch); - let prom = tel.snapshot().to_prometheus("w0"); + let config = PrometheusConfig::default(); + let prom = tel.snapshot().to_prometheus("w0", &config); - // Histogram header + // HELP and TYPE headers + assert!(prom.contains("# HELP confidence_resolve_latency_microseconds")); assert!(prom.contains("# TYPE confidence_resolve_latency_microseconds histogram")); - // Must have +Inf bucket with instance label + // Must have +Inf bucket with resolver_id label assert!(prom.contains( - r#"confidence_resolve_latency_microseconds_bucket{instance="w0",le="+Inf"} 2"# + r#"confidence_resolve_latency_microseconds_bucket{resolver_id="w0",le="+Inf"} 2"# )); - // Sum and count with instance label - assert!(prom.contains(r#"confidence_resolve_latency_microseconds_sum{instance="w0"} 600"#)); - assert!(prom.contains(r#"confidence_resolve_latency_microseconds_count{instance="w0"} 2"#)); + // Sum and count with resolver_id label + assert!( + prom.contains(r#"confidence_resolve_latency_microseconds_sum{resolver_id="w0"} 600"#) + ); + assert!( + prom.contains(r#"confidence_resolve_latency_microseconds_count{resolver_id="w0"} 2"#) + ); - // Counters with instance label + // Counters with resolver_id label + assert!(prom.contains("# HELP confidence_resolves_total")); assert!(prom.contains("# TYPE confidence_resolves_total counter")); assert!(prom.contains( - r#"confidence_resolves_total{instance="w0",reason="RESOLVE_REASON_MATCH"} 2"# + r#"confidence_resolves_total{resolver_id="w0",reason="RESOLVE_REASON_MATCH"} 2"# )); assert!(prom.contains( - r#"confidence_resolves_total{instance="w0",reason="RESOLVE_REASON_NO_SEGMENT_MATCH"} 1"# + r#"confidence_resolves_total{resolver_id="w0",reason="RESOLVE_REASON_NO_SEGMENT_MATCH"} 1"# )); // Zero-count reasons should be omitted assert!(!prom.contains("FLAG_ARCHIVED")); @@ -676,7 +767,8 @@ mod tests { tel.record_latency_us(10); tel.record_latency_us(10_000); - let prom = tel.snapshot().to_prometheus("w0"); + let config = PrometheusConfig::default(); + let prom = tel.snapshot().to_prometheus("w0", &config); // Parse all le buckets and verify they are monotonically non-decreasing let bucket_counts: Vec = prom @@ -702,11 +794,144 @@ mod tests { tel.mark_resolve(ResolveReason::Match); let snap = tel.snapshot(); - let mut combined = snap.to_prometheus("w0"); - combined.push_str(&snap.to_prometheus("w1")); + let config = PrometheusConfig::default(); + let mut combined = snap.to_prometheus("w0", &config); + combined.push_str(&snap.to_prometheus("w1", &config)); + + // Both resolver_ids present + assert!(combined.contains(r#"resolver_id="w0""#)); + assert!(combined.contains(r#"resolver_id="w1""#)); + } + + #[test] + fn prometheus_bucket_striding() { + let tel = Telemetry::new(); + tel.record_latency_us(100); + tel.record_latency_us(10_000); + tel.record_latency_us(1_000_000); + + let snap = tel.snapshot(); + + // Default (18 per decade) — count all le= lines + let full = snap.to_prometheus( + "w0", + &PrometheusConfig { + buckets_per_decade: 18, + ..PrometheusConfig::default() + }, + ); + let full_count = full.lines().filter(|l| l.contains("le=\"")).count(); + + // 9 per decade — should produce roughly half the bucket lines + let half = snap.to_prometheus( + "w0", + &PrometheusConfig { + buckets_per_decade: 9, + ..PrometheusConfig::default() + }, + ); + let half_count = half.lines().filter(|l| l.contains("le=\"")).count(); + + // 6 per decade — should produce roughly a third + let third = snap.to_prometheus( + "w0", + &PrometheusConfig { + buckets_per_decade: 6, + ..PrometheusConfig::default() + }, + ); + let third_count = third.lines().filter(|l| l.contains("le=\"")).count(); + + assert!( + half_count < full_count, + "9 bpd ({half_count}) should produce fewer buckets than 18 bpd ({full_count})" + ); + assert!( + third_count < half_count, + "6 bpd ({third_count}) should produce fewer buckets than 9 bpd ({half_count})" + ); + + // +Inf must always be present + assert!(half.contains(r#"le="+Inf""#)); + assert!(third.contains(r#"le="+Inf""#)); + + // Cumulative correctness: last non-Inf bucket count should equal +Inf count + // (since all observations are within u32 range) + let inf_line = half.lines().find(|l| l.contains("+Inf")).unwrap(); + let inf_count: u64 = inf_line.rsplit_once(' ').unwrap().1.parse().unwrap(); + assert_eq!(inf_count, 3); + } + + #[test] + fn openmetrics_parses_successfully() { + let tel = Telemetry::with_memory_provider(|| 1_048_576); + tel.record_latency_us(100); + tel.record_latency_us(500); + tel.mark_resolve(ResolveReason::Match); + tel.mark_resolve(ResolveReason::NoSegmentMatch); + + let config = PrometheusConfig { + openmetrics: true, + ..PrometheusConfig::default() + }; + let output = tel.snapshot().to_prometheus("w0", &config); + + // Must end with # EOF + assert!(output.trim_end().ends_with("# EOF")); + + // Must contain float-formatted values + assert!(output.contains(".0\n")); + + // Must parse with a strict OpenMetrics parser + let result = openmetrics_parser::openmetrics::parse_openmetrics(&output); + assert!( + result.is_ok(), + "OpenMetrics parser rejected output: {:?}\n\nRaw:\n{output}", + result.err() + ); + } + + #[test] + fn openmetrics_with_bucket_striding() { + let tel = Telemetry::with_memory_provider(|| 1_048_576); + tel.record_latency_us(100); + tel.record_latency_us(10_000); + tel.mark_resolve(ResolveReason::Match); - // Both instances present - assert!(combined.contains(r#"instance="w0""#)); - assert!(combined.contains(r#"instance="w1""#)); + let config = PrometheusConfig { + buckets_per_decade: 9, + openmetrics: true, + }; + let output = tel.snapshot().to_prometheus("w0", &config); + + let result = openmetrics_parser::openmetrics::parse_openmetrics(&output); + assert!( + result.is_ok(), + "OpenMetrics parser rejected strided output: {:?}\n\nRaw:\n{output}", + result.err() + ); + } + + #[test] + fn prometheus_bucket_stride_zero_means_default() { + let tel = Telemetry::new(); + tel.record_latency_us(100); + + let snap = tel.snapshot(); + let default_out = snap.to_prometheus( + "w0", + &PrometheusConfig { + buckets_per_decade: 18, + ..PrometheusConfig::default() + }, + ); + let zero_out = snap.to_prometheus( + "w0", + &PrometheusConfig { + buckets_per_decade: 0, + ..PrometheusConfig::default() + }, + ); + assert_eq!(default_out, zero_out); } } diff --git a/openfeature-provider/go/confidence/integration_test.go b/openfeature-provider/go/confidence/integration_test.go index d9ecfbaf..93c46ef5 100644 --- a/openfeature-provider/go/confidence/integration_test.go +++ b/openfeature-provider/go/confidence/integration_test.go @@ -11,6 +11,8 @@ import ( "time" "github.com/open-feature/go-sdk/openfeature" + "github.com/prometheus/common/expfmt" + "github.com/prometheus/common/model" fl "github.com/spotify/confidence-resolver/openfeature-provider/go/confidence/internal/flag_logger" lr "github.com/spotify/confidence-resolver/openfeature-provider/go/confidence/internal/local_resolver" resolverv1 "github.com/spotify/confidence-resolver/openfeature-provider/go/confidence/internal/proto/resolverinternal" @@ -741,11 +743,64 @@ func TestIntegration_GetPrometheusMetrics(t *testing.T) { t.Fatal("GetPrometheusMetrics() returned empty string, expected metrics output") } - if !strings.Contains(metrics, "confidence_resolve_latency") { - t.Errorf("Expected metrics to contain 'confidence_resolve_latency', got:\n%s", metrics) + t.Logf("Prometheus metrics output:\n%s", metrics) + + // Parse with prometheus/common/expfmt — this is the library the customer uses. + // v0.67+ is stricter about duplicate # TYPE headers, which was the reported bug. + parser := expfmt.NewTextParser(model.UTF8Validation) + families, err := parser.TextToMetricFamilies(strings.NewReader(metrics)) + if err != nil { + t.Fatalf("expfmt.TextParser failed to parse metrics output: %v\n\nRaw output:\n%s", err, metrics) } - t.Logf("Prometheus metrics output:\n%s", metrics) + // Verify expected metric families were parsed + latencyFamily, ok := families["confidence_resolve_latency_microseconds"] + if !ok { + t.Fatal("Parsed output missing metric family 'confidence_resolve_latency_microseconds'") + } + if _, ok := families["confidence_resolves_total"]; !ok { + t.Fatal("Parsed output missing metric family 'confidence_resolves_total'") + } + + // Verify resolver_id label is present and instance label is not + for _, m := range latencyFamily.GetMetric() { + hasResolverID := false + for _, lp := range m.GetLabel() { + if lp.GetName() == "resolver_id" { + hasResolverID = true + } + if lp.GetName() == "instance" { + t.Error("Metrics should not contain 'instance' label (renamed to resolver_id)") + } + } + if !hasResolverID { + t.Error("Expected 'resolver_id' label on latency metric") + } + } + + // Verify # HELP text was parsed (expfmt populates Help field) + if latencyFamily.GetHelp() == "" { + t.Error("Expected HELP text for latency histogram") + } + + // Verify BucketsPerDecade reduces output + fullMetrics := provider.GetPrometheusMetrics(SnapshotConfig{BucketsPerDecade: 18}) + halfMetrics := provider.GetPrometheusMetrics(SnapshotConfig{BucketsPerDecade: 9}) + + // Both must also parse cleanly + if _, err := parser.TextToMetricFamilies(strings.NewReader(fullMetrics)); err != nil { + t.Fatalf("BucketsPerDecade=18 output failed to parse: %v", err) + } + if _, err := parser.TextToMetricFamilies(strings.NewReader(halfMetrics)); err != nil { + t.Fatalf("BucketsPerDecade=9 output failed to parse: %v", err) + } + + fullBuckets := strings.Count(fullMetrics, `le="`) + halfBuckets := strings.Count(halfMetrics, `le="`) + if halfBuckets >= fullBuckets { + t.Errorf("BucketsPerDecade=9 (%d buckets) should produce fewer buckets than 18 (%d buckets)", + halfBuckets, fullBuckets) + } // Cleanup openfeature.Shutdown() diff --git a/openfeature-provider/go/confidence/internal/local_resolver/assets/confidence_resolver.wasm b/openfeature-provider/go/confidence/internal/local_resolver/assets/confidence_resolver.wasm index 13ca2da243c61f9ce6f71b0c06a91e63b3bd6aa2..8625ea50ea052e09f310bab8745f44ed277649d9 100755 GIT binary patch delta 58050 zcmd?Sd3;nw^Dmt4p2;L+;e-GQB!ro;g?&>t5jpJ0B8m%ch>D5voFdzZK9uV2ZAQ6IsAfSLo3CME4)#uD40sTGqzVAQx^S;l*nXc}xuCA`C zuC89P_VXH3*VM?KC)AH2MLAhwm=Gc-T)ihk5~5r#XGn-E=QGD}Q6VFLYfxB9keQk9 zts7jk<|$K2I>$LrI(~MfIZruGIL13C zIFCDjbAB80mvc;Tdhop94Z+)8J6wldg|1_+A6@HGg4YLc4W^LgA)kkA49N@G9I_>3 zcSu3VU*cOeY->oW`q{Ns4*tn?Nn91%9NQiFj>ED*&UDOjkYl!EhU0JN7mhC-IgS;M zm5x)c0>@Q#+I7-3Dda21TE{xaddCLGdm-5&pN1?ASr+nvW1-_i$H$H(j#UnyW3^+A zV_%4Ce@KPRCt`}cn?puNeK#&OHOMWKWC;HgZv2{aU+6q33#a8=42zNAc4*}9;zZ8X z$V4eKGIQKfE2Nl2clA?=-b>MyTq`!^r3M+9UZZj|QRp=*AFHtL9U!b2X3n_oom{1c zw|kX#6}~MC>MDx8`>G6$Ie3w4&P+w=ps?k{$*_*xJH|keiL)^kx#|1nNw=_eN z&7dTd^zLrf)jP5A1J##VSd29jP$VquT`3$XTqJk1x1{k?VxsraCQoxBuSwOYTrQg^ z-O??(#kFGSsV22WzBjySvP|3TeZ6Tb@t*hdrd8#P&ED-z)BZb!UA@bj1^*9J=In0v zgakWsnzn4F#KxRKt@b&@*_;-4yd%Um?^o?-G|fu`*>2@_n2wRj#`bhhi6rUvsBjS$ zW~Pdre`JvzpdoK5d7Z4Wm?J8 z{5x-@w|<9)(f`1F?{gjMFi)m-sOQqz=4x+lhyG%j*SM>-Sdr7?u7^Z;!PPvb(2Pws z3v-rs+$3cBM>)egRg?Uk)FoDi&(`8BK#cJ|9j_#={w5^K_MPcbGB7yRY|yVjc~;A^OXV%QPZER;90Rh(}Oz-y^H# zws&S0$&5;V+`-_nyP~_Ykwv40e5L9FM#NNr^b0}BRtFY(_uLb29Md#H`Yqrh@6pF% z#a!ww<&)sbaw-3jE9zgl;?3(S@6P@SkSXt({*%PYoLBE(EoAY>-lh-! z4t@QYOJhbQ0o-ckw$<;@{x(E@Deguk>ui&0U)u zrN73sY;F5CO?L#t_~zu(LsArY-S5Z9%#xh8?@tg&<;{r^7jg>b)O6U1q8uZu59bVA z5F=Adyz{?|$(g#KzX&ZkgI<|QW_He*g}NYivv5?{Pcd~ zw>+VkB+Q^rqP5D~?7jX`15jE2<45ps+Q%(r{^p!DA1_d7#3p9s(w9q#xv{L27_X64 zEvMfXx0ModgteMz^@;2+l%u^KaGD!uj?;XdH1jT5anK9-%uR#E?(bD zT<~64KUM!5>!l4#L{?76ynaHQ@J`>DR4Mx$8iX(y$;M`$^&_PBts6DGr#IHAQovbe zYn^H4>qO?SIfE4NlbZq50uoZjCQIpnTsIZgIAaEi6whrb&n_fGY${ceanH#MhD!8if& z%K7wg9|Z&@h0|n8;g2j{pI9!Fy8z z?my`jVrkB^r{)UU*<04~*7;?*_{e+kmv*5a@z}w+o9~S|^OpF)JL60*k>xEqQ%}tC zhMetce9ZabJgX^oCKh-fKl@O$R)`1?WPHX}CCto+t9JGgaUdu0T#+Mo?u9&zzuU~v zuI(4B+bEN9A*cGq7v$}`bZ#^ADt`@sZrbl$=XNufzX7NpQ?57nkI5n@r$=#u_H~B* zd6%eSx$ME zcMP`39St`KxT0;EG+GCHrK`NBE_an_=W?1{nJ2^s@2vG1Gm2Oc-h$WQ$x2PeqXlDmegT@F^ z5AIsUyF?Q06QcMI%QJJqUzKp3I*GSht7ec*BiZeAI}FS34ioDb|Y%^jfe;#OJ+Wv@*%JCs@pPBKcytXe2KB=7bA}Kr$;rG!YkRPXu~5 zoi0U)XJke`4U80V{CyMOb{YAbQiGCN?`5yJ#2^en<7E9&Bh6d$s+P&4y^*3i<2i?K z!mVXh*|HrgT4niL<)*TxZi$iSbJ`d!YKeVxGFmiD*`lYS3T~d68XCVd z2|&TA#(`9zA#+9=uYID7_scXGUP_4hq0X;dK0h3`Fo43)y|MZdQ6W46dFTOzvx1qH zMsq8RHu2}Qg>^E?#ve>jcx0G)VpARjE0Zo%*1{59MdZ;crx-*ntBNj_FL0Ickh1N} zq{&r9^=Qjy*U0St7t`vhVk@*}bc|>oQLZt{jS&war*f>gtyZ}lO@@QSjj%RVF)w(e z>gW#V)mK#nFP7^bhnue_d&0qa&gL1!4Duw}L@(tMi5^59meJH|;xDnpH@><^6XG+f z5HH$`rPMuMywrA?Mg&1f6P}7JN~ywA34AgCc@FW<78w`mLcFK~WWhB=LN7l!x7>s? zmnSFqcw5R%QoHmtRL?9fHw_&>&t;aF8A@&{^kYhG-bJUW^PEd-t`_$J(178)DCLw##mW%*m>rm|L3^mZ-LL>4We z&uWPVQ5j>lSY&x5XvrM2g-+KJFG6KUG!W60TwBbRSGSX+j+h~)``)VqTW06^j@E@C z_9rSP2{#hHX7xo!{-X9vLS*}XY$#HMxJ*wq78$i}4wbO^^ard?*fJm01Y>iV>NODq zZj#rGCZb-9$%ow1 zAFRhgVNgF|jAPlsB*xPO8B0Y?%4jMKkw){Hin?MvZD}gH$oIy18{cRpKJZ}{$`ZGP z$$S@^i&+k_o6=i>5xadKwPF_RpzWCyc6CTxJgJBIfxp{mqnO z98K;aJhaUecUCXB1d(hdGeh07lcTkqjjx#*nrvp4P>oI^Mx^;}?<8sp)OnyYs4Soj z-2uJeTiIDu5a2>y7f~OdGhM_=l*#HUMt~=kx`}1@Z0IJw0B}Ti(IP<_e~vpht7r z{KdS!n7=m8BKoeo7$_!Cs~+Nhi^e;}JG7&RxD5?l?;)!Di7i0vkclng7-E4V*-R~= z7CnV2Ciq771hD}&Wc%jcEj9{qmIn3~^{Ux@eci3Nb}9l?W=W9l>ipiqEzZ$~-r{X> ziSFzprlZcDKB8Xiztp)vk$pvV@hjEuE4HKN#l9jNsfG8z^Z1SO??DfKqZ<9ha^Tt3 z5A(`7y3kKl`!{q&qD(EljiTShc7jFl=_hmHUezdZI z4&EWDXzqY5>hKf`~*nK4^0|V*1;{{O_5n;OTMe(5V z7n2bfBjXaSeo^$cp$_hxa0dZedAR6X;UGKU9tCbuhvA|&NSQty8oG@-y(DT!?a}@K zmOnw(T6nK-)JvkJ5Z}|a2PY@G+@e>63*%=@qLE|7onkV5IY!(+M6awc zYy594u7D}9jZVsc!j&9al_7C8BFCQzOiNCHLe{5x# zj$|W43rsK@$U4 zKti+w{QZJJg_~+;h}Qobn7kSPj%m4t$u$M*h>w`Q-QpYH+^Hf~%DnO3we365=||<$ zof__@;s7W%S35QhZ#@XiA9kxV=0 zYr0U(2GV1`9gDFkmOrVwQ?>byx-{fVn7@N$&U3_)$`Pr^Ov@3^ioL$GIpT96_R^e{ z;*!|q^R5!Tg(#tmogHw-cZ<4IeKmTRPu*9GyJG%i)k%cP1;Z$w`JDR@ZEho)w;DS0 z2d!O=X3x_p{`^JJYhcJPQ1>-(QZ7(bxU&~My2lYErx(+)y`mmPekEeXCaU+9cvD>U zE&56f2sCWuia}^|M6T!&bKc)(B-)(%1-BV)?wL+|bFt<)Pe11(Mi3|yUZ%_K zW$LV&Nd@b$9x0*!tQSwAYlZ8@T@MG)Gds^^bJwbv<OL64-tV2)HqK(Z+-5epYud~Qh*po8M;5! zJgjOr5E^D;@IK!ts#BMZ2q0OBYv|Qyn{=;?&EhVMNx#jy^|$cpTn_Enf9zu@(~UzK zrR-K9!C&_5OggjKPkt9a$$m-l6Vs7ATjBgor+!;S7g0nrx55X?r=qRm_8JR{nX|FR zY@Ws8wN&8nKbAbT@zH|Wliu8h6=V@D+$I`uuXb+}HR=@c-gRs+1aKbB7`gW`uGpS*|xCi?6X1=Hvw^vgV zX;IQjva4jC&>IFf=<|H>yz>UjD&6xwLXd5Ch-MLad3mW;#4yNQLa*)+AIeQ>^vw&Z zG4;rKum1QaK~P(zt7Vnd&M~0KyJ<;z1a~clTqJ_Xn-s4xqNn9>$e@5XLvJXxTNyj7rOb9;ff$Fde+ z7JM__D&UQK>@>XVYkXLIB@og+{k`ZcW>BLeSigJe=n;SFManD`*Tqbl_5<9{S+wB? zF-Z{JeH0B5d5@x#v+2-LvD7`A=?-`CZVfo^pE%9H?hGwB#$g0%R3z?mB8~|L7)2t^ zf{aGnON+qO_sBSoHR}7+=C~LUvw)o}j{b2t1`Ze&L%L$#anU4BXF|%H#$@e72brH; z;V}<3b13vjxc+(wE7R^D#eLwE=O=XS0yVFR`61zi(DMUEpbNtKubqHao~L;yL^H%4 zcApT>{*z32Xz)q+@wZ@YNm)OO7BMB3c65!9kiIYU{1I&ann}O>jCt+WcI(lcQ=(3# zG9%9$5fbCP{}f#K8Glga(_)ot#ujGgT-tXUT*;-&r(yp#(PO`e_zG7yb8kb;v@JCD z7tuS^pI%Jg{UUmzl;@0i28Q9CGw9u7T7CxMxzDNSjHur1G*|V6LmFg9x5P4eg#`eo zgH0@%nd59TiWkBMGK&^cgR@xNO`&JcVud_~em*N&vH-`O1IioJ=N!cChHuC@c(91h zIevq2zCvAo6FcCsM4lHFCH&lki{h+9rf#LMzYsmkgzfrEyk30_+dgyJ8tw_lp%B@5 zPp^T-&`*DfyX1jFs(ne+lKTs3#4cw%J#$Gs81l6~ca&d9n=gr8^1DT3l!(ru-)N|E zhtS1uoY6GA1nn*>q|_48BKlLdn=naE^SeddBWZj}TT4Wu+*|1Tr3B<6%vS#j7L9q- z_loEih@a&8=3Nn$1a%d#8d<*Ge~T#bf7>}F8gX4r`7g(md^o1GK>UCGXc9Gd$VT># zpPVp`a+^Do5Fq@*Aq^~reVd#TQPw%s+$DSAbk*A~`2eE0yoYeFe~VmCja>anPUzO`S265GGB428GGyk$P`3k(-6{o<$#o$v4WJ7?V1@ zBh92_V<9ySmt~KPY3L$)G+aLFUd*vxFO=oEgUE6a3{`$swyjt>1qt_ZoH z>JtA62?SBBIyj<|Me8GEO~gZvM#x4+dOq+aLSK48d4i0CR6SDuN1UV?k+QisMcX3f zxoW?#jZSpHi0IV;463G#RLtj^vaj`d}U`=G>LNz006^b_G-iBm3K=oCT@?a;n zzB;Wu!I-4n$%b|A&|wzggkh>M-lNDU8H3rOZj>C6l*fbSpJ)=WNav8mh3z0&Q(J6J z9wpmpLzGN#*>Qe45heS|3m?+6Rpl_ssw8W7FWAhzbeRGh-TF71KFbn@U|$+YZz+Ln zr(0nGhVYEJMIyLn?4qz}IiSjxkAcj8DA%-7E9l|MvIVA=jLNbm?TnW7v8XDJmi_)~ z>AYH>GIQ8Uy_px4N8*2vf5-IUE)}D^4aP z*e6dpEcXuwPpL3YHb7BVJciOs?c$~X3;@oP(x`a34(M*LAs`7(S03HVUp;|d z@=1pViSjeCj0zHEqc|;?ERo?Jxbuj@doY%KdLnfK)vP54iPMx?OI8+Vd^2mwvFy*s z){))td7=(X;4yl$j=a~lsfbBgwvvw2kv;6Fm5%O8#HyOtl~oZ`=}}j9{-^Tsw5qOb z6z(^~FrV4gPu_jYvFAxuvh>z%Lo0NWsS{~eC^bno!B*Yp zN%HARS(XLm1-OB;H}%yLs$E|m56$M04{IFcCea`3qS_6l_x{;M0@PF2Mb9NYSVdxC2*zB;Y6`)E`XB?K9y_v!q%34w%uIW$X8p=lQJRgui z>&vvFL8#76tfD;)WlT&e9}xm5Pn1EI=c8*4Wlf-?ghq0RT(X(c8_6T^oCi0StubzM z8_U-dR%xM@=3am38gU8CVy)zD_*|eCP2?`Il5RAS6H#$uQ#lZ>ulrfq+jm8E4C)W%^|L{YiLo@AH-s&yE6Pm#-Iuq2@m7{gkXx^`c&)>6x=M zuYgjXl6T|641N8S{IrHOyYgbw4SyymtP)SYR?^6&cb}FYiWSsmu>2BYes-{&is5|Y z8347IkHNgY@L4$rc^#gECgfAt5ZS4@wKNRw8sWsLJ6fNY8f3;`X~^L{rycS_tO)CJ zkX!(9J5QsB$TmRf8zQgaGv*~(-M8m?7Ly`urony9r{tkfpyR%oLuFkdex%%C@<59v zY-2nQ%;s!VT0w=822TBLCFe4JecuMV|NI*&zVQY5kC#d4#tX7BZj(@x7iAs)brO2y zMR`B$|K~5twqk|vmlx%;BEo;8*N&2PQ0@3ASgkRX@VdM`cDfF(8G&j>7~g@x>^SswNJWt^;Xm?D zp6(udLvo;I)Elx75N&-!cCll&5Wp%Re2*3Ymw70Y+;7SnVj6XLQ`U^OA_5{J+9rlW z0x!KOdjsRTH#NqBH_@|vYV?-87x+iNC41b`m3V(wxb}~4X$qRXE!!sQFe&!v989z@ z{EfwzX>9V_vd=9DYnDY={I(h-CnCyItO2vFSojRt<+r_)|U5ZSi`KkV#nzScX zR!n$TZv)q4r(C#evTD~f_swDT2{pf;LswJ5J;e9CvN3fZBX8r-#M@)wWR${^e7}u> zNfXCu)i~J|T)R3>W+F8u&7bN`HOI?^u|F`~i8c8IY60)?*5uhO**NAqJf0P;aMnAr z2M#;*d`EV|ESB+(T-N)0-PPcm17H8`pc!PZbgxpM9m5f3N=wHqS41DB%R6v3{d69tDBH>8E#P zV@tONuo)TZ^_!6g=-B%*BG_pKpwD|{{9XQPKUhuKO<>)Ey_{m<(XjzPn}5=ss9=|= ztfSb2R$%Wl`vV!`v`pmC8M0ahzm9I0LsMtS>Va|kK9jzj0iV@Y%ce7B#m0d#23RM71rFx6_LV&`F)2Z%zvUTTx-m+BtYtAUAw_1GS%9;)qAAebx zj;!}&6?=OTvc>Y^&n$0*u)QeUiDhMrhs9s^jF(E@!-PGV?tUNBq&1$@ZnH#QeCXhd10@%5EROv z&2CgR8n-|u$i1^^(E?cuXtpoF3hXbsv;gksUv%3-gk8tevkT>;mH<_!!i89umQd?O z^2rK*oR|w1E|Rs`e);7pBx#8&+0agR*{&SbRQS7sy}L41!lF;wE_a~kUlVm$ENevn zbaMudX1usqK2*z!40!nGXLKtzN?B#FU%5f8gc9A7@Y7+oTY{SZti8lXSE*&njc ze4Gw_1n4PW)sL|($NJ&W64^OMw+2Ii1qN=_^L?_4*t$GH%|4MoilY?$sjkxhQ?7E7 zqL-nHrW{p@%2oN9URWkK;!Jka&*WJwALEwGPIAUHdSbaeDvnTx&t(U+{r2Y)7b(A? zFSBK>cs=;;7`xplQ;dM~2^C5<=T4)G+0sN{qU9H|iJUr)p8i4(bM4~Gl*TV~=nFZ> zRcI&f{8A2fohwb;M_0bYta_DdSUQ zuavPeeIbon2{-quZ{|vNWUtZVJ~+8{bGHFAYaO{dSkl6O}=_W{Z$Hx-D-WH{W2ahtn7ps=-axNG$S z#x$SCtc8G&p;Lf~vGhYP$|3j-!XxVSO-`K1QnMI1c#p1&k8miQ?~7 z;i&>ySX=?|O#|C}>*S#5UAooAK|!9TZZLVIdF}&xa6J|bV<~mLd#^im zh7Fkw@Q=sRoDFgylm7Pxgk{E1Y@XZ~cTjiVe{a*tVuR-9R!BCkkz*rPPGhl^C==<8 zjTq-NoHBt!KbA^1Vx2dR5;j4@#!$aa(7rJ=a+6GPozCKZkEbh}WDQq|PE4SL%`(xI zt~oNEx^I@vT}yOg0;M1ayj{3i9=e5;>@uWyx5#^|%+{6sw?!=~HF@z=WvlEC@*dwR z_X1s$ZSrxMJ)Nd(L&WDAa$!e|=otRHe_Q?VuaqY*zT%&&b%oDf zhzJnisT9UTM6W6-r!p6?nM6L_!HYMi-JgX-EKIJj}<5o+=+#AOQ&`d0p&a^l@B&>W5#Dx9^0Ec4B$6O(M< zD}Y7>pzoJ~h6kVv%Rs{d&`--iD+HilmVt&cG+sCNRT*$dpztO-en?hMIkB@$Zg3!X ze;KGV0R6rU)DeLG7=YsOhu6?2#aa52*NHP_@{HHa8)5|VWodr<-^&yT@fQ%K1+J9I z*R=zv$8M$U?_^tfViKMG4rXB)RVk45Ty8Ti4JpFc5hR|n6rHRA2=Z|70K1g>TllMhT)3O<4W+8Mii#|C9 z+jWq(AA{k3kERvLSl3#P8HC+U(~4CMntN1+($pfLDlCG=t)v#m5m3zc6&y#50(T;x z{|U1~vG3$hPzc;rX>t-V(o3}Kq?`>e;F+K0JK_iW^=Fxijp`Rqq3b!m<)>hPaqH;% zY1lX;D=*Kw3NJ7N%h`FpPG@9IN5W-)yc;_MZ4wuyj)OiwH`tr2ZFWWA^vl$sQ-AUX-`rjp;qf9o*>#{!{V62oB2eW>at{*Er({ zH{uqJgE~e8OFVtj$@L)G$=8v$%614%-&aHt(-TC~qMX%Jgc*+@oLM>9uvRz-zdGDO z<~Dt*ud4a8{`yxq`cxl#ktlvn2Y6AO5BB+s7wY2vr5E&@kERK;cCvXQ$T-e5upf8eg)q!0Gqy^2yT9c zj|N}DK}CJ@+yi;in@ZC!A?TL3ind>pb(`wVr9`~n<92pSn#*k-O?m|Py$c<2puSq zi_o1>mt~XSPkA#Ukv@M2UVq+Y89gwQS&;}hic~4_Z>( zjyN4;cj^-ZI)_WAGamkt;iKkP<+=EJ)-;;Ne~HK=Kxx$ zH&nc5+)#LTK6>(R`4;B)qkqds<>C+Nj;peI75ktFvrF$Y25CV_zA9Tc^07#S8H;!_ z1;!KGZ+D!LclU1wP z06u+^#d*9k$wILBB8|QV!Tg<;Uc*x1cRF%SrZv|kSat)&jj!x&QlQlDU9o(XzC}iv z*R36l4cBGOzz)XA>)1Ks7qMcg`wdw$WyQ5T^uRu%;O_&3)s-ZZOx-5lZn>o;>$r>6 zRwAOVx_OSg+WkXkJp5uvKEJkQ9AY{>dc0R~KRw~Hqw46!9!)noc>_Du8j40MSlpS} zNHwdkQCbuPo=?-ycKLe?^qQA5$E!QUA2TriX3)_m2e z>YxJ6b*dMU3=3A%s%GkbDW)i$trh>sSWVf%s=CaYLc4<1*eZY8)`^GsvTdDK(9PXXo^Z7Eq)ODfGau(kfIXqRPXe>bbD8RDT_+Ue-_S#nU4d)WhL^!j0tG zsorJ{dn#r8L1AI41*q!~h8ZKBGQ*uQ^jesDMir)Qqjp*GaMmPF2AWCCT2c?ZY2kSH@Fas!%tB3Ah+lg)LOoT zPiksPYO2v%4&e%X_^g#2fFHO9h*9b2Z=Z4Di3ZfeYTqh{W8ol{GeS(!4fi)tzrHdO zLt86&{-I@k1XKwv=OTn?7XdJK5mNryM+o%<1zHdN*i^42^6{id6(l64aHixmTn~F2 zl}X`SBu?nsYQ61R&D^KIA$AN*Z@Y423tcSr9@yDP@mDykEBFI(+S_37hb#Q$Z-Ctm zfBCbzynk09;BH`F*Sy8w80OKe8UmT*fqlJO1W+f*mmsq*;yaSXym8R$9&FVm$u90- z>!dTEc-Q-%9Fx7ku9S6nLbR5Z0ntZDFd2aJNM-V=Hf+~$2LH_DU$c3W6JutaWzk{) z_1oi!OYwUooORI>8@{{UTT6r%h%UecgZCId$QZz5!2=xIPfGznuLB#lkcQz?G#<93 z70VEx346(`gb7>!zh@kO<6eY~w1iZTnsGISo7Dbec|?mRltWa$QlJjDl9?}vJK@;~ zum-ZM20y<6WIxb`Bx*8J;AD@ zo=ZI#nEGmvCsdnmJnI7=GPoPl>JD||CBC4ySi}dRhiF`3CkWyXwZi0q%aHI|JeZJi z0J>gHeL;LCauGL+F^nWf4xht#7__V)X`09zgyA~QpdCA;X=>Y@Fmoe3; z6E!E6@Nf7MbNO44u3@wJH_9#MF9efWpT@s!d3Xyy7h2$vw7^hRQnHZC`79Qx)^Dxp z7(#%x;J_B(CuCO-qlvRHNJ4sytIT%m0vaXyNv4Qbq!6SS$L# zP%%$sSq*C2}I7^}qE8oMUCW zA0G@>KbE>SiNTON*83zJ;tcV)w7l_2Q-~H`MNw`%h8g4ub-P&5Tr5L+F82#~ik5Jw zXE^~MfONE3O$$3Lzj>L($SmT2HEa-I(o)*INt&^&w6G@_DZec7_#%bDZ^csterjRS zVrR((EU%HG1w#*&jj-td9YQ}oi!D%R7c^4yL%2i03TtMO9W^K10!n5rrkH^aPq1a6 zu%_V9Ev^|ON|G31&Rzx|g=00C5hKiVOZ}+`_~o#&`U-;S!ZPFn6VLG_JzHv%8e!W| zzxpkitQn?7-qE^FIhns%|7^7^uYX$JQ*Tj&GWv(ZcK@h<5t#De%fPrm91y2)*)pOR zQd+dw)Uj-(3%{if=@fJy7$HrOf><`MqHeN|+{tpnqYY4bgPfHS5E8cRs8A{a{sF^! zrQB?=_vSiX{;`r@!BhE}f>sD|{Lhx8oF&sNvy_gHKUq?$Eepi?X5$4J1ruJ;A$?{- zd1{N<^(~#d0@Yc@bq}kf4(4Q@U6KaL-j>Gp;AsLU-uR!|mb1q{oAE!l*=Z+SGJRnQ zvK(Z!3qm`=GHZv6#|hGmp)?k;wPyE9d7~1eDeHFWaKk(V_26UYWeQ-zQHOUIZVJ9RC0GJ@77U@$=VffK%522(1TEyhl>`Zx+2oMcG8`u}T=rcm%?@29 z*;qXeWN1>e1w3FTijT%|pTcQs6;(YV#LWLH4+f>aZsXo&SY+?4qAG=EZ3PskjJ$NR zifV&MLqb&*i!+?9tE!1&rEg1d4N%UaW7RWednV$~h?UKu*?a0i(u_#T59 z#+Pw>S5tS$)HIq{4fEm!np|ByK*Osm9QE-;4%ASO-x3d_jtO{OeGGk{pdPF?CXM@t^KY8Z8#f^6GbpS5wDt=${0su z>#7#fX`K6++X1)((NHzQNTcm_)qv2ngWTVW*mFp#r(TSkU{h42n4%KA@>h6@3m7Qy zMLpFqP#_htd#gYxhVUeHFP_vHn50Zlm6fD$Jl%IaNrlSly8`2y=PWa>ya(uMsBV|o z)Em%HWki&@qGn8|iw#v3Y;$HGaNG;-Zn)y$dxxH19o@j0uNtXZQHK~Ud)<69Q|~A~ zdCE~81B08{gXx9FVBSx@RgG1BA-xCbcoP+k2>PE*)Kh|d1DdKQWsJo(9HNNzINka* zMLdKkjrKNIyu0*Ub5*s@1&b+ej8CQ-#wRX965-V13(wE^fpd*Qs^3Dj!{NxmEx?3H zw5o;j;Vj>aE!BVUIo(qAmbu%h)orSa%-TY4-KKKn>h0A0cGWf_dn-=~2-`?=^;Q~n zyGp8+w;iB95jgA}A$fV5%W3W>AVWC#4jh|qR|gmuwNfh>SD)6ZvmY0hLX2x(Yt@9a zzG#zTUIf810WX5AYpZa2a0^{- zt44A9<#uX4r(4{i(kl7s#T%V|dh_qVD&jEv#+bZHNu;>;Dz2%YS0$nhv-BH3Pz<>L zPCq^TR1WBQroDQ)vONlJi5RQJ`Yqe3u)S(4_im?{JC&!+UhX8ithsZL69{lZp?hKR zpp#68RaHM4l8Cdui2mb=pgUE|ltNtrXA*$G4H4qoMQoBaoCoGc%~rCA3jrPi51xh3 zDw1XWw5Y~#*~!)X#T%XFb2Sv`@~qrlc5aGYT7!|DZ)G2}v+dFvirl?cZlRrP7uHbZ z7Ff9_>|DFBh9bAf$~|Z2+J#dz7}=+->|#6HF0G-+MSqsrITx&gSM8)-PeW1A&MmQX z^%qN>ZJKGOolLcp_@*$cGD_2&Ot+Kzi?wq3WTu_eU#x7(CuiG9{l#dNPiEOk{Y95_ z`=HuRFR`=rx0PH`KACMN^%ui!Cs*?q!(BdCLxDNZ%H37Iu!bTxe_M*Ke9+FeOXu6k zy;kKyJE_5~q=uq$ft7ord|?emZjqIH&d#+9YbbJ0Te-!${nFAJjN}C?`>I{pF0G-+ zEwOUbET!-l)==c8G8kiO=h}re6uIeE?hHFOHN{G6FtRhPOy_<4{^M+s+9w2Vkzj0CU<6D?uZ=scN!d zJDoOFqT99~oZ$}Y36rA%NW$}XvXyDjNi~!BKGsRyQO#}x_V(Y9e%(pc?UTjogLNcd z*+N)VF5z@*$u=$ofeUview+Vdj`;0iT^?OQua<444xQD*wo!JKHOilK4j5$&YN=5U z!Vz@L5gLY_jcjf_*~5*)DF4w}4felTg(!nEnJ4P8}Ir93We24Q%B1&c&Hw{^LzYTZtc3pVud4i6?q%-wby{?cM7 zQ(%bzMgWm;{EPaSi;}x3cfA6}%Wr`Iaj7H)svz2=C5XQ0rm9v8(8X_bp`L!uOHa2& z-Be-=-3s>;laWO#V#~Jt{R<~<>#j!B&}b3U;`@3IQ;jfHcME|=y{WrO!U&!1u4;I6 zZ@b|w1Ql>7x*-mWH$k}f(#W9tJyb2jMyQFtt?8kf)zx5rlZ+4U`?V~<8{T)-jI7vGRe8|g3IDJ$a{SjGOcqL1)Z9*V z^x=W_%LSqiNO|n0Egm|}`aP|w{*9gpc-i#yvB<<;n?_)!;5GqwB8-pYT9mKmbfPk$ z(%Pw#mQw`=1)M6s#!$1nRb|_a^nmv)x%b|!n%p|dR!1R;v+q_1*@=3wmwKj>ZVFDH zKtt^G!LdBqOZ5ymSLmSMx$4+kJR!w?2rN{>8*CGTna&qJzxxPCJP@q&tBI0U%yA*^y2)8U7((U?u< z0L3?S2MkbSaWv=1096lnFxA7VU)U%9=J4Rgz=u_%%Kl5Wd`8M#yqRgsr}+=7SCf`% zO3*3ZX5%0OM#W!Rl$I`_J04M^@Q_K)BkB{p_Wbyx>K-)u*`um%p#R@Ls@g|>%J{gS zIO&1cEo(ofZU;{uc}#Wl%+XoG&_%3Bh677Y1T?UHhUMc@+W44i*VW=pv=%00X?|sE zD$Bz@(=b>?2pay!8((2|3f=DbxT^D978kjN?Nv2utwD>q$Hrof=@#%u8eA_(cGOK^ z5)mVKH31R8{gJXf-u1Yu=bF#OgTiRuLx{F4rs#pHcBsG0xzuqWR63WE2dW2g`Euhx zg%hK;-0XQqwKIf~%k?$qaHQsadf*9lU#y1dM=DvdOkr%)G!6G=^{7n4CWuF=$snl1 zW*Ra`#XE~X-iqg?7G&Xm!@NQ21o*CBikrtT#a$`$b|p??S${Xmf!C~j8=g`#_<5E4 z2djtiSw2`T!RMi8RQqWCJ`IE~ERqRh!JR`NJ);uKl5XN%h^S}Pc3dMn{H*FK-u2ad zPW8f3Uf<9mYL$pN|K&zF8I6Nr!8z=SWf%OB^IuY%Wc8>kYfm2dznk7qR^#eqTM5mE zVC-HZsuzsenA=6~^K|Y$>NFHD+#T@!H537nD*J7oAxzJr!t2-9Yt zXnkHt9PdH6F~~SVUk+F8aZvdDaMdlcNK>G23TCS{ z6qbk9h{vQE<6k#Mnoru8GC^{42Ek%+4m(dCiYhCfa< zUsZE(>}~CkXUFa7F@E(@k6129oB`fhOJvs`M|3;%m zsd{mCIqaa8ma`JS(wb42e~-}LqtxRudL*okbC-_E!QTi5i-*4sZ>Eqb`Ny6zMNZ>a z`s{V^?7VN+>*^JWSXak4RSlOv3N`pm`mItL0INV~)R8?uwo2pf4c6nIIn`%dB zQF&O=TWTndB=&w=9m9RYMx&w8M}1vKs~<(!c_xa@mQn0$k&4p~HFO{FQpX7MwwFEF zBvyviEA-d+-S_esB?KNZ8#z`r#3ySkf@0s(?_(hlzf+rW3TF&y#W-~vhvm+UQ}yeu zVEORN9VXuC%3NFy#IrF9w@Xvwfg&$W-4UH}ylfqC$6Q8{<5e*(GpY%o@i5h!pt5ip zGJgWLvI@3y>D_dGf@%p^t#?!{*J%sp>+=qD2kJ64U8~E=bk!1%q^d~zD;;ysHHw^w zZlqJYiK;eIPas9VPE-cHKT-9^l>6I6^_y#UzJ>p%Nvd{>Z<$|RBjTaUdDzEHvwM0b zXwV2ev@27EyV7kw9kVhnXR1F#SKGmRTVNyJRl9D6mAtFo2;E!0+;p#M5qi2j>_>*> zZ^KxWDnQE_fTfm)rTqi!TZYXpTkfYa=&#OH_lIV3w*r`b!)GC&>)QJ@fDo;(W~+x_ z@G8Bh?ry$f7ht^Ykr><)OB2aw6wPcu5FXF;@u_?O!P*rIH!skX_f)Jal}i}Aeaqg% zrh}`&4g-|Q`Jgaju6hikkvv!R zuawHjlX+Vc2Piw;^$rZsp1JBNs6mT)Y9OATojy<9<4WCa)jTy%b;e21hVxZdSEdac zHDA?tW!Z_3=PQpZ+fMA6kL{Ls>D+uZ!j)&k27jQZq)i`a9sA`2m537IS?ZW;uU+Ez zEcF6pZqNdV%tU%)foklw6^TtVKhuQ$OTDgxGTHLAeSzw2D+jKDA{$}(e*Dk(gB<0BP9`xZlo*DX|G7%zW`FuJx-xuD>;7ON1FC{1am`0>qJ zqz2%U)cHlg;zw11?pdsy;I1EEMSA2zRojzq(QH^693Zj>6WQBrXUyOV4e07(6?St+ zI&zsVXt;bwPFWO={!qE9`LVJ&LE$$1PkW7Z{)gajCT;vsHNa$i?n9UaFID?UO{=EI z&w~T?)}A#KT2qB*r8a(~5*z9f<=4#}cmxcRi?gRN@4K}D0`??CZ18E6SiyLgB0p9S z$Jrz7FW|Nc)Yb*Yeyq|1101zP-R~;0M|;>371#X&Cro8)gLb#fKQv61gZ-&t!Vd$< zY*YX8*QVV|R7}cce^RTKh3sm1m|gKkd8kGKQtXoB%R_a^^ztyfv@JZCQb}pQ#4dz{X(5V8`Zyr<(ffzztplg^-EQoVsey$tCl8jA;Me0pFAp;4>gU=QQbYO zEd|#?Wfg{_+lIZ}_O?Q#<^Wsh3T)KPpn5BG0}O&YSz!gtPkG=88oolsHuu{TwBO8X z9^0?~)Dr$$7w@A_rqwHeayoD)iiw}Wa?0(b%0xVyz_SDrX%c@S?p^Y(QsV=PQ`3iC<72eO2g7{Y7rPq1 zE81_n1{3XGTfSacgZ;-{^wK(2jW(`PZCpiG#;mX4x9_DpzrwlNJv9C+jNl>K_!V{; z_b_kaOjj`L@aZxRIpM-%B(@yKAK0Zd$n(8{}Wp$+fC+y%puFq~k^v>ZBsv!F3|dEq0x@ zxhg5tPtZYMaxQGF)z5of7wo?0uTvw?$9C(nsM||>)~ou_)*=a-b6P9WIbr@uVH@BT z?xm(1R3DkTmqu^EY9pH#Z%}+!y;&YOQ$oY?)MHTvzi@ZH=I)q59>Y9Ahx61uQMsqf zgY&8FMzvQSE~Kzc%8Z(ErhIl5J-!J?5th)bP4Ih{QQ;=ltkTs}!1yryA?qXs|NMmrYg8j*cnf3 z3uHtU1yB{+xry6VvMbF_c(<$DUBxyDd$y~NAj`;CHSuX?efn9Sm-2D8+3GZH&&R=X z`}eV3@UQFdfO6Y_p*ygfZ~rdXf#~lp5<6kk7TYcM-l>TLtQ}q73947v85O?{WJKi! zsNQ1d?*Cf73bag(YcF#-JcQiq;ehO=^j)Q-ft&zo&A-vVpZEqY@?P5i4diBrFLF1Q zwQ+i!_z*Dv^(2^Yo}}zW2EDom-9Jn-tNjhj_L=Za13HZgW(7YOo8`&tDa7rR-SB} zE4Z0#I`}P^J&peU7DjeF#UE02Z>|MT^p;wK55XfmOzRG*`YD;3F#X^$^Ruykr3Bt_ zx^(3f7$SIBhGA7hxJdh5ToQ5V?Rl)l<-3){?KeU${876p)NyOh0vcFi4f_r(&ZG(7 zsWthf@x#OGvHxUNxwGjEM?^cevMoR4c4jkQ*`K@!sk2HK_U#HPYL{oj}Wu zK#k_ou_J0T|1HYV56UMZappUVN#Ioq!fR1KsODk;b^by9c*|cm!(Yj0;Ctezijep- zoG%o?zLd8Q8kGC$X}DAvLE}}{-~T1G2gpRsG*VT$Dw+%)xBj__r5)$KB<_`S5f?b zRC#ME6}56UrQDM$Onwf@Y3)p>dw*6Rh%e~k&uS!`@MllqR?9c!K8-+Gj&Im$xa49L zz4wc{ifeVV&Zw?@*X4&ZDC?v9&Z_TlC9=UeDEk`fi%;{fG>H!Gg@bz$6lSbt5Do_7 zQOqO{U&r8=txKQ{cum&G^({Q78cDIvx92w;R1@pTb5Y%ivvE&cROuOEntkJyw>Wk+;lP)}p0Z_1xa@*l{{{0qA)lPKX5rk#B1km87; z(U))wX#@Rw35Itgg_Wo=l{aau%H%~J?s38R;Q`025|tFcnR7gN1PBX%%mR(TDEvt> zxHb{S7W$#B(}TarfxmEs= zeKK>i;)X~fo155T3u-5pC-$WpYtf>XQ2XvJRYDb2FRATNDIry=_U&qIsil^pS{hnG zXcVOy>g(0oexEbXeUf{FzWsmyU+u}o+iJ$Deq0^Ul7_gLd3m=_%MH?@j9K#KNwkP1I?g7LFqNh(WM>X=k*wqgVM|e2|`e zd*N52xp$fo9nZu>2p$QSwF)0=VuslPIe){xvwxO-qBTnj&iu{)H~*9VtqNHKA!=sDGF*fmr+)fr^pLQR51C8j$ggT!|0 z7H7HQVhZo(J_{DtwV&Rb-?;SvoG4~LvPM>Rsq@8o5)!7U!N&`MJbk|G0j+RG@Oi56 z*jh5sP!JrbHO83Q9fe^7IyW)OI)A?2rgF9OTo)x+=hfJ5m^g-dW6lNIJ5*$j5L1*N zBtX@I{lfgjo?-J-yC!qBqPo4NU3`yDJ+a397lrf$h4kHV`mBeGok+PbTN}hF+gde; zn+jxDsVL$If(qnGR&BJKI&{e8qpMVlSI+8iA(&n&CE9>Zom5K11?Q~BLJ1xv01wmDPoB36o%>4(yA`iT zPnQ<2L2$ZC8F2>6b9?BrAtjU*|MtGkRl@Vk%(7xo>0JH*`twtL{%H_!4P-~(#W$K* z%I{L^8lt52jB{E!A#G5$Fsrf{Ydy0lP9%UVG>L=c@M-6)IPnH6>%3V-e1y;GRZ$1+ z)LL3xO{9PfmVZf<4^2LT`s%20ZLbH1)la-o@ydmG%uC_}vx+sUql))kRXkE%^!&Rj z?om~Y&8s2}sv)jbOl543;gSHY!~FyC!@f?>6mn!=+5a^{6FUx+|-m>fBsQ!0gQfdRkksUhMEX;$OZ~r=t%3 z(S`=;DEgv~h}LosP*xogF(6OXy0VCBR|~%Gp=uo>4A2jq>tci;)pgzV6YWjTz<0Ls z&5u5__$LHsDjUC|y^t8%0kRHIueze7L!~s7Lolx3QVb$VuIys9oCD5fbwwd-&0CyH zjJG}+hZg8KZ!Dou&&r;xSwZ7xrC3Z7@N`_2k2ONai}UPyA|%xF#d*%BNTV0mAAL$i z>WipC$C*h)l~hfsUVYIH%i;U=f$p%STVK411+i+ph|m^)O6}rBbrOEs+~-fOqy_~i z*G$#P1tu3AY9KaZ+Kg?8Y2&VaA9V)vSp^=#HrhR{WW6|UvSk=4NSUk@@ zUl0yIlyScPx@bo4UGpkQY4O6}x`p%|u%R^mOKW-Pjmp)<1Q52t38IwCq7sgZpks?o zFtGgoiUiTvJLP@`Ch9V}o*=4(D1%SHC>Hdvun3a_d_EL=#WfLiN_j~5bEhc#O~y$x z-e%fow3MdctvV}}>6fUzHn1qNH~ zE2w5OFz|P%OEXa+Xs#;2i}&Ldy<+*HCL%202y;f!kA!=YBa~Yq9&k^MO;_ zpc~U@Ya1~>ub9hIMAh(5xW?dbe8CrirDa6Kv)-O zTuFOh7CTJNbY43F^McI!>kh~Uf#+OU;Gsv6>w+J8y!nb~AMUAg)3xb`xgti453j4Y zAC8BjI8{vq37bQ06Gb%ck%lFr?^0Ovna;m3&_5JiXQ79v5cpefggFswz z>G2KGkJhvoEx@iR8-hL^MA5=o>p&BfW+jV$a2>^VK+m6|E*-?X!57)+56epM>vfwx z5l_nX3t1F6PL?VdHVHk)VX+-WZ|!0heb7;aY4~2vcA3LJbhv;EP5oL5zM;h7* z?4H_ZDDFF!zU?GdVJRKkSyXeg&A73%h|5#4o=U%T#`3$LYIhNB<_G^;gg)$o99Pq( zETX}dzdW};h_1FjYiY`>qPM~MmnGk>qPma4{%@X6 zm7l^w+sLly^^NpzguwZ#E0&XuRHU1zX>JUtO*b)Gdmi_{#aX61xc~Mq=Ki;G#h&H< zgRsRgxc|+n4PgA;xc{xtgQfu==D!9ywUG+HF8aY4{W@y46|z;LD|$GrhZqlMK@WvK z=3cJwqMo8N;&$mNTEbc0Q`81UJlhkK(q&@0NH0+o!ynTN!?<=jMM})JwY@~);BBfR zynFB(6XVOf2j4;VZFI7i=mD)omEOSQ+DJX#5D}ppIVPk5@pLh&gO`*s@prl=I9x(D}#yrBY>!P6fs*FxQ!`=XQfy(xkd zRGiV_$}pRMti=H)(C!)l-N73o2l`$Oh_N3sY7DtK-ly@Ru#rLriupK4{d}OR^67!9 zlG-5AvyIY+!eX0B2}(4UwOCE*t&awC!OxP_ht}Ak-qg6b;3ewl%Re3~9LNj~vy_gJ zMekc<=)@qrDxG$pv7k4hT*Z>$+aBkC7W?-!Y^0b2ooJ6oVo5$p)kld$dNe{5 z^1liuP8Eyehoz&$1*`UhVXk-Q7%~1?8@&2h5rP=XTrW);E5-+&RTT+GDU4fCzC%9a zfTx*MaUAB#Eb2C1L{ic?6noyebeyPYg<#y_e~B@9YhuD%qHGCcP4rcpX5(CoaWw$) zWK-S}y}(CbeoM5Ddx6PawT2r^1Mkt3$uRT_zDTDt@1jI^=7_3K12O&8 z3Dzh`v)-h>)6sLk(TeF}9=>YKFuyuF56u9Rgu}Z_GZo$!dtbbdUF^s2gAKSrSKr4z z@ixWI5^I~^X3Y5;6O(r{FpXWq3Vs4FzwsA`2u=~v!EF^9~+(n{aUh==#?*X+Wu$cGga}ph2h#$oli>B89IJ+-KPq@vX2}{5dKcs6* z#5L`cCv>O12=NOxCY->0y8}f@sFBZ7(aGTW8q(6GSkKOob(v^L50`>2xPHH$YW|L= zU8&e#Z=>u~92zJ_u@?jjz%MqWOCOu5IYecWwoE0|(?mQyT_z@*{(F|288)P-G&9a7 zE%`t6N1A|%%KuQ^72@-JwatOx$F?Zl&w_1sJX|3fSKKiJ7|C~ScyXT>xI;xU^%Dk~ z3Znb3#LUj73oFHgH$tW@PB<{^Ao+K#95;2wmOYt3&}M>Smy) zuI^yIrGz4!d|l#@OU)}KJ)Nek5(Si~n;Cg3@`eo-rOY5dBbvyTb zr=l^oX;Zd}&Xn;fR2Stb{$`63<}i6$l)zfawprDEbAJA8RlG$+`yIEo`c-0jA zDDpG+@#fw&10T$z!5)Ep0b>TfKcBw%3>z=i=V4@Biz$NrF%*o41?1QF=sX&_R)pm% zhG%3m8v|rpwiY!@X3mEv4U}c%1;WzC?;e4WA{#$6n z29a+F12^nyICv!PZvX{+K=n7Ok?67!H2Mf_*(e(3j|7jb*`F8*?@jI_!E@LQ#7J~T z;H!B(#ocHIzOU+E#HGQE^^iEiKsWNnn|sxadwCJJ(ZshBOp0Lihko9K-BmVK+ALZ> z(}_@tmfxs(n-wX#xLGvL??f)(J*7E5MEA4|j~T6EY)>$O7A5hiVDu?_5}fu~jc%wD;* zh?Z;acE5s>I*v1+VD4%=V1(s``ztJW0L-(SFpgZmv`Vgi(njB@UA zdFafz%NEdfrzoFSC6+ojXW<>EX!;C_Df2L9jP^Sf%}U>i$zYuFfim#;?od?-(N8R( zJ3E0kd4Wup%)o04DDn%FEG3o1;b#Wwoo}2=Nz4`)($Zgw7(cV}TNY5_mqOWNchNF) z05a#(Ld1lmz|Jp4bYANUba*j(`X$aCG4Xd96MvU#PS!p!#u>XrbQR-3G?3dAsLxo) z-C9^*s9YD)I07N(gBy7bXApD9zFXAA+-$uY>)Zocx?APCW49t^Vh_kk-b%+rVV%mz z5A%?^?Gg30ZHs8p9#JoRH&1+1uPGnJ-V)K}m9-Bi-Q0r?b5e=D*m!RzeJ?oLoit@H zn8Tg4c`wS_PRI9(vQcK<2aGaYxS;qMRndK5ezU2T{4eF$yRjz6HN!jQr1tO9cm$AW&o3)6#XMlP=q4W$8WETri?JFFCxqh$wN`yjX z{;sb?TelU;Y|M#;RAoOJ?uxu_zlbVc5NV`o2qS#5eg>QCZOrBIoHvJWh3XNLtg>V; z)SYUE#UB%W4ttPk)hKKUT{)ySH#7$1)KaO&o?tR*3r2K+?NjS zOEVDb=mi80NH!N)?mRQl(_$4HLU9yxpVd6G%(!b+oq}DX4juTp}L71@qZG&l!J6q*=Ao(`nyAYeR}SEc!(s z;oTMAp*kSc!YY-geFFZ+=)_^1bXcuF&cdgM-(gQ^f558D=Fw3Y;lj# ztKVRySxGb5NvAKq5p|=FtKFlGk6-ykDRc6;SQACP%L`074~)&BqvwH~KT|-KSkm@qg^oV|H-{N_vP4YKHI*YH znc}-}i>c>QxeLbK+dCJqGF_oP7sPb8bB?wb#lwF*teyz-sj^=dpl?a1&e`G_gb|m~ z@-q~78D|#CDNXw>i^w8w*P|fd*;MU2YcvI15w&co#_(?*DT0gg)&Ix*Z63*{(uga- z_oejl6&zwNrK4BGNbTSXivL->q#a*DBYqauaOSiUpS}u@9AGVI#j9RH6>*Wk+SAnT z5U2Dm9t~Uqq!=5kb5mBX%zVx)ipUYA=wy!YrItA&B0yDv(^??IIU)zgAy|8WWFdjkv8f*CYC5hq;-*KstBpoH#7bVEu)eoN%nQW;_)(5YdDh&0VtFM z2K*uFhq;%)i}ld!p4pB}3s3`&zC#sK@7OWL(S4ZP&

D*6Efa&`&lvp2Bej4wd@Lqt8EN$8V^Kvrl14Wli?Y>B!4t@{RJMfj z&8c1CDp+R-9pD;OY0ig&MPR8Pa=T9nPefTyDVNY_97^KZ&%`IV;F?QIp5UJ2cRKL| zRQDwLJ%zl=@6_Na3Qe9y!=7UIa*}2|6=nEy!&Bh(x*2rx2@9Y+#dh@MLWitjUAM5a zjMvuBpoJ|&^@U$c_P-%(M6Fj-P@kl5O*ZmA^FJA2u{Tg}O~!(8oTbTX9?Z0oVr6o{%=BqZzJktXX4;zr z*sr0NC(x z)j62!5Ygd-mkiKvp_rN16)$7=k;;&);&mta$eRC)#iWLdId#ppb85jVqHk3D4)5pE z^Z&EOBTroCg^R}|JQ#xci-BO7{tA$N12!v4XTeiW$T1HrB=zVs%;bTX9)}!v2Hzgz z_c{FD$}#=BTD>T78g>Gg3IU6^kycpV#0kXh!m?rMeAL7%TEz_WhE3=@!3^h3=)@v& zDRAso5wQN*dcp-fg!RAgaPyrISaR;$3tXGIK@LH zUy61OmFw!0H;mKxGE|32@dB5kI1Aei(GRfMjxgCC4;~5}nOTe;@{A@6s}m7> zwl&z6pZXotd|8<6#}~nYm#tC0@qE=9JIH>BDu&CUA(>n=HEY04qW>|WRW=!yG1>>h zWmtrY0m3O=YNx@CtuXd}vPxnR zDXSGY=jB0#fC^ez8cyyXv|xv|0i{IBmj84;FR%~&Nu34->NgiYXCHb>%U)@){MT1f zZ3oEHm(SsUwmlaewM#j6XoOt7( zH`BcG`kVh8tPWV72(@5s<7a) zvMgS4H7F}%BRx+58B>v;!VsP*&|KX{8pl)l9D0`ki#}x~V{2i3?n>t?<)pV24}hp^ ztZZQ2O^ae>YQ!EM5DX!XP(t}NIky7>P*5FUUr6oB%XfozD{ATSR%s7iEsv#j2^FpY zvb2cGSCBO|^Ci*}8ec)S4>OaU!J3L|SIF?=51zh!G@X|G=~doYsGgAEe&aft z9;|7ZGib`6(9L&2WQ(r<;gzJUJ{CN3q?;p7ac;Qj5 zft(QF{=$RSH;^F^K;Z`+K4TF7x$?e`h9`JpueM<|X~ zG8~r<@vVSE4-V4F)({Px+)DcK$5;C#j*?qRU;YSeBO~42s%>Nte?Ms@qu7mVEt9mJ z>2#uvEJbTt%hKBHuQ@xYllu8kv@Mw1Rn)jGDAESV zdP!xsZB<(|Xaf~_8H9Y3v);?Hg!Eo>5QNNrin@1@e`ss=(<>ciM4^nW@ML8~39M`G zt&Z|K2leTM?ZFGcpuxQg1f>~r!)*h%F%dyEKvBGyN!dWR-jFeQ-Z&{$-uI|v zZ`ljedvb5^hHL0}Z!8vnQL#Q)vj3u5edL>PmK$ID4d-ef`2jpf_Ek~f1kswlvasP5 zMBf;{f9i{+dky{BSGIto_mh|Kk~um_)`omkwT0JdVQ$;8fuz2EwvdY%Z6{~{)66G zkWLUiD2p_{SNmFhNf%l%=kj4-CB3(ObO_`BK3yL!n`obWM3qO#!IKZNr(Q+Brvf)K z=Z+P4+EVsC0(PE%H3#je=~{g(1Y)3$x=<_&=c7>`jtX7U{VLh=R6+~Vn< z@E7;YEq8B~{81U@Wp)kU{mpi-kcyPLoW38037?uu4@Sv8kUDs6w5*TqI*pd4Kx1}| zmNg-=cxN>3I+7n!^cdMN$|bbSJQA+JaI6dVq86+TvIN7lhx88Ogm}hb<`;o;9#ZMC zvS#FBciCrGHaK$QC<|4T`rf8sbpNq(VZm1w6aT@Bi`L_0&v1{I8mOc`X;6$8KjOp_ zj059jWqyKjcO2H^OlPxy$yRv%w}VpNlJ9A^zoL-wGS-p%m7*~`i-8yX6ir=o=*c$q z|9=pZFRj{|1C;iTY(=HsmYcPd9hCXDyrHG-psyy%6h}&$CKYK^ z*u9^SHL8S{kj76a8CC>wAv7+^X4-=DG0~)K*bw0k;+e&= z%=|~#pU{oTvTKo#7^h=k^e@O^fpSwyOqT84G@l~C(0r-_cP7ik7Y3G`A_sYPyX1#1 zKHPK5rxbkXBGIu%ub!JCyLxsz2mp6G?w^;{L`=NYKEQZ7EkMHW+m6;UP=Jp0G0dtU;cw zv#QoPeP)1W7YOoGK~!%h&fj+CI^UWpFKh9hsj&$z&|BUGee^5bzj`F%(ZUbpqymK) zIXKJBmf>1SS0kW(lkWh{Het_zfy5{Eo+FEcjs4dg>8Q2TXs{VK@6(8DeDdX>mwpSg z&sTp6WiYttIWb3eL~E!FNe4AqEsIfCl69SP=E^x5vW}c5%fo3gPri(&kqhU^IfC1c z*!#3l4@`0RvW86@fT3QGmdL)7kEnKrx%$Jkh&TfB`oqMY=f?~Ghw%!%DBk1(1senV z_o)4BoIpKLzx6ezGFO0SCIl)_V9=FMfpImmrjbi!naLhF^K5dc05dZ*`{o{fWf z_Fq%!sfW>&9(ry44FAYm{&M(BucfCRare#Oc0;FJmTJGr1*9n7NPUxsZ;qOS5qk31 zG%8hYulz`bnk2*O2-VZha^9z0RJ?>92<`ds4%BBKc6Rz0j8BMx?Tn+)%?T?An8t&@ z0yZ67CZj|15m*K0Bk<#8as$(@^~>P`HG8CiADnE~XPrlj%)0OP@HN|c+{4%Glif$% z`$Tmh`fr`P?{m6df2y!Q{C|yvRA!~ThM89=T}{B+>9RT|;NWz5O-!Dx3V$I&!mwch zOmKuf;{)j*Jyqpn;@I=&;WH`qBa@#yu_Fo=p+7&vXDavnc*xCcgsY)^E};H%CVqwk zvpQL7xJ(oa%!_We=a~##!$b$y%C6onbfi+B%QWrL5jyla_DHvmIB$K9W2u&>xM!3$ zR*`_`r4BircS1~I(QSM(0biedFZJzfekbrlct4bO?kK&y9!EGo9i?gO<%v9`%suUn zQYKO+pKDvu5s>lLvjgoEq z&Z1|Zc1CQ(cGNptMP5O*H-p>BJ>%TH8P`T$xAyNhj2ztGA}6)U>l}0fq;c}Q1{%{X z35{kt;QCCAvFeTa_7=T>dJL*!wG0{@maRjzMk zPTGSW?Ks0CD3)@+9iT{<1zE+-X9=T@_j zga9LXDwDeGkmX&TF7`MZil=wTGkFLy&?%QzPEo|mg;{)*r{}87-F#s0Lj@$CrD{9n z9xeAQVL#ZFoHk#0r6s zVw_%Nr0(VoDgbXEmp7&Cmc>iB1qZ5NyBWN9w`{N7{E-6p$PwYQuX2qm={d?hW!1M` zV~=0(qM1_n$hqMk-Q@3B{a59#)b!c6+2b<}5t{FnrCPWZ1Ov6tt6ro>^^n?V4A>0B z4lt{=X|F8K)jGV_s9xAUIYrA+XD6WnVib%zd$E;1_~1Myl0(!{jGL=YQ}C@$Q{Z;W zz-dbIdgCbOVum~z^e6|VtIa9TnE)Wqqa51t6|O*T_zQ(t~H4Picqb+uH1(De`OCT|1NG9RIaEZq*j#P>UmSNx-(B5f7S1 zc&AJ4kH|2x9hJR{tYUNkg7Gbge&a_PcN8ZMkk&hjqx4Vdx1;jk_t* zY2Rm2&M`S&`$oN1t;T9G=Y4}CDm+--^NmywR&B@SKV6~05Et~cp2MOFUlLoyb zEBj(E$*M%N4;s6vekXADv(vfcguG)dv6E{PZ3Ul=2hToysp8Eu-M-VA{wO}|=nVK?rdVrlVvN+w=;zoq*$?o9T61U*u}1sqbdDn|=D+9yOUIai zaQM;b(-NX3=g9kvY^*ImN3G7t%2jfC&cK8l-vFer@om(^l>rnt8Hu9{=VeK&&S2SC zNmtIG)0R`MA8`Ef361|j*5@2|{2(hwKbs?%5tuuIHCirY$vG04V1%8{4wy6n|KZbx{Q{elT}fv zQ|Dx*^GKfuxiwdFkGR?8|s$u}6r75&0N(;ff5xwfmP)1v@%>nssK5<^Y z%4Du=mK^P~Q1#7%bF?B$25YGA7g;hgkNDktF7caoj_zlH&v8Kg*J>(pAMG^$d3NCV;n|?4zftIbm`y<}&fZo?7&? zY@grDzyFNmL?@NYku$8voEvlGB5P=dxK zhu4^N{}+&jX+P1(iO8N>aY|&73gJVQ7H_xlk$~c>}yn#XAOU-V`lC1iy z{|!)3NJamOW9nlx^;b;Ld#h>Rud)mdIIsRHTU9>(DbCyYJkZNH4}_Z4LHZf?%zOBN z6#}btvK0ydU%M&igsfZbO5(0NY1`^Ww`57<0`~SuK8SUlO7=1dqJtjK1 znFQ;^82oSr#(-sVc(sqQgx5A=I|QzhEX|5R2{mGCwT&$O^-##Bp8j1{30CEy`i?=R zffdOXK8>-TrqEohPmexhM5>9q3PvuQKLYJ%>EF4ytooKB?#N1TTHTS|>}gyv5O*E{ zw!KSR?#LoV&Vv8y6sBgEF1-t~% z0c`*ssaUA3igkpuNvQ3i)wgYo9jAj9%P?vYW~=R-8fIHy)k20)XoM|>-S`Mw0(9q0 zj=`?%egDg_8+#^wzZn=gQ7j_*)7FT#z?sSaIX)c#FuP${d!^Kqw&3b zB(<$>YlnNYrPXaqt%+2-hV7wmj|O(o5sPII)vsymh!W=3v|X|eq2aY`PQOl#>=ru$ zyAN|#t!?X~dF|5ew4$!9xR%+T((2k$iZ^?uIqpb?BpAa~%F(h}+e8PIt#2z=-_f@B zn51ES$2*4icl009dw^q9(&*ts#w9s`#G`tT8902{Xve@|j-dnljv78XsqgS%{YJ<6 zStcT@zBD}ER-78wvsLiBj_ZaP@49iyJ+5Xo2MKI2I!=m+~E4MpMr;HM)8fXSs)1^`9Ht z77uUD73pH`Xbh!o&iv{n1;%z1PPqE&vo^^B@KBo5&$FQ+O z`y`F}n~p_CS}Xz0>=tjh#o$(eTguU?TVgYZqwnys!^R|y@=J7uzY5ovLgHYO&2JHQp9g*Q1K4 zOV{RhOPRl^Q`NX?HRGyTEE-iww1tq?z_zZ!8!gacq?rgXhk6YB_;>HVs((gT!S9(Z zoaY+YmU(G+TRY#`fzOX5)4)YsO%=puac9Qzb*2;4nG zDWab(rh@68?F!F@%XQx4a?N_5ZSQ=upY51c`(!xP=xZo2`nLxPms3tzLyOE9{n{2$&8x2JWcd!!X22!}?)V`z5{Q*a?3wc=`$o zT8!C!4&R*7?G@B=F*+k)rBQ*G;8ukH^&!q}i*1F|wSLW<2kUzWdui{sbY?a5Ztd%r z**;K>*V*<|wWaqYtwsmh)DjYA(>l=gmfj7E?(Pt1iA5Hr0X(sCI#8Wf-d(iVj+D~M zyQen0qcgXacP+?Eb)wid-h(vn&a|M7cSEgfXFAiyJ0W3B=Rh@W2sP$9z5?I@c%!Y| zG666LupV#{@GBtTWehB!HDDZI-pkart#|Xu&s~W@lNUD+w0s0e1uO%k0agM&0elKr z16T`K2iO4E1lR)D2G{}E3HTDQ8?YCU0oV^X2>2Rs1aPc*;AG2j{5T0X1^6Ct29OCj z2lxq)1-J;v23!H;0Db{n2mA`S1-K2!1>6PP1KbBZ0Q?1b1b70lv0bzg$Kor0Mhyj!alm)~BDgY`0;s8|vF9B)*Y60p1>H*>b z4FQb-34o@6=75%f)|2t4EubAB5zqn93D5=570?~h&Ym1;?UiRKL`EE0G9^gLU0pKsdBft}Y z1s$jX1i%LH0r&v|0EGc|Kv6&tAQ%t|2m?d_q5uv+44^ciEFcz80Z<7L2dK)J@DhI1 z0Mr810n`J;0~!Jv0}=pD0nGs|0j&XT0qp>ZfDV98fG&WpfbM|T0X+e40Qvy>0r~^p z1PlTU0Sp6-Xx<;4haY1A;{a~~CIBV^CIONGQvuTe(*ZL9vjDRJa{wuTd4L6gMS%Q& V*^P|f+54bbWxMlaqW5pM{|BdSAB_M2 delta 57137 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 QbpRaYHrL8m?00Pc1F?uO=l}o! diff --git a/openfeature-provider/go/confidence/internal/local_resolver/local_resolver.go b/openfeature-provider/go/confidence/internal/local_resolver/local_resolver.go index df59c705..1e7675db 100644 --- a/openfeature-provider/go/confidence/internal/local_resolver/local_resolver.go +++ b/openfeature-provider/go/confidence/internal/local_resolver/local_resolver.go @@ -24,7 +24,10 @@ type LocalResolver interface { ApplyFlags(*resolver.ApplyFlagsRequest) error FlushAllLogs() error FlushAssignLogs() error - PrometheusSnapshot() string + // PrometheusSnapshot returns Prometheus/OpenMetrics text-format metrics. + // bucketsPerDecade controls histogram bucket density (1-18, 0 = default 18). + // openmetrics switches output to OpenMetrics text format. + PrometheusSnapshot(bucketsPerDecade uint32, openmetrics bool) string Close(context.Context) error } diff --git a/openfeature-provider/go/confidence/internal/local_resolver/pool.go b/openfeature-provider/go/confidence/internal/local_resolver/pool.go index 58b23831..f264f932 100644 --- a/openfeature-provider/go/confidence/internal/local_resolver/pool.go +++ b/openfeature-provider/go/confidence/internal/local_resolver/pool.go @@ -123,12 +123,54 @@ func (s *PooledResolver) FlushAssignLogs() error { } // PrometheusSnapshot implements LocalResolver. -func (s *PooledResolver) PrometheusSnapshot() string { - var b strings.Builder +// Outputs from multiple pool instances are concatenated with duplicate +// # TYPE and # HELP meta lines removed so the result is valid Prometheus +// exposition text. +func (s *PooledResolver) PrometheusSnapshot(bucketsPerDecade uint32, openmetrics bool) string { + var fragments []string s.maintenance(func(lr LocalResolver) error { - b.WriteString(lr.PrometheusSnapshot()) + text := lr.PrometheusSnapshot(bucketsPerDecade, openmetrics) + if text != "" { + fragments = append(fragments, text) + } return nil }) + if len(fragments) <= 1 { + if len(fragments) == 1 { + return fragments[0] + } + return "" + } + return deduplicateMetaLines(fragments) +} + +// deduplicateMetaLines merges multiple Prometheus/OpenMetrics text fragments, +// keeping only the first occurrence of each # TYPE / # HELP line. +// Any # EOF lines are stripped from fragments and a single # EOF is appended +// at the end if one was present (required for OpenMetrics). +func deduplicateMetaLines(fragments []string) string { + seen := make(map[string]bool) + hasEOF := false + var b strings.Builder + for _, frag := range fragments { + for _, line := range strings.Split(frag, "\n") { + if line == "# EOF" { + hasEOF = true + continue + } + if strings.HasPrefix(line, "# ") { + if seen[line] { + continue + } + seen[line] = true + } + b.WriteString(line) + b.WriteByte('\n') + } + } + if hasEOF { + b.WriteString("# EOF\n") + } return b.String() } diff --git a/openfeature-provider/go/confidence/internal/local_resolver/recover.go b/openfeature-provider/go/confidence/internal/local_resolver/recover.go index 784e745c..97513205 100644 --- a/openfeature-provider/go/confidence/internal/local_resolver/recover.go +++ b/openfeature-provider/go/confidence/internal/local_resolver/recover.go @@ -141,7 +141,7 @@ func (r *RecoveringResolver) FlushAssignLogs() (err error) { return } -func (r *RecoveringResolver) PrometheusSnapshot() string { +func (r *RecoveringResolver) PrometheusSnapshot(bucketsPerDecade uint32, openmetrics bool) string { defer func() { if rec := recover(); rec != nil { slog.Warn("PrometheusSnapshot panicked, ignoring", "error", rec) @@ -151,7 +151,7 @@ func (r *RecoveringResolver) PrometheusSnapshot() string { return "" } lr := r.get() - return lr.PrometheusSnapshot() + return lr.PrometheusSnapshot(bucketsPerDecade, openmetrics) } func (r *RecoveringResolver) Close(ctx context.Context) error { diff --git a/openfeature-provider/go/confidence/internal/local_resolver/recover_test.go b/openfeature-provider/go/confidence/internal/local_resolver/recover_test.go index d44eb104..02dcde7a 100644 --- a/openfeature-provider/go/confidence/internal/local_resolver/recover_test.go +++ b/openfeature-provider/go/confidence/internal/local_resolver/recover_test.go @@ -27,7 +27,7 @@ func (m *mockResolver) RegisterResolve(*wasm.RegisterResolveRequest) {} func (m *mockResolver) ApplyFlags(*resolver.ApplyFlagsRequest) error { return nil } func (m *mockResolver) FlushAllLogs() error { return nil } func (m *mockResolver) FlushAssignLogs() error { return nil } -func (m *mockResolver) PrometheusSnapshot() string { return "" } +func (m *mockResolver) PrometheusSnapshot(_ uint32, _ bool) string { return "" } func (m *mockResolver) Close(context.Context) error { return nil } // mockFactory returns a panicking mockResolver on the first call and diff --git a/openfeature-provider/go/confidence/internal/local_resolver/wasm.go b/openfeature-provider/go/confidence/internal/local_resolver/wasm.go index 325819b5..07862b11 100644 --- a/openfeature-provider/go/confidence/internal/local_resolver/wasm.go +++ b/openfeature-provider/go/confidence/internal/local_resolver/wasm.go @@ -85,9 +85,11 @@ func (r *WasmResolver) FlushAssignLogs() error { return err } -func (r *WasmResolver) PrometheusSnapshot() string { +func (r *WasmResolver) PrometheusSnapshot(bucketsPerDecade uint32, openmetrics bool) string { req := &wasm.PrometheusSnapshotRequest{ - Instance: r.instanceID, + Instance: r.instanceID, + BucketsPerDecade: bucketsPerDecade, + Openmetrics: openmetrics, } resp := &wasm.PrometheusSnapshotResponse{} if err := r.call("wasm_msg_guest_prometheus_snapshot", req, resp); err != nil { diff --git a/openfeature-provider/go/confidence/internal/proto/admin/resolver.pb.go b/openfeature-provider/go/confidence/internal/proto/admin/resolver.pb.go index 7027d97e..ca3370ef 100644 --- a/openfeature-provider/go/confidence/internal/proto/admin/resolver.pb.go +++ b/openfeature-provider/go/confidence/internal/proto/admin/resolver.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.10 -// protoc v5.29.3 +// protoc-gen-go v1.36.6 +// protoc v5.27.3 // source: confidence/flags/admin/v1/resolver.proto package admin diff --git a/openfeature-provider/go/confidence/internal/proto/resolver/api.pb.go b/openfeature-provider/go/confidence/internal/proto/resolver/api.pb.go index 31626ca5..84e43f59 100644 --- a/openfeature-provider/go/confidence/internal/proto/resolver/api.pb.go +++ b/openfeature-provider/go/confidence/internal/proto/resolver/api.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.10 -// protoc v5.29.3 +// protoc-gen-go v1.36.6 +// protoc v5.27.3 // source: confidence/flags/resolver/v1/api.proto package resolver diff --git a/openfeature-provider/go/confidence/internal/proto/resolver/api_grpc.pb.go b/openfeature-provider/go/confidence/internal/proto/resolver/api_grpc.pb.go index 8954a668..875eef04 100644 --- a/openfeature-provider/go/confidence/internal/proto/resolver/api_grpc.pb.go +++ b/openfeature-provider/go/confidence/internal/proto/resolver/api_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.5.1 -// - protoc v5.29.3 +// - protoc v5.27.3 // source: confidence/flags/resolver/v1/api.proto package resolver diff --git a/openfeature-provider/go/confidence/internal/proto/resolver/types.pb.go b/openfeature-provider/go/confidence/internal/proto/resolver/types.pb.go index c2ed9bc6..99be39ba 100644 --- a/openfeature-provider/go/confidence/internal/proto/resolver/types.pb.go +++ b/openfeature-provider/go/confidence/internal/proto/resolver/types.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.10 -// protoc v5.29.3 +// protoc-gen-go v1.36.6 +// protoc v5.27.3 // source: confidence/flags/resolver/v1/types.proto package resolver diff --git a/openfeature-provider/go/confidence/internal/proto/resolverinternal/internal_api.pb.go b/openfeature-provider/go/confidence/internal/proto/resolverinternal/internal_api.pb.go index 3ca0b257..d91c0391 100644 --- a/openfeature-provider/go/confidence/internal/proto/resolverinternal/internal_api.pb.go +++ b/openfeature-provider/go/confidence/internal/proto/resolverinternal/internal_api.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.10 -// protoc v5.29.3 +// protoc-gen-go v1.36.6 +// protoc v5.27.3 // source: confidence/flags/resolver/v1/internal_api.proto // IMPORTANT: Package name must match backend expectation for gRPC service discovery diff --git a/openfeature-provider/go/confidence/internal/proto/resolverinternal/internal_api_grpc.pb.go b/openfeature-provider/go/confidence/internal/proto/resolverinternal/internal_api_grpc.pb.go index 7374215d..a36965c2 100644 --- a/openfeature-provider/go/confidence/internal/proto/resolverinternal/internal_api_grpc.pb.go +++ b/openfeature-provider/go/confidence/internal/proto/resolverinternal/internal_api_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.5.1 -// - protoc v5.29.3 +// - protoc v5.27.3 // source: confidence/flags/resolver/v1/internal_api.proto // IMPORTANT: Package name must match backend expectation for gRPC service discovery diff --git a/openfeature-provider/go/confidence/internal/proto/types/target.pb.go b/openfeature-provider/go/confidence/internal/proto/types/target.pb.go index 340b8a20..408bce9d 100644 --- a/openfeature-provider/go/confidence/internal/proto/types/target.pb.go +++ b/openfeature-provider/go/confidence/internal/proto/types/target.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.10 -// protoc v5.29.3 +// protoc-gen-go v1.36.6 +// protoc v5.27.3 // source: confidence/flags/types/v1/target.proto package types diff --git a/openfeature-provider/go/confidence/internal/proto/types/types.pb.go b/openfeature-provider/go/confidence/internal/proto/types/types.pb.go index d33c31b2..c0efd0ef 100644 --- a/openfeature-provider/go/confidence/internal/proto/types/types.pb.go +++ b/openfeature-provider/go/confidence/internal/proto/types/types.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.10 -// protoc v5.29.3 +// protoc-gen-go v1.36.6 +// protoc v5.27.3 // source: confidence/flags/types/v1/types.proto package types diff --git a/openfeature-provider/go/confidence/internal/proto/wasm/messages.pb.go b/openfeature-provider/go/confidence/internal/proto/wasm/messages.pb.go index b2ed8ff6..d855e31a 100644 --- a/openfeature-provider/go/confidence/internal/proto/wasm/messages.pb.go +++ b/openfeature-provider/go/confidence/internal/proto/wasm/messages.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.10 -// protoc v5.29.3 +// protoc-gen-go v1.36.6 +// protoc v5.27.3 // source: confidence/wasm/messages.proto package wasm @@ -245,10 +245,12 @@ func (*Response_Data) isResponse_Result() {} func (*Response_Error) isResponse_Result() {} type PrometheusSnapshotRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Instance string `protobuf:"bytes,1,opt,name=instance,proto3" json:"instance,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Instance string `protobuf:"bytes,1,opt,name=instance,proto3" json:"instance,omitempty"` + BucketsPerDecade uint32 `protobuf:"varint,2,opt,name=buckets_per_decade,json=bucketsPerDecade,proto3" json:"buckets_per_decade,omitempty"` + Openmetrics bool `protobuf:"varint,3,opt,name=openmetrics,proto3" json:"openmetrics,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *PrometheusSnapshotRequest) Reset() { @@ -288,6 +290,20 @@ func (x *PrometheusSnapshotRequest) GetInstance() string { return "" } +func (x *PrometheusSnapshotRequest) GetBucketsPerDecade() uint32 { + if x != nil { + return x.BucketsPerDecade + } + return 0 +} + +func (x *PrometheusSnapshotRequest) GetOpenmetrics() bool { + if x != nil { + return x.Openmetrics + } + return false +} + type PrometheusSnapshotResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Text string `protobuf:"bytes,1,opt,name=text,proto3" json:"text,omitempty"` @@ -348,9 +364,11 @@ const file_confidence_wasm_messages_proto_rawDesc = "" + "\bResponse\x12\x14\n" + "\x04data\x18\x01 \x01(\fH\x00R\x04data\x12\x16\n" + "\x05error\x18\x02 \x01(\tH\x00R\x05errorB\b\n" + - "\x06result\"7\n" + + "\x06result\"\x87\x01\n" + "\x19PrometheusSnapshotRequest\x12\x1a\n" + - "\binstance\x18\x01 \x01(\tR\binstance\"0\n" + + "\binstance\x18\x01 \x01(\tR\binstance\x12,\n" + + "\x12buckets_per_decade\x18\x02 \x01(\rR\x10bucketsPerDecade\x12 \n" + + "\vopenmetrics\x18\x03 \x01(\bR\vopenmetrics\"0\n" + "\x1aPrometheusSnapshotResponse\x12\x12\n" + "\x04text\x18\x01 \x01(\tR\x04textB\x8c\x01\n" + "\x1fcom.spotify.confidence.sdk.wasmB\bMessagesP\x00Z]github.com/spotify/confidence-resolver/openfeature-provider/go/confidence/internal/proto/wasmb\x06proto3" diff --git a/openfeature-provider/go/confidence/internal/proto/wasm/wasm_api.pb.go b/openfeature-provider/go/confidence/internal/proto/wasm/wasm_api.pb.go index 12fac671..b62fb247 100644 --- a/openfeature-provider/go/confidence/internal/proto/wasm/wasm_api.pb.go +++ b/openfeature-provider/go/confidence/internal/proto/wasm/wasm_api.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.10 -// protoc v5.29.3 +// protoc-gen-go v1.36.6 +// protoc v5.27.3 // source: confidence/wasm/wasm_api.proto package wasm diff --git a/openfeature-provider/go/confidence/internal/testutil/helpers.go b/openfeature-provider/go/confidence/internal/testutil/helpers.go index 60eb428d..4edb0836 100644 --- a/openfeature-provider/go/confidence/internal/testutil/helpers.go +++ b/openfeature-provider/go/confidence/internal/testutil/helpers.go @@ -428,7 +428,7 @@ func (m *MockedLocalResolver) ResolveProcess(*wasm.ResolveProcessRequest) (*wasm return m.Response, m.Err } func (m MockedLocalResolver) SetResolverState(*wasm.SetResolverStateRequest) error { return nil } -func (m MockedLocalResolver) PrometheusSnapshot() string { return "" } +func (m MockedLocalResolver) PrometheusSnapshot(_ uint32, _ bool) string { return "" } func (m MockedLocalResolver) RegisterResolve(*wasm.RegisterResolveRequest) {} func (m MockedLocalResolver) ApplyFlags(*resolver.ApplyFlagsRequest) error { return nil } diff --git a/openfeature-provider/go/confidence/materialization.go b/openfeature-provider/go/confidence/materialization.go index 31dc8b54..118fa593 100644 --- a/openfeature-provider/go/confidence/materialization.go +++ b/openfeature-provider/go/confidence/materialization.go @@ -189,8 +189,8 @@ func (m *materializationSupportedResolver) SetResolverState(request *wasm.SetRes return m.current.SetResolverState(request) } -func (m *materializationSupportedResolver) PrometheusSnapshot() string { - return m.current.PrometheusSnapshot() +func (m *materializationSupportedResolver) PrometheusSnapshot(bucketsPerDecade uint32, openmetrics bool) string { + return m.current.PrometheusSnapshot(bucketsPerDecade, openmetrics) } func (m *materializationSupportedResolver) Close(ctx context.Context) error { diff --git a/openfeature-provider/go/confidence/provider.go b/openfeature-provider/go/confidence/provider.go index 56d887a3..432e1cfc 100644 --- a/openfeature-provider/go/confidence/provider.go +++ b/openfeature-provider/go/confidence/provider.go @@ -353,17 +353,26 @@ func evaluate[T any]( // SnapshotConfig holds options for GetPrometheusMetrics. // // Experimental: this API is subject to change. -type SnapshotConfig struct{} +type SnapshotConfig struct { + // BucketsPerDecade controls histogram bucket density in the output. + // The internal histogram always stores 18 buckets per decimal order + // of magnitude. This controls how many are emitted by striding. + // Valid range: 1-18. Zero means default (18 — all buckets). + BucketsPerDecade uint32 + // OpenMetrics switches output to OpenMetrics text format. + // When false (default), Prometheus exposition format is used. + OpenMetrics bool +} // GetPrometheusMetrics returns a Prometheus text-format metrics snapshot // aggregated from all pooled resolver instances. // // Experimental: this API is subject to change. -func (p *LocalResolverProvider) GetPrometheusMetrics(_ SnapshotConfig) string { +func (p *LocalResolverProvider) GetPrometheusMetrics(config SnapshotConfig) string { if p.resolver == nil { return "" } - return p.resolver.PrometheusSnapshot() + return p.resolver.PrometheusSnapshot(config.BucketsPerDecade, config.OpenMetrics) } // Resolve resolves multiple flags for the given context. If flagNames is empty, diff --git a/openfeature-provider/go/confidence/provider_test.go b/openfeature-provider/go/confidence/provider_test.go index 58c5548c..1219c156 100644 --- a/openfeature-provider/go/confidence/provider_test.go +++ b/openfeature-provider/go/confidence/provider_test.go @@ -499,7 +499,7 @@ func (m *mockResolverAPIForInit) FlushAssignLogs() error { return nil } -func (m *mockResolverAPIForInit) PrometheusSnapshot() string { +func (m *mockResolverAPIForInit) PrometheusSnapshot(_ uint32, _ bool) string { return "" } diff --git a/openfeature-provider/go/go.mod b/openfeature-provider/go/go.mod index 1309c33e..f2343c9b 100644 --- a/openfeature-provider/go/go.mod +++ b/openfeature-provider/go/go.mod @@ -7,13 +7,18 @@ require github.com/open-feature/go-sdk v1.16.0 require github.com/tetratelabs/wazero v1.9.0 require ( + github.com/prometheus/common v0.67.5 google.golang.org/grpc v1.79.3 - google.golang.org/protobuf v1.36.10 + google.golang.org/protobuf v1.36.11 ) require ( github.com/go-logr/logr v1.4.3 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/prometheus/client_model v0.6.2 // indirect go.uber.org/mock v0.6.0 // indirect + go.yaml.in/yaml/v2 v2.4.3 // indirect golang.org/x/net v0.48.0 // indirect golang.org/x/sys v0.39.0 // indirect golang.org/x/text v0.32.0 // indirect diff --git a/openfeature-provider/go/go.sum b/openfeature-provider/go/go.sum index 19800881..896b9769 100644 --- a/openfeature-provider/go/go.sum +++ b/openfeature-provider/go/go.sum @@ -1,5 +1,8 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= @@ -10,8 +13,26 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/open-feature/go-sdk v1.16.0 h1:5NCHYv5slvNBIZhYXAzAufo0OI59OACZ5tczVqSE+Tg= github.com/open-feature/go-sdk v1.16.0/go.mod h1:EIF40QcoYT1VbQkMPy2ZJH4kvZeY+qGUXAorzSWgKSo= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4= +github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I= github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= @@ -28,6 +49,8 @@ go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6 go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= +go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= +go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= @@ -40,5 +63,10 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1: google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/openfeature-provider/js/src/WasmResolver.ts b/openfeature-provider/js/src/WasmResolver.ts index f1434420..2ff61f88 100644 --- a/openfeature-provider/js/src/WasmResolver.ts +++ b/openfeature-provider/js/src/WasmResolver.ts @@ -116,7 +116,10 @@ export class UnsafeWasmResolver implements LocalResolver { } prometheusSnapshot(instance: string): string { - const reqPtr = this.transferRequest({ instance }, PrometheusSnapshotRequest); + const reqPtr = this.transferRequest( + { instance, bucketsPerDecade: 0, openmetrics: false }, + PrometheusSnapshotRequest, + ); const resPtr = this.exports.wasm_msg_guest_prometheus_snapshot(reqPtr); return this.consumeResponse(resPtr, PrometheusSnapshotResponse).text; } diff --git a/openfeature-provider/proto/confidence/wasm/messages.proto b/openfeature-provider/proto/confidence/wasm/messages.proto index 17b4927d..b7dd1541 100644 --- a/openfeature-provider/proto/confidence/wasm/messages.proto +++ b/openfeature-provider/proto/confidence/wasm/messages.proto @@ -30,6 +30,8 @@ message Response { message PrometheusSnapshotRequest { string instance = 1; + uint32 buckets_per_decade = 2; + bool openmetrics = 3; } message PrometheusSnapshotResponse { diff --git a/wasm/proto/messages.proto b/wasm/proto/messages.proto index c602f364..8e8e9803 100644 --- a/wasm/proto/messages.proto +++ b/wasm/proto/messages.proto @@ -41,6 +41,8 @@ message Response { message PrometheusSnapshotRequest { string instance = 1; + uint32 buckets_per_decade = 2; + bool openmetrics = 3; } message PrometheusSnapshotResponse { diff --git a/wasm/rust-guest/src/lib.rs b/wasm/rust-guest/src/lib.rs index e4f255be..50a8988e 100644 --- a/wasm/rust-guest/src/lib.rs +++ b/wasm/rust-guest/src/lib.rs @@ -194,7 +194,11 @@ wasm_msg_guest! { } fn prometheus_snapshot(request: proto::PrometheusSnapshotRequest) -> WasmResult { - let text = TELEMETRY.snapshot().to_prometheus(&request.instance); + let config = confidence_resolver::telemetry::PrometheusConfig { + buckets_per_decade: request.buckets_per_decade, + openmetrics: request.openmetrics, + }; + let text = TELEMETRY.snapshot().to_prometheus(&request.instance, &config); Ok(proto::PrometheusSnapshotResponse { text }) } From 9848703e226c8d4c1c3ee37f552c449d90673153 Mon Sep 17 00:00:00 2001 From: Andreas Karlsson Date: Tue, 14 Apr 2026 13:51:02 +0200 Subject: [PATCH 2/2] fixup! feat: improve Prometheus metrics API --- confidence-resolver/src/telemetry.rs | 7 ++++++- .../assets/confidence_resolver.wasm | Bin 482446 -> 482451 bytes 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/confidence-resolver/src/telemetry.rs b/confidence-resolver/src/telemetry.rs index f93b6f0f..4d9e658c 100644 --- a/confidence-resolver/src/telemetry.rs +++ b/confidence-resolver/src/telemetry.rs @@ -188,8 +188,13 @@ impl TelemetrySnapshot { } else { config.buckets_per_decade.clamp(1, 18) as usize }; + // Ceiling division: emit at most bpd buckets per decade. // bpd is in 1..=18, so checked_div cannot return None. - let stride = 18usize.checked_div(bpd).unwrap_or(1); + let stride = 18usize + .saturating_add(bpd) + .saturating_sub(1) + .checked_div(bpd) + .unwrap_or(1); writeln!( w, diff --git a/openfeature-provider/go/confidence/internal/local_resolver/assets/confidence_resolver.wasm b/openfeature-provider/go/confidence/internal/local_resolver/assets/confidence_resolver.wasm index 8625ea50ea052e09f310bab8745f44ed277649d9..c8f1e88cdefd7e97232540e2c1bc1b3af4332b45 100755 GIT binary patch delta 89 zcmeC%DLZ+m?1lpzj3=58a-I(gwi-6Zp!R=aY}@~du}^RX04sAFKmY&$ delta 92 zcmbQdQ?_rX?1lpzjE9>Ka# m*YtB@%;N0}1ek%C1&CR