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 = toAbsolutePath(jfrFile);
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 @SuppressWarnings("JvmTaintAnalysis")
478 private static Path toAbsolutePath(String jfrFile) {
479 return Paths.get(jfrFile).toAbsolutePath();
480 }
481
482 void setUp(int[] pattern) {
483 adaptive128 = new AdaptiveByteBufAllocator();
484 pooled128 = new PooledByteBufAllocator();
485 adaptive2048 = new AdaptiveByteBufAllocator();
486 pooled2048 = new PooledByteBufAllocator();
487 PatternItr itr = new PatternItr(pattern);
488 size = new int[pattern.length >> 1];
489 cumulativeFrequency = new int[pattern.length >> 1];
490 sumFrequency = 0;
491 count = 0;
492 while (itr.next()) {
493 sumFrequency += itr.frequency();
494 size[count] = itr.size();
495 cumulativeFrequency[count] = sumFrequency;
496 count++;
497 }
498 }
499
500 void run(int concurrencyLevel, int runningTimeSeconds) throws Exception {
501 AllocConfig[] allocs = {
502 new AllocConfig(true, 128),
503 new AllocConfig(false, 128),
504 new AllocConfig(true, 512),
505 new AllocConfig(false, 512),
506 new AllocConfig(true, 1024),
507 new AllocConfig(false, 1024),
508 };
509
510 CountDownLatch startLatch = new CountDownLatch(1);
511 AtomicBoolean stopCondition = new AtomicBoolean();
512 List<Thread> threads = new ArrayList<>();
513
514 for (int i = 0; i < concurrencyLevel; i++) {
515 for (AllocConfig alloc : allocs) {
516 threads.add(alloc.start(startLatch, stopCondition));
517 }
518 }
519
520 DefaultCategoryDataset dataset = new DefaultCategoryDataset();
521 JFreeChart chart = ChartFactory.createLineChart("Memory Usage", "Time", "Bytes", dataset);
522 for (int i = 0; i < allocs.length; i++) {
523 chart.getCategoryPlot().getRenderer().setSeriesStroke(i, new BasicStroke(3.0f));
524 }
525 int windowWidth = 1400;
526 int windowHeight = 1050;
527 ImageIcon image = new ImageIcon(chart.createBufferedImage(windowWidth, windowHeight - 30));
528
529 JFrame frame = new JFrame("Results");
530 frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
531 frame.add(new JLabel(image));
532 frame.setBounds(0, 0, windowWidth, windowHeight);
533 frame.setVisible(true);
534 Runnable updateImage = () -> {
535 Rectangle bounds = frame.getBounds();
536 image.setImage(chart.createBufferedImage(bounds.width, bounds.height - 30));
537 frame.repaint();
538 };
539 frame.addComponentListener(new ComponentAdapter() {
540 @Override
541 public void componentResized(ComponentEvent e) {
542 updateImage.run();
543 }
544 });
545
546 startLatch.countDown();
547
548 System.out.println("Time," + Stream.of(allocs)
549 .map(AllocConfig::name)
550 .collect(Collectors.joining("\",\"", "\"", "\"")));
551 for (int i = 0; i < runningTimeSeconds; i++) {
552 Thread.sleep(1000);
553 Integer iteration = Integer.valueOf(i);
554
555 long[] usages = new long[allocs.length];
556 for (int j = 0; j < usages.length; j++) {
557 usages[j] = allocs[j].usedMemory();
558 }
559 System.out.println(iteration + "," + LongStream.of(usages)
560 .mapToObj(String::valueOf).collect(Collectors.joining(",")));
561 SwingUtilities.invokeLater(() -> {
562 for (int j = 0; j < usages.length; j++) {
563 dataset.addValue(usages[j], allocs[j].name(), iteration);
564 }
565 updateImage.run();
566 });
567 }
568
569 stopCondition.set(true);
570
571 for (Thread thread : threads) {
572 thread.join();
573 }
574 System.out.println("\nDone");
575 }
576
577 private final class AllocConfig {
578 private final AbstractByteBufAllocator allocator;
579 private final int avgLiveBufs;
580 private final SplittableRandom rng;
581 private final String name;
582
583 AllocConfig(boolean isAdaptive, int avgLiveBufs) {
584 allocator = isAdaptive ? new AdaptiveByteBufAllocator() : new PooledByteBufAllocator();
585 name = String.format(isAdaptive ? "Adaptive (%s)" : "Pooled (%s)", avgLiveBufs);
586 this.avgLiveBufs = avgLiveBufs;
587 rng = new SplittableRandom(0xBEEFBEEFL);
588 }
589
590 Thread start(CountDownLatch startLatch, AtomicBoolean stopCondition) {
591 return new Workload(startLatch, allocator, rng.split(), stopCondition, avgLiveBufs).start(name);
592 }
593
594 long usedMemory() {
595 if (allocator instanceof AdaptiveByteBufAllocator) {
596 return ((AdaptiveByteBufAllocator) allocator).usedHeapMemory();
597 }
598 return ((PooledByteBufAllocator) allocator).usedHeapMemory();
599 }
600
601 String name() {
602 return name;
603 }
604 }
605
606 private final class Workload implements Runnable {
607 private final CountDownLatch startLatch;
608 private final ByteBufAllocator allocator;
609 private final SplittableRandom rng;
610 private final AtomicBoolean stopCondition;
611 private final int avgLiveBuffers;
612
613 private Workload(CountDownLatch startLatch,
614 ByteBufAllocator allocator,
615 SplittableRandom rng,
616 AtomicBoolean stopCondition,
617 int avgLiveBuffers) {
618 this.startLatch = startLatch;
619 this.allocator = allocator;
620 this.rng = rng;
621 this.stopCondition = stopCondition;
622 this.avgLiveBuffers = avgLiveBuffers;
623 }
624
625 Thread start(String name) {
626 Thread thread = new Thread(this, name + '-' + THREAD_NAMES.compute(name, (n, c) -> c == null ? 1 : c + 1));
627 thread.start();
628 return thread;
629 }
630
631 @SuppressWarnings("BusyWait")
632 @Override
633 public void run() {
634 try {
635 startLatch.await();
636 List<ByteBuf> buffers = new ArrayList<>();
637 while (!stopCondition.get()) {
638 Thread.sleep(1);
639
640 int freqChoice = rng.nextInt(0, sumFrequency);
641 int choiceIndex = Arrays.binarySearch(cumulativeFrequency, freqChoice);
642 if (choiceIndex < 0) {
643 choiceIndex = -(choiceIndex + 1);
644 }
645 int chosenSize = size[choiceIndex];
646 ByteBuf buf = allocator.heapBuffer(chosenSize);
647 buffers.add(buf);
648 while (buffers.size() > rng.nextInt(0, avgLiveBuffers << 1)) {
649 int deallocChoice = rng.nextInt(0, buffers.size());
650 ByteBuf toDealloc = buffers.get(deallocChoice);
651 ByteBuf lastBuffer = buffers.remove(buffers.size() - 1);
652 if (toDealloc != lastBuffer) {
653 buffers.set(deallocChoice, lastBuffer);
654 }
655 toDealloc.release();
656 }
657 }
658 } catch (InterruptedException e) {
659 throw new RuntimeException(e);
660 }
661 }
662 }
663
664 private static final class PatternItr {
665 private final int[] pattern;
666 private int index;
667 private boolean hasData;
668 private int size;
669 private int frequency;
670
671 PatternItr(int[] pattern) {
672 this.pattern = pattern;
673 }
674
675 public boolean next() {
676 if (index < pattern.length) {
677 size = pattern[index++];
678 frequency = pattern[index++];
679 return hasData = true;
680 }
681 return hasData = false;
682 }
683
684 public int size() {
685 if (!hasData) {
686 throw new IllegalStateException();
687 }
688 return size;
689 }
690
691 public int frequency() {
692 if (!hasData) {
693 throw new IllegalStateException();
694 }
695 return frequency;
696 }
697 }
698 }