Skip to content

Commit

Permalink
First commit !
Browse files Browse the repository at this point in the history
  • Loading branch information
cambierr committed Nov 13, 2022
0 parents commit ff32c16
Show file tree
Hide file tree
Showing 12 changed files with 432 additions and 0 deletions.
15 changes: 15 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Thumbs.db
.DS_Store
.gradle
build/
target/
out/
.micronaut/
.idea
*.iml
*.ipr
*.iws
.project
.settings
.classpath
.factorypath
57 changes: 57 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
## What this is about

This is an exporter for Prometheus to be able to scrape metrics from Shelly (https://shelly.cloud/) modules.

## Why yet-another app doing this

Well, you may be right. But...

First, I wanted to try Micronaut Native for a while, this was a pretty good pretext.

Then, I wanted this to be stateless and config-less. That is, I wanted the full configuration to be in Prometheus and nothing here.

Finally, because I decided so.

## Why java ? this is old/slow/...

Have you actually been trying "modern" java ? That is java (and frameworks) not from previous century ?

## How to use

First, run this application:
* Either by downloading the native executable (see releases page) and starting it
* Or by using the docker image: `docker run -d -p 8080:8080 cambierr/shelly-exporter`

Then, in your prometheus config file, add a scrape job:

```yaml
scrape_configs:
# ... your existing jobs
- job_name: 'shelly'
metrics_path: /metrics
static_configs:
- targets:
- 127.0.0.1:8080 # This is where shelly-exporter is listening
scrape_interval: 60s
params:
host: ["192.168.0.13"] # This is the shelly module hostname
type: ["em"] # This is the shelly module type
username: ["some-username"] # optional, only if configured on your shelly module
password: ["some-password"] # optional, only if configured on your shelly module
```
## How to build
* `mvn package` will build you an executable jar
* `mvn package -Dpackaging=native-image` will build you a native executable (assuming you have graal available on your machine)
* `mvn package -Dpackaging=docker-native` will build you a docker image containing the native executable

## Supported Shelly modules

* Shelly EM

Not a lot, heh ? Feel free to send a PR with the implementation of a new module, or open an issue with the response of the `/status` call of your module :-)

## How to add support for a module

Just create a new class in the `be.romaincambier.shellyexporter.shelly.modules` package, extend the `Shelly` abstract class, and do your thing.
10 changes: 10 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
version: '3.7'

services:
prometheus:
image: prom/prometheus:v2.40.1
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
command:
- '--config.file=/etc/prometheus/prometheus.yml'
network_mode: host
125 changes: 125 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>be.romaincambier</groupId>
<artifactId>shelly-exporter</artifactId>
<version>0.1</version>
<packaging>${packaging}</packaging>

<parent>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-parent</artifactId>
<version>3.7.3</version>
</parent>

<properties>
<packaging>jar</packaging>
<jdk.version>17</jdk.version>
<release.version>17</release.version>
<micronaut.version>3.7.3</micronaut.version>
<micronaut.runtime>netty</micronaut.runtime>
<exec.mainClass>be.romaincambier.shellyexporter.Application</exec.mainClass>
</properties>

<repositories>
<repository>
<id>central</id>
<url>https://repo.maven.apache.org/maven2</url>
</repository>
</repositories>

<dependencies>
<dependency>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-inject</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-validation</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-http-client</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-http-server-netty</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-jackson-databind</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>io.micronaut.build</groupId>
<artifactId>micronaut-maven-plugin</artifactId>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<!-- Uncomment to enable incremental compilation -->
<!-- <useIncrementalCompilation>false</useIncrementalCompilation> -->

<annotationProcessorPaths combine.self="override">
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<path>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-inject-java</artifactId>
<version>${micronaut.version}</version>
</path>
<path>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-graal</artifactId>
<version>${micronaut.version}</version>
</path>
<path>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-http-validation</artifactId>
<version>${micronaut.version}</version>
</path>
<path>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-validation</artifactId>
<version>${micronaut.version}</version>
</path>
</annotationProcessorPaths>
<compilerArgs>
<arg>-Amicronaut.processing.group=be.romaincambier</arg>
<arg>-Amicronaut.processing.module=shelly-exporter</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>

</project>
10 changes: 10 additions & 0 deletions prometheus.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
scrape_configs:
- job_name: 'shelly'
metrics_path: /metrics
static_configs:
- targets:
- 127.0.0.1:8080
scrape_interval: 60s
params:
host: ["192.168.0.13"]
type: ["em"]
12 changes: 12 additions & 0 deletions src/main/java/be/romaincambier/shellyexporter/Application.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package be.romaincambier.shellyexporter;

import io.micronaut.runtime.Micronaut;

public class Application {
public static void main(String[] args) {
Micronaut.build(args)
.eagerInitSingletons(true)
.mainClass(Application.class)
.start();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package be.romaincambier.shellyexporter.prometheus;

import be.romaincambier.shellyexporter.shelly.Registry;
import be.romaincambier.shellyexporter.shelly.Shelly;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.QueryValue;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import java.util.Optional;

@Controller("/metrics")
@RequiredArgsConstructor
@Slf4j
public class PrometheusController {

private final Registry registry;

@Get(uri = "/", produces = MediaType.TEXT_PLAIN)
public String scrape(
@QueryValue("type") String type,
@QueryValue("host") String host,
@QueryValue(value = "username", defaultValue = "") String username,
@QueryValue(value = "password", defaultValue = "") String password) {

if (type == null || type.isBlank()) {
log.warn("Invalid value for type: '{}'", type);
return "";
}

if (host == null || host.isBlank()) {
log.warn("Invalid value for host: '{}'", host);
return "";
}

Optional<Shelly> scraperMaybe = registry.forName(type);

if (scraperMaybe.isEmpty()) {
log.warn("No such shelly scraper: {}", type);
return "";
}

return scraperMaybe.get().scrape(host, username, password);
}

}
21 changes: 21 additions & 0 deletions src/main/java/be/romaincambier/shellyexporter/shelly/Registry.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package be.romaincambier.shellyexporter.shelly;

import jakarta.inject.Singleton;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

@Singleton
public class Registry {

private final Map<String, Shelly> modules = new HashMap<>();

public void register(Shelly shelly, String name) {
modules.put(name, shelly);
}

public Optional<Shelly> forName(String name) {
return Optional.ofNullable(modules.get(name));
}
}
12 changes: 12 additions & 0 deletions src/main/java/be/romaincambier/shellyexporter/shelly/Shelly.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package be.romaincambier.shellyexporter.shelly;

public abstract class Shelly {

protected Shelly(Registry registry) {
registry.register(this, getName());
}

protected abstract String getName();

public abstract String scrape(String host, String username, String password);
}
Loading

0 comments on commit ff32c16

Please sign in to comment.