1
2
3
4
5
6
7
8
9
10
11
12
13 package io.netty.testsuite.transport.socket;
14
15 import io.netty.bootstrap.Bootstrap;
16 import io.netty.bootstrap.ServerBootstrap;
17 import io.netty.buffer.ByteBuf;
18 import io.netty.buffer.Unpooled;
19 import io.netty.channel.Channel;
20 import io.netty.channel.ChannelHandlerContext;
21 import io.netty.channel.ChannelInitializer;
22 import io.netty.channel.SimpleChannelInboundHandler;
23 import io.netty.channel.socket.SocketChannel;
24 import io.netty.handler.traffic.AbstractTrafficShapingHandler;
25 import io.netty.handler.traffic.ChannelTrafficShapingHandler;
26 import io.netty.handler.traffic.GlobalTrafficShapingHandler;
27 import io.netty.handler.traffic.TrafficCounter;
28 import io.netty.util.concurrent.DefaultEventExecutorGroup;
29 import io.netty.util.concurrent.EventExecutorGroup;
30 import io.netty.util.concurrent.Promise;
31 import io.netty.util.internal.logging.InternalLogger;
32 import io.netty.util.internal.logging.InternalLoggerFactory;
33 import org.junit.jupiter.api.AfterAll;
34 import org.junit.jupiter.api.BeforeAll;
35 import org.junit.jupiter.api.Test;
36 import org.junit.jupiter.api.TestInfo;
37 import org.junit.jupiter.api.Timeout;
38
39 import java.io.IOException;
40 import java.util.Arrays;
41 import java.util.concurrent.Executors;
42 import java.util.concurrent.ScheduledExecutorService;
43 import java.util.concurrent.ThreadLocalRandom;
44 import java.util.concurrent.TimeUnit;
45 import java.util.concurrent.atomic.AtomicReference;
46
47 import static org.junit.jupiter.api.Assertions.assertTrue;
48
49 public class TrafficShapingHandlerTest extends AbstractSocketTest {
50 private static final InternalLogger logger = InternalLoggerFactory.getInstance(TrafficShapingHandlerTest.class);
51 private static final InternalLogger loggerServer = InternalLoggerFactory.getInstance("ServerTSH");
52 private static final InternalLogger loggerClient = InternalLoggerFactory.getInstance("ClientTSH");
53
54 static final int messageSize = 1024;
55 static final int bandwidthFactor = 12;
56 static final int minfactor = 3;
57 static final int maxfactor = bandwidthFactor + bandwidthFactor / 2;
58 static final long stepms = (1000 / bandwidthFactor - 10) / 10 * 10;
59 static final long minimalms = Math.max(stepms / 2, 20) / 10 * 10;
60 static final long check = 10;
61 static final byte[] data = new byte[messageSize];
62
63 private static final String TRAFFIC = "traffic";
64 private static String currentTestName;
65 private static int currentTestRun;
66
67 private static EventExecutorGroup group;
68 private static EventExecutorGroup groupForGlobal;
69 private static final ScheduledExecutorService executor = Executors.newScheduledThreadPool(10);
70 static {
71 ThreadLocalRandom.current().nextBytes(data);
72 }
73
74 @BeforeAll
75 public static void createGroup() {
76 logger.info("Bandwidth: " + minfactor + " <= " + bandwidthFactor + " <= " + maxfactor +
77 " StepMs: " + stepms + " MinMs: " + minimalms + " CheckMs: " + check);
78 group = new DefaultEventExecutorGroup(8);
79 groupForGlobal = new DefaultEventExecutorGroup(8);
80 }
81
82 @AfterAll
83 public static void destroyGroup() throws Exception {
84 group.shutdownGracefully().sync();
85 groupForGlobal.shutdownGracefully().sync();
86 executor.shutdown();
87 }
88
89 private static long[] computeWaitRead(int[] multipleMessage) {
90 long[] minimalWaitBetween = new long[multipleMessage.length + 1];
91 minimalWaitBetween[0] = 0;
92 for (int i = 0; i < multipleMessage.length; i++) {
93 if (multipleMessage[i] > 1) {
94 minimalWaitBetween[i + 1] = (multipleMessage[i] - 1) * stepms + minimalms;
95 } else {
96 minimalWaitBetween[i + 1] = 10;
97 }
98 }
99 return minimalWaitBetween;
100 }
101
102 private static long[] computeWaitWrite(int[] multipleMessage) {
103 long[] minimalWaitBetween = new long[multipleMessage.length + 1];
104 for (int i = 0; i < multipleMessage.length; i++) {
105 if (multipleMessage[i] > 1) {
106 minimalWaitBetween[i] = (multipleMessage[i] - 1) * stepms + minimalms;
107 } else {
108 minimalWaitBetween[i] = 10;
109 }
110 }
111 return minimalWaitBetween;
112 }
113
114 private static long[] computeWaitAutoRead(int []autoRead) {
115 long [] minimalWaitBetween = new long[autoRead.length + 1];
116 minimalWaitBetween[0] = 0;
117 for (int i = 0; i < autoRead.length; i++) {
118 if (autoRead[i] != 0) {
119 if (autoRead[i] > 0) {
120 minimalWaitBetween[i + 1] = -1;
121 } else {
122 minimalWaitBetween[i + 1] = check;
123 }
124 } else {
125 minimalWaitBetween[i + 1] = 0;
126 }
127 }
128 return minimalWaitBetween;
129 }
130
131 @Test
132 @Timeout(value = 10000, unit = TimeUnit.MILLISECONDS)
133 public void testNoTrafficShapping(TestInfo testInfo) throws Throwable {
134 currentTestName = "TEST NO TRAFFIC";
135 currentTestRun = 0;
136 run(testInfo, new Runner<ServerBootstrap, Bootstrap>() {
137 @Override
138 public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable {
139 testNoTrafficShapping(serverBootstrap, bootstrap);
140 }
141 });
142 }
143
144 public void testNoTrafficShapping(ServerBootstrap sb, Bootstrap cb) throws Throwable {
145 int[] autoRead = null;
146 int[] multipleMessage = { 1, 2, 1 };
147 long[] minimalWaitBetween = null;
148 testTrafficShapping0(sb, cb, false, false, false, false, autoRead, minimalWaitBetween, multipleMessage);
149 }
150
151 @Test
152 @Timeout(value = 10000, unit = TimeUnit.MILLISECONDS)
153 public void testWriteTrafficShapping(TestInfo testInfo) throws Throwable {
154 currentTestName = "TEST WRITE";
155 currentTestRun = 0;
156 run(testInfo, new Runner<ServerBootstrap, Bootstrap>() {
157 @Override
158 public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable {
159 testWriteTrafficShapping(serverBootstrap, bootstrap);
160 }
161 });
162 }
163
164 public void testWriteTrafficShapping(ServerBootstrap sb, Bootstrap cb) throws Throwable {
165 int[] autoRead = null;
166 int[] multipleMessage = { 1, 2, 1, 1 };
167 long[] minimalWaitBetween = computeWaitWrite(multipleMessage);
168 testTrafficShapping0(sb, cb, false, false, true, false, autoRead, minimalWaitBetween, multipleMessage);
169 }
170
171 @Test
172 @Timeout(value = 10000, unit = TimeUnit.MILLISECONDS)
173 public void testReadTrafficShapping(TestInfo testInfo) throws Throwable {
174 currentTestName = "TEST READ";
175 currentTestRun = 0;
176 run(testInfo, new Runner<ServerBootstrap, Bootstrap>() {
177 @Override
178 public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable {
179 testReadTrafficShapping(serverBootstrap, bootstrap);
180 }
181 });
182 }
183
184 public void testReadTrafficShapping(ServerBootstrap sb, Bootstrap cb) throws Throwable {
185 int[] autoRead = null;
186 int[] multipleMessage = { 1, 2, 1, 1 };
187 long[] minimalWaitBetween = computeWaitRead(multipleMessage);
188 testTrafficShapping0(sb, cb, false, true, false, false, autoRead, minimalWaitBetween, multipleMessage);
189 }
190
191 @Test
192 @Timeout(value = 10000, unit = TimeUnit.MILLISECONDS)
193 public void testWrite1TrafficShapping(TestInfo testInfo) throws Throwable {
194 currentTestName = "TEST WRITE";
195 currentTestRun = 0;
196 run(testInfo, new Runner<ServerBootstrap, Bootstrap>() {
197 @Override
198 public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable {
199 testWrite1TrafficShapping(serverBootstrap, bootstrap);
200 }
201 });
202 }
203
204 public void testWrite1TrafficShapping(ServerBootstrap sb, Bootstrap cb) throws Throwable {
205 int[] autoRead = null;
206 int[] multipleMessage = { 1, 1, 1 };
207 long[] minimalWaitBetween = computeWaitWrite(multipleMessage);
208 testTrafficShapping0(sb, cb, false, false, true, false, autoRead, minimalWaitBetween, multipleMessage);
209 }
210
211 @Test
212 @Timeout(value = 10000, unit = TimeUnit.MILLISECONDS)
213 public void testRead1TrafficShapping(TestInfo testInfo) throws Throwable {
214 currentTestName = "TEST READ";
215 currentTestRun = 0;
216 run(testInfo, new Runner<ServerBootstrap, Bootstrap>() {
217 @Override
218 public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable {
219 testRead1TrafficShapping(serverBootstrap, bootstrap);
220 }
221 });
222 }
223
224 public void testRead1TrafficShapping(ServerBootstrap sb, Bootstrap cb) throws Throwable {
225 int[] autoRead = null;
226 int[] multipleMessage = { 1, 1, 1 };
227 long[] minimalWaitBetween = computeWaitRead(multipleMessage);
228 testTrafficShapping0(sb, cb, false, true, false, false, autoRead, minimalWaitBetween, multipleMessage);
229 }
230
231 @Test
232 @Timeout(value = 10000, unit = TimeUnit.MILLISECONDS)
233 public void testWriteGlobalTrafficShapping(TestInfo testInfo) throws Throwable {
234 currentTestName = "TEST GLOBAL WRITE";
235 currentTestRun = 0;
236 run(testInfo, new Runner<ServerBootstrap, Bootstrap>() {
237 @Override
238 public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable {
239 testWriteGlobalTrafficShapping(serverBootstrap, bootstrap);
240 }
241 });
242 }
243
244 public void testWriteGlobalTrafficShapping(ServerBootstrap sb, Bootstrap cb) throws Throwable {
245 int[] autoRead = null;
246 int[] multipleMessage = { 1, 2, 1, 1 };
247 long[] minimalWaitBetween = computeWaitWrite(multipleMessage);
248 testTrafficShapping0(sb, cb, false, false, true, true, autoRead, minimalWaitBetween, multipleMessage);
249 }
250
251 @Test
252 @Timeout(value = 10000, unit = TimeUnit.MILLISECONDS)
253 public void testReadGlobalTrafficShapping(TestInfo testInfo) throws Throwable {
254 currentTestName = "TEST GLOBAL READ";
255 currentTestRun = 0;
256 run(testInfo, new Runner<ServerBootstrap, Bootstrap>() {
257 @Override
258 public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable {
259 testReadGlobalTrafficShapping(serverBootstrap, bootstrap);
260 }
261 });
262 }
263
264 public void testReadGlobalTrafficShapping(ServerBootstrap sb, Bootstrap cb) throws Throwable {
265 int[] autoRead = null;
266 int[] multipleMessage = { 1, 2, 1, 1 };
267 long[] minimalWaitBetween = computeWaitRead(multipleMessage);
268 testTrafficShapping0(sb, cb, false, true, false, true, autoRead, minimalWaitBetween, multipleMessage);
269 }
270
271 @Test
272 @Timeout(value = 10000, unit = TimeUnit.MILLISECONDS)
273 public void testAutoReadTrafficShapping(TestInfo testInfo) throws Throwable {
274 currentTestName = "TEST AUTO READ";
275 currentTestRun = 0;
276 run(testInfo, new Runner<ServerBootstrap, Bootstrap>() {
277 @Override
278 public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable {
279 testAutoReadTrafficShapping(serverBootstrap, bootstrap);
280 }
281 });
282 }
283
284 public void testAutoReadTrafficShapping(ServerBootstrap sb, Bootstrap cb) throws Throwable {
285 int[] autoRead = { 1, -1, -1, 1, -2, 0, 1, 0, -3, 0, 1, 2, 0 };
286 int[] multipleMessage = new int[autoRead.length];
287 Arrays.fill(multipleMessage, 1);
288 long[] minimalWaitBetween = computeWaitAutoRead(autoRead);
289 testTrafficShapping0(sb, cb, false, true, false, false, autoRead, minimalWaitBetween, multipleMessage);
290 }
291
292 @Test
293 @Timeout(value = 10000, unit = TimeUnit.MILLISECONDS)
294 public void testAutoReadGlobalTrafficShapping(TestInfo testInfo) throws Throwable {
295 currentTestName = "TEST AUTO READ GLOBAL";
296 currentTestRun = 0;
297 run(testInfo, new Runner<ServerBootstrap, Bootstrap>() {
298 @Override
299 public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable {
300 testAutoReadGlobalTrafficShapping(serverBootstrap, bootstrap);
301 }
302 });
303 }
304
305 public void testAutoReadGlobalTrafficShapping(ServerBootstrap sb, Bootstrap cb) throws Throwable {
306 int[] autoRead = { 1, -1, -1, 1, -2, 0, 1, 0, -3, 0, 1, 2, 0 };
307 int[] multipleMessage = new int[autoRead.length];
308 Arrays.fill(multipleMessage, 1);
309 long[] minimalWaitBetween = computeWaitAutoRead(autoRead);
310 testTrafficShapping0(sb, cb, false, true, false, true, autoRead, minimalWaitBetween, multipleMessage);
311 }
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331 private static void testTrafficShapping0(
332 ServerBootstrap sb, Bootstrap cb, final boolean additionalExecutor,
333 final boolean limitRead, final boolean limitWrite, final boolean globalLimit, int[] autoRead,
334 long[] minimalWaitBetween, int[] multipleMessage) throws Throwable {
335
336 currentTestRun++;
337 logger.info("TEST: " + currentTestName + " RUN: " + currentTestRun +
338 " Exec: " + additionalExecutor + " Read: " + limitRead + " Write: " + limitWrite + " Global: "
339 + globalLimit);
340 final ServerHandler sh = new ServerHandler(autoRead, multipleMessage);
341 Promise<Boolean> promise = group.next().newPromise();
342 final ClientHandler ch = new ClientHandler(promise, minimalWaitBetween, multipleMessage,
343 autoRead);
344
345 final AbstractTrafficShapingHandler handler;
346 if (limitRead) {
347 if (globalLimit) {
348 handler = new GlobalTrafficShapingHandler(groupForGlobal, 0, bandwidthFactor * messageSize, check);
349 } else {
350 handler = new ChannelTrafficShapingHandler(0, bandwidthFactor * messageSize, check);
351 }
352 } else if (limitWrite) {
353 if (globalLimit) {
354 handler = new GlobalTrafficShapingHandler(groupForGlobal, bandwidthFactor * messageSize, 0, check);
355 } else {
356 handler = new ChannelTrafficShapingHandler(bandwidthFactor * messageSize, 0, check);
357 }
358 } else {
359 handler = null;
360 }
361
362 sb.childHandler(new ChannelInitializer<SocketChannel>() {
363 @Override
364 protected void initChannel(SocketChannel c) throws Exception {
365 if (limitRead) {
366 c.pipeline().addLast(TRAFFIC, handler);
367 }
368 c.pipeline().addLast(sh);
369 }
370 });
371 cb.handler(new ChannelInitializer<SocketChannel>() {
372 @Override
373 protected void initChannel(SocketChannel c) throws Exception {
374 if (limitWrite) {
375 c.pipeline().addLast(TRAFFIC, handler);
376 }
377 c.pipeline().addLast(ch);
378 }
379 });
380
381 Channel sc = sb.bind().sync().channel();
382 Channel cc = cb.connect(sc.localAddress()).sync().channel();
383
384 int totalNb = 0;
385 for (int i = 1; i < multipleMessage.length; i++) {
386 totalNb += multipleMessage[i];
387 }
388 Long start = TrafficCounter.milliSecondFromNano();
389 int nb = multipleMessage[0];
390 for (int i = 0; i < nb; i++) {
391 cc.write(cc.alloc().buffer().writeBytes(data));
392 }
393 cc.flush();
394
395 promise.await();
396 Long stop = TrafficCounter.milliSecondFromNano();
397 assertTrue(promise.isSuccess(), "Error during execution of TrafficShapping: " + promise.cause());
398
399 float average = (totalNb * messageSize) / (float) (stop - start);
400 logger.info("TEST: " + currentTestName + " RUN: " + currentTestRun +
401 " Average of traffic: " + average + " compare to " + bandwidthFactor);
402 sh.channel.close().sync();
403 ch.channel.close().sync();
404 sc.close().sync();
405 if (autoRead != null) {
406
407 Thread.sleep(minimalms);
408 }
409
410 if (autoRead == null && minimalWaitBetween != null) {
411 assertTrue(average <= maxfactor,
412 "Overall Traffic not ok since > " + maxfactor + ": " + average);
413 if (additionalExecutor) {
414
415 assertTrue(average >= 0.25, "Overall Traffic not ok since < 0.25: " + average);
416 } else {
417 assertTrue(average >= minfactor,
418 "Overall Traffic not ok since < " + minfactor + ": " + average);
419 }
420 }
421 if (handler != null && globalLimit) {
422 ((GlobalTrafficShapingHandler) handler).release();
423 }
424
425 if (sh.exception.get() != null && !(sh.exception.get() instanceof IOException)) {
426 throw sh.exception.get();
427 }
428 if (ch.exception.get() != null && !(ch.exception.get() instanceof IOException)) {
429 throw ch.exception.get();
430 }
431 if (sh.exception.get() != null) {
432 throw sh.exception.get();
433 }
434 if (ch.exception.get() != null) {
435 throw ch.exception.get();
436 }
437 }
438
439 private static class ClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
440 volatile Channel channel;
441 final AtomicReference<Throwable> exception = new AtomicReference<Throwable>();
442 volatile int step;
443
444 private long currentLastTime = TrafficCounter.milliSecondFromNano();
445 private final long[] minimalWaitBetween;
446 private final int[] multipleMessage;
447 private final int[] autoRead;
448 final Promise<Boolean> promise;
449
450 ClientHandler(Promise<Boolean> promise, long[] minimalWaitBetween, int[] multipleMessage,
451 int[] autoRead) {
452 this.minimalWaitBetween = minimalWaitBetween;
453 this.multipleMessage = Arrays.copyOf(multipleMessage, multipleMessage.length);
454 this.promise = promise;
455 this.autoRead = autoRead;
456 }
457
458 @Override
459 public void channelActive(ChannelHandlerContext ctx) throws Exception {
460 channel = ctx.channel();
461 }
462
463 @Override
464 public void channelRead0(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
465 long lastTimestamp = 0;
466 loggerClient.debug("Step: " + step + " Read: " + in.readableBytes() / 8 + " blocks");
467 while (in.isReadable()) {
468 lastTimestamp = in.readLong();
469 multipleMessage[step]--;
470 }
471 if (multipleMessage[step] > 0) {
472
473 return;
474 }
475 long minimalWait = minimalWaitBetween != null? minimalWaitBetween[step] : 0;
476 int ar = 0;
477 if (autoRead != null) {
478 if (step > 0 && autoRead[step - 1] != 0) {
479 ar = autoRead[step - 1];
480 }
481 }
482 loggerClient.info("Step: " + step + " Interval: " + (lastTimestamp - currentLastTime) + " compareTo "
483 + minimalWait + " (" + ar + ')');
484 assertTrue(lastTimestamp - currentLastTime >= minimalWait,
485 "The interval of time is incorrect:" + (lastTimestamp - currentLastTime) + " not> " + minimalWait);
486 currentLastTime = lastTimestamp;
487 step++;
488 if (multipleMessage.length > step) {
489 int nb = multipleMessage[step];
490 for (int i = 0; i < nb; i++) {
491 channel.write(channel.alloc().buffer().writeBytes(data));
492 }
493 channel.flush();
494 } else {
495 promise.setSuccess(true);
496 }
497 }
498
499 @Override
500 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
501 if (exception.compareAndSet(null, cause)) {
502 cause.printStackTrace();
503 promise.setFailure(cause);
504 ctx.close();
505 }
506 }
507 }
508
509 private static class ServerHandler extends SimpleChannelInboundHandler<ByteBuf> {
510 private final int[] autoRead;
511 private final int[] multipleMessage;
512 volatile Channel channel;
513 volatile int step;
514 final AtomicReference<Throwable> exception = new AtomicReference<Throwable>();
515
516 ServerHandler(int[] autoRead, int[] multipleMessage) {
517 this.autoRead = autoRead;
518 this.multipleMessage = Arrays.copyOf(multipleMessage, multipleMessage.length);
519 }
520
521 @Override
522 public void channelActive(ChannelHandlerContext ctx) throws Exception {
523 channel = ctx.channel();
524 }
525
526 @Override
527 public void channelRead0(final ChannelHandlerContext ctx, ByteBuf in) throws Exception {
528 byte[] actual = new byte[in.readableBytes()];
529 int nb = actual.length / messageSize;
530 loggerServer.info("Step: " + step + " Read: " + nb + " blocks");
531 in.readBytes(actual);
532 long timestamp = TrafficCounter.milliSecondFromNano();
533 int isAutoRead = 0;
534 int laststep = step;
535 for (int i = 0; i < nb; i++) {
536 multipleMessage[step]--;
537 if (multipleMessage[step] == 0) {
538
539 if (autoRead != null) {
540 isAutoRead = autoRead[step];
541 }
542 step++;
543 }
544 }
545 if (laststep != step) {
546
547 if (autoRead != null && isAutoRead != 2) {
548 if (isAutoRead != 0) {
549 loggerServer.info("Step: " + step + " Set AutoRead: " + (isAutoRead > 0));
550 channel.config().setAutoRead(isAutoRead > 0);
551 } else {
552 loggerServer.info("Step: " + step + " AutoRead: NO");
553 }
554 }
555 }
556 Thread.sleep(10);
557 loggerServer.debug("Step: " + step + " Write: " + nb);
558 for (int i = 0; i < nb; i++) {
559 channel.write(Unpooled.copyLong(timestamp));
560 }
561 channel.flush();
562 if (laststep != step) {
563
564 if (isAutoRead != 0) {
565 if (isAutoRead < 0) {
566 final int exactStep = step;
567 long wait = isAutoRead == -1? minimalms : stepms + minimalms;
568 if (isAutoRead == -3) {
569 wait = stepms * 3;
570 }
571 executor.schedule(new Runnable() {
572 @Override
573 public void run() {
574 loggerServer.info("Step: " + exactStep + " Reset AutoRead");
575 channel.config().setAutoRead(true);
576 }
577 }, wait, TimeUnit.MILLISECONDS);
578 } else {
579 if (isAutoRead > 1) {
580 loggerServer.debug("Step: " + step + " Will Set AutoRead: True");
581 final int exactStep = step;
582 executor.schedule(new Runnable() {
583 @Override
584 public void run() {
585 loggerServer.info("Step: " + exactStep + " Set AutoRead: True");
586 channel.config().setAutoRead(true);
587 }
588 }, stepms + minimalms, TimeUnit.MILLISECONDS);
589 }
590 }
591 }
592 }
593 }
594
595 @Override
596 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
597 if (exception.compareAndSet(null, cause)) {
598 cause.printStackTrace();
599 ctx.close();
600 }
601 }
602 }
603 }