/*
* Copyright 2017 LINE Corporation
*
* LINE Corporation 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.
*
* Copyright 2013 <kristofa@github.com>
*
* 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.
*/
package com.linecorp.armeria.server.logging;
import static com.google.common.base.Preconditions.checkArgument;
import java.util.BitSet;
import java.util.Random;
/**
* This sampler is appropriate for low-traffic instrumentation (ex servers that each receive <100K
* requests), or those who do not provision random trace ids. It not appropriate for collectors as
* the sampling decision isn't idempotent (consistent based on trace id).
*
* <h3>Implementation</h3>
*
* <p>This initializes a random bitset of size 100 (corresponding to 1% granularity). This means
* that it is accurate in units of 100 traces. At runtime, this loops through the bitset, returning
* the value according to a counter.
*
* <p>Forked from brave-core.
*/
final class CountingSampler extends Sampler {
private final BitSet sampleDecisions;
private int counter; // guarded by this
/** Fills a bitset with decisions according to the supplied rate. */
private CountingSampler(float rate) {
int outOf100 = (int) (rate * 100.0f);
sampleDecisions = randomBitSet(100, outOf100, new Random());
}
/**
* @param rate 0 means never sample, 1 means always sample. Otherwise minimum sample rate is 0.01,
* or 1% of traces
*/
public static Sampler create(final float rate) {
if (rate == 0) {
return NEVER_SAMPLE;
}
if (rate == 1.0) {
return ALWAYS_SAMPLE;
}
checkArgument(rate >= 0.01f && rate < 1, "rate should be between 0.01 and 1: was %s", rate);
return new CountingSampler(rate);
}
/**
* Reservoir sampling algorithm borrowed from Stack Overflow.
* http://stackoverflow.com/questions/12817946/generate-a-random-bitset-with-n-1s
*/
private static BitSet randomBitSet(int size, int cardinality, Random rnd) {
BitSet result = new BitSet(size);
int[] chosen = new int[cardinality];
int i;
for (i = 0; i < cardinality; ++i) {
chosen[i] = i;
result.set(i);
}
for (; i < size; ++i) {
int j = rnd.nextInt(i + 1);
if (j < cardinality) {
result.clear(chosen[j]);
result.set(i);
chosen[j] = i;
}
}
return result;
}
/**
* Returns true if the trace ID should be measured.
* Loops over the pre-canned decisions, resetting to zero when it gets to the end.
*/
@Override
public synchronized boolean isSampled() {
boolean result = sampleDecisions.get(counter++);
if (counter == 100) {
counter = 0;
}
return result;
}
}