Giter VIP home page Giter VIP logo

psmqtt's Introduction

Summary

PSMQTT is a cross-platform utility for reporting system and processes metrics (CPU, memory, disks, network, S.M.A.R.T. disk data) to an MQTT broker.

PSMQTT is written in Python and is based on:

Installation with pip

Clone this repository and then install the required Python libraries using pip:

pip install -r requirements.txt

(Note that you should consider installing the required libraries in a Python venv to maintain them isolated from the rest of your Python installation. See this page for more info.)

Finally you can run PSMQTT using:

python psmqtt.py

You may want to install PSMQTT as a service/system-daemon, see this page for more info.

Installing on FreeBSD

See FreeBSD doc

Installing on Windows

See Windows doc

Deploy with Docker

PSMQTT is packaged also a multi-arch Docker image. If your system does not have Python3 installed or you don't want to install PSMQTT Python dependencies in your environment, you can launch a Docker container:

docker run -d -v <your config file>:/opt/psmqtt/conf/psmqtt.conf \
   --privileged --hostname $(hostname) \
   ghcr.io/eschava/psmqtt:latest

General information about tasks and MQTT topics

Every utilization task has its own special name. It always starts with task name that could be followed with unique parameter name or/and device number/name.

E.g. task to get percent of CPU used by user apps use name cpu_times_percent/user. All possible tasks are described below.

Results for task TASK are pushed to the MQTT topic psmqtt/COMPUTER_NAME/TASK (prefix "psmqtt/COMPUTER_NAME/" is configurable)

In case of task execution error message is sent to the topic psmqtt/COMPUTER_NAME/error/TASK

Very often it could be useful to provide several parameters from the same task using one request. In such case next formats are used:

  • TASK/* - to get all possible parameters (MQTT topic per parameter)
  • or TASK/*; - to get all possible parameters in one topic (combined to JSON string)

Examples:

Task cpu_times_percent/* provides
psmqtt/COMPUTER_NAME/cpu_times_percent/user 12.0
psmqtt/COMPUTER_NAME/cpu_times_percent/nice  1.0
psmqtt/COMPUTER_NAME/cpu_times_percent/system 5.0
etc

Task cpu_times_percent/*; provides
psmqtt/COMPUTER_NAME/cpu_times_percent/*; {"user": 12.0, "nice": 1.0, "system": 5.0, ...}

Formatting output

Output of task could be formatted using Jinja2 templates. Append template to the task after one more "/" separator.

E.g.
cpu_times_percent/user/{{x}}%

To append % symbol after CPU usage.

For task providing many parameters (having *) all parameters are available by name if they are named or by index as x[1] if they are numbered.

NOTE: After formatting tasks providing many parametes are combined to single one.

Unnamed parameters are avaiable as x.

All additional filters are defined at the filters.py file. You also can add custom filters there.

Next filters are implemented now:

KB,MB,GB - to format value in bytes as KBytes, MBytes or GBytes.
uptime - to format boot_time as uptime string representation.

Examples:

virtual_memory/*/{{(100*free/total)|int}}% - free virtual memory in %
boot_time/{{x|uptime}} - uptime
cpu_times_percent/user/*/{{x[0]+x[1]}} - user CPU times for first and second processors total
virtual_memory/free/{{x|MB}} - Free RAM in MB

Configuration

All configuration is present in psmqtt.conf file in the app's directory or any other file referenced by PSMQTTCONFIG environment variable.

The configuration file is parsed using Python interpreter and contains constants for MQTT broker connection and tasks that have to be executed periodically (schedule).

There are two ways how to force sending some system state parameter over MQTT topic

  1. Schedule
  2. MQTT request

Schedule

schedule parameter in psmqtt.conf is a Python map having human-readable period as a key and task name (or list of task names) as a value.

You can check examples of recurring period definitions here.

Also value for scheduled task could be specified as Python dict ({"key": "value"} notation) to send result to the topic different to the task name.

E.g. {"boot_time/{{x|uptime}}": "uptime"} to have boot time posted to the psmqtt/COMPUTER_NAME/uptime topic.

NOTE: Please note that keys in Python dict (schedule) should be unique and if there are several schedules with the same period - only last one will be used. To avoid such issue please use period mapped to the list (or dict) of tasks.

MQTT request

It's better to describe how to use it using example. To get information for task "cpu_percent" with MQTT prefix "psmqtt/COMPUTER_NAME/" you need to send any string on topic:

psmqtt/COMPUTER_NAME/request/cpu_percent

and result will be pushed on the topic:

psmqtt/COMPUTER_NAME/cpu_percent

Tasks

CPU

cpu_times/* - CPU times information. Topic per parameter
cpu_times/*;  - CPU times information in one topic (JSON string)
cpu_times/{user/nice/system/idle/iowait/irq/softirq/steal/guest} - CPU times separate parameters
cpu_percent - CPU total usage in percent
cpu_percent/* - CPU usage in percent. Topic per CPU number
cpu_percent/*; - CPU usage in percent per CPU in one topic (JSON string)
cpu_percent/{0/1/2/etc} - CPU usage for single CPU
cpu_times_percent/* - CPU times in percent. Topic per parameter
cpu_times_percent/*;  - CPU times in percent in one topic (JSON string)
cpu_times_percent/{user/nice/system/idle/iowait/irq/softirq/steal/guest} - CPU times in percent separate parameters
cpu_times_percent/{user/nice/system/idle/iowait/irq/softirq/steal/guest}/* - CPU times in percent separate parameters. Topic per CPU number
cpu_times_percent/{user/nice/system/idle/iowait/irq/softirq/steal/guest}/*; - CPU times in percent separate parameters per CPU number in one topic (JSON string)
cpu_times_percent/{user/nice/system/idle/iowait/irq/softirq/steal/guest}/{0/1/2/etc} - CPU times in percent separate parameters for single CPU
cpu_times_percent/*/{0/1/2/etc} - CPU times in percent for single CPU. Topic per parameter
cpu_times_percent/*;/{0/1/2/etc} - CPU times in percent for single CPU in one topic (JSON string)
cpu_stats/* - CPU statistics. Topic per parameter
cpu_stats/*;  - CPU statistics in one topic (JSON string)
cpu_stats/{ctx_switches/interrupts/soft_interrupts/syscalls} - CPU statistics separate parameters

Memory

virtual_memory/* - Virtual memory. Topic per parameter
virtual_memory/*;  - Virtual memory in one topic (JSON string)
virtual_memory/{total/available/percent/used/free/active/inactive/buffers/cached} - Virtual memory separate parameters
swap_memory/* - Swap memory. Topic per parameter
swap_memory/*;  - Swap memory in one topic (JSON string)
swap_memory/{total/used/free/percent/sin/sout} - Swap memory separate parameters

Disks

disk_partitions/{device/mountpoint/fstype/opts}/* - Disk partitions separate parameters. Topic per disk number
disk_partitions/{device/mountpoint/fstype/opts}/*; - Disk partitions separate parameters per disk number in one topic (JSON string)
disk_partitions/{device/mountpoint/fstype/opts}/{0/1/2/etc} - Disk partitions separate parameter for single disk number
disk_partitions/*/{0/1/2/etc} - Disk partitions parameters for single disk number. Topic per parameter
disk_partitions/*;/{0/1/2/etc} - Disk partitions parameters for single disk number in one topic (JSON string)
disk_usage/{total/used/free/percent}/{drive} - Disk usage single parameter (slashes in drive should be replaced with vertical slash)
disk_usage/*/{drive} - Disk usage separate parameters. Topic per parameter
disk_usage/*;/{drive} - Disk usage separate parameters in one topic (JSON string)
disk_io_counters/* - Disk I/O counters. Topic per parameter
disk_io_counters/*;  - Disk I/O counters in one topic (JSON string)
disk_io_counters/{read_count/write_count/read_bytes/write_bytes/read_time/write_time/read_merged_count/write_merged_count/busy_time} - Disk I/O counters separate parameters
disk_io_counters/{read_count/write_count/read_bytes/write_bytes/read_time/write_time/read_merged_count/write_merged_count/busy_time}/* - Disk I/O counters separate parameters. Topic per disk number
disk_io_counters/{read_count/write_count/read_bytes/write_bytes/read_time/write_time/read_merged_count/write_merged_count/busy_time}/*; - Disk I/O counters separate parameters per disk number in one topic (JSON string)
disk_io_counters/{read_count/write_count/read_bytes/write_bytes/read_time/write_time/read_merged_count/write_merged_count/busy_time}/{0/1/2/etc} - Disk IO counters separate parameters for single disk
disk_io_counters/*/{0/1/2/etc} - Disk I/O counters for single disk. Topic per parameter
disk_io_counters/*;/{0/1/2/etc} - Disk I/O counters for single disk in one topic (JSON string)

Network

net_io_counters/* - Network I/O counters. Topic per parameter
net_io_counters/*;  - Network I/O counters in one topic (JSON string)
net_io_counters/{bytes_sent/bytes_recv/packets_sent/packets_recv/errin/errout/dropin/dropout} - Network I/O counters separate parameters
net_io_counters/{bytes_sent/bytes_recv/packets_sent/packets_recv/errin/errout/dropin/dropout}/* - Network I/O counters separate parameters. Topic per device name
net_io_counters/{bytes_sent/bytes_recv/packets_sent/packets_recv/errin/errout/dropin/dropout}/*; - Network I/O counters separate parameters per device in one topic (JSON string)
net_io_counters/{bytes_sent/bytes_recv/packets_sent/packets_recv/errin/errout/dropin/dropout}/{eth0/wlan0/etc} - Network I/O counters separate parameters for single device
net_io_counters/*/{eth0/wlan0/etc} - Network I/O counters for single device. Topic per parameter
net_io_counters/*;/{eth0/wlan0/etc} - Network I/O counters for single device in one topic (JSON string)

Temperature

sensors_temperatures/* - Sensors current temperatures. Topic per sensor
sensors_temperatures/*;  - Sensors current temperatures in one topic (JSON string)
sensors_temperatures/{SENSOR_NAME} - Single sensor current temperature (could be array value if sensor has several devices)
sensors_temperatures/{SENSOR_NAME}/* - Single sensor temperatures. Topic per temperature
sensors_temperatures/{SENSOR_NAME}/*; - Single sensor temperatures in one topic (JSON string)
sensors_temperatures/{SENSOR_NAME}/{DEVICE_NUMBER/DEVICE_LABEL} - Single sensor device by number/label current temperature
sensors_temperatures/{SENSOR_NAME}/{DEVICE_NUMBER/DEVICE_LABEL}/* - Single sensor device by number/label temperature. Topic per parameter
sensors_temperatures/{SENSOR_NAME}/{DEVICE_NUMBER/DEVICE_LABEL}/*; - Single sensor device by number/label temperature in one topic (JSON string)
sensors_temperatures/{SENSOR_NAME}/{DEVICE_NUMBER/DEVICE_LABEL}/{label/current/high/critical} - Single sensor device by number/label temperature separate parameters

Fan speed

sensors_fans/* - Fans current speeds. Topic per fan
sensors_fans/*;  - Fans current speeds in one topic (JSON string)
sensors_fans/{SENSOR_NAME} - Single fan current speed (could be array value if fan has several devices)
sensors_fans/{SENSOR_NAME}/* - Single fan speeds. Topic per speed
sensors_fans/{SENSOR_NAME}/*; - Single fan speeds in one topic (JSON string)
sensors_fans/{SENSOR_NAME}/{DEVICE_NUMBER/DEVICE_LABEL} - Single fan device by number/label current speed
sensors_fans/{SENSOR_NAME}/{DEVICE_NUMBER/DEVICE_LABEL}/* - Single fan device by number/label speed. Topic per parameter
sensors_fans/{SENSOR_NAME}/{DEVICE_NUMBER/DEVICE_LABEL}/*; - Single fan device by number/label speed in one topic (JSON string)
sensors_fans/{SENSOR_NAME}/{DEVICE_NUMBER/DEVICE_LABEL}/{label/current/high/critical} - Single fan device by number/label speed separate parameters

Battery

sensors_battery/* - Battery state. Topic per parameter
sensors_battery/*;  - Battery state parameters in one topic (JSON string)
sensors_battery/{percent/secsleft/power_plugged} - Battery state separate parameters
     where secsleft could be
         -1 if time is unknown
         -2 for unlimited time (power is plugged)
         or time in seconds

Other system info

users/{name/terminal/host/started}/* - Active users separate parameters. Topic per user
users/{name/terminal/host/started}/*; - Active users separate parameters per user in one topic (JSON string)
users/{name/terminal/host/started}/{0/1/2/etc} - Active users separate parameter for single user
users/*/{0/1/2/etc} - Active users parameters for single user. Topic per parameter
users/*;/{0/1/2/etc} - Active users parameters for single user in one topic (JSON string)
boot_time - System boot time as a Unix timestamp
boot_time/{{x|uptime}} - String representation of up time

Processes

pids/* - all system processes IDs. Topic per process
pids/*; - all system processes IDs in one topic (JSON string)
pids/{0/1/2/etc} - single process ID
pids/count - total number of processes
processes/{PROCESS_ID}/{PARAMETER_NAME} - single process parameter(s)
    where PROCESS_ID could be one of
        - numeric ID of the process
        - top_cpu - top CPU consuming process
        - top_cpu[N] - CPU consuming process number N
        - top_memory - top memory consuming process
        - top_memory[N] - memory consuming process number N
        - pid[PATH] - process with ID specified in the file having PATH path (.pid file). Slashes in path should be replaced with vertical slash
        - name[PATTERN] - process with name matching PATTERN pattern (use * to match zero or more characters, ? for single character)
        - * - to get value of some property for all processes. Topic per process ID
        - *; - to get value of some property for all processes in one topic (JSON string)
    and PARAMETER_NAME could be one of
        - pid - process ID
        - ppid - parent process ID
        - name - process name
        - exe - process executable file
        - cwd - process working directory
        - cmdline/* - command line. Topic per line
        - cmdline/*; - command line in one topic (JSON string)
        - cmdline/count - number of command line lines
        - cmdline/{0/1/etc} - command line single line
        - status - process status (running/sleeping/idle/dead/etc)
        - username - user started process
        - create_time - time when process was started (Unix timestamp)
        - terminal - terminal of the process
        - uids/* - process user IDs. Topic per parameter
        - uids/*; - process user IDs in one topic (JSON string)
        - uids/{real/effective/saved} - process user IDs single parameter
        - gids/* - process group IDs. Topic per parameter
        - gids/*; - process group IDs in one topic (JSON string)
        - gids/{real/effective/saved} - process group IDs single parameter
        - cpu_times/* - process CPU times. Topic per parameter
        - cpu_times/*; - process CPU times in one topic (JSON string)
        - cpu_times/{user/system/children_user/children_system} - process CPU times single parameter
        - cpu_percent - CPU percent used by process
        - memory_percent - memory percent used by process
        - memory_info/* - memory used by process. Topic per parameter
        - memory_info/*; - memory used by process in one topic (JSON string)
        - memory_info/{rss/vms/shared/text/lib/data/dirty/uss/pss/swap} - memory used by process single parameter
        - io_counters/* - process I/O counters. Topic per parameter
        - io_counters/*; - process I/O counters in one topic (JSON string)
        - io_counters/{read_count/write_count/read_bytes/write_bytes} - process I/O single counter
        - num_threads - number of threads
        - num_fds - number of file descriptors
        - num_ctx_switches/* - number of context switches. Topic per parameter
        - num_ctx_switches/*; - number of context switches in one topic (JSON string)
        - num_ctx_switches/{voluntary/involuntary} - context switches single counter
        - nice - nice value
        - * - all process properties. Topic per property
        - *; - all process properties in one topic (JSON string)
        - ** - all process properties and sub-properties. Topic per property
        - **; -  all process properties and sub-properties in one topic (JSON string)

Useful tasks

boot_time/{{x|uptime}} - Up time

cpu_percent - CPU usage in percent

virtual_memory/percent - RAM usage in percent

virtual_memory/free/{{x|MB}} - Free RAM in MB

disk_usage/percent/| - root drive (slash replaced with vertical slash) usage in percent (Linux)

disk_usage/free/|/{{x|GB}} - space left in GB for root drive (Linux)

disk_usage/percent/C: - C:/ drive usage in percent (Windows)

processes/top_cpu/name - name of top process consuming CPU

processes/top_memory/exe - executable file of top process consuming memory

smart/nvme0/ - all the device 'nvme0' SMART data, requries SUDO

smart/nvme0/temperature - 'nvme0' temperature, requires SUDO

Creating a new release

New docker releases will be automatically published by the GitHub CI whenever a new tag is released on the project. Sometimes however it may be useful to publish a docker release manually. To push manually a new multi-arch docker version, use:

docker buildx build --platform linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8, --tag ghcr.io/eschava/psmqtt:1.0.2 --build-arg USERNAME=root --push .

(remember to update the tag version)

psmqtt's People

Contributors

asokolsky avatar barbudor avatar chavaone avatar dependabot[bot] avatar eschava avatar f18m avatar mbuette avatar osos avatar shmick avatar stefanschornevorait avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

psmqtt's Issues

New Feature: Docker monitoring

Would it be possible to get some simple monitoring of docker?

Would like to see how many containers and then how many that are up and running of those registered.
For instance, this example below would show 7 total, 5 running, 2 stopped.

riro@photon01 [ ~ ]# sudo docker ps --all

CONTAINER ID   IMAGE                              COMMAND                  CREATED        STATUS                      PORTS                                                                   NAMES
9a7a33c7732e   txn2/irsync                        "/irsync --irsync-in…"   40 hours ago   Exited (2) 57 seconds ago                                                                           irsync
62f98e15b6d6   containrrr/watchtower              "/watchtower"            40 hours ago   Up 40 hours                 0.0.0.0:8880->8080/tcp, :::8880->8080/tcp                               watchtower
7bd8a4c9dcc3   telegraf                           "/entrypoint.sh tele…"   40 hours ago   Exited (0) 51 seconds ago                                                                           telegraf
f73f29272a9e   networkstatic/iperf3               "iperf3 -s"              40 hours ago   Up 40 hours                 0.0.0.0:5201->5201/tcp, :::5201->5201/tcp                               iperf3
ec1dde90cfdd   portainer/agent                    "./agent"                40 hours ago   Up 40 hours                 0.0.0.0:9001->9001/tcp, :::9001->9001/tcp                               portainer-agent
2331405e738c   senexcrenshaw/xteve                "/bin/sh -c '${XTEVE…"   5 days ago     Up 5 days                   0.0.0.0:34400->34400/tcp, :::34400->34400/tcp                           xteve
de276fc2cd91   tautulli/tautulli                  "./start.sh python T…"   5 days ago     Up 5 days (healthy)         0.0.0.0:8181->8181/tcp, :::8181->8181/tcp                               tautulli

Error on mac OS X 10.11.6

Hello here is error on start
ed@iMac-Ed > ~/Documents/GitHub/psmqtt: python3 psmqtt.py File "psmqtt.py", line 38 except Exception, e: ^ SyntaxError: invalid syntax

Battery support

Would love to use this to report the battery percentage of my Linux laptop to Home Assistant.
You can already do this using psutil, the example I found online makes it seem like adding it wouldn't be too difficult.

# python script showing battery details
import psutil
  
# function returning time in hh:mm:ss
def convertTime(seconds):
    minutes, seconds = divmod(seconds, 60)
    hours, minutes = divmod(minutes, 60)
    return "%d:%02d:%02d" % (hours, minutes, seconds)
  
# returns a tuple
battery = psutil.sensors_battery()
  
print("Battery percentage : ", battery.percent)
print("Power plugged in : ", battery.power_plugged)
  
# converting seconds to hh:mm:ss
print("Battery left : ", convertTime(battery.secsleft))

Run as Service or daemon at start up

Sorry if this isn't the right method to ask this question. I want to use your script to report the status of my raspberry pi's starting automatically at boot.

However I run in problems because I think the script expects it to be launched from the psmqtt directory only.

When i try to use "python /home/psmqtt/psmqtt.py" to start the script I get an error message;

"Cannot load configuration from file psmqtt.conf: [Errno 2] No such file or directory: 'psmqtt.conf'"

I was hoping to use cron job to start the script at boot do you think that is the best way or should be via a service on boot http://blog.scphillips.com/posts/2013/07/getting-a-python-script-to-run-in-the-background-as-a-service-on-boot/ for example.

I'm a bit of a noob and not really sure what it is I'm doing. Appreciate your help and thanks for the great script it does so much so easily.

BTW - Is there a task for reporting the CPU temperature? I had a look but couldn't see anything.

Regards

Shane

Documention for psmqtt.conf

Hello,
Documentation for psmqtt.conf is a little parse.

For example how to input another address that 'localhost' for the broker
Will it be:
mqtt_broker = 192.168.0.34 or
mqtt_broker = '192.168.0.34'

Neither work
localhost on the other pi running mosquitto works just fine...

Disconnecting every 11 sec

Hi,
first of all thanks for this. I have it run on my RPi 4 and everything works fine so far :)

Now I try to run it also on RPi Zero W and for some reasons connection to MQTT broker restarts every 11 seconds:

[2021-01-19 21:04:11,064] DEBUG Connected to MQTT broker, subscribing to topic psmqtt/raspberry/request/#
[2021-01-19 21:04:22,331] DEBUG OOOOPS! psmqtt disconnects
[2021-01-19 21:04:33,361] DEBUG Connected to MQTT broker, subscribing to topic psmqtt/raspberry/request/#
[2021-01-19 21:04:44,434] DEBUG OOOOPS! psmqtt disconnects


[2021-01-19 21:05:39,500] DEBUG Connected to MQTT broker, subscribing to topic psmqtt/raspberry/request/#
[2021-01-19 21:05:50,747] DEBUG OOOOPS! psmqtt disconnects
[2021-01-19 21:06:01,774] DEBUG Connected to MQTT broker, subscribing to topic psmqtt/raspberry/request/#
[2021-01-19 21:06:12,850] DEBUG OOOOPS! psmqtt disconnects
[2021-01-19 21:06:23,886] DEBUG Connected to MQTT broker, subscribing to topic psmqtt/raspberry/request/#
[2021-01-19 21:06:34,953] DEBUG OOOOPS! psmqtt disconnects
[2021-01-19 21:06:45,987] DEBUG Connected to MQTT broker, subscribing to topic psmqtt/raspberry/request/#


[2021-01-19 21:08:31,758] DEBUG Connected to MQTT broker, subscribing to topic psmqtt/raspberry/request/#
[2021-01-19 21:08:42,972] DEBUG OOOOPS! psmqtt disconnects
[2021-01-19 21:08:54,027] DEBUG Connected to MQTT broker, subscribing to topic psmqtt/raspberry/request/#
[2021-01-19 21:09:05,076] DEBUG OOOOPS! psmqtt disconnects

MQTT broker is on another RPi4 (3rd one (!))

As mentioned another connection works fine.

BTW
is there any possibity to add IP and MAC addresses per interface (I think both are part of psutil)?

Thanks in advance for help.

Raspberry pi CPU temp

Hello,
I get an error:
Exception: Element 'sensors_temperatures' in 'sensors_temperatures/*' is not supported
When using psmqtt on a rapsberry pi zero
Has the CPU temperature been implemented for raspis yet?

paho missing 1 required positional argument: 'callback_api_version'

Came across this after fresh install of psmqtt. Requirements.txt installs paho-mqtt V2.0, which has a breaking change with psmqtt. Fix for now that I found was to revert paho-mqtt to V1.6 by running this command:

sudo pip3 install "paho-mqtt < 2.0.0"

sudo is required because the psmqtt service runs under root.

Help with disk_usage

Hi,

Thanks for this project.

I am running on ubuntu, and a little confused by some of the results I am getting. Of course, this could easily be user error :)

Disk space according to df -h :

df -H /dev/sda2
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda2       491G  122G  344G  27% /

I am making a request on a topic as follows

psmqtt/<myhost>/request/disk_usage/free/|dev|sda2

listening on psmqtt/<myhost>/disk_usage/free/|dev|sda2, I receive the value 3927171072

which at a glance, looks about right, but if this is bytes, then

echo "scale = 2; 3927171072 / (1024^3) " | bc
3.65

Where am I going wrong? :)

File monitoring

First of all, thanks for this great piece of software!
May it be possible to implement file monitoring as well?
It would be a nice feature to get the file-modification timestamp and the size of a file.

Docker?

Any plans to support DockerFile container?

What volumes/folders does it need? Some root privilegies?
On my NAS i have all containerized and i'd like to have this too.

battery stats

Cool project. Would it be possible to add battery stats as well?

Specific use-case for me is to send to mqtt to be received by Node-RED to turn around and control a z-wave power outlet so I can sanely condition a brand new battery for my laptop (turn on power once battery is at or below X percent and turn it off once battery is at or above Y percent).

View sensors in HA

I have your program running, i can see it in mosquito broker but im unable to see it in homeassistant, can u please help me?

Extracting values to HA

Temp sensor in HA gives "unknown"

image

And MQTT Explorer gives

image

ASUS_CPU sensor is working but that ASUS_TEMP is not working? What should I do? What am I missing there?

  - platform: mqtt
    name: "ASUS_CPU"
    state_topic: "psmqtt/cpu_percent"
    
  - platform: mqtt
    name: "ASUS_TEMP"
    state_topic: "psmqtt/sensors_temperatures"
    value_template: "{{ value_json.asus }}"
    json_attributes_topic: "psmqtt/sensors_temperatures"

2 questions: syntax for USB HDD

hello

your script is working awesome, I`m have 1 question and need for help.

  1. what would be the best way to start the script on boot?
    @reboot /usr/bin/python3 /home/server/psmqtt/psmqtt.py - seems to be not working

I`m running Debian OS

thank you very much

Cannot load configuration from file psmqtt.conf

Hi everyone,
I am trying to use psmqtt on several computers but I’m always receiving the message:

Cannot load configuration from file psmqtt.conf: [Errno 2] No such file or directory: ‘psmqtt.conf’

Do you have any idea what I am doing wrong? I would really appreciate your help.

Unable to set-up with raid devices

Hi,
it seems that my raid 0 devices are not liked by the python program.
Here's my psmqtt.conf:

import socket

mqtt_broker = '192.168.1.65'       # default: 'localhost'
mqtt_port = 1883                # default: 1883
mqtt_clientid = 'GC01SRVR'
mqtt_username = 'mqtt'
mqtt_password = 'mqtt_password'
mqtt_clean_session = False
mqtt_qos = 0
mqtt_retain = False
#mqtt_topic_prefix = 'psmqtt/'
mqtt_topic_prefix = 'psmqtt/' + socket.gethostname() + '/'
mqtt_request_topic = 'request'




schedule = {
    "every 1 minute" :    [
        "cpu_percent",
        "virtual_memory/percent",
     ],
    "every 60 minutes"  :     "disk_usage/percent/|",  # slash replaced with vertical slash
    "every 1 second" : [
        "disk_usage/percent/|dev|mapper|backup",
        "disk_usage/percent/|dev|mappervm",
        "disk_usage/percent/|dev|mapper3tb",
        "disk_usage/percent/|dev|mapper|4tb",
        "disk_usage/percent/|",
    ]
}

Forgot to add the error I get:

root@GC01SRVR:psmqtt$ python3 psmqtt.py 
[2021-11-19 18:34:56,902] DEBUG Connected to MQTT broker, subscribing to topic psmqtt/GC01SRVR/request/#
Exception in thread Thread-1:
Traceback (most recent call last):
  File "/usr/lib/python3.9/threading.py", line 954, in _bootstrap_inner
    self.run()
  File "/home/programmi/psmqtt/psmqtt.py", line 212, in run
    self.s.run()
  File "/usr/lib/python3.9/sched.py", line 151, in run
    action(*argument, **kwargs)
  File "/home/programmi/psmqtt/psmqtt.py", line 178, in on_timer
    run_task(tasks, tasks)
  File "/home/programmi/psmqtt/psmqtt.py", line 63, in run_task
    if task.startswith(topic_prefix):
AttributeError: 'set' object has no attribute 'startswith'

Could you help me out a bit please? :)

Latest docker tag throws: "Error response from daemon: manifest unknown"

If I try to pull the latest tag, I get:

docker pull ghcr.io/eschava/psmqtt:latest
Error response from daemon: manifest unknown

However, if I try to pull a specific version, it works fine:

docker pull ghcr.io/eschava/psmqtt:1.0.0
1.0.0: Pulling from eschava/psmqtt
4abcf2066143: Pull complete  
c3cdf40b8bda: Pull complete 
ac499ccf2147: Pull complete 
416bfceb623e: Pull complete 
76351c33299b: Pull complete 
a0effc962a32: Pull complete 
c2df1b96dde7: Pull complete 
ccb762f971d6: Pull complete 
a8147cd1dc2b: Pull complete 
f932c4149149: Pull complete 
a12d92bb7e77: Pull complete 
50ec1c47ecf7: Pull complete 
cf6ad4db96fc: Pull complete 
d4e583386669: Pull complete 
30130d43bb04: Pull complete 
6ef465775921: Pull complete 
Digest: sha256:c59715110d227532423b3382ec8d211c9754348788b00bf71ce173171839c7a0
Status: Downloaded newer image for ghcr.io/eschava/psmqtt:1.0.0
ghcr.io/eschava/psmqtt:1.0.0

I'm using Docker version 26.1.3

Missing license

Hi!
I would like to contribute to this project and i also like to use it in a project i am currently working on.
Would mind licensing this project? Maybe MIT? http://choosealicense.com

Regards and keep up the good work :)

Autostart psmqtt

I finally managed to install on macOS, is there a way to start the program on boot? if I close the terminal, will the program keep running?
also, what's the correct syntax to get cpu temp for macOS? sensor_temperatures doesn't seem to work (I get an error)

Dockerfile not working

hi @eschava ,
I tried to build a docker image but the resulting image does not run properly:

fmontorsi@newpcstudio-linux:~/Documents/psmqtt$ docker run -ti psmqtt
Traceback (most recent call last):
  File "/opt/psmqtt/psmqtt.py", line 22, in <module>
    from src.config import load_config
ModuleNotFoundError: No module named 'src'

Since I would like to employ this utility and I have quite a bit of experience with Python, docker and MQTT, would you welcome some PRs to fix a few issues?
For example:

  • the project does not have a github CI/CD pipeline which builds the project and deploys pypi packages or docker images to ease installations/deployments
  • the software does not handle gracefully some type of errors: e.g. an invalid MQTT broker address produces a python stacktrace...
  • etc

Let me know: if you welcome contributions I will start opening PRs :)

thanks!!!

In Docker on Debian 12 : Exception: Use sudo to read '/dev/nvme0' SMART data

Under docker, even if I sudo docker run, I receive the following error for the disk info.

Linux yippie-ha 6.1.0-18-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.76-1 (2024-02-01) x86_64 GNU/Linux

[2024-05-07 17:43:14,103] ERROR Use sudo to read '/dev/nvme0' SMART data
[2024-05-07 17:43:14,103] INFO mqttc.publish('psmqtt/yippie-ha/smart/nvme0/temperature/error', 'Use sudo to read '/dev/nvme0' SMART data')
[2024-05-07 17:43:14,103] ERROR run_task caught: smart/nvme0/temperature : Use sudo to read '/dev/nvme0' SMART data
Traceback (most recent call last):
  File "/opt/psmqtt/src/task.py", line 154, in run_task
    payload = get_value(task)
              ^^^^^^^^^^^^^^^
  File "/opt/psmqtt/src/handlers.py", line 802, in get_value
    value = handler.handle(tail)
            ^^^^^^^^^^^^^^^^^^^^
  File "/opt/psmqtt/src/handlers.py", line 536, in handle
    raise Exception(errmsg)
Exception: Use sudo to read '/dev/nvme0' SMART data

Thank you for your work and help!

Add renovate bot to maintain dependencies up to date

hi @eschava ,
One good thing to have on Python projects is a bot like Renovate that will automatically create PRs to update dependencies when a new dependency version comes out.
This helps both to maintain the project alive and up to date and also with security (new versions hopefully contain security fixes for disclosed CVEs, etc).

As collaborator I cannot add the app myself to this project, because it's yet another owner-only thing.
Anyway it's very easy to do: just go to https://github.com/marketplace/renovate and scroll down to the end and click "Install it for free" (it's completely free for open source projects).
If I remember correctly it will guide you in a simple process ending with a commit of a file like this one: https://github.com/f18m/ha-alarm-raspy2mqtt/blob/main/renovate.json.

Here you can see the type of dashboard that gets created:
f18m/rpi2home-assistant#11
and an example of PR opened by the bot:
f18m/rpi2home-assistant#12

Hope this will be useful...

Temperatures and fan

Hi, is it possible to have some sort of explanation about the meaning of different temperatures.

sensors_temperatures/*; gives

image

And please add fan speed if possible.

Inconsistent reports for SMART data

Thanks for adding in SMART support!
Sadly, I've been fighting with psmqtt to get it working consistently (this is on windows)

I have 8 drives (5 hdd, 3 ssd), but at any given time, only some of the drives report statistics. The very unusual thing is out of the 8 drives, sometimes 4 will report smart data, sometimes 6 will report, sometimes, depending on their mood, only 3 of them will report.

The ones which aren't reporting properly show vague messages. I'm guessing the problem stems from something upstream (pysmart? smartctl?). I'm eager to trace this bug, but don't even know where to start. Any suggestions appreciated.

pqmqtt.log (in this case, sdb report is OK, but sdc is failing. sometimes it might be the other way around)

[2023-01-30 12:00:19,392] INFO mqttc.publish('psmqtt/naruto/smart/sdb/temperature', '30')
[2023-01-30 12:00:19,395] DEBUG run_task(smart/sdc/temperature, smart/sdc/temperature)
[2023-01-30 12:00:20,978] INFO mqttc.publish('psmqtt/naruto/smart/sdc/temperature/error', ''NoneType' object has no attribute 'groups'')
[2023-01-30 12:00:20,978] ERROR run_task caught: smart/sdc/temperature : 'NoneType' object has no attribute 'groups'
Traceback (most recent call last):
  File "C:\psmqtt\src\task.py", line 153, in run_task
    payload = get_value(task)
  File "C:\psmqtt\src\handlers.py", line 802, in get_value
    value = handler.handle(tail)
  File "C:\psmqtt\src\handlers.py", line 532, in handle
    self.device = SmartDevice(self.name)
  File "C:\Python\Python39\lib\site-packages\pySMART\device.py", line 295, in __init__
    self.update()
  File "C:\Python\Python39\lib\site-packages\pySMART\device.py", line 1023, in update
    parsed = re.compile(
AttributeError: 'NoneType' object has no attribute 'groups'

C:\psmqtt> smartctl --scan-open

/dev/sda -d ata # /dev/sda, ATA device
/dev/sdb -d ata # /dev/sdb, ATA device
/dev/sdc -d ata # /dev/sdc, ATA device
/dev/sdd -d ata # /dev/sdd, ATA device
/dev/sde -d ata # /dev/sde, ATA device
/dev/sdf -d ata # /dev/sdf, ATA device
/dev/sdg -d ata # /dev/sdg, ATA device
/dev/sdh -d usbprolific # /dev/sdh [USB Prolific], ATA device
/dev/csmi1,2 -d ata # /dev/csmi1,2, ATA device
/dev/csmi1,4 -d ata # /dev/csmi1,4, ATA device
/dev/csmi1,5 -d ata # /dev/csmi1,5, ATA device

C:\psmqtt> smartctl -a /dev/sdc

smartctl 7.3 2022-02-28 r5338 [x86_64-w64-mingw32-w10-21H2] (sf-7.3-1)
Copyright (C) 2002-22, Bruce Allen, Christian Franke, www.smartmontools.org

=== START OF INFORMATION SECTION ===
Model Family:     HGST Deskstar NAS
Device Model:     HGST HDN724040ALE640
Serial Number:    PK2334PBKLGXVT
LU WWN Device Id: 5 000cca 23df2ad3d
Firmware Version: MJAOA5E0
User Capacity:    4,000,787,030,016 bytes [4.00 TB]
Sector Sizes:     512 bytes logical, 4096 bytes physical
Rotation Rate:    7200 rpm
Form Factor:      3.5 inches
Device is:        In smartctl database 7.3/5319
ATA Version is:   ATA8-ACS T13/1699-D revision 4
SATA Version is:  SATA 3.0, 6.0 Gb/s (current: 6.0 Gb/s)
Local Time is:    Mon Jan 30 12:30:45 2023 PST
SMART support is: Available - device has SMART capability.
SMART support is: Enabled

=== START OF READ SMART DATA SECTION ===
SMART overall-health self-assessment test result: PASSED

General SMART Values:
Offline data collection status:  (0x82)	Offline data collection activity
					was completed without error.
					Auto Offline Data Collection: Enabled.
Self-test execution status:      (   0)	The previous self-test routine completed
					without error or no self-test has ever 
					been run.
Total time to complete Offline 
data collection: 		(   24) seconds.
Offline data collection
capabilities: 			 (0x5b) SMART execute Offline immediate.
					Auto Offline data collection on/off support.
					Suspend Offline collection upon new
					command.
					Offline surface scan supported.
					Self-test supported.
					No Conveyance Self-test supported.
					Selective Self-test supported.
SMART capabilities:            (0x0003)	Saves SMART data before entering
					power-saving mode.
					Supports SMART auto save timer.
Error logging capability:        (0x01)	Error logging supported.
					General Purpose Logging supported.
Short self-test routine 
recommended polling time: 	 (   1) minutes.
Extended self-test routine
recommended polling time: 	 ( 560) minutes.
SCT capabilities: 	       (0x003d)	SCT Status supported.
					SCT Error Recovery Control supported.
					SCT Feature Control supported.
					SCT Data Table supported.

SMART Attributes Data Structure revision number: 16
Vendor Specific SMART Attributes with Thresholds:
ID# ATTRIBUTE_NAME          FLAG     VALUE WORST THRESH TYPE      UPDATED  WHEN_FAILED RAW_VALUE
  1 Raw_Read_Error_Rate     0x000b   100   100   016    Pre-fail  Always       -       0
  2 Throughput_Performance  0x0005   136   136   054    Pre-fail  Offline      -       81
  3 Spin_Up_Time            0x0007   185   185   024    Pre-fail  Always       -       447 (Average 389)
  4 Start_Stop_Count        0x0012   100   100   000    Old_age   Always       -       288
  5 Reallocated_Sector_Ct   0x0033   100   100   005    Pre-fail  Always       -       0
  7 Seek_Error_Rate         0x000b   100   100   067    Pre-fail  Always       -       0
  8 Seek_Time_Performance   0x0005   124   124   020    Pre-fail  Offline      -       33
  9 Power_On_Hours          0x0012   091   091   000    Old_age   Always       -       65397
 10 Spin_Retry_Count        0x0013   100   100   060    Pre-fail  Always       -       0
 12 Power_Cycle_Count       0x0032   100   100   000    Old_age   Always       -       178
192 Power-Off_Retract_Count 0x0032   098   098   000    Old_age   Always       -       3103
193 Load_Cycle_Count        0x0012   098   098   000    Old_age   Always       -       3103
194 Temperature_Celsius     0x0002   187   187   000    Old_age   Always       -       32 (Min/Max 14/63)
196 Reallocated_Event_Count 0x0032   100   100   000    Old_age   Always       -       0
197 Current_Pending_Sector  0x0022   100   100   000    Old_age   Always       -       0
198 Offline_Uncorrectable   0x0008   100   100   000    Old_age   Offline      -       0
199 UDMA_CRC_Error_Count    0x000a   200   200   000    Old_age   Always       -       65

SMART Error Log Version: 1
ATA Error Count: 65 (device log contains only the most recent five errors)
	CR = Command Register [HEX]
	FR = Features Register [HEX]
	SC = Sector Count Register [HEX]
	SN = Sector Number Register [HEX]
	CL = Cylinder Low Register [HEX]
	CH = Cylinder High Register [HEX]
	DH = Device/Head Register [HEX]
	DC = Device Command Register [HEX]
	ER = Error register [HEX]
	ST = Status register [HEX]
Powered_Up_Time is measured from power on, and printed as
DDd+hh:mm:SS.sss where DD=days, hh=hours, mm=minutes,
SS=sec, and sss=millisec. It "wraps" after 49.710 days.

Error 65 occurred at disk power-on lifetime: 7 hours (0 days + 7 hours)
  When the command that caused the error occurred, the device was active or idle.

  After command completion occurred, registers were:
  ER ST SC SN CL CH DH
  -- -- -- -- -- -- --
  84 51 01 cf 51 2d 01  Error: ICRC, ABRT 1 sectors at LBA = 0x012d51cf = 19747279

  Commands leading to the command that caused the error were:
  CR FR SC SN CL CH DH DC   Powered_Up_Time  Command/Feature_Name
  -- -- -- -- -- -- -- --  ----------------  --------------------
  35 00 08 c8 51 2d 40 00      07:19:30.545  WRITE DMA EXT
  ef 02 00 00 00 00 40 00      07:19:30.544  SET FEATURES [Enable write cache]
  ef 03 46 00 00 00 40 00      07:19:30.544  SET FEATURES [Set transfer mode]
  c6 00 10 00 00 00 40 00      07:19:30.544  SET MULTIPLE MODE
  ec 00 00 00 00 00 40 00      07:19:30.542  IDENTIFY DEVICE

Error 64 occurred at disk power-on lifetime: 7 hours (0 days + 7 hours)
  When the command that caused the error occurred, the device was active or idle.

  After command completion occurred, registers were:
  ER ST SC SN CL CH DH
  -- -- -- -- -- -- --
  84 51 01 cf 51 2d 01  Error: ICRC, ABRT 1 sectors at LBA = 0x012d51cf = 19747279

  Commands leading to the command that caused the error were:
  CR FR SC SN CL CH DH DC   Powered_Up_Time  Command/Feature_Name
  -- -- -- -- -- -- -- --  ----------------  --------------------
  35 00 08 c8 51 2d 40 00      07:17:32.347  WRITE DMA EXT
  ef 02 00 00 00 00 40 00      07:17:32.347  SET FEATURES [Enable write cache]
  ef 03 46 00 00 00 40 00      07:17:32.346  SET FEATURES [Set transfer mode]
  c6 00 10 00 00 00 40 00      07:17:32.346  SET MULTIPLE MODE
  ec 00 00 00 00 00 40 00      07:17:32.345  IDENTIFY DEVICE

Error 63 occurred at disk power-on lifetime: 6 hours (0 days + 6 hours)
  When the command that caused the error occurred, the device was active or idle.

  After command completion occurred, registers were:
  ER ST SC SN CL CH DH
  -- -- -- -- -- -- --
  84 51 01 df 4f 2d 01  Error: ICRC, ABRT 1 sectors at LBA = 0x012d4fdf = 19746783

  Commands leading to the command that caused the error were:
  CR FR SC SN CL CH DH DC   Powered_Up_Time  Command/Feature_Name
  -- -- -- -- -- -- -- --  ----------------  --------------------
  35 00 08 d8 4f 2d 40 00      06:57:32.618  WRITE DMA EXT
  35 00 08 80 eb ff 40 00      06:57:32.290  WRITE DMA EXT
  ef 02 00 00 00 00 40 00      06:57:32.290  SET FEATURES [Enable write cache]
  ef 03 46 00 00 00 40 00      06:57:32.289  SET FEATURES [Set transfer mode]
  c6 00 10 00 00 00 40 00      06:57:32.289  SET MULTIPLE MODE

Error 62 occurred at disk power-on lifetime: 6 hours (0 days + 6 hours)
  When the command that caused the error occurred, the device was active or idle.

  After command completion occurred, registers were:
  ER ST SC SN CL CH DH
  -- -- -- -- -- -- --
  84 51 01 df 4f 2d 01  Error: ICRC, ABRT 1 sectors at LBA = 0x012d4fdf = 19746783

  Commands leading to the command that caused the error were:
  CR FR SC SN CL CH DH DC   Powered_Up_Time  Command/Feature_Name
  -- -- -- -- -- -- -- --  ----------------  --------------------
  35 00 08 d8 4f 2d 40 00      06:57:31.637  WRITE DMA EXT
  ef 02 00 00 00 00 40 00      06:57:31.637  SET FEATURES [Enable write cache]
  ef 03 46 00 00 00 40 00      06:57:31.636  SET FEATURES [Set transfer mode]
  c6 00 10 00 00 00 40 00      06:57:31.636  SET MULTIPLE MODE
  ec 00 00 00 00 00 40 00      06:57:31.635  IDENTIFY DEVICE

Error 61 occurred at disk power-on lifetime: 6 hours (0 days + 6 hours)
  When the command that caused the error occurred, the device was active or idle.

  After command completion occurred, registers were:
  ER ST SC SN CL CH DH
  -- -- -- -- -- -- --
  84 51 01 df 4f 2d 01  Error: ICRC, ABRT 1 sectors at LBA = 0x012d4fdf = 19746783

  Commands leading to the command that caused the error were:
  CR FR SC SN CL CH DH DC   Powered_Up_Time  Command/Feature_Name
  -- -- -- -- -- -- -- --  ----------------  --------------------
  35 00 08 d8 4f 2d 40 00      06:55:38.749  WRITE DMA EXT
  35 00 08 d0 4f 2d 40 00      06:55:38.749  WRITE DMA EXT
  ef 02 00 00 00 00 40 00      06:55:38.748  SET FEATURES [Enable write cache]
  ef 03 46 00 00 00 40 00      06:55:38.748  SET FEATURES [Set transfer mode]
  c6 00 10 00 00 00 40 00      06:55:38.748  SET MULTIPLE MODE

SMART Self-test log structure revision number 1
Num  Test_Description    Status                  Remaining  LifeTime(hours)  LBA_of_first_error
# 1  Short offline       Completed without error       00%      2785         -

SMART Selective self-test log data structure revision number 1
 SPAN  MIN_LBA  MAX_LBA  CURRENT_TEST_STATUS
    1        0        0  Not_testing
    2        0        0  Not_testing
    3        0        0  Not_testing
    4        0        0  Not_testing
    5        0        0  Not_testing
Selective self-test flags (0x0):
  After scanning selected spans, do NOT read-scan remainder of disk.
If Selective self-test is pending on power-up, resume after 0 minute delay.


disk SMART

there is any published topic with the disks SMART info ?

thank you

Gradually increasing CPU Load

Hi,
I'm really pleased with psmqtt, it's really nicely done.
However I have a small issue.

I'm running psmqtt on three Raspberry Pis (Original B, Pi 2 and Pi3) communicating with a mosquitto broker on the Pi3. After 24-36 hours (ish) the Original Pi B has gradually increased from a ~1-5% CPU load to 100%. The Pi2 and Pi3 have increased from 1-2% to 20% over the same period.

The Original B isn't running anything else - its a test box to trial stuff and is currently idle (or should be!).
The Pi2 is running PiVPN
The Pi3 is running openHAB and mosquitto

The only change to these systems has been the introduction of psmqtt.
There is nothing relevant in the psmqtt.log file, my psmqtt.conf file is attached
psmqtt.conf.txt

Hope you can help, let me know if you need anything.
Many thanks,
Brian.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.