gnuplot presentation similar to former isatest-statistics;
authorwenzelm
Sat Aug 13 23:45:29 2016 +0200 (2016-08-13)
changeset 63688cc57255bf6ae
parent 63687 1134ee401b20
child 63689 61171cbeedde
gnuplot presentation similar to former isatest-statistics;
etc/settings
src/Pure/General/time.scala
src/Pure/System/isabelle_tool.scala
src/Pure/Tools/build_stats.scala
     1.1 --- a/etc/settings	Sat Aug 13 23:33:58 2016 +0200
     1.2 +++ b/etc/settings	Sat Aug 13 23:45:29 2016 +0200
     1.3 @@ -132,6 +132,8 @@
     1.4  ### Misc settings
     1.5  ###
     1.6  
     1.7 +ISABELLE_GNUPLOT="gnuplot"
     1.8 +
     1.9  #ISABELLE_GHC="/usr/bin/ghc"
    1.10  #ISABELLE_OCAML="/usr/bin/ocaml"
    1.11  #ISABELLE_SWIPL="/usr/bin/swipl"
     2.1 --- a/src/Pure/General/time.scala	Sat Aug 13 23:33:58 2016 +0200
     2.2 +++ b/src/Pure/General/time.scala	Sat Aug 13 23:45:29 2016 +0200
     2.3 @@ -29,6 +29,7 @@
     2.4  final class Time private(val ms: Long) extends AnyVal
     2.5  {
     2.6    def seconds: Double = ms / 1000.0
     2.7 +  def minutes: Double = ms / 60000.0
     2.8  
     2.9    def + (t: Time): Time = new Time(ms + t.ms)
    2.10    def - (t: Time): Time = new Time(ms - t.ms)
     3.1 --- a/src/Pure/System/isabelle_tool.scala	Sat Aug 13 23:33:58 2016 +0200
     3.2 +++ b/src/Pure/System/isabelle_tool.scala	Sat Aug 13 23:45:29 2016 +0200
     3.3 @@ -101,6 +101,7 @@
     3.4      List(
     3.5        Build.isabelle_tool,
     3.6        Build_Doc.isabelle_tool,
     3.7 +      Build_Stats.isabelle_tool,
     3.8        Check_Sources.isabelle_tool,
     3.9        Doc.isabelle_tool,
    3.10        ML_Process.isabelle_tool,
     4.1 --- a/src/Pure/Tools/build_stats.scala	Sat Aug 13 23:33:58 2016 +0200
     4.2 +++ b/src/Pure/Tools/build_stats.scala	Sat Aug 13 23:45:29 2016 +0200
     4.3 @@ -13,6 +13,8 @@
     4.4  
     4.5  object Build_Stats
     4.6  {
     4.7 +  /* parse build output */
     4.8 +
     4.9    private val Session_Finished =
    4.10      new Regex("""^Finished (\S+) \((\d+):(\d+):(\d+) elapsed time, (\d+):(\d+):(\d+) cpu time.*$""")
    4.11    private val Session_Timing =
    4.12 @@ -63,6 +65,108 @@
    4.13  
    4.14      Build_Stats(ml_options.toList, finished, timing, threads)
    4.15    }
    4.16 +
    4.17 +
    4.18 +  /* presentation */
    4.19 +
    4.20 +  def present(job: String, history_length: Int, target_dir: Path)
    4.21 +  {
    4.22 +    val build_infos = CI_API.build_job_builds(job).sortBy(_.timestamp).reverse.take(history_length)
    4.23 +    if (build_infos.isEmpty) error("No build infos for job " + quote(job))
    4.24 +
    4.25 +    val dir = target_dir + Path.basic(job)
    4.26 +    Isabelle_System.mkdirs(dir)
    4.27 +
    4.28 +    val all_build_stats =
    4.29 +      Par_List.map((info: CI_API.Build_Info) =>
    4.30 +        (info.timestamp / 1000, parse(Url.read(info.output))), build_infos)
    4.31 +    val all_sessions =
    4.32 +      (Set.empty[String] /: all_build_stats)(
    4.33 +        { case (s, (_, stats)) => s ++ stats.sessions })
    4.34 +
    4.35 +    for {
    4.36 +      session <- all_sessions
    4.37 +      if all_build_stats.filter({ case (_, stats) => stats.finished.isDefinedAt(session) }).length > 3
    4.38 +    } {
    4.39 +      Isabelle_System.with_tmp_file(session, "png") { data_file =>
    4.40 +        Isabelle_System.with_tmp_file(session, "gnuplot") { plot_file =>
    4.41 +          val data_file_name = File.standard_path(data_file.getAbsolutePath)
    4.42 +          val data =
    4.43 +            for { (t, stats) <- all_build_stats if stats.finished.isDefinedAt(session) }
    4.44 +            yield {
    4.45 +              val finished = stats.finished(session)
    4.46 +              t.toString + " " + finished.cpu.minutes + " " + finished.elapsed.minutes
    4.47 +            }
    4.48 +          File.write(data_file, cat_lines(data))
    4.49 +
    4.50 +          File.write(plot_file, """
    4.51 +set terminal png size 1024,768
    4.52 +set output """ + quote(File.standard_path(dir + Path.basic(session + ".png"))) + """
    4.53 +set xdata time
    4.54 +set timefmt "%s"
    4.55 +set format x "%d-%b"
    4.56 +set xlabel """ + quote(session) + """
    4.57 +set key left top
    4.58 +plot [] [0:] """ +
    4.59 +  quote(data_file_name) + """ using 1:2 smooth sbezier title "interpolated cpu time",""" +
    4.60 +  quote(data_file_name) + """ using 1:2 smooth csplines title "cpu time", """ +
    4.61 +  quote(data_file_name) + """ using 1:3 smooth sbezier title "interpolated elapsed time",""" +
    4.62 +  quote(data_file_name) + """ using 1:3 smooth csplines title "elapsed time"
    4.63 +""")
    4.64 +          val result = Isabelle_System.bash("\"$ISABELLE_GNUPLOT\" " + File.bash_path(plot_file))
    4.65 +          if (result.rc != 0) {
    4.66 +            Output.error_message("Session " + session + ": gnuplot error")
    4.67 +            result.print
    4.68 +          }
    4.69 +        }
    4.70 +      }
    4.71 +    }
    4.72 +  }
    4.73 +
    4.74 +
    4.75 +  /* Isabelle tool wrapper */
    4.76 +
    4.77 +  val isabelle_tool =
    4.78 +    Isabelle_Tool("build_stats", "present statistics from session build output", args =>
    4.79 +    {
    4.80 +      var target_dir = Path.explode("stats")
    4.81 +      var only_jobs = Set.empty[String]
    4.82 +      var history_length = 100
    4.83 +
    4.84 +      val getopts = Getopts("""
    4.85 +Usage: isabelle build_stats [OPTIONS]
    4.86 +
    4.87 +  Options are:
    4.88 +    -D DIR       target directory (default "stats")
    4.89 +    -J JOB       select named JOB (default: all jobs, multiple -J options possible)
    4.90 +    -l LENGTH    length of history (default 100)
    4.91 +
    4.92 +  Present statistics from session build output, from Jenkins continuous build
    4.93 +  service specified as URL via ISABELLE_JENKINS_ROOT.
    4.94 +""",
    4.95 +        "D:" -> (arg => target_dir = Path.explode(arg)),
    4.96 +        "J:" -> (arg => only_jobs += arg),
    4.97 +        "l:" -> (arg => history_length = Properties.Value.Int.parse(arg)))
    4.98 +
    4.99 +      val more_args = getopts(args)
   4.100 +      if (!more_args.isEmpty) getopts.usage()
   4.101 +
   4.102 +      val all_jobs = CI_API.build_jobs()
   4.103 +      val jobs =
   4.104 +        if (only_jobs.isEmpty) all_jobs
   4.105 +        else {
   4.106 +          val bad = (only_jobs -- all_jobs).toList.sorted
   4.107 +          if (bad.isEmpty) only_jobs.toList
   4.108 +          else
   4.109 +            error("Unknown build jobs: " + commas_quote(bad) +
   4.110 +              "\nPossible jobs: " + commas_quote(all_jobs.sorted))
   4.111 +        }
   4.112 +
   4.113 +      for (job <- jobs) {
   4.114 +        Output.writeln((target_dir + Path.basic(job)).implode)
   4.115 +        present(job, history_length, target_dir)
   4.116 +      }
   4.117 +    })
   4.118  }
   4.119  
   4.120  sealed case class Build_Stats(