View Javadoc
1   /*
2    * Copyright 2025 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
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       * An allocation pattern derived from a web socket proxy service.
52       */
53      private static final int[] WEB_SOCKET_PROXY_PATTERN = {
54              // Size, Frequency
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 }