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 = 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 }