private void computeDeviationCumulativeBytes() { // compute the maximum cumulativeXxxxBytes among still connected Channels long maxWrittenBytes = 0; long maxReadBytes = 0; long minWrittenBytes = Long.MAX_VALUE; long minReadBytes = Long.MAX_VALUE; for (PerChannel perChannel : channelQueues.values()) { long value = perChannel.channelTrafficCounter.cumulativeWrittenBytes(); if (maxWrittenBytes < value) { maxWrittenBytes = value; } if (minWrittenBytes > value) { minWrittenBytes = value; } value = perChannel.channelTrafficCounter.cumulativeReadBytes(); if (maxReadBytes < value) { maxReadBytes = value; } if (minReadBytes > value) { minReadBytes = value; } } boolean multiple = channelQueues.size() > 1; readDeviationActive = multiple && minReadBytes < maxReadBytes / 2; writeDeviationActive = multiple && minWrittenBytes < maxWrittenBytes / 2; cumulativeWrittenBytes.set(maxWrittenBytes); cumulativeReadBytes.set(maxReadBytes); }
private PerChannel getOrSetPerChannel(ChannelHandlerContext ctx) { // ensure creation is limited to one thread per channel Channel channel = ctx.channel(); Integer key = channel.hashCode(); PerChannel perChannel = channelQueues.get(key); if (perChannel == null) { perChannel = new PerChannel(); perChannel.messagesQueue = new ArrayDeque<ToSend>(); // Don't start it since managed through the Global one perChannel.channelTrafficCounter = new TrafficCounter(this, null, "ChannelTC" + ctx.channel().hashCode(), checkInterval); perChannel.queueSize = 0L; perChannel.lastReadTimestamp = TrafficCounter.milliSecondFromNano(); perChannel.lastWriteTimestamp = perChannel.lastReadTimestamp; channelQueues.put(key, perChannel); } return perChannel; }
@Override public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { trafficCounter.resetCumulativeTime(); Channel channel = ctx.channel(); Integer key = channel.hashCode(); PerChannel perChannel = channelQueues.remove(key); if (perChannel != null) { if (channel.isActive()) { for (ToSend toSend : perChannel.messagesQueue) { long size = calculateSize(toSend.toSend); trafficCounter.bytesRealWriteFlowControl(size); perChannel.channelTrafficCounter.bytesRealWriteFlowControl(size); perChannel.queueSize -= size; queuesSize.addAndGet(-size); ctx.write(toSend.toSend, toSend.promise); queuesSize.addAndGet(-perChannel.queueSize); for (ToSend toSend : perChannel.messagesQueue) { if (toSend.toSend instanceof ByteBuf) { perChannel.messagesQueue.clear();
/** * <p>Note the change will be taken as best effort, meaning * that all already scheduled traffics will not be * changed, but only applied to new traffics.</p> * <p>So the expected usage of this method is to be used not too often, * accordingly to the traffic shaping configuration.</p> * * @param readLimit the readLimit to set */ public void setReadLimit(long readLimit) { this.readLimit = readLimit; if (trafficCounter != null) { trafficCounter.resetAccounting(TrafficCounter.milliSecondFromNano()); } }
/** * @param writeLimit Channel write limit */ public void setWriteChannelLimit(long writeLimit) { writeChannelLimit = writeLimit; long now = TrafficCounter.milliSecondFromNano(); for (PerChannel perChannel : channelQueues.values()) { perChannel.channelTrafficCounter.resetAccounting(now); } }
private void init(long checkInterval) { // absolute time: informative only lastCumulativeTime = System.currentTimeMillis(); writingTime = milliSecondFromNano(); readingTime = writingTime; lastWritingTime = writingTime; lastReadingTime = writingTime; configure(checkInterval); }
final long size, final long writedelay, final long now, final ChannelPromise promise) { Channel channel = ctx.channel(); Integer key = channel.hashCode(); PerChannel perChannel = channelQueues.get(key); if (perChannel == null) { if (writedelay == 0 && perChannel.messagesQueue.isEmpty()) { trafficCounter.bytesRealWriteFlowControl(size); perChannel.channelTrafficCounter.bytesRealWriteFlowControl(size); ctx.write(msg, promise); perChannel.lastWriteTimestamp = now; return; perChannel.messagesQueue.addLast(newToSend); perChannel.queueSize += size; queuesSize.addAndGet(size); checkWriteSuspend(ctx, delay, perChannel.queueSize); if (queuesSize.get() > maxGlobalWriteSize) { globalSizeExceeded = true; ctx.executor().schedule(new Runnable() { @Override public void run() {
@Override public void channelRead(final ChannelHandlerContext ctx, final Object msg) throws Exception { long size = calculateSize(msg); long now = TrafficCounter.milliSecondFromNano(); if (size > 0) { long waitGlobal = trafficCounter.readTimeToWait(size, getReadLimit(), maxTime, now); Integer key = ctx.channel().hashCode(); PerChannel perChannel = channelQueues.get(key); long wait = 0; if (perChannel != null) { wait = perChannel.channelTrafficCounter.readTimeToWait(size, readChannelLimit, maxTime, now); if (readDeviationActive) { maxLocalRead = perChannel.channelTrafficCounter.cumulativeReadBytes(); long maxGlobalRead = cumulativeReadBytes.get(); if (maxLocalRead <= 0) { maxLocalRead = 0; channel.attr(READ_SUSPENDED).set(true); Attribute<Runnable> attr = channel.attr(REOPEN_TASK); Runnable reopenTask = attr.get(); if (reopenTask == null) { attr.set(reopenTask);
throws Exception { long size = calculateSize(msg); long now = TrafficCounter.milliSecondFromNano(); if (size > 0) { long waitGlobal = trafficCounter.writeTimeToWait(size, getWriteLimit(), maxTime, now); Integer key = ctx.channel().hashCode(); PerChannel perChannel = channelQueues.get(key); long wait = 0; if (perChannel != null) { wait = perChannel.channelTrafficCounter.writeTimeToWait(size, writeChannelLimit, maxTime, now); if (writeDeviationActive) { maxLocalWrite = perChannel.channelTrafficCounter.cumulativeWrittenBytes(); long maxGlobalWrite = cumulativeWrittenBytes.get(); if (maxLocalWrite <= 0) { maxLocalWrite = 0; if (logger.isDebugEnabled()) { logger.debug("Write suspend: " + wait + ':' + ctx.channel().config().isAutoRead() + ':' + isHandlerActive(ctx));
bytesWriteFlowControl(size); if (size == 0 || limitTraffic == 0) { return 0; final long lastTimeCheck = lastTime.get(); long sum = currentWrittenBytes.get(); long lastWB = lastWrittenBytes; long localWritingTime = writingTime; if (logger.isDebugEnabled()) { logger.debug("Time: " + time + ':' + sum + ':' + interval + ':' + pastDelay); long lastinterval = interval + checkInterval.get(); long time = lastsum * 1000 / limitTraffic - lastinterval + pastDelay; if (time > AbstractTrafficShapingHandler.MINIMAL_WAIT) { if (logger.isDebugEnabled()) { logger.debug("Time: " + time + ':' + lastsum + ':' + lastinterval + ':' + pastDelay);
bytesRecvFlowControl(size); if (size == 0 || limitTraffic == 0) { return 0; final long lastTimeCheck = lastTime.get(); long sum = currentReadBytes.get(); long localReadingTime = readingTime; long lastRB = lastReadBytes; if (logger.isDebugEnabled()) { logger.debug("Time: " + time + ':' + sum + ':' + interval + ':' + pastDelay); long lastinterval = interval + checkInterval.get(); long time = lastsum * 1000 / limitTraffic - lastinterval + pastDelay; if (time > AbstractTrafficShapingHandler.MINIMAL_WAIT) { if (logger.isDebugEnabled()) { logger.debug("Time: " + time + ':' + lastsum + ':' + lastinterval + ':' + pastDelay);
@Override public void run() { if (!counter.monitorActive) { return; } long newLastTime = milliSecondFromNano(); counter.resetAccounting(newLastTime); for (PerChannel perChannel : trafficShapingHandler1.channelQueues.values()) { perChannel.channelTrafficCounter.resetAccounting(newLastTime); } trafficShapingHandler1.doAccounting(counter); counter.scheduledFuture = counter.executor.schedule(this, counter.checkInterval.get(), TimeUnit.MILLISECONDS); } }
/** * Start the monitoring process. */ public synchronized void start() { if (monitorActive) { return; } lastTime.set(milliSecondFromNano()); long localCheckInterval = checkInterval.get(); // if executor is null, it means it is piloted by a GlobalChannelTrafficCounter, so no executor if (localCheckInterval > 0 && executor != null) { monitorActive = true; monitor = new TrafficMonitoringTask(); scheduledFuture = executor.schedule(monitor, localCheckInterval, TimeUnit.MILLISECONDS); } }
private void sendAllValid(final ChannelHandlerContext ctx, final PerChannel perChannel, final long now) { // write operations need synchronization synchronized (perChannel) { ToSend newToSend = perChannel.messagesQueue.pollFirst(); for (; newToSend != null; newToSend = perChannel.messagesQueue.pollFirst()) { if (newToSend.relativeTimeAction <= now) { long size = newToSend.size; trafficCounter.bytesRealWriteFlowControl(size); perChannel.channelTrafficCounter.bytesRealWriteFlowControl(size); perChannel.queueSize -= size; queuesSize.addAndGet(-size); ctx.write(newToSend.toSend, newToSend.promise); perChannel.lastWriteTimestamp = now; } else { perChannel.messagesQueue.addFirst(newToSend); break; } } if (perChannel.messagesQueue.isEmpty()) { releaseWriteSuspended(ctx); } } ctx.flush(); }
@Override public void run() { if (!monitorActive) { return; } resetAccounting(milliSecondFromNano()); if (trafficShapingHandler != null) { trafficShapingHandler.doAccounting(TrafficCounter.this); } scheduledFuture = executor.schedule(this, checkInterval.get(), TimeUnit.MILLISECONDS); } }
/** * Change checkInterval between two computations in millisecond. * * @param newCheckInterval The new check interval (in milliseconds) */ public void configure(long newCheckInterval) { long newInterval = newCheckInterval / 10 * 10; if (checkInterval.getAndSet(newInterval) != newInterval) { if (newInterval <= 0) { stop(); // No more active monitoring lastTime.set(milliSecondFromNano()); } else { // Start if necessary start(); } } }
@Override public void channelRead(final ChannelHandlerContext ctx, final Object msg) throws Exception { long size = calculateSize(msg); long now = TrafficCounter.milliSecondFromNano(); if (size > 0) { long wait = trafficCounter.readTimeToWait(size, readLimit, maxTime, now); wait = checkWaitReadTime(ctx, wait, now); if (wait >= MINIMAL_WAIT) { // At least 10ms seems a minimal channel.attr(READ_SUSPENDED).set(true); Attribute<Runnable> attr = channel.attr(REOPEN_TASK); Runnable reopenTask = attr.get(); if (reopenTask == null) { attr.set(reopenTask); ctx.executor().schedule(reopenTask, wait, TimeUnit.MILLISECONDS); if (logger.isDebugEnabled()) { logger.debug("Suspend final status => " + config.isAutoRead() + ':' + isHandlerActive(ctx) + " will reopened at: " + wait); ctx.fireChannelRead(msg);
@Override public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { trafficCounter.stop(); // write order control synchronized (this) { if (ctx.channel().isActive()) { for (ToSend toSend : messagesQueue) { long size = calculateSize(toSend.toSend); trafficCounter.bytesRealWriteFlowControl(size); queueSize -= size; ctx.write(toSend.toSend, toSend.promise); } } else { for (ToSend toSend : messagesQueue) { if (toSend.toSend instanceof ByteBuf) { ((ByteBuf) toSend.toSend).release(); } } } messagesQueue.clear(); } releaseWriteSuspended(ctx); releaseReadSuspended(ctx); super.handlerRemoved(ctx); }
@Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { TrafficCounter trafficCounter = new TrafficCounter(this, ctx.executor(), "ChannelTC" + ctx.channel().hashCode(), checkInterval); setTrafficCounter(trafficCounter); trafficCounter.start(); super.handlerAdded(ctx); }
@Override void submitWrite(final ChannelHandlerContext ctx, final Object msg, final long size, final long delay, final long now, final ChannelPromise promise) { final ToSend newToSend; // write order control synchronized (this) { if (delay == 0 && messagesQueue.isEmpty()) { trafficCounter.bytesRealWriteFlowControl(size); ctx.write(msg, promise); return; } newToSend = new ToSend(delay + now, msg, promise); messagesQueue.addLast(newToSend); queueSize += size; checkWriteSuspend(ctx, delay, queueSize); } final long futureNow = newToSend.relativeTimeAction; ctx.executor().schedule(new Runnable() { @Override public void run() { sendAllValid(ctx, futureNow); } }, delay, TimeUnit.MILLISECONDS); }