2020-12-10 12:33:48 +00:00
/*
2024-01-29 17:12:13 +00:00
* Copyright ( c ) 2020 , 2024 , Oracle and / or its affiliates . All rights reserved .
2020-12-10 12:33:48 +00:00
* 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"
2022-11-24 15:06:19 +00:00
# include <cmath>
2020-12-10 12:33:48 +00:00
// #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 ) {
2023-08-08 11:01:59 +00:00
return c * ( jlong ) NANOS_PER_MILLISEC ;
2020-12-10 12:33:48 +00:00
}
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 ] ;
}
2023-08-08 11:01:59 +00:00
double population_mean = ( double ) population_sum / ( double ) population_size ;
double sample_mean = ( double ) sample_sum / ( double ) sample_size ;
2020-12-10 12:33:48 +00:00
double population_variance = 0 ;
double sample_variance = 0 ;
for ( int i = 0 ; i < distr_slots ; i + + ) {
double population_diff = i - population_mean ;
2023-08-08 11:01:59 +00:00
population_variance = ( double ) population [ i ] * population_diff * population_diff ;
2020-12-10 12:33:48 +00:00
double sample_diff = i - sample_mean ;
2023-08-08 11:01:59 +00:00
sample_variance = ( double ) sample [ i ] * sample_diff * sample_diff ;
2020-12-10 12:33:48 +00:00
}
2023-08-08 11:01:59 +00:00
population_variance = population_variance / ( double ) ( population_size - 1 ) ;
sample_variance = sample_variance / ( double ) ( sample_size - 1 ) ;
2020-12-10 12:33:48 +00:00
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 ) {
2024-01-29 17:12:13 +00:00
assert ( description ! = nullptr , " invariant " ) ;
2020-12-10 12:33:48 +00:00
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 ;
2023-08-08 11:01:59 +00:00
EXPECT_NEAR ( ( double ) target_sample_size , ( double ) sample_size , ( double ) expected_sample_points * error_factor ) < < output ;
2020-12-10 12:33:48 +00:00
strcat ( output , " , hit distribution " ) ;
assertDistributionProperties ( 100 , population , sample , population_size , sample_size , output ) ;
}
2020-12-10 22:25:23 +00:00
TEST_VM_F ( JfrGTestAdaptiveSampling , DISABLED_uniform_rate ) {
2020-12-10 12:33:48 +00:00
test ( & JfrGTestAdaptiveSampling : : incoming_uniform , expected_sample_points_per_window , 0.05 , " random uniform, all samples " ) ;
}
2020-12-10 22:25:23 +00:00
TEST_VM_F ( JfrGTestAdaptiveSampling , DISABLED_low_rate ) {
2020-12-10 12:33:48 +00:00
test ( & JfrGTestAdaptiveSampling : : incoming_low_rate , min_population_per_window , 0.05 , " low rate " ) ;
}
2020-12-10 22:25:23 +00:00
TEST_VM_F ( JfrGTestAdaptiveSampling , DISABLED_high_rate ) {
2020-12-10 12:33:48 +00:00
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)
//
2020-12-10 22:25:23 +00:00
TEST_VM_F ( JfrGTestAdaptiveSampling , DISABLED_early_burst ) {
2020-12-10 12:33:48 +00:00
test ( & JfrGTestAdaptiveSampling : : incoming_early_burst , expected_sample_points_per_window , 0.9 , " early burst " ) ;
}
2020-12-10 22:25:23 +00:00
TEST_VM_F ( JfrGTestAdaptiveSampling , DISABLED_mid_burst ) {
2020-12-10 12:33:48 +00:00
test ( & JfrGTestAdaptiveSampling : : incoming_mid_burst , expected_sample_points_per_window , 0.5 , " mid burst " ) ;
}
2020-12-10 22:25:23 +00:00
TEST_VM_F ( JfrGTestAdaptiveSampling , DISABLED_late_burst ) {
2020-12-10 12:33:48 +00:00
test ( & JfrGTestAdaptiveSampling : : incoming_late_burst , expected_sample_points_per_window , 0.0 , " late burst " ) ;
}
// These are randomized burst tests
2020-12-10 22:25:23 +00:00
TEST_VM_F ( JfrGTestAdaptiveSampling , DISABLED_bursty_rate_10_percent ) {
2020-12-10 12:33:48 +00:00
test ( & JfrGTestAdaptiveSampling : : incoming_bursty_10_percent , expected_sample_points_per_window , 0.96 , " bursty 10% " ) ;
}
2020-12-10 22:25:23 +00:00
TEST_VM_F ( JfrGTestAdaptiveSampling , DISABLED_bursty_rate_90_percent ) {
2020-12-10 12:33:48 +00:00
test ( & JfrGTestAdaptiveSampling : : incoming_bursty_10_percent , expected_sample_points_per_window , 0.96 , " bursty 90% " ) ;
}