Skip to content

Commit

Permalink
Move Resampler
Browse files Browse the repository at this point in the history
  • Loading branch information
danielmitterdorfer committed Sep 20, 2023
1 parent 4f38465 commit 9df6232
Show file tree
Hide file tree
Showing 3 changed files with 164 additions and 60 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

package org.elasticsearch.xpack.profiling;

import java.util.Random;
import java.util.random.RandomGenerator;

class Resampler {
private final boolean requiresResampling;
private final RandomGenerator r;
private final double adjustedSampleRate;
private final double p;

Resampler(GetStackTracesRequest request, double sampleRate, long totalCount) {
// Manually reduce sample count if totalCount exceeds sampleSize by 10%.
if (totalCount > request.getSampleSize() * 1.1) {
this.requiresResampling = true;
// Make the RNG predictable to get reproducible results.
this.r = createRandom(request);
this.p = (double) request.getSampleSize() / totalCount;
} else {
this.requiresResampling = false;
this.r = null;
this.p = 1.0d;
}
// TODO: Just use the sample rate as is once all resampling is done server-side
this.adjustedSampleRate = request.isAdjustSampleCount() ? sampleRate : 1.0d;
}

protected RandomGenerator createRandom(GetStackTracesRequest request) {
return new Random(request.hashCode());
}

public int adjustSampleCount(int originalCount) {
int rawCount;
if (requiresResampling) {
rawCount = 0;
for (int i = 0; i < originalCount; i++) {
if (r.nextDouble() < p) {
rawCount++;
}
}
} else {
rawCount = originalCount;
}
// Adjust the sample counts from down-sampled to fully sampled.
// Be aware that downsampling drops entries from stackTraceEvents, so that
// the sum of the upscaled count values is less that totalCount.
return (int) Math.floor(rawCount / (p * adjustedSampleRate));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
Expand Down Expand Up @@ -155,7 +154,6 @@ private void searchEventGroupByStackTrace(
) {
long start = System.nanoTime();
GetStackTracesResponseBuilder responseBuilder = new GetStackTracesResponseBuilder();
int exp = eventsIndex.getExponent();
responseBuilder.setSampleRate(eventsIndex.getSampleRate());
client.prepareSearch(eventsIndex.getName())
.setTrackTotalHits(false)
Expand Down Expand Up @@ -372,64 +370,6 @@ private void retrieveStackTraceDetails(
}
}

private static class Resampler {
private final boolean requiresResampling;

private final Random r;

private final double sampleRate;

private final double p;

private final boolean adjustSampleCount;

Resampler(GetStackTracesRequest request, double sampleRate, long totalCount) {
// Manually reduce sample count if totalCount exceeds sampleSize by 10%.
if (totalCount > request.getSampleSize() * 1.1) {
this.requiresResampling = true;
// Make the RNG predictable to get reproducible results.
this.r = new Random(request.hashCode());
this.sampleRate = sampleRate;
this.p = (double) request.getSampleSize() / totalCount;
} else {
this.requiresResampling = false;
this.r = null;
this.sampleRate = sampleRate;
this.p = 1.0d;
}
this.adjustSampleCount = request.isAdjustSampleCount();
}

public int adjustSampleCount(int originalCount) {
int rawCount;
if (requiresResampling) {
int newCount = 0;
for (int i = 0; i < originalCount; i++) {
if (r.nextDouble() < p) {
newCount++;
}
}
if (newCount > 0) {
// Adjust the sample counts from down-sampled to fully sampled.
// Be aware that downsampling drops entries from stackTraceEvents, so that
// the sum of the upscaled count values is less that totalCount.
// This code needs to be refactored to move all scaling into the server
// side, not just the resampling-scaling.
rawCount = (int) Math.floor(newCount / (p));
} else {
rawCount = 0;
}
} else {
rawCount = originalCount;
}
if (adjustSampleCount) {
return (int) Math.floor(rawCount / sampleRate);
} else {
return rawCount;
}
}
}

/**
* Collects stack trace details which are retrieved concurrently and sends a response only when all details are known.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

package org.elasticsearch.xpack.profiling;

import org.elasticsearch.test.ESTestCase;

import java.util.random.RandomGenerator;

public class ResamplerTests extends ESTestCase {

private Resampler createResampler(GetStackTracesRequest request, double sampleRate, long totalCount) {
return new Resampler(request, sampleRate, totalCount) {
@Override
protected RandomGenerator createRandom(GetStackTracesRequest request) {
return DeterministicRandom.of(0.0d, 1.0d);
}
};
}

public void testNoResamplingNoSampleRateAdjustment() {
// corresponds to profiling-events-5pow01
double sampleRate = 1.0d / Math.pow(5.0d, 1);
int requestedSamples = 20_000;
int actualTotalSamples = 10_000;

GetStackTracesRequest request = new GetStackTracesRequest(requestedSamples, null);
request.setAdjustSampleCount(false);

Resampler resampler = createResampler(request, sampleRate, actualTotalSamples);

int actualSamplesSingleTrace = 5_000;
assertEquals(5_000, resampler.adjustSampleCount(actualSamplesSingleTrace));
}

public void testNoResamplingButAdjustSampleRate() {
// corresponds to profiling-events-5pow01
double sampleRate = 1.0d / Math.pow(5.0d, 1);
int requestedSamples = 20_000;
int actualTotalSamples = 10_000;

GetStackTracesRequest request = new GetStackTracesRequest(requestedSamples, null);
request.setAdjustSampleCount(true);

Resampler resampler = createResampler(request, sampleRate, actualTotalSamples);

int actualSamplesSingleTrace = 5_000;
assertEquals(25_000, resampler.adjustSampleCount(actualSamplesSingleTrace));
}

public void testResamplingNoSampleRateAdjustment() {
// corresponds to profiling-events-5pow01
double sampleRate = 1.0d / Math.pow(5.0d, 1);
int requestedSamples = 20_000;
int actualTotalSamples = 40_000;

GetStackTracesRequest request = new GetStackTracesRequest(requestedSamples, null);
request.setAdjustSampleCount(false);

Resampler resampler = createResampler(request, sampleRate, actualTotalSamples);

int actualSamplesSingleTrace = 20_000;
assertEquals(20_000, resampler.adjustSampleCount(actualSamplesSingleTrace));
}

public void testResamplingAndSampleRateAdjustment() {
// corresponds to profiling-events-5pow01
double sampleRate = 1.0d / Math.pow(5.0d, 1);
int requestedSamples = 20_000;
int actualTotalSamples = 40_000;

GetStackTracesRequest request = new GetStackTracesRequest(requestedSamples, null);
request.setAdjustSampleCount(true);

Resampler resampler = createResampler(request, sampleRate, actualTotalSamples);

int actualSamplesSingleTrace = 20_000;
assertEquals(100_000, resampler.adjustSampleCount(actualSamplesSingleTrace));
}

private static class DeterministicRandom implements RandomGenerator {
private final double[] values;
private int idx;

private DeterministicRandom(double... values) {
this.values = values;
this.idx = 0;
}

public static RandomGenerator of(double... values) {
return new DeterministicRandom(values);
}

@Override
public long nextLong() {
return Double.doubleToLongBits(nextDouble());
}

@Override
public double nextDouble() {
return values[idx++ % values.length];
}
}
}

0 comments on commit 9df6232

Please sign in to comment.