store errors in build_history logs and database;
authorwenzelm
Fri, 26 May 2017 20:52:01 +0200
changeset 65937 fde7b5d209d5
parent 65936 aece72468de5
child 65938 1b297ce1e8aa
store errors in build_history logs and database;
src/Pure/Admin/build_history.scala
src/Pure/Admin/build_log.scala
src/Pure/PIDE/markup.scala
src/Pure/Thy/sessions.scala
--- a/src/Pure/Admin/build_history.scala	Fri May 26 19:39:02 2017 +0200
+++ b/src/Pure/Admin/build_history.scala	Fri May 26 20:52:01 2017 +0200
@@ -250,6 +250,22 @@
             properties.map(props => (Build_Log.SESSION_NAME -> session_name) :: props)
           })
 
+      build_out_progress.echo("Reading error messages ...")
+      val session_errors =
+        build_info.failed_sessions.flatMap(session_name =>
+          {
+            val database = isabelle_output + store.database(session_name)
+            val errors =
+              if (database.is_file) {
+                try {
+                  using(SQLite.open_database(database))(db => store.read_errors(db, session_name))
+                } // column "errors" could be missing
+                catch { case _: java.sql.SQLException => Nil }
+              }
+              else Nil
+            errors.map(msg => List(Build_Log.SESSION_NAME -> session_name, Markup.CONTENT -> msg))
+          })
+
       build_out_progress.echo("Reading heap sizes ...")
       val heap_sizes =
         build_info.finished_sessions.flatMap(session_name =>
@@ -265,6 +281,7 @@
         terminate_lines(
           Build_Log.Log_File.print_props(META_INFO_MARKER, meta_info) :: build_result.out_lines :::
           ml_statistics.map(Build_Log.Log_File.print_props(Build_Log.ML_STATISTICS_MARKER, _)) :::
+          session_errors.map(Build_Log.Log_File.print_props(Build_Log.ERROR_MESSAGE_MARKER, _)) :::
           heap_sizes), XZ.options(6))
 
 
--- a/src/Pure/Admin/build_log.scala	Fri May 26 19:39:02 2017 +0200
+++ b/src/Pure/Admin/build_log.scala	Fri May 26 20:52:01 2017 +0200
@@ -444,6 +444,7 @@
   /** build info: toplevel output of isabelle build or Admin/build_history **/
 
   val ML_STATISTICS_MARKER = "\fML_statistics = "
+  val ERROR_MESSAGE_MARKER = "\ferror_message = "
   val SESSION_NAME = "session_name"
 
   object Session_Status extends Enumeration
@@ -464,16 +465,18 @@
     ml_timing: Timing = Timing.zero,
     heap_size: Option[Long] = None,
     status: Option[Session_Status.Value] = None,
+    errors: List[String] = Nil,
     ml_statistics: List[Properties.T] = Nil)
   {
     def proper_groups: Option[String] = if (groups.isEmpty) None else Some(cat_lines(groups))
     def finished: Boolean = status == Some(Session_Status.finished)
+    def failed: Boolean = status == Some(Session_Status.failed)
   }
 
   sealed case class Build_Info(sessions: Map[String, Session_Entry])
   {
-    def finished_sessions: List[String] =
-      for ((name, entry) <- sessions.toList if entry.finished) yield name
+    def finished_sessions: List[String] = for ((a, b) <- sessions.toList if b.finished) yield a
+    def failed_sessions: List[String] = for ((a, b) <- sessions.toList if b.failed) yield a
   }
 
   private def parse_build_info(log_file: Log_File, parse_ml_statistics: Boolean): Build_Info =
@@ -510,6 +513,7 @@
     var cancelled = Set.empty[String]
     var heap_sizes = Map.empty[String, Long]
     var ml_statistics = Map.empty[String, List[Properties.T]]
+    var errors = Map.empty[String, List[String]]
 
     def all_sessions: Set[String] =
       chapter.keySet ++ groups.keySet ++ threads.keySet ++ timing.keySet ++ ml_timing.keySet ++
@@ -552,15 +556,22 @@
         case Heap(name, Value.Long(size)) =>
           heap_sizes += (name -> size)
 
-        case _
-        if parse_ml_statistics && line.startsWith(ML_STATISTICS_MARKER) && YXML.detect(line) =>
+        case _ if parse_ml_statistics && line.startsWith(ML_STATISTICS_MARKER) && YXML.detect(line) =>
           val (name, props) =
             Library.try_unprefix(ML_STATISTICS_MARKER, line).map(log_file.parse_props(_)) match {
-              case Some((SESSION_NAME, session_name) :: props) => (session_name, props)
+              case Some((SESSION_NAME, name) :: props) => (name, props)
               case _ => log_file.err("malformed ML_statistics " + quote(line))
             }
           ml_statistics += (name -> (props :: ml_statistics.getOrElse(name, Nil)))
 
+        case _ if line.startsWith(ERROR_MESSAGE_MARKER) && YXML.detect(line) =>
+          val (name, msg) =
+            Library.try_unprefix(ERROR_MESSAGE_MARKER, line).map(log_file.parse_props(_)) match {
+              case Some(List((SESSION_NAME, name), (Markup.CONTENT, msg))) => (name, msg)
+              case _ => log_file.err("malformed error message " + quote(line))
+            }
+          errors += (name -> (Library.decode_lines(msg) :: errors.getOrElse(name, Nil)))
+
         case _ =>
       }
     }
@@ -584,6 +595,7 @@
               ml_timing = ml_timing.getOrElse(name, Timing.zero),
               heap_size = heap_sizes.get(name),
               status = Some(status),
+              errors = errors.getOrElse(name, Nil).reverse,
               ml_statistics = ml_statistics.getOrElse(name, Nil).reverse)
           (name -> entry)
         }):_*)
@@ -612,9 +624,17 @@
       command_timings = if (command_timings) log_file.filter_props("\fcommand_timing = ") else Nil,
       ml_statistics = if (ml_statistics) log_file.filter_props(ML_STATISTICS_MARKER) else Nil,
       task_statistics = if (task_statistics) log_file.filter_props("\ftask_statistics = ") else Nil,
-      errors = log_file.filter_lines("\ferror_message = ").map(Library.decode_lines(_)))
+      errors = log_file.filter_lines(ERROR_MESSAGE_MARKER).map(Library.decode_lines(_)))
   }
 
+  def compress_errors(errors: List[String]): Option[Bytes] =
+    if (errors.isEmpty) None
+    else Some(Bytes(YXML.string_of_body(XML.Encode.list(XML.Encode.string)(errors))).compress())
+
+  def uncompress_errors(bytes: Bytes): List[String] =
+    if (bytes.isEmpty) Nil
+    else XML.Decode.list(YXML.string_of_body(_))(YXML.parse_body(bytes.uncompress().text))
+
 
 
   /** persistent store **/
@@ -644,6 +664,7 @@
     val ml_timing_factor = SQL.Column.double("ml_timing_factor")
     val heap_size = SQL.Column.long("heap_size")
     val status = SQL.Column.string("status")
+    val errors = SQL.Column.bytes("errors")
     val ml_statistics = SQL.Column.bytes("ml_statistics")
     val known = SQL.Column.bool("known")
 
@@ -654,7 +675,7 @@
       build_log_table("sessions",
         List(log_name, session_name, chapter, groups, threads, timing_elapsed, timing_cpu,
           timing_gc, timing_factor, ml_timing_elapsed, ml_timing_cpu, ml_timing_gc, ml_timing_factor,
-          heap_size, status))
+          heap_size, status, errors))
 
     val ml_statistics_table =
       build_log_table("ml_statistics", List(log_name, session_name, ml_statistics))
@@ -880,6 +901,7 @@
           stmt.double(13) = session.ml_timing.factor
           stmt.long(14) = session.heap_size
           stmt.string(15) = session.status.map(_.toString)
+          stmt.bytes(16) = compress_errors(session.errors)
           stmt.execute()
         }
       })
@@ -1013,6 +1035,7 @@
                   res.timing(Data.ml_timing_elapsed, Data.ml_timing_cpu, Data.ml_timing_gc),
                 heap_size = res.get_long(Data.heap_size),
                 status = res.get_string(Data.status).map(Session_Status.withName(_)),
+                errors = uncompress_errors(res.bytes(Data.errors)),
                 ml_statistics =
                   if (ml_statistics)
                     Properties.uncompress(res.bytes(Data.ml_statistics), Some(xml_cache))
--- a/src/Pure/PIDE/markup.scala	Fri May 26 19:39:02 2017 +0200
+++ b/src/Pure/PIDE/markup.scala	Fri May 26 20:52:01 2017 +0200
@@ -43,6 +43,9 @@
   val KIND = "kind"
   val Kind = new Properties.String(KIND)
 
+  val CONTENT = "content"
+  val Content = new Properties.String(CONTENT)
+
   val SERIAL = "serial"
   val Serial = new Properties.Long(SERIAL)
 
--- a/src/Pure/Thy/sessions.scala	Fri May 26 19:39:02 2017 +0200
+++ b/src/Pure/Thy/sessions.scala	Fri May 26 20:52:01 2017 +0200
@@ -815,14 +815,6 @@
 
     /* session info */
 
-    def compress_errors(errors: List[String]): Option[Bytes] =
-      if (errors.isEmpty) None
-      else Some(Bytes(YXML.string_of_body(XML.Encode.list(XML.Encode.string)(errors))).compress())
-
-    def uncompress_errors(bytes: Bytes): List[String] =
-      if (bytes.isEmpty) Nil
-      else XML.Decode.list(YXML.string_of_body(_))(YXML.parse_body(bytes.uncompress().text))
-
     def write_session_info(
       db: SQL.Database,
       name: String,
@@ -840,7 +832,7 @@
           stmt.bytes(3) = Properties.compress(build_log.command_timings)
           stmt.bytes(4) = Properties.compress(build_log.ml_statistics)
           stmt.bytes(5) = Properties.compress(build_log.task_statistics)
-          stmt.bytes(6) = compress_errors(build_log.errors)
+          stmt.bytes(6) = Build_Log.compress_errors(build_log.errors)
           stmt.string(7) = cat_lines(build.sources)
           stmt.string(8) = cat_lines(build.input_heaps)
           stmt.string(9) = build.output_heap getOrElse ""
@@ -862,6 +854,9 @@
     def read_task_statistics(db: SQL.Database, name: String): List[Properties.T] =
       read_properties(db, name, Session_Info.task_statistics)
 
+    def read_errors(db: SQL.Database, name: String): List[String] =
+      Build_Log.uncompress_errors(read_bytes(db, name, Session_Info.errors))
+
     def read_build_log(db: SQL.Database, name: String,
       command_timings: Boolean = false,
       ml_statistics: Boolean = false,
@@ -872,7 +867,7 @@
         command_timings = if (command_timings) read_command_timings(db, name) else Nil,
         ml_statistics = if (ml_statistics) read_ml_statistics(db, name) else Nil,
         task_statistics = if (task_statistics) read_task_statistics(db, name) else Nil,
-        errors = uncompress_errors(read_bytes(db, name, Session_Info.errors)))
+        errors = read_errors(db, name).map(Library.decode_lines(_)))
     }
 
     def read_build(db: SQL.Database, name: String): Option[Build.Session_Info] =