/* * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, Datadog, Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. * */ #include "precompiled.hpp" // This test performs mocking of certain JVM functionality. This works by // including the source file under test inside an anonymous namespace (which // prevents linking conflicts) with the mocked symbols redefined. // The include list should mirror the one found in the included source file - // with the ones that should pick up the mocks removed. Those should be included // later after the mocks have been defined. #include "jfr/utilities/jfrAllocation.hpp" #include "jfr/utilities/jfrRandom.inline.hpp" #include "jfr/utilities/jfrSpinlockHelper.hpp" #include "jfr/utilities/jfrTime.hpp" #include "jfr/utilities/jfrTimeConverter.hpp" #include "jfr/utilities/jfrTryLock.hpp" #include "logging/log.hpp" #include "runtime/atomic.hpp" #include "utilities/globalDefinitions.hpp" #include "unittest.hpp" #include // #undef SHARE_JFR_SUPPORT_JFRADAPTIVESAMPLER_HPP namespace { class MockJfrTimeConverter : public ::JfrTimeConverter { public: static double nano_to_counter_multiplier(bool is_os_time = false) { return 1.0; } static jlong counter_to_nanos(jlong c, bool is_os_time = false) { return c; } static jlong counter_to_millis(jlong c, bool is_os_time = false) { return c * (jlong)NANOS_PER_MILLISEC; } static jlong nanos_to_countertime(jlong c, bool as_os_time = false) { return c; } }; class MockJfrTickValue { private: jlong _ticks; public: MockJfrTickValue(jlong ticks) : _ticks(ticks) {}; jlong value() { return _ticks; } }; class MockJfrTicks { public: static jlong tick; static MockJfrTickValue now() { return MockJfrTickValue(tick); } }; jlong MockJfrTicks::tick = 0; // Reincluding source files in the anonymous namespace unfortunately seems to // behave strangely with precompiled headers (only when using gcc though) #ifndef DONT_USE_PRECOMPILED_HEADER #define DONT_USE_PRECOMPILED_HEADER #endif #define JfrTicks MockJfrTicks #define JfrTimeConverter MockJfrTimeConverter #include "jfr/support/jfrAdaptiveSampler.hpp" #include "jfr/support/jfrAdaptiveSampler.cpp" #undef JfrTimeConverter #undef JfrTicks } // anonymous namespace class JfrGTestAdaptiveSampling : public ::testing::Test { protected: const int max_population_per_window = 2000; const int min_population_per_window = 2; const int window_count = 10000; const clock_t window_duration_ms = 100; const size_t expected_sample_points_per_window = 50; const size_t expected_sample_points = expected_sample_points_per_window * (size_t)window_count; const size_t window_lookback_count = 50; // 50 windows == 5 seconds (for a window duration of 100 ms) const double max_sample_bias = 0.11; void SetUp() { // Ensure that tests are separated in time by spreading them by 24hrs apart MockJfrTicks::tick += (24 * 60 * 60) * NANOSECS_PER_SEC; } void TearDown() { // nothing } void assertDistributionProperties(int distr_slots, jlong* population, jlong* sample, size_t population_size, size_t sample_size, const char* msg) { size_t population_sum = 0; size_t sample_sum = 0; for (int i = 0; i < distr_slots; i++) { population_sum += i * population[i]; sample_sum += i * sample[i]; } double population_mean = (double)population_sum / (double)population_size; double sample_mean = (double)sample_sum / (double)sample_size; double population_variance = 0; double sample_variance = 0; for (int i = 0; i < distr_slots; i++) { double population_diff = i - population_mean; population_variance = (double)population[i] * population_diff * population_diff; double sample_diff = i - sample_mean; sample_variance = (double)sample[i] * sample_diff * sample_diff; } population_variance = population_variance / (double)(population_size - 1); sample_variance = sample_variance / (double)(sample_size - 1); double population_stdev = sqrt(population_variance); double sample_stdev = sqrt(sample_variance); // make sure the standard deviation is ok EXPECT_NEAR(population_stdev, sample_stdev, 0.5) << msg; // make sure that the subsampled set mean is within 2-sigma of the original set mean EXPECT_NEAR(population_mean, sample_mean, population_stdev) << msg; // make sure that the original set mean is within 2-sigma of the subsampled set mean EXPECT_NEAR(sample_mean, population_mean, sample_stdev) << msg; } typedef size_t(JfrGTestAdaptiveSampling::* incoming)() const; void test(incoming inc, size_t events_per_window, double expectation, const char* description); public: size_t incoming_uniform() const { return os::random() % max_population_per_window + min_population_per_window; } size_t incoming_bursty_10_percent() const { bool is_burst = (os::random() % 100) < 10; // 10% burst chance return is_burst ? max_population_per_window : min_population_per_window; } size_t incoming_bursty_90_percent() const { bool is_burst = (os::random() % 100) < 90; // 90% burst chance return is_burst ? max_population_per_window : min_population_per_window; } size_t incoming_low_rate() const { return min_population_per_window; } size_t incoming_high_rate() const { return max_population_per_window; } size_t incoming_burst_eval(size_t& count, size_t mod_value) const { return count++ % 10 == mod_value ? max_population_per_window : 0; } size_t incoming_early_burst() const { static size_t count = 1; return incoming_burst_eval(count, 1); } size_t incoming_mid_burst() const { static size_t count = 1; return incoming_burst_eval(count, 5); } size_t incoming_late_burst() const { static size_t count = 1; return incoming_burst_eval(count, 0); } }; void JfrGTestAdaptiveSampling::test(JfrGTestAdaptiveSampling::incoming inc, size_t sample_points_per_window, double error_factor, const char* const description) { assert(description != nullptr, "invariant"); char output[1024] = "Adaptive sampling: "; strcat(output, description); fprintf(stdout, "=== %s\n", output); jlong population[100] = { 0 }; jlong sample[100] = { 0 }; ::JfrGTestFixedRateSampler sampler = ::JfrGTestFixedRateSampler(expected_sample_points_per_window, window_duration_ms, window_lookback_count); EXPECT_TRUE(sampler.initialize()); size_t population_size = 0; size_t sample_size = 0; for (int t = 0; t < window_count; t++) { const size_t incoming_events = (this->*inc)(); for (size_t i = 0; i < incoming_events; i++) { ++population_size; size_t index = os::random() % 100; population[index] += 1; if (sampler.sample()) { ++sample_size; sample[index] += 1; } } MockJfrTicks::tick += window_duration_ms * NANOSECS_PER_MILLISEC + 1; sampler.sample(); // window rotation } const size_t target_sample_size = sample_points_per_window * window_count; EXPECT_NEAR((double)target_sample_size, (double)sample_size, (double)expected_sample_points * error_factor) << output; strcat(output, ", hit distribution"); assertDistributionProperties(100, population, sample, population_size, sample_size, output); } TEST_VM_F(JfrGTestAdaptiveSampling, DISABLED_uniform_rate) { test(&JfrGTestAdaptiveSampling::incoming_uniform, expected_sample_points_per_window, 0.05, "random uniform, all samples"); } TEST_VM_F(JfrGTestAdaptiveSampling, DISABLED_low_rate) { test(&JfrGTestAdaptiveSampling::incoming_low_rate, min_population_per_window, 0.05, "low rate"); } TEST_VM_F(JfrGTestAdaptiveSampling, DISABLED_high_rate) { test(&JfrGTestAdaptiveSampling::incoming_high_rate, expected_sample_points_per_window, 0.02, "high rate"); } // We can think of the windows as splitting up a time period, for example a second (window_duration_ms = 100) // The burst tests for early, mid and late apply a burst rate at a selected window, with other windows having no incoming input. // // - early during the first window of a new time period // - mid during the middle window of a new time period // - late during the last window of a new time period // // The tests verify the total sample size correspond to the selected bursts: // // - early start of a second -> each second will have sampled the window set point for a single window only since no debt has accumulated into the new time period. // - mid middle of the second -> each second will have sampled the window set point + accumulated debt for the first 4 windows. // - late end of the second -> each second will have sampled the window set point + accumulated debt for the first 9 windows (i.e. it will have sampled all) // TEST_VM_F(JfrGTestAdaptiveSampling, DISABLED_early_burst) { test(&JfrGTestAdaptiveSampling::incoming_early_burst, expected_sample_points_per_window, 0.9, "early burst"); } TEST_VM_F(JfrGTestAdaptiveSampling, DISABLED_mid_burst) { test(&JfrGTestAdaptiveSampling::incoming_mid_burst, expected_sample_points_per_window, 0.5, "mid burst"); } TEST_VM_F(JfrGTestAdaptiveSampling, DISABLED_late_burst) { test(&JfrGTestAdaptiveSampling::incoming_late_burst, expected_sample_points_per_window, 0.0, "late burst"); } // These are randomized burst tests TEST_VM_F(JfrGTestAdaptiveSampling, DISABLED_bursty_rate_10_percent) { test(&JfrGTestAdaptiveSampling::incoming_bursty_10_percent, expected_sample_points_per_window, 0.96, "bursty 10%"); } TEST_VM_F(JfrGTestAdaptiveSampling, DISABLED_bursty_rate_90_percent) { test(&JfrGTestAdaptiveSampling::incoming_bursty_10_percent, expected_sample_points_per_window, 0.96, "bursty 90%"); }