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, 8 Feb 2021 06:27:11 -0500 (EST)

branch: master
commit ac98f81671c7c2b4edd2e3527fb11019ed5083a2
Author: Mathieu Othacehe <mathieu@berlin.guix.gnu.org>
AuthorDate: Mon Feb 8 12:25:47 2021 +0100

    Add Zabbix support.
---
 Makefile.am               |   3 +-
 src/cuirass/http.scm      |  58 ++++++++++++++++
 src/cuirass/templates.scm | 129 +++++++++++++++++++++++++++++++++-
 src/cuirass/zabbix.scm    | 172 ++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 358 insertions(+), 4 deletions(-)

diff --git a/Makefile.am b/Makefile.am
index 9cc0bb2..d1c5452 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -62,7 +62,8 @@ dist_pkgmodule_DATA =                         \
   src/cuirass/ui.scm                           \
   src/cuirass/utils.scm                                \
   src/cuirass/templates.scm                     \
-  src/cuirass/watchdog.scm
+  src/cuirass/watchdog.scm                     \
+  src/cuirass/zabbix.scm
 
 nodist_pkgmodule_DATA = \
   src/cuirass/config.scm
diff --git a/src/cuirass/http.scm b/src/cuirass/http.scm
index 2d3d4cb..f80311f 100644
--- a/src/cuirass/http.scm
+++ b/src/cuirass/http.scm
@@ -30,6 +30,7 @@
   #:use-module (cuirass logging)
   #:use-module (cuirass remote)
   #:use-module (cuirass rss)
+  #:use-module (cuirass zabbix)
   #:use-module (srfi srfi-1)
   #:use-module (srfi srfi-11)
   #:use-module (srfi srfi-26)
@@ -46,6 +47,7 @@
   #:use-module ((rnrs bytevectors) #:select (utf8->string))
   #:use-module (sxml simple)
   #:use-module (cuirass templates)
+  #:use-module (guix progress)
   #:use-module (guix utils)
   #:use-module ((guix store) #:select (%store-prefix))
   #:use-module (guix build union)
@@ -276,6 +278,58 @@ Hydra format."
       'percentage-failed-eval-per-spec)))
    '()))
 
+(define (machine-page name)
+  (define zabbix-info
+    (if (zabbix-available?)
+        (with-zabbix-connection
+         (let* ((host-id        (zabbix-host-id name))
+                (enabled?       (zabbix-host-enabled? name))
+                (value          (cut zabbix-item-value <> host-id))
+                (history        (lambda (key type)
+                                  (zabbix-history
+                                   (zabbix-item-id key host-id)
+                                   #:limit 100
+                                   #:type type))))
+           (if enabled?
+               `((#:hostname . ,(value "system.hostname"))
+                 (#:info . ,(value "system.uname"))
+                 (#:boottime . ,(string->number
+                                 (value "system.boottime")))
+                 (#:ram . ,(byte-count->string
+                            (string->number
+                             (value "vm.memory.size[total]"))))
+                 (#:root-space . ,(byte-count->string
+                                   (string->number
+                                    (value "vfs.fs.size[/,total]"))))
+                 (#:store-space
+                  . ,(byte-count->string
+                      (string->number
+                       (value "vfs.fs.size[/gnu/store,total]"))))
+                 (#:cpu-idle . ,(history "system.cpu.util[,idle]" 'float))
+                 (#:ram-available . ,(history "vm.memory.size[available]"
+                                              'unsigned))
+                 (#:store-free . ,(history "vfs.fs.size[/gnu/store,pfree]"
+                                           'float)))
+               '())))
+        '()))
+
+  (let ((builds (db-get-builds `((status . started)
+                                 (order . status+submission-time))))
+        (workers (filter (lambda (worker)
+                           (string=? name (worker-machine worker)))
+                         (db-get-workers))))
+    (html-page
+     name
+     (machine-status name workers
+                     (map (lambda (worker)
+                            (filter (lambda (build)
+                                      (string=? (assq-ref build #:worker)
+                                                (worker-name worker)))
+                                    builds))
+                          workers)
+                     zabbix-info)
+     '())))
+
 
 ;;;
 ;;; Web server.
@@ -723,6 +777,10 @@ Hydra format."
             500
             "Could not find the request build product."))))
 
+    (('GET "machine" name)
+     (respond-html
+      (machine-page name)))
+
     (('GET "static" path ...)
      (respond-static-file path))
     (_
diff --git a/src/cuirass/templates.scm b/src/cuirass/templates.scm
index bc3eade..ae3de20 100644
--- a/src/cuirass/templates.scm
+++ b/src/cuirass/templates.scm
@@ -46,7 +46,8 @@
             evaluation-build-table
             running-builds-table
             global-metrics-content
-            workers-status))
+            workers-status
+            machine-status))
 
 (define (navigation-items navigation)
   (match navigation
@@ -922,6 +923,7 @@ and BUILD-MAX are global minimal and maximal row 
identifiers."
                           xaxes-labels
                           x-label
                           y-label
+                          (x-unit "day")
                           title
                           labels
                           colors)
@@ -932,7 +934,7 @@ and BUILD-MAX are global minimal and maximal row 
identifiers."
                                   . ((display . #t)
                                      (labelString . ,x-label))))))
          (time-xAxes (vector `((type . "time")
-                               (time . ((unit . "day")))
+                               (time . ((unit . ,x-unit)))
                                (display . #t)
                                (distribution . "series")
                                (scaleLabel
@@ -1126,7 +1128,8 @@ completed builds divided by the time required to build 
them.")
                      ((build _ ...) build)))
                  workers)))
       `(div (@ (class "col-sm-4 mt-3"))
-            (h6 ,machine)
+            (a (@(href "/machine/" ,machine))
+               (h6 ,machine))
             ,(map (lambda (build)
                     (let ((style (format #f
                                          "width: ~a%"
@@ -1164,3 +1167,123 @@ text-dark d-flex position-absolute w-100"))
       (div (@ (class "container"))
            (div (@ (class "row"))
                 ,@(map machine-row machines))))))
+
+(define* (machine-status name workers builds info)
+  (define (history->json-scm history)
+    (apply vector
+           (map (match-lambda
+                  ((field . value)
+                   `((x . ,(* field 1000)) (y . ,value))))
+                history)))
+
+  (define (ram-available->json-scm history)
+    (apply vector
+           (map (match-lambda
+                  ((field . value)
+                   `((x . ,(* field 1000))
+                     (y . ,(/ value (expt 2 30))))))
+                history)))
+
+  `((p (@ (class "lead")) "Machine " ,name)
+    ,@(if (null? info)
+          '()
+          `((table
+             (@ (class "table table-sm table-hover table-striped"))
+             (tbody
+              (tr (th "Hostname")
+                  (td ,(assq-ref info #:hostname)))
+              (tr (th "Info")
+                  (td ,(assq-ref info #:info)))
+              (tr (th "Boot time")
+                  (td ,(time->string
+                        (assq-ref info #:boottime))))
+              (tr (th "Total RAM")
+                  (td ,(assq-ref info #:ram)))
+              (tr (th "Total root disk space")
+                  (td ,(assq-ref info #:root-space)))
+              (tr (th "Total store disk space")
+                  (td ,(assq-ref info #:store-space)))))))
+    (h6 "Workers")
+    (table
+     (@ (class "table table-sm table-hover table-striped"))
+     ,@(if (null? workers)
+           `((th (@ (scope "col")) "No elements here."))
+           `((thead
+              (tr
+               (th (@ (scope "col")) "Name")
+               (th (@ (scope "col")) "Systems")
+               (th (@ (scope "col")) "Building")
+               (th (@ (scope "col")) "Last seen")))
+             (tbody
+              ,@(map
+                 (lambda (worker build)
+                   `(tr (td ,(worker-name worker))
+                        (td ,(string-join (worker-systems worker)
+                                          ", "))
+                        (td ,(match build
+                               (() "idle")
+                               ((build)
+                                `(a (@ (class "text-truncate")
+                                       (style "max-width: 150px")
+                                       (href "/build/"
+                                             ,(assq-ref build #:id)
+                                             "/details"))
+                                    ,(assq-ref build #:job-name)))))
+                        (td ,(time->string
+                              (worker-last-seen worker)))))
+                 workers builds)))))
+    ,@(if (null? info)
+          '((div (@ (class "alert alert-danger"))
+                 "Could not find machine information using Zabbix."))
+          `((h6 "CPU idle time")
+            ,@(let ((cpu-idle (assq-ref info #:cpu-idle))
+                    (cpu-idle-chart "cpu_idle_chart"))
+                `((script (@ (src "/static/js/chart.js")))
+                  (br)
+                  (canvas (@ (id ,cpu-idle-chart)))
+                  ,@(make-line-chart cpu-idle-chart
+                                     (list (history->json-scm cpu-idle))
+                                     #:time-x-axes? #t
+                                     #:x-label "Time"
+                                     #:y-label "Percentage"
+                                     #:x-unit "minute"
+                                     #:title "CPU idle time"
+                                     #:labels '("CPU idle time")
+                                     #:colors (list "#3e95cd"))))
+            (br)
+            (h6 "Available memory")
+            ,@(let ((ram-available (assq-ref info #:ram-available))
+                    (ram-available-chart "ram_available_chart"))
+                `((script (@ (src "/static/js/chart.js")))
+                  (br)
+                  (canvas (@ (id ,ram-available-chart)))
+                  ,@(make-line-chart ram-available-chart
+                                     (list
+                                      (ram-available->json-scm ram-available))
+                                     #:time-x-axes? #t
+                                     #:x-label "Time"
+                                     #:y-label "GiB"
+                                     #:x-unit "minute"
+                                     #:title
+                                     "Available memory"
+                                     #:labels
+                                     '("Available memory")
+                                     #:colors (list "#3e95cd"))))
+            (br)
+            (h6 "Free store disk space percentage")
+            ,@(let ((store-free (assq-ref info #:store-free))
+                    (store-free-chart "store_free_chart"))
+                `((script (@ (src "/static/js/chart.js")))
+                  (br)
+                  (canvas (@ (id ,store-free-chart)))
+                  ,@(make-line-chart store-free-chart
+                                     (list (history->json-scm store-free))
+                                     #:time-x-axes? #t
+                                     #:x-label "Time"
+                                     #:y-label "Percentage"
+                                     #:x-unit "minute"
+                                     #:title
+                                     "Free store disk space percentage"
+                                     #:labels
+                                     '("Free store disk space percentage")
+                                     #:colors (list "#3e95cd"))))))))
diff --git a/src/cuirass/zabbix.scm b/src/cuirass/zabbix.scm
new file mode 100644
index 0000000..3ceff34
--- /dev/null
+++ b/src/cuirass/zabbix.scm
@@ -0,0 +1,172 @@
+;;; zabbix.scm -- Zabbix API connection.
+;;; Copyright © 2021 Mathieu Othacehe <othacehe@gnu.org>
+;;;
+;;; This file is part of Cuirass.
+;;;
+;;; GNU Guix 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.
+;;;
+;;; GNU Guix 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 GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (cuirass zabbix)
+  #:use-module (guix import json)
+  #:use-module (web uri)
+  #:use-module (web client)
+  #:use-module (web response)
+  #:use-module (json)
+  #:use-module (rnrs bytevectors)
+  #:use-module (srfi srfi-11)
+  #:use-module (ice-9 match)
+  #:export (zabbix-api-version
+            zabbix-available?
+            zabbix-login
+            zabbix-logout
+            with-zabbix-connection
+            zabbix-host-id
+            zabbix-host-enabled?
+            zabbix-item-id
+            zabbix-item-value
+            zabbix-history))
+
+(define %zabbix-auth
+  (make-parameter #f))
+
+(define %zabbix-uri
+  (make-parameter
+   (getenv "CUIRASS_ZABBIX_URI")))
+
+(define %zabbix-user
+  (make-parameter
+   (or (getenv "CUIRASS_ZABBIX_USER") "Admin")))
+
+(define %zabbix-password
+  (make-parameter
+   (or (getenv "CUIRASS_ZABBIX_PASSWORD") "zabbix")))
+
+(define* (zabbix-request params)
+  (let ((headers `((User-Agent . "Cuirass")
+                   (Accept . "application/json")
+                   (Content-Type . "application/json"))))
+    (let-values (((response port)
+                  (http-post (%zabbix-uri)
+                             #:headers headers
+                             #:body (string->utf8
+                                     (scm->json-string params))
+                             #:streaming? #t)))
+      (cond ((= 200 (response-code response))
+             (let ((result (json->scm port)))
+               (close-port port)
+               (and result (assoc-ref result "result"))))
+            (else
+             (close-port port)
+             #f)))))
+
+(define* (zabbix-params method #:optional extra-params)
+  (let ((auth (%zabbix-auth)))
+    `(("jsonrpc" . "2.0")
+      ("method" . ,method)
+      ,@(if auth
+            `(("auth" . ,auth))
+            '())
+      ("params" . ,(or extra-params (vector)))
+      ("id" . 1))))
+
+(define (zabbix-type type)
+  (case type
+    ((float) 0)
+    ((character) 1)
+    ((log) 2)
+    ((unsigned) 3)
+    ((text) 4)))
+
+(define (zabbix-api-version)
+  (let* ((params (zabbix-params "apiinfo.version"))
+         (result (zabbix-request params)))
+    result))
+
+(define (zabbix-available?)
+  (and (%zabbix-uri)
+       (string? (zabbix-api-version))))
+
+(define (zabbix-login)
+  (let* ((params (zabbix-params "user.login"
+                                `(("user" . ,(%zabbix-user))
+                                  ("password" . ,(%zabbix-password)))))
+         (result (zabbix-request params)))
+    (%zabbix-auth result)
+    result))
+
+(define (zabbix-logout)
+  (let* ((params (zabbix-params "user.logout"))
+         (result (zabbix-request params)))
+    (%zabbix-auth #f)
+    result))
+
+(define-syntax-rule (with-zabbix-connection exp ...)
+  (dynamic-wind
+    (lambda ()
+      (zabbix-login))
+    (lambda ()
+      exp ...)
+    (lambda ()
+      (zabbix-logout))))
+
+(define (zabbix-host-search host)
+  (let* ((params (zabbix-params "host.get"
+                                `(("filter"
+                                   . (("host" . ,(vector host)))))))
+         (result (zabbix-request params)))
+    (match (vector->list result)
+      ((host) host)
+      (else #f))))
+
+(define (zabbix-host-id host)
+  (let ((host (zabbix-host-search host)))
+    (assoc-ref host "hostid")))
+
+(define (zabbix-host-enabled? host)
+  (let* ((host (zabbix-host-search host))
+         (status (assoc-ref host "status")))
+    (and status
+         (eq? (string->number status) 0))))
+
+(define (zabbix-item-search key host-id)
+  (let* ((params (zabbix-params "item.get"
+                                `(("hostids" . ,host-id)
+                                  ("search"
+                                   . (("key_" . ,key))))))
+         (result (zabbix-request params)))
+    (match (vector->list result)
+      ((item) item )
+      (else #f))))
+
+(define (zabbix-item-id key host-id)
+  (let ((item (zabbix-item-search key host-id)))
+    (assoc-ref item "itemid")))
+
+(define (zabbix-item-value key host-id)
+  (let ((item (zabbix-item-search key host-id)))
+    (assoc-ref item "lastvalue")))
+
+(define* (zabbix-history item-id #:key limit type)
+  (define (format-item item)
+    (let ((clock (assoc-ref item "clock"))
+          (value (assoc-ref item "value")))
+      (cons (string->number clock) (string->number value))))
+
+  (let* ((params (zabbix-params "history.get"
+                                `(("history" . ,(zabbix-type type))
+                                  ("itemids" . ,item-id)
+                                  ("sortfield" . "clock")
+                                  ("sortorder" . "DESC")
+                                  ("limit" . ,limit))))
+         (result (zabbix-request params)))
+    (map format-item (vector->list result))))



reply via email to

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