[RELEASE] Prometheus device metrics / hubitat2prom in C#

If I compare the info about COMMAND from

docker ps --no-trunc

for this c sharp version vs the python version it looks like this:
C#: "./hubitat2prom --urls=http://0.0.0.0:80"
Python: "/bin/sh -c 'gunicorn -w 4 -b 0.0.0.0:5000 app:app'"

Also, for all other containers (except this hubitat2promCsharp) the command is running in the root (/bin usually), whereas the C# version is running in ./ - I don't know if that could be an issue?

Or if there's a mismatch between the contnainer name and the command? I have added "Csharp" to the container name now that I'm trying to run both at the same time (I actually fixed the Python version, but I'd still like to try this one to see if performance improves).

Thanks for sharing the file contents. I'll see what I can find with that. I don't usually use Docker Compose, instead relying just on writing a Dockerfile and the CLI. That said, there is no reason Docker Compose wouldn't work!

If I compare the info about COMMAND from

docker ps --no-trunc

for this c sharp version vs the python version it looks like this:
C#: "./hubitat2prom --urls=http://0.0.0.0:80"
Python: "/bin/sh -c 'gunicorn -w 4 -b 0.0.0.0:5000 app:app'"

Also, for all other containers (except this hubitat2promCsharp) the command is running in the root (/bin usually), whereas the C# version is running in ./ - I don't know if that could be an issue?

Good observation, but this isn't the issue per se. Forgive me if you're already aware of this; let me provide some background on what's going on here.

Python and .NET applications (.NET being the runtime for C#) execute differently - and this is especially true for many Python web applications. The Python hubitat2prom uses WSGI through gunicorn. gunicorn essentially acts as a web server, and sends HTTP requests to hubitat2prom. So what you're seeing above is that Docker runs a shell command (/bin/sh -c ...) which then starts gunicorn for the application named app. The particulars of how that works are married to how Python modules work, if you're interested in learning more about that. Regarding Docker, review the Python hubitat2prom Dockerfile and the Docker CMD directive to better understand how Docker starts the Python hubitat2prom.

The .NET application, on the other hand, is executed directly. The runtime (.NET), the webserver (ASP.NET), and the application (hubitat2prom) are all embedded in the executable called hubitat2prom. As a result, Docker can use that single executable as the entrypoint for the Docker container. You can see how that is set up in the Dockerfile, which uses the ENTRYPOINT directive.

Or if there's a mismatch between the contnainer name and the command? I have added "Csharp" to the container name now that I'm trying to run both at the same time (I actually fixed the Python version, but I'd still like to try this one to see if performance improves).

That shouldn't be a problem. The container name is arbitrary and does not affect what the container is doing. That said, I don't know a lot about Docker Compose so perhaps there is something unexpected there.

I'd still like to try this one to see if performance improves

I'm really curious to know how it works out for you! I have not done a ton of work with optimization in this application. The C# hubitat2prom also supports many more devices and attributes that may or may not cause efficiency issues. Please do let me know what you discover!


Okay, so - onto Docker Compose. I made one minor change to your compose.yaml, which is to add a services section at the top. It now looks like this:

services:
  hubitat2promCsharp:
    image: aholmes0/hubitat2prom:v1.2.3
    platform: linux/amd64
    container_name: hubitat2promCsharp
    env_file:
    - './.envCsharp'
    ports:
    - 8080:80
    restart: unless-stopped

I also changed the platform to amd64 for my initial testing, which appears to work. The application starts and responds to requests:

$ docker-compose up
Creating network "temp_default" with the default driver
Pulling hubitat2promCsharp (aholmes0/hubitat2prom:v1.2.3)...
v1.2.3: Pulling from aholmes0/hubitat2prom
578acb154839: Pull complete
b5c13ee662c3: Pull complete
b87d321f96b6: Pull complete
27ff45226a4f: Pull complete
8543982bc2b3: Pull complete
88e622ea1fdf: Pull complete
e7502f5de4f2: Pull complete
73a66c771ce6: Pull complete
Digest: sha256:b0cab87124c63973dcc9b27061567b36aba4c2652fe0946832be4ed67bad5894
Status: Downloaded newer image for aholmes0/hubitat2prom:v1.2.3
Creating hubitat2promCsharp ... done
Attaching to hubitat2promCsharp
hubitat2promCsharp    | warn: Microsoft.AspNetCore.Hosting.Diagnostics[15]
hubitat2promCsharp    |       Overriding HTTP_PORTS '8080' and HTTPS_PORTS ''. Binding to values defined by URLS instead 'http://0.0.0.0:80'.
hubitat2promCsharp    | info: Microsoft.Hosting.Lifetime[14]
hubitat2promCsharp    |       Now listening on: http://0.0.0.0:80
hubitat2promCsharp    | info: Microsoft.Hosting.Lifetime[0]
hubitat2promCsharp    |       Application started. Press Ctrl+C to shut down.
hubitat2promCsharp    | info: Microsoft.Hosting.Lifetime[0]
hubitat2promCsharp    |       Hosting environment: Production
hubitat2promCsharp    | info: Microsoft.Hosting.Lifetime[0]
hubitat2promCsharp    |       Content root path: /app
hubitat2promCsharp    | info: System.Net.Http.HttpClient.Default.LogicalHandler[100]
hubitat2promCsharp    |       Start processing HTTP request GET http://192.168.50.22/apps/api/712/devices/all?access_token=f63543c2-0d44-483c-bc52-ae79e82cb88d
hubitat2promCsharp    | info: System.Net.Http.HttpClient.Default.ClientHandler[100]
hubitat2promCsharp    |       Sending HTTP request GET http://192.168.50.22/apps/api/712/devices/all?access_token=f63543c2-0d44-483c-bc52-ae79e82cb88d
hubitat2promCsharp    | info: System.Net.Http.HttpClient.Default.ClientHandler[101]
hubitat2promCsharp    |       Received HTTP response headers after 257.3691ms - 200
hubitat2promCsharp    | info: System.Net.Http.HttpClient.Default.LogicalHandler[101]
hubitat2promCsharp    |       End processing HTTP request after 268.6607ms - 200

My .envCsharp file contains:

HE_URI=http://<redacted>/apps/api/712/devices
HE_TOKEN=<redacted>
HE_METRICS=battery,humidity,illuminance,level,switch,temperature,heatingSetpoint,thermostatSetpoint,thermostatFanMode,thermostatOperatingState,thermostatMode,coolingSetpoint,power,energy,current,voltage,saturatedDepthOfWater,depthOfWater,lastWateredDuration,zoneTotalDuration,efficiency,availableWater,watering,cpu5min,cpuPct,freeMemory,commStatus,flowStatus,presence,water,contact

So - linux/amd64 is working.

And it seems linux/arm64 is not:

$ sudo docker compose up
[+] Running 1/1
 โœ” Container hubitat2promCsharp  Recreated                                                                     40.6s
Attaching to hubitat2promCsharp
hubitat2promCsharp  | exec ./hubitat2prom: no such file or directory
^CGracefully stopping... (press Ctrl+C again to force)
Aborting on container exit...
[+] Stopping 1/1
 โœ” Container hubitat2promCsharp  Stopped                                                                       45.8s
canceled

Interesting! I must have messed up the multi-arch build.

I've tried a few things to get the multi-arch build working, and I even made an image just for arm64 (aholmes0/hubitat2prom:v1.2.3-arm64), yet it still fails to run, unlike the amd64 image. Quite perplexing!

$ docker run -it --rm aholmes0/hubitat2prom:v1.2.3-arm64
Unable to find image 'aholmes0/hubitat2prom:v1.2.3-arm64' locally
v1.2.3-arm64: Pulling from aholmes0/hubitat2prom
578acb154839: Pull complete
b5c13ee662c3: Pull complete
b87d321f96b6: Pull complete
27ff45226a4f: Pull complete
8543982bc2b3: Pull complete
88e622ea1fdf: Pull complete
0fe5063d65ae: Pull complete
0c0be03f4798: Pull complete
Digest: sha256:b1b4fd1afa8145ef7f2630a2823befb8c36021d575cbf9321c91444b6e48ec56
Status: Downloaded newer image for aholmes0/hubitat2prom:v1.2.3-arm64
exec ./hubitat2prom: no such file or directory

I'm pausing on this for now as life needs attention, but I will get back to this and figure it out. In the mean time, you can always download the code and get it running with dotnet if you're feeling adventurous!

Ok, thanks a lot for the detailed description! It makes sense what you're saying, and it was all new info for me so thanks a lot!

Great! At least I know that it's most likely not on my side that the problem lies. I'll see if I try with running it with dotnet...I don't think I have the time (read "skills") to try that right now...after all, the python version works decently so I can get by with that for now.

Thanks a lot for your effort!

Disclaimer: I'm very new to Prometheus

Is anyone using this to get stats from the hub information driver into Prometheus? I'd love to be able to see load and free memory over time. I can see the hub temperature from that driver in Prom, but none of its other stats. (or I may not know how to discover those stats)

Hi @chowell,

Yep! The "Hub Information" virtual device is treated like any other: its attributes are ingested by Prometheus as metrics, and the "device_name" label to match is whatever you named your device (mine is "Hub Information," so the label is hub_information).

To collect these metrics, you will need to add them to the HE_METRICS environment variable. For example, here's the command I use to start hubitat2prom. I've highlighted freeMemory.

Now, all of that said ... it seems there may be a bug I hadn't noticed. I am collecting freeMemory, but it's value has been 0 all this time. It comes back correctly from the Maker API so I'm certain the problem is in hubitat2prom somewhere. I've opened an issue to keep it on my radar. If you can give it a try too and report back, that'll help me determine whether it's just my system.

1 Like

Adding it to HE_metrics is what I was missing. Thank you

1 Like

FWIW, I am also seeing the issue with FreeMemory being recorded as 0 even though that's not what MakerAPI reports.

Thanks for letting me know! I will see whether I can get to this today.

Hi @chowell,

I've pushed v1.2.4 to both GitHub and Dockerhub. Please give either one of these a go and let me know whether it works for you. Please note the multi-arch Docker image is still experimental; I'd be curious to know whether it works for you should you decide to use it.

hmmm.... I'm still getting 0's out of it. I'm using the amd64 docker image.

Thanks for letting me know! I wasn't able to test the change locally yesterday, so I totally missed an aspect of the issue.

Anyway, it's now fixed in v1.2.5. I also fixed the multi-arch Docker image, so you can try running aholmes0/hubitat2prom:v1.2.5 instead of aholmes0/hubitat2prom:v1.2.5-arm64 if you'd like. It's working on my Pi, at least!

Working now. Thanks!

1 Like

Hi @wentzel,

I revisited the multi-arch Docker image and it seems to work now. Give aholmes0/hubitat2prom:v1.2.5 a go if you'd like to use it.

One more question.... Does hubitat2prom support presence devices? It looks like it comes through to Prometheus, but all values are 0. Some quick googling makes it sound we just need an enum to translate "present" and "not present" to 1 and 0 respectively.

Good catch, thank you. I've released v1.2.6 with the associated change.

Docker image

When I'm able to make more time for the project, I am actively refactoring to avoid these kinds of issues. This happened because, at the time I wrote it, I did not have any devices with "presence" and so wasn't aware to include it. You can follow along in the main branch if interested. Stay tuned~

1 Like

And its working! Thank you!

1 Like