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