app/model/ELB.scala (70 lines of code) (raw):

package model import com.amazonaws.services.elasticloadbalancing.model.{DescribeInstanceHealthRequest, InstanceState} import lib.{AmazonConnection, AWS} import com.amazonaws.services.cloudwatch.model.Statistic._ import scala.jdk.CollectionConverters._ import scala.concurrent.{ExecutionContext, Future} import com.amazonaws.services.cloudwatch.model.{Statistic, Datapoint, Dimension, GetMetricStatisticsRequest} import org.joda.time.DateTime import play.api.libs.json.Json import scala.annotation.tailrec case class ELB(name: String, members: List[ELBMember], latency: Seq[Datapoint], errorCount: Seq[Datapoint], active: Boolean) case class ELBMember(id: String, state: String, description: Option[String], reasonCode: Option[String]) object ELB { import AWS.Writes._ implicit val elbMemberWrites = Json.writes[ELBMember] implicit val elbWrites = Json.writes[ELB] import ExecutionContext.Implicits.global val timeSpan: DateTime => DateTime = _.minusHours(3) def statistic(lbName: String)(metricName: String, statistics: Statistic*)(implicit conn: AmazonConnection) = AWS.futureOf(conn.cloudWatch.getMetricStatisticsAsync, new GetMetricStatisticsRequest() .withDimensions(new Dimension().withName("LoadBalancerName").withValue(lbName)) .withMetricName(metricName).withNamespace("AWS/ELB").withPeriod(60) .withStatistics(statistics: _*) .withStartTime(timeSpan(DateTime.now()).toDate).withEndTime(DateTime.now().toDate) ) def latency(lbName: String)(implicit conn: AmazonConnection) = statistic(lbName)("Latency", Average, Maximum) def requestCount(lbName: String)(implicit conn: AmazonConnection) = statistic(lbName)("RequestCount", Sum) def errorCount(lbName: String)(implicit conn: AmazonConnection) = statistic(lbName)("HTTPCode_Backend_5XX", Sum) def forName(lbName: String)(implicit conn: AmazonConnection): Future[ELB] = for { elbHealths <- AWS.futureOf(conn.elb.describeInstanceHealthAsync, new DescribeInstanceHealthRequest(lbName)) latency <- latency(lbName) requestCount <- requestCount(lbName) errorCount <- errorCount(lbName) } yield { val members = elbHealths.getInstanceStates.asScala.toList.map(i => ELBMember( i.getInstanceId, i.getState, Option(i.getDescription).filter(_ != "N/A"), Option(i.getReasonCode).filter(_ != "N/A") )) val active = requestCount.getDatapoints.asScala.nonEmpty && requestCount.getDatapoints.asScala.map(_.getSum).max > 10 val latencyInMs = latency.getDatapoints.asScala.toSeq.sortBy(_.getTimestamp).map(p => new Datapoint().withTimestamp(p.getTimestamp).withAverage(p.getAverage * 1000) ) val start = extremeTime(latencyInMs)(_.minBy(_.getTimestamp)) val end = extremeTime(latencyInMs)(_.maxBy(_.getTimestamp)) ELB(lbName, members, latencyInMs, zeroFillPerMinute(start, end)(errorCount.getDatapoints.asScala.toSeq), active) } def extremeTime(series: Seq[Datapoint])(f: Seq[Datapoint] => Datapoint): DateTime = if (series.isEmpty) DateTime.now().withSecondOfMinute(0).withMillisOfSecond(0) else new DateTime(f(series).getTimestamp) def perMinute(from: DateTime, to: DateTime): Seq[DateTime] = { @tailrec def perMin(acc: Seq[DateTime], from: DateTime): Seq[DateTime] = { if (from.isAfter(to)) acc else perMin(from +: acc, from.plusMinutes(1)) } perMin(Nil, from).reverse } private def zeroFillPerMinute(from: DateTime, to: DateTime)(line: Seq[Datapoint]): Seq[Datapoint] = { def zeroDatapoint(t: DateTime) = new Datapoint().withAverage(0).withMaximum(0).withMinimum(0) .withSampleCount(0).withSum(0).withTimestamp(t.toDate) val dataMap = line.map(p => new DateTime(p.getTimestamp) -> p).toMap for (t <- perMinute(from, to)) yield { dataMap.getOrElse(t, zeroDatapoint(t)) } } }