Sharing the Stats

Junior

Good morning!

Senior

Just about time… We are in trouble!

The report stuff was a complete success, so much so that now Susan has hired a frontend developer to create a custom dashboard to see the stats in real time.

Now we have to provide the backend for the solution.

Junior

And what’s the problem?

Senior

We are not developers! What are we doing writing a backend?

Junior

Just chill out. Can’t be that difficult… What do they need, exactly?

Senior

We have to provide a new endpoint to serve the same data but in JSON format.

Junior

So, we have half of the work done already!

What about this?

kapow route add /capacitystats - <<-'EOF'
        echo "{\"memory\": \"`free -m`\"}"  | kapow set /response/body
EOF

Senior

For starters, that’s not valid JSON. The output would be something like:

$ echo "{\"memory\": \"`free -m`\"}"
{"memory": "              total        used        free      shared  buff/cache   available
Mem:          31967        3121       21680         980        7166       27418
Swap:             0           0           0"}

You can’t add new lines inside a JSON string that way, you have to escape the new line characters as \n.

Junior

Are you sure?

Senior

See for yourself.

$ echo "{\"memory\": \"`free -m`\"}" | jq
parse error: Invalid string: control characters from U+0000 through U+001F must be escaped at line 3, column 44

Junior

jq? What is that command?

Senior

jq is a wonderful tool for working with JSON data from the command line. With jq you can extract data from a JSON document and it also allows you to generate a well-formed JSON document.

Junior

Let’s use it, then!

How can we generate a JSON document with jq?

Senior

To generate a document we use the -n option:

$ jq -n '{"mykey": "myvalue"}'
{
  "mykey": "myvalue"
}

Junior

That does not seem very useful. The output is just the same.

Senior

Bear with me, it gets better. You can add variables to the JSON and jq will escape them for you.

$ jq -n --arg myvar "$(echo -n myvalue)" '{"mykey": $myvar}'
{
  "mykey": "myvalue"
}

Junior

Sweet! That’s just what I need.

(hacks away for a few minutes).

What do you think of this?

$ jq -n --arg host "$(hostname)" --arg date "$(date)" --arg memory "$(free -m)" --arg load "$(uptime)" --arg disk "$(df -h)" '{"hostname": $host, "date": $date, "memory": $memory, "load": $load, "disk": $disk}'
{
  "hostname": "junior-host",
  "date": "Tue 26 Nov 2019 05:27:24 PM CET",
  "memory": "              total        used        free      shared  buff/cache   available\nMem:          31967        3114       21744         913        7109       27492\nSwap:             0           0           0",
  "load": " 17:27:24 up 10:21,  1 user,  load average: 0.20, 0.26, 0.27",
  "disk": "Filesystem          Size  Used Avail Use% Mounted on\ndev                  16G     0   16G   0% /dev"
}

Senior

That is the output we have to produce, right. But the code is far from readable. And you also forgot about adding the endpoint.

Can we do any better?

Junior

That’s easy:

kapow route add /capacitystats - <<-'EOF'
        jq -n \
           --arg hostname "$(hostname)" \
           --arg date "$(date)" \
           --arg memory "$(free -m)" \
           --arg load "$(uptime)" \
           --arg disk "$(df -h)" \
           '{"hostname": $hostname, "date": $date, "memory": $memory, "load": $load, "disk": $disk}' \
        | kapow set /response/body
EOF

What do you think?

Senior

I’m afraid you forgot an important detail.

Junior

I don’t think so! the JSON is well-formed and it contains all the required data. And the code is quite readable.

Senior

You are right, but you are not using HTTP correctly. You have to set the Content-Type header to let your client know the format of the data you are outputting.

Junior

Oh, I see. Let me try again:

kapow route add /capacitystats - <<-'EOF'
        jq -n \
           --arg hostname "$(hostname)" \
           --arg date "$(date)" \
           --arg memory "$(free -m)" \
           --arg load "$(uptime)" \
           --arg disk "$(df -h)" \
           '{"hostname": $hostname, "date": $date, "memory": $memory, "load": $load, "disk": $disk}' \
        | kapow set /response/body
        echo application/json | kapow set /response/headers/Content-Type
EOF

Senior

Better. Just a couple of details.

  1. You have to set the headers before writing to the body. This is because the body can be so big that Kapow! is forced to start sending it out.

  2. In cases where you want to set a small piece of data (like the header), it is better not to use stdin. Kapow! provides a secondary syntax for these cases:

    $ kapow set <resource> <value>
    

Junior

Something like this?

kapow route add /capacitystats - <<-'EOF'
        kapow set /response/headers/Content-Type application/json
        jq -n \
           --arg hostname "$(hostname)" \
           --arg date "$(date)" \
           --arg memory "$(free -m)" \
           --arg load "$(uptime)" \
           --arg disk "$(df -h)" \
           '{"hostname": $hostname, "date": $date, "memory": $memory, "load": $load, "disk": $disk}' \
        | kapow set /response/body
EOF

Senior

That’s perfect! Now, let’s upload this to the Corporate Server and tell the frontend developer about it.