app/metrics/CloudWatch.scala (140 lines of code) (raw):
package metrics
import aws.AwsAsyncHandler.awsToScala
import com.amazonaws.auth.profile.ProfileCredentialsProvider
import com.amazonaws.auth.{
AWSCredentialsProviderChain,
InstanceProfileCredentialsProvider
}
import com.amazonaws.regions.{Region, Regions}
import com.amazonaws.services.cloudwatch.AmazonCloudWatchAsyncClientBuilder
import com.amazonaws.services.cloudwatch.model._
import config.AmiableConfigProvider
import models.Attempt
import org.joda.time.{DateTime, DateTimeZone}
import play.api.Logging
import play.api.mvc.Handler.Stage
import services.OldInstanceAccountHistory
import scala.collection.JavaConverters._
import scala.concurrent.{ExecutionContext, Future}
sealed abstract class CloudWatchMetric(val name: String)
object CloudWatchMetrics {
case object OldCount
extends CloudWatchMetric("instances-running-out-of-date-amis")
case object AmisAgePercentile25th
extends CloudWatchMetric("instances-amis-age-percentile-25th")
case object AmisAgePercentile50th
extends CloudWatchMetric("instances-amis-age-percentile-50th")
case object AmisAgePercentile75th
extends CloudWatchMetric("instances-amis-age-percentile-75th")
case object AmisAgePercentile90th
extends CloudWatchMetric("instances-amis-age-percentile-90th")
case object AmisAgePercentileHighest
extends CloudWatchMetric("instances-amis-age-percentile-highest")
case object OldCountByAccount extends CloudWatchMetric("Vulnerabilities")
}
class CloudWatch() extends Logging {
lazy val client = {
val credentialsProvider = new AWSCredentialsProviderChain(
InstanceProfileCredentialsProvider.getInstance(),
new ProfileCredentialsProvider("deployTools")
)
val region = Option(Regions.getCurrentRegion).getOrElse(
Region.getRegion(Regions.EU_WEST_1)
)
val acwac = AmazonCloudWatchAsyncClientBuilder
.standard()
.withCredentials(credentialsProvider)
.withRegion(region.getName)
.build()
acwac
}
private[metrics] def putRequest(
namespace: String,
metricName: String,
value: Int,
dimensions: List[Dimension] = List.empty
): PutMetricDataRequest = {
new PutMetricDataRequest()
.withNamespace(namespace)
.withMetricData {
new MetricDatum()
.withMetricName(metricName)
.withValue(value.toDouble)
.withDimensions(dimensions.asJava)
}
}
private def getRequest(
namespace: String,
metricName: String
): GetMetricStatisticsRequest = {
val now = DateTime.now(DateTimeZone.UTC)
new GetMetricStatisticsRequest()
.withNamespace(namespace)
.withMetricName(metricName)
.withPeriod(60 * 60 * 24) // 1 day (24 hrs)
.withStartTime(now.minusDays(90).toDate)
.withEndTime(now.toDate)
.withStatistics(Statistic.Maximum)
}
private[metrics] def extractDataFromResult(
result: GetMetricStatisticsResult
): List[(DateTime, Double)] = {
result.getDatapoints.asScala.toList
.map { dp =>
new DateTime(dp.getTimestamp) -> dp.getMaximum.toDouble
}
.sortBy(_._1.getMillis)
}
private def getWithRequest(request: GetMetricStatisticsRequest)(implicit
executionContext: ExecutionContext
): Future[List[(DateTime, Double)]] = {
awsToScala(client.getMetricStatisticsAsync)(request)
.map(extractDataFromResult)
}
private def putWithRequest(request: PutMetricDataRequest) = {
awsToScala(client.putMetricDataAsync)(request)
}
def get(namespace: String, metricName: String)(implicit
executionContext: ExecutionContext
): Attempt[Option[List[(DateTime, Double)]]] = {
Attempt.fromFuture(
getWithRequest(getRequest(namespace, metricName)).map(ds =>
Right(Option(ds))
)
) { case e =>
logger.warn("Failed to fetch CloudWatch data", e)
Right(None)
}
}
def put(
namespace: String,
metricName: String,
maybeValue: Option[Int]
): Unit = {
maybeValue.fold {
logger.warn(
s"Not updating CloudWatch - no value available for '$metricName'"
)
} { value =>
putWithRequest(putRequest(namespace, metricName, value))
logger.debug(
s"Updated CloudWatch metric '$metricName' with value '$value'"
)
}
}
def put(
namespace: String,
metricName: String,
oldInstanceAccountHistory: List[OldInstanceAccountHistory]
): Unit = {
oldInstanceAccountHistory.foreach { oldAMICount =>
val dimensions = List(
new Dimension().withName("Account").withValue(oldAMICount.accountName),
new Dimension().withName("DataType").withValue("oldami/total")
)
putWithRequest(
putRequest(namespace, metricName, oldAMICount.count, dimensions)
)
}
}
}