<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Uptimekuma on blog.iankulin.com</title><link>https://blog.iankulin.com/tags/uptimekuma/</link><description>Recent content in Uptimekuma on blog.iankulin.com</description><generator>Hugo</generator><language>en-AU</language><lastBuildDate>Thu, 03 Sep 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.iankulin.com/tags/uptimekuma/index.xml" rel="self" type="application/rss+xml"/><item><title>JSONata in Uptime Kuma</title><link>https://blog.iankulin.com/jsonata-in-kuma/</link><pubDate>Thu, 03 Sep 2026 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/jsonata-in-kuma/</guid><description>&lt;p&gt;When I wrote a &lt;a href="https://github.com/IanKulin/vitals-glimpse"&gt;simple metrics reporting utility&lt;/a&gt;, I had it output keywords triggered by thresholds so they could be checked for by &lt;a href="https://github.com/louislam/uptime-kuma"&gt;Uptime Kuma&lt;/a&gt;. Here&amp;rsquo;s some sample output:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;title&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;vitals-glimpse&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;version&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#b48ead"&gt;0.6&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;hostname&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;ct350-boinc&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;mem_status&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;mem_fail&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;mem_percent&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#b48ead"&gt;96&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;disk_status&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;disk_okay&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;disk_percent&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#b48ead"&gt;49&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;cpu_status&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;cpu_okay&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;cpu_percent&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#b48ead"&gt;4&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;disk_iops_status&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;disk_iops_okay&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;disk_iops&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#b48ead"&gt;0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Uptime Kuma has the ability to check for keywords, so in the case above we might set up a monitor for &lt;em&gt;HTTP(s) Keyword&lt;/em&gt; looking for &lt;code&gt;disk_okay&lt;/code&gt;. The monitor has an internal threshold, and when the &lt;code&gt;disk_percent&lt;/code&gt; goes over it, the &lt;code&gt;disk_status&lt;/code&gt; keyword will switch to &lt;code&gt;disk_fail&lt;/code&gt; and the Uptime Kima monitor will trigger because it can no longer find the keyword it&amp;rsquo;s looking for.&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/jsonata-in-kuma-keyword_hu_a6686cc5ab17bb6d.webp" width="927" height="889" alt="Screenshot of Uptime Kuma keyword settings" loading="eager" fetchpriority="high" decoding="async"&gt;&lt;/p&gt;&#10;&lt;p&gt;I included the numeric values in the JSON output since it was cheap and helped humans.&lt;/p&gt;&#10;&lt;h2 id="the-problem"&gt;The problem&lt;/h2&gt;&#10;&lt;p&gt;The JSON above is from an LXC running in my homelab. The workload is BOINC (Berkeley Open Infrastructure for Network Computing) which is a SETI@Home type distributed computing model run by &lt;a href="www.worldcommunitygrid.org"&gt;World Community Grid&lt;/a&gt;. I&amp;rsquo;m effectively donating a tiny amount of compute to their projects (currently mapping cancer markers or similar). Since most of their workloads require a GPU which I don&amp;rsquo;t have on this mouse powered server, they don&amp;rsquo;t send me much work, and often it will be idle for weeks.&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/jsonata-in-kuma-boinc-history_hu_e015c1af4f8582d.webp" width="600" height="233" alt="Graph of BOINC workloads over last 90 days" loading="lazy" decoding="async"&gt;&lt;/p&gt;&#10;&lt;p&gt;Usually the workloads are very cpu intensive - pinning a core for a few seconds, then dropping back to a fraction of that (I guess while it sends that result), but previously the memory use has been low - around 200MB.&lt;/p&gt;&#10;&lt;p&gt;Something must have changed recently, since my 90% memory threshold monitor was triggered. It was reporting 96% used. I doubled the LXC memory from 256MB to 512MB, and it immediately used 96% of that. So I guess this is going to be the new normal, and I should just change my threshold to 97% for this machine.&lt;/p&gt;&#10;&lt;p&gt;I don&amp;rsquo;t actually remember how I run vitals-glimpse on this machine, it&amp;rsquo;s installed with an Ansible playbook, so probably systemd. I&amp;rsquo;m too lazy to figure that out and look up my own docs for that and the command line flag to set the threshold. What I&amp;rsquo;d rather do is some click-ops in Uptime Kuma and get it to read the value and test it against a threshold.&lt;/p&gt;&#10;&lt;p&gt;Luckily, since I&amp;rsquo;ve started using Uptime Kuma, that exact feature has been added - I think in about v1.23. It uses a great library: &lt;a href="https://jsonata.org"&gt;JSONata&lt;/a&gt;. This is query language against a JSON object - Uptime Kuma will fetch the JSON, the apply your JSONata query to it, and test the result of that query against a threshold:&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/jsonata-in-kuma-keyword-json-query_hu_7fb68e43c0f1d118.webp" width="931" height="1193" alt="Screenshot of Uptime Kuma JSON query settings" loading="lazy" decoding="async"&gt;&lt;/p&gt;&#10;&lt;p&gt;This is a very simple query for JSONata - we could have just as well done something more complicated such as using a memory threshold much lower IF the cpu was low, but allowing it to scale up to 97% if the cpu went over 50%&lt;/p&gt;&#10;&lt;p&gt;with:&#10;&lt;code&gt;((cpu_percent&amp;lt;50) and (mem_percent&amp;lt;80)) or ((cpu_percent&amp;gt;49) and (mem_percent&amp;lt;96))&lt;/code&gt;&#10;then I&amp;rsquo;d test that against &lt;code&gt;== true&lt;/code&gt;&lt;/p&gt;&#10;&lt;p&gt;JSONata deals neatly with arrays and nesting, so you can imagine the queries start to get complicated looking. To test your logic the author, Andrew Coleman, has a &lt;a href="https://try.jsonata.org"&gt;JSONata Exerciser&lt;/a&gt; where you can paste some JSON and try queries against it. Andrew also has a video with clear explanations to get you going.&lt;/p&gt;&#10;&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;&#10;&#9;&#9;&#9;&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/ZBaK40rtIBM?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;&#10;&#9;&#9;&lt;/div&gt;&#10;&#10;</description></item></channel></rss>