| author | wenzelm | 
| Fri, 11 Sep 2020 13:54:22 +0200 | |
| changeset 72254 | 8c5b8d7999bd | 
| parent 71894 | ab21876c30c1 | 
| child 72375 | e48d93811ed7 | 
| permissions | -rw-r--r-- | 
| 65743 | 1 | /* Title: Pure/Admin/build_status.scala | 
| 63686 | 2 | Author: Makarius | 
| 3 | ||
| 65743 | 4 | Present recent build status information from database. | 
| 63686 | 5 | */ | 
| 6 | ||
| 7 | package isabelle | |
| 8 | ||
| 9 | ||
| 65743 | 10 | object Build_Status | 
| 63686 | 11 | {
 | 
| 65799 | 12 | /* defaults */ | 
| 13 | ||
| 14 |   val default_target_dir = Path.explode("build_status")
 | |
| 15 | val default_image_size = (800, 600) | |
| 16 | val default_history = 30 | |
| 17 | ||
| 18 | def default_profiles: List[Profile] = | |
| 19 | Jenkins.build_status_profiles ::: Isabelle_Cronjob.build_status_profiles | |
| 20 | ||
| 21 | ||
| 65791 | 22 | /* data profiles */ | 
| 23 | ||
| 66880 
486f4af28db9
more thorough treatment of afp_version and afp_pull_date;
 wenzelm parents: 
66254diff
changeset | 24 | sealed case class Profile( | 
| 69693 | 25 | description: String, history: Int = 0, afp: Boolean = false, bulky: Boolean = false, sql: String) | 
| 65791 | 26 |   {
 | 
| 65799 | 27 |     def days(options: Options): Int = options.int("build_log_history") max history
 | 
| 28 | ||
| 29 | def stretch(options: Options): Double = | |
| 30 | (days(options) max default_history min (default_history * 5)).toDouble / default_history | |
| 31 | ||
| 32 | def select(options: Options, columns: List[SQL.Column], only_sessions: Set[String]): SQL.Source = | |
| 65791 | 33 |     {
 | 
| 34 | Build_Log.Data.universal_table.select(columns, distinct = true, | |
| 35 | sql = "WHERE " + | |
| 66880 
486f4af28db9
more thorough treatment of afp_version and afp_pull_date;
 wenzelm parents: 
66254diff
changeset | 36 | Build_Log.Data.pull_date(afp) + " > " + Build_Log.Data.recent_time(days(options)) + | 
| 
486f4af28db9
more thorough treatment of afp_version and afp_pull_date;
 wenzelm parents: 
66254diff
changeset | 37 | " AND " + | 
| 65893 | 38 | SQL.member(Build_Log.Data.status.ident, | 
| 39 | List( | |
| 40 | Build_Log.Session_Status.finished.toString, | |
| 41 | Build_Log.Session_Status.failed.toString)) + | |
| 65804 | 42 | (if (only_sessions.isEmpty) "" | 
| 43 | else " AND " + SQL.member(Build_Log.Data.session_name.ident, only_sessions)) + | |
| 66981 
e76c6cb0d461
proper order for entries from multiple profiles, notably "AFP";
 wenzelm parents: 
66980diff
changeset | 44 | " AND " + SQL.enclose(sql)) | 
| 65791 | 45 | } | 
| 46 | } | |
| 47 | ||
| 48 | ||
| 65785 | 49 | /* build status */ | 
| 50 | ||
| 51 | def build_status(options: Options, | |
| 71726 
a5fda30edae2
clarified signature: more uniform treatment of stopped/interrupted state;
 wenzelm parents: 
70855diff
changeset | 52 | progress: Progress = new Progress, | 
| 65785 | 53 | profiles: List[Profile] = default_profiles, | 
| 54 | only_sessions: Set[String] = Set.empty, | |
| 55 | verbose: Boolean = false, | |
| 56 | target_dir: Path = default_target_dir, | |
| 65854 | 57 | ml_statistics: Boolean = false, | 
| 65785 | 58 | image_size: (Int, Int) = default_image_size) | 
| 59 |   {
 | |
| 67760 
553d9ad7d679
more compact ML_Statistics, to make build_status work with less than 2GB heap;
 wenzelm parents: 
67759diff
changeset | 60 | val ml_statistics_domain = | 
| 69822 
8c587dd44f51
updated to polyml-5.8-20190220 (pre-release of Poly/ML 5.8);
 wenzelm parents: 
69740diff
changeset | 61 | Iterator(ML_Statistics.heap_fields, ML_Statistics.program_fields, ML_Statistics.tasks_fields, | 
| 
8c587dd44f51
updated to polyml-5.8-20190220 (pre-release of Poly/ML 5.8);
 wenzelm parents: 
69740diff
changeset | 62 | ML_Statistics.workers_fields).flatMap(_._2).toSet | 
| 67760 
553d9ad7d679
more compact ML_Statistics, to make build_status work with less than 2GB heap;
 wenzelm parents: 
67759diff
changeset | 63 | |
| 65785 | 64 | val data = | 
| 65 | read_data(options, progress = progress, profiles = profiles, | |
| 67760 
553d9ad7d679
more compact ML_Statistics, to make build_status work with less than 2GB heap;
 wenzelm parents: 
67759diff
changeset | 66 | only_sessions = only_sessions, verbose = verbose, | 
| 
553d9ad7d679
more compact ML_Statistics, to make build_status work with less than 2GB heap;
 wenzelm parents: 
67759diff
changeset | 67 | ml_statistics = ml_statistics, ml_statistics_domain = ml_statistics_domain) | 
| 65785 | 68 | |
| 69 | present_data(data, progress = progress, target_dir = target_dir, image_size = image_size) | |
| 70 | } | |
| 65736 | 71 | |
| 72 | ||
| 65791 | 73 | /* read data */ | 
| 65736 | 74 | |
| 65792 | 75 | sealed case class Data(date: Date, entries: List[Data_Entry]) | 
| 65799 | 76 | sealed case class Data_Entry( | 
| 77 | name: String, hosts: List[String], stretch: Double, sessions: List[Session]) | |
| 65893 | 78 |   {
 | 
| 79 | def failed_sessions: List[Session] = | |
| 80 | sessions.filter(_.head.failed).sortBy(_.name) | |
| 81 | } | |
| 65865 | 82 | sealed case class Session( | 
| 67759 | 83 | name: String, threads: Int, entries: List[Entry], | 
| 84 | ml_statistics: ML_Statistics, ml_statistics_date: Long) | |
| 65754 | 85 |   {
 | 
| 65795 | 86 | require(entries.nonEmpty) | 
| 87 | ||
| 67739 
e512938b853c
clarified date for presentation vs. formal pull_date;
 wenzelm parents: 
67738diff
changeset | 88 | lazy val sorted_entries: List[Entry] = entries.sortBy(entry => - entry.date) | 
| 66981 
e76c6cb0d461
proper order for entries from multiple profiles, notably "AFP";
 wenzelm parents: 
66980diff
changeset | 89 | |
| 67003 | 90 | def head: Entry = sorted_entries.head | 
| 65863 | 91 | def order: Long = - head.timing.elapsed.ms | 
| 65860 | 92 | |
| 67003 | 93 | def finished_entries: List[Entry] = sorted_entries.filter(_.finished) | 
| 67739 
e512938b853c
clarified date for presentation vs. formal pull_date;
 wenzelm parents: 
67738diff
changeset | 94 | def finished_entries_size: Int = finished_entries.map(_.date).toSet.size | 
| 65893 | 95 | |
| 66254 | 96 | def check_timing: Boolean = finished_entries_size >= 3 | 
| 65854 | 97 | def check_heap: Boolean = | 
| 66254 | 98 | finished_entries_size >= 3 && | 
| 65893 | 99 | finished_entries.forall(entry => | 
| 65859 | 100 | entry.maximum_heap > 0 || | 
| 101 | entry.average_heap > 0 || | |
| 65866 | 102 | entry.stored_heap > 0) | 
| 67738 | 103 | |
| 104 | def make_csv: CSV.File = | |
| 105 |     {
 | |
| 106 | val header = | |
| 107 |         List("session_name",
 | |
| 108 | "chapter", | |
| 109 | "pull_date", | |
| 67739 
e512938b853c
clarified date for presentation vs. formal pull_date;
 wenzelm parents: 
67738diff
changeset | 110 | "afp_pull_date", | 
| 67738 | 111 | "isabelle_version", | 
| 112 | "afp_version", | |
| 113 | "timing_elapsed", | |
| 114 | "timing_cpu", | |
| 115 | "timing_gc", | |
| 116 | "ml_timing_elapsed", | |
| 117 | "ml_timing_cpu", | |
| 118 | "ml_timing_gc", | |
| 69832 | 119 | "maximum_code", | 
| 120 | "average_code", | |
| 121 | "maximum_stack", | |
| 122 | "average_stack", | |
| 67738 | 123 | "maximum_heap", | 
| 124 | "average_heap", | |
| 125 | "stored_heap", | |
| 126 | "status") | |
| 127 |       val date_format = Date.Format("uuuu-MM-dd HH:mm:ss")
 | |
| 128 | val records = | |
| 67749 | 129 |         for (entry <- sorted_entries) yield {
 | 
| 67738 | 130 | CSV.Record(name, | 
| 131 | entry.chapter, | |
| 132 | date_format(entry.pull_date), | |
| 67739 
e512938b853c
clarified date for presentation vs. formal pull_date;
 wenzelm parents: 
67738diff
changeset | 133 |             entry.afp_pull_date match { case Some(date) => date_format(date) case None => "" },
 | 
| 67738 | 134 | entry.isabelle_version, | 
| 135 | entry.afp_version, | |
| 136 | entry.timing.elapsed.ms, | |
| 137 | entry.timing.cpu.ms, | |
| 138 | entry.timing.gc.ms, | |
| 139 | entry.ml_timing.elapsed.ms, | |
| 140 | entry.ml_timing.cpu.ms, | |
| 141 | entry.ml_timing.gc.ms, | |
| 69832 | 142 | entry.maximum_code, | 
| 143 | entry.average_code, | |
| 144 | entry.maximum_stack, | |
| 145 | entry.average_stack, | |
| 67738 | 146 | entry.maximum_heap, | 
| 147 | entry.average_heap, | |
| 148 | entry.stored_heap, | |
| 149 | entry.status) | |
| 150 | } | |
| 151 | CSV.File(name, header, records) | |
| 152 | } | |
| 65754 | 153 | } | 
| 65854 | 154 | sealed case class Entry( | 
| 66881 | 155 | chapter: String, | 
| 65854 | 156 | pull_date: Date, | 
| 67739 
e512938b853c
clarified date for presentation vs. formal pull_date;
 wenzelm parents: 
67738diff
changeset | 157 | afp_pull_date: Option[Date], | 
| 65854 | 158 | isabelle_version: String, | 
| 159 | afp_version: String, | |
| 160 | timing: Timing, | |
| 161 | ml_timing: Timing, | |
| 69832 | 162 | maximum_code: Long, | 
| 163 | average_code: Long, | |
| 164 | maximum_stack: Long, | |
| 165 | average_stack: Long, | |
| 65859 | 166 | maximum_heap: Long, | 
| 167 | average_heap: Long, | |
| 65893 | 168 | stored_heap: Long, | 
| 65940 | 169 | status: Build_Log.Session_Status.Value, | 
| 170 | errors: List[String]) | |
| 65893 | 171 |   {
 | 
| 67739 
e512938b853c
clarified date for presentation vs. formal pull_date;
 wenzelm parents: 
67738diff
changeset | 172 | val date: Long = (afp_pull_date getOrElse pull_date).unix_epoch | 
| 
e512938b853c
clarified date for presentation vs. formal pull_date;
 wenzelm parents: 
67738diff
changeset | 173 | |
| 65893 | 174 | def finished: Boolean = status == Build_Log.Session_Status.finished | 
| 175 | def failed: Boolean = status == Build_Log.Session_Status.failed | |
| 65940 | 176 | |
| 177 | def present_errors(name: String): XML.Body = | |
| 66880 
486f4af28db9
more thorough treatment of afp_version and afp_pull_date;
 wenzelm parents: 
66254diff
changeset | 178 |     {
 | 
| 66881 | 179 | if (errors.isEmpty) | 
| 180 | HTML.text(name + print_version(isabelle_version, afp_version, chapter)) | |
| 65940 | 181 |       else {
 | 
| 65944 | 182 | HTML.tooltip_errors(HTML.text(name), errors.map(s => HTML.text(Symbol.decode(s)))) :: | 
| 66881 | 183 | HTML.text(print_version(isabelle_version, afp_version, chapter)) | 
| 65940 | 184 | } | 
| 66880 
486f4af28db9
more thorough treatment of afp_version and afp_pull_date;
 wenzelm parents: 
66254diff
changeset | 185 | } | 
| 65893 | 186 | } | 
| 65736 | 187 | |
| 65865 | 188 | sealed case class Image(name: String, width: Int, height: Int) | 
| 189 |   {
 | |
| 190 | def path: Path = Path.basic(name) | |
| 191 | } | |
| 192 | ||
| 66881 | 193 | def print_version( | 
| 70855 | 194 | isabelle_version: String, afp_version: String = "", chapter: String = AFP.chapter): String = | 
| 66880 
486f4af28db9
more thorough treatment of afp_version and afp_pull_date;
 wenzelm parents: 
66254diff
changeset | 195 |   {
 | 
| 
486f4af28db9
more thorough treatment of afp_version and afp_pull_date;
 wenzelm parents: 
66254diff
changeset | 196 | val body = | 
| 
486f4af28db9
more thorough treatment of afp_version and afp_pull_date;
 wenzelm parents: 
66254diff
changeset | 197 |       proper_string(isabelle_version).map("Isabelle/" + _).toList :::
 | 
| 70855 | 198 |       (if (chapter == AFP.chapter) proper_string(afp_version).map("AFP/" + _) else None).toList
 | 
| 66880 
486f4af28db9
more thorough treatment of afp_version and afp_pull_date;
 wenzelm parents: 
66254diff
changeset | 199 |     if (body.isEmpty) "" else body.mkString(" (", ", ", ")")
 | 
| 
486f4af28db9
more thorough treatment of afp_version and afp_pull_date;
 wenzelm parents: 
66254diff
changeset | 200 | } | 
| 
486f4af28db9
more thorough treatment of afp_version and afp_pull_date;
 wenzelm parents: 
66254diff
changeset | 201 | |
| 65736 | 202 | def read_data(options: Options, | 
| 71726 
a5fda30edae2
clarified signature: more uniform treatment of stopped/interrupted state;
 wenzelm parents: 
70855diff
changeset | 203 | progress: Progress = new Progress, | 
| 65785 | 204 | profiles: List[Profile] = default_profiles, | 
| 65736 | 205 | only_sessions: Set[String] = Set.empty, | 
| 65854 | 206 | ml_statistics: Boolean = false, | 
| 67760 
553d9ad7d679
more compact ML_Statistics, to make build_status work with less than 2GB heap;
 wenzelm parents: 
67759diff
changeset | 207 | ml_statistics_domain: String => Boolean = (key: String) => true, | 
| 65750 | 208 | verbose: Boolean = false): Data = | 
| 65736 | 209 |   {
 | 
| 65754 | 210 | val date = Date.now() | 
| 65792 | 211 | var data_hosts = Map.empty[String, Set[String]] | 
| 65799 | 212 | var data_stretch = Map.empty[String, Double] | 
| 65762 | 213 | var data_entries = Map.empty[String, Map[String, Session]] | 
| 63688 
cc57255bf6ae
gnuplot presentation similar to former isatest-statistics;
 wenzelm parents: 
63686diff
changeset | 214 | |
| 65792 | 215 | def get_hosts(data_name: String): Set[String] = | 
| 216 | data_hosts.getOrElse(data_name, Set.empty) | |
| 217 | ||
| 65736 | 218 | val store = Build_Log.store(options) | 
| 68018 | 219 | |
| 65736 | 220 | using(store.open_database())(db => | 
| 64089 | 221 |     {
 | 
| 65764 | 222 |       for (profile <- profiles.sortBy(_.description)) {
 | 
| 223 |         progress.echo("input " + quote(profile.description))
 | |
| 65854 | 224 | |
| 66880 
486f4af28db9
more thorough treatment of afp_version and afp_pull_date;
 wenzelm parents: 
66254diff
changeset | 225 | val afp = profile.afp | 
| 65736 | 226 | val columns = | 
| 227 | List( | |
| 67739 
e512938b853c
clarified date for presentation vs. formal pull_date;
 wenzelm parents: 
67738diff
changeset | 228 | Build_Log.Data.pull_date(afp = false), | 
| 
e512938b853c
clarified date for presentation vs. formal pull_date;
 wenzelm parents: 
67738diff
changeset | 229 | Build_Log.Data.pull_date(afp = true), | 
| 65792 | 230 | Build_Log.Prop.build_host, | 
| 65796 | 231 | Build_Log.Prop.isabelle_version, | 
| 232 | Build_Log.Prop.afp_version, | |
| 65752 
ed7b5cd3a7f2
more uniform threads value, notably for Pure session;
 wenzelm parents: 
65751diff
changeset | 233 | Build_Log.Settings.ISABELLE_BUILD_OPTIONS, | 
| 65736 | 234 | Build_Log.Settings.ML_PLATFORM, | 
| 235 | Build_Log.Data.session_name, | |
| 66881 | 236 | Build_Log.Data.chapter, | 
| 66980 | 237 | Build_Log.Data.groups, | 
| 65736 | 238 | Build_Log.Data.threads, | 
| 239 | Build_Log.Data.timing_elapsed, | |
| 240 | Build_Log.Data.timing_cpu, | |
| 241 | Build_Log.Data.timing_gc, | |
| 242 | Build_Log.Data.ml_timing_elapsed, | |
| 243 | Build_Log.Data.ml_timing_cpu, | |
| 65769 | 244 | Build_Log.Data.ml_timing_gc, | 
| 65893 | 245 | Build_Log.Data.heap_size, | 
| 65940 | 246 | Build_Log.Data.status, | 
| 247 | Build_Log.Data.errors) ::: | |
| 65854 | 248 | (if (ml_statistics) List(Build_Log.Data.ml_statistics) else Nil) | 
| 63700 | 249 | |
| 65752 
ed7b5cd3a7f2
more uniform threads value, notably for Pure session;
 wenzelm parents: 
65751diff
changeset | 250 | val Threads_Option = """threads\s*=\s*(\d+)""".r | 
| 
ed7b5cd3a7f2
more uniform threads value, notably for Pure session;
 wenzelm parents: 
65751diff
changeset | 251 | |
| 65799 | 252 | val sql = profile.select(options, columns, only_sessions) | 
| 71894 | 253 | progress.echo_if(verbose, sql) | 
| 65750 | 254 | |
| 255 | db.using_statement(sql)(stmt => | |
| 65736 | 256 |         {
 | 
| 65740 | 257 | val res = stmt.execute_query() | 
| 258 |           while (res.next()) {
 | |
| 65766 
2edb89630a80
more specific workaround (see also ed7b5cd3a7f2);
 wenzelm parents: 
65765diff
changeset | 259 | val session_name = res.string(Build_Log.Data.session_name) | 
| 66881 | 260 | val chapter = res.string(Build_Log.Data.chapter) | 
| 66980 | 261 | val groups = split_lines(res.string(Build_Log.Data.groups)) | 
| 65764 | 262 | val threads = | 
| 263 |             {
 | |
| 264 | val threads1 = | |
| 265 |                 res.string(Build_Log.Settings.ISABELLE_BUILD_OPTIONS) match {
 | |
| 65895 
744878d72021
more general workaround for failed sessions (again, see also 2edb89630a80, ed7b5cd3a7f2);
 wenzelm parents: 
65893diff
changeset | 266 | case Threads_Option(Value.Int(i)) => i | 
| 65764 | 267 | case _ => 1 | 
| 268 | } | |
| 269 | val threads2 = res.get_int(Build_Log.Data.threads).getOrElse(1) | |
| 270 | threads1 max threads2 | |
| 271 | } | |
| 65766 
2edb89630a80
more specific workaround (see also ed7b5cd3a7f2);
 wenzelm parents: 
65765diff
changeset | 272 | val ml_platform = res.string(Build_Log.Settings.ML_PLATFORM) | 
| 65762 | 273 | val data_name = | 
| 65764 | 274 | profile.description + | 
| 69734 | 275 |                 (if (ml_platform.startsWith("x86_64-")) ", 64bit" else "") +
 | 
| 65764 | 276 | (if (threads == 1) "" else ", " + threads + " threads") | 
| 63703 | 277 | |
| 65799 | 278 | res.get_string(Build_Log.Prop.build_host).foreach(host => | 
| 279 | data_hosts += (data_name -> (get_hosts(data_name) + host))) | |
| 280 | ||
| 281 | data_stretch += (data_name -> profile.stretch(options)) | |
| 282 | ||
| 65854 | 283 | val isabelle_version = res.string(Build_Log.Prop.isabelle_version) | 
| 66880 
486f4af28db9
more thorough treatment of afp_version and afp_pull_date;
 wenzelm parents: 
66254diff
changeset | 284 | val afp_version = res.string(Build_Log.Prop.afp_version) | 
| 65854 | 285 | |
| 286 | val ml_stats = | |
| 287 | ML_Statistics( | |
| 68018 | 288 |                 if (ml_statistics) {
 | 
| 289 | Properties.uncompress( | |
| 290 | res.bytes(Build_Log.Data.ml_statistics), cache = store.xz_cache) | |
| 291 | } | |
| 66881 | 292 | else Nil, | 
| 67760 
553d9ad7d679
more compact ML_Statistics, to make build_status work with less than 2GB heap;
 wenzelm parents: 
67759diff
changeset | 293 | domain = ml_statistics_domain, | 
| 66881 | 294 | heading = session_name + print_version(isabelle_version, afp_version, chapter)) | 
| 65854 | 295 | |
| 65736 | 296 | val entry = | 
| 65796 | 297 | Entry( | 
| 66881 | 298 | chapter = chapter, | 
| 67739 
e512938b853c
clarified date for presentation vs. formal pull_date;
 wenzelm parents: 
67738diff
changeset | 299 | pull_date = res.date(Build_Log.Data.pull_date(afp = false)), | 
| 
e512938b853c
clarified date for presentation vs. formal pull_date;
 wenzelm parents: 
67738diff
changeset | 300 | afp_pull_date = | 
| 
e512938b853c
clarified date for presentation vs. formal pull_date;
 wenzelm parents: 
67738diff
changeset | 301 | if (afp) res.get_date(Build_Log.Data.pull_date(afp = true)) else None, | 
| 65854 | 302 | isabelle_version = isabelle_version, | 
| 66880 
486f4af28db9
more thorough treatment of afp_version and afp_pull_date;
 wenzelm parents: 
66254diff
changeset | 303 | afp_version = afp_version, | 
| 65796 | 304 | timing = | 
| 305 | res.timing( | |
| 306 | Build_Log.Data.timing_elapsed, | |
| 307 | Build_Log.Data.timing_cpu, | |
| 308 | Build_Log.Data.timing_gc), | |
| 309 | ml_timing = | |
| 310 | res.timing( | |
| 311 | Build_Log.Data.ml_timing_elapsed, | |
| 312 | Build_Log.Data.ml_timing_cpu, | |
| 313 | Build_Log.Data.ml_timing_gc), | |
| 69832 | 314 | maximum_code = ml_stats.maximum(ML_Statistics.CODE_SIZE).toLong, | 
| 315 | average_code = ml_stats.average(ML_Statistics.CODE_SIZE).toLong, | |
| 316 | maximum_stack = ml_stats.maximum(ML_Statistics.STACK_SIZE).toLong, | |
| 317 | average_stack = ml_stats.average(ML_Statistics.STACK_SIZE).toLong, | |
| 318 | maximum_heap = ml_stats.maximum(ML_Statistics.HEAP_SIZE).toLong, | |
| 319 | average_heap = ml_stats.average(ML_Statistics.HEAP_SIZE).toLong, | |
| 320 | stored_heap = ML_Statistics.mem_scale(res.long(Build_Log.Data.heap_size)), | |
| 65940 | 321 | status = Build_Log.Session_Status.withName(res.string(Build_Log.Data.status)), | 
| 68018 | 322 | errors = | 
| 323 | Build_Log.uncompress_errors(res.bytes(Build_Log.Data.errors), | |
| 324 | cache = store.xz_cache)) | |
| 65736 | 325 | |
| 65762 | 326 | val sessions = data_entries.getOrElse(data_name, Map.empty) | 
| 67759 | 327 | val session = | 
| 328 |               sessions.get(session_name) match {
 | |
| 329 | case None => | |
| 330 | Session(session_name, threads, List(entry), ml_stats, entry.date) | |
| 331 | case Some(old) => | |
| 332 | val (ml_stats1, ml_stats1_date) = | |
| 333 | if (entry.date > old.ml_statistics_date) (ml_stats, entry.date) | |
| 334 | else (old.ml_statistics, old.ml_statistics_date) | |
| 335 | Session(session_name, threads, entry :: old.entries, ml_stats1, ml_stats1_date) | |
| 336 | } | |
| 66882 | 337 | |
| 70855 | 338 | if ((!afp || chapter == AFP.chapter) && | 
| 69740 
18d383f41477
proper operation in weakly-typed Scala (amending 06153e2e0cdb);
 wenzelm parents: 
69734diff
changeset | 339 |                 (!profile.bulky || groups.exists(AFP.groups_bulky.toSet))) {
 | 
| 66882 | 340 | data_entries += (data_name -> (sessions + (session_name -> session))) | 
| 341 | } | |
| 65736 | 342 | } | 
| 343 | }) | |
| 344 | } | |
| 345 | }) | |
| 346 | ||
| 65792 | 347 | val sorted_entries = | 
| 65762 | 348 |       (for {
 | 
| 65792 | 349 | (name, sessions) <- data_entries.toList | 
| 67003 | 350 | sorted_sessions <- proper_list(sessions.toList.map(_._2).sortBy(_.order)) | 
| 65799 | 351 | } | 
| 352 |       yield {
 | |
| 353 | val hosts = get_hosts(name).toList.sorted | |
| 354 | val stretch = data_stretch(name) | |
| 355 | Data_Entry(name, hosts, stretch, sorted_sessions) | |
| 356 | }).sortBy(_.name) | |
| 65792 | 357 | |
| 358 | Data(date, sorted_entries) | |
| 65736 | 359 | } | 
| 360 | ||
| 361 | ||
| 362 | /* present data */ | |
| 363 | ||
| 364 | def present_data(data: Data, | |
| 71726 
a5fda30edae2
clarified signature: more uniform treatment of stopped/interrupted state;
 wenzelm parents: 
70855diff
changeset | 365 | progress: Progress = new Progress, | 
| 65736 | 366 | target_dir: Path = default_target_dir, | 
| 65759 | 367 | image_size: (Int, Int) = default_image_size) | 
| 65736 | 368 |   {
 | 
| 65791 | 369 | def clean_name(name: String): String = | 
| 370 | name.flatMap(c => if (c == ' ' || c == '/') "_" else if (c == ',') "" else c.toString) | |
| 371 | ||
| 65838 | 372 | HTML.write_document(target_dir, "index.html", | 
| 65945 | 373 |       List(HTML.title("Isabelle build status")),
 | 
| 65838 | 374 |       List(HTML.chapter("Isabelle build status"),
 | 
| 375 | HTML.par( | |
| 376 | List(HTML.description( | |
| 377 |             List(HTML.text("status date:") -> HTML.text(data.date.toString))))),
 | |
| 378 | HTML.par( | |
| 379 |           List(HTML.itemize(data.entries.map({ case data_entry =>
 | |
| 65893 | 380 | List( | 
| 381 | HTML.link(clean_name(data_entry.name) + "/index.html", | |
| 382 | HTML.text(data_entry.name))) ::: | |
| 383 |             (data_entry.failed_sessions match {
 | |
| 384 | case Nil => Nil | |
| 385 | case sessions => | |
| 65940 | 386 | HTML.break ::: | 
| 65992 | 387 |                 List(HTML.span(HTML.error_message, HTML.text("Failed sessions:"))) :::
 | 
| 65940 | 388 | List(HTML.itemize(sessions.map(s => s.head.present_errors(s.name)))) | 
| 65893 | 389 | }) | 
| 390 | })))))) | |
| 65786 | 391 | |
| 65792 | 392 |     for (data_entry <- data.entries) {
 | 
| 393 | val data_name = data_entry.name | |
| 394 | ||
| 65865 | 395 | val (image_width, image_height) = image_size | 
| 396 | val image_width_stretch = (image_width * data_entry.stretch).toInt | |
| 65799 | 397 | |
| 65786 | 398 |       progress.echo("output " + quote(data_name))
 | 
| 399 | ||
| 65764 | 400 | val dir = target_dir + Path.basic(clean_name(data_name)) | 
| 65736 | 401 | Isabelle_System.mkdirs(dir) | 
| 402 | ||
| 67738 | 403 | val data_files = | 
| 404 |         (for (session <- data_entry.sessions) yield {
 | |
| 405 | val csv_file = session.make_csv | |
| 406 | csv_file.write(dir) | |
| 407 | session.name -> csv_file | |
| 408 | }).toMap | |
| 409 | ||
| 65764 | 410 | val session_plots = | 
| 411 | Par_List.map((session: Session) => | |
| 412 |           Isabelle_System.with_tmp_file(session.name, "data") { data_file =>
 | |
| 413 |             Isabelle_System.with_tmp_file(session.name, "gnuplot") { gnuplot_file =>
 | |
| 63706 | 414 | |
| 65865 | 415 | def plot_name(kind: String): String = session.name + "_" + kind + ".png" | 
| 416 | ||
| 65764 | 417 | File.write(data_file, | 
| 418 | cat_lines( | |
| 65893 | 419 | session.finished_entries.map(entry => | 
| 67739 
e512938b853c
clarified date for presentation vs. formal pull_date;
 wenzelm parents: 
67738diff
changeset | 420 | List(entry.date, | 
| 65764 | 421 | entry.timing.elapsed.minutes, | 
| 422 | entry.timing.resources.minutes, | |
| 423 | entry.ml_timing.elapsed.minutes, | |
| 65769 | 424 | entry.ml_timing.resources.minutes, | 
| 69832 | 425 | entry.maximum_code, | 
| 426 | entry.maximum_code, | |
| 427 | entry.average_stack, | |
| 428 | entry.maximum_stack, | |
| 429 | entry.average_heap, | |
| 65866 | 430 | entry.average_heap, | 
| 431 |                       entry.stored_heap).mkString(" "))))
 | |
| 65736 | 432 | |
| 65764 | 433 | val max_time = | 
| 65893 | 434 |                 ((0.0 /: session.finished_entries){ case (m, entry) =>
 | 
| 65764 | 435 | m.max(entry.timing.elapsed.minutes). | 
| 436 | max(entry.timing.resources.minutes). | |
| 437 | max(entry.ml_timing.elapsed.minutes). | |
| 438 | max(entry.ml_timing.resources.minutes) } max 0.1) * 1.1 | |
| 65769 | 439 | val timing_range = "[0:" + max_time + "]" | 
| 65763 | 440 | |
| 65865 | 441 | def gnuplot(plot_name: String, plots: List[String], range: String): Image = | 
| 65764 | 442 |               {
 | 
| 65865 | 443 | val image = Image(plot_name, image_width_stretch, image_height) | 
| 65764 | 444 | |
| 445 | File.write(gnuplot_file, """ | |
| 65865 | 446 | set terminal png size """ + image.width + "," + image.height + """ | 
| 447 | set output """ + quote(File.standard_path(dir + image.path)) + """ | |
| 65759 | 448 | set xdata time | 
| 449 | set timefmt "%s" | |
| 450 | set format x "%d-%b" | |
| 65762 | 451 | set xlabel """ + quote(session.name) + """ noenhanced | 
| 65763 | 452 | set key left bottom | 
| 65769 | 453 | plot [] """ + range + " " + | 
| 454 |                 plots.map(s => quote(data_file.implode) + " " + s).mkString(", ") + "\n")
 | |
| 65759 | 455 | |
| 65764 | 456 | val result = | 
| 457 |                   Isabelle_System.bash("\"$ISABELLE_GNUPLOT\" " + File.bash_path(gnuplot_file))
 | |
| 458 | if (!result.ok) | |
| 459 |                   result.error("Gnuplot failed for " + data_name + "/" + plot_name).check
 | |
| 460 | ||
| 65865 | 461 | image | 
| 65764 | 462 | } | 
| 65759 | 463 | |
| 65765 | 464 | val timing_plots = | 
| 65769 | 465 |               {
 | 
| 466 | val plots1 = | |
| 467 | List( | |
| 468 | """ using 1:2 smooth sbezier title "elapsed time (smooth)" """, | |
| 469 | """ using 1:2 smooth csplines title "elapsed time" """) | |
| 470 | val plots2 = | |
| 471 | List( | |
| 472 | """ using 1:3 smooth sbezier title "cpu time (smooth)" """, | |
| 473 | """ using 1:3 smooth csplines title "cpu time" """) | |
| 474 | if (session.threads == 1) plots1 else plots1 ::: plots2 | |
| 475 | } | |
| 65765 | 476 | |
| 65764 | 477 | val ml_timing_plots = | 
| 478 | List( | |
| 65765 | 479 | """ using 1:4 smooth sbezier title "ML elapsed time (smooth)" """, | 
| 480 | """ using 1:4 smooth csplines title "ML elapsed time" """, | |
| 65764 | 481 | """ using 1:5 smooth sbezier title "ML cpu time (smooth)" """, | 
| 65765 | 482 | """ using 1:5 smooth csplines title "ML cpu time" """) | 
| 65736 | 483 | |
| 65769 | 484 | val heap_plots = | 
| 485 | List( | |
| 69894 | 486 | """ using 1:10 smooth sbezier title "heap maximum (smooth)" """, | 
| 487 | """ using 1:10 smooth csplines title "heap maximum" """, | |
| 488 | """ using 1:11 smooth sbezier title "heap average (smooth)" """, | |
| 489 | """ using 1:11 smooth csplines title "heap average" """, | |
| 490 | """ using 1:12 smooth sbezier title "heap stored (smooth)" """, | |
| 491 | """ using 1:12 smooth csplines title "heap stored" """) | |
| 65769 | 492 | |
| 65865 | 493 | def jfreechart(plot_name: String, fields: ML_Statistics.Fields): Image = | 
| 494 |               {
 | |
| 495 | val image = Image(plot_name, image_width, image_height) | |
| 496 | val chart = | |
| 497 | session.ml_statistics.chart( | |
| 498 | fields._1 + ": " + session.ml_statistics.heading, fields._2) | |
| 499 | Graphics_File.write_chart_png( | |
| 500 | (dir + image.path).file, chart, image.width, image.height) | |
| 501 | image | |
| 502 | } | |
| 503 | ||
| 504 | val images = | |
| 65847 
ad35427dbe88
less restrictive filter: omit empty charts, but show latest timing;
 wenzelm parents: 
65838diff
changeset | 505 | (if (session.check_timing) | 
| 
ad35427dbe88
less restrictive filter: omit empty charts, but show latest timing;
 wenzelm parents: 
65838diff
changeset | 506 | List( | 
| 65865 | 507 |                     gnuplot(plot_name("timing"), timing_plots, timing_range),
 | 
| 508 |                     gnuplot(plot_name("ml_timing"), ml_timing_plots, timing_range))
 | |
| 509 | else Nil) ::: | |
| 510 | (if (session.check_heap) | |
| 511 |                   List(gnuplot(plot_name("heap"), heap_plots, "[0:]"))
 | |
| 65847 
ad35427dbe88
less restrictive filter: omit empty charts, but show latest timing;
 wenzelm parents: 
65838diff
changeset | 512 | else Nil) ::: | 
| 65865 | 513 | (if (session.ml_statistics.content.nonEmpty) | 
| 69822 
8c587dd44f51
updated to polyml-5.8-20190220 (pre-release of Poly/ML 5.8);
 wenzelm parents: 
69740diff
changeset | 514 |                   List(jfreechart(plot_name("heap_chart"), ML_Statistics.heap_fields),
 | 
| 
8c587dd44f51
updated to polyml-5.8-20190220 (pre-release of Poly/ML 5.8);
 wenzelm parents: 
69740diff
changeset | 515 |                     jfreechart(plot_name("program_chart"), ML_Statistics.program_fields)) :::
 | 
| 65865 | 516 | (if (session.threads > 1) | 
| 65867 | 517 | List( | 
| 518 |                       jfreechart(plot_name("tasks_chart"), ML_Statistics.tasks_fields),
 | |
| 519 |                       jfreechart(plot_name("workers_chart"), ML_Statistics.workers_fields))
 | |
| 65865 | 520 | else Nil) | 
| 521 | else Nil) | |
| 65769 | 522 | |
| 65865 | 523 | session.name -> images | 
| 65764 | 524 | } | 
| 65792 | 525 | }, data_entry.sessions).toMap | 
| 65754 | 526 | |
| 65838 | 527 | HTML.write_document(dir, "index.html", | 
| 528 |         List(HTML.title("Isabelle build status for " + data_name)),
 | |
| 529 |         HTML.chapter("Isabelle build status for " + data_name) ::
 | |
| 530 | HTML.par( | |
| 531 | List(HTML.description( | |
| 65754 | 532 | List( | 
| 65838 | 533 |               HTML.text("status date:") -> HTML.text(data.date.toString),
 | 
| 534 |               HTML.text("build host:") -> HTML.text(commas(data_entry.hosts)))))) ::
 | |
| 535 | HTML.par( | |
| 536 | List(HTML.itemize( | |
| 537 | data_entry.sessions.map(session => | |
| 538 |               HTML.link("#session_" + session.name, HTML.text(session.name)) ::
 | |
| 65863 | 539 |               HTML.text(" (" + session.head.timing.message_resources + ")"))))) ::
 | 
| 65838 | 540 | data_entry.sessions.flatMap(session => | 
| 541 | List( | |
| 65992 | 542 |             HTML.section(HTML.id("session_" + session.name), session.name),
 | 
| 65838 | 543 | HTML.par( | 
| 544 | HTML.description( | |
| 545 | List( | |
| 67738 | 546 |                   HTML.text("data:") ->
 | 
| 547 |                     List(HTML.link(data_files(session.name).file_name, HTML.text("CSV"))),
 | |
| 65863 | 548 |                   HTML.text("timing:") -> HTML.text(session.head.timing.message_resources),
 | 
| 549 |                   HTML.text("ML timing:") -> HTML.text(session.head.ml_timing.message_resources)) :::
 | |
| 69832 | 550 | ML_Statistics.mem_print(session.head.maximum_code).map(s => | 
| 69833 | 551 |                   HTML.text("code maximum:") -> HTML.text(s)).toList :::
 | 
| 69832 | 552 | ML_Statistics.mem_print(session.head.average_code).map(s => | 
| 69833 | 553 |                   HTML.text("code average:") -> HTML.text(s)).toList :::
 | 
| 69832 | 554 | ML_Statistics.mem_print(session.head.maximum_stack).map(s => | 
| 69833 | 555 |                   HTML.text("stack maximum:") -> HTML.text(s)).toList :::
 | 
| 69832 | 556 | ML_Statistics.mem_print(session.head.average_stack).map(s => | 
| 69833 | 557 |                   HTML.text("stack average:") -> HTML.text(s)).toList :::
 | 
| 69832 | 558 | ML_Statistics.mem_print(session.head.maximum_heap).map(s => | 
| 69833 | 559 |                   HTML.text("heap maximum:") -> HTML.text(s)).toList :::
 | 
| 69832 | 560 | ML_Statistics.mem_print(session.head.average_heap).map(s => | 
| 69833 | 561 |                   HTML.text("heap average:") -> HTML.text(s)).toList :::
 | 
| 69832 | 562 | ML_Statistics.mem_print(session.head.stored_heap).map(s => | 
| 69833 | 563 |                   HTML.text("heap stored:") -> HTML.text(s)).toList :::
 | 
| 65863 | 564 | proper_string(session.head.isabelle_version).map(s => | 
| 65838 | 565 |                   HTML.text("Isabelle version:") -> HTML.text(s)).toList :::
 | 
| 65863 | 566 | proper_string(session.head.afp_version).map(s => | 
| 65838 | 567 |                   HTML.text("AFP version:") -> HTML.text(s)).toList) ::
 | 
| 65865 | 568 | session_plots.getOrElse(session.name, Nil).map(image => | 
| 65946 | 569 | HTML.size(image.width / 2, image.height / 2)(HTML.image(image.name))))))) | 
| 63688 
cc57255bf6ae
gnuplot presentation similar to former isatest-statistics;
 wenzelm parents: 
63686diff
changeset | 570 | } | 
| 
cc57255bf6ae
gnuplot presentation similar to former isatest-statistics;
 wenzelm parents: 
63686diff
changeset | 571 | } | 
| 
cc57255bf6ae
gnuplot presentation similar to former isatest-statistics;
 wenzelm parents: 
63686diff
changeset | 572 | |
| 
cc57255bf6ae
gnuplot presentation similar to former isatest-statistics;
 wenzelm parents: 
63686diff
changeset | 573 | |
| 
cc57255bf6ae
gnuplot presentation similar to former isatest-statistics;
 wenzelm parents: 
63686diff
changeset | 574 | /* Isabelle tool wrapper */ | 
| 
cc57255bf6ae
gnuplot presentation similar to former isatest-statistics;
 wenzelm parents: 
63686diff
changeset | 575 | |
| 
cc57255bf6ae
gnuplot presentation similar to former isatest-statistics;
 wenzelm parents: 
63686diff
changeset | 576 | val isabelle_tool = | 
| 65743 | 577 |     Isabelle_Tool("build_status", "present recent build status information from database", args =>
 | 
| 63688 
cc57255bf6ae
gnuplot presentation similar to former isatest-statistics;
 wenzelm parents: 
63686diff
changeset | 578 |     {
 | 
| 65736 | 579 | var target_dir = default_target_dir | 
| 65854 | 580 | var ml_statistics = false | 
| 65736 | 581 | var only_sessions = Set.empty[String] | 
| 582 | var options = Options.init() | |
| 583 | var image_size = default_image_size | |
| 65750 | 584 | var verbose = false | 
| 63688 
cc57255bf6ae
gnuplot presentation similar to former isatest-statistics;
 wenzelm parents: 
63686diff
changeset | 585 | |
| 
cc57255bf6ae
gnuplot presentation similar to former isatest-statistics;
 wenzelm parents: 
63686diff
changeset | 586 |       val getopts = Getopts("""
 | 
| 65743 | 587 | Usage: isabelle build_status [OPTIONS] | 
| 63688 
cc57255bf6ae
gnuplot presentation similar to former isatest-statistics;
 wenzelm parents: 
63686diff
changeset | 588 | |
| 
cc57255bf6ae
gnuplot presentation similar to former isatest-statistics;
 wenzelm parents: 
63686diff
changeset | 589 | Options are: | 
| 65737 | 590 | -D DIR target directory (default """ + default_target_dir + """) | 
| 65854 | 591 | -M include full ML statistics | 
| 63700 | 592 | -S SESSIONS only given SESSIONS (comma separated) | 
| 67002 | 593 |     -l DAYS      length of relevant history (default """ + options.int("build_log_history") + """)
 | 
| 65736 | 594 | -o OPTION override Isabelle system OPTION (via NAME=VAL or NAME) | 
| 65737 | 595 | -s WxH size of PNG image (default """ + image_size._1 + "x" + image_size._2 + """) | 
| 65750 | 596 | -v verbose | 
| 63688 
cc57255bf6ae
gnuplot presentation similar to former isatest-statistics;
 wenzelm parents: 
63686diff
changeset | 597 | |
| 65736 | 598 | Present performance statistics from build log database, which is specified | 
| 65782 | 599 | via system options build_log_database_host, build_log_database_user, | 
| 600 | build_log_history etc. | |
| 63688 
cc57255bf6ae
gnuplot presentation similar to former isatest-statistics;
 wenzelm parents: 
63686diff
changeset | 601 | """, | 
| 
cc57255bf6ae
gnuplot presentation similar to former isatest-statistics;
 wenzelm parents: 
63686diff
changeset | 602 | "D:" -> (arg => target_dir = Path.explode(arg)), | 
| 65854 | 603 | "M" -> (_ => ml_statistics = true), | 
| 63700 | 604 |         "S:" -> (arg => only_sessions = space_explode(',', arg).toSet),
 | 
| 67002 | 605 |         "l:" -> (arg => options = options + ("build_log_history=" + arg)),
 | 
| 65736 | 606 | "o:" -> (arg => options = options + arg), | 
| 63700 | 607 | "s:" -> (arg => | 
| 63805 | 608 |           space_explode('x', arg).map(Value.Int.parse(_)) match {
 | 
| 65736 | 609 | case List(w, h) if w > 0 && h > 0 => image_size = (w, h) | 
| 63700 | 610 |             case _ => error("Error bad PNG image size: " + quote(arg))
 | 
| 65750 | 611 | }), | 
| 612 | "v" -> (_ => verbose = true)) | |
| 63688 
cc57255bf6ae
gnuplot presentation similar to former isatest-statistics;
 wenzelm parents: 
63686diff
changeset | 613 | |
| 65736 | 614 | val more_args = getopts(args) | 
| 615 | if (more_args.nonEmpty) getopts.usage() | |
| 63700 | 616 | |
| 65736 | 617 | val progress = new Console_Progress | 
| 63688 
cc57255bf6ae
gnuplot presentation similar to former isatest-statistics;
 wenzelm parents: 
63686diff
changeset | 618 | |
| 65785 | 619 | build_status(options, progress = progress, only_sessions = only_sessions, verbose = verbose, | 
| 65854 | 620 | target_dir = target_dir, ml_statistics = ml_statistics, image_size = image_size) | 
| 69277 
258bef08b31e
support for user-defined Isabelle/Scala command-line tools;
 wenzelm parents: 
68018diff
changeset | 621 | }) | 
| 63686 | 622 | } |