[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))))