diff --git a/benchmarks/src/main/java/org/elasticsearch/benchmark/exponentialhistogram/ExponentialHistogramGenerationBench.java b/benchmarks/src/main/java/org/elasticsearch/benchmark/exponentialhistogram/ExponentialHistogramGenerationBench.java index f4c6e5315ed30..121789210eab4 100644 --- a/benchmarks/src/main/java/org/elasticsearch/benchmark/exponentialhistogram/ExponentialHistogramGenerationBench.java +++ b/benchmarks/src/main/java/org/elasticsearch/benchmark/exponentialhistogram/ExponentialHistogramGenerationBench.java @@ -9,6 +9,7 @@ package org.elasticsearch.benchmark.exponentialhistogram; +import org.elasticsearch.exponentialhistogram.ExponentialHistogramCircuitBreaker; import org.elasticsearch.exponentialhistogram.ExponentialHistogramGenerator; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; @@ -59,7 +60,7 @@ public class ExponentialHistogramGenerationBench { @Setup public void setUp() { random = ThreadLocalRandom.current(); - histoGenerator = new ExponentialHistogramGenerator(bucketCount); + histoGenerator = ExponentialHistogramGenerator.create(bucketCount, ExponentialHistogramCircuitBreaker.noop()); DoubleSupplier nextRandom = () -> distribution.equals("GAUSSIAN") ? random.nextGaussian() : random.nextDouble(); diff --git a/benchmarks/src/main/java/org/elasticsearch/benchmark/exponentialhistogram/ExponentialHistogramMergeBench.java b/benchmarks/src/main/java/org/elasticsearch/benchmark/exponentialhistogram/ExponentialHistogramMergeBench.java index 36e56e12a9190..b638ff140bf19 100644 --- a/benchmarks/src/main/java/org/elasticsearch/benchmark/exponentialhistogram/ExponentialHistogramMergeBench.java +++ b/benchmarks/src/main/java/org/elasticsearch/benchmark/exponentialhistogram/ExponentialHistogramMergeBench.java @@ -11,6 +11,7 @@ import org.elasticsearch.exponentialhistogram.BucketIterator; import org.elasticsearch.exponentialhistogram.ExponentialHistogram; +import org.elasticsearch.exponentialhistogram.ExponentialHistogramCircuitBreaker; import org.elasticsearch.exponentialhistogram.ExponentialHistogramGenerator; import org.elasticsearch.exponentialhistogram.ExponentialHistogramMerger; import org.openjdk.jmh.annotations.Benchmark; @@ -56,13 +57,14 @@ public class ExponentialHistogramMergeBench { @Setup public void setUp() { random = ThreadLocalRandom.current(); - histoMerger = new ExponentialHistogramMerger(bucketCount); + ExponentialHistogramCircuitBreaker breaker = ExponentialHistogramCircuitBreaker.noop(); + histoMerger = ExponentialHistogramMerger.create(bucketCount, breaker); - ExponentialHistogramGenerator initial = new ExponentialHistogramGenerator(bucketCount); + ExponentialHistogramGenerator initialGenerator = ExponentialHistogramGenerator.create(bucketCount, breaker); for (int j = 0; j < bucketCount; j++) { - initial.add(Math.pow(1.001, j)); + initialGenerator.add(Math.pow(1.001, j)); } - ExponentialHistogram initialHisto = initial.get(); + ExponentialHistogram initialHisto = initialGenerator.getAndClear(); int cnt = getBucketCount(initialHisto); if (cnt < bucketCount) { throw new IllegalArgumentException("Expected bucket count to be " + bucketCount + ", but was " + cnt); @@ -72,14 +74,14 @@ public void setUp() { int dataPointSize = (int) Math.round(bucketCount * mergedHistoSizeFactor); for (int i = 0; i < toMerge.length; i++) { - ExponentialHistogramGenerator generator = new ExponentialHistogramGenerator(dataPointSize); + ExponentialHistogramGenerator generator = ExponentialHistogramGenerator.create(dataPointSize, breaker); int bucketIndex = 0; for (int j = 0; j < dataPointSize; j++) { bucketIndex += 1 + random.nextInt(bucketCount) % (Math.max(1, bucketCount / dataPointSize)); generator.add(Math.pow(1.001, bucketIndex)); } - toMerge[i] = generator.get(); + toMerge[i] = generator.getAndClear(); cnt = getBucketCount(toMerge[i]); if (cnt < dataPointSize) { throw new IllegalArgumentException("Expected bucket count to be " + dataPointSize + ", but was " + cnt); diff --git a/libs/exponential-histogram/build.gradle b/libs/exponential-histogram/build.gradle index 08628d0f20875..3b163f42f1015 100644 --- a/libs/exponential-histogram/build.gradle +++ b/libs/exponential-histogram/build.gradle @@ -12,6 +12,9 @@ apply plugin: 'elasticsearch.build' dependencies { + api project(':libs:core') + api "org.apache.lucene:lucene-core:${versions.lucene}" + testImplementation(project(":test:framework")) testImplementation('ch.obermuhlner:big-math:2.3.2') testImplementation('org.apache.commons:commons-math3:3.6.1') diff --git a/libs/exponential-histogram/licenses/lucene-core-LICENSE.txt b/libs/exponential-histogram/licenses/lucene-core-LICENSE.txt new file mode 100644 index 0000000000000..28b134f5f8e4d --- /dev/null +++ b/libs/exponential-histogram/licenses/lucene-core-LICENSE.txt @@ -0,0 +1,475 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed 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. + + + +Some code in core/src/java/org/apache/lucene/util/UnicodeUtil.java was +derived from unicode conversion examples available at +http://www.unicode.org/Public/PROGRAMS/CVTUTF. Here is the copyright +from those sources: + +/* + * Copyright 2001-2004 Unicode, Inc. + * + * Disclaimer + * + * This source code is provided as is by Unicode, Inc. No claims are + * made as to fitness for any particular purpose. No warranties of any + * kind are expressed or implied. The recipient agrees to determine + * applicability of information provided. If this file has been + * purchased on magnetic or optical media from Unicode, Inc., the + * sole remedy for any claim will be exchange of defective media + * within 90 days of receipt. + * + * Limitations on Rights to Redistribute This Code + * + * Unicode, Inc. hereby grants the right to freely use the information + * supplied in this file in the creation of products supporting the + * Unicode Standard, and to make copies of this file in any form + * for internal or external distribution as long as this notice + * remains attached. + */ + + +Some code in core/src/java/org/apache/lucene/util/ArrayUtil.java was +derived from Python 2.4.2 sources available at +http://www.python.org. Full license is here: + + http://www.python.org/download/releases/2.4.2/license/ + +Some code in core/src/java/org/apache/lucene/util/UnicodeUtil.java was +derived from Python 3.1.2 sources available at +http://www.python.org. Full license is here: + + http://www.python.org/download/releases/3.1.2/license/ + +Some code in core/src/java/org/apache/lucene/util/automaton was +derived from Brics automaton sources available at +www.brics.dk/automaton/. Here is the copyright from those sources: + +/* + * Copyright (c) 2001-2009 Anders Moeller + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +The levenshtein automata tables in core/src/java/org/apache/lucene/util/automaton +were automatically generated with the moman/finenight FSA package. +Here is the copyright for those sources: + +# Copyright (c) 2010, Jean-Philippe Barrette-LaPierre, +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + +Some code in core/src/java/org/apache/lucene/util/UnicodeUtil.java was +derived from ICU (http://www.icu-project.org) +The full license is available here: + http://source.icu-project.org/repos/icu/icu/trunk/license.html + +/* + * Copyright (C) 1999-2010, International Business Machines + * Corporation and others. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * provided that the above copyright notice(s) and this permission notice appear + * in all copies of the Software and that both the above copyright notice(s) and + * this permission notice appear in supporting documentation. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE + * LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR + * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Except as contained in this notice, the name of a copyright holder shall not + * be used in advertising or otherwise to promote the sale, use or other + * dealings in this Software without prior written authorization of the + * copyright holder. + */ + +The following license applies to the Snowball stemmers: + +Copyright (c) 2001, Dr Martin Porter +Copyright (c) 2002, Richard Boulton +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holders nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The following license applies to the KStemmer: + +Copyright © 2003, +Center for Intelligent Information Retrieval, +University of Massachusetts, Amherst. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. The names "Center for Intelligent Information Retrieval" and +"University of Massachusetts" must not be used to endorse or promote products +derived from this software without prior written permission. To obtain +permission, contact info@ciir.cs.umass.edu. + +THIS SOFTWARE IS PROVIDED BY UNIVERSITY OF MASSACHUSETTS AND OTHER CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. + +The following license applies to the Morfologik project: + +Copyright (c) 2006 Dawid Weiss +Copyright (c) 2007-2011 Dawid Weiss, Marcin Miłkowski +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of Morfologik nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +--- + +The dictionary comes from Morfologik project. Morfologik uses data from +Polish ispell/myspell dictionary hosted at http://www.sjp.pl/slownik/en/ and +is licenced on the terms of (inter alia) LGPL and Creative Commons +ShareAlike. The part-of-speech tags were added in Morfologik project and +are not found in the data from sjp.pl. The tagset is similar to IPI PAN +tagset. + +--- + +The following license applies to the Morfeusz project, +used by org.apache.lucene.analysis.morfologik. + +BSD-licensed dictionary of Polish (SGJP) +http://sgjp.pl/morfeusz/ + +Copyright © 2011 Zygmunt Saloni, Włodzimierz Gruszczyński, + Marcin Woliński, Robert Wołosz + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the + distribution. + +THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDERS “AS IS” AND ANY EXPRESS +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDERS OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/libs/exponential-histogram/licenses/lucene-core-NOTICE.txt b/libs/exponential-histogram/licenses/lucene-core-NOTICE.txt new file mode 100644 index 0000000000000..1a1d51572432a --- /dev/null +++ b/libs/exponential-histogram/licenses/lucene-core-NOTICE.txt @@ -0,0 +1,192 @@ +Apache Lucene +Copyright 2014 The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). + +Includes software from other Apache Software Foundation projects, +including, but not limited to: + - Apache Ant + - Apache Jakarta Regexp + - Apache Commons + - Apache Xerces + +ICU4J, (under analysis/icu) is licensed under an MIT styles license +and Copyright (c) 1995-2008 International Business Machines Corporation and others + +Some data files (under analysis/icu/src/data) are derived from Unicode data such +as the Unicode Character Database. See http://unicode.org/copyright.html for more +details. + +Brics Automaton (under core/src/java/org/apache/lucene/util/automaton) is +BSD-licensed, created by Anders Møller. See http://www.brics.dk/automaton/ + +The levenshtein automata tables (under core/src/java/org/apache/lucene/util/automaton) were +automatically generated with the moman/finenight FSA library, created by +Jean-Philippe Barrette-LaPierre. This library is available under an MIT license, +see http://sites.google.com/site/rrettesite/moman and +http://bitbucket.org/jpbarrette/moman/overview/ + +The class org.apache.lucene.util.WeakIdentityMap was derived from +the Apache CXF project and is Apache License 2.0. + +The Google Code Prettify is Apache License 2.0. +See http://code.google.com/p/google-code-prettify/ + +JUnit (junit-4.10) is licensed under the Common Public License v. 1.0 +See http://junit.sourceforge.net/cpl-v10.html + +This product includes code (JaspellTernarySearchTrie) from Java Spelling Checkin +g Package (jaspell): http://jaspell.sourceforge.net/ +License: The BSD License (http://www.opensource.org/licenses/bsd-license.php) + +The snowball stemmers in + analysis/common/src/java/net/sf/snowball +were developed by Martin Porter and Richard Boulton. +The snowball stopword lists in + analysis/common/src/resources/org/apache/lucene/analysis/snowball +were developed by Martin Porter and Richard Boulton. +The full snowball package is available from + http://snowball.tartarus.org/ + +The KStem stemmer in + analysis/common/src/org/apache/lucene/analysis/en +was developed by Bob Krovetz and Sergio Guzman-Lara (CIIR-UMass Amherst) +under the BSD-license. + +The Arabic,Persian,Romanian,Bulgarian, Hindi and Bengali analyzers (common) come with a default +stopword list that is BSD-licensed created by Jacques Savoy. These files reside in: +analysis/common/src/resources/org/apache/lucene/analysis/ar/stopwords.txt, +analysis/common/src/resources/org/apache/lucene/analysis/fa/stopwords.txt, +analysis/common/src/resources/org/apache/lucene/analysis/ro/stopwords.txt, +analysis/common/src/resources/org/apache/lucene/analysis/bg/stopwords.txt, +analysis/common/src/resources/org/apache/lucene/analysis/hi/stopwords.txt, +analysis/common/src/resources/org/apache/lucene/analysis/bn/stopwords.txt +See http://members.unine.ch/jacques.savoy/clef/index.html. + +The German,Spanish,Finnish,French,Hungarian,Italian,Portuguese,Russian and Swedish light stemmers +(common) are based on BSD-licensed reference implementations created by Jacques Savoy and +Ljiljana Dolamic. These files reside in: +analysis/common/src/java/org/apache/lucene/analysis/de/GermanLightStemmer.java +analysis/common/src/java/org/apache/lucene/analysis/de/GermanMinimalStemmer.java +analysis/common/src/java/org/apache/lucene/analysis/es/SpanishLightStemmer.java +analysis/common/src/java/org/apache/lucene/analysis/fi/FinnishLightStemmer.java +analysis/common/src/java/org/apache/lucene/analysis/fr/FrenchLightStemmer.java +analysis/common/src/java/org/apache/lucene/analysis/fr/FrenchMinimalStemmer.java +analysis/common/src/java/org/apache/lucene/analysis/hu/HungarianLightStemmer.java +analysis/common/src/java/org/apache/lucene/analysis/it/ItalianLightStemmer.java +analysis/common/src/java/org/apache/lucene/analysis/pt/PortugueseLightStemmer.java +analysis/common/src/java/org/apache/lucene/analysis/ru/RussianLightStemmer.java +analysis/common/src/java/org/apache/lucene/analysis/sv/SwedishLightStemmer.java + +The Stempel analyzer (stempel) includes BSD-licensed software developed +by the Egothor project http://egothor.sf.net/, created by Leo Galambos, Martin Kvapil, +and Edmond Nolan. + +The Polish analyzer (stempel) comes with a default +stopword list that is BSD-licensed created by the Carrot2 project. The file resides +in stempel/src/resources/org/apache/lucene/analysis/pl/stopwords.txt. +See http://project.carrot2.org/license.html. + +The SmartChineseAnalyzer source code (smartcn) was +provided by Xiaoping Gao and copyright 2009 by www.imdict.net. + +WordBreakTestUnicode_*.java (under modules/analysis/common/src/test/) +is derived from Unicode data such as the Unicode Character Database. +See http://unicode.org/copyright.html for more details. + +The Morfologik analyzer (morfologik) includes BSD-licensed software +developed by Dawid Weiss and Marcin Miłkowski (http://morfologik.blogspot.com/). + +Morfologik uses data from Polish ispell/myspell dictionary +(http://www.sjp.pl/slownik/en/) licenced on the terms of (inter alia) +LGPL and Creative Commons ShareAlike. + +Morfologic includes data from BSD-licensed dictionary of Polish (SGJP) +(http://sgjp.pl/morfeusz/) + +Servlet-api.jar and javax.servlet-*.jar are under the CDDL license, the original +source code for this can be found at http://www.eclipse.org/jetty/downloads.php + +=========================================================================== +Kuromoji Japanese Morphological Analyzer - Apache Lucene Integration +=========================================================================== + +This software includes a binary and/or source version of data from + + mecab-ipadic-2.7.0-20070801 + +which can be obtained from + + http://atilika.com/releases/mecab-ipadic/mecab-ipadic-2.7.0-20070801.tar.gz + +or + + http://jaist.dl.sourceforge.net/project/mecab/mecab-ipadic/2.7.0-20070801/mecab-ipadic-2.7.0-20070801.tar.gz + +=========================================================================== +mecab-ipadic-2.7.0-20070801 Notice +=========================================================================== + +Nara Institute of Science and Technology (NAIST), +the copyright holders, disclaims all warranties with regard to this +software, including all implied warranties of merchantability and +fitness, in no event shall NAIST be liable for +any special, indirect or consequential damages or any damages +whatsoever resulting from loss of use, data or profits, whether in an +action of contract, negligence or other tortuous action, arising out +of or in connection with the use or performance of this software. + +A large portion of the dictionary entries +originate from ICOT Free Software. The following conditions for ICOT +Free Software applies to the current dictionary as well. + +Each User may also freely distribute the Program, whether in its +original form or modified, to any third party or parties, PROVIDED +that the provisions of Section 3 ("NO WARRANTY") will ALWAYS appear +on, or be attached to, the Program, which is distributed substantially +in the same form as set out herein and that such intended +distribution, if actually made, will neither violate or otherwise +contravene any of the laws and regulations of the countries having +jurisdiction over the User or the intended distribution itself. + +NO WARRANTY + +The program was produced on an experimental basis in the course of the +research and development conducted during the project and is provided +to users as so produced on an experimental basis. Accordingly, the +program is provided without any warranty whatsoever, whether express, +implied, statutory or otherwise. The term "warranty" used herein +includes, but is not limited to, any warranty of the quality, +performance, merchantability and fitness for a particular purpose of +the program and the nonexistence of any infringement or violation of +any right of any third party. + +Each user of the program will agree and understand, and be deemed to +have agreed and understood, that there is no warranty whatsoever for +the program and, accordingly, the entire risk arising from or +otherwise connected with the program is assumed by the user. + +Therefore, neither ICOT, the copyright holder, or any other +organization that participated in or was otherwise related to the +development of the program and their respective officials, directors, +officers and other employees shall be held liable for any and all +damages, including, without limitation, general, special, incidental +and consequential damages, arising out of or otherwise in connection +with the use or inability to use the program or any product, material +or result produced or otherwise obtained by using the program, +regardless of whether they have been advised of, or otherwise had +knowledge of, the possibility of such damages at any time during the +project or thereafter. Each user will be deemed to have agreed to the +foregoing by his or her commencement of use of the program. The term +"use" as used herein includes, but is not limited to, the use, +modification, copying and distribution of the program and the +production of secondary products from the program. + +In the case where the program, whether in its original form or +modified, was distributed or delivered to or received by a user from +any person, organization or entity other than ICOT, unless it makes or +grants independently of ICOT any specific warranty to the user in +writing, such person, organization or entity, will also be exempted +from and not be held liable to the user for any such damages as noted +above as far as the program is concerned. diff --git a/libs/exponential-histogram/src/main/java/org/elasticsearch/exponentialhistogram/BucketArrayIterator.java b/libs/exponential-histogram/src/main/java/org/elasticsearch/exponentialhistogram/BucketArrayIterator.java new file mode 100644 index 0000000000000..8ddd259bea739 --- /dev/null +++ b/libs/exponential-histogram/src/main/java/org/elasticsearch/exponentialhistogram/BucketArrayIterator.java @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V., and/or licensed to Elasticsearch B.V. + * under one or more license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. 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. + * + * This file is based on a modification of https://github.com/open-telemetry/opentelemetry-java which is licensed under the Apache 2.0 License. + */ + +package org.elasticsearch.exponentialhistogram; + +class BucketArrayIterator implements CopyableBucketIterator { + + private final int scale; + private final long[] bucketCounts; + private final long[] bucketIndices; + + private int currentSlot; + private final int limit; + + BucketArrayIterator(int scale, long[] bucketCounts, long[] bucketIndices, int startSlot, int limit) { + this.scale = scale; + this.bucketCounts = bucketCounts; + this.bucketIndices = bucketIndices; + this.currentSlot = startSlot; + this.limit = limit; + } + + @Override + public boolean hasNext() { + return currentSlot < limit; + } + + @Override + public long peekCount() { + ensureEndNotReached(); + return bucketCounts[currentSlot]; + } + + @Override + public long peekIndex() { + ensureEndNotReached(); + return bucketIndices[currentSlot]; + } + + @Override + public void advance() { + ensureEndNotReached(); + currentSlot++; + } + + @Override + public int scale() { + return scale; + } + + @Override + public CopyableBucketIterator copy() { + return new BucketArrayIterator(scale, bucketCounts, bucketIndices, currentSlot, limit); + } + + private void ensureEndNotReached() { + if (hasNext() == false) { + throw new IllegalStateException("Iterator has no more buckets"); + } + } +} diff --git a/libs/exponential-histogram/src/main/java/org/elasticsearch/exponentialhistogram/DownscaleStats.java b/libs/exponential-histogram/src/main/java/org/elasticsearch/exponentialhistogram/DownscaleStats.java index 30926ed5865b4..4cf8f6f89d18f 100644 --- a/libs/exponential-histogram/src/main/java/org/elasticsearch/exponentialhistogram/DownscaleStats.java +++ b/libs/exponential-histogram/src/main/java/org/elasticsearch/exponentialhistogram/DownscaleStats.java @@ -21,6 +21,8 @@ package org.elasticsearch.exponentialhistogram; +import org.apache.lucene.util.RamUsageEstimator; + import java.util.Arrays; import static org.elasticsearch.exponentialhistogram.ExponentialHistogram.MAX_INDEX; @@ -33,6 +35,8 @@ */ class DownscaleStats { + static final long SIZE = RamUsageEstimator.shallowSizeOf(DownscaleStats.class) + RamEstimationUtil.estimateIntArray(MAX_INDEX_BITS); + // collapsedBucketCount[i] stores the number of additional // collapsed buckets when increasing the scale by (i+1) instead of just by (i) int[] collapsedBucketCount = new int[MAX_INDEX_BITS]; diff --git a/libs/exponential-histogram/src/main/java/org/elasticsearch/exponentialhistogram/EmptyExponentialHistogram.java b/libs/exponential-histogram/src/main/java/org/elasticsearch/exponentialhistogram/EmptyExponentialHistogram.java new file mode 100644 index 0000000000000..f2d21f0861065 --- /dev/null +++ b/libs/exponential-histogram/src/main/java/org/elasticsearch/exponentialhistogram/EmptyExponentialHistogram.java @@ -0,0 +1,84 @@ +/* + * Copyright Elasticsearch B.V., and/or licensed to Elasticsearch B.V. + * under one or more license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. 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. + * + * This file is based on a modification of https://github.com/open-telemetry/opentelemetry-java which is licensed under the Apache 2.0 License. + */ + +package org.elasticsearch.exponentialhistogram; + +import java.util.OptionalLong; + +class EmptyExponentialHistogram implements ReleasableExponentialHistogram { + + static final EmptyExponentialHistogram INSTANCE = new EmptyExponentialHistogram(); + + /** + * The default empty histogram always has MAX_SCALE to not cause accidental downscaling + * when combining with other histograms. + */ + private static final int SCALE = ExponentialHistogram.MAX_SCALE; + + private static class EmptyBuckets implements Buckets { + + private static final EmptyBuckets INSTANCE = new EmptyBuckets(); + private static final CopyableBucketIterator EMPTY_ITERATOR = new BucketArrayIterator(SCALE, new long[0], new long[0], 0, 0); + + @Override + public CopyableBucketIterator iterator() { + return EMPTY_ITERATOR; + } + + @Override + public OptionalLong maxBucketIndex() { + return OptionalLong.empty(); + } + + @Override + public long valueCount() { + return 0; + } + } + + @Override + public void close() {} + + @Override + public int scale() { + return SCALE; + } + + @Override + public ZeroBucket zeroBucket() { + return ZeroBucket.minimalEmpty(); + } + + @Override + public Buckets positiveBuckets() { + return EmptyBuckets.INSTANCE; + } + + @Override + public Buckets negativeBuckets() { + return EmptyBuckets.INSTANCE; + } + + @Override + public long ramBytesUsed() { + return 0; + } +} diff --git a/libs/exponential-histogram/src/main/java/org/elasticsearch/exponentialhistogram/ExponentialHistogram.java b/libs/exponential-histogram/src/main/java/org/elasticsearch/exponentialhistogram/ExponentialHistogram.java index 38583d05c8d6f..f4603f3fe679c 100644 --- a/libs/exponential-histogram/src/main/java/org/elasticsearch/exponentialhistogram/ExponentialHistogram.java +++ b/libs/exponential-histogram/src/main/java/org/elasticsearch/exponentialhistogram/ExponentialHistogram.java @@ -21,6 +21,8 @@ package org.elasticsearch.exponentialhistogram; +import org.apache.lucene.util.Accountable; + import java.util.Iterator; import java.util.List; import java.util.OptionalLong; @@ -43,7 +45,7 @@ * Additionally, all algorithms assume that samples within a bucket are located at a single point: the point of least relative error * (see {@link ExponentialScaleUtils#getPointOfLeastRelativeError(long, int)}). */ -public interface ExponentialHistogram { +public interface ExponentialHistogram extends Accountable { // TODO(b/128622): support min/max/sum/count storage and merging. // TODO(b/128622): Add special positive and negative infinity buckets @@ -114,47 +116,64 @@ interface Buckets { } + static ExponentialHistogram empty() { + return EmptyExponentialHistogram.INSTANCE; + } + /** * Creates a histogram representing the distribution of the given values with at most the given number of buckets. * If the given {@code maxBucketCount} is greater than or equal to the number of values, the resulting histogram will have a * relative error of less than {@code 2^(2^-MAX_SCALE) - 1}. * * @param maxBucketCount the maximum number of buckets + * @param breaker the circuit breaker to use to limit memory allocations * @param values the values to be added to the histogram - * @return a new {@link ExponentialHistogram} + * @return a new {@link ReleasableExponentialHistogram} */ - static ExponentialHistogram create(int maxBucketCount, double... values) { - ExponentialHistogramGenerator generator = new ExponentialHistogramGenerator(maxBucketCount); - for (double val : values) { - generator.add(val); + static ReleasableExponentialHistogram create(int maxBucketCount, ExponentialHistogramCircuitBreaker breaker, double... values) { + try (ExponentialHistogramGenerator generator = ExponentialHistogramGenerator.create(maxBucketCount, breaker)) { + for (double val : values) { + generator.add(val); + } + return generator.getAndClear(); } - return generator.get(); } /** * Merges the provided exponential histograms to a new, single histogram with at most the given amount of buckets. * * @param maxBucketCount the maximum number of buckets the result histogram is allowed to have - * @param histograms teh histograms to merge + * @param breaker the circuit breaker to use to limit memory allocations + * @param histograms the histograms to merge * @return the merged histogram */ - static ExponentialHistogram merge(int maxBucketCount, Iterator histograms) { - ExponentialHistogramMerger merger = new ExponentialHistogramMerger(maxBucketCount); - while (histograms.hasNext()) { - merger.add(histograms.next()); + static ReleasableExponentialHistogram merge( + int maxBucketCount, + ExponentialHistogramCircuitBreaker breaker, + Iterator histograms + ) { + try (ExponentialHistogramMerger merger = ExponentialHistogramMerger.create(maxBucketCount, breaker)) { + while (histograms.hasNext()) { + merger.add(histograms.next()); + } + return merger.getAndClear(); } - return merger.get(); } /** * Merges the provided exponential histograms to a new, single histogram with at most the given amount of buckets. * * @param maxBucketCount the maximum number of buckets the result histogram is allowed to have - * @param histograms teh histograms to merge + * @param breaker the circuit breaker to use to limit memory allocations + * @param histograms the histograms to merge * @return the merged histogram */ - static ExponentialHistogram merge(int maxBucketCount, ExponentialHistogram... histograms) { - return merge(maxBucketCount, List.of(histograms).iterator()); + static ReleasableExponentialHistogram merge( + int maxBucketCount, + ExponentialHistogramCircuitBreaker breaker, + ExponentialHistogram... histograms + ) { + return merge(maxBucketCount, breaker, List.of(histograms).iterator()); } } diff --git a/libs/exponential-histogram/src/main/java/org/elasticsearch/exponentialhistogram/ExponentialHistogramCircuitBreaker.java b/libs/exponential-histogram/src/main/java/org/elasticsearch/exponentialhistogram/ExponentialHistogramCircuitBreaker.java new file mode 100644 index 0000000000000..8bf21fd9e57f7 --- /dev/null +++ b/libs/exponential-histogram/src/main/java/org/elasticsearch/exponentialhistogram/ExponentialHistogramCircuitBreaker.java @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V., and/or licensed to Elasticsearch B.V. + * under one or more license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. 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. + * + * This file is based on a modification of https://github.com/open-telemetry/opentelemetry-java which is licensed under the Apache 2.0 License. + */ + +package org.elasticsearch.exponentialhistogram; + +/** + * Interface for a memory-allocation circuit breaker used for {@link ReleasableExponentialHistogram}s. + */ +public interface ExponentialHistogramCircuitBreaker { + + /** + * Adjusts the circuit breaker, potentially throwing an exception if the limit is exceeded. + * Guaranteed to never cause an exception when called with a negative number to reduce the breaker count. + * + * @param bytesAllocated the number of bytes allocated, or a negative value if deallocated + */ + void adjustBreaker(long bytesAllocated); + + static ExponentialHistogramCircuitBreaker noop() { + return bytesAllocated -> {}; + } + +} diff --git a/libs/exponential-histogram/src/main/java/org/elasticsearch/exponentialhistogram/ExponentialHistogramGenerator.java b/libs/exponential-histogram/src/main/java/org/elasticsearch/exponentialhistogram/ExponentialHistogramGenerator.java index 13f2fa4215a36..ef4e7f57dba44 100644 --- a/libs/exponential-histogram/src/main/java/org/elasticsearch/exponentialhistogram/ExponentialHistogramGenerator.java +++ b/libs/exponential-histogram/src/main/java/org/elasticsearch/exponentialhistogram/ExponentialHistogramGenerator.java @@ -21,6 +21,11 @@ package org.elasticsearch.exponentialhistogram; +import org.apache.lucene.util.Accountable; +import org.apache.lucene.util.RamUsageEstimator; +import org.elasticsearch.core.Releasable; +import org.elasticsearch.core.Releasables; + import java.util.Arrays; import static org.elasticsearch.exponentialhistogram.ExponentialScaleUtils.computeIndex; @@ -32,42 +37,64 @@ * If the number of values is less than or equal to the bucket capacity, the resulting histogram is guaranteed * to represent the exact raw values with a relative error less than {@code 2^(2^-MAX_SCALE) - 1}. */ -public class ExponentialHistogramGenerator { +public class ExponentialHistogramGenerator implements Accountable, Releasable { + + private static final long SHALLOW_SIZE = RamUsageEstimator.shallowSizeOfInstance(ExponentialHistogramGenerator.class); // Merging individual values into a histogram would be way too slow with our sparse, array-backed histogram representation. // Therefore, for a bucket capacity of c, we first buffer c raw values to be inserted. // We then turn those into an "exact" histogram, which in turn we merge with our actual result accumulator. // This yields an amortized runtime of O(log(c)). private final double[] rawValueBuffer; - int valueCount; + private int valueCount; private final ExponentialHistogramMerger resultMerger; private final FixedCapacityExponentialHistogram valueBuffer; - private boolean isFinished = false; + private final ExponentialHistogramCircuitBreaker circuitBreaker; + private boolean closed = false; /** * Creates a new instance with the specified maximum number of buckets. * * @param maxBucketCount the maximum number of buckets for the generated histogram + * @param circuitBreaker the circuit breaker to use to limit memory allocations */ - public ExponentialHistogramGenerator(int maxBucketCount) { + public static ExponentialHistogramGenerator create(int maxBucketCount, ExponentialHistogramCircuitBreaker circuitBreaker) { + long size = estimateBaseSize(maxBucketCount); + circuitBreaker.adjustBreaker(size); + try { + return new ExponentialHistogramGenerator(maxBucketCount, circuitBreaker); + } catch (RuntimeException e) { + circuitBreaker.adjustBreaker(-size); + throw e; + } + } + + private ExponentialHistogramGenerator(int maxBucketCount, ExponentialHistogramCircuitBreaker circuitBreaker) { + this.circuitBreaker = circuitBreaker; rawValueBuffer = new double[maxBucketCount]; valueCount = 0; - valueBuffer = new FixedCapacityExponentialHistogram(maxBucketCount); - resultMerger = new ExponentialHistogramMerger(maxBucketCount); + FixedCapacityExponentialHistogram buffer = null; + ExponentialHistogramMerger merger = null; + try { + buffer = FixedCapacityExponentialHistogram.create(maxBucketCount, circuitBreaker); + merger = ExponentialHistogramMerger.create(maxBucketCount, circuitBreaker); + } catch (RuntimeException e) { + Releasables.close(buffer, merger); + throw e; + } + this.valueBuffer = buffer; + this.resultMerger = merger; } /** * Adds the given value to the histogram. - * Must not be called after {@link #get()} has been called. * * @param value the value to add */ public void add(double value) { - if (isFinished) { - throw new IllegalStateException("get() has already been called"); - } + assert closed == false : "ExponentialHistogramGenerator has already been closed"; if (valueCount == rawValueBuffer.length) { mergeValuesToHistogram(); } @@ -80,10 +107,9 @@ public void add(double value) { * * @return the histogram representing the distribution of all accumulated values */ - public ExponentialHistogram get() { - isFinished = true; + public ReleasableExponentialHistogram getAndClear() { mergeValuesToHistogram(); - return resultMerger.get(); + return resultMerger.getAndClear(); } private void mergeValuesToHistogram() { @@ -135,4 +161,24 @@ private void mergeValuesToHistogram() { valueCount = 0; } + private static long estimateBaseSize(int numBuckets) { + return SHALLOW_SIZE + RamEstimationUtil.estimateDoubleArray(numBuckets); + }; + + @Override + public long ramBytesUsed() { + return estimateBaseSize(rawValueBuffer.length) + resultMerger.ramBytesUsed() + valueBuffer.ramBytesUsed(); + } + + @Override + public void close() { + if (closed) { + assert false : "ExponentialHistogramGenerator closed multiple times"; + } else { + closed = true; + resultMerger.close(); + valueBuffer.close(); + circuitBreaker.adjustBreaker(-estimateBaseSize(rawValueBuffer.length)); + } + } } diff --git a/libs/exponential-histogram/src/main/java/org/elasticsearch/exponentialhistogram/ExponentialHistogramMerger.java b/libs/exponential-histogram/src/main/java/org/elasticsearch/exponentialhistogram/ExponentialHistogramMerger.java index b00ad053837d9..3228e7083de0e 100644 --- a/libs/exponential-histogram/src/main/java/org/elasticsearch/exponentialhistogram/ExponentialHistogramMerger.java +++ b/libs/exponential-histogram/src/main/java/org/elasticsearch/exponentialhistogram/ExponentialHistogramMerger.java @@ -21,6 +21,11 @@ package org.elasticsearch.exponentialhistogram; +import org.apache.lucene.util.Accountable; +import org.apache.lucene.util.RamUsageEstimator; +import org.elasticsearch.core.Nullable; +import org.elasticsearch.core.Releasable; + import java.util.OptionalLong; import static org.elasticsearch.exponentialhistogram.ExponentialScaleUtils.getMaximumScaleIncrease; @@ -29,59 +34,93 @@ * Allows accumulating multiple {@link ExponentialHistogram} into a single one * while keeping the bucket count in the result below a given limit. */ -public class ExponentialHistogramMerger { +public class ExponentialHistogramMerger implements Accountable, Releasable { + + private static final long BASE_SIZE = RamUsageEstimator.shallowSizeOfInstance(ExponentialHistogramMerger.class) + DownscaleStats.SIZE; // Our algorithm is not in-place, therefore we use two histograms and ping-pong between them + @Nullable private FixedCapacityExponentialHistogram result; + @Nullable private FixedCapacityExponentialHistogram buffer; + private final int bucketLimit; + private final int maxScale; + private final DownscaleStats downscaleStats; - private boolean isFinished; + private final ExponentialHistogramCircuitBreaker circuitBreaker; + private boolean closed = false; /** * Creates a new instance with the specified bucket limit. * * @param bucketLimit the maximum number of buckets the result histogram is allowed to have + * @param circuitBreaker the circuit breaker to use to limit memory allocations */ - public ExponentialHistogramMerger(int bucketLimit) { - downscaleStats = new DownscaleStats(); - result = new FixedCapacityExponentialHistogram(bucketLimit); - buffer = new FixedCapacityExponentialHistogram(bucketLimit); + public static ExponentialHistogramMerger create(int bucketLimit, ExponentialHistogramCircuitBreaker circuitBreaker) { + circuitBreaker.adjustBreaker(BASE_SIZE); + return new ExponentialHistogramMerger(bucketLimit, circuitBreaker); + } + + private ExponentialHistogramMerger(int bucketLimit, ExponentialHistogramCircuitBreaker circuitBreaker) { + this(bucketLimit, ExponentialHistogram.MAX_SCALE, circuitBreaker); } // Only intended for testing, using this in production means an unnecessary reduction of precision - private ExponentialHistogramMerger(int bucketLimit, int minScale) { - this(bucketLimit); - result.resetBuckets(minScale); - buffer.resetBuckets(minScale); + private ExponentialHistogramMerger(int bucketLimit, int maxScale, ExponentialHistogramCircuitBreaker circuitBreaker) { + this.bucketLimit = bucketLimit; + this.maxScale = maxScale; + this.circuitBreaker = circuitBreaker; + downscaleStats = new DownscaleStats(); } - static ExponentialHistogramMerger createForTesting(int bucketLimit, int minScale) { - return new ExponentialHistogramMerger(bucketLimit, minScale); + static ExponentialHistogramMerger createForTesting(int bucketLimit, int maxScale, ExponentialHistogramCircuitBreaker circuitBreaker) { + circuitBreaker.adjustBreaker(BASE_SIZE); + return new ExponentialHistogramMerger(bucketLimit, maxScale, circuitBreaker); } - /** - * Merges the given histogram into the current result. - * Must not be called after {@link #get()} has been called. - * - * @param toAdd the histogram to merge - */ - public void add(ExponentialHistogram toAdd) { - if (isFinished) { - throw new IllegalStateException("get() has already been called"); + @Override + public void close() { + if (closed) { + assert false : "ExponentialHistogramMerger closed multiple times"; + } else { + closed = true; + if (result != null) { + result.close(); + result = null; + } + if (buffer != null) { + buffer.close(); + buffer = null; + } + circuitBreaker.adjustBreaker(-BASE_SIZE); } - doMerge(toAdd); + } + + @Override + public long ramBytesUsed() { + long size = BASE_SIZE; + if (result != null) { + size += result.ramBytesUsed(); + } + if (buffer != null) { + size += buffer.ramBytesUsed(); + } + return size; } /** - * Returns the merged histogram. + * Returns the merged histogram and clears this merger. + * The caller takes ownership of the returned histogram and must ensure that {@link #close()} is called. * * @return the merged histogram */ - public ExponentialHistogram get() { - isFinished = true; - return result; + public ReleasableExponentialHistogram getAndClear() { + assert closed == false : "ExponentialHistogramMerger already closed"; + ReleasableExponentialHistogram retVal = result == null ? ReleasableExponentialHistogram.empty() : result; + result = null; + return retVal; } // TODO(b/128622): this algorithm is very efficient if b has roughly as many buckets as a @@ -90,9 +129,14 @@ public ExponentialHistogram get() { // then in O(log(n)) turn them into a single, merged histogram. // (n is the number of buffered buckets) - private void doMerge(ExponentialHistogram b) { - - ExponentialHistogram a = result; + /** + * Merges the given histogram into the current result. + * + * @param toAdd the histogram to merge + */ + public void add(ExponentialHistogram toAdd) { + ExponentialHistogram a = result == null ? ExponentialHistogram.empty() : result; + ExponentialHistogram b = toAdd; CopyableBucketIterator posBucketsA = a.positiveBuckets().iterator(); CopyableBucketIterator negBucketsA = a.negativeBuckets().iterator(); @@ -102,12 +146,15 @@ private void doMerge(ExponentialHistogram b) { ZeroBucket zeroBucket = a.zeroBucket().merge(b.zeroBucket()); zeroBucket = zeroBucket.collapseOverlappingBucketsForAll(posBucketsA, negBucketsA, posBucketsB, negBucketsB); + if (buffer == null) { + buffer = FixedCapacityExponentialHistogram.create(bucketLimit, circuitBreaker); + } buffer.setZeroBucket(zeroBucket); // We attempt to bring everything to the scale of A. // This might involve increasing the scale for B, which would increase its indices. // We need to ensure that we do not exceed MAX_INDEX / MIN_INDEX in this case. - int targetScale = a.scale(); + int targetScale = Math.min(maxScale, a.scale()); if (targetScale > b.scale()) { if (negBucketsB.hasNext()) { long smallestIndex = negBucketsB.peekIndex(); diff --git a/libs/exponential-histogram/src/main/java/org/elasticsearch/exponentialhistogram/FixedCapacityExponentialHistogram.java b/libs/exponential-histogram/src/main/java/org/elasticsearch/exponentialhistogram/FixedCapacityExponentialHistogram.java index 5dde66d4a46ec..255206cad8e81 100644 --- a/libs/exponential-histogram/src/main/java/org/elasticsearch/exponentialhistogram/FixedCapacityExponentialHistogram.java +++ b/libs/exponential-histogram/src/main/java/org/elasticsearch/exponentialhistogram/FixedCapacityExponentialHistogram.java @@ -21,6 +21,8 @@ package org.elasticsearch.exponentialhistogram; +import org.apache.lucene.util.RamUsageEstimator; + import java.util.OptionalLong; /** @@ -29,7 +31,10 @@ * Consumers must ensure that if the histogram is mutated, all previously acquired {@link BucketIterator} * instances are no longer used. */ -final class FixedCapacityExponentialHistogram implements ExponentialHistogram { +final class FixedCapacityExponentialHistogram implements ReleasableExponentialHistogram { + + static final long BASE_SIZE = RamUsageEstimator.shallowSizeOfInstance(FixedCapacityExponentialHistogram.class) + ZeroBucket.SHALLOW_SIZE + + 2 * Buckets.SHALLOW_SIZE; // These arrays represent both the positive and the negative buckets. // To avoid confusion, we refer to positions within the array as "slots" instead of indices in this file @@ -48,13 +53,22 @@ final class FixedCapacityExponentialHistogram implements ExponentialHistogram { private final Buckets positiveBuckets = new Buckets(true); + private final ExponentialHistogramCircuitBreaker circuitBreaker; + private boolean closed = false; + + static FixedCapacityExponentialHistogram create(int bucketCapacity, ExponentialHistogramCircuitBreaker circuitBreaker) { + circuitBreaker.adjustBreaker(estimateSize(bucketCapacity)); + return new FixedCapacityExponentialHistogram(bucketCapacity, circuitBreaker); + } + /** * Creates an empty histogram with the given capacity and a {@link ZeroBucket#minimalEmpty()} zero bucket. * The scale is initialized to the maximum possible precision ({@link #MAX_SCALE}). * * @param bucketCapacity the maximum total number of positive and negative buckets this histogram can hold. */ - FixedCapacityExponentialHistogram(int bucketCapacity) { + private FixedCapacityExponentialHistogram(int bucketCapacity, ExponentialHistogramCircuitBreaker circuitBreaker) { + this.circuitBreaker = circuitBreaker; bucketIndices = new long[bucketCapacity]; bucketCounts = new long[bucketCapacity]; reset(); @@ -142,8 +156,29 @@ public ExponentialHistogram.Buckets positiveBuckets() { return positiveBuckets; } + @Override + public void close() { + if (closed) { + assert false : "FixedCapacityExponentialHistogram closed multiple times"; + } else { + closed = true; + circuitBreaker.adjustBreaker(-ramBytesUsed()); + } + } + + static long estimateSize(int bucketCapacity) { + return BASE_SIZE + 2 * RamEstimationUtil.estimateLongArray(bucketCapacity); + } + + @Override + public long ramBytesUsed() { + return estimateSize(bucketIndices.length); + } + private class Buckets implements ExponentialHistogram.Buckets { + static final long SHALLOW_SIZE = RamUsageEstimator.shallowSizeOfInstance(Buckets.class); + private final boolean isPositive; private int numBuckets; private int cachedValueSumForNumBuckets; @@ -186,7 +221,7 @@ boolean tryAddBucket(long index, long count) { @Override public CopyableBucketIterator iterator() { int start = startSlot(); - return new BucketArrayIterator(start, start + numBuckets); + return new BucketArrayIterator(bucketScale, bucketCounts, bucketIndices, start, start + numBuckets); } @Override @@ -209,53 +244,4 @@ public long valueCount() { } } - private class BucketArrayIterator implements CopyableBucketIterator { - - int currentSlot; - final int limit; - - private BucketArrayIterator(int startSlot, int limit) { - this.currentSlot = startSlot; - this.limit = limit; - } - - @Override - public boolean hasNext() { - return currentSlot < limit; - } - - @Override - public long peekCount() { - ensureEndNotReached(); - return bucketCounts[currentSlot]; - } - - @Override - public long peekIndex() { - ensureEndNotReached(); - return bucketIndices[currentSlot]; - } - - @Override - public void advance() { - ensureEndNotReached(); - currentSlot++; - } - - @Override - public int scale() { - return FixedCapacityExponentialHistogram.this.scale(); - } - - @Override - public CopyableBucketIterator copy() { - return new BucketArrayIterator(currentSlot, limit); - } - - private void ensureEndNotReached() { - if (hasNext() == false) { - throw new IllegalStateException("Iterator has no more buckets"); - } - } - } } diff --git a/libs/exponential-histogram/src/main/java/org/elasticsearch/exponentialhistogram/RamEstimationUtil.java b/libs/exponential-histogram/src/main/java/org/elasticsearch/exponentialhistogram/RamEstimationUtil.java new file mode 100644 index 0000000000000..25f5eb69d31f4 --- /dev/null +++ b/libs/exponential-histogram/src/main/java/org/elasticsearch/exponentialhistogram/RamEstimationUtil.java @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V., and/or licensed to Elasticsearch B.V. + * under one or more license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. 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. + * + * This file is based on a modification of https://github.com/open-telemetry/opentelemetry-java which is licensed under the Apache 2.0 License. + */ + +package org.elasticsearch.exponentialhistogram; + +import org.apache.lucene.util.RamUsageEstimator; + +class RamEstimationUtil { + + private static long estimatedArraySize(int arrayLength, int bytesPerElement) { + return RamUsageEstimator.alignObjectSize(RamUsageEstimator.NUM_BYTES_ARRAY_HEADER + ((long) arrayLength) * bytesPerElement); + } + + static long estimateLongArray(int length) { + return estimatedArraySize(length, Long.BYTES); + } + + static long estimateIntArray(int length) { + return estimatedArraySize(length, Integer.BYTES); + } + + static long estimateDoubleArray(int length) { + return estimatedArraySize(length, Double.BYTES); + } + +} diff --git a/libs/exponential-histogram/src/main/java/org/elasticsearch/exponentialhistogram/ReleasableExponentialHistogram.java b/libs/exponential-histogram/src/main/java/org/elasticsearch/exponentialhistogram/ReleasableExponentialHistogram.java new file mode 100644 index 0000000000000..7bf1ad5e394e6 --- /dev/null +++ b/libs/exponential-histogram/src/main/java/org/elasticsearch/exponentialhistogram/ReleasableExponentialHistogram.java @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V., and/or licensed to Elasticsearch B.V. + * under one or more license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. 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. + * + * This file is based on a modification of https://github.com/open-telemetry/opentelemetry-java which is licensed under the Apache 2.0 License. + */ + +package org.elasticsearch.exponentialhistogram; + +import org.elasticsearch.core.Releasable; + +/** + * A histogram which participates in the {@link ExponentialHistogramCircuitBreaker} and therefore requires proper releasing. + */ +public interface ReleasableExponentialHistogram extends ExponentialHistogram, Releasable { + + /** + * @return an empty singleton, which does not allocate any memory and therefore {@link #close()} is a no-op. + */ + static ReleasableExponentialHistogram empty() { + return EmptyExponentialHistogram.INSTANCE; + } +} diff --git a/libs/exponential-histogram/src/main/java/org/elasticsearch/exponentialhistogram/ZeroBucket.java b/libs/exponential-histogram/src/main/java/org/elasticsearch/exponentialhistogram/ZeroBucket.java index 9ea75f3a82c27..1341f283c5487 100644 --- a/libs/exponential-histogram/src/main/java/org/elasticsearch/exponentialhistogram/ZeroBucket.java +++ b/libs/exponential-histogram/src/main/java/org/elasticsearch/exponentialhistogram/ZeroBucket.java @@ -21,6 +21,8 @@ package org.elasticsearch.exponentialhistogram; +import org.apache.lucene.util.RamUsageEstimator; + import static org.elasticsearch.exponentialhistogram.ExponentialHistogram.MAX_SCALE; import static org.elasticsearch.exponentialhistogram.ExponentialHistogram.MIN_INDEX; import static org.elasticsearch.exponentialhistogram.ExponentialHistogram.MIN_SCALE; @@ -41,6 +43,8 @@ */ public record ZeroBucket(long index, int scale, long count) { + public static final long SHALLOW_SIZE = RamUsageEstimator.shallowSizeOfInstance(ZeroBucket.class); + // A singleton for an empty zero bucket with the smallest possible threshold. private static final ZeroBucket MINIMAL_EMPTY = new ZeroBucket(MIN_INDEX, MIN_SCALE, 0); diff --git a/libs/exponential-histogram/src/test/java/org/elasticsearch/exponentialhistogram/DownscaleStatsTests.java b/libs/exponential-histogram/src/test/java/org/elasticsearch/exponentialhistogram/DownscaleStatsTests.java index 63f0b8a301cfe..d32e1d3a43c9a 100644 --- a/libs/exponential-histogram/src/test/java/org/elasticsearch/exponentialhistogram/DownscaleStatsTests.java +++ b/libs/exponential-histogram/src/test/java/org/elasticsearch/exponentialhistogram/DownscaleStatsTests.java @@ -21,8 +21,6 @@ package org.elasticsearch.exponentialhistogram; -import org.elasticsearch.test.ESTestCase; - import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; @@ -33,10 +31,9 @@ import static org.elasticsearch.exponentialhistogram.ExponentialHistogram.MAX_INDEX; import static org.elasticsearch.exponentialhistogram.ExponentialHistogram.MAX_INDEX_BITS; import static org.elasticsearch.exponentialhistogram.ExponentialHistogram.MIN_INDEX; -import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; -public class DownscaleStatsTests extends ESTestCase { +public class DownscaleStatsTests extends ExponentialHistogramTestCase { public void testExponential() { long[] values = IntStream.range(0, 100).mapToLong(i -> (long) Math.min(MAX_INDEX, Math.pow(1.1, i))).distinct().toArray(); diff --git a/libs/exponential-histogram/src/test/java/org/elasticsearch/exponentialhistogram/ExponentialHistogramGeneratorTests.java b/libs/exponential-histogram/src/test/java/org/elasticsearch/exponentialhistogram/ExponentialHistogramGeneratorTests.java index 337cfbd69033e..aa9746e22d54c 100644 --- a/libs/exponential-histogram/src/test/java/org/elasticsearch/exponentialhistogram/ExponentialHistogramGeneratorTests.java +++ b/libs/exponential-histogram/src/test/java/org/elasticsearch/exponentialhistogram/ExponentialHistogramGeneratorTests.java @@ -21,17 +21,19 @@ package org.elasticsearch.exponentialhistogram; -import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.common.breaker.CircuitBreaker; +import org.elasticsearch.common.unit.ByteSizeValue; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.hamcrest.Matchers.lessThanOrEqualTo; -public class ExponentialHistogramGeneratorTests extends ESTestCase { +public class ExponentialHistogramGeneratorTests extends ExponentialHistogramTestCase { public void testVeryLargeValue() { double value = Double.MAX_VALUE / 10; - ExponentialHistogram histo = ExponentialHistogram.create(1, value); - + ExponentialHistogram histo = createAutoReleasedHistogram(1, value); long index = histo.positiveBuckets().iterator().peekIndex(); int scale = histo.scale(); @@ -42,4 +44,45 @@ public void testVeryLargeValue() { assertThat("Upper bucket boundary should be greater than value", upperBound, greaterThanOrEqualTo(value)); } + public void testCircuitBreakerTripDuringConstruction() { + for (int allowedAllocations = 0; allowedAllocations < 5; allowedAllocations++) { + TrippingCircuitBreaker breaker = new TrippingCircuitBreaker(allowedAllocations); + + try (ReleasableExponentialHistogram histo = ExponentialHistogram.create(100, breaker, 1.0, 2.0, 3.0)) { + assertThat(breaker.getUsed(), greaterThan(0L)); + assertThat(breaker.getUsed(), equalTo(histo.ramBytesUsed())); + } catch (DummyCircuitBreakerTripException dummyTrip) {} + + assertThat(breaker.getUsed(), equalTo(0L)); + } + } + + private static class DummyCircuitBreakerTripException extends RuntimeException {} + + static class TrippingCircuitBreaker implements ExponentialHistogramCircuitBreaker { + + private final CircuitBreaker esBreaker = newLimitedBreaker(ByteSizeValue.ofMb(100)); + private int allocationsLeftUntilTrip; + + TrippingCircuitBreaker(int allocationsUntilTrip) { + this.allocationsLeftUntilTrip = allocationsUntilTrip; + } + + @Override + public void adjustBreaker(long bytesAllocated) { + if (bytesAllocated > 0) { + if (allocationsLeftUntilTrip > 0) { + allocationsLeftUntilTrip--; + } else { + throw new DummyCircuitBreakerTripException(); + } + } + esBreaker.addWithoutBreaking(bytesAllocated); + } + + public long getUsed() { + return esBreaker.getUsed(); + } + } + } diff --git a/libs/exponential-histogram/src/test/java/org/elasticsearch/exponentialhistogram/ExponentialHistogramMergerTests.java b/libs/exponential-histogram/src/test/java/org/elasticsearch/exponentialhistogram/ExponentialHistogramMergerTests.java index 9d46798d1a627..5113b73653641 100644 --- a/libs/exponential-histogram/src/test/java/org/elasticsearch/exponentialhistogram/ExponentialHistogramMergerTests.java +++ b/libs/exponential-histogram/src/test/java/org/elasticsearch/exponentialhistogram/ExponentialHistogramMergerTests.java @@ -21,7 +21,8 @@ package org.elasticsearch.exponentialhistogram; -import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.common.breaker.CircuitBreaker; +import org.elasticsearch.common.unit.ByteSizeValue; import java.util.ArrayList; import java.util.Arrays; @@ -35,14 +36,16 @@ import static org.elasticsearch.exponentialhistogram.ExponentialScaleUtils.adjustScale; import static org.hamcrest.Matchers.closeTo; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThan; -public class ExponentialHistogramMergerTests extends ESTestCase { +public class ExponentialHistogramMergerTests extends ExponentialHistogramTestCase { public void testZeroThresholdCollapsesOverlappingBuckets() { - FixedCapacityExponentialHistogram first = new FixedCapacityExponentialHistogram(100); + + FixedCapacityExponentialHistogram first = createAutoReleasedHistogram(100); first.setZeroBucket(new ZeroBucket(2.0001, 10)); - FixedCapacityExponentialHistogram second = new FixedCapacityExponentialHistogram(100); + FixedCapacityExponentialHistogram second = createAutoReleasedHistogram(100); first.resetBuckets(0); // scale 0 means base 2 first.tryAddBucket(0, 1, false); // bucket (-2, 1] first.tryAddBucket(1, 1, false); // bucket (-4, 2] @@ -72,7 +75,7 @@ public void testZeroThresholdCollapsesOverlappingBuckets() { assertThat(posBuckets.hasNext(), equalTo(false)); // ensure buckets of the accumulated histogram are collapsed too if needed - FixedCapacityExponentialHistogram third = new FixedCapacityExponentialHistogram(100); + FixedCapacityExponentialHistogram third = createAutoReleasedHistogram(100); third.setZeroBucket(new ZeroBucket(45.0, 1)); mergeResult = mergeWithMinimumScale(100, 0, mergeResult, third); @@ -83,12 +86,12 @@ public void testZeroThresholdCollapsesOverlappingBuckets() { } public void testEmptyZeroBucketIgnored() { - FixedCapacityExponentialHistogram first = new FixedCapacityExponentialHistogram(100); + FixedCapacityExponentialHistogram first = createAutoReleasedHistogram(100); first.setZeroBucket(new ZeroBucket(2.0, 10)); first.resetBuckets(0); // scale 0 means base 2 first.tryAddBucket(2, 42L, true); // bucket (4, 8] - FixedCapacityExponentialHistogram second = new FixedCapacityExponentialHistogram(100); + FixedCapacityExponentialHistogram second = createAutoReleasedHistogram(100); second.setZeroBucket(new ZeroBucket(100.0, 0)); ExponentialHistogram mergeResult = mergeWithMinimumScale(100, 0, first, second); @@ -109,22 +112,20 @@ public void testUpscalingDoesNotExceedIndexLimits() { boolean isPositive = i % 2 == 0; boolean useMinIndex = i > 1; - FixedCapacityExponentialHistogram histo = new FixedCapacityExponentialHistogram(2); + FixedCapacityExponentialHistogram histo = createAutoReleasedHistogram(2); histo.resetBuckets(20); long index = useMinIndex ? MIN_INDEX / 2 : MAX_INDEX / 2; histo.tryAddBucket(index, 1, isPositive); - ExponentialHistogramMerger merger = new ExponentialHistogramMerger(100); - merger.add(histo); - ExponentialHistogram result = merger.get(); - - assertThat(result.scale(), equalTo(21)); - if (isPositive) { - assertThat(result.positiveBuckets().iterator().peekIndex(), equalTo(adjustScale(index, 20, 1))); - } else { - assertThat(result.negativeBuckets().iterator().peekIndex(), equalTo(adjustScale(index, 20, 1))); + try (ReleasableExponentialHistogram result = ExponentialHistogram.merge(100, breaker(), histo)) { + assertThat(result.scale(), equalTo(21)); + if (isPositive) { + assertThat(result.positiveBuckets().iterator().peekIndex(), equalTo(adjustScale(index, 20, 1))); + } else { + assertThat(result.negativeBuckets().iterator().peekIndex(), equalTo(adjustScale(index, 20, 1))); + } } } } @@ -138,17 +139,54 @@ public void testMergeOrderIndependence() { .boxed() .collect(Collectors.toCollection(ArrayList::new)); - ExponentialHistogram reference = ExponentialHistogram.create(20, values.stream().mapToDouble(Double::doubleValue).toArray()); + ReleasableExponentialHistogram reference = ExponentialHistogram.create( + 20, + breaker(), + values.stream().mapToDouble(Double::doubleValue).toArray() + ); + autoReleaseOnTestEnd(reference); for (int i = 0; i < 100; i++) { Collections.shuffle(values, random()); - ExponentialHistogram shuffled = ExponentialHistogram.create(20, values.stream().mapToDouble(Double::doubleValue).toArray()); + double[] vals = values.stream().mapToDouble(Double::doubleValue).toArray(); + try (ReleasableExponentialHistogram shuffled = ExponentialHistogram.create(20, breaker(), vals)) { + assertThat("Expected same scale", shuffled.scale(), equalTo(reference.scale())); + assertThat("Expected same zero-bucket", shuffled.zeroBucket(), equalTo(reference.zeroBucket())); + assertBucketsEqual(shuffled.negativeBuckets(), reference.negativeBuckets()); + assertBucketsEqual(shuffled.positiveBuckets(), reference.positiveBuckets()); + } + } + } + + public void testMemoryAccounting() { + CircuitBreaker esBreaker = newLimitedBreaker(ByteSizeValue.ofMb(100)); + try (ExponentialHistogramMerger merger = ExponentialHistogramMerger.create(100, breaker(esBreaker))) { + + long emptyMergerSize = merger.ramBytesUsed(); + assertThat(emptyMergerSize, greaterThan(0L)); + assertThat(esBreaker.getUsed(), equalTo(emptyMergerSize)); - assertThat("Expected same scale", shuffled.scale(), equalTo(reference.scale())); - assertThat("Expected same zero-bucket", shuffled.zeroBucket(), equalTo(reference.zeroBucket())); - assertBucketsEqual(shuffled.negativeBuckets(), reference.negativeBuckets()); - assertBucketsEqual(shuffled.positiveBuckets(), reference.positiveBuckets()); + merger.add(createAutoReleasedHistogram(10, 1.0, 2.0, 3.0)); + + long singleBufferSize = merger.ramBytesUsed(); + assertThat(singleBufferSize, greaterThan(emptyMergerSize)); + assertThat(esBreaker.getUsed(), equalTo(singleBufferSize)); + + merger.add(createAutoReleasedHistogram(10, 1.0, 2.0, 3.0)); + + long doubleBufferSize = merger.ramBytesUsed(); + assertThat(doubleBufferSize, greaterThan(singleBufferSize)); + assertThat(esBreaker.getUsed(), equalTo(doubleBufferSize)); + + ReleasableExponentialHistogram result = merger.getAndClear(); + + assertThat(merger.ramBytesUsed(), equalTo(singleBufferSize)); + assertThat(esBreaker.getUsed(), equalTo(doubleBufferSize)); + + result.close(); + assertThat(esBreaker.getUsed(), equalTo(singleBufferSize)); } + assertThat(esBreaker.getUsed(), equalTo(0L)); } private void assertBucketsEqual(ExponentialHistogram.Buckets bucketsA, ExponentialHistogram.Buckets bucketsB) { @@ -164,10 +202,13 @@ private void assertBucketsEqual(ExponentialHistogram.Buckets bucketsA, Exponenti } } - private static ExponentialHistogram mergeWithMinimumScale(int bucketCount, int scale, ExponentialHistogram... histograms) { - ExponentialHistogramMerger merger = ExponentialHistogramMerger.createForTesting(bucketCount, scale); - Arrays.stream(histograms).forEach(merger::add); - return merger.get(); + private ExponentialHistogram mergeWithMinimumScale(int bucketCount, int scale, ExponentialHistogram... histograms) { + try (ExponentialHistogramMerger merger = ExponentialHistogramMerger.createForTesting(bucketCount, scale, breaker())) { + Arrays.stream(histograms).forEach(merger::add); + ReleasableExponentialHistogram result = merger.getAndClear(); + autoReleaseOnTestEnd(result); + return result; + } } } diff --git a/libs/exponential-histogram/src/test/java/org/elasticsearch/exponentialhistogram/ExponentialHistogramTestCase.java b/libs/exponential-histogram/src/test/java/org/elasticsearch/exponentialhistogram/ExponentialHistogramTestCase.java new file mode 100644 index 0000000000000..c13c4d2608295 --- /dev/null +++ b/libs/exponential-histogram/src/test/java/org/elasticsearch/exponentialhistogram/ExponentialHistogramTestCase.java @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V., and/or licensed to Elasticsearch B.V. + * under one or more license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. 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. + * + * This file is based on a modification of https://github.com/open-telemetry/opentelemetry-java which is licensed under the Apache 2.0 License. + */ + +package org.elasticsearch.exponentialhistogram; + +import org.elasticsearch.common.breaker.CircuitBreaker; +import org.elasticsearch.common.unit.ByteSizeValue; +import org.elasticsearch.core.Releasables; +import org.elasticsearch.test.ESTestCase; +import org.junit.After; + +import java.util.ArrayList; + +public abstract class ExponentialHistogramTestCase extends ESTestCase { + + private final ArrayList releaseBeforeEnd = new ArrayList<>(); + + /** + * Release all histograms created via {@link #createAutoReleasedHistogram(int)} + * before {@link ESTestCase} checks for unreleased bytes. + */ + @After + public void releaseHistograms() { + Releasables.close(releaseBeforeEnd); + releaseBeforeEnd.clear(); + } + + ExponentialHistogramCircuitBreaker breaker(CircuitBreaker esBreaker) { + return bytesAllocated -> { + if (bytesAllocated > 0) { + esBreaker.addEstimateBytesAndMaybeBreak(bytesAllocated, "exponential-histo-test-case"); + } else { + esBreaker.addWithoutBreaking(bytesAllocated); + } + }; + } + + ExponentialHistogramCircuitBreaker breaker() { + return breaker(newLimitedBreaker(ByteSizeValue.ofMb(100))); + } + + void autoReleaseOnTestEnd(ReleasableExponentialHistogram toRelease) { + releaseBeforeEnd.add(toRelease); + } + + FixedCapacityExponentialHistogram createAutoReleasedHistogram(int numBuckets) { + FixedCapacityExponentialHistogram result = FixedCapacityExponentialHistogram.create(numBuckets, breaker()); + releaseBeforeEnd.add(result); + return result; + } + + ExponentialHistogram createAutoReleasedHistogram(int numBuckets, double... values) { + ReleasableExponentialHistogram result = ExponentialHistogram.create(numBuckets, breaker(), values); + releaseBeforeEnd.add(result); + return result; + } +} diff --git a/libs/exponential-histogram/src/test/java/org/elasticsearch/exponentialhistogram/ExponentialScaleUtilsTests.java b/libs/exponential-histogram/src/test/java/org/elasticsearch/exponentialhistogram/ExponentialScaleUtilsTests.java index a75aedbf35231..397f9c5913ae4 100644 --- a/libs/exponential-histogram/src/test/java/org/elasticsearch/exponentialhistogram/ExponentialScaleUtilsTests.java +++ b/libs/exponential-histogram/src/test/java/org/elasticsearch/exponentialhistogram/ExponentialScaleUtilsTests.java @@ -23,8 +23,6 @@ import ch.obermuhlner.math.big.BigDecimalMath; -import org.elasticsearch.test.ESTestCase; - import java.math.BigDecimal; import java.math.MathContext; import java.math.RoundingMode; @@ -49,7 +47,7 @@ import static org.hamcrest.Matchers.lessThan; import static org.hamcrest.Matchers.lessThanOrEqualTo; -public class ExponentialScaleUtilsTests extends ESTestCase { +public class ExponentialScaleUtilsTests extends ExponentialHistogramTestCase { public void testMaxIndex() { assertThat(getMaximumScaleIncrease(MAX_INDEX), equalTo(0)); diff --git a/libs/exponential-histogram/src/test/java/org/elasticsearch/exponentialhistogram/FixedCapacityExponentialHistogramTests.java b/libs/exponential-histogram/src/test/java/org/elasticsearch/exponentialhistogram/FixedCapacityExponentialHistogramTests.java index 6701bee418299..7f98c3e2d4e74 100644 --- a/libs/exponential-histogram/src/test/java/org/elasticsearch/exponentialhistogram/FixedCapacityExponentialHistogramTests.java +++ b/libs/exponential-histogram/src/test/java/org/elasticsearch/exponentialhistogram/FixedCapacityExponentialHistogramTests.java @@ -21,15 +21,17 @@ package org.elasticsearch.exponentialhistogram; -import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.common.breaker.CircuitBreaker; +import org.elasticsearch.common.unit.ByteSizeValue; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThan; -public class FixedCapacityExponentialHistogramTests extends ESTestCase { +public class FixedCapacityExponentialHistogramTests extends ExponentialHistogramTestCase { public void testValueCountUpdatedCorrectly() { - FixedCapacityExponentialHistogram histogram = new FixedCapacityExponentialHistogram(100); + FixedCapacityExponentialHistogram histogram = createAutoReleasedHistogram(100); assertThat(histogram.negativeBuckets().valueCount(), equalTo(0L)); assertThat(histogram.positiveBuckets().valueCount(), equalTo(0L)); @@ -57,4 +59,13 @@ public void testValueCountUpdatedCorrectly() { assertThat(histogram.negativeBuckets().valueCount(), equalTo(0L)); assertThat(histogram.positiveBuckets().valueCount(), equalTo(0L)); } + + public void testMemoryAccounting() { + CircuitBreaker esBreaker = newLimitedBreaker(ByteSizeValue.ofMb(100)); + try (FixedCapacityExponentialHistogram histogram = FixedCapacityExponentialHistogram.create(100, breaker(esBreaker))) { + assertThat(histogram.ramBytesUsed(), greaterThan(2 * RamEstimationUtil.estimateLongArray(100))); + assertThat(esBreaker.getUsed(), equalTo(histogram.ramBytesUsed())); + } + assertThat(esBreaker.getUsed(), equalTo(0L)); + } } diff --git a/libs/exponential-histogram/src/test/java/org/elasticsearch/exponentialhistogram/QuantileAccuracyTests.java b/libs/exponential-histogram/src/test/java/org/elasticsearch/exponentialhistogram/QuantileAccuracyTests.java index 3f1ffaffda30b..cec0d1f0f0ff7 100644 --- a/libs/exponential-histogram/src/test/java/org/elasticsearch/exponentialhistogram/QuantileAccuracyTests.java +++ b/libs/exponential-histogram/src/test/java/org/elasticsearch/exponentialhistogram/QuantileAccuracyTests.java @@ -30,7 +30,6 @@ import org.apache.commons.math3.distribution.UniformRealDistribution; import org.apache.commons.math3.distribution.WeibullDistribution; import org.apache.commons.math3.random.Well19937c; -import org.elasticsearch.test.ESTestCase; import java.util.Arrays; import java.util.HashSet; @@ -47,7 +46,7 @@ import static org.hamcrest.Matchers.lessThanOrEqualTo; import static org.hamcrest.Matchers.notANumber; -public class QuantileAccuracyTests extends ESTestCase { +public class QuantileAccuracyTests extends ExponentialHistogramTestCase { public static final double[] QUANTILES_TO_TEST = { 0, 0.0000001, 0.01, 0.1, 0.25, 0.5, 0.75, 0.9, 0.95, 0.99, 0.999999, 1.0 }; @@ -57,7 +56,7 @@ private static int randomBucketCount() { } public void testNoNegativeZeroReturned() { - FixedCapacityExponentialHistogram histogram = new FixedCapacityExponentialHistogram(2); + FixedCapacityExponentialHistogram histogram = createAutoReleasedHistogram(2); histogram.resetBuckets(MAX_SCALE); // add a single, negative bucket close to zero histogram.tryAddBucket(MIN_INDEX, 3, false); @@ -100,7 +99,7 @@ public void testBasicSmall() { } public void testPercentileOverlapsZeroBucket() { - ExponentialHistogram histo = ExponentialHistogram.create(9, -3.0, -2, -1, 0, 0, 0, 1, 2, 3); + ExponentialHistogram histo = createAutoReleasedHistogram(9, -3.0, -2, -1, 0, 0, 0, 1, 2, 3); assertThat(ExponentialHistogramQuantile.getQuantile(histo, 8.0 / 16.0), equalTo(0.0)); assertThat(ExponentialHistogramQuantile.getQuantile(histo, 7.0 / 16.0), equalTo(0.0)); assertThat(ExponentialHistogramQuantile.getQuantile(histo, 9.0 / 16.0), equalTo(0.0)); @@ -154,14 +153,14 @@ public void testExplicitSkewedData() { } public void testEmptyHistogram() { - ExponentialHistogram histo = ExponentialHistogram.create(1); + ExponentialHistogram histo = ExponentialHistogram.empty(); for (double q : QUANTILES_TO_TEST) { assertThat(ExponentialHistogramQuantile.getQuantile(histo, q), notANumber()); } } public void testSingleValueHistogram() { - ExponentialHistogram histo = ExponentialHistogram.create(1, 42.0); + ExponentialHistogram histo = createAutoReleasedHistogram(1, 42.0); for (double q : QUANTILES_TO_TEST) { assertThat(ExponentialHistogramQuantile.getQuantile(histo, q), closeTo(42, 0.0000001)); } @@ -232,12 +231,12 @@ private static double[] generateSamples(RealDistribution distribution, int sampl } private double testQuantileAccuracy(double[] values, int bucketCount) { - // Create histogram - ExponentialHistogram histogram = ExponentialHistogram.create(bucketCount, values); + ExponentialHistogram histogram = createAutoReleasedHistogram(bucketCount, values); + Arrays.sort(values); - double allowedError = getMaximumRelativeError(values, bucketCount); double maxError = 0; + double allowedError = getMaximumRelativeError(values, bucketCount); // Compare histogram quantiles with exact quantiles for (double q : QUANTILES_TO_TEST) { diff --git a/libs/exponential-histogram/src/test/java/org/elasticsearch/exponentialhistogram/RamEstimationUtilTests.java b/libs/exponential-histogram/src/test/java/org/elasticsearch/exponentialhistogram/RamEstimationUtilTests.java new file mode 100644 index 0000000000000..2bb17ae264270 --- /dev/null +++ b/libs/exponential-histogram/src/test/java/org/elasticsearch/exponentialhistogram/RamEstimationUtilTests.java @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V., and/or licensed to Elasticsearch B.V. + * under one or more license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. 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. + * + * This file is based on a modification of https://github.com/open-telemetry/opentelemetry-java which is licensed under the Apache 2.0 License. + */ + +package org.elasticsearch.exponentialhistogram; + +import org.apache.lucene.util.RamUsageEstimator; + +import static org.hamcrest.Matchers.equalTo; + +public class RamEstimationUtilTests extends ExponentialHistogramTestCase { + + public void testLongArrayEstimation() { + assertThat(RamEstimationUtil.estimateLongArray(0), equalTo(RamUsageEstimator.sizeOf(new long[0]))); + assertThat(RamEstimationUtil.estimateLongArray(1), equalTo(RamUsageEstimator.sizeOf(new long[1]))); + assertThat(RamEstimationUtil.estimateLongArray(1000), equalTo(RamUsageEstimator.sizeOf(new long[1000]))); + } + + public void testDoubleArrayEstimation() { + assertThat(RamEstimationUtil.estimateDoubleArray(0), equalTo(RamUsageEstimator.sizeOf(new double[0]))); + assertThat(RamEstimationUtil.estimateDoubleArray(1), equalTo(RamUsageEstimator.sizeOf(new double[1]))); + assertThat(RamEstimationUtil.estimateDoubleArray(1000), equalTo(RamUsageEstimator.sizeOf(new double[1000]))); + } + + public void testIntArrayEstimation() { + assertThat(RamEstimationUtil.estimateIntArray(0), equalTo(RamUsageEstimator.sizeOf(new int[0]))); + assertThat(RamEstimationUtil.estimateIntArray(1), equalTo(RamUsageEstimator.sizeOf(new int[1]))); + assertThat(RamEstimationUtil.estimateIntArray(1000), equalTo(RamUsageEstimator.sizeOf(new int[1000]))); + } + +} diff --git a/libs/exponential-histogram/src/test/java/org/elasticsearch/exponentialhistogram/ZeroBucketTests.java b/libs/exponential-histogram/src/test/java/org/elasticsearch/exponentialhistogram/ZeroBucketTests.java index 43873fba53ec7..fdea89d0421c5 100644 --- a/libs/exponential-histogram/src/test/java/org/elasticsearch/exponentialhistogram/ZeroBucketTests.java +++ b/libs/exponential-histogram/src/test/java/org/elasticsearch/exponentialhistogram/ZeroBucketTests.java @@ -21,11 +21,9 @@ package org.elasticsearch.exponentialhistogram; -import org.elasticsearch.test.ESTestCase; - import static org.hamcrest.Matchers.equalTo; -public class ZeroBucketTests extends ESTestCase { +public class ZeroBucketTests extends ExponentialHistogramTestCase { public void testMinimalBucketHasZeroThreshold() { assertThat(ZeroBucket.minimalWithCount(42).zeroThreshold(), equalTo(0.0));