Compare commits
48 commits
Author | SHA1 | Date | |
---|---|---|---|
|
0c832c4dec | ||
|
e176ed4db9 | ||
|
560a8c093c | ||
|
bd5c47c7fc | ||
|
c59a3d23d6 | ||
|
d6a4b3cc6c | ||
|
907ba3a708 | ||
|
7e3d56c3e0 | ||
|
b52e18aca1 | ||
|
00a1ed912b | ||
|
0ea6cd556c | ||
|
6fb59dd7be | ||
|
035752eca9 | ||
|
299192121a | ||
|
0bf2b5a6ee | ||
|
5784e15fa0 | ||
|
e9d70573a7 | ||
|
9ea3bac4af | ||
|
e78aad9501 | ||
|
657181a728 | ||
|
2d17c2f066 | ||
|
8fb9f9d7f6 | ||
|
edd88f3883 | ||
|
17ecce3cc6 | ||
|
809e084698 | ||
|
4cbc006e84 | ||
|
2865d787d7 | ||
|
fa224e66e4 | ||
|
c880ba2e36 | ||
|
8a7ebfefa1 | ||
|
cf29943ebe | ||
|
3b4e9143be | ||
|
05a3dd2501 | ||
|
e6870170aa | ||
|
81b4b9be55 | ||
|
abce8035f3 | ||
|
b5ced9df2c | ||
|
5b426932cf | ||
|
be9d6fa0dc | ||
|
ad306c90e3 | ||
|
4022be77ae | ||
|
bf3ea6ad35 | ||
|
107112b139 | ||
|
3749b1eef9 | ||
|
5c230986d0 | ||
|
a3f089ed86 | ||
|
a29621a3b3 | ||
|
e1e068dd5f |
30 changed files with 1705 additions and 1157 deletions
|
@ -1,4 +1,10 @@
|
||||||
img/
|
# folders
|
||||||
grafana/
|
grafana/
|
||||||
.github/
|
.github/
|
||||||
Dockerfile
|
img/
|
||||||
|
|
||||||
|
# files
|
||||||
|
.env*
|
||||||
|
.gitignore
|
||||||
|
Dockerfile
|
||||||
|
README.md
|
||||||
|
|
|
@ -1,3 +1,2 @@
|
||||||
IMMICH_USERNAME=
|
IMMICH_API_KEY=
|
||||||
IMMICH_PASSWORD=
|
|
||||||
IMMICH_BASE_URL=
|
IMMICH_BASE_URL=
|
6
.github/dependabot.yml
vendored
Normal file
6
.github/dependabot.yml
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
16
.github/workflows/build.yml
vendored
Normal file
16
.github/workflows/build.yml
vendored
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
name: Build
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
branches: ["main"]
|
||||||
|
pull_request:
|
||||||
|
branches: ["main"]
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Run build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Build
|
||||||
|
run: make build
|
39
.github/workflows/codeql-analysis.yml
vendored
Normal file
39
.github/workflows/codeql-analysis.yml
vendored
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
name: CodeQL
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
branches: ["main"]
|
||||||
|
pull_request:
|
||||||
|
branches: ["main"]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
name: Analyze
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
actions: read
|
||||||
|
contents: read
|
||||||
|
security-events: write
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
language: ["go"]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v3
|
||||||
|
with:
|
||||||
|
languages: ${{ matrix.language }}
|
||||||
|
|
||||||
|
- name: Autobuild
|
||||||
|
uses: github/codeql-action/autobuild@v3
|
||||||
|
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v3
|
||||||
|
with:
|
||||||
|
category: "/language:${{matrix.language}}"
|
69
.github/workflows/docker.yml
vendored
Normal file
69
.github/workflows/docker.yml
vendored
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
name: Docker Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
tags:
|
||||||
|
description: "version"
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
release:
|
||||||
|
types:
|
||||||
|
- created
|
||||||
|
push:
|
||||||
|
branches: ["main"]
|
||||||
|
pull_request:
|
||||||
|
branches: ["main"]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build_and_push:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: "main"
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: |
|
||||||
|
ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}
|
||||||
|
tags: |
|
||||||
|
type=raw,value=latest,enable=${{ github.event_name == 'release' }}
|
||||||
|
type=ref,event=branch
|
||||||
|
type=ref,event=pr
|
||||||
|
type=ref,event=tag
|
||||||
|
type=raw,value=${{ inputs.tags }},enable=${{ github.event_name == 'workflow_dispatch' }}
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
id: buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Login to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GH_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: ./
|
||||||
|
platforms: linux/arm/v7,linux/amd64,linux/arm64
|
||||||
|
push: ${{ !github.event.pull_request.head.repo.fork }}
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
build-args: BUILD_VERSION=${{ github.event.release.name }}
|
47
.github/workflows/push_docker.yml
vendored
47
.github/workflows/push_docker.yml
vendored
|
@ -1,47 +0,0 @@
|
||||||
name: manual push
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
tags:
|
|
||||||
description: 'version'
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
|
|
||||||
|
|
||||||
build_push_monolith:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
ref: 'main'
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v2.1.0
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
id: buildx
|
|
||||||
uses: docker/setup-buildx-action@v2.2.1
|
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
|
||||||
uses: docker/login-action@v2
|
|
||||||
with:
|
|
||||||
username: martabal
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Build and push immich
|
|
||||||
uses: docker/build-push-action@v3.2.0
|
|
||||||
with:
|
|
||||||
context: ./
|
|
||||||
file: ./Dockerfile
|
|
||||||
platforms: linux/arm/v7,linux/amd64,linux/arm64
|
|
||||||
push: ${{ github.event_name != 'pull_request' }}
|
|
||||||
tags: |
|
|
||||||
martabal/qbitorrent-exporter:${{ inputs.tags }}
|
|
||||||
martabal/qbitorrent-exporter:latest
|
|
||||||
|
|
27
.github/workflows/test.yml
vendored
Normal file
27
.github/workflows/test.yml
vendored
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
name: Tests
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
branches: ["main"]
|
||||||
|
pull_request:
|
||||||
|
branches: ["main"]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
name: Run tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Run unit tests
|
||||||
|
run: make test
|
||||||
|
|
||||||
|
- name: Run formatter
|
||||||
|
run: cd src && test -z $(gofmt -l .)
|
||||||
|
|
||||||
|
- name: golangci-lint
|
||||||
|
uses: golangci/golangci-lint-action@v3
|
||||||
|
with:
|
||||||
|
version: v1.55.2
|
||||||
|
working-directory: src
|
18
Dockerfile
18
Dockerfile
|
@ -1,14 +1,22 @@
|
||||||
FROM golang:1.19-alpine3.17 AS builder
|
FROM golang:1.21-alpine3.19 AS builder
|
||||||
|
|
||||||
|
ARG BUILD_VERSION
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY . .
|
COPY src src
|
||||||
|
|
||||||
RUN go get -d -v ./src/ && \
|
RUN cd src && \
|
||||||
go build -o /go/bin/immich-exporter ./src
|
if [ -n "${BUILD_VERSION}" ]; then \
|
||||||
|
go build -o /go/bin/immich-exporter -ldflags="-X 'main.Version=${BUILD_VERSION}'" . ; \
|
||||||
|
else \
|
||||||
|
go build -o /go/bin/immich-exporter . ; \
|
||||||
|
fi
|
||||||
|
|
||||||
FROM alpine:3.17
|
FROM alpine:3.19
|
||||||
|
|
||||||
COPY --from=builder /go/bin/immich-exporter /go/bin/immich-exporter
|
COPY --from=builder /go/bin/immich-exporter /go/bin/immich-exporter
|
||||||
|
|
||||||
|
WORKDIR /go/bin
|
||||||
|
|
||||||
CMD ["/go/bin/immich-exporter"]
|
CMD ["/go/bin/immich-exporter"]
|
||||||
|
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2023 martabal
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
20
Makefile
Normal file
20
Makefile
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
build:
|
||||||
|
cd src && go build -o ../immich-exporter.out .
|
||||||
|
|
||||||
|
dev :
|
||||||
|
cd src && go run .
|
||||||
|
|
||||||
|
dev-env :
|
||||||
|
cd src && go run . -e
|
||||||
|
|
||||||
|
format :
|
||||||
|
cd src && test -z $(gofmt -l .)
|
||||||
|
|
||||||
|
lint:
|
||||||
|
docker run --rm -v ./src:/app -w /app golangci/golangci-lint:latest golangci-lint run -v
|
||||||
|
|
||||||
|
test:
|
||||||
|
cd src && go test -v ./tests
|
||||||
|
|
||||||
|
update:
|
||||||
|
cd src && go get -u . && go mod tidy
|
40
README.md
40
README.md
|
@ -1,25 +1,28 @@
|
||||||
# immich-exporter
|
# immich-exporter
|
||||||
|
|
||||||
[](https://github.com/martabal/immich-exporter/actions/workflows/push_docker.yml)
|
[](https://github.com/martabal/immich-exporter/actions/workflows/docker.yml)
|
||||||
|
[](https://github.com/martabal/immich-exporter/actions/workflows/build.yml)
|
||||||
|
[](https://github.com/martabal/immich-exporter/actions/workflows/test.yml)
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="img/immich.png" width=100> <img src="img/prometheus.png" width=100><img src="img/golang.png" width=100>
|
<img src="img/immich.png" width=100> <img src="img/prometheus.png" width=100><img src="img/golang.png" width=100>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
This app is a Prometheus exporter for immich.
|
This app is a Prometheus exporter for immich.
|
||||||
This app is made to be integrated with the [immich-grafana-dashboard](https://github.com/martabal/immich-exporter/grafana/dashboard.json)
|
This app is made to be integrated with the [immich-grafana-dashboard](https://github.com/martabal/immich-exporter/blob/main/grafana/dashboard.json)
|
||||||
|
|
||||||
## Run it
|
## Run it
|
||||||
|
|
||||||
|
Create an API key in your Immich settings and set `IMMICH_API_KEY` to is value.
|
||||||
|
|
||||||
### Docker-cli ([click here for more info](https://docs.docker.com/engine/reference/commandline/cli/))
|
### Docker-cli ([click here for more info](https://docs.docker.com/engine/reference/commandline/cli/))
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
docker run --name=immich-exporter \
|
docker run --name=immich-exporter \
|
||||||
-e IMMICH_URL=http://192.168.1.10:8080 \
|
-e IMMICH_BASE_URL=http://192.168.1.10:8080 \
|
||||||
-e IMMICH_PASSWORD='<your_password>' \
|
-e IMMICH_API_KEY=<your_api_key> \
|
||||||
-e IMMICH_USERNAME=admin \
|
|
||||||
-p 8090:8090 \
|
-p 8090:8090 \
|
||||||
martabal/immich-exporter
|
ghcr.io/martabal/immich-exporter
|
||||||
```
|
```
|
||||||
|
|
||||||
### Docker-compose
|
### Docker-compose
|
||||||
|
@ -28,12 +31,11 @@ docker run --name=immich-exporter \
|
||||||
version: "2.1"
|
version: "2.1"
|
||||||
services:
|
services:
|
||||||
immich:
|
immich:
|
||||||
image: martabal/immich-exporter:latest
|
image: ghcr.io/martabal/immich-exporter:latest
|
||||||
container_name: immich-exporter
|
container_name: immich-exporter
|
||||||
environment:
|
environment:
|
||||||
- IMMICH_URL=http://192.168.1.10:8080
|
- IMMICH_BASE_URL=http://192.168.1.10:8080
|
||||||
- IMMICH_PASSWORD='<your_password>'
|
- IMMICH_API_KEY=<your_api_key>
|
||||||
- IMMICH_USERNAME=admin
|
|
||||||
ports:
|
ports:
|
||||||
- 8090:8090
|
- 8090:8090
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
@ -63,12 +65,24 @@ If you want to use an .env file, edit `.env.example` to match your setup, rename
|
||||||
| Parameters | Function |
|
| Parameters | Function |
|
||||||
| :-----: | ----- |
|
| :-----: | ----- |
|
||||||
| `-p 8090` | Webservice port |
|
| `-p 8090` | Webservice port |
|
||||||
| `-e IMMICH_USERNAME` | Immich username |
|
|
||||||
| `-e IMMICH_PASSWORD` | Immich password |
|
|
||||||
| `-e IMMICH_BASE_URL` | Immich base URL |
|
| `-e IMMICH_BASE_URL` | Immich base URL |
|
||||||
|
| `-e IMMICH_API_KEY` | Immich API key |
|
||||||
|
| `-e EXPORTER_PORT` | qbittorrent export port (optional) | `8090` |
|
||||||
|
| `-e LOG_LEVEL` | App log level (`TRACE`, `DEBUG`, `INFO`, `WARN` and `ERROR`) | `INFO` |
|
||||||
|
|
||||||
### Arguments
|
### Arguments
|
||||||
|
|
||||||
| Arguments | Function |
|
| Arguments | Function |
|
||||||
| :-----: | ----- |
|
| :-----: | ----- |
|
||||||
| -e | Use a .env file containing environment variables (.env file must be placed in the same directory) |
|
| -e | If qbittorrent-exporter detects a .env file in the same directory, the values in the .env will be used, `-e` forces the usage of environment variables |
|
||||||
|
|
||||||
|
### Setup
|
||||||
|
|
||||||
|
Add the target to your `scrape_configs` in your `prometheus.yml` file of your Prometheus instance.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
scrape_configs:
|
||||||
|
- job_name: 'immich'
|
||||||
|
static_configs:
|
||||||
|
- targets: [ '<your_ip_address>:8090' ]
|
||||||
|
```
|
||||||
|
|
5
go.mod
5
go.mod
|
@ -1,5 +0,0 @@
|
||||||
module immich-exporter
|
|
||||||
|
|
||||||
go 1.20
|
|
||||||
|
|
||||||
require github.com/joho/godotenv v1.5.1
|
|
2
go.sum
2
go.sum
|
@ -1,2 +0,0 @@
|
||||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
|
||||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
|
File diff suppressed because it is too large
Load diff
19
package.json
19
package.json
|
@ -1,19 +0,0 @@
|
||||||
{
|
|
||||||
"name": "immich-exporter",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"description": "exporter for immich",
|
|
||||||
"main": "src/main.go",
|
|
||||||
"scripts": {
|
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
|
||||||
},
|
|
||||||
"keywords": [
|
|
||||||
"exporter",
|
|
||||||
"immich",
|
|
||||||
"grafana",
|
|
||||||
"dashboard",
|
|
||||||
"metrics",
|
|
||||||
"prometheus"
|
|
||||||
],
|
|
||||||
"author": "martabal",
|
|
||||||
"license": "ISC"
|
|
||||||
}
|
|
19
src/go.mod
Normal file
19
src/go.mod
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
module immich-exp
|
||||||
|
|
||||||
|
go 1.21
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/joho/godotenv v1.5.1
|
||||||
|
github.com/prometheus/client_golang v1.18.0
|
||||||
|
github.com/sirupsen/logrus v1.9.3
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
|
github.com/prometheus/client_model v0.5.0 // indirect
|
||||||
|
github.com/prometheus/common v0.46.0 // indirect
|
||||||
|
github.com/prometheus/procfs v0.12.0 // indirect
|
||||||
|
golang.org/x/sys v0.16.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.32.0 // indirect
|
||||||
|
)
|
34
src/go.sum
Normal file
34
src/go.sum
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
|
||||||
|
github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
|
||||||
|
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
|
||||||
|
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
|
||||||
|
github.com/prometheus/common v0.46.0 h1:doXzt5ybi1HBKpsZOL0sSkaNHJJqkyfEWZGGqqScV0Y=
|
||||||
|
github.com/prometheus/common v0.46.0/go.mod h1:Tp0qkxpb9Jsg54QMe+EAmqXkSV7Evdy1BTn+g2pa/hQ=
|
||||||
|
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
||||||
|
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
||||||
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||||
|
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
|
||||||
|
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
@ -1,60 +0,0 @@
|
||||||
package immich
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"immich-exporter/src/models"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Auth() {
|
|
||||||
|
|
||||||
url := models.GetURL() + "/api/auth/login"
|
|
||||||
method := "POST"
|
|
||||||
|
|
||||||
payload := strings.NewReader(`{
|
|
||||||
"email": "` + models.GetUsername() + `",
|
|
||||||
"password": "` + models.Getpassword() + `"}`)
|
|
||||||
|
|
||||||
client := &http.Client{}
|
|
||||||
req, err := http.NewRequest(method, url, payload)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
req.Header.Add("Content-Type", "application/json")
|
|
||||||
req.Header.Add("Accept", "application/json")
|
|
||||||
|
|
||||||
res, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
defer res.Body.Close()
|
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(res.Body)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if res.StatusCode == 400 {
|
|
||||||
log.Fatalln("Incorrect login")
|
|
||||||
} else {
|
|
||||||
var result models.Login
|
|
||||||
if err := json.Unmarshal(body, &result); err != nil {
|
|
||||||
log.Println("Can not unmarshal JSON")
|
|
||||||
}
|
|
||||||
|
|
||||||
models.SetAccessToken(result.AccessToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -3,164 +3,218 @@ package immich
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"immich-exporter/src/models"
|
"immich-exp/models"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
prom "immich-exp/prometheus"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Allrequests() string {
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
return serverversion() + Analyze()
|
var (
|
||||||
|
mutex sync.Mutex
|
||||||
|
)
|
||||||
|
|
||||||
|
type Data struct {
|
||||||
|
URL string
|
||||||
|
HTTPMethod string
|
||||||
}
|
}
|
||||||
|
|
||||||
func Analyze() string {
|
var httpGetUsers = Data{
|
||||||
allusers, err := GetAllUsers()
|
URL: "/api/user?isAll=true",
|
||||||
users, err2 := users()
|
HTTPMethod: http.MethodGet,
|
||||||
if err != nil && err2 != nil {
|
}
|
||||||
return ""
|
var httpServerVersion = Data{
|
||||||
} else {
|
URL: "/api/server-info/version",
|
||||||
return Sendbackmessagepreference(users, allusers)
|
HTTPMethod: http.MethodGet,
|
||||||
|
}
|
||||||
|
var httpStatistics = Data{
|
||||||
|
URL: "/api/server-info/statistics",
|
||||||
|
HTTPMethod: http.MethodGet,
|
||||||
|
}
|
||||||
|
var httpGetJobs = Data{
|
||||||
|
URL: "/api/jobs",
|
||||||
|
HTTPMethod: http.MethodGet,
|
||||||
|
}
|
||||||
|
|
||||||
|
var unmarshalError = "Can not unmarshal JSON"
|
||||||
|
|
||||||
|
func Allrequests(r *prometheus.Registry) {
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go ServerVersion(r)
|
||||||
|
wg.Add(1)
|
||||||
|
go Analyze(r)
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Analyze(r *prometheus.Registry) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
alljobsstatus := make(chan func() (*models.StructAllJobsStatus, error))
|
||||||
|
allusers := make(chan func() (*models.StructAllUsers, error))
|
||||||
|
serverinfo := make(chan func() (*models.StructServerInfo, error))
|
||||||
|
defer func() {
|
||||||
|
close(serverinfo)
|
||||||
|
close(allusers)
|
||||||
|
close(alljobsstatus)
|
||||||
|
}()
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go GetAllJobsStatus(alljobsstatus)
|
||||||
|
res1, err := (<-alljobsstatus)()
|
||||||
|
wg.Add(1)
|
||||||
|
go GetAllUsers(allusers)
|
||||||
|
res2, err2 := (<-allusers)()
|
||||||
|
wg.Add(1)
|
||||||
|
go ServerInfo(serverinfo)
|
||||||
|
|
||||||
|
res3, err3 := (<-serverinfo)()
|
||||||
|
|
||||||
|
if err == nil && err2 == nil && err3 == nil {
|
||||||
|
prom.SendBackMessagePreference(res3, res2, res1, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAllUsers() (*models.AllUsers, error) {
|
func GetAllUsers(c chan func() (*models.StructAllUsers, error)) {
|
||||||
resp, err := Apirequest("/api/user?isAll=true", "GET")
|
defer wg.Done()
|
||||||
if err != nil {
|
resp, err := Apirequest(httpGetUsers.URL, httpGetUsers.HTTPMethod)
|
||||||
if err.Error() == "403" {
|
if err == nil {
|
||||||
log.Println("Cookie changed, try to reconnect ...")
|
|
||||||
Auth()
|
body, err := io.ReadAll(resp.Body)
|
||||||
} else {
|
|
||||||
if models.GetPromptError() == false {
|
|
||||||
log.Println("Error : ", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if models.GetPromptError() == true {
|
|
||||||
models.SetPromptError(false)
|
|
||||||
}
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
var result models.AllUsers
|
result := new(models.StructAllUsers)
|
||||||
if err := json.Unmarshal(body, &result); err != nil { // Parse []byte to go struct pointer
|
if err := json.Unmarshal(body, &result); err != nil {
|
||||||
log.Println("Can not unmarshal JSON")
|
log.Error(unmarshalError)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &result, err
|
c <- (func() (*models.StructAllUsers, error) { return result, nil })
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &models.AllUsers{}, err
|
c <- (func() (*models.StructAllUsers, error) { return new(models.StructAllUsers), err })
|
||||||
}
|
}
|
||||||
|
|
||||||
func serverversion() string {
|
func ServerVersion(r *prometheus.Registry) {
|
||||||
resp, err := Apirequest("/api/server-info/version", "GET")
|
defer wg.Done()
|
||||||
if err != nil {
|
resp, err := Apirequest(httpServerVersion.URL, httpServerVersion.HTTPMethod)
|
||||||
if err.Error() == "403" {
|
if err == nil {
|
||||||
log.Println("Cookie changed, try to reconnect ...")
|
|
||||||
Auth()
|
body, err := io.ReadAll(resp.Body)
|
||||||
} else {
|
|
||||||
if models.GetPromptError() == false {
|
|
||||||
log.Println("Error : ", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if models.GetPromptError() == true {
|
|
||||||
models.SetPromptError(false)
|
|
||||||
}
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
var result models.ServerVersion
|
var result models.StructServerVersion
|
||||||
if err := json.Unmarshal(body, &result); err != nil { // Parse []byte to go struct pointer
|
if err := json.Unmarshal(body, &result); err != nil {
|
||||||
log.Println("Can not unmarshal JSON")
|
log.Error(unmarshalError)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Sendbackmessageserverversion(&result)
|
prom.SendBackMessageserverVersion(&result, r)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func users() (*models.Users, error) {
|
func ServerInfo(c chan func() (*models.StructServerInfo, error)) {
|
||||||
resp, err := Apirequest("/api/server-info/stats", "GET")
|
defer wg.Done()
|
||||||
if err != nil {
|
resp, err := Apirequest(httpStatistics.URL, httpStatistics.HTTPMethod)
|
||||||
if err.Error() == "403" {
|
if err == nil {
|
||||||
log.Println("Cookie changed, try to reconnect ...")
|
|
||||||
Auth()
|
|
||||||
} else {
|
|
||||||
if models.GetPromptError() == false {
|
|
||||||
log.Println("Error : ", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
body, err := io.ReadAll(resp.Body)
|
||||||
if models.GetPromptError() == true {
|
|
||||||
models.SetPromptError(false)
|
|
||||||
}
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
var result models.Users
|
result := new(models.StructServerInfo)
|
||||||
if err := json.Unmarshal(body, &result); err != nil { // Parse []byte to go struct pointer
|
if err := json.Unmarshal(body, &result); err != nil {
|
||||||
log.Println("Can not unmarshal JSON")
|
log.Println(unmarshalError)
|
||||||
}
|
}
|
||||||
|
c <- (func() (*models.StructServerInfo, error) { return result, nil })
|
||||||
return &result, nil
|
return
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &models.Users{}, err
|
c <- (func() (*models.StructServerInfo, error) { return new(models.StructServerInfo), err })
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAllJobsStatus(c chan func() (*models.StructAllJobsStatus, error)) {
|
||||||
|
defer wg.Done()
|
||||||
|
resp, err := Apirequest(httpGetJobs.URL, httpGetJobs.HTTPMethod)
|
||||||
|
if err == nil {
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
} else {
|
||||||
|
|
||||||
|
result := new(models.StructAllJobsStatus)
|
||||||
|
if err := json.Unmarshal(body, &result); err != nil {
|
||||||
|
log.Println(unmarshalError)
|
||||||
|
}
|
||||||
|
c <- (func() (*models.StructAllJobsStatus, error) { return result, nil })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c <- (func() (*models.StructAllJobsStatus, error) { return new(models.StructAllJobsStatus), err })
|
||||||
}
|
}
|
||||||
|
|
||||||
func Apirequest(uri string, method string) (*http.Response, error) {
|
func Apirequest(uri string, method string) (*http.Response, error) {
|
||||||
|
|
||||||
req, err := http.NewRequest(method, models.GetURL()+uri, nil)
|
req, err := http.NewRequest(method, models.Getbaseurl()+uri, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln("Error with url")
|
log.Fatal("Error with url")
|
||||||
}
|
}
|
||||||
|
req.Header.Add("Accept", "application/json")
|
||||||
req.AddCookie(&http.Cookie{Name: "immich_access_token", Value: models.GetAccessToken()})
|
req.Header.Add("x-api-key", models.GetApiKey())
|
||||||
client := &http.Client{}
|
client := &http.Client{}
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
|
||||||
err := fmt.Errorf("Can't connect to server")
|
err := fmt.Errorf("Can't connect to server")
|
||||||
if models.GetPromptError() == false {
|
mutex.Lock()
|
||||||
log.Println(err.Error())
|
if !models.GetPromptError() {
|
||||||
|
log.Error(err.Error())
|
||||||
models.SetPromptError(true)
|
models.SetPromptError(true)
|
||||||
}
|
}
|
||||||
|
mutex.Unlock()
|
||||||
return resp, err
|
return resp, err
|
||||||
|
|
||||||
} else {
|
}
|
||||||
models.SetPromptError(false)
|
switch resp.StatusCode {
|
||||||
if resp.StatusCode == 200 {
|
case http.StatusOK:
|
||||||
|
mutex.Lock()
|
||||||
return resp, nil
|
if models.GetPromptError() {
|
||||||
|
models.SetPromptError(false)
|
||||||
} else {
|
|
||||||
err := fmt.Errorf("%d", resp.StatusCode)
|
|
||||||
if models.GetPromptError() == false {
|
|
||||||
models.SetPromptError(true)
|
|
||||||
|
|
||||||
log.Println("Error code", err.Error())
|
|
||||||
|
|
||||||
}
|
|
||||||
return resp, err
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
mutex.Unlock()
|
||||||
|
return resp, nil
|
||||||
|
case http.StatusNotFound:
|
||||||
|
|
||||||
|
log.Fatal("Error code ", resp.StatusCode, " for ", models.Getbaseurl()+uri)
|
||||||
|
|
||||||
|
return resp, fmt.Errorf("%d", resp.StatusCode)
|
||||||
|
case http.StatusUnauthorized, http.StatusForbidden:
|
||||||
|
|
||||||
|
log.Fatal("Api key unauthorized")
|
||||||
|
|
||||||
|
return resp, fmt.Errorf("%d", resp.StatusCode)
|
||||||
|
default:
|
||||||
|
err := fmt.Errorf("%d", resp.StatusCode)
|
||||||
|
mutex.Lock()
|
||||||
|
if !models.GetPromptError() {
|
||||||
|
models.SetPromptError(true)
|
||||||
|
log.Debug("Error code ", resp.StatusCode)
|
||||||
|
}
|
||||||
|
mutex.Unlock()
|
||||||
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,46 +0,0 @@
|
||||||
package immich
|
|
||||||
|
|
||||||
import (
|
|
||||||
"immich-exporter/src/models"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Sendbackmessagepreference(result *models.Users, result2 *models.AllUsers) string {
|
|
||||||
total := "# HELP immich_app_number_users The number of immich users\n# TYPE immich_app_max_active_downloads gauge\nimmich_app_nb_users " + strconv.Itoa(len((*result).UsageByUser)) + "\n"
|
|
||||||
total = total + "# HELP immich_app_total_photos The total number of photos\n# TYPE immich_app_total_photos gauge\nimmich_app_total_photos " + strconv.Itoa((*result).Photos) + "\n"
|
|
||||||
total = total + "# HELP immich_app_total_videos The total number of videos\n# TYPE immich_app_total_videos gauge\nimmich_app_total_videos " + strconv.Itoa((*result).Videos) + "\n"
|
|
||||||
total = total + "# HELP immich_app_total_usage The usage of the user\n# TYPE immich_app_total_usage gauge\nimmich_app_total_usage " + strconv.Itoa(int((*result).UsageRaw)) + "\n"
|
|
||||||
immich_user_videos := "# HELP immich_app_user_videos The number of videos of the user\n# TYPE immich_app_user_videos gauge\n"
|
|
||||||
immich_user_photos := "# HELP immich_app_user_photos The number of photo of the user\n# TYPE immich_app_user_photos gauge\n"
|
|
||||||
immich_user_usageRaw := "# HELP immich_app_user_usage The usage of the user\n# TYPE immich_app_user_usage gauge\n"
|
|
||||||
immich_user_info := "# HELP immich_user_info All info for torrents\n# TYPE immich_user_info gauge\n"
|
|
||||||
for i := 0; i < len((*result).UsageByUser); i++ {
|
|
||||||
var myuser = GetName((*result).UsageByUser[i].UserID, result2)
|
|
||||||
immich_user_info = immich_user_info + `immich_user_info{videos="` + strconv.Itoa((*result).UsageByUser[i].Videos) + `",photos="` + strconv.Itoa((*result).UsageByUser[i].Photos) + `",uid="` + (*result).UsageByUser[i].UserID + `",usage="` + strconv.Itoa(int((*result).UsageByUser[i].UsageRaw)) + `",firstname="` + myuser.FirstName + `",lastname="` + myuser.LastName + `"} 1.0` + "\n"
|
|
||||||
immich_user_usageRaw = immich_user_usageRaw + `immich_user_usage{uid="` + (*result).UsageByUser[i].UserID + `",firstname="` + myuser.FirstName + `",lastname="` + myuser.LastName + `",} ` + strconv.Itoa(int((*result).UsageByUser[i].UsageRaw)) + "\n"
|
|
||||||
immich_user_photos = immich_user_photos + `immich_user_photos{uid="` + (*result).UsageByUser[i].UserID + `",firstname="` + myuser.FirstName + `",lastname="` + myuser.LastName + `",} ` + strconv.Itoa((*result).UsageByUser[i].Photos) + "\n"
|
|
||||||
immich_user_videos = immich_user_videos + `immich_user_videos{uid="` + (*result).UsageByUser[i].UserID + `",firstname="` + myuser.FirstName + `",lastname="` + myuser.LastName + `",} ` + strconv.Itoa((*result).UsageByUser[i].Videos) + "\n"
|
|
||||||
}
|
|
||||||
return total + immich_user_info + immich_user_videos + immich_user_photos + immich_user_usageRaw
|
|
||||||
}
|
|
||||||
|
|
||||||
func Sendbackmessageserverversion(result *models.ServerVersion) string {
|
|
||||||
|
|
||||||
return "# HELP immich_app_version The current immich version\n# TYPE immich_app_version gauge\nimmich_app_version" + `{version="` + strconv.Itoa((*result).Major) + "." + strconv.Itoa((*result).Minor) + "." + strconv.Itoa((*result).Patch) + `",} 1.0` + "\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetName(result string, result2 *models.AllUsers) models.CustomUser {
|
|
||||||
var myuser models.CustomUser
|
|
||||||
for i := 0; i < len(*result2); i++ {
|
|
||||||
if (*result2)[i].ID == result {
|
|
||||||
|
|
||||||
myuser.ID = (*result2)[i].ID
|
|
||||||
myuser.FirstName = (*result2)[i].FirstName
|
|
||||||
myuser.LastName = (*result2)[i].LastName
|
|
||||||
myuser.Email = (*result2)[i].Email
|
|
||||||
myuser.IsAdmin = (*result2)[i].IsAdmin
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return myuser
|
|
||||||
}
|
|
140
src/init.go
140
src/init.go
|
@ -2,69 +2,115 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"immich-exporter/src/immich"
|
"fmt"
|
||||||
"immich-exporter/src/models"
|
immich "immich-exp/immich"
|
||||||
|
"immich-exp/models"
|
||||||
|
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func startup() {
|
const DEFAULTPORT = 8090
|
||||||
var envfile bool
|
|
||||||
|
|
||||||
|
var (
|
||||||
|
Version = "dev"
|
||||||
|
Author = "martabal"
|
||||||
|
ProjectName = "immich-exporter"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
loadenv()
|
||||||
|
fmt.Printf("%s (version %s)\n", ProjectName, Version)
|
||||||
|
fmt.Println("Author: ", Author)
|
||||||
|
fmt.Println("Using log level: ", log.GetLevel())
|
||||||
|
log.Info("Immich URL: ", models.Getbaseurl())
|
||||||
|
log.Info("Started")
|
||||||
|
http.HandleFunc("/metrics", metrics)
|
||||||
|
addr := ":" + strconv.Itoa(models.GetPort())
|
||||||
|
log.Info("Listening on port ", models.GetPort())
|
||||||
|
err := http.ListenAndServe(addr, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func metrics(w http.ResponseWriter, r *http.Request) {
|
||||||
|
log.Trace("New request")
|
||||||
|
registry := prometheus.NewRegistry()
|
||||||
|
immich.Allrequests(registry)
|
||||||
|
h := promhttp.HandlerFor(registry, promhttp.HandlerOpts{})
|
||||||
|
h.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadenv() {
|
||||||
|
var envfile bool
|
||||||
flag.BoolVar(&envfile, "e", false, "Use .env file")
|
flag.BoolVar(&envfile, "e", false, "Use .env file")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
log.Println("Loading all parameters")
|
_, err := os.Stat(".env")
|
||||||
if envfile {
|
if !os.IsNotExist(err) && !envfile {
|
||||||
useenvfile()
|
err := godotenv.Load(".env")
|
||||||
} else {
|
if err != nil {
|
||||||
initenv()
|
log.Panic("Error loading .env file:", err)
|
||||||
|
}
|
||||||
|
// fmt.Println("Using .env file")
|
||||||
}
|
}
|
||||||
|
|
||||||
immich.Auth()
|
immichapikey := getEnv("IMMICH_API_KEY", "", false, "Immich API Key is not set", true)
|
||||||
|
immichURL := getEnv("IMMICH_BASE_URL", "http://localhost:8080", true, "Qbittorrent base_url is not set. Using default base_url", false)
|
||||||
|
exporterPort := getEnv("EXPORTER_PORT", strconv.Itoa(DEFAULTPORT), false, "", false)
|
||||||
|
|
||||||
}
|
num, err := strconv.Atoi(exporterPort)
|
||||||
|
|
||||||
func useenvfile() {
|
|
||||||
myEnv, err := godotenv.Read()
|
|
||||||
username := myEnv["IMMICH_USERNAME"]
|
|
||||||
password := myEnv["IMMICH_PASSWORD"]
|
|
||||||
immich_url := myEnv["IMMICH_BASE_URL"]
|
|
||||||
if myEnv["IMMICH_USERNAME"] == "" {
|
|
||||||
log.Panic("Immich username is not set.")
|
|
||||||
}
|
|
||||||
if myEnv["IMMICH_PASSWORD"] == "" {
|
|
||||||
log.Panic("Immich password is not set.")
|
|
||||||
}
|
|
||||||
if myEnv["IMMICH_BASE_URL"] == "" {
|
|
||||||
log.Panic("IMMICH base_url is not set.")
|
|
||||||
}
|
|
||||||
models.Setuser(username, password, immich_url)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Error loading .env file")
|
log.Panic("EXPORTER_PORT must be an integer")
|
||||||
}
|
}
|
||||||
log.Println("Using .env file")
|
if num < 0 || num > 65353 {
|
||||||
|
log.Panic("EXPORTER_PORT must be > 0 and < 65353")
|
||||||
|
}
|
||||||
|
|
||||||
|
setLogLevel(getEnv("LOG_LEVEL", "INFO", false, "", false))
|
||||||
|
models.SetApp(num, false)
|
||||||
|
models.Setuser(immichURL, immichapikey)
|
||||||
|
}
|
||||||
|
func setLogLevel(logLevel string) {
|
||||||
|
logLevels := map[string]log.Level{
|
||||||
|
"TRACE": log.TraceLevel,
|
||||||
|
"DEBUG": log.DebugLevel,
|
||||||
|
"INFO": log.InfoLevel,
|
||||||
|
"WARN": log.WarnLevel,
|
||||||
|
"ERROR": log.ErrorLevel,
|
||||||
|
}
|
||||||
|
|
||||||
|
level, found := logLevels[strings.ToUpper(logLevel)]
|
||||||
|
if !found {
|
||||||
|
level = log.InfoLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
log.SetLevel(level)
|
||||||
|
log.SetFormatter(&log.TextFormatter{
|
||||||
|
DisableColors: false,
|
||||||
|
FullTimestamp: true,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func initenv() {
|
func getEnv(key string, fallback string, printLog bool, logPrinted string, needed bool) string {
|
||||||
username := os.Getenv("IMMICH_USERNAME")
|
value, ok := os.LookupEnv(key)
|
||||||
password := os.Getenv("IMMICH_PASSWORD")
|
if !ok || value == "" {
|
||||||
immich_url := os.Getenv("IMMICH_BASE_URL")
|
if needed {
|
||||||
if os.Getenv("IMMICH_USERNAME") == "" {
|
log.Panicln("Please set a value for", key)
|
||||||
log.Panic("Immich username is not set.")
|
}
|
||||||
|
if printLog {
|
||||||
|
log.Warn(logPrinted)
|
||||||
|
}
|
||||||
|
return fallback
|
||||||
}
|
}
|
||||||
if os.Getenv("IMMICH_PASSWORD") == "" {
|
return value
|
||||||
log.Panic("Immich password is not set.")
|
|
||||||
|
|
||||||
}
|
|
||||||
if os.Getenv("IMMICH_BASE_URL") == "" {
|
|
||||||
log.Panic("Immich base_url is not set.")
|
|
||||||
|
|
||||||
}
|
|
||||||
models.Setuser(username, password, immich_url)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
28
src/main.go
28
src/main.go
|
@ -1,28 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"immich-exporter/src/immich"
|
|
||||||
"immich-exporter/src/models"
|
|
||||||
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
startup()
|
|
||||||
log.Println("Immich URL :", models.GetURL())
|
|
||||||
log.Println("username :", models.GetUsername())
|
|
||||||
log.Println("password :", models.Getpasswordmasked())
|
|
||||||
log.Println("Started")
|
|
||||||
http.HandleFunc("/metrics", metrics)
|
|
||||||
http.ListenAndServe(":8090", nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func metrics(w http.ResponseWriter, req *http.Request) {
|
|
||||||
value := immich.Allrequests()
|
|
||||||
if value == "" {
|
|
||||||
value = immich.Allrequests()
|
|
||||||
}
|
|
||||||
fmt.Fprintf(w, value)
|
|
||||||
}
|
|
|
@ -2,31 +2,29 @@ package models
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
type Login struct {
|
type StructLogin struct {
|
||||||
AccessToken string `json:"accessToken"`
|
AccessToken string `json:"accessToken"`
|
||||||
UserID string `json:"userId"`
|
UserID string `json:"userId"`
|
||||||
UserEmail string `json:"userEmail"`
|
UserEmail string `json:"userEmail"`
|
||||||
FirstName string `json:"firstName"`
|
Name string `json:"name"`
|
||||||
LastName string `json:"lastName"`
|
|
||||||
IsAdmin bool `json:"isAdmin"`
|
IsAdmin bool `json:"isAdmin"`
|
||||||
ShouldChangePassword bool `json:"shouldChangePassword"`
|
ShouldChangePassword bool `json:"shouldChangePassword"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Users struct {
|
type StructServerInfo struct {
|
||||||
Photos int `json:"photos"`
|
Photos int `json:"photos"`
|
||||||
Videos int `json:"videos"`
|
Videos int `json:"videos"`
|
||||||
|
Usage int64 `json:"usage"`
|
||||||
UsageByUser []struct {
|
UsageByUser []struct {
|
||||||
UserID string `json:"userId"`
|
UserID string `json:"userId"`
|
||||||
Videos int `json:"videos"`
|
UserName string `json:"userName"`
|
||||||
Photos int `json:"photos"`
|
Photos int `json:"photos"`
|
||||||
UsageRaw int64 `json:"usageRaw"`
|
Videos int `json:"videos"`
|
||||||
Usage string `json:"usage"`
|
Usage int `json:"usage"`
|
||||||
} `json:"usageByUser"`
|
} `json:"usageByUser"`
|
||||||
UsageRaw int64 `json:"usageRaw"`
|
|
||||||
Usage string `json:"usage"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ServerInfo struct {
|
type StructDiskInfo struct {
|
||||||
DiskAvailable string `json:"diskAvailable"`
|
DiskAvailable string `json:"diskAvailable"`
|
||||||
DiskSize string `json:"diskSize"`
|
DiskSize string `json:"diskSize"`
|
||||||
DiskUse string `json:"diskUse"`
|
DiskUse string `json:"diskUse"`
|
||||||
|
@ -36,17 +34,16 @@ type ServerInfo struct {
|
||||||
DiskUsagePercentage float64 `json:"diskUsagePercentage"`
|
DiskUsagePercentage float64 `json:"diskUsagePercentage"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ServerVersion struct {
|
type StructServerVersion struct {
|
||||||
Major int `json:"major"`
|
Major int `json:"major"`
|
||||||
Minor int `json:"minor"`
|
Minor int `json:"minor"`
|
||||||
Patch int `json:"patch"`
|
Patch int `json:"patch"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AllUsers []struct {
|
type StructAllUsers []struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
FirstName string `json:"firstName"`
|
Name string `json:"name"`
|
||||||
LastName string `json:"lastName"`
|
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
ProfileImagePath string `json:"profileImagePath"`
|
ProfileImagePath string `json:"profileImagePath"`
|
||||||
ShouldChangePassword bool `json:"shouldChangePassword"`
|
ShouldChangePassword bool `json:"shouldChangePassword"`
|
||||||
|
@ -55,10 +52,39 @@ type AllUsers []struct {
|
||||||
OauthID string `json:"oauthId"`
|
OauthID string `json:"oauthId"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CustomUser struct {
|
type StructCustomUser struct {
|
||||||
Email string
|
Email string
|
||||||
ID string
|
ID string
|
||||||
FirstName string
|
Name string
|
||||||
LastName string
|
IsAdmin bool
|
||||||
IsAdmin bool
|
}
|
||||||
|
|
||||||
|
type StructJobStatus struct {
|
||||||
|
JobCounts struct {
|
||||||
|
Active int `json:"active"`
|
||||||
|
Completed int `json:"completed"`
|
||||||
|
Failed int `json:"failed"`
|
||||||
|
Delayed int `json:"delayed"`
|
||||||
|
Waiting int `json:"waiting"`
|
||||||
|
Paused int `json:"paused"`
|
||||||
|
} `json:"jobCounts"`
|
||||||
|
QueueStatus struct {
|
||||||
|
IsActive bool `json:"isActive"`
|
||||||
|
IsPaused bool `json:"isPaused"`
|
||||||
|
} `json:"queueStatus"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type StructAllJobsStatus struct {
|
||||||
|
ThumbnailGeneration StructJobStatus `json:"thumbnailGeneration"`
|
||||||
|
MetadataExtraction StructJobStatus `json:"metadataExtraction"`
|
||||||
|
VideoConversion StructJobStatus `json:"videoConversion"`
|
||||||
|
ObjectTagging StructJobStatus `json:"objectTagging"`
|
||||||
|
RecognizeFaces StructJobStatus `json:"recognizeFaces"`
|
||||||
|
ClipEncoding StructJobStatus `json:"clipEncoding"`
|
||||||
|
BackgroundTask StructJobStatus `json:"backgroundTask"`
|
||||||
|
StorageTemplateMigration StructJobStatus `json:"storageTemplateMigration"`
|
||||||
|
Migration StructJobStatus `json:"migration"`
|
||||||
|
Search StructJobStatus `json:"search"`
|
||||||
|
Sidecar StructJobStatus `json:"sidecar"`
|
||||||
|
Library StructJobStatus `json:"library"`
|
||||||
}
|
}
|
||||||
|
|
27
src/models/app.go
Normal file
27
src/models/app.go
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
type TypeAppConfig struct {
|
||||||
|
Port int
|
||||||
|
Error bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var AppConfig TypeAppConfig
|
||||||
|
|
||||||
|
func SetApp(setport int, seterror bool) {
|
||||||
|
AppConfig = TypeAppConfig{
|
||||||
|
Port: setport,
|
||||||
|
Error: seterror,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPort() int {
|
||||||
|
return AppConfig.Port
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetPromptError(prompt bool) {
|
||||||
|
AppConfig.Error = prompt
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPromptError() bool {
|
||||||
|
return AppConfig.Error
|
||||||
|
}
|
|
@ -1,11 +0,0 @@
|
||||||
package models
|
|
||||||
|
|
||||||
var myerr bool
|
|
||||||
|
|
||||||
func SetPromptError(prompt bool) {
|
|
||||||
myerr = prompt
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetPromptError() bool {
|
|
||||||
return myerr
|
|
||||||
}
|
|
22
src/models/immich.go
Normal file
22
src/models/immich.go
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
type StructImmich struct {
|
||||||
|
APIKey string
|
||||||
|
URL string
|
||||||
|
}
|
||||||
|
|
||||||
|
var myuser StructImmich
|
||||||
|
|
||||||
|
func Setuser(url string, apikey string) {
|
||||||
|
myuser.URL = url
|
||||||
|
myuser.APIKey = apikey
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func Getbaseurl() string {
|
||||||
|
return myuser.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetApiKey() string {
|
||||||
|
return myuser.APIKey
|
||||||
|
}
|
|
@ -1,51 +0,0 @@
|
||||||
package models
|
|
||||||
|
|
||||||
type User struct {
|
|
||||||
Username string
|
|
||||||
Password string
|
|
||||||
URL string
|
|
||||||
accessToken string
|
|
||||||
}
|
|
||||||
|
|
||||||
var myuser User
|
|
||||||
|
|
||||||
func mask(input string) string {
|
|
||||||
hide := ""
|
|
||||||
for i := 0; i < len(input); i++ {
|
|
||||||
hide += "*"
|
|
||||||
}
|
|
||||||
return hide
|
|
||||||
}
|
|
||||||
|
|
||||||
func Getuser() (string, string) {
|
|
||||||
return myuser.Username, myuser.Password
|
|
||||||
}
|
|
||||||
|
|
||||||
func Setuser(username string, password string, url string) {
|
|
||||||
myuser.Username = username
|
|
||||||
myuser.Password = password
|
|
||||||
myuser.URL = url
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetUsername() string {
|
|
||||||
return myuser.Username
|
|
||||||
}
|
|
||||||
|
|
||||||
func Getpassword() string {
|
|
||||||
return myuser.Password
|
|
||||||
}
|
|
||||||
func Getpasswordmasked() string {
|
|
||||||
return mask(myuser.Password)
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetAccessToken(accessToken string) {
|
|
||||||
myuser.accessToken = accessToken
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetAccessToken() string {
|
|
||||||
return myuser.accessToken
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetURL() string {
|
|
||||||
return myuser.URL
|
|
||||||
}
|
|
133
src/prometheus/prometheus.go
Normal file
133
src/prometheus/prometheus.go
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
package prom
|
||||||
|
|
||||||
|
import (
|
||||||
|
"immich-exp/models"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Gauge []struct {
|
||||||
|
name string
|
||||||
|
help string
|
||||||
|
value float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func SendBackMessagePreference(
|
||||||
|
result *models.StructServerInfo,
|
||||||
|
result2 *models.StructAllUsers,
|
||||||
|
result3 *models.StructAllJobsStatus,
|
||||||
|
r *prometheus.Registry,
|
||||||
|
) {
|
||||||
|
|
||||||
|
gauges := Gauge{
|
||||||
|
{"total photos", "The total number of photos", float64((*result).Photos)},
|
||||||
|
{"total videos", "The total number of videos", float64((*result).Videos)},
|
||||||
|
{"total usage", "The max number of active torrents allowed", float64((*result).Usage)},
|
||||||
|
{"number users", "The total number of users", float64(len((*result).UsageByUser))},
|
||||||
|
}
|
||||||
|
|
||||||
|
register(gauges, r)
|
||||||
|
|
||||||
|
user_info := prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||||
|
Name: "immich_user_info",
|
||||||
|
Help: "All infos about users",
|
||||||
|
}, []string{"videos", "photos", "uid", "usage", "name"})
|
||||||
|
|
||||||
|
user_usage := prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||||
|
Name: "immich_user_usage",
|
||||||
|
Help: "The usage of the user",
|
||||||
|
}, []string{"uid", "name"})
|
||||||
|
user_photos := prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||||
|
Name: "immich_user_photos",
|
||||||
|
Help: "The number of photo of the user",
|
||||||
|
}, []string{"uid", "name"})
|
||||||
|
user_videos := prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||||
|
Name: "immich_user_videos",
|
||||||
|
Help: "The number of videos of the user",
|
||||||
|
}, []string{"uid", "name"})
|
||||||
|
|
||||||
|
job_count := prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||||
|
Name: "immich_job_count",
|
||||||
|
Help: "The item count in the job",
|
||||||
|
}, []string{"status", "job_name"})
|
||||||
|
|
||||||
|
r.MustRegister(user_info)
|
||||||
|
r.MustRegister(user_usage)
|
||||||
|
r.MustRegister(user_videos)
|
||||||
|
r.MustRegister(user_photos)
|
||||||
|
r.MustRegister(job_count)
|
||||||
|
|
||||||
|
for i := 0; i < len((*result).UsageByUser); i++ {
|
||||||
|
var myuser = GetName((*result).UsageByUser[i].UserID, result2)
|
||||||
|
user_info.With(prometheus.Labels{"videos": strconv.Itoa((*result).UsageByUser[i].Videos), "photos": strconv.Itoa((*result).UsageByUser[i].Photos), "uid": (*result).UsageByUser[i].UserID, "usage": strconv.Itoa(int((*result).UsageByUser[i].Usage)), "name": myuser.Name}).Inc()
|
||||||
|
user_photos.With(prometheus.Labels{"uid": (*result).UsageByUser[i].UserID, "name": myuser.Name}).Set(float64((*result).UsageByUser[i].Photos))
|
||||||
|
user_usage.With(prometheus.Labels{"uid": (*result).UsageByUser[i].UserID, "name": myuser.Name}).Set(float64((*result).UsageByUser[i].Usage))
|
||||||
|
user_videos.With(prometheus.Labels{"uid": (*result).UsageByUser[i].UserID, "name": myuser.Name}).Set(float64((*result).UsageByUser[i].Videos))
|
||||||
|
}
|
||||||
|
|
||||||
|
setJobStatusCounts(job_count, "background_task", &result3.BackgroundTask)
|
||||||
|
setJobStatusCounts(job_count, "clip_encoding", &result3.ClipEncoding)
|
||||||
|
setJobStatusCounts(job_count, "library", &result3.Library)
|
||||||
|
setJobStatusCounts(job_count, "metadata_extraction", &result3.MetadataExtraction)
|
||||||
|
setJobStatusCounts(job_count, "migration", &result3.Migration)
|
||||||
|
setJobStatusCounts(job_count, "object_tagging", &result3.ObjectTagging)
|
||||||
|
setJobStatusCounts(job_count, "recognize_faces", &result3.RecognizeFaces)
|
||||||
|
setJobStatusCounts(job_count, "search", &result3.Search)
|
||||||
|
setJobStatusCounts(job_count, "sidecar", &result3.Sidecar)
|
||||||
|
setJobStatusCounts(job_count, "storage_template_migration", &result3.StorageTemplateMigration)
|
||||||
|
setJobStatusCounts(job_count, "thumbnail_generation", &result3.ThumbnailGeneration)
|
||||||
|
setJobStatusCounts(job_count, "video_conversion", &result3.VideoConversion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setJobStatusCounts(job_count *prometheus.GaugeVec, jobName string, result *models.StructJobStatus) {
|
||||||
|
job_count.With(prometheus.Labels{"status": "active", "job_name": jobName}).Set(float64(result.JobCounts.Active))
|
||||||
|
job_count.With(prometheus.Labels{"status": "completed", "job_name": jobName}).Set(float64(result.JobCounts.Completed))
|
||||||
|
job_count.With(prometheus.Labels{"status": "failed", "job_name": jobName}).Set(float64(result.JobCounts.Failed))
|
||||||
|
job_count.With(prometheus.Labels{"status": "delayed", "job_name": jobName}).Set(float64(result.JobCounts.Delayed))
|
||||||
|
job_count.With(prometheus.Labels{"status": "waiting", "job_name": jobName}).Set(float64(result.JobCounts.Waiting))
|
||||||
|
job_count.With(prometheus.Labels{"status": "paused", "job_name": jobName}).Set(float64(result.JobCounts.Paused))
|
||||||
|
}
|
||||||
|
|
||||||
|
func SendBackMessageserverVersion(result *models.StructServerVersion, r *prometheus.Registry) {
|
||||||
|
|
||||||
|
version := prometheus.NewGauge(prometheus.GaugeOpts{
|
||||||
|
Name: "immich_app_version",
|
||||||
|
Help: "Immich version",
|
||||||
|
ConstLabels: map[string]string{
|
||||||
|
"version": strconv.Itoa((*result).Major) + "." + strconv.Itoa((*result).Minor) + "." + strconv.Itoa((*result).Patch),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
version.Set(1)
|
||||||
|
r.MustRegister(version)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetName(result string, result2 *models.StructAllUsers) models.StructCustomUser {
|
||||||
|
var myuser models.StructCustomUser
|
||||||
|
for i := 0; i < len(*result2); i++ {
|
||||||
|
if (*result2)[i].ID == result {
|
||||||
|
|
||||||
|
myuser.ID = (*result2)[i].ID
|
||||||
|
myuser.Name = (*result2)[i].Name
|
||||||
|
myuser.Email = (*result2)[i].Email
|
||||||
|
myuser.IsAdmin = (*result2)[i].IsAdmin
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return myuser
|
||||||
|
}
|
||||||
|
|
||||||
|
func register(gauges Gauge, r *prometheus.Registry) {
|
||||||
|
for _, gauge := range gauges {
|
||||||
|
name := "immich_app_" + strings.Replace(gauge.name, " ", "_", -1)
|
||||||
|
help := gauge.help
|
||||||
|
g := prometheus.NewGauge(prometheus.GaugeOpts{
|
||||||
|
Name: name,
|
||||||
|
Help: help,
|
||||||
|
})
|
||||||
|
r.MustRegister(g)
|
||||||
|
g.Set(gauge.value)
|
||||||
|
}
|
||||||
|
}
|
34
src/tests/prometheus_test.go
Normal file
34
src/tests/prometheus_test.go
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
package prom
|
||||||
|
|
||||||
|
import (
|
||||||
|
"immich-exp/models"
|
||||||
|
prom "immich-exp/prometheus"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetName(t *testing.T) {
|
||||||
|
result2 := &models.StructAllUsers{
|
||||||
|
{ID: "1", Name: "John", Email: "john@example.com", IsAdmin: true},
|
||||||
|
{ID: "2", Name: "Jane", Email: "jane@example.com", IsAdmin: false},
|
||||||
|
}
|
||||||
|
|
||||||
|
result := "1"
|
||||||
|
expected := models.StructCustomUser{
|
||||||
|
ID: "1",
|
||||||
|
Name: "John",
|
||||||
|
Email: "john@example.com",
|
||||||
|
IsAdmin: true,
|
||||||
|
}
|
||||||
|
actual := prom.GetName(result, result2)
|
||||||
|
if !reflect.DeepEqual(expected, actual) {
|
||||||
|
t.Errorf("Expected: %v, but got: %v", expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
result = "3"
|
||||||
|
expected = models.StructCustomUser{}
|
||||||
|
actual = prom.GetName(result, result2)
|
||||||
|
if !reflect.DeepEqual(expected, actual) {
|
||||||
|
t.Errorf("Expected: %v, but got: %v", expected, actual)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue