diff --git a/CHANGELOG.md b/CHANGELOG.md index 89e3b797..895f7188 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ All significant changes to this project will be documented in this file. * `TDigest` can now be serialized and deserialized directly without converting through `TDigestMut` at the call site. * Add Relative Error Quantiles (REQ) sketches behind the `req` feature, including configurable high- or low-rank accuracy, rank, quantile, PMF, and CDF queries, typed rank confidence bounds, merging, totally ordered custom item types, the `ReqFloat` adapter for non-NaN floating-point values, and C++/Java-compatible serialization. +* Add diagnostic `summary()` methods for HLL and CPC sketches and unions. ### Performance improvements diff --git a/datasketches/src/cpc/sketch.rs b/datasketches/src/cpc/sketch.rs index 8599e46d..3c59820f 100644 --- a/datasketches/src/cpc/sketch.rs +++ b/datasketches/src/cpc/sketch.rs @@ -473,6 +473,26 @@ impl CpcSketch { } impl CpcSketch { + /// Returns a human-readable diagnostic summary. + /// + /// The output is for inspection and debugging. Its format may change and + /// should not be parsed. + pub fn summary(&self) -> String { + format!( + "CPC Sketch Summary:\n\ + \x20\x20flavor : {:?}\n\ + \x20\x20lg k : {}\n\ + \x20\x20merged : {}\n\ + \x20\x20estimate : {}\n\ + \x20\x20num coupons : {}", + self.flavor(), + self.lg_k(), + self.merge_flag, + self.estimate(), + self.num_coupons, + ) + } + /// Serializes this `CpcSketch` to bytes. pub fn serialize(&self) -> Vec { let flavor = self.flavor(); diff --git a/datasketches/src/cpc/union.rs b/datasketches/src/cpc/union.rs index 51742a30..460b1b28 100644 --- a/datasketches/src/cpc/union.rs +++ b/datasketches/src/cpc/union.rs @@ -354,6 +354,29 @@ impl CpcUnion { }; size_of::() + heap_size } + + /// Returns a human-readable diagnostic summary. + /// + /// The output is for inspection and debugging. Its format may change and + /// should not be parsed. + pub fn summary(&self) -> String { + let state = match &self.state { + UnionState::Accumulator(_) => "Accumulator", + UnionState::BitMatrix(_) => "BitMatrix", + }; + let num_coupons = match &self.state { + UnionState::Accumulator(sketch) => sketch.num_coupons, + UnionState::BitMatrix(matrix) => count_bits_set_in_matrix(matrix), + }; + + format!( + "CPC Union Summary:\n\ + \x20\x20lg k : {}\n\ + \x20\x20state : {state}\n\ + \x20\x20num coupons : {num_coupons}", + self.lg_k(), + ) + } } fn or_window_into_matrix( diff --git a/datasketches/src/hll/sketch.rs b/datasketches/src/hll/sketch.rs index 4e0ed719..ea820a09 100644 --- a/datasketches/src/hll/sketch.rs +++ b/datasketches/src/hll/sketch.rs @@ -457,6 +457,37 @@ impl HllSketch { size_of::() + heap_size } + + /// Returns a human-readable diagnostic summary. + /// + /// The output is for inspection and debugging. Its format may change and + /// should not be parsed. + pub fn summary(&self) -> String { + let target_type = match self.target_type() { + HllType::Hll4 => "Hll4", + HllType::Hll6 => "Hll6", + HllType::Hll8 => "Hll8", + }; + let current_mode = match &self.mode { + Mode::List { .. } => "List", + Mode::Set { .. } => "Set", + Mode::Array4(_) | Mode::Array6(_) | Mode::Array8(_) => "Hll", + }; + + format!( + "HLL Sketch Summary:\n\ + \x20\x20lg config k : {}\n\ + \x20\x20target type : {target_type}\n\ + \x20\x20current mode : {current_mode}\n\ + \x20\x20lower bound : {}\n\ + \x20\x20estimate : {}\n\ + \x20\x20upper bound : {}", + self.lg_config_k(), + self.lower_bound(NumStdDev::One), + self.estimate(), + self.upper_bound(NumStdDev::One), + ) + } } fn promote_container_to_set(container: &Container, hll_type: HllType) -> Mode { diff --git a/datasketches/src/hll/union.rs b/datasketches/src/hll/union.rs index eb983829..955c9c1b 100644 --- a/datasketches/src/hll/union.rs +++ b/datasketches/src/hll/union.rs @@ -336,6 +336,26 @@ impl HllUnion { // The gadget's inline size is already covered by size_of::(). size_of::() - size_of::() + self.gadget.estimated_size() } + + /// Returns a human-readable diagnostic summary. + /// + /// The output is for inspection and debugging. Its format may change and + /// should not be parsed. + pub fn summary(&self) -> String { + format!( + "HLL Union Summary:\n\ + \x20\x20lg max k : {}\n\ + \x20\x20lg config k : {}\n\ + \x20\x20lower bound : {}\n\ + \x20\x20estimate : {}\n\ + \x20\x20upper bound : {}", + self.lg_max_k(), + self.lg_config_k(), + self.lower_bound(NumStdDev::One), + self.estimate(), + self.upper_bound(NumStdDev::One), + ) + } } /// Convert a coupon mode (List or Set) to Hll8 target type diff --git a/tests-integration/tests/cpc_test/main.rs b/tests-integration/tests/cpc_test/main.rs index 7b98ba98..254c6d24 100644 --- a/tests-integration/tests/cpc_test/main.rs +++ b/tests-integration/tests/cpc_test/main.rs @@ -16,6 +16,7 @@ // under the License. mod deserialize; +mod summary; mod union; mod update; mod wrapper; diff --git a/tests-integration/tests/cpc_test/summary.rs b/tests-integration/tests/cpc_test/summary.rs new file mode 100644 index 00000000..02b17d5a --- /dev/null +++ b/tests-integration/tests/cpc_test/summary.rs @@ -0,0 +1,60 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use datasketches::cpc::CpcSketch; +use datasketches::cpc::CpcUnion; +use insta::assert_snapshot; + +#[test] +fn summary_empty_sketch() { + let sketch = CpcSketch::new(11).unwrap(); + + assert_snapshot!(sketch.summary(), @r" + CPC Sketch Summary: + flavor : Empty + lg k : 11 + merged : false + estimate : 0 + num coupons : 0 + "); +} + +#[test] +fn summary_populated_sketch() { + let mut sketch = CpcSketch::new(11).unwrap(); + sketch.update("apple"); + + let summary = sketch.summary(); + assert!(summary.contains("flavor : Sparse\n")); + assert!(summary.contains("num coupons : 1\n")); + assert!(!summary.contains("estimate : 0\n")); +} + +#[test] +fn summary_union() { + let mut sketch = CpcSketch::new(11).unwrap(); + sketch.update("apple"); + let mut union = CpcUnion::new(11).unwrap(); + union.update(&sketch).unwrap(); + + assert_snapshot!(union.summary(), @r" + CPC Union Summary: + lg k : 11 + state : Accumulator + num coupons : 1 + "); +} diff --git a/tests-integration/tests/hll_test/main.rs b/tests-integration/tests/hll_test/main.rs index 30fb7dce..ffa065a5 100644 --- a/tests-integration/tests/hll_test/main.rs +++ b/tests-integration/tests/hll_test/main.rs @@ -16,5 +16,6 @@ // under the License. mod bounds; +mod summary; mod union; mod update; diff --git a/tests-integration/tests/hll_test/summary.rs b/tests-integration/tests/hll_test/summary.rs new file mode 100644 index 00000000..81396d0f --- /dev/null +++ b/tests-integration/tests/hll_test/summary.rs @@ -0,0 +1,61 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use datasketches::hll::HllSketch; +use datasketches::hll::HllType; +use datasketches::hll::HllUnion; +use insta::assert_snapshot; + +#[test] +fn summary_empty_sketch() { + let sketch = HllSketch::new(12, HllType::Hll8).unwrap(); + + assert_snapshot!(sketch.summary(), @r" + HLL Sketch Summary: + lg config k : 12 + target type : Hll8 + current mode : List + lower bound : 0 + estimate : 0 + upper bound : 0 + "); +} + +#[test] +fn summary_populated_sketch() { + let mut sketch = HllSketch::new(10, HllType::Hll4).unwrap(); + for value in 0..1_000 { + sketch.update(value); + } + + let summary = sketch.summary(); + assert!(summary.contains("target type : Hll4\n")); + assert!(summary.contains("current mode : Hll\n")); + assert!(!summary.contains("estimate : 0\n")); +} + +#[test] +fn summary_union() { + let mut union = HllUnion::new(12).unwrap(); + union.update_value("apple"); + + let summary = union.summary(); + assert!(summary.starts_with("HLL Union Summary:\n")); + assert!(summary.contains("lg max k : 12\n")); + assert!(summary.contains("lg config k : 12\n")); + assert!(!summary.contains("estimate : 0\n")); +}