@Test public void testWriteThreadSafety() { final MockedTime time = new MockedTime(); final HystrixRollingPercentile p = new HystrixRollingPercentile(time, 100, 25, 1000, HystrixProperty.Factory.asProperty(true)); final int NUM_THREADS = 10; final int NUM_ITERATIONS = 1000; final CountDownLatch latch = new CountDownLatch(NUM_THREADS); final Random r = new Random(); final AtomicInteger added = new AtomicInteger(0); for (int i = 0; i < NUM_THREADS; i++) { threadPool.submit(new Runnable() { @Override public void run() { for (int j = 1; j < NUM_ITERATIONS / NUM_THREADS + 1; j++) { int nextInt = r.nextInt(100); p.addValue(nextInt); added.getAndIncrement(); } latch.countDown(); } }); } try { latch.await(100, TimeUnit.SECONDS); assertEquals(added.get(), p.buckets.peekLast().data.length()); } catch (InterruptedException ex) { fail("Timeout on all threads writing percentiles"); } }
/** * Add value (or values) to current bucket. * * @param value * Value to be stored in current bucket such as execution latency in milliseconds */ public void addValue(int... value) { /* no-op if disabled */ if (!enabled.get()) return; for (int v : value) { try { getCurrentBucket().data.addValue(v); } catch (Exception e) { logger.error("Failed to add value: " + v, e); } } }
/* package for testing */ PercentileSnapshot(Bucket[] buckets) { int lengthFromBuckets = 0; // we need to calculate it dynamically as it could have been changed by properties (rare, but possible) // also this way we capture the actual index size rather than the max so size the int[] to only what we need for (Bucket bd : buckets) { lengthFromBuckets += bd.data.length; } data = new int[lengthFromBuckets]; int index = 0; int sum = 0; for (Bucket bd : buckets) { PercentileBucketData pbd = bd.data; int length = pbd.length(); for (int i = 0; i < length; i++) { int v = pbd.list.get(i); this.data[index++] = v; sum += v; } } this.length = index; if (this.length == 0) { this.mean = 0; } else { this.mean = sum / this.length; } Arrays.sort(this.data, 0, length); }
/** * Add value (or values) to current bucket. * * @param value * Value to be stored in current bucket such as execution latency in milliseconds */ public void addValue(int... value) { /* no-op if disabled */ if (!enabled.get()) return; for (int v : value) { try { getCurrentBucket().data.addValue(v); } catch (Exception e) { logger.error("Failed to add value: " + v, e); } } }
Bucket(long startTime, int bucketDataLength) { this.windowStart = startTime; this.data = new PercentileBucketData(bucketDataLength); }
/* package for testing */ PercentileSnapshot(Bucket[] buckets) { int lengthFromBuckets = 0; // we need to calculate it dynamically as it could have been changed by properties (rare, but possible) // also this way we capture the actual index size rather than the max so size the int[] to only what we need for (Bucket bd : buckets) { lengthFromBuckets += bd.data.length; } data = new int[lengthFromBuckets]; int index = 0; int sum = 0; for (Bucket bd : buckets) { PercentileBucketData pbd = bd.data; int length = pbd.length(); for (int i = 0; i < length; i++) { int v = pbd.list.get(i); this.data[index++] = v; sum += v; } } this.length = index; if (this.length == 0) { this.mean = 0; } else { this.mean = sum / this.length; } Arrays.sort(this.data, 0, length); }
Bucket(long startTime, int bucketDataLength) { this.windowStart = startTime; this.data = new PercentileBucketData(bucketDataLength); }