diff --git a/doc/api.md b/doc/api.md index 7883811..69c1e4b 100644 --- a/doc/api.md +++ b/doc/api.md @@ -31,12 +31,14 @@ $ curl --silent -G http://0.0.0.0:8080/summary | jq . "job_id": 0, "name": "Job1", "missing": 4, + "oldest_mising_period": 10, "alert_level": "warn" }, { "job_id": 1, "name": "Job2", "missing": 2, + "oldest_mising_period": 3, "alert_level": "normal" } ] @@ -49,12 +51,14 @@ $ curl --silent -G http://0.0.0.0:8080/summary | jq . "job_id": 0, "name": "Job3", "missing": 6, + "oldest_mising_period": 6, "alert_level": "critical" }, { "job_id": 1, "name": "Job4", "missing": 0, + "oldest_mising_period": 0, "alert_level": "great" } ] diff --git a/src/main/scala/checker/CommandRunner.scala b/src/main/scala/checker/CommandRunner.scala index 973421b..f99516e 100644 --- a/src/main/scala/checker/CommandRunner.scala +++ b/src/main/scala/checker/CommandRunner.scala @@ -58,7 +58,7 @@ class CommandRunner(statsActor: ActorRef) extends Actor with ActorLogging { period => PeriodHealth(period, mapped(period)) } context.sender ! RunResult(periodHealths, group, job, clockCounter) statsActor ! MissingPeriods(prometheusId, periodHealths.count(!_.ok)) - val oldestMissingPeriod = CommandRunner.computeOldest(periodHealths) + val oldestMissingPeriod = computeOldest(periodHealths) statsActor ! OldestMissingPeriod(prometheusId, oldestMissingPeriod) } } @@ -66,11 +66,6 @@ class CommandRunner(statsActor: ActorRef) extends Actor with ActorLogging { object CommandRunner { private[this] val Matcher = "^greenish-period\t(.*)\t(1|0)$".r - protected[checker] def computeOldest(periodHealths: Seq[PeriodHealth]): Int = { - val missingIndex = periodHealths.indexWhere(!_.ok) - if(missingIndex == -1) 0 - else periodHealths.length - missingIndex - } protected[checker] def parseOutput(lines: LazyList[String], periods: Set[String]): Seq[(String, Boolean)] = diff --git a/src/main/scala/checker/StatusChecker.scala b/src/main/scala/checker/StatusChecker.scala index 48c9be7..ef85af9 100644 --- a/src/main/scala/checker/StatusChecker.scala +++ b/src/main/scala/checker/StatusChecker.scala @@ -47,7 +47,9 @@ trait StatusCheckerApi { else if(missing <= status.job.alertLevels.normal) Normal else if(missing <= status.job.alertLevels.warn) Warn else Critical - JobStatusSummary(status.job.jobId, status.job.name, missing, alertLevel) + + val oldestMissingPeriod = computeOldest(status.periodHealth) + JobStatusSummary(status.job.jobId, status.job.name, missing, oldestMissingPeriod, alertLevel) }.toSeq GroupStatusSummary(group.group.groupId, group.group.name, status) } diff --git a/src/main/scala/checker/checker.scala b/src/main/scala/checker/checker.scala new file mode 100644 index 0000000..265666e --- /dev/null +++ b/src/main/scala/checker/checker.scala @@ -0,0 +1,11 @@ +package me.amanj.greenish + +import me.amanj.greenish.models.PeriodHealth + +package object checker { + protected[checker] def computeOldest(periodHealths: Seq[PeriodHealth]): Int = { + val missingIndex = periodHealths.indexWhere(!_.ok) + if(missingIndex == -1) 0 + else periodHealths.length - missingIndex + } +} diff --git a/src/main/scala/models/JobStatusSummary.scala b/src/main/scala/models/JobStatusSummary.scala index f11c290..b8bb9a4 100644 --- a/src/main/scala/models/JobStatusSummary.scala +++ b/src/main/scala/models/JobStatusSummary.scala @@ -7,6 +7,7 @@ case class JobStatusSummary( jobId: Int, name: String, missing: Int, + oldestMissingPeriod: Int, alertLevel: AlertLevel, ) object JobStatusSummary { diff --git a/src/test/scala/checker/CheckerSpec.scala b/src/test/scala/checker/CheckerSpec.scala new file mode 100644 index 0000000..c1efe84 --- /dev/null +++ b/src/test/scala/checker/CheckerSpec.scala @@ -0,0 +1,80 @@ +package me.amanj.greenish.checker + +import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach} +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpecLike +import me.amanj.greenish.models.PeriodHealth + +class CheckerSpec() extends AnyWordSpecLike with Matchers { + + "computeOldest" must { + "work for empty period health lists" in { + val periods = Seq.empty[PeriodHealth] + val actual = computeOldest(periods) + val expected = 0 + actual shouldBe expected + } + + "work when the first period is missing" in { + val periods = Seq( + PeriodHealth("kaka", false), + PeriodHealth("kaka", true), + PeriodHealth("kaka", true), + PeriodHealth("kaka", true), + ) + val actual = computeOldest(periods) + val expected = 4 + actual shouldBe expected + } + + "work when a middle period is missing" in { + val periods = Seq( + PeriodHealth("kaka", true), + PeriodHealth("kaka", false), + PeriodHealth("kaka", true), + PeriodHealth("kaka", true), + ) + val actual = computeOldest(periods) + val expected = 3 + actual shouldBe expected + } + + "work when the last period is missing" in { + val periods = Seq( + PeriodHealth("kaka", true), + PeriodHealth("kaka", true), + PeriodHealth("kaka", true), + PeriodHealth("kaka", false), + ) + val actual = computeOldest(periods) + val expected = 1 + actual shouldBe expected + } + + "work when more than a period is missing" in { + val periods = Seq( + PeriodHealth("kaka", true), + PeriodHealth("kaka", false), + PeriodHealth("kaka", true), + PeriodHealth("kaka", false), + ) + val actual = computeOldest(periods) + val expected = 3 + actual shouldBe expected + } + + "work when no period is missing" in { + val periods = Seq( + PeriodHealth("kaka", true), + PeriodHealth("kaka", true), + PeriodHealth("kaka", true), + PeriodHealth("kaka", true), + ) + val actual = computeOldest(periods) + val expected = 0 + actual shouldBe expected + } + } +} + + diff --git a/src/test/scala/checker/CommandRunnerSpec.scala b/src/test/scala/checker/CommandRunnerSpec.scala index 4177584..ebdd53e 100644 --- a/src/test/scala/checker/CommandRunnerSpec.scala +++ b/src/test/scala/checker/CommandRunnerSpec.scala @@ -52,76 +52,6 @@ class CommandRunnerSpec() Props(new StatsCollector(Set("p1", "p2", "p3")))) } - "computeOldest" must { - import CommandRunner.computeOldest - "work for empty period health lists" in { - val periods = Seq.empty[PeriodHealth] - val actual = computeOldest(periods) - val expected = 0 - actual shouldBe expected - } - - "work when the first period is missing" in { - val periods = Seq( - PeriodHealth("kaka", false), - PeriodHealth("kaka", true), - PeriodHealth("kaka", true), - PeriodHealth("kaka", true), - ) - val actual = computeOldest(periods) - val expected = 4 - actual shouldBe expected - } - - "work when a middle period is missing" in { - val periods = Seq( - PeriodHealth("kaka", true), - PeriodHealth("kaka", false), - PeriodHealth("kaka", true), - PeriodHealth("kaka", true), - ) - val actual = computeOldest(periods) - val expected = 3 - actual shouldBe expected - } - - "work when the last period is missing" in { - val periods = Seq( - PeriodHealth("kaka", true), - PeriodHealth("kaka", true), - PeriodHealth("kaka", true), - PeriodHealth("kaka", false), - ) - val actual = computeOldest(periods) - val expected = 1 - actual shouldBe expected - } - - "work when more than a period is missing" in { - val periods = Seq( - PeriodHealth("kaka", true), - PeriodHealth("kaka", false), - PeriodHealth("kaka", true), - PeriodHealth("kaka", false), - ) - val actual = computeOldest(periods) - val expected = 3 - actual shouldBe expected - } - - "work when no period is missing" in { - val periods = Seq( - PeriodHealth("kaka", true), - PeriodHealth("kaka", true), - PeriodHealth("kaka", true), - PeriodHealth("kaka", true), - ) - val actual = computeOldest(periods) - val expected = 0 - actual shouldBe expected - } - } - "parseOutput" must { "parse output lines correctly" in { val lines = LazyList( diff --git a/src/test/scala/checker/StatusCheckerSpec.scala b/src/test/scala/checker/StatusCheckerSpec.scala index 3bc972a..a1d7ddd 100644 --- a/src/test/scala/checker/StatusCheckerSpec.scala +++ b/src/test/scala/checker/StatusCheckerSpec.scala @@ -83,28 +83,32 @@ class StatusCheckerSpec() override protected[this] var state = IndexedSeq.empty[GroupStatus] } + val singletonState = IndexedSeq(GroupStatus( + group1, Array(JobStatus(job1, tstamp, Seq(PeriodHealth("1", false)))) + )) + val singletonChecker = new StatusCheckerApi { - override protected[this] var state = IndexedSeq(GroupStatus( - group1, Array(JobStatus(job1, tstamp, Seq(PeriodHealth("1", false)))) - )) + override protected[this] var state = singletonState } - val nestedChecker = new StatusCheckerApi { - override protected[this] var state = IndexedSeq( - GroupStatus( - group1, Array( - JobStatus(job1, tstamp, Seq(PeriodHealth("1", true), PeriodHealth("1", true))), - JobStatus(job1, tstamp, Seq(PeriodHealth("2", false), PeriodHealth("3", false))), - ) - ), - GroupStatus( - group1, Array( - JobStatus(job1, tstamp, Seq(PeriodHealth("1", false), PeriodHealth("1", true))), - JobStatus(job1, tstamp, Seq(PeriodHealth("2", false), - PeriodHealth("3", false), PeriodHealth("4", false))), - ) + val deeplyNestedState = IndexedSeq( + GroupStatus( + group1, Array( + JobStatus(job1, tstamp, Seq(PeriodHealth("1", true), PeriodHealth("1", true))), + JobStatus(job1, tstamp, Seq(PeriodHealth("2", false), PeriodHealth("3", false))), + ) + ), + GroupStatus( + group1, Array( + JobStatus(job1, tstamp, Seq(PeriodHealth("1", false), PeriodHealth("1", true))), + JobStatus(job1, tstamp, Seq(PeriodHealth("2", false), + PeriodHealth("3", false), PeriodHealth("4", false))), ) ) + ) + + val nestedChecker = new StatusCheckerApi { + override protected[this] var state = deeplyNestedState } "maxLag" must { @@ -129,11 +133,11 @@ class StatusCheckerSpec() } "work when state is not empty" in { - singletonChecker.allEntries.length shouldBe 1 + singletonChecker.allEntries shouldBe singletonState } "work when state is deeply nested" in { - nestedChecker.allEntries.length shouldBe 2 + nestedChecker.allEntries shouldBe deeplyNestedState } } @@ -179,7 +183,7 @@ class StatusCheckerSpec() "work when state is not empty" in { val expected = Seq( - GroupStatusSummary(0, "group1", Seq(JobStatusSummary(0, "job1", 1, Normal))) + GroupStatusSummary(0, "group1", Seq(JobStatusSummary(0, "job1", 1, 1, Normal))) ) singletonChecker.summary shouldBe expected } @@ -188,14 +192,14 @@ class StatusCheckerSpec() val expected = Seq( GroupStatusSummary( 0, "group1", Seq( - JobStatusSummary(0, "job1", 0, Great), - JobStatusSummary(0, "job1", 2, Warn), + JobStatusSummary(0, "job1", 0, 0, Great), + JobStatusSummary(0, "job1", 2, 2, Warn), ) ), GroupStatusSummary( 0, "group1", Seq( - JobStatusSummary(0, "job1", 1, Normal), - JobStatusSummary(0, "job1", 3, Critical), + JobStatusSummary(0, "job1", 1, 2, Normal), + JobStatusSummary(0, "job1", 3, 3, Critical), ) ) ) @@ -298,7 +302,7 @@ class StatusCheckerSpec() actual shouldBe expected } - "various cron styles" in { + "work various cron styles" in { val job = cron => Job(1, null, null, null, "yyyy-MM-dd-HH-mm", cron, 1, ZoneId.of("UTC"), 2, null, Seq.empty diff --git a/src/test/scala/endpoints/RoutesSpec.scala b/src/test/scala/endpoints/RoutesSpec.scala index 41458d2..eda3d2d 100644 --- a/src/test/scala/endpoints/RoutesSpec.scala +++ b/src/test/scala/endpoints/RoutesSpec.scala @@ -215,11 +215,11 @@ class RoutesSpec() .flatMap(_.as[Seq[GroupStatusSummary]]).getOrElse(null) val expected = Seq( GroupStatusSummary(0, group1.name, Seq( - JobStatusSummary(0, job1.name, 1, Normal), - JobStatusSummary(1, job2.name, 1, Normal), + JobStatusSummary(0, job1.name, 1, 2, Normal), + JobStatusSummary(1, job2.name, 1, 2, Normal), )), GroupStatusSummary(1, group2.name, Seq( - JobStatusSummary(0, job3.name, 1, Normal), + JobStatusSummary(0, job3.name, 1, 2, Normal), )), ) actual shouldBe expected diff --git a/src/test/scala/models/JsonSerdeSpec.scala b/src/test/scala/models/JsonSerdeSpec.scala index 0fc0edb..8ee1905 100644 --- a/src/test/scala/models/JsonSerdeSpec.scala +++ b/src/test/scala/models/JsonSerdeSpec.scala @@ -163,7 +163,7 @@ class JsonSerdeSpec() extends Matchers } "GroupStatusSummary" must { - val jobStatus = Seq(JobStatusSummary(0, "j", 1, Critical)) + val jobStatus = Seq(JobStatusSummary(0, "j", 1, 1, Critical)) val groupStatusSummary = GroupStatusSummary(2, "g", jobStatus) "produce correct JSON" in { @@ -248,12 +248,13 @@ class JsonSerdeSpec() extends Matchers } "JobStatusSummary" must { - val jobStatusSummary = JobStatusSummary(0, "j", 1, Critical) + val jobStatusSummary = JobStatusSummary(0, "j", 1, 2, Critical) "produce correct JSON" in { val expected = Json.obj( "job_id" -> 0.asJson, "name" -> "j".asJson, "missing" -> 1.asJson, + "oldest_missing_period" -> 2.asJson, "alert_level" -> "critical".asJson, )