guix-commits
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[no subject]


From: Mathieu Othacehe
Date: Mon, 14 Sep 2020 08:35:35 -0400 (EDT)

branch: master
commit cf11b73db00678b45b70108768138d0fb74d9506
Author: Mathieu Othacehe <othacehe@gnu.org>
AuthorDate: Mon Sep 14 14:25:23 2020 +0200

    Add metrics support.
    
    * Makefile.am: Add "upgrade-11.sql", "chart.js" and "metrics.js".
    * bin/cuirass.in (main): Add 'metrics fiber.
    * src/cuirass/http.scm (%file-white-list): Add "js/chart.js".
    (url-handler): Add "/metrics" route.
    * src/cuirass/metrics.scm: New file.
    * src/cuirass/templates.scm (html-page): Add a "Global metrics" sub-menu to
    "Status" dropdown menu.
    (make-line-chart, global-metrics-content): New procedures.
    * src/schema.sql (Metrics): New table.
    * src/sql/upgrade-11.sql: New file.
    * src/static/js/chart.js: New file.
---
 Makefile.am               |   7 +-
 bin/cuirass.in            |  12 ++-
 src/cuirass/http.scm      |  25 +++++-
 src/cuirass/metrics.scm   | 199 ++++++++++++++++++++++++++++++++++++++++++++++
 src/cuirass/templates.scm |  78 +++++++++++++++++-
 src/schema.sql            |   8 ++
 src/sql/upgrade-11.sql    |  11 +++
 src/static/js/chart.js    |   7 ++
 8 files changed, 343 insertions(+), 4 deletions(-)

diff --git a/Makefile.am b/Makefile.am
index 9c86276..df24342 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -40,12 +40,14 @@ staticdir = $(pkgdatadir)/static
 cssdir = $(staticdir)/css
 fontsdir = $(staticdir)/fonts
 imagesdir = $(staticdir)/images
+jsdir = $(staticdir)/js
 
 dist_pkgmodule_DATA =                          \
   src/cuirass/base.scm                         \
   src/cuirass/database.scm                     \
   src/cuirass/http.scm                         \
   src/cuirass/logging.scm                      \
+  src/cuirass/metrics.scm                      \
   src/cuirass/send-events.scm                  \
   src/cuirass/ui.scm                           \
   src/cuirass/utils.scm             \
@@ -76,7 +78,8 @@ dist_sql_DATA =                               \
   src/sql/upgrade-7.sql                                \
   src/sql/upgrade-8.sql                                \
   src/sql/upgrade-9.sql                                \
-  src/sql/upgrade-10.sql
+  src/sql/upgrade-10.sql                       \
+  src/sql/upgrade-11.sql
 
 dist_css_DATA =                                        \
   src/static/css/cuirass.css                   \
@@ -90,6 +93,8 @@ dist_fonts_DATA =                             \
   src/static/fonts/open-iconic.woff
 dist_images_DATA =                             \
   src/static/images/logo.png
+dist_js_DATA =                                 \
+  src/static/js/chart.js
 
 TEST_EXTENSIONS = .scm .sh
 AM_TESTS_ENVIRONMENT = \
diff --git a/bin/cuirass.in b/bin/cuirass.in
index c4bcfaa..9c856fc 100644
--- a/bin/cuirass.in
+++ b/bin/cuirass.in
@@ -29,6 +29,7 @@ exec ${GUILE:-@GUILE@} --no-auto-compile -e main -s "$0" "$@"
 (use-modules (cuirass)
              (cuirass ui)
              (cuirass logging)
+             (cuirass metrics)
              (cuirass utils)
              (guix ui)
              ((guix build utils) #:select (mkdir-p))
@@ -183,11 +184,20 @@ exec ${GUILE:-@GUILE@} --no-auto-compile -e main -s "$0" 
"$@"
                                (lambda ()
                                  (while #t
                                    (process-specs (db-get-specifications))
-                                   (log-message "next evaluation in ~a 
seconds" interval)
+                                   (log-message
+                                    "next evaluation in ~a seconds" interval)
                                    (sleep interval)))))
 
                              (spawn-fiber
                               (essential-task
+                               'metrics exit-channel
+                               (lambda ()
+                                 (while #t
+                                   (db-update-metrics)
+                                   (sleep 3600)))))
+
+                             (spawn-fiber
+                              (essential-task
                                'monitor exit-channel
                                (lambda ()
                                  (while #t
diff --git a/src/cuirass/http.scm b/src/cuirass/http.scm
index 98696a6..cc88f04 100644
--- a/src/cuirass/http.scm
+++ b/src/cuirass/http.scm
@@ -25,6 +25,7 @@
   #:use-module (cuirass config)
   #:use-module (cuirass database)
   #:use-module ((cuirass base) #:select (evaluation-log-file))
+  #:use-module (cuirass metrics)
   #:use-module (cuirass utils)
   #:use-module (cuirass logging)
   #:use-module (srfi srfi-1)
@@ -72,7 +73,8 @@
     "css/open-iconic-bootstrap.css"
     "fonts/open-iconic.otf"
     "fonts/open-iconic.woff"
-    "images/logo.png"))
+    "images/logo.png"
+    "js/chart.js"))
 
 (define (build->hydra-build build)
   "Convert BUILD to an assoc list matching hydra API format."
@@ -604,6 +606,27 @@ Hydra format."
               (respond-json-with-error 500 "No build found.")))
            (respond-json-with-error 500 "Query parameter not provided."))))
 
+    (('GET "metrics")
+     (respond-html
+      (html-page
+       "Global metrics"
+       (let ((builds-per-day
+              (db-get-metrics-with-id 'builds-previous-day
+                                      #:limit 10
+                                      #:order "field"))
+             (avg-eval-durations
+              (list
+               (db-get-metrics-with-id
+                'average-10-last-eval-duration-per-spec)
+               (db-get-metrics-with-id
+                'average-100-last-eval-duration-per-spec)
+               (db-get-metrics-with-id
+                'average-eval-duration-per-spec))))
+         (global-metrics-content
+          #:avg-eval-durations avg-eval-durations
+          #:builds-per-day builds-per-day))
+       '())))
+
     (('GET "status")
      (respond-html
       (html-page
diff --git a/src/cuirass/metrics.scm b/src/cuirass/metrics.scm
new file mode 100644
index 0000000..c5880e3
--- /dev/null
+++ b/src/cuirass/metrics.scm
@@ -0,0 +1,199 @@
+;;; metrics.scm -- Compute and store metrics.
+;;; This file is part of Cuirass.
+;;;
+;;; Cuirass is free software: you can redistribute it and/or modify
+;;; it under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation, either version 3 of the License, or
+;;; (at your option) any later version.
+;;;
+;;; Cuirass is distributed in the hope that it will be useful,
+;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with Cuirass.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (cuirass metrics)
+  #:use-module (cuirass database)
+  #:use-module (cuirass logging)
+  #:use-module (guix records)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-19)
+  #:use-module (srfi srfi-26)
+  #:use-module (ice-9 match)
+  #:export (metric
+            metric?
+            metric-id
+            metric-proc
+
+            %metrics
+            metric->type
+            compute-metric
+
+            db-get-metric
+            db-get-metrics-with-id
+            db-update-metric
+            db-update-metrics))
+
+
+;;;
+;;; Metric record.
+;;;
+
+(define-record-type* <metric> metric make-metric
+  metric?
+  (id              metric-id)
+  (compute-proc    metric-compute-proc)
+  (field-proc      metric-field-proc
+                   (default #f)))
+
+
+;;;
+;;; Database procedures.
+;;;
+
+(define* (db-average-eval-duration-per-spec spec #:key limit)
+  "Return the evaluation duration of EVAL."
+  (with-db-worker-thread db
+    (let ((rows (sqlite-exec db "SELECT AVG(duration) FROM
+(SELECT (evaltime - timestamp) as duration
+FROM Evaluations WHERE specification = " spec
+" AND evaltime != 0 LIMIT " (or limit -1) ");")))
+      (and=> (expect-one-row rows) (cut vector-ref <> 0)))))
+
+(define (db-builds-previous-day _)
+  "Return the builds count of the previous day."
+  (with-db-worker-thread db
+    (let ((rows (sqlite-exec db "SELECT COUNT(*) from Builds
+WHERE date(stoptime, 'unixepoch') = date('now', '-1 day');")))
+      (and=> (expect-one-row rows) (cut vector-ref <> 0)))))
+
+(define (db-previous-day-timestamp)
+  "Return the timestamp of the previous day."
+  (with-db-worker-thread db
+    (let ((rows (sqlite-exec db "SELECT strftime('%s',
+date('now', '-1 day'));")))
+      (and=> (expect-one-row rows) (cut vector-ref <> 0)))))
+
+
+;;;
+;;; Definitions.
+;;;
+
+(define %metrics
+  (list
+   ;; Average evaluation duration per specification.
+   (metric
+    (id 'average-10-last-eval-duration-per-spec)
+    (compute-proc
+     (cut db-average-eval-duration-per-spec <> #:limit 10)))
+   (metric
+    (id 'average-100-last-eval-duration-per-spec)
+    (compute-proc
+     (cut db-average-eval-duration-per-spec <> #:limit 100)))
+   (metric
+    (id 'average-eval-duration-per-spec)
+    (compute-proc db-average-eval-duration-per-spec))
+
+   ;; Builds count over last 24 hours.
+   (metric
+    (id 'builds-previous-day)
+    (compute-proc db-builds-previous-day)
+    (field-proc db-previous-day-timestamp))))
+
+(define (metric->type metric)
+  "Return the index of the given METRIC in %metrics list.  This index is used
+to identify the metric type in database."
+  (list-index
+   (lambda (cur-metric)
+     (eq? (metric-id cur-metric) (metric-id metric)))
+   %metrics))
+
+(define (find-metric id)
+  "Find the metric with the given ID."
+  (find (lambda (metric)
+          (eq? (metric-id metric) id))
+        %metrics))
+
+(define* (compute-metric metric field)
+  "Compute the given METRIC on FIELD and return the associated value."
+  (let ((compute (metric-compute-proc metric)))
+    (compute field)))
+
+(define* (db-get-metric id field)
+  "Return the metric with the given ID and FIELD."
+  (let* ((metric (find-metric id))
+         (type (metric->type metric)))
+    (with-db-worker-thread db
+    (let ((rows (sqlite-exec db "SELECT value from Metrics
+WHERE type = " type " AND field = " field ";")))
+      (and=> (expect-one-row rows) (cut vector-ref <> 0))))))
+
+(define* (db-get-metrics-with-id id
+                                 #:key
+                                 limit
+                                 (order "timestamp"))
+  "Return the metrics with the given ID.  If LIMIT is set, the resulting list
+if restricted to LIMIT records."
+  (let* ((metric (find-metric id))
+         (type (metric->type metric))
+         (limit (or limit -1)))
+    (with-db-worker-thread db
+      (let ((query (format #f "SELECT field, value from Metrics
+WHERE type = ? ORDER BY ~a LIMIT ~a" order limit)))
+        (let loop ((rows (%sqlite-exec db query type))
+                   (metrics '()))
+          (match rows
+            (() (reverse metrics))
+            ((#(field value) . rest)
+             (loop rest
+                   `((,field . ,value)
+                     ,@metrics)))))))))
+
+(define* (db-update-metric id #:optional field)
+  "Compute and update the value of the metric ID in database.
+
+  FIELD is optional and can be the id of a database object such as an
+evaluation or a specification that the METRIC applies to.  If FIELD is not
+passed then the METRIC may provide a FIELD-PROC to compute it.  It is useful
+for periodical metrics for instance."
+  (define now
+    (time-second (current-time time-utc)))
+
+  (let* ((metric (find-metric id))
+         (field-proc (metric-field-proc metric))
+         (field (or field (field-proc)))
+         (value (compute-metric metric field)))
+    (if value
+        (begin
+          (log-message "Updating metric ~a (~a) to ~a."
+                       (symbol->string id) field value)
+          (with-db-worker-thread db
+            (sqlite-exec db "\
+INSERT OR REPLACE INTO Metrics (field, type, value,
+timestamp) VALUES ("
+                         field ", "
+                         (metric->type metric) ", "
+                         value ", "
+                         now ");")
+            (last-insert-rowid db)))
+        (log-message "Failed to compute metric ~a (~a)."
+                     (symbol->string id) field))))
+
+(define (db-update-metrics)
+  "Compute and update all available metrics in database."
+  (define specifications
+    (map (cut assq-ref <> #:name) (db-get-specifications)))
+
+  (db-update-metric 'builds-previous-day)
+
+  ;; Update specification related metrics.
+  (for-each (lambda (spec)
+              (db-update-metric
+               'average-10-last-eval-duration-per-spec spec)
+              (db-update-metric
+               'average-100-last-eval-duration-per-spec spec)
+              (db-update-metric
+               'average-eval-duration-per-spec spec))
+            specifications))
diff --git a/src/cuirass/templates.scm b/src/cuirass/templates.scm
index 3128b45..e5a7526 100644
--- a/src/cuirass/templates.scm
+++ b/src/cuirass/templates.scm
@@ -26,6 +26,7 @@
   #:use-module (srfi srfi-2)
   #:use-module (srfi srfi-19)
   #:use-module (srfi srfi-26)
+  #:use-module (json)
   #:use-module (web uri)
   #:use-module (guix derivations)
   #:use-module (guix progress)
@@ -40,7 +41,8 @@
             build-search-results-table
             build-details
             evaluation-build-table
-            running-builds-table))
+            running-builds-table
+            global-metrics-content))
 
 (define (navigation-items navigation)
   (match navigation
@@ -134,6 +136,9 @@ system whose names start with " (code "guile-") ":" (br)
                             (div (@ (class "dropdown-menu")
                                     (aria-labelledby "navbarDropdow"))
                                  (a (@ (class "dropdown-item")
+                                       (href "/metrics"))
+                                    "Global metrics")
+                                 (a (@ (class "dropdown-item")
                                        (href "/status"))
                                     "Running builds")))
                         (li (@ (class "nav-item"))
@@ -820,3 +825,74 @@ and BUILD-MAX are global minimal and maximal row 
identifiers."
                         (th (@ (scope "col")) "System")))
              (tbody
               ,(map build-row builds)))))))
+
+(define* (make-line-chart id data
+                          #:key
+                          title
+                          color)
+  (let* ((scales  `((xAxes
+                     . ,(vector '((type . "time")
+                                  (time . ((unit . "day")))
+                                  (display . #t)
+                                  (distribution . "series")
+                                  (scaleLabel
+                                   . ((display . #t)
+                                      (labelString . "Day"))))))
+                    (yAxes
+                     . ,(vector '((display . #t)
+                                  (scaleLabel
+                                   . ((display . #t)
+                                      (labelString . "Builds"))))))))
+         (chart `((type . "line")
+                  (data . ((datasets . ,(vector `((fill . #f)
+                                                  (borderColor . ,color)
+                                                  (data . ,data))))))
+                  (options . ((responsive . #t)
+                               (tooltips . ((enabled . #f)))
+                               (legend . ((display . #f)))
+                               (title . ((display . #t)
+                                         (text . ,title)))
+                               (scales . ,scales))))))
+    `((script ,(format #f "window.onload = function() {\
+window.~a = new Chart\
+(document.getElementById('~a').getContext('2d'), ~a);\
+};" id id (scm->json-string chart))))))
+
+(define* (global-metrics-content #:key
+                                 builds-per-day
+                                 avg-eval-durations)
+  (define (avg-eval-duration-row . eval-durations)
+    (let ((spec (match eval-durations
+                  (((spec . _) . rest) spec))))
+      `(tr (td ,spec)
+           ,@(map (lambda (duration)
+                    `(td ,(number->string
+                           (nearest-exact-integer duration))))
+                  (map cdr eval-durations)))))
+
+  (define builds-json-scm
+    (apply vector
+           (map (match-lambda
+                  ((field . value)
+                   `((x . ,(* field 1000)) (y . ,value))))
+                builds-per-day)))
+
+  (let ((builds-chart "builds_per_day"))
+    `((div
+       (p (@ (class "lead")) "Global metrics")
+       (h6 "Average evaluation duration per specification (seconds).")
+       (table
+        (@ (class "table table-sm table-hover table-striped"))
+        (thead (tr (th (@ (scope "col")) "Specification")
+                   (th (@ (scope "col")) "10 last evaluations")
+                   (th (@ (scope "col")) "100 last evaluations")
+                   (th (@ (scope "col")) "All evaluations")))
+        (tbody
+         ,(apply map avg-eval-duration-row avg-eval-durations)))
+       (br)
+       (h6 "Build speed.")
+       (canvas (@ (id ,builds-chart)))
+       (script (@ (src "/static/js/chart.js")))
+       ,@(make-line-chart builds-chart builds-json-scm
+                          #:title "Builds per day"
+                          #:color "#3e95cd")))))
diff --git a/src/schema.sql b/src/schema.sql
index 335a6d4..ed5893e 100644
--- a/src/schema.sql
+++ b/src/schema.sql
@@ -70,6 +70,14 @@ CREATE TABLE Builds (
   FOREIGN KEY (evaluation) REFERENCES Evaluations (id)
 );
 
+CREATE TABLE Metrics (
+  field         INTEGER NOT NULL,
+  type          INTEGER NOT NULL,
+  value         DOUBLE PRECISION NOT NULL,
+  timestamp     INTEGER NOT NULL,
+  PRIMARY KEY (field, type)
+);
+
 CREATE TABLE BuildProducts (
   build         INTEGER NOT NULL,
   type          TEXT NOT NULL,
diff --git a/src/sql/upgrade-11.sql b/src/sql/upgrade-11.sql
new file mode 100644
index 0000000..22f2dac
--- /dev/null
+++ b/src/sql/upgrade-11.sql
@@ -0,0 +1,11 @@
+BEGIN TRANSACTION;
+
+CREATE TABLE Metrics (
+  field         INTEGER NOT NULL,
+  type          INTEGER NOT NULL,
+  value         DOUBLE PRECISION NOT NULL,
+  timestamp     INTEGER NOT NULL,
+  PRIMARY KEY (field, type)
+);
+
+COMMIT;
diff --git a/src/static/js/chart.js b/src/static/js/chart.js
new file mode 100644
index 0000000..55d9eb0
--- /dev/null
+++ b/src/static/js/chart.js
@@ -0,0 +1,7 @@
+/*!
+ * Chart.js v2.9.3
+ * https://www.chartjs.org
+ * (c) 2019 Chart.js Contributors
+ * Released under the MIT License
+ */
+!function(t,e){"object"==typeof exports&&"undefined"!=typeof 
module?module.exports=e():"function"==typeof 
define&&define.amd?define(e):(t=t||self).Chart=e()}(this,(function(){"use 
strict";"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof 
window?window:"undefined"!=typeof global?global:"undefined"!=typeof 
self&&self;function t(){throw new Error("Dynamic requires are not currently 
supported by rollup-plugin-commonjs")}function e(t,e){return 
t(e={exports:{}},e.exports),e.exports [...]



reply via email to

[Prev in Thread] Current Thread [Next in Thread]