1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty.buffer;
17
18 import jdk.jfr.consumer.RecordedEvent;
19 import jdk.jfr.consumer.RecordingFile;
20 import org.jfree.chart.ChartFactory;
21 import org.jfree.chart.JFreeChart;
22 import org.jfree.data.category.DefaultCategoryDataset;
23
24 import java.awt.BasicStroke;
25 import java.awt.Rectangle;
26 import java.awt.event.ComponentAdapter;
27 import java.awt.event.ComponentEvent;
28 import java.io.IOException;
29 import java.nio.file.Path;
30 import java.nio.file.Paths;
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.SplittableRandom;
36 import java.util.TreeMap;
37 import java.util.concurrent.ConcurrentHashMap;
38 import java.util.concurrent.CountDownLatch;
39 import java.util.concurrent.atomic.AtomicBoolean;
40 import java.util.stream.Collectors;
41 import java.util.stream.LongStream;
42 import java.util.stream.Stream;
43 import javax.swing.ImageIcon;
44 import javax.swing.JFrame;
45 import javax.swing.JLabel;
46 import javax.swing.SwingUtilities;
47 import javax.swing.WindowConstants;
48
49 public class AllocationPatternSimulator {
50
51
52
53 private static final int[] WEB_SOCKET_PROXY_PATTERN = {
54
55 9, 316,
56 13, 3,
57 15, 10344,
58 17, 628,
59 21, 316,
60 36, 338,
61 48, 338,
62 64, 23,
63 128, 17,
64 256, 21272,
65 287, 69,
66 304, 65,
67 331, 11,
68 332, 7,
69 335, 2,
70 343, 2,
71 362, 1,
72 363, 16,
73 365, 17,
74 370, 11,
75 371, 51,
76 392, 11,
77 393, 4,
78 396, 3,
79 401, 1,
80 402, 3,
81 413, 1,
82 414, 2,
83 419, 16,
84 421, 1,
85 423, 16,
86 424, 46,
87 433, 1,
88 435, 1,
89 439, 3,
90 441, 13,
91 444, 3,
92 449, 1,
93 450, 1,
94 453, 2,
95 455, 3,
96 458, 3,
97 462, 7,
98 463, 8,
99 464, 1,
100 466, 59,
101 470, 1,
102 472, 2,
103 475, 1,
104 478, 2,
105 480, 12,
106 481, 16,
107 482, 2,
108 483, 2,
109 486, 1,
110 489, 2,
111 493, 2,
112 494, 1,
113 495, 1,
114 497, 14,
115 498, 1,
116 499, 2,
117 500, 58,
118 503, 1,
119 507, 1,
120 509, 2,
121 510, 2,
122 511, 13,
123 512, 3,
124 513, 4,
125 516, 1,
126 519, 2,
127 520, 1,
128 522, 5,
129 523, 1,
130 525, 15,
131 526, 1,
132 527, 55,
133 528, 2,
134 529, 1,
135 530, 1,
136 531, 3,
137 533, 1,
138 534, 1,
139 535, 1,
140 536, 10,
141 538, 4,
142 539, 3,
143 540, 2,
144 541, 1,
145 542, 3,
146 543, 10,
147 545, 5,
148 546, 1,
149 547, 14,
150 548, 1,
151 549, 53,
152 551, 1,
153 552, 1,
154 553, 1,
155 554, 1,
156 555, 2,
157 556, 11,
158 557, 3,
159 558, 7,
160 559, 4,
161 561, 3,
162 562, 1,
163 563, 6,
164 564, 3,
165 565, 13,
166 566, 31,
167 567, 24,
168 568, 1,
169 569, 1,
170 570, 4,
171 571, 2,
172 572, 9,
173 573, 7,
174 574, 3,
175 575, 2,
176 576, 4,
177 577, 2,
178 578, 7,
179 579, 12,
180 580, 38,
181 581, 22,
182 582, 1,
183 583, 3,
184 584, 5,
185 585, 9,
186 586, 9,
187 587, 6,
188 588, 3,
189 589, 5,
190 590, 8,
191 591, 23,
192 592, 42,
193 593, 3,
194 594, 5,
195 595, 11,
196 596, 10,
197 597, 7,
198 598, 5,
199 599, 13,
200 600, 26,
201 601, 41,
202 602, 8,
203 603, 14,
204 604, 18,
205 605, 14,
206 606, 16,
207 607, 35,
208 608, 57,
209 609, 74,
210 610, 13,
211 611, 24,
212 612, 22,
213 613, 52,
214 614, 88,
215 615, 28,
216 616, 23,
217 617, 37,
218 618, 70,
219 619, 74,
220 620, 31,
221 621, 59,
222 622, 110,
223 623, 37,
224 624, 67,
225 625, 110,
226 626, 55,
227 627, 140,
228 628, 71,
229 629, 141,
230 630, 141,
231 631, 147,
232 632, 190,
233 633, 254,
234 634, 349,
235 635, 635,
236 636, 5443,
237 637, 459,
238 639, 1,
239 640, 2,
240 642, 1,
241 644, 2,
242 645, 1,
243 647, 1,
244 649, 1,
245 650, 1,
246 652, 1,
247 655, 3,
248 656, 1,
249 658, 4,
250 659, 2,
251 660, 1,
252 661, 1,
253 662, 6,
254 663, 8,
255 664, 9,
256 665, 4,
257 666, 5,
258 667, 62,
259 668, 5,
260 693, 1,
261 701, 2,
262 783, 1,
263 941, 1,
264 949, 1,
265 958, 16,
266 988, 1,
267 1024, 29289,
268 1028, 1,
269 1086, 1,
270 1249, 2,
271 1263, 1,
272 1279, 24,
273 1280, 11,
274 1309, 1,
275 1310, 1,
276 1311, 2,
277 1343, 1,
278 1360, 2,
279 1483, 1,
280 1567, 1,
281 1957, 1,
282 2048, 2636,
283 2060, 1,
284 2146, 1,
285 2190, 1,
286 2247, 1,
287 2273, 1,
288 2274, 1,
289 2303, 106,
290 2304, 45,
291 2320, 1,
292 2333, 10,
293 2334, 14,
294 2335, 7,
295 2367, 7,
296 2368, 2,
297 2384, 7,
298 2399, 1,
299 2400, 14,
300 2401, 6,
301 2423, 1,
302 2443, 9,
303 2444, 1,
304 2507, 3,
305 3039, 1,
306 3140, 1,
307 3891, 1,
308 3893, 1,
309 4096, 26,
310 4118, 1,
311 4321, 1,
312 4351, 226,
313 4352, 15,
314 4370, 1,
315 4381, 1,
316 4382, 11,
317 4383, 10,
318 4415, 4,
319 4416, 3,
320 4432, 5,
321 4447, 1,
322 4448, 31,
323 4449, 14,
324 4471, 1,
325 4491, 42,
326 4492, 16,
327 4555, 26,
328 4556, 19,
329 4571, 1,
330 4572, 2,
331 4573, 53,
332 4574, 165,
333 5770, 1,
334 5803, 2,
335 6026, 1,
336 6144, 2,
337 6249, 1,
338 6278, 1,
339 6466, 1,
340 6680, 1,
341 6726, 2,
342 6728, 1,
343 6745, 1,
344 6746, 1,
345 6759, 1,
346 6935, 1,
347 6978, 1,
348 6981, 2,
349 6982, 1,
350 7032, 1,
351 7081, 1,
352 7086, 1,
353 7110, 1,
354 7172, 3,
355 7204, 2,
356 7236, 2,
357 7238, 1,
358 7330, 1,
359 7427, 3,
360 7428, 1,
361 7458, 1,
362 7459, 1,
363 7650, 2,
364 7682, 6,
365 7765, 1,
366 7937, 3,
367 7969, 1,
368 8192, 2,
369 8415, 1,
370 8447, 555,
371 8478, 3,
372 8479, 5,
373 8511, 2,
374 8512, 1,
375 8528, 1,
376 8543, 2,
377 8544, 9,
378 8545, 8,
379 8567, 1,
380 8587, 16,
381 8588, 12,
382 8650, 1,
383 8651, 9,
384 8652, 9,
385 8668, 3,
386 8669, 46,
387 8670, 195,
388 8671, 6,
389 10240, 4,
390 14336, 1,
391 14440, 4,
392 14663, 3,
393 14919, 1,
394 14950, 2,
395 15002, 1,
396 15159, 1,
397 15173, 2,
398 15205, 1,
399 15395, 1,
400 15396, 1,
401 15397, 2,
402 15428, 1,
403 15446, 1,
404 15619, 7,
405 15651, 5,
406 15683, 2,
407 15874, 8,
408 15906, 8,
409 15907, 2,
410 16128, 2,
411 16129, 37,
412 16161, 3,
413 16352, 2,
414 16383, 1,
415 16384, 42,
416 16610, 2,
417 16639, 9269,
418 16704, 2,
419 16736, 3,
420 16737, 2,
421 16779, 2,
422 16780, 7,
423 16843, 2,
424 16844, 5,
425 16860, 6,
426 16861, 67,
427 16862, 281,
428 16863, 13,
429 18432, 6,
430 };
431 private static final int CONCURRENCY_LEVEL = 4;
432 private static final int RUNNING_TIME_SECONDS = 120;
433 private static final ConcurrentHashMap<String, Integer> THREAD_NAMES = new ConcurrentHashMap<>();
434
435 AdaptiveByteBufAllocator adaptive128;
436 PooledByteBufAllocator pooled128;
437 AdaptiveByteBufAllocator adaptive2048;
438 PooledByteBufAllocator pooled2048;
439 int[] size;
440 int[] cumulativeFrequency;
441 int sumFrequency;
442 int count;
443
444 public static void main(String[] args) throws Exception {
445 int[] pattern = args.length == 0 ? WEB_SOCKET_PROXY_PATTERN : buildPattern(args[0]);
446 AllocationPatternSimulator runner = new AllocationPatternSimulator();
447 runner.setUp(pattern);
448 runner.run(CONCURRENCY_LEVEL, RUNNING_TIME_SECONDS);
449 }
450
451 private static int[] buildPattern(String jfrFile) throws IOException {
452 Path path = Paths.get(jfrFile).toAbsolutePath();
453 TreeMap<Integer, Integer> summation = new TreeMap<>();
454 try (RecordingFile eventReader = new RecordingFile(path)) {
455 while (eventReader.hasMoreEvents()) {
456 RecordedEvent event = eventReader.readEvent();
457 String name = event.getEventType().getName();
458 if (("AllocateBufferEvent".equals(name) || "io.netty.AllocateBuffer".equals(name)) &&
459 event.hasField("size")) {
460 int size = event.getInt("size");
461 summation.compute(size, (k, v) -> v == null ? 1 : v + 1);
462 }
463 }
464 }
465 if (summation.isEmpty()) {
466 throw new IllegalStateException("No 'AllocateBufferEvent' records found in JFR file: " + jfrFile);
467 }
468 int[] pattern = new int[summation.size() * 2];
469 int index = 0;
470 for (Map.Entry<Integer, Integer> entry : summation.entrySet()) {
471 pattern[index++] = entry.getKey();
472 pattern[index++] = entry.getValue();
473 }
474 return pattern;
475 }
476
477 void setUp(int[] pattern) {
478 adaptive128 = new AdaptiveByteBufAllocator();
479 pooled128 = new PooledByteBufAllocator();
480 adaptive2048 = new AdaptiveByteBufAllocator();
481 pooled2048 = new PooledByteBufAllocator();
482 PatternItr itr = new PatternItr(pattern);
483 size = new int[pattern.length >> 1];
484 cumulativeFrequency = new int[pattern.length >> 1];
485 sumFrequency = 0;
486 count = 0;
487 while (itr.next()) {
488 sumFrequency += itr.frequency();
489 size[count] = itr.size();
490 cumulativeFrequency[count] = sumFrequency;
491 count++;
492 }
493 }
494
495 void run(int concurrencyLevel, int runningTimeSeconds) throws Exception {
496 AllocConfig[] allocs = {
497 new AllocConfig(true, 128),
498 new AllocConfig(false, 128),
499 new AllocConfig(true, 512),
500 new AllocConfig(false, 512),
501 new AllocConfig(true, 1024),
502 new AllocConfig(false, 1024),
503 };
504
505 CountDownLatch startLatch = new CountDownLatch(1);
506 AtomicBoolean stopCondition = new AtomicBoolean();
507 List<Thread> threads = new ArrayList<>();
508
509 for (int i = 0; i < concurrencyLevel; i++) {
510 for (AllocConfig alloc : allocs) {
511 threads.add(alloc.start(startLatch, stopCondition));
512 }
513 }
514
515 DefaultCategoryDataset dataset = new DefaultCategoryDataset();
516 JFreeChart chart = ChartFactory.createLineChart("Memory Usage", "Time", "Bytes", dataset);
517 for (int i = 0; i < allocs.length; i++) {
518 chart.getCategoryPlot().getRenderer().setSeriesStroke(i, new BasicStroke(3.0f));
519 }
520 int windowWidth = 1400;
521 int windowHeight = 1050;
522 ImageIcon image = new ImageIcon(chart.createBufferedImage(windowWidth, windowHeight - 30));
523
524 JFrame frame = new JFrame("Results");
525 frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
526 frame.add(new JLabel(image));
527 frame.setBounds(0, 0, windowWidth, windowHeight);
528 frame.setVisible(true);
529 Runnable updateImage = () -> {
530 Rectangle bounds = frame.getBounds();
531 image.setImage(chart.createBufferedImage(bounds.width, bounds.height - 30));
532 frame.repaint();
533 };
534 frame.addComponentListener(new ComponentAdapter() {
535 @Override
536 public void componentResized(ComponentEvent e) {
537 updateImage.run();
538 }
539 });
540
541 startLatch.countDown();
542
543 System.out.println("Time," + Stream.of(allocs)
544 .map(AllocConfig::name)
545 .collect(Collectors.joining("\",\"", "\"", "\"")));
546 for (int i = 0; i < runningTimeSeconds; i++) {
547 Thread.sleep(1000);
548 Integer iteration = Integer.valueOf(i);
549
550 long[] usages = new long[allocs.length];
551 for (int j = 0; j < usages.length; j++) {
552 usages[j] = allocs[j].usedMemory();
553 }
554 System.out.println(iteration + "," + LongStream.of(usages)
555 .mapToObj(String::valueOf).collect(Collectors.joining(",")));
556 SwingUtilities.invokeLater(() -> {
557 for (int j = 0; j < usages.length; j++) {
558 dataset.addValue(usages[j], allocs[j].name(), iteration);
559 }
560 updateImage.run();
561 });
562 }
563
564 stopCondition.set(true);
565
566 for (Thread thread : threads) {
567 thread.join();
568 }
569 System.out.println("\nDone");
570 }
571
572 private final class AllocConfig {
573 private final AbstractByteBufAllocator allocator;
574 private final int avgLiveBufs;
575 private final SplittableRandom rng;
576 private final String name;
577
578 AllocConfig(boolean isAdaptive, int avgLiveBufs) {
579 allocator = isAdaptive ? new AdaptiveByteBufAllocator() : new PooledByteBufAllocator();
580 name = String.format(isAdaptive ? "Adaptive (%s)" : "Pooled (%s)", avgLiveBufs);
581 this.avgLiveBufs = avgLiveBufs;
582 rng = new SplittableRandom(0xBEEFBEEFL);
583 }
584
585 Thread start(CountDownLatch startLatch, AtomicBoolean stopCondition) {
586 return new Workload(startLatch, allocator, rng.split(), stopCondition, avgLiveBufs).start(name);
587 }
588
589 long usedMemory() {
590 if (allocator instanceof AdaptiveByteBufAllocator) {
591 return ((AdaptiveByteBufAllocator) allocator).usedHeapMemory();
592 }
593 return ((PooledByteBufAllocator) allocator).usedHeapMemory();
594 }
595
596 String name() {
597 return name;
598 }
599 }
600
601 private final class Workload implements Runnable {
602 private final CountDownLatch startLatch;
603 private final ByteBufAllocator allocator;
604 private final SplittableRandom rng;
605 private final AtomicBoolean stopCondition;
606 private final int avgLiveBuffers;
607
608 private Workload(CountDownLatch startLatch,
609 ByteBufAllocator allocator,
610 SplittableRandom rng,
611 AtomicBoolean stopCondition,
612 int avgLiveBuffers) {
613 this.startLatch = startLatch;
614 this.allocator = allocator;
615 this.rng = rng;
616 this.stopCondition = stopCondition;
617 this.avgLiveBuffers = avgLiveBuffers;
618 }
619
620 Thread start(String name) {
621 Thread thread = new Thread(this, name + '-' + THREAD_NAMES.compute(name, (n, c) -> c == null ? 1 : c + 1));
622 thread.start();
623 return thread;
624 }
625
626 @SuppressWarnings("BusyWait")
627 @Override
628 public void run() {
629 try {
630 startLatch.await();
631 List<ByteBuf> buffers = new ArrayList<>();
632 while (!stopCondition.get()) {
633 Thread.sleep(1);
634
635 int freqChoice = rng.nextInt(0, sumFrequency);
636 int choiceIndex = Arrays.binarySearch(cumulativeFrequency, freqChoice);
637 if (choiceIndex < 0) {
638 choiceIndex = -(choiceIndex + 1);
639 }
640 int chosenSize = size[choiceIndex];
641 ByteBuf buf = allocator.heapBuffer(chosenSize);
642 buffers.add(buf);
643 while (buffers.size() > rng.nextInt(0, avgLiveBuffers << 1)) {
644 int deallocChoice = rng.nextInt(0, buffers.size());
645 ByteBuf toDealloc = buffers.get(deallocChoice);
646 ByteBuf lastBuffer = buffers.remove(buffers.size() - 1);
647 if (toDealloc != lastBuffer) {
648 buffers.set(deallocChoice, lastBuffer);
649 }
650 toDealloc.release();
651 }
652 }
653 } catch (InterruptedException e) {
654 throw new RuntimeException(e);
655 }
656 }
657 }
658
659 private static final class PatternItr {
660 private final int[] pattern;
661 private int index;
662 private boolean hasData;
663 private int size;
664 private int frequency;
665
666 PatternItr(int[] pattern) {
667 this.pattern = pattern;
668 }
669
670 public boolean next() {
671 if (index < pattern.length) {
672 size = pattern[index++];
673 frequency = pattern[index++];
674 return hasData = true;
675 }
676 return hasData = false;
677 }
678
679 public int size() {
680 if (!hasData) {
681 throw new IllegalStateException();
682 }
683 return size;
684 }
685
686 public int frequency() {
687 if (!hasData) {
688 throw new IllegalStateException();
689 }
690 return frequency;
691 }
692 }
693 }