clarified signature and modules: more explicit Build_Log.History;
authorwenzelm
Sun, 19 Nov 2023 12:46:41 +0100
changeset 78991 ae2f5fd0bb5d
parent 78990 f728be354ffb
child 78992 bd250213c262
clarified signature and modules: more explicit Build_Log.History;
src/Pure/Admin/build_log.scala
src/Pure/Admin/isabelle_cronjob.scala
--- a/src/Pure/Admin/build_log.scala	Sat Nov 18 21:14:34 2023 +0100
+++ b/src/Pure/Admin/build_log.scala	Sun Nov 19 12:46:41 2023 +0100
@@ -1190,6 +1190,88 @@
   }
 
 
+
+  /** build history **/
+
+  object History {
+    sealed case class Entry(
+      known: Boolean,
+      isabelle_version: String,
+      afp_version: Option[String],
+      pull_date: Date
+    ) {
+      def unknown: Boolean = !known
+      def versions: (String, Option[String]) = (isabelle_version, afp_version)
+
+      def known_versions(rev: String, afp_rev: Option[String]): Boolean =
+        known && rev.nonEmpty && isabelle_version == rev &&
+        (afp_rev.isEmpty || afp_rev.get.nonEmpty && afp_version == afp_rev)
+    }
+
+    object Run {
+      val empty: Run = Run()
+      def longest(runs: List[Run]): Run = runs.foldLeft(empty)(_ max _)
+    }
+
+    sealed case class Run(entries: List[Entry] = Nil) {
+      def is_empty: Boolean = entries.isEmpty
+      val length: Int = entries.length
+      def max(other: Run): Run = if (length >= other.length) this else other
+      def median: Option[Entry] = if (is_empty) None else Some(entries(length / 2))
+
+      override def toString: String = {
+        val s = if (is_empty) "" else "length = " + length + ", median = " + median.get.pull_date
+        "Build_Log.History.Run(" + s + ")"
+      }
+    }
+
+    def retrieve(
+      db: SQL.Database,
+      days: Int,
+      rev: String,
+      afp_rev: Option[String],
+      sql: PostgreSQL.Source
+    ): History = {
+      val afp = afp_rev.isDefined
+
+      val entries =
+        db.execute_query_statement(
+          private_data.select_recent_versions(
+            days = days, rev = rev, afp_rev = afp_rev, sql = SQL.where(sql)),
+          List.from[Entry],
+          { res =>
+            val known = res.bool(private_data.known)
+            val isabelle_version = res.string(Prop.isabelle_version)
+            val afp_version = if (afp) proper_string(res.string(Prop.afp_version)) else None
+            val pull_date = res.date(private_data.pull_date(afp))
+            Entry(known, isabelle_version, afp_version, pull_date)
+          })
+
+      new History(entries)
+    }
+  }
+
+  final class History private(val entries: List[History.Entry]) {
+    override def toString: String = "Build_Log.History(" + entries.length + ")"
+
+    def unknown_runs(
+      pre: History.Entry => Boolean = _ => true,
+      post: History.Run => Boolean = _ => true
+    ): List[History.Run] = {
+      var rest = entries.filter(pre)
+      val result = new mutable.ListBuffer[History.Run]
+      while (rest.nonEmpty) {
+        val (a, b) = Library.take_prefix[History.Entry](_.unknown, rest.dropWhile(_.known))
+        val run = History.Run(a)
+        if (!run.is_empty && post(run)) result += run
+        rest = b
+      }
+      result.toList
+    }
+  }
+
+
+
   /** maintain build_log database **/
 
   def build_log_database(options: Options, logs: List[Path],
--- a/src/Pure/Admin/isabelle_cronjob.scala	Sat Nov 18 21:14:34 2023 +0100
+++ b/src/Pure/Admin/isabelle_cronjob.scala	Sun Nov 19 12:46:41 2023 +0100
@@ -118,53 +118,6 @@
 
   /* remote build_history */
 
-  sealed case class Item(
-    known: Boolean,
-    isabelle_version: String,
-    afp_version: Option[String],
-    pull_date: Date
-  ) {
-    def unknown: Boolean = !known
-    def versions: (String, Option[String]) = (isabelle_version, afp_version)
-
-    def known_versions(rev: String, afp_rev: Option[String]): Boolean =
-      known && rev.nonEmpty && isabelle_version == rev &&
-      (afp_rev.isEmpty || afp_rev.get.nonEmpty && afp_version == afp_rev)
-  }
-
-  def recent_items(
-    db: SQL.Database,
-    days: Int,
-    rev: String,
-    afp_rev: Option[String],
-    sql: PostgreSQL.Source
-  ): List[Item] = {
-    val afp = afp_rev.isDefined
-
-    db.execute_query_statement(
-      Build_Log.private_data.select_recent_versions(
-        days = days, rev = rev, afp_rev = afp_rev, sql = SQL.where(sql)),
-      List.from[Item],
-      { res =>
-        val known = res.bool(Build_Log.private_data.known)
-        val isabelle_version = res.string(Build_Log.Prop.isabelle_version)
-        val afp_version = if (afp) proper_string(res.string(Build_Log.Prop.afp_version)) else None
-        val pull_date = res.date(Build_Log.private_data.pull_date(afp))
-        Item(known, isabelle_version, afp_version, pull_date)
-      })
-  }
-
-  def unknown_runs(items: List[Item]): List[List[Item]] = {
-    var rest = items
-    val result = new mutable.ListBuffer[List[Item]]
-    while (rest.nonEmpty) {
-      val (run, r) = Library.take_prefix[Item](_.unknown, rest.dropWhile(_.known))
-      if (run.nonEmpty) result += run
-      rest = r
-    }
-    result.toList
-  }
-
   sealed case class Remote_Build(
     description: String,
     host: String,
@@ -195,29 +148,26 @@
     def profile: Build_Status.Profile =
       Build_Status.Profile(description, history = history, afp = afp, bulky = bulky, sql = sql)
 
-    def pick(options: Options, filter: Item => Boolean): Option[(String, Option[String])] = {
+    def pick(
+      options: Options,
+      filter: Build_Log.History.Entry => Boolean
+    ): Option[(String, Option[String])] = {
       val rev = get_rev()
       val afp_rev = if (afp) Some(get_afp_rev()) else None
 
       val store = Build_Log.store(options)
       using(store.open_database()) { db =>
-        def pick_recent(days: Int, gap: Int): Option[(String, Option[String])] = {
-          val items = recent_items(db, days, rev, afp_rev, sql).filter(filter)
-          val runs = unknown_runs(items).filter(run => run.length >= gap)
-          val (longest_run, longest_length) =
-            runs.foldLeft((List.empty[Item], 0)) {
-              case (res@(item1, len1), item2) =>
-                val len2 = item2.length
-                if (len1 >= len2) res else (item2, len2)
-            }
-          if (longest_run.isEmpty) None
-          else Some(longest_run(longest_length / 2).versions)
+        def get(days: Int, gap: Int): Option[(String, Option[String])] = {
+          val runs =
+            Build_Log.History.retrieve(db, days, rev, afp_rev, sql)
+              .unknown_runs(pre = filter, post = run => run.length >= gap)
+          Build_Log.History.Run.longest(runs).median.map(_.versions)
         }
 
-        pick_recent(options.int("build_log_history") max history, 2) orElse
-        pick_recent(300, 8) orElse
-        pick_recent(3000, 32) orElse
-        pick_recent(0, 1)
+        get(options.int("build_log_history") max history, 2) orElse
+        get(300, 8) orElse
+        get(3000, 32) orElse
+        get(0, 1)
       }
     }
 
@@ -615,10 +565,10 @@
     val hg = Mercurial.repository(isabelle_repos)
     val hg_graph = hg.graph()
 
-    def history_base_filter(r: Remote_Build): Item => Boolean = {
+    def history_base_filter(r: Remote_Build): Build_Log.History.Entry => Boolean = {
       val base_rev = hg.id(r.history_base)
       val nodes = hg_graph.all_succs(List(base_rev)).toSet
-      (item: Item) => nodes(item.isabelle_version)
+      (entry: Build_Log.History.Entry) => nodes(entry.isabelle_version)
     }