All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.twitter.finagle.netty4.channel.ChannelStatsHandler.scala Maven / Gradle / Ivy

The newest version!
package com.twitter.finagle.netty4.channel

import com.twitter.finagle.Failure
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.util.{Duration, Monitor, Stopwatch}
import io.netty.buffer.ByteBuf
import io.netty.channel.{ChannelDuplexHandler, ChannelHandlerContext, ChannelPromise}
import io.netty.channel.ChannelHandler.Sharable
import io.netty.util.AttributeKey
import java.io.IOException
import java.util.concurrent.atomic.AtomicLong
import java.util.logging.{Level, Logger}


private[channel] case class ChannelStats(bytesRead: AtomicLong, bytesWritten: AtomicLong)

private[netty4] object ChannelStatsHandler {
  private[channel] val ConnectionStatsKey = AttributeKey.valueOf[ChannelStats]("channel_stats")
  private[channel] val ConnectionDurationKey = AttributeKey.valueOf[Stopwatch.Elapsed]("connection_duration")
  private[channel] val ChannelWasWritableKey = AttributeKey.valueOf[Boolean]("channel_has_been_writable")
  private[channel] val ChannelWritableDurationKey = AttributeKey.valueOf[Stopwatch.Elapsed]("channel_writable_duration")
}

/**
 * A [[io.netty.channel.ChannelDuplexHandler]] that tracks channel/connection
 * statistics. The handler is meant to be shared by all
 * [[io.netty.channel.Channel Channels]] within a Finagle client or
 * server in order to consolidate statistics across a number of channels.
 */
@Sharable
private[netty4] class ChannelStatsHandler(statsReceiver: StatsReceiver)
  extends ChannelDuplexHandler {
  import ChannelStatsHandler._

  private[this] val log = Logger.getLogger(getClass.getName)
  private[this] val connectionCount: AtomicLong = new AtomicLong()

  private[this] val connects                = statsReceiver.counter("connects")
  private[this] val connectionDuration      = statsReceiver.stat("connection_duration")
  private[this] val connectionReceivedBytes = statsReceiver.stat("connection_received_bytes")
  private[this] val connectionSentBytes     = statsReceiver.stat("connection_sent_bytes")
  private[this] val receivedBytes           = statsReceiver.counter("received_bytes")
  private[this] val sentBytes               = statsReceiver.counter("sent_bytes")
  private[this] val closeChans              = statsReceiver.counter("closechans")
  private[this] val writable                = statsReceiver.counter("socket_writable_ms")
  private[this] val unwritable              = statsReceiver.counter("socket_unwritable_ms")
  private[this] val exceptions              = statsReceiver.scope("exn")
  private[this] val closesCount             = statsReceiver.counter("closes")
  private[this] val connections             = statsReceiver.addGauge("connections") {
    connectionCount.get()
  }



  override def channelActive(ctx: ChannelHandlerContext): Unit = {
    ctx.attr(ChannelWasWritableKey).set(true) //netty channels start in writable state
    ctx.attr(ChannelWritableDurationKey).set(Stopwatch.start())
    ctx.attr(ConnectionStatsKey).set(ChannelStats(new AtomicLong(0), new AtomicLong(0)))
    connects.incr()
    connectionCount.incrementAndGet()

    ctx.attr(ConnectionDurationKey).set(Stopwatch.start())
    super.channelActive(ctx)
  }

  override def write(ctx: ChannelHandlerContext, msg: Object, p: ChannelPromise) {
    val channelWriteCount = ctx.attr(ConnectionStatsKey).get.bytesWritten

    msg match {
      case buffer: ByteBuf =>
        val readableBytes = buffer.readableBytes
        channelWriteCount.getAndAdd(readableBytes)
        sentBytes.incr(readableBytes)
      case _ =>
        log.warning("ChannelStatsHandler received non-channelbuffer write")
    }

    super.write(ctx, msg, p)
  }

  override def channelRead(ctx: ChannelHandlerContext, msg: Object) {
    msg match {
      case buffer: ByteBuf =>
        val channelReadCount = ctx.attr(ConnectionStatsKey).get.bytesRead
        val readableBytes = buffer.readableBytes
        channelReadCount.getAndAdd(readableBytes)
        receivedBytes.incr(readableBytes)
      case _ =>
        log.warning("ChannelStatsHandler received non-channelbuffer read")
    }

    super.channelRead(ctx, msg)
  }

  override def close(ctx: ChannelHandlerContext, p: ChannelPromise) {
    closesCount.incr()
    super.close(ctx, p)
  }

  override def channelInactive(ctx: ChannelHandlerContext) {
    closeChans.incr()
    val channelStats = ctx.attr(ConnectionStatsKey).get

    connectionReceivedBytes.add(channelStats.bytesRead.get)
    connectionSentBytes.add(channelStats.bytesWritten.get)

    val elapsed = ctx.attr(ConnectionDurationKey).get()
    connectionDuration.add(elapsed().inMilliseconds)
    connectionCount.decrementAndGet()
    super.channelInactive(ctx)
  }

  override def exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) {
    exceptions.counter(cause.getClass.getName).incr()
    // If no Monitor is active, then log the exception so we don't fail silently.
    if (!Monitor.isActive) {
      val level = cause match {
        case t: IOException => Level.FINE
        case f: Failure => f.logLevel
        case _ => Level.WARNING
      }
      log.log(level, "ChannelStatsHandler caught an exception", cause)
    }
    super.exceptionCaught(ctx, cause)
  }


  override def channelWritabilityChanged(ctx: ChannelHandlerContext): Unit = {
    val isWritable = ctx.channel.isWritable()
    val wasWritableAttr = ctx.attr(ChannelWasWritableKey)
    if (isWritable != wasWritableAttr.get) {
      val writableDuration = ctx.attr(ChannelWritableDurationKey)
      val elapsed: Duration = writableDuration.get().apply()
      val stat = if (wasWritableAttr.get) writable else unwritable
      stat.incr(elapsed.inMilliseconds.toInt)

      wasWritableAttr.set(isWritable)
      writableDuration.set(Stopwatch.start())
    }
    super.channelWritabilityChanged(ctx)
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy