clarified modules;
authorwenzelm
Wed May 03 14:55:34 2017 +0200 (2017-05-03)
changeset 65694b82f2990161a
parent 65693 99676834e53c
child 65695 4edac706bc5e
clarified modules;
src/Pure/Admin/build_log.scala
src/Pure/Admin/isabelle_cronjob.scala
     1.1 --- a/src/Pure/Admin/build_log.scala	Wed May 03 13:54:22 2017 +0200
     1.2 +++ b/src/Pure/Admin/build_log.scala	Wed May 03 14:55:34 2017 +0200
     1.3 @@ -294,10 +294,6 @@
     1.4    object Meta_Info
     1.5    {
     1.6      val empty: Meta_Info = Meta_Info(Nil, Nil)
     1.7 -
     1.8 -    val log_name = SQL.Column.string("log_name", primary_key = true)
     1.9 -    val table =
    1.10 -      SQL.Table("isabelle_build_log_meta_info", log_name :: Prop.all_props ::: Settings.all_settings)
    1.11    }
    1.12  
    1.13    sealed case class Meta_Info(props: Properties.T, settings: Settings.T)
    1.14 @@ -477,34 +473,6 @@
    1.15      def finished: Boolean = status == Some(Session_Status.finished)
    1.16    }
    1.17  
    1.18 -  object Build_Info
    1.19 -  {
    1.20 -    val session_name = SQL.Column.string("session_name", primary_key = true)
    1.21 -    val chapter = SQL.Column.string("chapter")
    1.22 -    val groups = SQL.Column.string("groups")
    1.23 -    val threads = SQL.Column.int("threads")
    1.24 -    val timing_elapsed = SQL.Column.long("timing_elapsed")
    1.25 -    val timing_cpu = SQL.Column.long("timing_cpu")
    1.26 -    val timing_gc = SQL.Column.long("timing_gc")
    1.27 -    val timing_factor = SQL.Column.double("timing_factor")
    1.28 -    val ml_timing_elapsed = SQL.Column.long("ml_timing_elapsed")
    1.29 -    val ml_timing_cpu = SQL.Column.long("ml_timing_cpu")
    1.30 -    val ml_timing_gc = SQL.Column.long("ml_timing_gc")
    1.31 -    val ml_timing_factor = SQL.Column.double("ml_timing_factor")
    1.32 -    val heap_size = SQL.Column.long("heap_size")
    1.33 -    val status = SQL.Column.string("status")
    1.34 -    val ml_statistics = SQL.Column.bytes("ml_statistics")
    1.35 -
    1.36 -    val sessions_table =
    1.37 -      SQL.Table("isabelle_build_log_sessions",
    1.38 -        List(Meta_Info.log_name, session_name, chapter, groups, threads, timing_elapsed, timing_cpu,
    1.39 -          timing_gc, timing_factor, ml_timing_elapsed, ml_timing_cpu, ml_timing_gc, ml_timing_factor,
    1.40 -          heap_size, status))
    1.41 -    val ml_statistics_table =
    1.42 -      SQL.Table("isabelle_build_log_ml_statistics",
    1.43 -        List(Meta_Info.log_name, session_name, ml_statistics))
    1.44 -  }
    1.45 -
    1.46    sealed case class Build_Info(sessions: Map[String, Session_Entry])
    1.47    {
    1.48      def session(name: String): Session_Entry = sessions(name)
    1.49 @@ -665,6 +633,90 @@
    1.50  
    1.51    /** persistent store **/
    1.52  
    1.53 +  /* SQL data model */
    1.54 +
    1.55 +  object Data
    1.56 +  {
    1.57 +    /* main content */
    1.58 +
    1.59 +    val log_name = SQL.Column.string("log_name", primary_key = true)
    1.60 +    val session_name = SQL.Column.string("session_name", primary_key = true)
    1.61 +    val chapter = SQL.Column.string("chapter")
    1.62 +    val groups = SQL.Column.string("groups")
    1.63 +    val threads = SQL.Column.int("threads")
    1.64 +    val timing_elapsed = SQL.Column.long("timing_elapsed")
    1.65 +    val timing_cpu = SQL.Column.long("timing_cpu")
    1.66 +    val timing_gc = SQL.Column.long("timing_gc")
    1.67 +    val timing_factor = SQL.Column.double("timing_factor")
    1.68 +    val ml_timing_elapsed = SQL.Column.long("ml_timing_elapsed")
    1.69 +    val ml_timing_cpu = SQL.Column.long("ml_timing_cpu")
    1.70 +    val ml_timing_gc = SQL.Column.long("ml_timing_gc")
    1.71 +    val ml_timing_factor = SQL.Column.double("ml_timing_factor")
    1.72 +    val heap_size = SQL.Column.long("heap_size")
    1.73 +    val status = SQL.Column.string("status")
    1.74 +    val ml_statistics = SQL.Column.bytes("ml_statistics")
    1.75 +
    1.76 +    val meta_info_table =
    1.77 +      SQL.Table("isabelle_build_log_meta_info", log_name :: Prop.all_props ::: Settings.all_settings)
    1.78 +
    1.79 +    val sessions_table =
    1.80 +      SQL.Table("isabelle_build_log_sessions",
    1.81 +        List(log_name, session_name, chapter, groups, threads, timing_elapsed, timing_cpu,
    1.82 +          timing_gc, timing_factor, ml_timing_elapsed, ml_timing_cpu, ml_timing_gc, ml_timing_factor,
    1.83 +          heap_size, status))
    1.84 +
    1.85 +    val ml_statistics_table =
    1.86 +      SQL.Table("isabelle_build_log_ml_statistics",
    1.87 +        List(log_name, session_name, ml_statistics))
    1.88 +
    1.89 +
    1.90 +    /* full view on build_log data */
    1.91 +
    1.92 +    // WARNING: This may cause performance problems, e.g. with sqlitebrowser
    1.93 +    val full_table: SQL.Table =
    1.94 +    {
    1.95 +      val columns =
    1.96 +        meta_info_table.columns :::
    1.97 +          sessions_table.columns.tail.map(_.copy(primary_key = false))
    1.98 +      SQL.Table("isabelle_build_log", columns,
    1.99 +        view =
   1.100 +          {
   1.101 +            val table1 = meta_info_table
   1.102 +            val table2 = sessions_table
   1.103 +            SQL.select(log_name(table1) :: columns.tail) +
   1.104 +            SQL.join(table1, table2, log_name(table1).sql + " = " + log_name(table2).sql)
   1.105 +          })
   1.106 +    }
   1.107 +
   1.108 +
   1.109 +    /* earliest pull date for repository version */
   1.110 +
   1.111 +    val pull_date = SQL.Column.date("pull_date")
   1.112 +
   1.113 +    def pull_date_table(name: String, version: SQL.Column): SQL.Table =
   1.114 +      SQL.Table("isabelle_build_log_" + name, List(version.copy(primary_key = true), pull_date),
   1.115 +        view = // PostgreSQL
   1.116 +          "SELECT " + version.sql + ", min(" + Prop.build_start.sql + ") AS " + pull_date.sql +
   1.117 +          " FROM " + meta_info_table.sql +
   1.118 +          " WHERE " + version.sql + " IS NOT NULL AND" + Prop.build_start.sql + " IS NOT NULL" +
   1.119 +          " GROUP BY " + version.sql)
   1.120 +
   1.121 +    val isabelle_pull_date_table = pull_date_table("isabelle_pull_date", Prop.isabelle_version)
   1.122 +    val afp_pull_date_table = pull_date_table("afp_pull_date", Prop.afp_version)
   1.123 +
   1.124 +    def recent(table: SQL.Table, days: Int): String =
   1.125 +      table.sql_select(table.columns) +
   1.126 +      " WHERE " + pull_date(table).sql + " > now() - INTERVAL '" + days.max(0) + " days'"
   1.127 +
   1.128 +    def select_recent(table: SQL.Table, columns: List[SQL.Column], days: Int): String =
   1.129 +      table.sql_select(columns) +
   1.130 +      " INNER JOIN (" + recent(isabelle_pull_date_table, days) + ") AS recent" +
   1.131 +      " ON " + Prop.isabelle_version(table).sql + " = recent." + Prop.isabelle_version.sql
   1.132 +  }
   1.133 +
   1.134 +
   1.135 +  /* database access */
   1.136 +
   1.137    def store(options: Options): Store = new Store(options)
   1.138  
   1.139    class Store private[Build_Log](options: Options) extends Properties.Store
   1.140 @@ -687,6 +739,47 @@
   1.141          ssh_close = true)
   1.142      }
   1.143  
   1.144 +    def update_database(db: SQL.Database, dirs: List[Path], ml_statistics: Boolean = false)
   1.145 +    {
   1.146 +      write_info(db, Log_File.find_files(dirs), ml_statistics = ml_statistics)
   1.147 +
   1.148 +      if (db.isInstanceOf[PostgreSQL.Database]) {
   1.149 +        List(Data.full_table, Data.isabelle_pull_date_table, Data.afp_pull_date_table)
   1.150 +          .foreach(db.create_view(_))
   1.151 +      }
   1.152 +    }
   1.153 +
   1.154 +    def snapshot(db: PostgreSQL.Database, sqlite_database: Path, days: Int = 100)
   1.155 +    {
   1.156 +      Isabelle_System.mkdirs(sqlite_database.dir)
   1.157 +      sqlite_database.file.delete
   1.158 +
   1.159 +      using(SQLite.open_database(sqlite_database))(db2 =>
   1.160 +      {
   1.161 +        db.transaction {
   1.162 +          db2.transaction {
   1.163 +            // pull_date tables
   1.164 +            List(Data.isabelle_pull_date_table, Data.afp_pull_date_table).foreach(table =>
   1.165 +            {
   1.166 +              db2.create_table(table)
   1.167 +              using(db2.insert(table))(stmt2 =>
   1.168 +              {
   1.169 +                using(db.statement(Data.recent(table, days)))(stmt =>
   1.170 +                {
   1.171 +                  val rs = stmt.executeQuery
   1.172 +                  while (rs.next()) {
   1.173 +                    for ((c, i) <- table.columns.zipWithIndex)
   1.174 +                      db2.set_string(stmt2, i + 1, db.get(rs, c, db.string _))
   1.175 +                    stmt2.execute
   1.176 +                  }
   1.177 +                })
   1.178 +              })
   1.179 +            })
   1.180 +          }
   1.181 +        }
   1.182 +      })
   1.183 +    }
   1.184 +
   1.185      def domain(db: SQL.Database, table: SQL.Table, column: SQL.Column): Set[String] =
   1.186        using(db.select(table, List(column), distinct = true))(stmt =>
   1.187          SQL.iterator(stmt.executeQuery)(db.string(_, column)).toSet)
   1.188 @@ -694,10 +787,10 @@
   1.189      def update_meta_info(db: SQL.Database, log_file: Log_File)
   1.190      {
   1.191        val meta_info = log_file.parse_meta_info()
   1.192 -      val table = Meta_Info.table
   1.193 +      val table = Data.meta_info_table
   1.194  
   1.195        db.transaction {
   1.196 -        using(db.delete(table, Meta_Info.log_name.sql_where_equal(log_file.name)))(_.execute)
   1.197 +        using(db.delete(table, Data.log_name.sql_where_equal(log_file.name)))(_.execute)
   1.198          using(db.insert(table))(stmt =>
   1.199          {
   1.200            db.set_string(stmt, 1, log_file.name)
   1.201 @@ -715,10 +808,10 @@
   1.202      def update_sessions(db: SQL.Database, log_file: Log_File)
   1.203      {
   1.204        val build_info = log_file.parse_build_info()
   1.205 -      val table = Build_Info.sessions_table
   1.206 +      val table = Data.sessions_table
   1.207  
   1.208        db.transaction {
   1.209 -        using(db.delete(table, Meta_Info.log_name.sql_where_equal(log_file.name)))(_.execute)
   1.210 +        using(db.delete(table, Data.log_name.sql_where_equal(log_file.name)))(_.execute)
   1.211          using(db.insert(table))(stmt =>
   1.212          {
   1.213            val entries_iterator =
   1.214 @@ -749,10 +842,10 @@
   1.215      def update_ml_statistics(db: SQL.Database, log_file: Log_File)
   1.216      {
   1.217        val build_info = log_file.parse_build_info(ml_statistics = true)
   1.218 -      val table = Build_Info.ml_statistics_table
   1.219 +      val table = Data.ml_statistics_table
   1.220  
   1.221        db.transaction {
   1.222 -        using(db.delete(table, Meta_Info.log_name.sql_where_equal(log_file.name)))(_.execute)
   1.223 +        using(db.delete(table, Data.log_name.sql_where_equal(log_file.name)))(_.execute)
   1.224          using(db.insert(table))(stmt =>
   1.225          {
   1.226            val ml_stats: List[(String, Option[Bytes])] =
   1.227 @@ -775,7 +868,7 @@
   1.228        class Table_Status(table: SQL.Table, update_db: (SQL.Database, Log_File) => Unit)
   1.229        {
   1.230          db.create_table(table)
   1.231 -        private var known: Set[String] = domain(db, table, Meta_Info.log_name)
   1.232 +        private var known: Set[String] = domain(db, table, Data.log_name)
   1.233  
   1.234          def required(file: JFile): Boolean = !known(Log_File.plain_name(file.getName))
   1.235          def update(log_file: Log_File)
   1.236 @@ -788,9 +881,9 @@
   1.237        }
   1.238        val status =
   1.239          List(
   1.240 -          new Table_Status(Meta_Info.table, update_meta_info _),
   1.241 -          new Table_Status(Build_Info.sessions_table, update_sessions _),
   1.242 -          new Table_Status(Build_Info.ml_statistics_table,
   1.243 +          new Table_Status(Data.meta_info_table, update_meta_info _),
   1.244 +          new Table_Status(Data.sessions_table, update_sessions _),
   1.245 +          new Table_Status(Data.ml_statistics_table,
   1.246              if (ml_statistics) update_ml_statistics _
   1.247              else (_: SQL.Database, _: Log_File) => ()))
   1.248  
   1.249 @@ -802,9 +895,9 @@
   1.250  
   1.251      def read_meta_info(db: SQL.Database, log_name: String): Option[Meta_Info] =
   1.252      {
   1.253 -      val table = Meta_Info.table
   1.254 +      val table = Data.meta_info_table
   1.255        val columns = table.columns.tail
   1.256 -      using(db.select(table, columns, Meta_Info.log_name.sql_where_equal(log_name)))(stmt =>
   1.257 +      using(db.select(table, columns, Data.log_name.sql_where_equal(log_name)))(stmt =>
   1.258        {
   1.259          val rs = stmt.executeQuery
   1.260          if (!rs.next) None
   1.261 @@ -829,29 +922,29 @@
   1.262        session_names: List[String] = Nil,
   1.263        ml_statistics: Boolean = false): Build_Info =
   1.264      {
   1.265 -      val table1 = Build_Info.sessions_table
   1.266 -      val table2 = Build_Info.ml_statistics_table
   1.267 +      val table1 = Data.sessions_table
   1.268 +      val table2 = Data.ml_statistics_table
   1.269  
   1.270        val where_log_name =
   1.271 -        Meta_Info.log_name(table1).sql_where_equal(log_name) + " AND " +
   1.272 -          Build_Info.session_name(table1).sql + " <> ''"
   1.273 +        Data.log_name(table1).sql_where_equal(log_name) + " AND " +
   1.274 +          Data.session_name(table1).sql + " <> ''"
   1.275        val where =
   1.276          if (session_names.isEmpty) where_log_name
   1.277          else
   1.278            where_log_name + " AND " +
   1.279 -          session_names.map(a => Build_Info.session_name(table1).sql + " = " + SQL.string(a)).
   1.280 +          session_names.map(a => Data.session_name(table1).sql + " = " + SQL.string(a)).
   1.281              mkString("(", " OR ", ")")
   1.282  
   1.283        val columns1 = table1.columns.tail.map(_.apply(table1))
   1.284        val (columns, from) =
   1.285          if (ml_statistics) {
   1.286 -          val columns = columns1 ::: List(Build_Info.ml_statistics(table2))
   1.287 +          val columns = columns1 ::: List(Data.ml_statistics(table2))
   1.288            val join =
   1.289              SQL.join_outer(table1, table2,
   1.290 -              Meta_Info.log_name(table1).sql + " = " +
   1.291 -              Meta_Info.log_name(table2).sql + " AND " +
   1.292 -              Build_Info.session_name(table1).sql + " = " +
   1.293 -              Build_Info.session_name(table2).sql)
   1.294 +              Data.log_name(table1).sql + " = " +
   1.295 +              Data.log_name(table2).sql + " AND " +
   1.296 +              Data.session_name(table1).sql + " = " +
   1.297 +              Data.session_name(table2).sql)
   1.298            (columns, SQL.enclose(join))
   1.299          }
   1.300          else (columns1, table1.sql)
   1.301 @@ -861,26 +954,26 @@
   1.302          {
   1.303            SQL.iterator(stmt.executeQuery)(rs =>
   1.304            {
   1.305 -            val session_name = db.string(rs, Build_Info.session_name)
   1.306 +            val session_name = db.string(rs, Data.session_name)
   1.307              val session_entry =
   1.308                Session_Entry(
   1.309 -                chapter = db.string(rs, Build_Info.chapter),
   1.310 -                groups = split_lines(db.string(rs, Build_Info.groups)),
   1.311 -                threads = db.get(rs, Build_Info.threads, db.int _),
   1.312 +                chapter = db.string(rs, Data.chapter),
   1.313 +                groups = split_lines(db.string(rs, Data.groups)),
   1.314 +                threads = db.get(rs, Data.threads, db.int _),
   1.315                  timing =
   1.316 -                  Timing(Time.ms(db.long(rs, Build_Info.timing_elapsed)),
   1.317 -                    Time.ms(db.long(rs, Build_Info.timing_cpu)),
   1.318 -                    Time.ms(db.long(rs, Build_Info.timing_gc))),
   1.319 +                  Timing(Time.ms(db.long(rs, Data.timing_elapsed)),
   1.320 +                    Time.ms(db.long(rs, Data.timing_cpu)),
   1.321 +                    Time.ms(db.long(rs, Data.timing_gc))),
   1.322                  ml_timing =
   1.323 -                  Timing(Time.ms(db.long(rs, Build_Info.ml_timing_elapsed)),
   1.324 -                    Time.ms(db.long(rs, Build_Info.ml_timing_cpu)),
   1.325 -                    Time.ms(db.long(rs, Build_Info.ml_timing_gc))),
   1.326 -                heap_size = db.get(rs, Build_Info.heap_size, db.long _),
   1.327 +                  Timing(Time.ms(db.long(rs, Data.ml_timing_elapsed)),
   1.328 +                    Time.ms(db.long(rs, Data.ml_timing_cpu)),
   1.329 +                    Time.ms(db.long(rs, Data.ml_timing_gc))),
   1.330 +                heap_size = db.get(rs, Data.heap_size, db.long _),
   1.331                  status =
   1.332 -                  db.get(rs, Build_Info.status, db.string _).
   1.333 +                  db.get(rs, Data.status, db.string _).
   1.334                      map(Session_Status.withName(_)),
   1.335                  ml_statistics =
   1.336 -                  if (ml_statistics) uncompress_properties(db.bytes(rs, Build_Info.ml_statistics))
   1.337 +                  if (ml_statistics) uncompress_properties(db.bytes(rs, Data.ml_statistics))
   1.338                    else Nil)
   1.339              session_name -> session_entry
   1.340            }).toMap
   1.341 @@ -888,98 +981,4 @@
   1.342        Build_Info(sessions)
   1.343      }
   1.344    }
   1.345 -
   1.346 -
   1.347 -  /** high-level database structure **/
   1.348 -
   1.349 -  object Database
   1.350 -  {
   1.351 -    /* full view on build_log data */
   1.352 -
   1.353 -    // WARNING: This may cause performance problems, e.g. with sqlitebrowser
   1.354 -    val full_table: SQL.Table =
   1.355 -    {
   1.356 -      val columns =
   1.357 -        Meta_Info.table.columns :::
   1.358 -          Build_Info.sessions_table.columns.tail.map(_.copy(primary_key = false))
   1.359 -      SQL.Table("isabelle_build_log", columns,
   1.360 -        view =
   1.361 -          {
   1.362 -            val table1 = Meta_Info.table
   1.363 -            val table2 = Build_Info.sessions_table
   1.364 -            SQL.select(Meta_Info.log_name(table1) :: columns.tail) +
   1.365 -            SQL.join(table1, table2,
   1.366 -              Meta_Info.log_name(table1).sql + " = " + Meta_Info.log_name(table2).sql)
   1.367 -          })
   1.368 -    }
   1.369 -
   1.370 -
   1.371 -    /* earliest pull date for repository version */
   1.372 -
   1.373 -    val pull_date = SQL.Column.date("pull_date")
   1.374 -
   1.375 -    def pull_date_table(name: String, version: SQL.Column): SQL.Table =
   1.376 -      SQL.Table("isabelle_build_log_" + name, List(version.copy(primary_key = true), pull_date),
   1.377 -        view = // PostgreSQL
   1.378 -          "SELECT " + version.sql + ", min(" + Prop.build_start.sql + ") AS " + pull_date.sql +
   1.379 -          " FROM " + Meta_Info.table.sql +
   1.380 -          " WHERE " + version.sql + " IS NOT NULL AND" + Prop.build_start.sql + " IS NOT NULL" +
   1.381 -          " GROUP BY " + version.sql)
   1.382 -
   1.383 -    val isabelle_pull_date_table = pull_date_table("isabelle_pull_date", Prop.isabelle_version)
   1.384 -    val afp_pull_date_table = pull_date_table("afp_pull_date", Prop.afp_version)
   1.385 -
   1.386 -    def recent(table: SQL.Table, days: Int): String =
   1.387 -      table.sql_select(table.columns) +
   1.388 -      " WHERE " + pull_date(table).sql + " > now() - INTERVAL '" + days.max(0) + " days'"
   1.389 -
   1.390 -    def select_recent(table: SQL.Table, columns: List[SQL.Column], days: Int): String =
   1.391 -      table.sql_select(columns) +
   1.392 -      " INNER JOIN (" + recent(isabelle_pull_date_table, days) + ") AS recent" +
   1.393 -      " ON " + Prop.isabelle_version(table).sql + " = recent." + Prop.isabelle_version.sql
   1.394 -
   1.395 -
   1.396 -    /* main operations */
   1.397 -
   1.398 -    def update(store: Store, db: SQL.Database, dirs: List[Path], ml_statistics: Boolean = false)
   1.399 -    {
   1.400 -      store.write_info(db, Log_File.find_files(dirs), ml_statistics = ml_statistics)
   1.401 -
   1.402 -      if (db.isInstanceOf[PostgreSQL.Database]) {
   1.403 -        List(full_table, isabelle_pull_date_table, afp_pull_date_table)
   1.404 -          .foreach(db.create_view(_))
   1.405 -      }
   1.406 -    }
   1.407 -
   1.408 -    def snapshot(store: Store, db: PostgreSQL.Database, sqlite_database: Path, days: Int = 100)
   1.409 -    {
   1.410 -      Isabelle_System.mkdirs(sqlite_database.dir)
   1.411 -      sqlite_database.file.delete
   1.412 -
   1.413 -      using(SQLite.open_database(sqlite_database))(db2 =>
   1.414 -      {
   1.415 -        db.transaction {
   1.416 -          db2.transaction {
   1.417 -            // pull_date tables
   1.418 -            List(isabelle_pull_date_table, afp_pull_date_table).foreach(table =>
   1.419 -            {
   1.420 -              db2.create_table(table)
   1.421 -              using(db2.insert(table))(stmt2 =>
   1.422 -              {
   1.423 -                using(db.statement(recent(table, days)))(stmt =>
   1.424 -                {
   1.425 -                  val rs = stmt.executeQuery
   1.426 -                  while (rs.next()) {
   1.427 -                    for ((c, i) <- table.columns.zipWithIndex)
   1.428 -                      db2.set_string(stmt2, i + 1, db.get(rs, c, db.string _))
   1.429 -                    stmt2.execute
   1.430 -                  }
   1.431 -                })
   1.432 -              })
   1.433 -            })
   1.434 -          }
   1.435 -        }
   1.436 -      })
   1.437 -    }
   1.438 -  }
   1.439  }
     2.1 --- a/src/Pure/Admin/isabelle_cronjob.scala	Wed May 03 13:54:22 2017 +0200
     2.2 +++ b/src/Pure/Admin/isabelle_cronjob.scala	Wed May 03 14:55:34 2017 +0200
     2.3 @@ -149,7 +149,7 @@
     2.4    {
     2.5      val store = Build_Log.store(options)
     2.6      using(store.open_database())(db =>
     2.7 -      Build_Log.Database.update(store, db, database_dirs, ml_statistics = true))
     2.8 +      store.update_database(db, database_dirs, ml_statistics = true))
     2.9    }
    2.10  
    2.11