# HG changeset patch
# User wenzelm
# Date 1494097114 -7200
# Node ID 99f4e4e0303022cfa264dd6c03893351bf47831b
# Parent 1019be449dbd4355794557b8949317649d9fe699# Parent 1f4a80e80c8824e0244d2a91af55b17aa4bbaed8
merged
diff -r 1019be449dbd -r 99f4e4e03030 .hgignore
--- a/.hgignore Fri May 05 11:16:13 2017 +0200
+++ b/.hgignore Sat May 06 20:58:34 2017 +0200
@@ -15,13 +15,9 @@
^contrib
^heaps/
^browser_info/
-^doc/.*\.dvi
-^doc/.*\.eps
^doc/.*\.pdf
-^doc/.*\.ps
^src/Tools/jEdit/dist/
^src/Tools/VSCode/out/
^src/Tools/VSCode/extension/node_modules/
^src/Tools/VSCode/extension/protocol.log
^Admin/jenkins/ci-extras/target/
-^stats/
diff -r 1019be449dbd -r 99f4e4e03030 Admin/jenkins/build/ci_build_stats.scala
--- a/Admin/jenkins/build/ci_build_stats.scala Fri May 05 11:16:13 2017 +0200
+++ b/Admin/jenkins/build/ci_build_stats.scala Sat May 06 20:58:34 2017 +0200
@@ -26,7 +26,7 @@
println(s"=== $job ===")
val dir = target_dir + Path.basic(job)
- val sessions = Build_Stats.present_job(job, dir)
+ val sessions = Build_Stats_Legacy.present_job(job, dir)
val sections =
cat_lines(
diff -r 1019be449dbd -r 99f4e4e03030 src/Pure/Admin/build_log.scala
--- a/src/Pure/Admin/build_log.scala Fri May 05 11:16:13 2017 +0200
+++ b/src/Pure/Admin/build_log.scala Sat May 06 20:58:34 2017 +0200
@@ -11,7 +11,6 @@
import java.time.ZoneId
import java.time.format.{DateTimeFormatter, DateTimeParseException}
import java.util.Locale
-import java.sql.PreparedStatement
import scala.collection.immutable.SortedMap
import scala.collection.mutable
@@ -687,20 +686,22 @@
" GROUP BY " + version)
}
+ def recent_time(days: Int): SQL.Source =
+ "now() - INTERVAL '" + days.max(0) + " days'"
+
def recent_table(days: Int): SQL.Table =
{
val table = pull_date_table
SQL.Table("recent", table.columns,
- table.select(table.columns) +
- " WHERE " + pull_date(table) + " > now() - INTERVAL '" + days.max(0) + " days'")
+ table.select(table.columns, "WHERE " + pull_date(table) + " > " + recent_time(days)))
}
def select_recent(table: SQL.Table, columns: List[SQL.Column], days: Int,
- distinct: Boolean = false, pull_date: Boolean = false): String =
+ distinct: Boolean = false, pull_date: Boolean = false): SQL.Source =
{
val recent = recent_table(days)
val columns1 = if (pull_date) columns ::: List(Data.pull_date(recent)) else columns
- table.select(columns1, distinct = distinct) + " INNER JOIN " + recent.query_alias() +
+ table.select(columns1, distinct = distinct) + " INNER JOIN " + recent.query_name +
" ON " + Prop.isabelle_version(table) + " = " + Prop.isabelle_version(recent)
}
@@ -722,7 +723,7 @@
val columns = aux_columns ::: sessions_table.columns.tail
SQL.Table("isabelle_build_log", columns,
{
- SQL.select(log_name(aux_table) :: columns.tail) + aux_table.query_alias() +
+ SQL.select(log_name(aux_table) :: columns.tail) + aux_table.query_name +
" INNER JOIN " + table3 + " ON " + log_name(aux_table) + " = " + log_name(table3)
})
}
@@ -779,8 +780,8 @@
val recent_log_names =
db.using_statement(
Data.select_recent(
- Data.meta_info_table, List(Data.log_name), days, distinct = true))(
- stmt => SQL.iterator(stmt.executeQuery)(db.string(_, Data.log_name)).toList)
+ Data.meta_info_table, List(Data.log_name), days, distinct = true))(stmt =>
+ stmt.execute_query().iterator(_.string(Data.log_name)).toList)
for (log_name <- recent_log_names) {
read_meta_info(db, log_name).foreach(meta_info =>
@@ -800,11 +801,12 @@
{
db.using_statement(Data.recent_table(days).query)(stmt =>
{
- val rs = stmt.executeQuery
- while (rs.next()) {
- for ((c, i) <- table.columns.zipWithIndex)
- db2.set_string(stmt2, i + 1, db.get_string(rs, c))
- stmt2.execute
+ val res = stmt.execute_query()
+ while (res.next()) {
+ for ((c, i) <- table.columns.zipWithIndex) {
+ stmt2.string(i + 1) = res.get_string(c)
+ }
+ stmt2.execute()
}
})
})
@@ -820,19 +822,19 @@
def domain(db: SQL.Database, table: SQL.Table, column: SQL.Column): Set[String] =
db.using_statement(table.select(List(column), distinct = true))(stmt =>
- SQL.iterator(stmt.executeQuery)(db.string(_, column)).toSet)
+ stmt.execute_query().iterator(_.string(column)).toSet)
def update_meta_info(db: SQL.Database, log_name: String, meta_info: Meta_Info)
{
val table = Data.meta_info_table
db.using_statement(db.insert_permissive(table))(stmt =>
{
- db.set_string(stmt, 1, log_name)
+ stmt.string(1) = log_name
for ((c, i) <- table.columns.tail.zipWithIndex) {
if (c.T == SQL.Type.Date)
- db.set_date(stmt, i + 2, meta_info.get_date(c))
+ stmt.date(i + 2) = meta_info.get_date(c)
else
- db.set_string(stmt, i + 2, meta_info.get(c))
+ stmt.string(i + 2) = meta_info.get(c)
}
stmt.execute()
})
@@ -847,21 +849,21 @@
if (build_info.sessions.isEmpty) Iterator("" -> Session_Entry.empty)
else build_info.sessions.iterator
for ((session_name, session) <- entries_iterator) {
- db.set_string(stmt, 1, log_name)
- db.set_string(stmt, 2, session_name)
- db.set_string(stmt, 3, session.proper_chapter)
- db.set_string(stmt, 4, session.proper_groups)
- db.set_int(stmt, 5, session.threads)
- db.set_long(stmt, 6, session.timing.elapsed.proper_ms)
- db.set_long(stmt, 7, session.timing.cpu.proper_ms)
- db.set_long(stmt, 8, session.timing.gc.proper_ms)
- db.set_double(stmt, 9, session.timing.factor)
- db.set_long(stmt, 10, session.ml_timing.elapsed.proper_ms)
- db.set_long(stmt, 11, session.ml_timing.cpu.proper_ms)
- db.set_long(stmt, 12, session.ml_timing.gc.proper_ms)
- db.set_double(stmt, 13, session.ml_timing.factor)
- db.set_long(stmt, 14, session.heap_size)
- db.set_string(stmt, 15, session.status.map(_.toString))
+ stmt.string(1) = log_name
+ stmt.string(2) = session_name
+ stmt.string(3) = session.proper_chapter
+ stmt.string(4) = session.proper_groups
+ stmt.int(5) = session.threads
+ stmt.long(6) = session.timing.elapsed.proper_ms
+ stmt.long(7) = session.timing.cpu.proper_ms
+ stmt.long(8) = session.timing.gc.proper_ms
+ stmt.double(9) = session.timing.factor
+ stmt.long(10) = session.ml_timing.elapsed.proper_ms
+ stmt.long(11) = session.ml_timing.cpu.proper_ms
+ stmt.long(12) = session.ml_timing.gc.proper_ms
+ stmt.double(13) = session.ml_timing.factor
+ stmt.long(14) = session.heap_size
+ stmt.string(15) = session.status.map(_.toString)
stmt.execute()
}
})
@@ -878,9 +880,9 @@
build_info.sessions.iterator.filter(p => p._2.ml_statistics.nonEmpty).toList)
val entries = if (ml_stats.nonEmpty) ml_stats else List("" -> None)
for ((session_name, ml_statistics) <- entries) {
- db.set_string(stmt, 1, log_name)
- db.set_string(stmt, 2, session_name)
- db.set_bytes(stmt, 3, ml_statistics)
+ stmt.string(1) = log_name
+ stmt.string(2) = session_name
+ stmt.bytes(3) = ml_statistics
stmt.execute()
}
})
@@ -934,15 +936,15 @@
val columns = table.columns.tail
db.using_statement(table.select(columns, Data.log_name.where_equal(log_name)))(stmt =>
{
- val rs = stmt.executeQuery
- if (!rs.next) None
+ val res = stmt.execute_query()
+ if (!res.next) None
else {
val results =
columns.map(c => c.name ->
(if (c.T == SQL.Type.Date)
- db.get_date(rs, c).map(Log_File.Date_Format(_))
+ res.get_date(c).map(Log_File.Date_Format(_))
else
- db.get_string(rs, c)))
+ res.get_string(c)))
val n = Prop.all_props.length
val props = for ((x, Some(y)) <- results.take(n)) yield (x, y)
val settings = for ((x, Some(y)) <- results.drop(n)) yield (x, y)
@@ -975,9 +977,9 @@
if (ml_statistics) {
val columns = columns1 ::: List(Data.ml_statistics(table2))
val join =
- SQL.join_outer(table1, table2,
- Data.log_name(table1) + " = " + Data.log_name(table2) + " AND " +
- Data.session_name(table1) + " = " + Data.session_name(table2))
+ table1 + " LEFT OUTER JOIN " + table2 + " ON " +
+ Data.log_name(table1) + " = " + Data.log_name(table2) + " AND " +
+ Data.session_name(table1) + " = " + Data.session_name(table2)
(columns, SQL.enclose(join))
}
else (columns1, table1.ident)
@@ -985,26 +987,21 @@
val sessions =
db.using_statement(SQL.select(columns) + from + " " + where)(stmt =>
{
- SQL.iterator(stmt.executeQuery)(rs =>
+ stmt.execute_query().iterator(res =>
{
- val session_name = db.string(rs, Data.session_name)
+ val session_name = res.string(Data.session_name)
val session_entry =
Session_Entry(
- chapter = db.string(rs, Data.chapter),
- groups = split_lines(db.string(rs, Data.groups)),
- threads = db.get_int(rs, Data.threads),
- timing =
- Timing(Time.ms(db.long(rs, Data.timing_elapsed)),
- Time.ms(db.long(rs, Data.timing_cpu)),
- Time.ms(db.long(rs, Data.timing_gc))),
+ chapter = res.string(Data.chapter),
+ groups = split_lines(res.string(Data.groups)),
+ threads = res.get_int(Data.threads),
+ timing = res.timing(Data.timing_elapsed, Data.timing_cpu, Data.timing_gc),
ml_timing =
- Timing(Time.ms(db.long(rs, Data.ml_timing_elapsed)),
- Time.ms(db.long(rs, Data.ml_timing_cpu)),
- Time.ms(db.long(rs, Data.ml_timing_gc))),
- heap_size = db.get_long(rs, Data.heap_size),
- status = db.get_string(rs, Data.status).map(Session_Status.withName(_)),
+ 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(_)),
ml_statistics =
- if (ml_statistics) uncompress_properties(db.bytes(rs, Data.ml_statistics))
+ if (ml_statistics) uncompress_properties(res.bytes(Data.ml_statistics))
else Nil)
session_name -> session_entry
}).toMap
diff -r 1019be449dbd -r 99f4e4e03030 src/Pure/Admin/build_stats.scala
--- a/src/Pure/Admin/build_stats.scala Fri May 05 11:16:13 2017 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,194 +0,0 @@
-/* Title: Pure/Admin/build_stats.scala
- Author: Makarius
-
-Statistics from session build output.
-*/
-
-package isabelle
-
-
-object Build_Stats
-{
- /* presentation */
-
- private val default_history_length = 100
- private val default_size = (800, 600)
- private val default_only_sessions = Set.empty[String]
- private val default_elapsed_threshold = Time.zero
- private val default_ml_timing: Option[Boolean] = None
-
- def present_job(job: String, dir: Path,
- history_length: Int = default_history_length,
- size: (Int, Int) = default_size,
- only_sessions: Set[String] = default_only_sessions,
- elapsed_threshold: Time = default_elapsed_threshold,
- ml_timing: Option[Boolean] = default_ml_timing): List[String] =
- {
- val job_infos = Jenkins.build_job_infos(job).take(history_length)
- if (job_infos.isEmpty) error("No build infos for job " + quote(job))
-
- val all_infos =
- Par_List.map((info: Jenkins.Job_Info) =>
- {
- val t = info.timestamp / 1000
- val log_file = Build_Log.Log_File(info.log_filename.implode, Url.read(info.main_log))
- (t, log_file.parse_build_info())
- }, job_infos)
- val all_sessions =
- (Set.empty[String] /: all_infos)(
- { case (s, (_, info)) => s ++ info.sessions.keySet })
-
- def check_threshold(info: Build_Log.Build_Info, session: String): Boolean =
- {
- val t = info.timing(session)
- !t.is_zero && t.elapsed >= elapsed_threshold
- }
-
- val sessions =
- for {
- session <- (if (only_sessions.isEmpty) all_sessions else all_sessions & only_sessions)
- if all_infos.filter({ case (_, info) => check_threshold(info, session) }).length >= 3
- } yield session
-
- Isabelle_System.mkdirs(dir)
- for (session <- sessions) {
- Isabelle_System.with_tmp_file(session, "png") { data_file =>
- Isabelle_System.with_tmp_file(session, "gnuplot") { plot_file =>
- val data =
- for { (t, info) <- all_infos if info.finished(session) }
- yield {
- val timing1 = info.timing(session)
- val timing2 = info.ml_timing(session)
- List(t.toString,
- timing1.elapsed.minutes,
- timing1.cpu.minutes,
- timing2.elapsed.minutes,
- timing2.cpu.minutes,
- timing2.gc.minutes).mkString(" ")
- }
- File.write(data_file, cat_lines(data))
-
- val plots1 =
- List(
- """ using 1:3 smooth sbezier title "cpu time (smooth)" """,
- """ using 1:3 smooth csplines title "cpu time" """,
- """ using 1:2 smooth sbezier title "elapsed time (smooth)" """,
- """ using 1:2 smooth csplines title "elapsed time" """)
- val plots2 =
- List(
- """ using 1:5 smooth sbezier title "ML cpu time (smooth)" """,
- """ using 1:5 smooth csplines title "ML cpu time" """,
- """ using 1:4 smooth sbezier title "ML elapsed time (smooth)" """,
- """ using 1:4 smooth csplines title "ML elapsed time" """,
- """ using 1:6 smooth sbezier title "ML gc time (smooth)" """,
- """ using 1:6 smooth csplines title "ML gc time" """)
- val plots =
- ml_timing match {
- case None => plots1
- case Some(false) => plots1 ::: plots2
- case Some(true) => plots2
- }
-
- File.write(plot_file, """
-set terminal png size """ + size._1 + "," + size._2 + """
-set output """ + quote(File.standard_path(dir + Path.basic(session + ".png"))) + """
-set xdata time
-set timefmt "%s"
-set format x "%d-%b"
-set xlabel """ + quote(session) + """ noenhanced
-set key left top
-plot [] [0:] """ + plots.map(s => quote(data_file.implode) + " " + s).mkString(", ") + "\n")
- val result = Isabelle_System.bash("\"$ISABELLE_GNUPLOT\" " + File.bash_path(plot_file))
- if (result.rc != 0) {
- Output.error_message("Session " + session + ": gnuplot error")
- result.print
- }
- }
- }
- }
-
- sessions.toList.sorted
- }
-
-
- /* Isabelle tool wrapper */
-
- private val html_header = """
-
-
Performance statistics from session build output
-
-"""
- private val html_footer = """
-
-
-"""
-
- val isabelle_tool =
- Isabelle_Tool("build_stats", "present statistics from session build output", args =>
- {
- var target_dir = Path.explode("stats")
- var ml_timing = default_ml_timing
- var only_sessions = default_only_sessions
- var elapsed_threshold = default_elapsed_threshold
- var history_length = default_history_length
- var size = default_size
-
- val getopts = Getopts("""
-Usage: isabelle build_stats [OPTIONS] [JOBS ...]
-
- Options are:
- -D DIR target directory (default "stats")
- -M only ML timing
- -S SESSIONS only given SESSIONS (comma separated)
- -T THRESHOLD only sessions with elapsed time >= THRESHOLD (minutes)
- -l LENGTH length of history (default 100)
- -m include ML timing
- -s WxH size of PNG image (default 800x600)
-
- Present statistics from session build output of the given JOBS, from Jenkins
- continuous build service specified as URL via ISABELLE_JENKINS_ROOT.
-""",
- "D:" -> (arg => target_dir = Path.explode(arg)),
- "M" -> (_ => ml_timing = Some(true)),
- "S:" -> (arg => only_sessions = space_explode(',', arg).toSet),
- "T:" -> (arg => elapsed_threshold = Time.minutes(Value.Double.parse(arg))),
- "l:" -> (arg => history_length = Value.Int.parse(arg)),
- "m" -> (_ => ml_timing = Some(false)),
- "s:" -> (arg =>
- space_explode('x', arg).map(Value.Int.parse(_)) match {
- case List(w, h) if w > 0 && h > 0 => size = (w, h)
- case _ => error("Error bad PNG image size: " + quote(arg))
- }))
-
- val jobs = getopts(args)
- val all_jobs = Jenkins.build_job_names()
- val bad_jobs = jobs.filterNot(all_jobs.contains(_)).sorted
-
- if (jobs.isEmpty)
- error("No build jobs given. Available jobs: " + all_jobs.sorted.mkString(" "))
-
- if (bad_jobs.nonEmpty)
- error("Unknown build jobs: " + bad_jobs.mkString(" ") +
- "\nAvailable jobs: " + all_jobs.sorted.mkString(" "))
-
- for (job <- jobs) {
- val dir = target_dir + Path.basic(job)
- Output.writeln(dir.implode)
- val sessions =
- present_job(job, dir, history_length, size, only_sessions, elapsed_threshold, ml_timing)
- File.write(dir + Path.basic("index.html"),
- html_header + "\n" + HTML.output(job) + "
\n" +
- cat_lines(
- sessions.map(session =>
- """
""")) +
- "\n" + html_footer)
- }
-
- File.write(target_dir + Path.basic("index.html"),
- html_header + "\n\n" + html_footer)
- }, admin = true)
-}
diff -r 1019be449dbd -r 99f4e4e03030 src/Pure/Admin/build_stats_legacy.scala
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/Pure/Admin/build_stats_legacy.scala Sat May 06 20:58:34 2017 +0200
@@ -0,0 +1,111 @@
+/* Title: Pure/Admin/build_stats_legacy.scala
+ Author: Makarius
+
+Statistics from session build output.
+*/
+
+package isabelle
+
+
+object Build_Stats_Legacy
+{
+ /* presentation */
+
+ private val default_history_length = 100
+ private val default_size = (800, 600)
+ private val default_only_sessions = Set.empty[String]
+ private val default_elapsed_threshold = Time.zero
+ private val default_ml_timing: Option[Boolean] = None
+
+ def present_job(job: String, dir: Path,
+ history_length: Int = default_history_length,
+ size: (Int, Int) = default_size,
+ only_sessions: Set[String] = default_only_sessions,
+ elapsed_threshold: Time = default_elapsed_threshold,
+ ml_timing: Option[Boolean] = default_ml_timing): List[String] =
+ {
+ val job_infos = Jenkins.build_job_infos(job).take(history_length)
+ if (job_infos.isEmpty) error("No build infos for job " + quote(job))
+
+ val all_infos =
+ Par_List.map((info: Jenkins.Job_Info) =>
+ {
+ val log_file = Build_Log.Log_File(info.log_filename.implode, Url.read(info.main_log))
+ (info.date, log_file.parse_build_info())
+ }, job_infos)
+ val all_sessions =
+ (Set.empty[String] /: all_infos)(
+ { case (s, (_, info)) => s ++ info.sessions.keySet })
+
+ def check_threshold(info: Build_Log.Build_Info, session: String): Boolean =
+ {
+ val t = info.timing(session)
+ !t.is_zero && t.elapsed >= elapsed_threshold
+ }
+
+ val sessions =
+ for {
+ session <- (if (only_sessions.isEmpty) all_sessions else all_sessions & only_sessions)
+ if all_infos.filter({ case (_, info) => check_threshold(info, session) }).length >= 3
+ } yield session
+
+ Isabelle_System.mkdirs(dir)
+ for (session <- sessions) {
+ Isabelle_System.with_tmp_file(session, "png") { data_file =>
+ Isabelle_System.with_tmp_file(session, "gnuplot") { plot_file =>
+ val data =
+ for { (date, info) <- all_infos if info.finished(session) }
+ yield {
+ val timing1 = info.timing(session)
+ val timing2 = info.ml_timing(session)
+ List(date.unix_epoch.toString,
+ timing1.elapsed.minutes,
+ timing1.cpu.minutes,
+ timing2.elapsed.minutes,
+ timing2.cpu.minutes,
+ timing2.gc.minutes).mkString(" ")
+ }
+ File.write(data_file, cat_lines(data))
+
+ val plots1 =
+ List(
+ """ using 1:3 smooth sbezier title "cpu time (smooth)" """,
+ """ using 1:3 smooth csplines title "cpu time" """,
+ """ using 1:2 smooth sbezier title "elapsed time (smooth)" """,
+ """ using 1:2 smooth csplines title "elapsed time" """)
+ val plots2 =
+ List(
+ """ using 1:5 smooth sbezier title "ML cpu time (smooth)" """,
+ """ using 1:5 smooth csplines title "ML cpu time" """,
+ """ using 1:4 smooth sbezier title "ML elapsed time (smooth)" """,
+ """ using 1:4 smooth csplines title "ML elapsed time" """,
+ """ using 1:6 smooth sbezier title "ML gc time (smooth)" """,
+ """ using 1:6 smooth csplines title "ML gc time" """)
+ val plots =
+ ml_timing match {
+ case None => plots1
+ case Some(false) => plots1 ::: plots2
+ case Some(true) => plots2
+ }
+
+ File.write(plot_file, """
+set terminal png size """ + size._1 + "," + size._2 + """
+set output """ + quote(File.standard_path(dir + Path.basic(session + ".png"))) + """
+set xdata time
+set timefmt "%s"
+set format x "%d-%b"
+set xlabel """ + quote(session) + """ noenhanced
+set key left top
+plot [] [0:] """ + plots.map(s => quote(data_file.implode) + " " + s).mkString(", ") + "\n")
+ val result = Isabelle_System.bash("\"$ISABELLE_GNUPLOT\" " + File.bash_path(plot_file))
+ if (result.rc != 0) {
+ Output.error_message("Session " + session + ": gnuplot error")
+ result.print
+ }
+ }
+ }
+ }
+
+ sessions.toList.sorted
+ }
+}
diff -r 1019be449dbd -r 99f4e4e03030 src/Pure/Admin/build_status.scala
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/Pure/Admin/build_status.scala Sat May 06 20:58:34 2017 +0200
@@ -0,0 +1,276 @@
+/* Title: Pure/Admin/build_status.scala
+ Author: Makarius
+
+Present recent build status information from database.
+*/
+
+package isabelle
+
+
+object Build_Status
+{
+ private val default_target_dir = Path.explode("build_status")
+ private val default_history_length = 30
+ private val default_image_size = (800, 600)
+
+
+ /* data profiles */
+
+ sealed case class Profile(name: String, sql: String)
+ {
+ def select(columns: List[SQL.Column], days: Int, only_sessions: Set[String]): SQL.Source =
+ {
+ val sql_sessions =
+ if (only_sessions.isEmpty) ""
+ else
+ only_sessions.iterator.map(a => Build_Log.Data.session_name + " = " + SQL.string(a))
+ .mkString("(", " OR ", ") AND ")
+
+ Build_Log.Data.universal_table.select(columns, distinct = true,
+ sql = "WHERE " +
+ Build_Log.Data.pull_date + " > " + Build_Log.Data.recent_time(days) + " AND " +
+ Build_Log.Data.status + " = " + SQL.string(Build_Log.Session_Status.finished.toString) +
+ " AND " + sql_sessions + SQL.enclose(sql) +
+ " ORDER BY " + Build_Log.Data.pull_date + " DESC")
+ }
+ }
+
+ val standard_profiles: List[Profile] =
+ Jenkins.build_status_profiles ::: Isabelle_Cronjob.build_status_profiles
+
+ sealed case class Entry(date: Date, timing: Timing, ml_timing: Timing)
+ {
+ def check(elapsed_threshold: Time): Boolean =
+ !timing.is_zero && timing.elapsed >= elapsed_threshold
+ }
+
+ type Data = Map[String, Map[String, List[Entry]]]
+
+
+ /* read data */
+
+ def read_data(options: Options,
+ profiles: List[Profile] = standard_profiles,
+ progress: Progress = No_Progress,
+ history_length: Int = default_history_length,
+ only_sessions: Set[String] = Set.empty,
+ elapsed_threshold: Time = Time.zero): Data =
+ {
+ var data: Data = Map.empty
+
+ val store = Build_Log.store(options)
+ using(store.open_database())(db =>
+ {
+ for (profile <- profiles) {
+ progress.echo("input " + quote(profile.name))
+ val columns =
+ List(
+ Build_Log.Data.pull_date,
+ Build_Log.Settings.ML_PLATFORM,
+ Build_Log.Data.session_name,
+ Build_Log.Data.threads,
+ Build_Log.Data.timing_elapsed,
+ Build_Log.Data.timing_cpu,
+ Build_Log.Data.timing_gc,
+ Build_Log.Data.ml_timing_elapsed,
+ Build_Log.Data.ml_timing_cpu,
+ Build_Log.Data.ml_timing_gc)
+
+ db.using_statement(profile.select(columns, history_length, only_sessions))(stmt =>
+ {
+ val res = stmt.execute_query()
+ while (res.next()) {
+ val ml_platform = res.string(Build_Log.Settings.ML_PLATFORM)
+ val threads = res.get_int(Build_Log.Data.threads)
+ val name =
+ profile.name +
+ "_m" + (if (ml_platform.startsWith("x86_64")) "64" else "32") +
+ "_M" + threads.getOrElse(1)
+
+ val session = res.string(Build_Log.Data.session_name)
+ val entry =
+ Entry(res.date(Build_Log.Data.pull_date),
+ res.timing(
+ Build_Log.Data.timing_elapsed,
+ Build_Log.Data.timing_cpu,
+ Build_Log.Data.timing_gc),
+ res.timing(
+ Build_Log.Data.ml_timing_elapsed,
+ Build_Log.Data.ml_timing_cpu,
+ Build_Log.Data.ml_timing_gc))
+
+ val session_entries = data.getOrElse(name, Map.empty)
+ val entries = session_entries.getOrElse(session, Nil)
+ data += (name -> (session_entries + (session -> (entry :: entries))))
+ }
+ })
+ }
+ })
+
+ for {
+ (name, session_entries) <- data
+ session_entries1 <-
+ {
+ val session_entries1 =
+ for {
+ (session, entries) <- session_entries
+ if entries.filter(_.check(elapsed_threshold)).length >= 3
+ } yield (session, entries)
+ if (session_entries1.isEmpty) None
+ else Some(session_entries1)
+ }
+ } yield (name, session_entries1)
+ }
+
+
+ /* present data */
+
+ private val html_header = """
+
+Build status
+
+"""
+ private val html_footer = """
+
+
+"""
+
+ def present_data(data: Data,
+ progress: Progress = No_Progress,
+ target_dir: Path = default_target_dir,
+ image_size: (Int, Int) = default_image_size,
+ ml_timing: Option[Boolean] = None)
+ {
+ val data_entries = data.toList.sortBy(_._1)
+ for ((name, session_entries) <- data_entries) {
+ val dir = target_dir + Path.explode(name)
+ progress.echo("output " + dir)
+ Isabelle_System.mkdirs(dir)
+
+ for ((session, entries) <- session_entries) {
+ Isabelle_System.with_tmp_file(session, "data") { data_file =>
+ Isabelle_System.with_tmp_file(session, "gnuplot") { gnuplot_file =>
+
+ File.write(data_file,
+ cat_lines(
+ entries.map(entry =>
+ List(entry.date.unix_epoch.toString,
+ entry.timing.elapsed.minutes,
+ entry.timing.cpu.minutes,
+ entry.ml_timing.elapsed.minutes,
+ entry.ml_timing.cpu.minutes,
+ entry.ml_timing.gc.minutes).mkString(" "))))
+
+ val plots1 =
+ List(
+ """ using 1:3 smooth sbezier title "cpu time (smooth)" """,
+ """ using 1:3 smooth csplines title "cpu time" """,
+ """ using 1:2 smooth sbezier title "elapsed time (smooth)" """,
+ """ using 1:2 smooth csplines title "elapsed time" """)
+ val plots2 =
+ List(
+ """ using 1:5 smooth sbezier title "ML cpu time (smooth)" """,
+ """ using 1:5 smooth csplines title "ML cpu time" """,
+ """ using 1:4 smooth sbezier title "ML elapsed time (smooth)" """,
+ """ using 1:4 smooth csplines title "ML elapsed time" """,
+ """ using 1:6 smooth sbezier title "ML gc time (smooth)" """,
+ """ using 1:6 smooth csplines title "ML gc time" """)
+ val plots =
+ ml_timing match {
+ case None => plots1
+ case Some(false) => plots1 ::: plots2
+ case Some(true) => plots2
+ }
+
+ File.write(gnuplot_file, """
+set terminal png size """ + image_size._1 + "," + image_size._2 + """
+set output """ + quote(File.standard_path(dir + Path.basic(session + ".png"))) + """
+set xdata time
+set timefmt "%s"
+set format x "%d-%b"
+set xlabel """ + quote(session) + """ noenhanced
+set key left top
+plot [] [0:] """ + plots.map(s => quote(data_file.implode) + " " + s).mkString(", ") + "\n")
+
+ val result =
+ Isabelle_System.bash("\"$ISABELLE_GNUPLOT\" " + File.bash_path(gnuplot_file))
+ if (result.rc != 0)
+ result.error("Gnuplot failed for " + name + "/" + session).check
+ }
+ }
+ }
+
+ File.write(dir + Path.basic("index.html"),
+ html_header + "\n" + HTML.output(name) + "
\n" +
+ cat_lines(
+ session_entries.toList.map(_._1).sorted.map(session =>
+ """
""")) +
+ "\n" + html_footer)
+ }
+
+ File.write(target_dir + Path.basic("index.html"),
+ html_header + "\n\n" + html_footer)
+ }
+
+
+ /* Isabelle tool wrapper */
+
+ val isabelle_tool =
+ Isabelle_Tool("build_status", "present recent build status information from database", args =>
+ {
+ var target_dir = default_target_dir
+ var ml_timing: Option[Boolean] = None
+ var only_sessions = Set.empty[String]
+ var elapsed_threshold = Time.zero
+ var history_length = default_history_length
+ var options = Options.init()
+ var image_size = default_image_size
+
+ val getopts = Getopts("""
+Usage: isabelle build_status [OPTIONS]
+
+ Options are:
+ -D DIR target directory (default """ + default_target_dir + """)
+ -M only ML timing
+ -S SESSIONS only given SESSIONS (comma separated)
+ -T THRESHOLD only sessions with elapsed time >= THRESHOLD (minutes)
+ -l LENGTH length of history (default """ + default_history_length + """)
+ -m include ML timing
+ -o OPTION override Isabelle system OPTION (via NAME=VAL or NAME)
+ -s WxH size of PNG image (default """ + image_size._1 + "x" + image_size._2 + """)
+
+ Present performance statistics from build log database, which is specified
+ via system options build_log_database_host, build_log_database_user etc.
+""",
+ "D:" -> (arg => target_dir = Path.explode(arg)),
+ "M" -> (_ => ml_timing = Some(true)),
+ "S:" -> (arg => only_sessions = space_explode(',', arg).toSet),
+ "T:" -> (arg => elapsed_threshold = Time.minutes(Value.Double.parse(arg))),
+ "l:" -> (arg => history_length = Value.Int.parse(arg)),
+ "m" -> (_ => ml_timing = Some(false)),
+ "o:" -> (arg => options = options + arg),
+ "s:" -> (arg =>
+ space_explode('x', arg).map(Value.Int.parse(_)) match {
+ case List(w, h) if w > 0 && h > 0 => image_size = (w, h)
+ case _ => error("Error bad PNG image size: " + quote(arg))
+ }))
+
+ val more_args = getopts(args)
+ if (more_args.nonEmpty) getopts.usage()
+
+ val progress = new Console_Progress
+
+ val data =
+ read_data(options, progress = progress, history_length = history_length,
+ only_sessions = only_sessions, elapsed_threshold = elapsed_threshold)
+
+ present_data(data, progress = progress, target_dir = target_dir,
+ image_size = image_size, ml_timing = ml_timing)
+
+ }, admin = true)
+}
diff -r 1019be449dbd -r 99f4e4e03030 src/Pure/Admin/isabelle_cronjob.scala
--- a/src/Pure/Admin/isabelle_cronjob.scala Fri May 05 11:16:13 2017 +0200
+++ b/src/Pure/Admin/isabelle_cronjob.scala Sat May 06 20:58:34 2017 +0200
@@ -29,10 +29,11 @@
val afp_source = "https://bitbucket.org/isa-afp/afp-devel"
val devel_dir = Path.explode("~/html-data/devel")
- val release_snapshot = devel_dir + Path.explode("release_snapshot")
- val build_log_snapshot = devel_dir + Path.explode("build_log.db")
+ val release_snapshot_dir = devel_dir + Path.explode("release_snapshot")
+ val build_log_db = devel_dir + Path.explode("build_log.db")
+ val build_status_dir = devel_dir + Path.explode("build_status")
- val jenkins_jobs = List("isabelle-nightly-benchmark", "identify")
+ val jenkins_jobs = "identify" :: Jenkins.build_log_jobs
@@ -50,8 +51,8 @@
File.write(logger.log_dir + Build_Log.log_filename("isabelle_identify", logger.start_date),
Build_Log.Identify.content(logger.start_date, Some(rev), Some(afp_rev)))
- val new_snapshot = release_snapshot.ext("new")
- val old_snapshot = release_snapshot.ext("old")
+ val new_snapshot = release_snapshot_dir.ext("new")
+ val old_snapshot = release_snapshot_dir.ext("old")
Isabelle_System.rm_tree(new_snapshot)
Isabelle_System.rm_tree(old_snapshot)
@@ -59,8 +60,8 @@
Build_Release.build_release(base_dir, rev = rev, afp_rev = afp_rev,
parallel_jobs = 4, remote_mac = "macbroy31", website = Some(new_snapshot))
- if (release_snapshot.is_dir) File.move(release_snapshot, old_snapshot)
- File.move(new_snapshot, release_snapshot)
+ if (release_snapshot_dir.is_dir) File.move(release_snapshot_dir, old_snapshot)
+ File.move(new_snapshot, release_snapshot_dir)
Isabelle_System.rm_tree(old_snapshot)
}))
@@ -87,29 +88,55 @@
/* remote build_history */
sealed case class Remote_Build(
+ name: String,
host: String,
user: String = "",
port: Int = 0,
shared_home: Boolean = true,
options: String = "",
- args: String = "")
+ args: String = "",
+ detect: SQL.Source = "")
+ {
+ def profile: Build_Status.Profile =
+ {
+ val sql =
+ Build_Log.Prop.build_engine + " = " + SQL.string(Build_History.engine) + " AND " +
+ Build_Log.Prop.build_host + " = " + SQL.string(host) +
+ (if (detect == "") "" else " AND " + SQL.enclose(detect))
+ Build_Status.Profile(name, sql)
+ }
+ }
- private val remote_builds =
+ private val remote_builds: List[List[Remote_Build]] =
+ {
List(
- List(Remote_Build("lxbroy8",
+ List(Remote_Build("polyml-test", "lxbroy8",
options = "-m32 -B -M1x2,2 -t polyml-test -e 'init_component /home/isabelle/contrib/polyml-5.7-20170217'",
- args = "-N -g timing")),
- List(Remote_Build("lxbroy9", options = "-m32 -B -M1x2,2", args = "-N -g timing")),
- List(Remote_Build("lxbroy10", options = "-m32 -B -M1x4,2,4,6", args = "-N -g timing")),
+ args = "-N -g timing",
+ detect = Build_Log.Prop.build_tags + " = " + SQL.string("polyml-test"))),
+ List(Remote_Build("linux1", "lxbroy9",
+ options = "-m32 -B -M1x2,2", args = "-N -g timing")),
+ List(Remote_Build("linux2", "lxbroy10",
+ options = "-m32 -B -M1x4,2,4,6", args = "-N -g timing")),
List(
- Remote_Build("macbroy2", options = "-m32 -M8", args = "-a"),
- Remote_Build("macbroy2", options = "-m32 -M8 -t quick_and_dirty", args = "-a -o quick_and_dirty"),
- Remote_Build("macbroy2", options = "-m32 -M8 -t skip_proofs", args = "-a -o skip_proofs")),
- List(Remote_Build("macbroy30", options = "-m32 -M2", args = "-a")),
- List(Remote_Build("macbroy31", options = "-m32 -M2", args = "-a")),
+ Remote_Build("macos1", "macbroy2", options = "-m32 -M8", args = "-a",
+ detect = Build_Log.Prop.build_tags + " IS NULL"),
+ Remote_Build("macos1_quick_and_dirty", "macbroy2",
+ options = "-m32 -M8 -t quick_and_dirty", args = "-a -o quick_and_dirty",
+ detect = Build_Log.Prop.build_tags + " = " + SQL.string("quick_and_dirty")),
+ Remote_Build("macos1_skip_proofs", "macbroy2",
+ options = "-m32 -M8 -t skip_proofs", args = "-a -o skip_proofs",
+ detect = Build_Log.Prop.build_tags + " = " + SQL.string("skip_proofs"))),
+ List(Remote_Build("macos2", "macbroy30", options = "-m32 -M2", args = "-a")),
+ List(Remote_Build("macos3", "macbroy31", options = "-m32 -M2", args = "-a")),
List(
- Remote_Build("vmnipkow9", shared_home = false, options = "-m32 -M4", args = "-a"),
- Remote_Build("vmnipkow9", shared_home = false, options = "-m64 -M4", args = "-a")))
+ Remote_Build("windows", "vmnipkow9", shared_home = false,
+ options = "-m32 -M4", args = "-a",
+ detect = Build_Log.Settings.ML_PLATFORM + " = " + SQL.string("x86-windows")),
+ Remote_Build("windows", "vmnipkow9", shared_home = false,
+ options = "-m64 -M4", args = "-a",
+ detect = Build_Log.Settings.ML_PLATFORM + " = " + SQL.string("x86_64-windows"))))
+ }
private def remote_build_history(rev: String, r: Remote_Build): Logger_Task =
{
@@ -153,11 +180,22 @@
using(store.open_database())(db =>
{
store.update_database(db, database_dirs, ml_statistics = true)
- store.snapshot_database(db, build_log_snapshot)
+ store.snapshot_database(db, build_log_db)
})
}
+ /* present build status */
+
+ val build_status_profiles: List[Build_Status.Profile] =
+ remote_builds.flatten.map(_.profile)
+
+ def build_status(options: Options)
+ {
+ Build_Status.present_data(Build_Status.read_data(options), target_dir = build_status_dir)
+ }
+
+
/** task logging **/
@@ -304,7 +342,8 @@
SEQ(List(build_release, build_history_base,
PAR(remote_builds.map(seq => SEQ(seq.map(remote_build_history(rev, _))))),
Logger_Task("jenkins_logs", _ => Jenkins.download_logs(jenkins_jobs, main_dir)),
- Logger_Task("build_log_database", logger => database_update(logger.options)))))))
+ Logger_Task("build_log_database", logger => database_update(logger.options)),
+ Logger_Task("build_status", logger => build_status(logger.options)))))))
log_service.shutdown()
diff -r 1019be449dbd -r 99f4e4e03030 src/Pure/Admin/jenkins.scala
--- a/src/Pure/Admin/jenkins.scala Fri May 05 11:16:13 2017 +0200
+++ b/src/Pure/Admin/jenkins.scala Sat May 06 20:58:34 2017 +0200
@@ -48,6 +48,17 @@
}
+ /* build log status */
+
+ val build_log_jobs = List("isabelle-nightly-benchmark")
+
+ val build_status_profiles: List[Build_Status.Profile] =
+ build_log_jobs.map(job_name =>
+ Build_Status.Profile("jenkins_" + job_name,
+ Build_Log.Prop.build_engine + " = " + SQL.string(Build_Log.Jenkins.engine) + " AND " +
+ Build_Log.Data.log_name + " LIKE " + SQL.string("%" + job_name)))
+
+
/* job info */
sealed case class Job_Info(
diff -r 1019be449dbd -r 99f4e4e03030 src/Pure/General/date.scala
--- a/src/Pure/General/date.scala Fri May 05 11:16:13 2017 +0200
+++ b/src/Pure/General/date.scala Sat May 06 20:58:34 2017 +0200
@@ -93,6 +93,7 @@
def to(zone: ZoneId): Date = new Date(rep.withZoneSameInstant(zone))
def to_utc: Date = to(ZoneId.of("UTC"))
+ def unix_epoch: Long = rep.toEpochSecond
def instant: Instant = Instant.from(rep)
def time: Time = Time.instant(instant)
def timezone: ZoneId = rep.getZone
diff -r 1019be449dbd -r 99f4e4e03030 src/Pure/General/sql.scala
--- a/src/Pure/General/sql.scala Fri May 05 11:16:13 2017 +0200
+++ b/src/Pure/General/sql.scala Sat May 06 20:58:34 2017 +0200
@@ -9,11 +9,16 @@
import java.time.OffsetDateTime
import java.sql.{DriverManager, Connection, PreparedStatement, ResultSet}
+import scala.collection.mutable
+
object SQL
{
/** SQL language **/
+ type Source = String
+
+
/* concrete syntax */
def escape_char(c: Char): String =
@@ -30,24 +35,17 @@
case _ => c.toString
}
- def string(s: String): String =
+ def string(s: String): Source =
"'" + s.map(escape_char(_)).mkString + "'"
- def ident(s: String): String =
+ def ident(s: String): Source =
Long_Name.implode(Long_Name.explode(s).map(a => quote(a.replace("\"", "\"\""))))
- def enclose(s: String): String = "(" + s + ")"
- def enclosure(ss: Iterable[String]): String = ss.mkString("(", ", ", ")")
-
- def select(columns: List[Column], distinct: Boolean = false): String =
- "SELECT " + (if (distinct) "DISTINCT " else "") + commas(columns.map(_.ident)) + " FROM "
+ def enclose(s: Source): Source = "(" + s + ")"
+ def enclosure(ss: Iterable[Source]): Source = ss.mkString("(", ", ", ")")
- def join(table1: Table, table2: Table, sql: String = "", outer: Boolean = false): String =
- table1.ident + (if (outer) " LEFT OUTER JOIN " else " INNER JOIN ") + table2.ident +
- (if (sql == "") "" else " ON " + sql)
-
- def join_outer(table1: Table, table2: Table, sql: String = ""): String =
- join(table1, table2, sql, outer = true)
+ def select(columns: List[Column], distinct: Boolean = false): Source =
+ "SELECT " + (if (distinct) "DISTINCT " else "") + commas(columns.map(_.ident)) + " FROM "
/* types */
@@ -63,14 +61,14 @@
val Date = Value("TIMESTAMP WITH TIME ZONE")
}
- def sql_type_default(T: Type.Value): String = T.toString
+ def sql_type_default(T: Type.Value): Source = T.toString
- def sql_type_sqlite(T: Type.Value): String =
+ def sql_type_sqlite(T: Type.Value): Source =
if (T == Type.Boolean) "INTEGER"
else if (T == Type.Date) "TEXT"
else sql_type_default(T)
- def sql_type_postgresql(T: Type.Value): String =
+ def sql_type_postgresql(T: Type.Value): Source =
if (T == Type.Bytes) "BYTEA"
else sql_type_default(T)
@@ -101,20 +99,20 @@
def apply(table: Table): Column =
Column(Long_Name.qualify(table.name, name), T, strict = strict, primary_key = primary_key)
- def ident: String = SQL.ident(name)
+ def ident: Source = SQL.ident(name)
- def decl(sql_type: Type.Value => String): String =
+ def decl(sql_type: Type.Value => Source): Source =
ident + " " + sql_type(T) + (if (strict || primary_key) " NOT NULL" else "")
- def where_equal(s: String): String = "WHERE " + ident + " = " + string(s)
+ def where_equal(s: String): Source = "WHERE " + ident + " = " + string(s)
- override def toString: String = ident
+ override def toString: Source = ident
}
/* tables */
- sealed case class Table(name: String, columns: List[Column], body: String = "")
+ sealed case class Table(name: String, columns: List[Column], body: Source = "")
{
private val columns_index: Map[String, Int] =
columns.iterator.map(_.name).zipWithIndex.toMap
@@ -124,16 +122,15 @@
case bad => error("Duplicate column names " + commas_quote(bad) + " for table " + quote(name))
}
- def ident: String = SQL.ident(name)
+ def ident: Source = SQL.ident(name)
- def query: String =
+ def query: Source =
if (body == "") error("Missing SQL body for table " + quote(name))
else SQL.enclose(body)
- def query_alias(alias: String = name): String =
- query + " AS " + SQL.ident(alias)
+ def query_name: Source = query + " AS " + SQL.ident(name)
- def create(strict: Boolean = false, sql_type: Type.Value => String): String =
+ def create(strict: Boolean = false, sql_type: Type.Value => Source): Source =
{
val primary_key =
columns.filter(_.primary_key).map(_.name) match {
@@ -145,46 +142,160 @@
}
def create_index(index_name: String, index_columns: List[Column],
- strict: Boolean = false, unique: Boolean = false): String =
+ strict: Boolean = false, unique: Boolean = false): Source =
"CREATE " + (if (unique) "UNIQUE " else "") + "INDEX " +
(if (strict) "" else "IF NOT EXISTS ") + SQL.ident(index_name) + " ON " +
ident + " " + enclosure(index_columns.map(_.name))
- def insert_cmd(cmd: String, sql: String = ""): String =
+ def insert_cmd(cmd: Source, sql: Source = ""): Source =
cmd + " INTO " + ident + " VALUES " + enclosure(columns.map(_ => "?")) +
(if (sql == "") "" else " " + sql)
- def insert(sql: String = ""): String = insert_cmd("INSERT", sql)
+ def insert(sql: Source = ""): Source = insert_cmd("INSERT", sql)
- def delete(sql: String = ""): String =
+ def delete(sql: Source = ""): Source =
"DELETE FROM " + ident +
(if (sql == "") "" else " " + sql)
- def select(select_columns: List[Column], sql: String = "", distinct: Boolean = false): String =
+ def select(select_columns: List[Column], sql: Source = "", distinct: Boolean = false): Source =
SQL.select(select_columns, distinct = distinct) + ident +
(if (sql == "") "" else " " + sql)
- override def toString: String = ident
+ override def toString: Source = ident
}
/** SQL database operations **/
+ /* statements */
+
+ class Statement private[SQL](val db: Database, val rep: PreparedStatement)
+ {
+ stmt =>
+
+ object bool
+ {
+ def update(i: Int, x: Boolean) { rep.setBoolean(i, x) }
+ def update(i: Int, x: Option[Boolean])
+ {
+ if (x.isDefined) update(i, x.get)
+ else rep.setNull(i, java.sql.Types.BOOLEAN)
+ }
+ }
+ object int
+ {
+ def update(i: Int, x: Int) { rep.setInt(i, x) }
+ def update(i: Int, x: Option[Int])
+ {
+ if (x.isDefined) update(i, x.get)
+ else rep.setNull(i, java.sql.Types.INTEGER)
+ }
+ }
+ object long
+ {
+ def update(i: Int, x: Long) { rep.setLong(i, x) }
+ def update(i: Int, x: Option[Long])
+ {
+ if (x.isDefined) update(i, x.get)
+ else rep.setNull(i, java.sql.Types.BIGINT)
+ }
+ }
+ object double
+ {
+ def update(i: Int, x: Double) { rep.setDouble(i, x) }
+ def update(i: Int, x: Option[Double])
+ {
+ if (x.isDefined) update(i, x.get)
+ else rep.setNull(i, java.sql.Types.DOUBLE)
+ }
+ }
+ object string
+ {
+ def update(i: Int, x: String) { rep.setString(i, x) }
+ def update(i: Int, x: Option[String]): Unit = update(i, x.orNull)
+ }
+ object bytes
+ {
+ def update(i: Int, bytes: Bytes)
+ {
+ if (bytes == null) rep.setBytes(i, null)
+ else rep.setBinaryStream(i, bytes.stream(), bytes.length)
+ }
+ def update(i: Int, bytes: Option[Bytes]): Unit = update(i, bytes.orNull)
+ }
+ object date
+ {
+ def update(i: Int, date: Date): Unit = db.update_date(stmt, i, date)
+ def update(i: Int, date: Option[Date]): Unit = update(i, date.orNull)
+ }
+
+ def execute(): Boolean = rep.execute()
+ def execute_query(): Result = new Result(this, rep.executeQuery())
+
+ def close(): Unit = rep.close
+ }
+
+
/* results */
- def iterator[A](rs: ResultSet)(get: ResultSet => A): Iterator[A] = new Iterator[A]
+ class Result private[SQL](val stmt: Statement, val rep: ResultSet)
{
- private var _next: Boolean = rs.next()
- def hasNext: Boolean = _next
- def next: A = { val x = get(rs); _next = rs.next(); x }
+ res =>
+
+ def next(): Boolean = rep.next()
+
+ def iterator[A](get: Result => A): Iterator[A] = new Iterator[A]
+ {
+ private var _next: Boolean = res.next()
+ def hasNext: Boolean = _next
+ def next: A = { val x = get(res); _next = res.next(); x }
+ }
+
+ def bool(column: Column): Boolean = rep.getBoolean(column.name)
+ def int(column: Column): Int = rep.getInt(column.name)
+ def long(column: Column): Long = rep.getLong(column.name)
+ def double(column: Column): Double = rep.getDouble(column.name)
+ def string(column: Column): String =
+ {
+ val s = rep.getString(column.name)
+ if (s == null) "" else s
+ }
+ def bytes(column: Column): Bytes =
+ {
+ val bs = rep.getBytes(column.name)
+ if (bs == null) Bytes.empty else Bytes(bs)
+ }
+ def date(column: Column): Date = stmt.db.date(res, column)
+
+ def timing(c1: Column, c2: Column, c3: Column) =
+ Timing(Time.ms(long(c1)), Time.ms(long(c2)), Time.ms(long(c3)))
+
+ def get[A](column: Column, f: Column => A): Option[A] =
+ {
+ val x = f(column)
+ if (rep.wasNull) None else Some(x)
+ }
+ def get_bool(column: Column): Option[Boolean] = get(column, bool _)
+ def get_int(column: Column): Option[Int] = get(column, int _)
+ def get_long(column: Column): Option[Long] = get(column, long _)
+ def get_double(column: Column): Option[Double] = get(column, double _)
+ def get_string(column: Column): Option[String] = get(column, string _)
+ def get_bytes(column: Column): Option[Bytes] = get(column, bytes _)
+ def get_date(column: Column): Option[Date] = get(column, date _)
}
+
+ /* database */
+
trait Database
{
+ db =>
+
+
/* types */
- def sql_type(T: Type.Value): String
+ def sql_type(T: Type.Value): Source
/* connection */
@@ -210,102 +321,31 @@
}
- /* statements */
+ /* statements and results */
- def statement(sql: String): PreparedStatement =
- connection.prepareStatement(sql)
+ def statement(sql: Source): Statement =
+ new Statement(db, connection.prepareStatement(sql))
- def using_statement[A](sql: String)(f: PreparedStatement => A): A =
+ def using_statement[A](sql: Source)(f: Statement => A): A =
using(statement(sql))(f)
- def insert_permissive(table: Table, sql: String = ""): String
-
-
- /* input */
-
- def set_bool(stmt: PreparedStatement, i: Int, x: Boolean) { stmt.setBoolean(i, x) }
- def set_bool(stmt: PreparedStatement, i: Int, x: Option[Boolean])
- {
- if (x.isDefined) set_bool(stmt, i, x.get)
- else stmt.setNull(i, java.sql.Types.BOOLEAN)
- }
-
- def set_int(stmt: PreparedStatement, i: Int, x: Int) { stmt.setInt(i, x) }
- def set_int(stmt: PreparedStatement, i: Int, x: Option[Int])
- {
- if (x.isDefined) set_int(stmt, i, x.get)
- else stmt.setNull(i, java.sql.Types.INTEGER)
- }
-
- def set_long(stmt: PreparedStatement, i: Int, x: Long) { stmt.setLong(i, x) }
- def set_long(stmt: PreparedStatement, i: Int, x: Option[Long])
- {
- if (x.isDefined) set_long(stmt, i, x.get)
- else stmt.setNull(i, java.sql.Types.BIGINT)
- }
-
- def set_double(stmt: PreparedStatement, i: Int, x: Double) { stmt.setDouble(i, x) }
- def set_double(stmt: PreparedStatement, i: Int, x: Option[Double])
- {
- if (x.isDefined) set_double(stmt, i, x.get)
- else stmt.setNull(i, java.sql.Types.DOUBLE)
- }
-
- def set_string(stmt: PreparedStatement, i: Int, x: String) { stmt.setString(i, x) }
- def set_string(stmt: PreparedStatement, i: Int, x: Option[String]): Unit =
- set_string(stmt, i, x.orNull)
+ def update_date(stmt: Statement, i: Int, date: Date): Unit
+ def date(res: Result, column: Column): Date
- def set_bytes(stmt: PreparedStatement, i: Int, bytes: Bytes)
- {
- if (bytes == null) stmt.setBytes(i, null)
- else stmt.setBinaryStream(i, bytes.stream(), bytes.length)
- }
- def set_bytes(stmt: PreparedStatement, i: Int, bytes: Option[Bytes]): Unit =
- set_bytes(stmt, i, bytes.orNull)
-
- def set_date(stmt: PreparedStatement, i: Int, date: Date): Unit
- def set_date(stmt: PreparedStatement, i: Int, date: Option[Date]): Unit =
- set_date(stmt, i, date.orNull)
-
-
- /* output */
-
- def bool(rs: ResultSet, column: Column): Boolean = rs.getBoolean(column.name)
- def int(rs: ResultSet, column: Column): Int = rs.getInt(column.name)
- def long(rs: ResultSet, column: Column): Long = rs.getLong(column.name)
- def double(rs: ResultSet, column: Column): Double = rs.getDouble(column.name)
- def string(rs: ResultSet, column: Column): String =
- {
- val s = rs.getString(column.name)
- if (s == null) "" else s
- }
- def bytes(rs: ResultSet, column: Column): Bytes =
- {
- val bs = rs.getBytes(column.name)
- if (bs == null) Bytes.empty else Bytes(bs)
- }
- def date(rs: ResultSet, column: Column): Date
-
- def get[A](rs: ResultSet, column: Column, f: (ResultSet, Column) => A): Option[A] =
- {
- val x = f(rs, column)
- if (rs.wasNull) None else Some(x)
- }
- def get_bool(rs: ResultSet, column: Column): Option[Boolean] = get(rs, column, bool _)
- def get_int(rs: ResultSet, column: Column): Option[Int] = get(rs, column, int _)
- def get_long(rs: ResultSet, column: Column): Option[Long] = get(rs, column, long _)
- def get_double(rs: ResultSet, column: Column): Option[Double] = get(rs, column, double _)
- def get_string(rs: ResultSet, column: Column): Option[String] = get(rs, column, string _)
- def get_bytes(rs: ResultSet, column: Column): Option[Bytes] = get(rs, column, bytes _)
- def get_date(rs: ResultSet, column: Column): Option[Date] = get(rs, column, date _)
+ def insert_permissive(table: Table, sql: Source = ""): Source
/* tables and views */
def tables: List[String] =
- iterator(connection.getMetaData.getTables(null, null, "%", null))(_.getString(3)).toList
+ {
+ val result = new mutable.ListBuffer[String]
+ val rs = connection.getMetaData.getTables(null, null, "%", null)
+ while (rs.next) { result += rs.getString(3) }
+ result.toList
+ }
- def create_table(table: Table, strict: Boolean = false, sql: String = ""): Unit =
+ def create_table(table: Table, strict: Boolean = false, sql: Source = ""): Unit =
using_statement(
table.create(strict, sql_type) + (if (sql == "") "" else " " + sql))(_.execute())
@@ -316,7 +356,7 @@
def create_view(table: Table, strict: Boolean = false): Unit =
{
if (strict || !tables.contains(table.name)) {
- val sql = "CREATE VIEW " + table.ident + " AS " + { table.query; table.body }
+ val sql = "CREATE VIEW " + table + " AS " + { table.query; table.body }
using_statement(sql)(_.execute())
}
}
@@ -348,16 +388,16 @@
{
override def toString: String = name
- def sql_type(T: SQL.Type.Value): String = SQL.sql_type_sqlite(T)
+ def sql_type(T: SQL.Type.Value): SQL.Source = SQL.sql_type_sqlite(T)
- def set_date(stmt: PreparedStatement, i: Int, date: Date): Unit =
- if (date == null) set_string(stmt, i, null: String)
- else set_string(stmt, i, date_format(date))
+ def update_date(stmt: SQL.Statement, i: Int, date: Date): Unit =
+ if (date == null) stmt.string(i) = (null: String)
+ else stmt.string(i) = date_format(date)
- def date(rs: ResultSet, column: SQL.Column): Date =
- date_format.parse(string(rs, column))
+ def date(res: SQL.Result, column: SQL.Column): Date =
+ date_format.parse(res.string(column))
- def insert_permissive(table: SQL.Table, sql: String = ""): String =
+ def insert_permissive(table: SQL.Table, sql: SQL.Source = ""): SQL.Source =
table.insert_cmd("INSERT OR IGNORE", sql = sql)
def rebuild { using_statement("VACUUM")(_.execute()) }
@@ -420,20 +460,20 @@
{
override def toString: String = name
- def sql_type(T: SQL.Type.Value): String = SQL.sql_type_postgresql(T)
+ def sql_type(T: SQL.Type.Value): SQL.Source = SQL.sql_type_postgresql(T)
// see https://jdbc.postgresql.org/documentation/head/8-date-time.html
- def set_date(stmt: PreparedStatement, i: Int, date: Date): Unit =
- if (date == null) stmt.setObject(i, null)
- else stmt.setObject(i, OffsetDateTime.from(date.to_utc.rep))
+ def update_date(stmt: SQL.Statement, i: Int, date: Date): Unit =
+ if (date == null) stmt.rep.setObject(i, null)
+ else stmt.rep.setObject(i, OffsetDateTime.from(date.to_utc.rep))
- def date(rs: ResultSet, column: SQL.Column): Date =
+ def date(res: SQL.Result, column: SQL.Column): Date =
{
- val obj = rs.getObject(column.name, classOf[OffsetDateTime])
+ val obj = res.rep.getObject(column.name, classOf[OffsetDateTime])
if (obj == null) null else Date.instant(obj.toInstant)
}
- def insert_permissive(table: SQL.Table, sql: String = ""): String =
+ def insert_permissive(table: SQL.Table, sql: SQL.Source = ""): SQL.Source =
table.insert_cmd("INSERT",
sql = sql + (if (sql == "") "" else " ") + "ON CONFLICT DO NOTHING")
diff -r 1019be449dbd -r 99f4e4e03030 src/Pure/System/isabelle_tool.scala
--- a/src/Pure/System/isabelle_tool.scala Fri May 05 11:16:13 2017 +0200
+++ b/src/Pure/System/isabelle_tool.scala Sat May 06 20:58:34 2017 +0200
@@ -105,7 +105,7 @@
Build_Docker.isabelle_tool,
Build_JDK.isabelle_tool,
Build_PolyML.isabelle_tool,
- Build_Stats.isabelle_tool,
+ Build_Status.isabelle_tool,
Check_Sources.isabelle_tool,
Doc.isabelle_tool,
Imports.isabelle_tool,
diff -r 1019be449dbd -r 99f4e4e03030 src/Pure/Thy/sessions.scala
--- a/src/Pure/Thy/sessions.scala Fri May 05 11:16:13 2017 +0200
+++ b/src/Pure/Thy/sessions.scala Sat May 06 20:58:34 2017 +0200
@@ -10,7 +10,6 @@
import java.nio.ByteBuffer
import java.nio.channels.FileChannel
import java.nio.file.StandardOpenOption
-import java.sql.PreparedStatement
import scala.collection.SortedSet
import scala.collection.mutable
@@ -766,8 +765,8 @@
db.using_statement(Session_Info.table.select(List(column),
Session_Info.session_name.where_equal(name)))(stmt =>
{
- val rs = stmt.executeQuery
- if (!rs.next) Bytes.empty else db.bytes(rs, column)
+ val res = stmt.execute_query()
+ if (!res.next()) Bytes.empty else res.bytes(column)
})
def read_properties(db: SQL.Database, name: String, column: SQL.Column): List[Properties.T] =
@@ -825,15 +824,15 @@
Session_Info.table.delete(Session_Info.session_name.where_equal(name)))(_.execute)
db.using_statement(Session_Info.table.insert())(stmt =>
{
- db.set_string(stmt, 1, name)
- db.set_bytes(stmt, 2, encode_properties(build_log.session_timing))
- db.set_bytes(stmt, 3, compress_properties(build_log.command_timings))
- db.set_bytes(stmt, 4, compress_properties(build_log.ml_statistics))
- db.set_bytes(stmt, 5, compress_properties(build_log.task_statistics))
- db.set_string(stmt, 6, cat_lines(build.sources))
- db.set_string(stmt, 7, cat_lines(build.input_heaps))
- db.set_string(stmt, 8, build.output_heap getOrElse "")
- db.set_int(stmt, 9, build.return_code)
+ stmt.string(1) = name
+ stmt.bytes(2) = encode_properties(build_log.session_timing)
+ stmt.bytes(3) = compress_properties(build_log.command_timings)
+ stmt.bytes(4) = compress_properties(build_log.ml_statistics)
+ stmt.bytes(5) = compress_properties(build_log.task_statistics)
+ stmt.string(6) = cat_lines(build.sources)
+ stmt.string(7) = cat_lines(build.input_heaps)
+ stmt.string(8) = build.output_heap getOrElse ""
+ stmt.int(9) = build.return_code
stmt.execute()
})
}
@@ -867,15 +866,15 @@
db.using_statement(Session_Info.table.select(Session_Info.build_columns,
Session_Info.session_name.where_equal(name)))(stmt =>
{
- val rs = stmt.executeQuery
- if (!rs.next) None
+ val res = stmt.execute_query()
+ if (!res.next()) None
else {
Some(
Build.Session_Info(
- split_lines(db.string(rs, Session_Info.sources)),
- split_lines(db.string(rs, Session_Info.input_heaps)),
- db.string(rs, Session_Info.output_heap) match { case "" => None case s => Some(s) },
- db.int(rs, Session_Info.return_code)))
+ split_lines(res.string(Session_Info.sources)),
+ split_lines(res.string(Session_Info.input_heaps)),
+ res.string(Session_Info.output_heap) match { case "" => None case s => Some(s) },
+ res.int(Session_Info.return_code)))
}
})
}
diff -r 1019be449dbd -r 99f4e4e03030 src/Pure/build-jars
--- a/src/Pure/build-jars Fri May 05 11:16:13 2017 +0200
+++ b/src/Pure/build-jars Sat May 06 20:58:34 2017 +0200
@@ -17,7 +17,8 @@
Admin/build_log.scala
Admin/build_polyml.scala
Admin/build_release.scala
- Admin/build_stats.scala
+ Admin/build_stats_legacy.scala
+ Admin/build_status.scala
Admin/check_sources.scala
Admin/ci_profile.scala
Admin/isabelle_cronjob.scala