misc tuning and clarification;
authorwenzelm
Thu, 06 Oct 2016 11:13:12 +0200
changeset 64062 a7352cbde7d7
parent 64061 1bbea2b55d22
child 64063 2c5039363ea3
misc tuning and clarification;
src/Pure/Tools/build.scala
src/Pure/Tools/build_log.scala
src/Pure/Tools/build_stats.scala
src/Pure/Tools/ci_api.scala
--- a/src/Pure/Tools/build.scala	Wed Oct 05 22:09:53 2016 +0200
+++ b/src/Pure/Tools/build.scala	Thu Oct 06 11:13:12 2016 +0200
@@ -477,7 +477,7 @@
       }
 
       try {
-        val info = Build_Log.parse_session_info(name, split_lines(text), false)
+        val info = Build_Log.Log_File(name, text).parse_session_info(name, false)
         val session_timing = Markup.Elapsed.unapply(info.session_timing) getOrElse 0.0
         (info.command_timings, session_timing)
       }
--- a/src/Pure/Tools/build_log.scala	Wed Oct 05 22:09:53 2016 +0200
+++ b/src/Pure/Tools/build_log.scala	Thu Oct 06 11:13:12 2016 +0200
@@ -16,18 +16,73 @@
 
 object Build_Log
 {
-  /* inlined properties (YXML) */
+  /** log file **/
 
-  object Props
+  object Log_File
+  {
+    def apply(name: String, lines: List[String]): Log_File =
+      new Log_File(name, lines)
+
+    def apply(name: String, text: String): Log_File =
+      Log_File(name, split_lines(Library.trim_line(text)).map(Library.trim_line(_)))
+  }
+
+  class Log_File private(val name: String, val lines: List[String])
   {
-    def print(props: Properties.T): String = YXML.string_of_body(XML.Encode.properties(props))
-    def parse(text: String): Properties.T = XML.Decode.properties(YXML.parse_body(text))
+    log_file =>
+
+    override def toString: String = name
+
+    def text: String = cat_lines(lines)
+
+    def err(msg: String): Nothing =
+      error("Error in log file " + quote(name) + ": " + msg)
+
+
+    /* inlined content */
+
+    def find[A](f: String => Option[A]): Option[A] =
+      lines.iterator.map(f).find(_.isDefined).map(_.get)
+
+    def find_match(regex: Regex): Option[String] =
+      lines.iterator.map(regex.unapplySeq(_)).find(res => res.isDefined && res.get.length == 1).
+        map(res => res.get.head)
+
+
+    /* settings */
+
+    def get_setting(setting: String): String =
+      lines.find(_.startsWith(setting + "=")) getOrElse err("missing " + setting)
 
-    def parse_lines(prefix: String, lines: List[String]): List[Properties.T] =
-      for (line <- lines; s <- Library.try_unprefix(prefix, line)) yield parse(s)
+    def get_settings(settings: List[String]): List[String] =
+      settings.map(get_setting(_))
+
+
+    /* properties (YXML) */
+
+    val xml_cache = new XML.Cache()
+
+    def parse_props(text: String): Properties.T =
+      xml_cache.props(XML.Decode.properties(YXML.parse_body(text)))
+
+    def filter_props(prefix: String): List[Properties.T] =
+      for (line <- lines; s <- Library.try_unprefix(prefix, line)) yield parse_props(s)
 
-    def find_parse_line(prefix: String, lines: List[String]): Option[Properties.T] =
-      lines.find(_.startsWith(prefix)).map(line => parse(line.substring(prefix.length)))
+    def find_line(prefix: String): Option[String] =
+      find(Library.try_unprefix(prefix, _))
+
+    def find_props(prefix: String): Option[Properties.T] =
+      find_line(prefix).map(parse_props(_))
+
+
+    /* parse various formats */
+
+    def parse_session_info(session_name: String, full: Boolean): Session_Info =
+      Build_Log.parse_session_info(log_file, session_name, full)
+
+    def parse_header: Header = Build_Log.parse_header(log_file)
+
+    def parse_info: Info = Build_Log.parse_info(log_file)
   }
 
 
@@ -40,31 +95,26 @@
     ml_statistics: List[Properties.T],
     task_statistics: List[Properties.T])
 
-  val SESSION_NAME = "\fSession.name = "
-
-  def parse_session_info(name0: String, lines: List[String], full: Boolean): Session_Info =
+  private def parse_session_info(log_file: Log_File, name0: String, full: Boolean): Session_Info =
   {
     val xml_cache = new XML.Cache()
-    def parse_lines(prfx: String): List[Properties.T] =
-      Props.parse_lines(prfx, lines).map(xml_cache.props(_))
 
     val session_name =
-      lines.find(_.startsWith(SESSION_NAME)).map(_.substring(SESSION_NAME.length)) match {
+      log_file.find_line("\fSession.name = ") match {
         case None => name0
         case Some(name) if name0 == "" || name0 == name => name
-        case Some(name) =>
-          error("Session log for " + quote(name0) + " is actually from " + quote(name))
+        case Some(name) => log_file.err("log from different session " + quote(name))
       }
-    val session_timing = Props.find_parse_line("\fTiming = ", lines) getOrElse Nil
-    val command_timings = parse_lines("\fcommand_timing = ")
-    val ml_statistics = if (full) parse_lines("\fML_statistics = ") else Nil
-    val task_statistics = if (full) parse_lines("\ftask_statistics = ") else Nil
+    val session_timing = log_file.find_props("\fTiming = ") getOrElse Nil
+    val command_timings = log_file.filter_props("\fcommand_timing = ")
+    val ml_statistics = if (full) log_file.filter_props("\fML_statistics = ") else Nil
+    val task_statistics = if (full) log_file.filter_props("\ftask_statistics = ") else Nil
 
     Session_Info(session_name, session_timing, command_timings, ml_statistics, task_statistics)
   }
 
 
-  /* header and data fields */
+  /* header and meta data */
 
   object Header_Kind extends Enumeration
   {
@@ -84,44 +134,47 @@
     val afp_version = "afp_version"
   }
 
+  val ml_settings = List("ML_PLATFORM", "ML_HOME", "ML_SYSTEM", "ML_OPTIONS")
+
   object AFP
   {
     val Date_Format =
       Date.Format.make_patterns(List("EEE MMM d HH:mm:ss VV yyyy", "EEE MMM d HH:mm:ss O yyyy"),
         // workaround for jdk-8u102
         s => Word.implode(Word.explode(s).map({ case "CEST" => "GMT+2" case a => a })))
+
     val Test_Start = new Regex("""^Start test for .+ at (.+), (\w+)$""")
     val Test_End = new Regex("""^End test on (.+), \w+, elapsed time:.*$""")
     val Isabelle_Version = new Regex("""^Isabelle version: .* -- hg id (\w+)$""")
     val AFP_Version = new Regex("""^AFP version: .* -- hg id (\w+)$""")
-    val settings =
-      List("ISABELLE_BUILD_OPTIONS=", "ML_PLATFORM=", "ML_HOME=", "ML_SYSTEM=", "ML_OPTIONS=")
+    val settings = "ISABELLE_BUILD_OPTIONS" :: ml_settings
   }
 
-  def parse_header(lines: List[String]): Header =
+  private def parse_header(log_file: Log_File): Header =
   {
-    val proper_lines = lines.filterNot(line => line.forall(Character.isWhitespace(_)))
-
-    def err(msg: String): Nothing = error(cat_lines((msg + ":") :: lines.take(10)))
+    log_file.lines match {
+      case AFP.Test_Start(start, hostname) :: _ =>
+        (start, log_file.lines.last) match {
+          case (AFP.Date_Format(start_date), AFP.Test_End(AFP.Date_Format(end_date))) =>
+            val isabelle_version =
+              log_file.find_match(AFP.Isabelle_Version) getOrElse
+                log_file.err("missing Isabelle version")
+            val afp_version =
+              log_file.find_match(AFP.AFP_Version) getOrElse
+                log_file.err("missing AFP version")
 
-    proper_lines match {
-      case AFP.Test_Start(start, hostname) :: _ =>
-        (start, proper_lines.last) match {
-          case (AFP.Date_Format(start_date), AFP.Test_End(AFP.Date_Format(end_date))) =>
-            val props =
+            Header(Header_Kind.AFP_TEST,
               List(
                 Field.build_host -> hostname,
                 Field.build_start -> start_date.toString,
-                Field.build_end -> end_date.toString) :::
-              lines.collectFirst(
-                { case AFP.Isabelle_Version(id) => Field.isabelle_version -> id }).toList :::
-              lines.collectFirst(
-                { case AFP.AFP_Version(id) => Field.afp_version -> id }).toList
-            val settings = lines.filter(line => AFP.settings.exists(line.startsWith(_)))
-            Header(Header_Kind.AFP_TEST, props, settings)
-          case _ => err("Malformed start/end date in afp-test log")
+                Field.build_end -> end_date.toString,
+                Field.isabelle_version -> isabelle_version,
+                Field.afp_version -> afp_version),
+              log_file.get_settings(AFP.settings))
+
+          case _ => log_file.err("cannot detect start/end date in afp-test log")
         }
-      case _ => err("Failed to detect build log header")
+      case _ => log_file.err("cannot detect log header format")
     }
   }
 
@@ -169,14 +222,14 @@
       }
   }
 
-  def parse_info(text: String): Info =
+  private def parse_info(log_file: Log_File): Info =
   {
     val ml_options = new mutable.ListBuffer[(String, String)]
     var finished = Map.empty[String, Timing]
     var timing = Map.empty[String, Timing]
     var threads = Map.empty[String, Int]
 
-    for (line <- split_lines(text)) {
+    for (line <- log_file.lines) {
       line match {
         case Session_Finished1(name,
             Value.Int(e1), Value.Int(e2), Value.Int(e3),
--- a/src/Pure/Tools/build_stats.scala	Wed Oct 05 22:09:53 2016 +0200
+++ b/src/Pure/Tools/build_stats.scala	Thu Oct 06 11:13:12 2016 +0200
@@ -29,7 +29,7 @@
 
     val all_infos =
       Par_List.map((job_info: CI_API.Job_Info) =>
-        (job_info.timestamp / 1000, Build_Log.parse_info(Url.read(job_info.output))), job_infos)
+        (job_info.timestamp / 1000, job_info.read_output.parse_info), job_infos)
     val all_sessions =
       (Set.empty[String] /: all_infos)(
         { case (s, (_, info)) => s ++ info.sessions })
--- a/src/Pure/Tools/ci_api.scala	Wed Oct 05 22:09:53 2016 +0200
+++ b/src/Pure/Tools/ci_api.scala	Thu Oct 06 11:13:12 2016 +0200
@@ -44,12 +44,12 @@
     output: URL,
     session_logs: List[(String, URL)])
   {
-    def read_output(): String = Url.read(output)
+    def read_output(): Build_Log.Log_File = Build_Log.Log_File(job_name, Url.read(output))
     def read_log(name: String, full: Boolean): Build_Log.Session_Info =
     {
       val text =
         session_logs.collectFirst({ case (a, b) if a == name => Url.read_gzip(b) }) getOrElse ""
-      Build_Log.parse_session_info(name, split_lines(text), full)
+      Build_Log.Log_File(name, text).parse_session_info(name, full)
     }
   }