Checkmk1 is an IT monitoring software. I used to work for them some time ago and I still use it in my personal live. It provides a Web User Interface(UI) with dashboards to see the status of your monitored system. The way that UI queries they monitoring software is via the livestatus .

You can query it directly over the terminal using unixcat and in the documentation they explain how to implement a minimal Python client. The terminal is cumbersome, because the command line is one dimensional. It is hard to edit your queries. That is when scripts are useful, you have a file you can edit and then execute. Whether a simple Bash script or even the Python script the hosting scripting language gets on the way of your query.

It is then again much nicer to use the command line pipe, that way you have a file dedicated for your query and another program being the client to live status. That is the moment I think, it would certainly be better in Emacs1.

Entering Emacs

The goal is straightforward. Have a dedicated mode to edit livestatus queries and make it executable in some way to get the result.

Emacs can create a network progress, that is the way to connect to a socket. You get then the IO buffer. Setting up the connection to a local socket2 looks like this.

 1(defun cmk-oneshot (&optional keep-buffer)
 2  "One shot socket connection. non-nil KEEP-BUFFER after socket closes."
 3  (make-network-process
 4   :name "Checkmk"
 5   :host 'local
 6   :service 6557
 7   :buffer "CMK"
 8   :sentinel (lambda (process event)
 9               (message "Process: %s had the event '%s'" process (string-trim event))
10               (unless keep-buffer
11                 (kill-buffer (process-buffer process))))))

In this case I hard-coded where my livestatus socket is listening, which is a bad practice. I should better have a struct with the connection configuration. That is an exercise left to the reader.

The function that sends the query is simple.

1(defun cmk-livestatus-query (query)
2  "One shot livestatus QUERY, return network process."
3  (let ((cmks (cmk-oneshot 'keep)))
4    (with-current-buffer (process-buffer cmks) (erase-buffer))
5    (process-send-string cmks query)
6    cmks))

That one needs another wrapper to display the results in a readable way.

 1(defvar-local cmk-livestatus-query ""
 2  "Livestatus query in buffer.")
 3
 4(defun cmk-livestatus-get-csv-result (query)
 5  "Send QUERY to a new livestatus process.
 6Display result buffer in csv mode with aligned entries."
 7  (let ((cmk (cmk-livestatus-query query)))
 8    (switch-to-buffer (process-buffer cmk))
 9    (csv-set-separator ?\;)
10    (csv-align-mode)
11    (setq-local cmk-livestatus-query query)))

Abusing3 that Emacs-Lisp is a Lisp 2, I dare to call the buffer local variable that stores the query that generated the result the same as the function that performs the query against the livestatus socket.

Lastly I need to setup the buffer where I’ll edit the query and then pipe it to the query function.

 1(defun cmk-custom-livestatus-query ()
 2  "Open buffer to write a livestatus query."
 3  (interactive)
 4  (let ((query cmk-livestatus-query)
 5        (edit-buffer (get-buffer-create "*LQ*")))
 6    (with-current-buffer edit-buffer
 7      (cmk-livestatus-mode)
 8      (if (string-blank-p query) (insert "GET ") (insert query))
 9      (local-set-key "\C-c\C-c"
10                     (lambda ()
11                       (interactive)
12                       (let ((new-query (-> (buffer-string) (string-trim) (concat "\n\n"))))
13                         (kill-buffer)
14                         (cmk-livestatus-get-csv-result new-query)))))
15    (switch-to-buffer edit-buffer)))

For the convenience of editing the query in a dedicated buffer I still need to define cmk-livestatus-mode. At this point, I’ll only focus on syntax highlighting. Using regular expressions to highlight keywords.

 1(defconst cmk-livestatus-headers
 2  '("Filter" "Columns" "Stats" "Limit" "ColumnHeaders" "OutputFormat"))
 3
 4(define-derived-mode cmk-livestatus-mode prog-mode "livestatus"
 5  "Major mode to edit livestatus queries."
 6  (setq font-lock-defaults `(((,(rx bol "GET" eow) . font-lock-keyword-face)
 7                              (,(rx word-start (or "hosts" "services")  word-end) . font-lock-constant-face)
 8                              (,(concat "^" (regexp-opt cmk-livestatus-headers 'words) ":[[:space:]]") . font-lock-function-name-face)
 9                              (,(rx bol (or "And" "Or" "Negate") ":" whitespace) . font-lock-keyword-face)
10                              (,(rx whitespace (or "~" "=" "=~" "~~" "<" ">" "<=" ">=") whitespace) . font-lock-keyword-face)))))

That is all you need. Not it is super convenient to perform some quick queries to the monitoring server anytime you want. It is a little improvement, yet the true advantage comes with saved queries and displaying views. In essence, an alternative UI in Emacs. That comes in the next post.


  1. I know I’m abusing the availability of checkmk’s and Emacs’ SVG logo files to dare to draw a new image that puts them together through a jigsaw puzzle and make it the leading image of this post. I understand I shouldn’t modify logos, I hope in good will it showcases compatibility between the projects and nobody gets offended. In no way does this image represent that any of software projects considered or intended for them to work together or endorse the other. In the good will spirit of the GPL to study, modify and share, I daringly put them together. ↩︎ ↩︎

  2. Checkmk runs on a VPS. I build an ssh tunnel to it and configure the port forwarding to reach the livestatus port on that server. ↩︎

  3. Well it is a valid feature of the language and I have not yet decided if it is a good and useful practice. ↩︎