package org.red5.server.stream;
/*
* RED5 Open Source Flash Server - http://www.osflash.org/red5
*
* Copyright (c) 2006-2009 by respective authors (see below). All rights reserved.
*
* This library is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 2.1 of the License, or (at your option) any later
* version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with this library; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
import net.sourceforge.groboutils.junit.v1.MultiThreadedTestRunner;
import net.sourceforge.groboutils.junit.v1.TestRunnable;
import org.red5.server.api.IBWControllable;
import org.red5.server.api.IBandwidthConfigure;
import org.red5.server.api.stream.support.SimpleBandwidthConfigure;
import org.red5.server.stream.ITokenBucket.ITokenBucketCallback;
import org.springframework.test.AbstractDependencyInjectionSpringContextTests;
public class BandwidthControlServiceTest extends
AbstractDependencyInjectionSpringContextTests {
class Client extends TestRunnable {
private boolean[][] availArray;
private int clientNum;
private IBandwidthConfigure config;
private Node parent;
private long timeout;
private long tokenCount;
Client(int clientNum, Node parent, IBandwidthConfigure config,
long tokenCount, boolean[][] availArray, long timeout) {
this.clientNum = clientNum;
this.parent = parent;
this.config = config;
this.tokenCount = tokenCount;
this.availArray = availArray;
for (int channel = 0; channel < 3; channel++) {
for (int i = 0; i < availArray[channel].length; i++) {
availArray[channel][i] = false;
}
}
this.timeout = timeout;
}
@Override
public void runTest() throws Throwable {
long before = System.currentTimeMillis();
Node node = new Node();
node.setParentBWControllable(parent);
node.setBandwidthConfigure(config);
IBWControlService service = (IBWControlService) applicationContext
.getBean(IBWControlService.KEY);
IBWControlContext context = service.registerBWControllable(node);
ITokenBucket[] buckets = new ITokenBucket[3];
buckets[0] = service.getAudioBucket(context);
buckets[1] = service.getVideoBucket(context);
buckets[2] = service.getDataBucket(context);
Loop: for (int channel = 0; channel < 3; channel++) {
for (int i = 0; i < availArray[channel].length; i++) {
ITokenBucketCallback callback = new ClientCallback(
clientNum, channel, i, availArray);
boolean result = buckets[channel].acquireTokenNonblocking(
tokenCount, callback);
if (result)
availArray[channel][i] = true;
while (!result && !availArray[channel][i]) {
if (timeout >= 0
&& System.currentTimeMillis() - before > timeout) {
break Loop;
}
Thread.sleep(10);
}
}
}
service.unregisterBWControllable(context);
}
}
class ClientCallback implements ITokenBucketCallback {
boolean[][] availArray;
int channel;
int clientNum;
int requestNum;
ClientCallback(int clientNum, int channel, int requestNum,
boolean[][] availArray) {
this.clientNum = clientNum;
this.channel = channel;
this.requestNum = requestNum;
this.availArray = availArray;
}
public void available(ITokenBucket bucket, long tokenCount) {
availArray[channel][requestNum] = true;
}
public void reset(ITokenBucket bucket, long tokenCount) {
System.err.println("Client " + clientNum + " on Channel " + channel
+ " reset on " + requestNum + "th request");
}
}
class Node implements IBWControllable {
IBandwidthConfigure config;
IBWControllable parent;
public IBandwidthConfigure getBandwidthConfigure() {
return config;
}
public IBWControllable getParentBWControllable() {
return parent;
}
public void setBandwidthConfigure(IBandwidthConfigure config) {
this.config = config;
}
public void setParentBWControllable(IBWControllable parent) {
this.parent = parent;
}
}
class TokenBucketCallback implements ITokenBucketCallback {
public void available(ITokenBucket bucket, long tokenCount) {
isAvailable = true;
}
public void reset(ITokenBucket bucket, long tokenCount) {
isReset = true;
}
}
private boolean isAvailable;
private boolean isReset;
@Override
protected String[] getConfigLocations() {
return new String[] { "org/red5/server/stream/BandwidthControlServiceTest.xml" };
}
public void testCreation() throws Throwable
{
// Do nothing
return;
}
/*
* NOTE: These tests are Disabled by default because they depend so much on timing
* of different systems that they don't run reliably on different OS systems. Feel
* free to re-enable for personal testing, but please rework them to correctly
* wait and signal 100% of the time before adding back to main system.
*/
public void disable_testMultiChildrenNoParent() throws Throwable {
final int CLIENT_COUNT = 1000;
final int REQUEST_COUNT = 100;
boolean[][][] avail = new boolean[CLIENT_COUNT][3][REQUEST_COUNT];
TestRunnable[] runnables = new TestRunnable[CLIENT_COUNT];
for (int i = 0; i < CLIENT_COUNT; i++) {
IBandwidthConfigure c = new SimpleBandwidthConfigure();
long[] b = c.getChannelBandwidth();
b[3] = 100 * 1024 * 1024;
long[] initial = c.getChannelInitialBurst();
initial[3] = 0;
runnables[i] = new Client(i, null, c, 1024 * 64, avail[i], 1700);
}
MultiThreadedTestRunner mttr = new MultiThreadedTestRunner(runnables);
mttr.runTestRunnables();
Thread.sleep(2000);
// check all avails
for (int i = 0; i < CLIENT_COUNT; i++) {
for (int channel = 0; channel < 3; channel++) {
for (int j = 0; j < REQUEST_COUNT; j++) {
assertTrue("Client No." + i + " on Channel " + channel
+ " fails on " + j + "th request",
avail[i][channel][j]);
}
}
}
}
public void disable_testMultiChildrenWithParent() throws Throwable {
final int CLIENT_COUNT = 50;
final int REQUEST_COUNT = 100;
Node parent = new Node();
parent.setParentBWControllable(null);
IBandwidthConfigure config = new SimpleBandwidthConfigure();
long[] channelBandwidth = config.getChannelBandwidth();
channelBandwidth[3] = 100 * 1024 * 1024 * CLIENT_COUNT;
long[] initialBurst = config.getChannelInitialBurst();
initialBurst[3] = 100 * 1024 * 1024;
parent.setBandwidthConfigure(config);
IBWControlService service = (IBWControlService) applicationContext
.getBean(IBWControlService.KEY);
IBWControlContext context = service.registerBWControllable(parent);
boolean[][][] avail = new boolean[CLIENT_COUNT][3][REQUEST_COUNT];
TestRunnable[] runnables = new TestRunnable[CLIENT_COUNT];
for (int i = 0; i < CLIENT_COUNT; i++) {
IBandwidthConfigure c = new SimpleBandwidthConfigure();
long[] b = c.getChannelBandwidth();
b[3] = 100 * 1024 * 1024;
long[] initial = c.getChannelInitialBurst();
initial[3] = 0;
runnables[i] = new Client(i, parent, c, 1024 * 64, avail[i], 1700);
}
MultiThreadedTestRunner mttr = new MultiThreadedTestRunner(runnables);
mttr.runTestRunnables();
Thread.sleep(2000);
// check all avails
for (int i = 0; i < CLIENT_COUNT; i++) {
for (int channel = 0; channel < 3; channel++) {
for (int j = 0; j < REQUEST_COUNT; j++) {
assertTrue("Client No." + i + " on Channel " + channel
+ " fails on " + j + "th request",
avail[i][channel][j]);
}
}
}
service.unregisterBWControllable(context);
}
public void disable_testSingleNodeNoParent() throws Exception {
IBWControlService bwController = (IBWControlService) applicationContext
.getBean(IBWControlService.KEY);
ITokenBucketCallback callback = new TokenBucketCallback();
Node node = new Node();
node.setParentBWControllable(null);
node.setBandwidthConfigure(null);
IBWControlContext context = bwController.registerBWControllable(node);
ITokenBucket audioBucket = bwController.getAudioBucket(context);
ITokenBucket videoBucket = bwController.getVideoBucket(context);
ITokenBucket dataBucket = bwController.getDataBucket(context);
// no bandwidth configure, all should succeed
assertTrue(audioBucket.acquireToken(0, 0));
assertTrue(audioBucket.acquireToken(1024, 0));
assertTrue(audioBucket.acquireToken(1024 * 1024, 0));
assertTrue(videoBucket.acquireToken(0, 0));
assertTrue(videoBucket.acquireToken(1024, 0));
assertTrue(videoBucket.acquireToken(1024 * 1024, 0));
assertTrue(dataBucket.acquireToken(0, 0));
assertTrue(dataBucket.acquireToken(1024, 0));
assertTrue(dataBucket.acquireToken(1024 * 1024, 0));
// use overall bandwidth
SimpleBandwidthConfigure config = new SimpleBandwidthConfigure();
long[] channelBandwidth = config.getChannelBandwidth();
long[] channelInitialBurst = config.getChannelInitialBurst();
channelBandwidth[3] = 1024 * 1024; // 1Mbps overall bandwidth
channelInitialBurst[3] = 1024 * 1024;
node.setBandwidthConfigure(config);
bwController.updateBWConfigure(context);
// acquire the initial 1024 * 1024 tokens, should succeed
assertTrue(audioBucket.acquireToken(1024 * 1024, 0));
// acquire 1024 * 1024 tokens w/o wait, should fail
assertFalse(audioBucket.acquireToken(1024 * 1024, 0));
assertFalse(videoBucket.acquireToken(1024 * 1024, 0));
assertFalse(dataBucket.acquireToken(1024 * 1024, 0));
// wait 1.1s for 128KB, should succeed
assertTrue(audioBucket.acquireToken(1024 * 1024 / 8, 1100));
// wait 0.5s for 128KB, should fail
assertFalse(videoBucket.acquireToken(1024 * 1024 / 8, 500));
// wait another 0.6s for 128KB, should succeed
assertTrue(dataBucket.acquireToken(1024 * 1024 / 8, 600));
isAvailable = false;
isReset = false;
// expect false result and wait for callback
assertFalse(audioBucket.acquireTokenNonblocking(1024 * 1024 / 8,
callback));
Thread.sleep(500);
assertFalse(isAvailable);
assertFalse(isReset);
Thread.sleep(600);
assertTrue(isAvailable);
assertFalse(isReset);
// wait 500 then best-effort acquire 128KB
// the value should be larger than 0 and less than 128KB
audioBucket.acquireTokenBestEffort(1024 * 1024);
Thread.sleep(500);
long acquired = audioBucket.acquireTokenBestEffort(1024 * 1024 / 8);
assertTrue("Acquired " + acquired, acquired > 0
&& acquired < 1024 * 1024 / 8);
isAvailable = false;
isReset = false;
// expect false result and wait for callback
assertFalse(videoBucket.acquireTokenNonblocking(1024 * 1024 / 8,
callback));
bwController.resetBuckets(context);
Thread.sleep(500);
assertFalse(isAvailable);
assertTrue(isReset);
Thread.sleep(600);
assertFalse(isAvailable);
assertTrue(isReset);
isAvailable = false;
isReset = false;
isAvailable = false;
isReset = false;
// expect false result and wait for callback
assertFalse(dataBucket.acquireTokenNonblocking(1024 * 1024, callback));
bwController.unregisterBWControllable(context);
assertFalse(isAvailable);
assertTrue(isReset);
channelBandwidth[3] = -1;
channelBandwidth[0] = 1024 * 1024; // 1Mbps
channelBandwidth[1] = 1024 * 512; // 512Kbps
channelBandwidth[2] = 1024 * 256; // 256Kbps
channelInitialBurst[0] = channelInitialBurst[1] = channelInitialBurst[2] = 0;
context = bwController.registerBWControllable(node);
audioBucket = bwController.getAudioBucket(context);
videoBucket = bwController.getVideoBucket(context);
dataBucket = bwController.getDataBucket(context);
assertFalse(audioBucket.acquireToken(1024 * 1024 / 8, 500));
assertTrue(audioBucket.acquireToken(1024 * 1024 / 8, 600));
Thread.sleep(500);
acquired = videoBucket.acquireTokenBestEffort(1024 * 1024 / 8);
assertTrue("Acquired " + acquired, acquired > 0
&& acquired < 1024 * 1024 / 8);
Thread.sleep(500);
isAvailable = false;
isReset = false;
// expect false result and wait for callback
assertFalse(dataBucket.acquireTokenNonblocking(1024 * 1024 / 8,
callback));
Thread.sleep(1000);
assertFalse(isAvailable);
assertFalse(isReset);
Thread.sleep(1100);
assertTrue(isAvailable);
assertFalse(isReset);
bwController.unregisterBWControllable(context);
assertTrue(isAvailable);
assertFalse(isReset);
}
public void disable_testSingleNodeWithParent() throws Exception {
IBWControlService bwController = (IBWControlService) applicationContext
.getBean(IBWControlService.KEY);
ITokenBucketCallback callback = new TokenBucketCallback();
Node parent = new Node();
parent.setParentBWControllable(null);
parent.setBandwidthConfigure(null);
IBWControlContext parentContext = bwController
.registerBWControllable(parent);
Node node = new Node();
node.setParentBWControllable(parent);
node.setBandwidthConfigure(null);
IBWControlContext context = bwController.registerBWControllable(node);
ITokenBucket audioBucket = bwController.getAudioBucket(context);
ITokenBucket videoBucket = bwController.getVideoBucket(context);
ITokenBucket dataBucket = bwController.getDataBucket(context);
// no bandwidth configure, all should succeed
assertTrue(audioBucket.acquireToken(0, 0));
assertTrue(audioBucket.acquireToken(1024, 0));
assertTrue(audioBucket.acquireToken(1024 * 1024, 0));
assertTrue(videoBucket.acquireToken(0, 0));
assertTrue(videoBucket.acquireToken(1024, 0));
assertTrue(videoBucket.acquireToken(1024 * 1024, 0));
assertTrue(dataBucket.acquireToken(0, 0));
assertTrue(dataBucket.acquireToken(1024, 0));
assertTrue(dataBucket.acquireToken(1024 * 1024, 0));
// use overall bandwidth
SimpleBandwidthConfigure config = new SimpleBandwidthConfigure();
long[] channelBandwidth = config.getChannelBandwidth();
long[] channelInitialBurst = config.getChannelInitialBurst();
channelBandwidth[3] = 1024 * 1024; // 1Mbps overall bandwidth
channelInitialBurst[3] = 1024 * 1024;
parent.setBandwidthConfigure(config);
bwController.updateBWConfigure(parentContext);
// acquire the initial 1024 * 1024 tokens, should succeed
assertTrue(audioBucket.acquireToken(1024 * 1024, 0));
// acquire 1024 * 1024 tokens w/o wait, should fail
assertFalse(audioBucket.acquireToken(1024 * 1024, 0));
assertFalse(videoBucket.acquireToken(1024 * 1024, 0));
assertFalse(dataBucket.acquireToken(1024 * 1024, 0));
// wait 1.1s for 128KB, should succeed
assertTrue(audioBucket.acquireToken(1024 * 1024 / 8, 1100));
// wait 0.5s for 128KB, should fail
assertFalse(videoBucket.acquireToken(1024 * 1024 / 8, 500));
// wait another 0.6s for 128KB, should succeed
assertTrue(dataBucket.acquireToken(1024 * 1024 / 8, 600));
isAvailable = false;
isReset = false;
// expect false result and wait for callback
assertFalse(audioBucket.acquireTokenNonblocking(1024 * 1024 / 8,
callback));
Thread.sleep(500);
assertFalse(isAvailable);
assertFalse(isReset);
Thread.sleep(600);
assertTrue(isAvailable);
assertFalse(isReset);
// wait 500 then best-effort acquire 128KB
// the value should be larger than 0 and less than 128KB
audioBucket.acquireTokenBestEffort(1024 * 1024);
Thread.sleep(500);
long acquired = audioBucket.acquireTokenBestEffort(1024 * 1024 / 8);
assertTrue("Acquired " + acquired, acquired > 0
&& acquired < 1024 * 1024 / 8);
isAvailable = false;
isReset = false;
// expect false result and wait for callback
assertFalse(videoBucket.acquireTokenNonblocking(1024 * 1024 / 8,
callback));
bwController.resetBuckets(context);
Thread.sleep(500);
assertFalse(isAvailable);
assertTrue(isReset);
Thread.sleep(600);
assertFalse(isAvailable);
assertTrue(isReset);
isAvailable = false;
isReset = false;
isAvailable = false;
isReset = false;
// expect false result and wait for callback
assertFalse(dataBucket.acquireTokenNonblocking(1024 * 1024, callback));
bwController.unregisterBWControllable(context);
assertFalse(isAvailable);
assertTrue(isReset);
channelBandwidth[3] = -1;
channelBandwidth[0] = 1024 * 1024; // 1Mbps
channelBandwidth[1] = 1024 * 512; // 512Kbps
channelBandwidth[2] = 1024 * 256; // 256Kbps
channelInitialBurst[0] = channelInitialBurst[1] = channelInitialBurst[2] = 0;
bwController.updateBWConfigure(parentContext);
context = bwController.registerBWControllable(node);
audioBucket = bwController.getAudioBucket(context);
videoBucket = bwController.getVideoBucket(context);
dataBucket = bwController.getDataBucket(context);
assertFalse(audioBucket.acquireToken(1024 * 1024 / 8, 500));
assertTrue(audioBucket.acquireToken(1024 * 1024 / 8, 600));
Thread.sleep(500);
acquired = videoBucket.acquireTokenBestEffort(1024 * 1024 / 8);
assertTrue("Acquired " + acquired, acquired > 0
&& acquired < 1024 * 1024 / 8);
Thread.sleep(500);
isAvailable = false;
isReset = false;
// expect false result and wait for callback
assertFalse(dataBucket.acquireTokenNonblocking(1024 * 1024 / 8,
callback));
Thread.sleep(1000);
assertFalse(isAvailable);
assertFalse(isReset);
Thread.sleep(1100);
assertTrue(isAvailable);
assertFalse(isReset);
bwController.unregisterBWControllable(context);
assertTrue(isAvailable);
assertFalse(isReset);
bwController.unregisterBWControllable(parentContext);
}
}