From eb095a464b74ebfe955d69aa60301194f23f1d4d Mon Sep 17 00:00:00 2001 From: donpat1to Date: Sun, 3 Aug 2025 21:20:26 +0200 Subject: [PATCH 01/49] changed path permissions --- Dockerfile.linux | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Dockerfile.linux b/Dockerfile.linux index 6dd2861..9688402 100644 --- a/Dockerfile.linux +++ b/Dockerfile.linux @@ -15,6 +15,9 @@ copy ./WatcherAgent/ . # Build for Linux run cargo build --release --target x86_64-unknown-linux-gnu +# Setze Berechtigungen für das Binary +RUN chmod +x /app/target/x86_64-unknown-linux-gnu/release/$BINARY_NAME + # Final Linux image from debian:bullseye-slim From daeb8ca38f269f8d0b2b4e8522fb9d47b9b5a8c4 Mon Sep 17 00:00:00 2001 From: donpat1to Date: Sun, 3 Aug 2025 21:25:33 +0200 Subject: [PATCH 02/49] fixed tag to development --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7fec8bd..97ee090 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,8 +11,8 @@ env: CARGO_TERM_COLOR: always REGISTRY: git.triggermeelmo.com IMAGE_NAME: donpat1to/watcher-agent - TAG: ${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.number) || github.ref_name }} - + TAG: development + jobs: detect-project: name: Detect Rust Project From 333ccc5be51e213a36d17df707069473479c22d6 Mon Sep 17 00:00:00 2001 From: donpat1to Date: Sun, 3 Aug 2025 21:37:31 +0200 Subject: [PATCH 03/49] fixed tag to development --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 97ee090..599aece 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,7 +3,7 @@ name: Rust Cross-Platform Build on: workflow_dispatch: push: - branches: [ "development", "main", "feature/*", bug/* ] + branches: [ "development", "main", "feature/*", bug/*, enhancement/* ] pull_request: branches: [ "development", "main" ] From b7fbd93e842c7d917ccb016afe27ed9d470cf58f Mon Sep 17 00:00:00 2001 From: donpat1to Date: Sun, 3 Aug 2025 21:46:55 +0200 Subject: [PATCH 04/49] fixed permission path to binary --- Dockerfile.linux | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Dockerfile.linux b/Dockerfile.linux index 9688402..a5d2ef7 100644 --- a/Dockerfile.linux +++ b/Dockerfile.linux @@ -28,11 +28,12 @@ run apt-get update && \ && rm -rf /var/lib/apt/lists/* # Make binary name configurable -arg BINARY_NAME=WatcherAgent +ARG BINARY_NAME +ENV BINARY_NAME=${BINARY_NAME:-WatcherAgent} # Default falls nicht gesetzt copy --from=linux-builder /app/target/x86_64-unknown-linux-gnu/release/$BINARY_NAME /usr/local/bin/ # Verify binary works -run /usr/local/bin/$BINARY_NAME --version +run chmod +x /usr/local/bin/${BINARY_NAME} && /usr/local/bin/${BINARY_NAME} --version # Health check healthcheck --interval=30s --timeout=3s \ From 891f8635d4602e79e03bdcd380b2195efab17d92 Mon Sep 17 00:00:00 2001 From: donpat1to Date: Sun, 3 Aug 2025 21:56:41 +0200 Subject: [PATCH 05/49] removed invalid syntax --- Dockerfile.linux | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.linux b/Dockerfile.linux index a5d2ef7..433d77d 100644 --- a/Dockerfile.linux +++ b/Dockerfile.linux @@ -29,7 +29,7 @@ run apt-get update && \ # Make binary name configurable ARG BINARY_NAME -ENV BINARY_NAME=${BINARY_NAME:-WatcherAgent} # Default falls nicht gesetzt +ENV BINARY_NAME=${BINARY_NAME:-WatcherAgent} copy --from=linux-builder /app/target/x86_64-unknown-linux-gnu/release/$BINARY_NAME /usr/local/bin/ # Verify binary works From 4b669148d63959f38ece1de2645f51be35bdce3b Mon Sep 17 00:00:00 2001 From: donpat1to Date: Mon, 4 Aug 2025 11:31:47 +0200 Subject: [PATCH 06/49] removed binary testing --- Dockerfile.linux | 3 --- 1 file changed, 3 deletions(-) diff --git a/Dockerfile.linux b/Dockerfile.linux index 433d77d..69e4290 100644 --- a/Dockerfile.linux +++ b/Dockerfile.linux @@ -32,9 +32,6 @@ ARG BINARY_NAME ENV BINARY_NAME=${BINARY_NAME:-WatcherAgent} copy --from=linux-builder /app/target/x86_64-unknown-linux-gnu/release/$BINARY_NAME /usr/local/bin/ -# Verify binary works -run chmod +x /usr/local/bin/${BINARY_NAME} && /usr/local/bin/${BINARY_NAME} --version - # Health check healthcheck --interval=30s --timeout=3s \ cmd /usr/local/bin/$BINARY_NAME healthcheck || exit 1 From ccbcae60e70adbe0847d99f61875c38a7f8bb2b7 Mon Sep 17 00:00:00 2001 From: donpat1to Date: Mon, 4 Aug 2025 12:00:03 +0200 Subject: [PATCH 07/49] changed docker login --- .github/workflows/build.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 599aece..10881c8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -159,9 +159,7 @@ jobs: -t ${{ env.IMAGE_NAME }}:linux-${{ env.TAG }} . - name: Login to Docker registry - if: ${{ success() }} run: | - echo "Logging in to Docker registry: ${{ env.REGISTRY }}" echo "${{ secrets.DOCKER_PASSWORD }}" | docker login git.triggermeelmo.com -u ${{ secrets.DOCKER_USERNAME }} --password-stdin - name: Tag and Push Linux Docker image From 0c12b6f9ff8367d4ff621db60bea1643158df3c2 Mon Sep 17 00:00:00 2001 From: donpat1to Date: Mon, 4 Aug 2025 13:18:11 +0200 Subject: [PATCH 08/49] used official docker login --- .github/workflows/build.yml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 10881c8..94251b7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -158,10 +158,13 @@ jobs: --build-arg BINARY_NAME=${{ needs.detect-project.outputs.project-name }} \ -t ${{ env.IMAGE_NAME }}:linux-${{ env.TAG }} . - - name: Login to Docker registry - run: | - echo "${{ secrets.DOCKER_PASSWORD }}" | docker login git.triggermeelmo.com -u ${{ secrets.DOCKER_USERNAME }} --password-stdin - + - name: Login to Docker Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + - name: Tag and Push Linux Docker image if: ${{ success() }} run: | From 690514dea906a4ca0b967a5cb022c6e4db59b5fc Mon Sep 17 00:00:00 2001 From: donpat1to Date: Mon, 4 Aug 2025 13:27:11 +0200 Subject: [PATCH 09/49] registry as https --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 94251b7..ccb3890 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,7 +9,7 @@ on: env: CARGO_TERM_COLOR: always - REGISTRY: git.triggermeelmo.com + REGISTRY: https://git.triggermeelmo.com IMAGE_NAME: donpat1to/watcher-agent TAG: development @@ -164,7 +164,7 @@ jobs: registry: ${{ env.REGISTRY }} username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - + - name: Tag and Push Linux Docker image if: ${{ success() }} run: | From b8f9d61291db6b35adfec5a9dd226235d517bcec Mon Sep 17 00:00:00 2001 From: donpat1to Date: Mon, 4 Aug 2025 18:10:23 +0200 Subject: [PATCH 10/49] added docker secrets --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ccb3890..6f4bac2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -178,7 +178,7 @@ jobs: run: | echo "Building Windows Docker-Image with env-Tag: ${{env.TAG }}" docker build -f Dockerfile.windows -t ${{ env.IMAGE_NAME }}:windows-${{ env.TAG }} . - echo "${{ secrets.DOCKER_PASSWORD }}" | docker login git.triggermeelmo.com -u ${{ secrets.DOCKER_USERNAME }} --password-stdin + echo docker login git.triggermeelmo.com -u ${{ secrets.DOCKER_USERNAME }} --password-stdin echo "Tagging Windows Docker image" docker tag ${{ env.IMAGE_NAME }}:windows-${{ env.TAG }} ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:windows-${{ env.TAG }} echo "Pushing Windows Docker image to registry: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:windows-${{ env.TAG }}" From 44b1f10f6effdb3d0a3540fcad556ec57724111b Mon Sep 17 00:00:00 2001 From: donpat1to Date: Mon, 4 Aug 2025 18:21:31 +0200 Subject: [PATCH 11/49] added docker secrets --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6f4bac2..6953861 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,7 +9,7 @@ on: env: CARGO_TERM_COLOR: always - REGISTRY: https://git.triggermeelmo.com + REGISTRY: git.triggermeelmo.com IMAGE_NAME: donpat1to/watcher-agent TAG: development From 334c0e42f45efc5fc2e7c729c47ee88a2a154ee7 Mon Sep 17 00:00:00 2001 From: donpat1to Date: Mon, 4 Aug 2025 18:29:19 +0200 Subject: [PATCH 12/49] fixed image name --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6953861..5eaf4bb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,7 +10,7 @@ on: env: CARGO_TERM_COLOR: always REGISTRY: git.triggermeelmo.com - IMAGE_NAME: donpat1to/watcher-agent + IMAGE_NAME: watcher-agent TAG: development jobs: From 5041cf195b68eb53c987d2804dbf542ff813b696 Mon Sep 17 00:00:00 2001 From: donpat1to Date: Mon, 4 Aug 2025 18:38:10 +0200 Subject: [PATCH 13/49] added --load flag for local access of image --- .github/workflows/build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5eaf4bb..001f976 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -156,6 +156,7 @@ jobs: --platform linux/amd64 \ -f Dockerfile.linux \ --build-arg BINARY_NAME=${{ needs.detect-project.outputs.project-name }} \ + --load \ -t ${{ env.IMAGE_NAME }}:linux-${{ env.TAG }} . - name: Login to Docker Registry @@ -178,7 +179,7 @@ jobs: run: | echo "Building Windows Docker-Image with env-Tag: ${{env.TAG }}" docker build -f Dockerfile.windows -t ${{ env.IMAGE_NAME }}:windows-${{ env.TAG }} . - echo docker login git.triggermeelmo.com -u ${{ secrets.DOCKER_USERNAME }} --password-stdin + echo docker login ${{ env.REGISTRY }} -u ${{ secrets.DOCKER_USERNAME }} --password-stdin echo "Tagging Windows Docker image" docker tag ${{ env.IMAGE_NAME }}:windows-${{ env.TAG }} ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:windows-${{ env.TAG }} echo "Pushing Windows Docker image to registry: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:windows-${{ env.TAG }}" From 6c5cc014f3f7b3fe3c0e73ff6e58d7b5fccd9101 Mon Sep 17 00:00:00 2001 From: donpat1to Date: Mon, 4 Aug 2025 18:47:08 +0200 Subject: [PATCH 14/49] added right image name --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 001f976..c5359e7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,7 +10,7 @@ on: env: CARGO_TERM_COLOR: always REGISTRY: git.triggermeelmo.com - IMAGE_NAME: watcher-agent + IMAGE_NAME: donpat1to/watcher-agent TAG: development jobs: From f77733be71641362e0ed28fcf619000e078c4141 Mon Sep 17 00:00:00 2001 From: donpat1to Date: Mon, 4 Aug 2025 19:03:30 +0200 Subject: [PATCH 15/49] changed entry point setup --- Dockerfile.linux | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Dockerfile.linux b/Dockerfile.linux index 69e4290..9841ad2 100644 --- a/Dockerfile.linux +++ b/Dockerfile.linux @@ -16,7 +16,7 @@ copy ./WatcherAgent/ . run cargo build --release --target x86_64-unknown-linux-gnu # Setze Berechtigungen für das Binary -RUN chmod +x /app/target/x86_64-unknown-linux-gnu/release/$BINARY_NAME +run chmod +x /app/target/x86_64-unknown-linux-gnu/release/$BINARY_NAME # Final Linux image from debian:bullseye-slim @@ -28,12 +28,12 @@ run apt-get update && \ && rm -rf /var/lib/apt/lists/* # Make binary name configurable -ARG BINARY_NAME -ENV BINARY_NAME=${BINARY_NAME:-WatcherAgent} +arg BINARY_NAME +env BINARY_NAME=${BINARY_NAME:-WatcherAgent} copy --from=linux-builder /app/target/x86_64-unknown-linux-gnu/release/$BINARY_NAME /usr/local/bin/ # Health check healthcheck --interval=30s --timeout=3s \ cmd /usr/local/bin/$BINARY_NAME healthcheck || exit 1 -entrypoint ["/usr/local/bin/$BINARY_NAME"] \ No newline at end of file +ENTRYPOINT ["/bin/sh", "-c", "/usr/local/bin/${BINARY_NAME}"] \ No newline at end of file From 57ebef3af8ebbb5d9e219c70eebf61210f17d1a8 Mon Sep 17 00:00:00 2001 From: donpat1to Date: Mon, 4 Aug 2025 19:17:10 +0200 Subject: [PATCH 16/49] changed entry point setup --- Dockerfile.linux | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile.linux b/Dockerfile.linux index 9841ad2..9659d0c 100644 --- a/Dockerfile.linux +++ b/Dockerfile.linux @@ -30,10 +30,10 @@ run apt-get update && \ # Make binary name configurable arg BINARY_NAME env BINARY_NAME=${BINARY_NAME:-WatcherAgent} -copy --from=linux-builder /app/target/x86_64-unknown-linux-gnu/release/$BINARY_NAME /usr/local/bin/ +copy --from=linux-builder /app/target/x86_64-unknown-linux-gnu/release/${BINARY_NAME} /usr/local/bin/ # Health check healthcheck --interval=30s --timeout=3s \ - cmd /usr/local/bin/$BINARY_NAME healthcheck || exit 1 + cmd /usr/local/bin/${BINARY_NAME} healthcheck || exit 1 ENTRYPOINT ["/bin/sh", "-c", "/usr/local/bin/${BINARY_NAME}"] \ No newline at end of file From a08e3142b053d1d86f4c09cc69e683b2443e0d7b Mon Sep 17 00:00:00 2001 From: donpat1to Date: Mon, 4 Aug 2025 19:49:34 +0200 Subject: [PATCH 17/49] newer version of debian docker image builder --- Dockerfile.linux | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.linux b/Dockerfile.linux index 9659d0c..f9d8d91 100644 --- a/Dockerfile.linux +++ b/Dockerfile.linux @@ -19,7 +19,7 @@ run cargo build --release --target x86_64-unknown-linux-gnu run chmod +x /app/target/x86_64-unknown-linux-gnu/release/$BINARY_NAME # Final Linux image -from debian:bullseye-slim +from debian:bookworm-slim run apt-get update && \ apt-get install -y apt-transport-https && \ From 5a87336d98e0195efef659447891d0d81b2c1b4b Mon Sep 17 00:00:00 2001 From: donpat1to Date: Mon, 4 Aug 2025 20:11:25 +0200 Subject: [PATCH 18/49] seperate linux windows build --- .github/workflows/build.yml | 37 ++++++++++++++++++++++++++++++++----- Dockerfile.windows | 4 ++-- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c5359e7..187da3d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -149,7 +149,7 @@ jobs: echo "Windows binary:" ls -la windows-bin/ - - name: Build using Docker Buildx + - name: Linux build using Docker Buildx run: | docker buildx create --use docker buildx build \ @@ -173,13 +173,40 @@ jobs: docker tag ${{ env.IMAGE_NAME }}:linux-${{ env.TAG }} ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:linux-${{ env.TAG }} echo "Pushing Linux Docker image to registry: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:linux-${{ env.TAG }}" docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:linux-${{ env.TAG }} + + windows-docker-build: + name: Build Windows Docker Image + needs: [windows-cross] + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 - - name: Build Windows Docker image + - name: Download Windows artifact + uses: actions/download-artifact@v3 + with: + name: windows-binary + path: windows-bin + + - name: Login to Docker Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Windows build using Docker Buildx + run: | + docker buildx create --use + docker buildx build \ + --platform windows/amd64 \ + -f Dockerfile.windows \ + --build-arg BINARY_NAME=${{ needs.detect-project.outputs.project-name }} \ + --load \ + -t ${{ env.IMAGE_NAME }}:windows-${{ env.TAG }} . + + - name: Tag and Push Windows Docker image if: ${{ success() }} run: | - echo "Building Windows Docker-Image with env-Tag: ${{env.TAG }}" - docker build -f Dockerfile.windows -t ${{ env.IMAGE_NAME }}:windows-${{ env.TAG }} . - echo docker login ${{ env.REGISTRY }} -u ${{ secrets.DOCKER_USERNAME }} --password-stdin echo "Tagging Windows Docker image" docker tag ${{ env.IMAGE_NAME }}:windows-${{ env.TAG }} ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:windows-${{ env.TAG }} echo "Pushing Windows Docker image to registry: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:windows-${{ env.TAG }}" diff --git a/Dockerfile.windows b/Dockerfile.windows index 6d2b20a..124c721 100644 --- a/Dockerfile.windows +++ b/Dockerfile.windows @@ -5,7 +5,7 @@ FROM mcr.microsoft.com/windows/servercore:ltsc2022 WORKDIR C:/app # Copy the Windows binary -ARG BINARY_PATH -COPY ${BINARY_PATH} watcher_agent.exe +ARG BINARY_NAME +COPY ${BINARY_NAME}.exe . ENTRYPOINT ["C:/app/watcher_agent.exe"] \ No newline at end of file From 16f615f4499c34269308106a16e237f358a0ce34 Mon Sep 17 00:00:00 2001 From: donpat1to Date: Mon, 4 Aug 2025 20:14:33 +0200 Subject: [PATCH 19/49] cleaned up code --- .github/workflows/build.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 187da3d..925389f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -149,6 +149,13 @@ jobs: echo "Windows binary:" ls -la windows-bin/ + - name: Login to Docker Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + - name: Linux build using Docker Buildx run: | docker buildx create --use @@ -159,13 +166,6 @@ jobs: --load \ -t ${{ env.IMAGE_NAME }}:linux-${{ env.TAG }} . - - name: Login to Docker Registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - name: Tag and Push Linux Docker image if: ${{ success() }} run: | From bbcdfa73f174195776fb1c274edba3980cd95d89 Mon Sep 17 00:00:00 2001 From: donpat1to Date: Mon, 4 Aug 2025 20:19:53 +0200 Subject: [PATCH 20/49] cleaned up description --- .github/workflows/build.yml | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 925389f..a1e3458 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -122,7 +122,7 @@ jobs: path: ${{ needs.detect-project.outputs.project-dir }}/target/x86_64-pc-windows-gnu/release/${{ needs.detect-project.outputs.project-name }}.exe docker-build: - name: Build Docker Images + name: Build Linux Docker Image needs: [native-build, windows-cross] runs-on: ubuntu-latest steps: @@ -135,19 +135,10 @@ jobs: name: linux-binary path: linux-bin - - name: Download Windows artifact - id: download-windows - uses: actions/download-artifact@v3 - with: - name: windows-binary - path: windows-bin - - name: Verify artifacts run: | echo "Linux binary:" ls -la linux-bin/ - echo "Windows binary:" - ls -la windows-bin/ - name: Login to Docker Registry uses: docker/login-action@v3 @@ -186,6 +177,11 @@ jobs: with: name: windows-binary path: windows-bin + + - name: Verify artifacts + run: | + echo "Windows binary:" + ls -la windows-bin/ - name: Login to Docker Registry uses: docker/login-action@v3 From ba14f91d67289491ce7ba4567fd9c3fee026832f Mon Sep 17 00:00:00 2001 From: donpat1to Date: Mon, 4 Aug 2025 20:35:35 +0200 Subject: [PATCH 21/49] changed runs-on to ubuntu --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a1e3458..72ab3fb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -168,7 +168,7 @@ jobs: windows-docker-build: name: Build Windows Docker Image needs: [windows-cross] - runs-on: windows-latest + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 From 3e3e32484c6ac9de94cc70fc6d5660d8e61cb4e1 Mon Sep 17 00:00:00 2001 From: donpat1to Date: Mon, 4 Aug 2025 20:51:44 +0200 Subject: [PATCH 22/49] changed binary direction in Dockerfile.windows --- Dockerfile.windows | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile.windows b/Dockerfile.windows index 124c721..ae6b771 100644 --- a/Dockerfile.windows +++ b/Dockerfile.windows @@ -6,6 +6,6 @@ WORKDIR C:/app # Copy the Windows binary ARG BINARY_NAME -COPY ${BINARY_NAME}.exe . +COPY windows-bin/${BINARY_NAME}.exe . -ENTRYPOINT ["C:/app/watcher_agent.exe"] \ No newline at end of file +ENTRYPOINT ["C:/app/${BINARY_NAME}.exe"] \ No newline at end of file From ccff41a4735585e36d97a259791c8f38afc1d274 Mon Sep 17 00:00:00 2001 From: donpat1to Date: Mon, 4 Aug 2025 20:57:27 +0200 Subject: [PATCH 23/49] get defaul binary name --- Dockerfile.windows | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile.windows b/Dockerfile.windows index ae6b771..23b7631 100644 --- a/Dockerfile.windows +++ b/Dockerfile.windows @@ -6,6 +6,7 @@ WORKDIR C:/app # Copy the Windows binary ARG BINARY_NAME +env BINARY_NAME=${BINARY_NAME:-WatcherAgent} COPY windows-bin/${BINARY_NAME}.exe . ENTRYPOINT ["C:/app/${BINARY_NAME}.exe"] \ No newline at end of file From 4c03e8f91975399198a2e6c11b15dc005e57a671 Mon Sep 17 00:00:00 2001 From: donpat1to Date: Mon, 4 Aug 2025 21:12:55 +0200 Subject: [PATCH 24/49] building tagging and pushing in one go --- .github/workflows/build.yml | 12 ++---------- Dockerfile.windows | 2 +- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 72ab3fb..0b1a2f0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -197,16 +197,8 @@ jobs: --platform windows/amd64 \ -f Dockerfile.windows \ --build-arg BINARY_NAME=${{ needs.detect-project.outputs.project-name }} \ - --load \ - -t ${{ env.IMAGE_NAME }}:windows-${{ env.TAG }} . - - - name: Tag and Push Windows Docker image - if: ${{ success() }} - run: | - echo "Tagging Windows Docker image" - docker tag ${{ env.IMAGE_NAME }}:windows-${{ env.TAG }} ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:windows-${{ env.TAG }} - echo "Pushing Windows Docker image to registry: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:windows-${{ env.TAG }}" - docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:windows-${{ env.TAG }} + --push \ + -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:windows-${{ env.TAG }} . cleanup: name: Cleanup diff --git a/Dockerfile.windows b/Dockerfile.windows index 23b7631..5fac439 100644 --- a/Dockerfile.windows +++ b/Dockerfile.windows @@ -6,7 +6,7 @@ WORKDIR C:/app # Copy the Windows binary ARG BINARY_NAME -env BINARY_NAME=${BINARY_NAME:-WatcherAgent} +ENV BINARY_NAME=${BINARY_NAME:-WatcherAgent} COPY windows-bin/${BINARY_NAME}.exe . ENTRYPOINT ["C:/app/${BINARY_NAME}.exe"] \ No newline at end of file From 6371c78fc70395fe59f9ac15f7be8362d58c80f8 Mon Sep 17 00:00:00 2001 From: donpat1to Date: Tue, 5 Aug 2025 11:37:27 +0200 Subject: [PATCH 25/49] added insecure entitlement network.host --- .github/workflows/build.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0b1a2f0..038b76f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -149,8 +149,9 @@ jobs: - name: Linux build using Docker Buildx run: | - docker buildx create --use + docker buildx create --use --buildkitd-flags '--allow-insecure-entitlement network.host' docker buildx build \ + --allow network.host \ --platform linux/amd64 \ -f Dockerfile.linux \ --build-arg BINARY_NAME=${{ needs.detect-project.outputs.project-name }} \ @@ -192,8 +193,9 @@ jobs: - name: Windows build using Docker Buildx run: | - docker buildx create --use + docker buildx create --use --buildkitd-flags '--allow-insecure-entitlement network.host' docker buildx build \ + --allow network.host \ --platform windows/amd64 \ -f Dockerfile.windows \ --build-arg BINARY_NAME=${{ needs.detect-project.outputs.project-name }} \ @@ -203,7 +205,7 @@ jobs: cleanup: name: Cleanup if: always() - needs: [docker-build, native-build, windows-cross] + needs: [docker-build, native-build, windows-cross, windows-docker-build] runs-on: ubuntu-latest steps: - name: Cleanup Docker images From b1ea1dc96a6998e9676525a0233c772be6593b45 Mon Sep 17 00:00:00 2001 From: donpat1to Date: Tue, 5 Aug 2025 18:22:50 +0200 Subject: [PATCH 26/49] =?UTF-8?q?was=20ne=20verfreckte=20schei=C3=9Fe?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- WatcherAgent/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WatcherAgent/src/main.rs b/WatcherAgent/src/main.rs index 0a33294..6796e14 100644 --- a/WatcherAgent/src/main.rs +++ b/WatcherAgent/src/main.rs @@ -416,7 +416,7 @@ impl MetricsCollector { } fn get_cpu_temp() -> Option { - println!("Attempting to get CPU temperature..."); + println!("Attempting to get CPU temperature... "); #[cfg(target_os = "linux")] { From 59e5677ca41fb307876ff6fb30918a8608619396 Mon Sep 17 00:00:00 2001 From: donpat1to Date: Tue, 5 Aug 2025 18:56:34 +0200 Subject: [PATCH 27/49] removed windows from docker builds --- .github/workflows/build.yml | 39 +------------------------------------ 1 file changed, 1 insertion(+), 38 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 038b76f..febcde7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -149,9 +149,8 @@ jobs: - name: Linux build using Docker Buildx run: | - docker buildx create --use --buildkitd-flags '--allow-insecure-entitlement network.host' + docker buildx create --use docker buildx build \ - --allow network.host \ --platform linux/amd64 \ -f Dockerfile.linux \ --build-arg BINARY_NAME=${{ needs.detect-project.outputs.project-name }} \ @@ -166,42 +165,6 @@ jobs: echo "Pushing Linux Docker image to registry: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:linux-${{ env.TAG }}" docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:linux-${{ env.TAG }} - windows-docker-build: - name: Build Windows Docker Image - needs: [windows-cross] - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Download Windows artifact - uses: actions/download-artifact@v3 - with: - name: windows-binary - path: windows-bin - - - name: Verify artifacts - run: | - echo "Windows binary:" - ls -la windows-bin/ - - - name: Login to Docker Registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Windows build using Docker Buildx - run: | - docker buildx create --use --buildkitd-flags '--allow-insecure-entitlement network.host' - docker buildx build \ - --allow network.host \ - --platform windows/amd64 \ - -f Dockerfile.windows \ - --build-arg BINARY_NAME=${{ needs.detect-project.outputs.project-name }} \ - --push \ - -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:windows-${{ env.TAG }} . - cleanup: name: Cleanup if: always() From 2ac3111387c02a179d4ce5f0964b9725b94e1b24 Mon Sep 17 00:00:00 2001 From: donpat1to Date: Tue, 5 Aug 2025 19:37:31 +0200 Subject: [PATCH 28/49] removed pushing image --- .github/workflows/build.yml | 16 +++++++++------- Dockerfile.windows | 12 ------------ 2 files changed, 9 insertions(+), 19 deletions(-) delete mode 100644 Dockerfile.windows diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index febcde7..7f9b583 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -157,18 +157,20 @@ jobs: --load \ -t ${{ env.IMAGE_NAME }}:linux-${{ env.TAG }} . - - name: Tag and Push Linux Docker image - if: ${{ success() }} + - name: Save Docker image as artifact run: | - echo "Tagging Linux Docker image" - docker tag ${{ env.IMAGE_NAME }}:linux-${{ env.TAG }} ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:linux-${{ env.TAG }} - echo "Pushing Linux Docker image to registry: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:linux-${{ env.TAG }}" - docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:linux-${{ env.TAG }} + docker save ${{ env.IMAGE_NAME }}:linux-${{ env.TAG }} -o linux-image.tar + + - name: Upload Docker image artifact + uses: actions/upload-artifact@v3 + with: + name: linux-docker-image + path: linux-image.tar cleanup: name: Cleanup if: always() - needs: [docker-build, native-build, windows-cross, windows-docker-build] + needs: [docker-build, native-build, windows-cross] runs-on: ubuntu-latest steps: - name: Cleanup Docker images diff --git a/Dockerfile.windows b/Dockerfile.windows deleted file mode 100644 index 5fac439..0000000 --- a/Dockerfile.windows +++ /dev/null @@ -1,12 +0,0 @@ -# Using Windows Server Core as base -FROM mcr.microsoft.com/windows/servercore:ltsc2022 - -# Create app directory -WORKDIR C:/app - -# Copy the Windows binary -ARG BINARY_NAME -ENV BINARY_NAME=${BINARY_NAME:-WatcherAgent} -COPY windows-bin/${BINARY_NAME}.exe . - -ENTRYPOINT ["C:/app/${BINARY_NAME}.exe"] \ No newline at end of file From cb63c45fb6fd984cc3159ca4dcb118ff50ab1f54 Mon Sep 17 00:00:00 2001 From: donpat1to Date: Thu, 7 Aug 2025 18:17:23 +0200 Subject: [PATCH 29/49] checking build --- WatcherAgent/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WatcherAgent/src/main.rs b/WatcherAgent/src/main.rs index 6796e14..0a33294 100644 --- a/WatcherAgent/src/main.rs +++ b/WatcherAgent/src/main.rs @@ -416,7 +416,7 @@ impl MetricsCollector { } fn get_cpu_temp() -> Option { - println!("Attempting to get CPU temperature... "); + println!("Attempting to get CPU temperature..."); #[cfg(target_os = "linux")] { From 172782e3e5b597ad848390be07a678c8414caeb2 Mon Sep 17 00:00:00 2001 From: donpat1to Date: Fri, 8 Aug 2025 12:45:57 +0200 Subject: [PATCH 30/49] removed build on push for feat, bug and enhancement branaches --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7f9b583..42849d5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,7 +3,7 @@ name: Rust Cross-Platform Build on: workflow_dispatch: push: - branches: [ "development", "main", "feature/*", bug/*, enhancement/* ] + branches: [ "development", "main"] #"feature/*", bug/*, enhancement/* ] pull_request: branches: [ "development", "main" ] From 0cdab2443d4f86d556981fe594d9d37ecbd0491a Mon Sep 17 00:00:00 2001 From: donpat1to Date: Fri, 8 Aug 2025 14:19:12 +0200 Subject: [PATCH 31/49] put all files in their expected file --- WatcherAgent/Cargo.toml | 2 +- WatcherAgent/src/api.rs | 149 ++++++ WatcherAgent/src/hardware.rs | 79 +++ WatcherAgent/src/library.rs | 23 - WatcherAgent/src/main.rs | 938 +---------------------------------- WatcherAgent/src/metrics.rs | 507 +++++++++++++++++++ WatcherAgent/src/models.rs | 81 +++ 7 files changed, 835 insertions(+), 944 deletions(-) create mode 100644 WatcherAgent/src/api.rs create mode 100644 WatcherAgent/src/hardware.rs delete mode 100644 WatcherAgent/src/library.rs create mode 100644 WatcherAgent/src/metrics.rs create mode 100644 WatcherAgent/src/models.rs diff --git a/WatcherAgent/Cargo.toml b/WatcherAgent/Cargo.toml index 3c79d8d..8c46c74 100644 --- a/WatcherAgent/Cargo.toml +++ b/WatcherAgent/Cargo.toml @@ -16,7 +16,7 @@ sysinfo = "0.36.1" metrics = "0.24.2" chrono = "0.4" nvml-wrapper = "0.10" - +anyhow = "1.0.98" [target.'cfg(windows)'.dependencies] winapi = { version = "0.3", features = ["winuser", "pdh", "ifmib", "iphlpapi", "winerror" ,"wbemcli", "combaseapi"] } diff --git a/WatcherAgent/src/api.rs b/WatcherAgent/src/api.rs new file mode 100644 index 0000000..044ce3f --- /dev/null +++ b/WatcherAgent/src/api.rs @@ -0,0 +1,149 @@ +use std::time::Duration; + +use crate::models::{HardwareDto, HeartbeatDto, IdResponse, MetricDto, RegistrationDto}; +use anyhow::Result; +use reqwest::{Client, StatusCode}; +use std::error::Error; +use tokio::time::{interval, sleep}; + +pub async fn register_with_server(base_url: &str) -> Result<(i32, String), Box> { + // First get local IP + let ip = local_ip_address::local_ip()?.to_string(); + println!("Local IP address detected: {}", ip); + + // Get server ID from backend (this will retry until successful) + let (server_id, registered_ip) = get_server_id_by_ip(base_url, &ip).await?; + + // Create HTTP client for registration + let client = Client::builder() + .danger_accept_invalid_certs(true) + .build()?; + + // Collect hardware info + let hardware = HardwareDto::collect().await?; + + // Prepare registration data + let registration = RegistrationDto { + id: server_id, + ip_address: registered_ip.clone(), + cpu_type: hardware.cpu_type, + cpu_cores: hardware.cpu_cores, + gpu_type: hardware.gpu_type, + ram_size: hardware.ram_size, + }; + + // Try to register (will retry on failure) + loop { + println!("Attempting to register with server..."); + let url = format!("{}/monitoring/register-agent-by-id", base_url); + match client.post(&url).json(®istration).send().await { + Ok(resp) if resp.status().is_success() => { + println!("✅ Successfully registered with server."); + return Ok((server_id, registered_ip)); + } + Ok(resp) => { + let status = resp.status(); + let text = resp.text().await.unwrap_or_default(); + println!( + "⚠️ Registration failed ({}): {} (will retry in 10 seconds)", + status, text + ); + } + Err(err) => { + println!("⚠️ Registration error: {} (will retry in 10 seconds)", err); + } + } + sleep(Duration::from_secs(10)).await; + } +} + +async fn get_server_id_by_ip(base_url: &str, ip: &str) -> Result<(i32, String), Box> { + let client = Client::builder() + .danger_accept_invalid_certs(true) + .build()?; + + let url = format!("{}/monitoring/server-id-by-ip?ipAddress={}", base_url, ip); + + loop { + println!("Attempting to fetch server ID for IP {}...", ip); + match client.get(&url).send().await { + Ok(resp) if resp.status().is_success() => { + let text = resp.text().await?; + println!("Raw response: {}", text); // Debug output + + let id_resp: IdResponse = serde_json::from_str(&text).map_err(|e| { + println!("Failed to parse response: {}", e); + e + })?; + + println!( + "✅ Received ID {} for IP {}", + id_resp.id, id_resp.ip_address + ); + return Ok((id_resp.id, id_resp.ip_address)); + } + Ok(resp) if resp.status() == StatusCode::NOT_FOUND => { + println!( + "❌ Server with IP {} not found in database (will retry in 10 seconds)", + ip + ); + sleep(Duration::from_secs(10)).await; + } + Ok(resp) => { + println!( + "⚠️ Server responded with status: {} - {}", + resp.status(), + resp.text().await? + ); + sleep(Duration::from_secs(10)).await; + } + Err(err) => { + println!("⚠️ Request failed: {} (will retry in 10 seconds)", err); + sleep(Duration::from_secs(10)).await; + } + } + } +} + +pub async fn heartbeat_loop(base_url: &str, ip: &str) -> Result<(), Box> { + let client = Client::builder() + .danger_accept_invalid_certs(true) + .build()?; + let url = format!("{}/heartbeat/receive", base_url); + + loop { + let payload = HeartbeatDto { + ip_address: ip.to_string(), + }; + + match client.post(&url).json(&payload).send().await { + Ok(res) if res.status().is_success() => { + println!("Heartbeat sent successfully."); + } + Ok(res) => eprintln!("Server responded with status: {}", res.status()), + Err(e) => eprintln!("Heartbeat error: {}", e), + } + + sleep(Duration::from_secs(20)).await; + } +} + +pub async fn send_metrics(base_url: &str, metrics: &MetricDto) -> Result<(), Box> { + let client = Client::new(); + let url = format!("{}/monitoring/metric", base_url); + let mut interval = interval(Duration::from_secs(20)); + + loop { + interval.tick().await; + let metric = metrics; + + match client.post(&url).json(&metric).send().await { + Ok(res) => println!( + "✅ Sent metrics for server {} | Status: {}", + metric.server_id, + res.status() + ), + Err(err) => eprintln!("❌ Failed to send metrics: {}", err), + } + } +} diff --git a/WatcherAgent/src/hardware.rs b/WatcherAgent/src/hardware.rs new file mode 100644 index 0000000..cf6126c --- /dev/null +++ b/WatcherAgent/src/hardware.rs @@ -0,0 +1,79 @@ +use crate::models::HardwareDto; +use nvml_wrapper::Nvml; +use sysinfo::System; + +impl HardwareDto { + pub async fn collect() -> anyhow::Result { + let mut sys = System::new_all(); + sys.refresh_cpu_all(); + sys.refresh_memory(); + + let cpus = sys.cpus(); + let cpu_type = if !cpus.is_empty() { + cpus[0].brand().to_string() + } else { + "Unknown CPU".to_string() + }; + let cpu_cores = cpus.len() as i32; + let ram_bytes = sys.total_memory() as f64; + let gpu_type = Self::detect_gpu_name(); + let ip_address = local_ip_address::local_ip()?.to_string(); + + Ok(Self { + cpu_type, + cpu_cores, + gpu_type, + ram_size: ram_bytes, + ip_address, + }) + } + + fn detect_gpu_name() -> String { + // First try NVML (NVIDIA Management Library) + if let Some(name) = Self::try_nvml_gpu_name() { + return name; + } + + // Fallback to OS-specific commands + #[cfg(target_os = "linux")] + { + if let Ok(output) = std::process::Command::new("lshw") + .args(&["-C", "display"]) + .output() + { + if let Some(name) = String::from_utf8_lossy(&output.stdout) + .lines() + .find(|l| l.contains("product:")) + .map(|l| l.trim().replace("product:", "").trim().to_string()) + { + return name; + } + } + } + + #[cfg(target_os = "windows")] + { + if let Ok(output) = std::process::Command::new("wmic") + .args(&["path", "win32_VideoController", "get", "name"]) + .output() + { + if let Some(name) = String::from_utf8_lossy(&output.stdout) + .lines() + .nth(1) + .map(|s| s.trim().to_string()) + { + return name; + } + } + } + + // If all else fails + "Unknown GPU".to_string() + } + + fn try_nvml_gpu_name() -> Option { + let nvml = Nvml::init().ok()?; + let device = nvml.device_by_index(0).ok()?; + device.name().ok().map(|s| s.to_string()) + } +} diff --git a/WatcherAgent/src/library.rs b/WatcherAgent/src/library.rs deleted file mode 100644 index 5145f0a..0000000 --- a/WatcherAgent/src/library.rs +++ /dev/null @@ -1,23 +0,0 @@ -/* -$ rustc --crate-type=lib rary.rs -$ ls lib* -library.rlib - -// extern crate rary; // May be required for Rust 2015 edition or earlier - -fn main() { - rary::public_function(); - - // Error! `private_function` is private - //rary::private_function(); - - rary::indirect_access(); -} - -# Where library.rlib is the path to the compiled library, assumed that it's -# in the same directory here: -$ rustc executable.rs --extern rary=library.rlib && ./executable -called rary's `public_function()` -called rary's `indirect_access()`, that -> called rary's `private_function()` -*/ diff --git a/WatcherAgent/src/main.rs b/WatcherAgent/src/main.rs index 0a33294..b32c3d4 100644 --- a/WatcherAgent/src/main.rs +++ b/WatcherAgent/src/main.rs @@ -1,940 +1,38 @@ /// WatcherAgent - A Rust-based system monitoring agent /// This agent collects hardware metrics and sends them to a backend server. /// It supports CPU, GPU, RAM, disk, and network metrics. -//use chrono::Utc; -use nvml_wrapper::Nvml; -use reqwest::{Client, StatusCode}; -use serde::{Deserialize, Serialize}; -use std::{error::Error, time::Duration}; -use sysinfo::{Components, Disks, System}; -use tokio::time::{interval, sleep, Instant}; +mod api; +mod hardware; +mod metrics; +mod models; -// Windows specific imports - -// Data structures matching the C# DTOs -#[derive(Serialize, Debug)] -struct RegistrationDto { - #[serde(rename = "id")] - id: i32, - #[serde(rename = "ipAddress")] - ip_address: String, - #[serde(rename = "cpuType")] - cpu_type: String, - #[serde(rename = "cpuCores")] - cpu_cores: i32, - #[serde(rename = "gpuType")] - gpu_type: String, - #[serde(rename = "ramSize")] - ram_size: f64, -} - -#[derive(Serialize, Debug)] -struct MetricDto { - #[serde(rename = "serverId")] - server_id: i32, - #[serde(rename = "ipAddress")] - ip_address: String, - #[serde(rename = "cpu_Load")] - cpu_load: f64, - #[serde(rename = "cpu_Temp")] - cpu_temp: f64, - #[serde(rename = "gpu_Load")] - gpu_load: f64, - #[serde(rename = "gpu_Temp")] - gpu_temp: f64, - #[serde(rename = "gpu_Vram_Size")] - gpu_vram_size: f64, - #[serde(rename = "gpu_Vram_Usage")] - gpu_vram_usage: f64, - #[serde(rename = "ram_Load")] - ram_load: f64, - #[serde(rename = "ram_Size")] - ram_size: f64, - #[serde(rename = "disk_Size")] - disk_size: f64, - #[serde(rename = "disk_Usage")] - disk_usage: f64, - #[serde(rename = "disk_Temp")] - disk_temp: f64, - #[serde(rename = "net_In")] - net_in: f64, - #[serde(rename = "net_Out")] - net_out: f64, -} - -#[derive(Deserialize)] -struct IdResponse { - id: i32, - #[serde(rename = "ipAddress")] - ip_address: String, -} - -#[derive(Serialize)] -struct HeartbeatPayload { - #[serde(rename = "IpAddress")] - ip_address: String, -} - -#[derive(Serialize, Debug)] -struct HardwareInfo { - cpu_type: String, - cpu_cores: i32, - gpu_type: String, - ram_size: f64, - ip_address: String, -} - -struct NetworkState { - prev_rx: u64, - prev_tx: u64, - last_update: Instant, -} - -impl NetworkState { - fn new() -> Self { - Self { - prev_rx: 0, - prev_tx: 0, - last_update: Instant::now(), - } - } -} - -impl HardwareInfo { - async fn collect() -> Result> { - let mut sys = System::new_all(); - sys.refresh_cpu_all(); - sys.refresh_memory(); - - let cpus = sys.cpus(); - let cpu_type = if !cpus.is_empty() { - cpus[0].brand().to_string() - } else { - "Unknown CPU".to_string() - }; - let cpu_cores = cpus.len() as i32; - let ram_bytes = sys.total_memory() as f64; - let gpu_type = Self::detect_gpu_name(); - let ip_address = local_ip_address::local_ip()?.to_string(); - - Ok(Self { - cpu_type, - cpu_cores, - gpu_type, - ram_size: ram_bytes, - ip_address, - }) - } - - fn detect_gpu_name() -> String { - Self::try_nvml_gpu_name() - .or_else(Self::fallback_gpu_name) - .unwrap_or_else(|| "Unknown GPU".to_string()) - } - - fn try_nvml_gpu_name() -> Option { - let nvml = Nvml::init().ok()?; - let device = nvml.device_by_index(0).ok()?; - device.name().ok().map(|s| s.to_string()) - } - - fn fallback_gpu_name() -> Option { - #[cfg(target_os = "linux")] - { - let output = std::process::Command::new("lshw") - .args(&["-C", "display"]) - .output() - .ok()?; - Some( - String::from_utf8_lossy(&output.stdout) - .lines() - .find(|l| l.contains("product:")) - .map(|l| l.trim().replace("product:", "").trim().to_string()) - .unwrap_or("Unknown GPU".to_string()), - ) - } - - #[cfg(target_os = "windows")] - { - let output = std::process::Command::new("wmic") - .args(&["path", "win32_VideoController", "get", "name"]) - .output() - .ok()?; - Some( - String::from_utf8_lossy(&output.stdout) - .lines() - .nth(1) - .map(|s| s.trim().to_string()) - .unwrap_or("Unknown GPU".to_string()), - ) - } - } -} - -async fn get_server_id_by_ip(base_url: &str, ip: &str) -> Result<(i32, String), Box> { - let client = Client::builder() - .danger_accept_invalid_certs(true) - .build()?; - - let url = format!("{}/monitoring/server-id-by-ip?ipAddress={}", base_url, ip); - - loop { - println!("Attempting to fetch server ID for IP {}...", ip); - match client.get(&url).send().await { - Ok(resp) if resp.status().is_success() => { - let text = resp.text().await?; - println!("Raw response: {}", text); // Debug output - - let id_resp: IdResponse = serde_json::from_str(&text).map_err(|e| { - println!("Failed to parse response: {}", e); - e - })?; - - println!( - "✅ Received ID {} for IP {}", - id_resp.id, id_resp.ip_address - ); - return Ok((id_resp.id, id_resp.ip_address)); - } - Ok(resp) if resp.status() == StatusCode::NOT_FOUND => { - println!( - "❌ Server with IP {} not found in database (will retry in 10 seconds)", - ip - ); - sleep(Duration::from_secs(10)).await; - } - Ok(resp) => { - println!( - "⚠️ Server responded with status: {} - {}", - resp.status(), - resp.text().await? - ); - sleep(Duration::from_secs(10)).await; - } - Err(err) => { - println!("⚠️ Request failed: {} (will retry in 10 seconds)", err); - sleep(Duration::from_secs(10)).await; - } - } - } -} - -async fn register_with_server(base_url: &str) -> Result<(i32, String), Box> { - // First get local IP - let ip = local_ip_address::local_ip()?.to_string(); - println!("Local IP address detected: {}", ip); - - // Get server ID from backend (this will retry until successful) - let (server_id, registered_ip) = get_server_id_by_ip(base_url, &ip).await?; - - // Create HTTP client for registration - let client = Client::builder() - .danger_accept_invalid_certs(true) - .build()?; - - // Collect hardware info - let hardware = HardwareInfo::collect().await?; - - // Prepare registration data - let registration = RegistrationDto { - id: server_id, - ip_address: registered_ip.clone(), - cpu_type: hardware.cpu_type, - cpu_cores: hardware.cpu_cores, - gpu_type: hardware.gpu_type, - ram_size: hardware.ram_size, - }; - - // Try to register (will retry on failure) - loop { - println!("Attempting to register with server..."); - let url = format!("{}/monitoring/register-agent-by-id", base_url); - match client.post(&url).json(®istration).send().await { - Ok(resp) if resp.status().is_success() => { - println!("✅ Successfully registered with server."); - return Ok((server_id, registered_ip)); - } - Ok(resp) => { - let status = resp.status(); - let text = resp.text().await.unwrap_or_default(); - println!( - "⚠️ Registration failed ({}): {} (will retry in 10 seconds)", - status, text - ); - } - Err(err) => { - println!("⚠️ Registration error: {} (will retry in 10 seconds)", err); - } - } - sleep(Duration::from_secs(10)).await; - } -} - -async fn heartbeat_loop(base_url: &str, ip: &str) -> Result<(), Box> { - let client = Client::builder() - .danger_accept_invalid_certs(true) - .build()?; - let url = format!("{}/heartbeat/receive", base_url); - - loop { - let payload = HeartbeatPayload { - ip_address: ip.to_string(), - }; - - match client.post(&url).json(&payload).send().await { - Ok(res) if res.status().is_success() => { - println!("Heartbeat sent successfully."); - } - Ok(res) => eprintln!("Server responded with status: {}", res.status()), - Err(e) => eprintln!("Heartbeat error: {}", e), - } - - sleep(Duration::from_secs(20)).await; - } -} - -struct MetricsCollector { - sys: System, - nvml: Option, - server_id: i32, - ip_address: String, - network_state: NetworkState, -} - -impl MetricsCollector { - fn new(server_id: i32, ip_address: String) -> Self { - Self { - sys: System::new(), - nvml: Nvml::init().ok(), - server_id, - ip_address, - network_state: NetworkState::new(), - } - } - - async fn collect_and_send_loop(&mut self, base_url: &str) -> Result<(), Box> { - let client = Client::new(); - let url = format!("{}/monitoring/metric", base_url); - let mut interval = interval(Duration::from_secs(20)); - - loop { - interval.tick().await; - let metric = self.collect_metrics(); - println!("Collected metrics: {:?}", metric); - - match client.post(&url).json(&metric).send().await { - Ok(res) => println!( - "✅ Sent metrics for server {} | Status: {}", - metric.server_id, - res.status() - ), - Err(err) => eprintln!("❌ Failed to send metrics: {}", err), - } - } - } - - fn collect_metrics(&mut self) -> MetricDto { - self.sys.refresh_all(); - - // CPU - updated for sysinfo 0.35 - let cpu_load = self.sys.global_cpu_usage() as f64; - let cpu_temp = get_cpu_temp().unwrap_or(0.0) as f64; - - // RAM - updated for sysinfo 0.35 - let total_memory = self.sys.total_memory() as f64; - let used_memory = self.sys.used_memory() as f64; - let ram_load = (used_memory / total_memory) * 100.0; - let ram_size = total_memory; - - // Disk - updated for sysinfo 0.35 - let (disk_size, disk_usage, disk_temp) = get_disk_info(); - // GPU (NVIDIA) - let (gpu_temp, gpu_load, vram_used, vram_total) = if let Some(nvml) = &self.nvml { - if let Ok(device) = nvml.device_by_index(0) { - let temp = device - .temperature(nvml_wrapper::enum_wrappers::device::TemperatureSensor::Gpu) - .unwrap_or(0) as f64; - let load = device - .utilization_rates() - .map(|u| u.gpu as f64) - .unwrap_or(0.0); - let mem = device.memory_info().ok(); - let used = mem.clone().map(|m| (m.used as f64)).unwrap_or(0.0); // B - let total = mem.map(|m| (m.total as f64)).unwrap_or(0.0); // B - (temp, load, used, total) - } else { - (0.0, 0.0, 0.0, 0.0) - } - } else { - (0.0, 0.0, 0.0, 0.0) - }; - - // Network metrics - let (current_rx, current_tx) = get_network_traffic().unwrap_or((0, 0)); - let elapsed_secs = self.network_state.last_update.elapsed().as_secs_f64(); - self.network_state.last_update = Instant::now(); - - // Calculate the difference since the last call - let net_in = if current_rx >= self.network_state.prev_rx { - ((current_rx - self.network_state.prev_rx) as f64 * 8.0) / elapsed_secs - } else { - 0.0 - }; - - let net_out = if current_tx >= self.network_state.prev_tx { - ((current_tx - self.network_state.prev_tx) as f64 * 8.0) / elapsed_secs - } else { - 0.0 - }; - - // Store the current values for the next call - self.network_state.prev_rx = current_rx; - self.network_state.prev_tx = current_tx; - - MetricDto { - server_id: self.server_id, - ip_address: self.ip_address.clone(), - cpu_load, - cpu_temp, - gpu_load, - gpu_temp, - gpu_vram_size: vram_total, - gpu_vram_usage: if vram_total > 0.0 { - (vram_used / vram_total) * 100.0 - } else { - 0.0 - }, - ram_load, - ram_size, - disk_size, - disk_usage: disk_usage, - disk_temp: disk_temp, // not supported - net_in, - net_out, - } - } -} - -fn get_cpu_temp() -> Option { - println!("Attempting to get CPU temperature..."); - - #[cfg(target_os = "linux")] - { - use std::fs; - use std::process::Command; - println!(""); - if let Ok(output) = Command::new("sensors").output() { - let stdout = String::from_utf8_lossy(&output.stdout); - for line in stdout.lines() { - if line.contains("Package id") || line.contains("Tdie") || line.contains("CPU Temp") - { - if let Some(temp_str) = line - .split('+') - .nth(1) - .and_then(|s| s.split_whitespace().next()) - { - if let Ok(temp) = temp_str.replace("°C", "").parse::() { - return Some(temp); - } - } - } - } - } - - // 2. Sysfs (Intel/AMD) - if let Ok(content) = fs::read_to_string("/sys/class/thermal/thermal_zone0/temp") { - if let Ok(temp) = content.trim().parse::() { - return Some(temp / 1000.0); - } - } - - // 3. Alternative Sysfs-Pfade - let paths = [ - "/sys/class/hwmon/hwmontemp1_input", - "/sys/class/hwmon/hwmondevice/temp1_input", - ]; - - for path_pattern in &paths { - if let Ok(paths) = glob::glob(path_pattern) { - for path in paths.flatten() { - if let Ok(content) = fs::read_to_string(&path) { - if let Ok(temp) = content.trim().parse::() { - return Some(temp / 1000.0); - } - } - } - } - } - - None - } - - #[cfg(target_os = "windows")] - fn failed(hr: winapi::shared::winerror::HRESULT) -> bool { - hr < 0 - } - - #[cfg(target_os = "windows")] - { - use com::runtime::init_runtime; - use com::sys::CLSCTX_INPROC_SERVER; - use widestring::U16CString; - use winapi::shared::rpcdce::*; - use winapi::shared::wtypes::VT_I4; - use winapi::um::oaidl::VARIANT; - use winapi::um::objidlbase::EOAC_NONE; - use winapi::um::{combaseapi, wbemcli}; - - init_runtime().ok()?; - - unsafe { - let mut locator: *mut wbemcli::IWbemLocator = std::ptr::null_mut(); - let hr = combaseapi::CoCreateInstance( - &wbemcli::CLSID_WbemLocator, - std::ptr::null_mut(), - CLSCTX_INPROC_SERVER, - &wbemcli::IID_IWbemLocator, - &mut locator as *mut _ as *mut _, - ); - - if hr != 0 { - eprintln!("Failed to create WbemLocator (HRESULT: {})", hr); - return None; - } - - let mut services: *mut wbemcli::IWbemServices = std::ptr::null_mut(); - let namespace = U16CString::from_str("root\\cimv2").unwrap(); // Changed to more common namespace - let hr = (*locator).ConnectServer( - namespace.as_ptr().cast_mut(), - std::ptr::null_mut(), - std::ptr::null_mut(), - std::ptr::null_mut(), - 0, - std::ptr::null_mut(), - std::ptr::null_mut(), - &mut services, - ); - - if hr != 0 { - eprintln!("Failed to connect to WMI (HRESULT: {})", hr); - (*locator).Release(); - return None; - } - - // Set security levels - let hr = combaseapi::CoSetProxyBlanket( - services as *mut _, - RPC_C_AUTHN_WINNT, - RPC_C_AUTHZ_NONE, - std::ptr::null_mut(), - RPC_C_AUTHN_LEVEL_CALL, - RPC_C_IMP_LEVEL_IMPERSONATE, - std::ptr::null_mut(), - EOAC_NONE, - ); - - if hr != 0 { - eprintln!("Failed to set proxy blanket (HRESULT: {})", hr); - (*services).Release(); - (*locator).Release(); - return None; - } - - // Try different temperature queries - some systems might have different WMI classes - let queries = [ - "SELECT * FROM Win32_PerfFormattedData_Counters_ThermalZoneInformation", - "SELECT * FROM MSAcpi_ThermalZoneTemperature", - "SELECT * FROM Win32_TemperatureProbe", - ]; - - let mut result = None; - - for query_str in queries.iter() { - let query = U16CString::from_str(query_str).unwrap(); - let mut enumerator: *mut wbemcli::IEnumWbemClassObject = std::ptr::null_mut(); - let hr = (*services).ExecQuery( - U16CString::from_str("WQL").unwrap().as_ptr().cast_mut(), - query.as_ptr().cast_mut(), - wbemcli::WBEM_FLAG_FORWARD_ONLY as i32, - std::ptr::null_mut(), - &mut enumerator, - ); - - if hr != 0 { - continue; // Try next query if this one fails - } - - let mut obj: *mut wbemcli::IWbemClassObject = std::ptr::null_mut(); - let mut returned = 0; - let hr = (*enumerator).Next( - wbemcli::WBEM_INFINITE as i32, // Fixed: cast directly to i32 - 1, - &mut obj, - &mut returned, - ); - - if failed(hr) { - eprintln!("Failed to enumerate WMI objects (HRESULT: {})", hr); - (*enumerator).Release(); - continue; - } - - if returned == 0 { - // No more items - (*enumerator).Release(); - continue; - } - - if hr == 0 && returned > 0 { - let mut variant = std::mem::zeroed::(); - // Try different possible property names - let property_names = ["CurrentTemperature", "Temperature", "CurrentReading"]; - - for prop in property_names.iter() { - let hr = (*obj).Get( - U16CString::from_str(prop).unwrap().as_ptr(), - 0, - &mut variant, - std::ptr::null_mut(), - std::ptr::null_mut(), - ); - - if hr == 0 && variant.n1.n2().vt as u32 == VT_I4 { - let temp_kelvin = *variant.n1.n2().n3.intVal() as f32 / 10.0; - result = Some(temp_kelvin - 273.15); // Convert to Celsius - break; - } - } - - (*obj).Release(); - (*enumerator).Release(); - if result.is_some() { - break; - } - } - - if !enumerator.is_null() { - (*enumerator).Release(); - } - } - - (*services).Release(); - (*locator).Release(); - - result - } - } - - #[cfg(not(any(target_os = "linux", target_os = "windows")))] - { - println!("CPU temperature retrieval not supported on this OS."); - None - } -} - -fn get_disk_info() -> (f64, f64, f64) { - let mut sys = System::new(); - sys.refresh_all(); - //sys.refresh_disks_list(); - - let mut total_size = 0u64; - let mut total_used = 0u64; - let mut count = 0; - - let disks = Disks::new_with_refreshed_list(); - for disk in disks.list() { - // Ignoriere CD-ROMs und kleine Systempartitionen - println!( - "Disk_Name: {:?}, Disk_Kind: {}, Total: {}, Available: {}", - disk.name(), - disk.kind(), - disk.total_space(), - disk.available_space() - ); - if disk.total_space() > 100 * 1024 * 1024 { - // > 100MB - total_size += disk.total_space(); - total_used += disk.total_space() - disk.available_space(); - count += 1; - } - } - let components = Components::new_with_refreshed_list(); - for component in &components { - if let Some(temperature) = component.temperature() { - println!( - "Component_Label: {}, Temperature: {}°C", - component.label(), - temperature - ); - } - } - - // Berechnungen - let size_b = if count > 0 { - total_size as f64 // in Bytes - } else { - // Fallback: Versuche df unter Linux - println!("Fallback: Using 'df' command to get disk info."); - #[cfg(target_os = "linux")] - { - use std::process::Command; - if let Ok(output) = Command::new("df") - .arg("-B1") - .arg("--output=size,used") - .output() - { - let stdout = String::from_utf8_lossy(&output.stdout); - for line in stdout.lines().skip(1) { - let parts: Vec<&str> = line.split_whitespace().collect(); - if parts.len() == 2 { - if let (Ok(size), Ok(used)) = - (parts[0].parse::(), parts[1].parse::()) - { - total_size += size; - total_used += used; - count += 1; - } - } - } - total_size as f64 // in Bytes - } else { - 0.0 - } - } - #[cfg(not(target_os = "linux"))] - { - 0.0 - } - }; - - let usage = if total_size > 0 { - (total_used as f64 / total_size as f64) * 100.0 - } else { - 0.0 - }; - - (size_b, usage, 0.0) // Disk-Temp bleibt 0.0 ohne spezielle Hardware -} - -fn get_network_traffic() -> Option<(u64, u64)> { - #[cfg(target_os = "windows")] - { - use std::ptr::null_mut; - use winapi::shared::ifmib::MIB_IFTABLE; - use winapi::um::iphlpapi::GetIfTable; - - unsafe { - // Erste Abfrage zur Bestimmung der benötigten Puffergröße - let mut buffer_size = 0u32; - if GetIfTable(null_mut(), &mut buffer_size, 0) - != winapi::shared::winerror::ERROR_INSUFFICIENT_BUFFER - { - return None; - } - - // Puffer allozieren - let mut buffer = vec![0u8; buffer_size as usize]; - let if_table = buffer.as_mut_ptr() as *mut MIB_IFTABLE; - - // Tatsächliche Daten abrufen - if GetIfTable(if_table, &mut buffer_size, 0) != 0 { - return None; - } - - // Daten auswerten - let mut rx_total = 0u64; - let mut tx_total = 0u64; - - for i in 0..(*if_table).dwNumEntries { - let row = &*((*if_table).table.as_ptr().offset(i as isize)); - rx_total += row.dwInOctets as u64; - tx_total += row.dwOutOctets as u64; - } - - if rx_total == 0 && tx_total == 0 { - return None; - } else { - return Some((rx_total, tx_total)); - } - } - } - - #[cfg(target_os = "linux")] - { - use std::fs; - - let mut rx_total = 0u64; - let mut tx_total = 0u64; - if let Ok(dir) = fs::read_dir("/sys/class/net") { - for entry in dir.flatten() { - let iface = entry.file_name(); - let iface_name = iface.to_string_lossy(); - - // Ignoriere virtuelle Interfaces - if !iface_name.starts_with("lo") && !iface_name.starts_with("virbr") { - if let (Ok(rx), Ok(tx)) = ( - fs::read_to_string(entry.path().join("statistics/rx_bytes")), - fs::read_to_string(entry.path().join("statistics/tx_bytes")), - ) { - rx_total += rx.trim().parse::().unwrap_or(0); - tx_total += tx.trim().parse::().unwrap_or(0); - } - } - } - } - - if rx_total == 0 && tx_total == 0 { - return None; - } else { - return Some((rx_total, tx_total)); - } - } - - #[cfg(not(any(target_os = "windows", target_os = "linux")))] - None -} +use anyhow::Result; +use std::error::Error; #[tokio::main] async fn main() -> Result<(), Box> { - let server_base_url = "http://localhost:5000"; + let server_url = "http://localhost:5000"; - // Registration phase - println!("Starting registration process..."); - let (server_id, ip_address) = register_with_server(server_base_url).await?; + // Registration + let (server_id, ip) = api::register_with_server(server_url).await?; + // Start background tasks // Start heartbeat in background let heartbeat_handle = tokio::spawn({ - let ip = ip_address.clone(); + let ip = ip.clone(); async move { - if let Err(e) = heartbeat_loop(server_base_url, &ip).await { + if let Err(e) = api::heartbeat_loop(server_url, &ip).await { eprintln!("Heartbeat loop failed: {}", e); } } }); - // Start metrics collection - println!("Starting metrics collection..."); - let mut metrics_collector = MetricsCollector::new(server_id, ip_address); - metrics_collector - .collect_and_send_loop(server_base_url) - .await?; - heartbeat_handle.await?; + + // Main metrics loop + println!("Starting metrics collection..."); + let mut collector = metrics::Collector::new(server_id, ip); + collector.run(server_url).await?; + Ok(()) } - -#[cfg(test)] -mod tests { - use super::*; - - // Test CPU temperature collection - #[test] - fn test_cpu_temp() { - let temp = get_cpu_temp(); - println!("CPU Temperature: {:?}°C", temp); - - // Basic validation - temp should be between 0-100°C if available - if let Some(t) = temp { - assert!( - t >= 0.0 && t <= 100.0, - "CPU temperature out of reasonable range" - ); - } - } - - // Test disk information collection - #[test] - fn test_disk_info() { - let (size, usage, _temp) = get_disk_info(); - println!("Disk Size: {:.2}GB, Usage: {:.2}%", size, usage); - - assert!(size >= 0.0, "Disk size should be non-negative"); - assert!( - usage >= 0.0 && usage <= 100.0, - "Disk usage should be 0-100%" - ); - } - - // Test hardware info collection - #[tokio::test] - async fn test_hardware_info() { - let hardware = HardwareInfo::collect().await.unwrap(); - println!("Hardware Info: {:?}", hardware); - - assert!( - !hardware.cpu_type.is_empty(), - "CPU type should not be empty" - ); - assert!(hardware.cpu_cores > 0, "CPU cores should be positive"); - assert!( - !hardware.gpu_type.is_empty(), - "GPU type should not be empty" - ); - assert!(hardware.ram_size > 0.0, "RAM size should be positive"); - assert!( - !hardware.ip_address.is_empty(), - "IP address should not be empty" - ); - } - - // Test metrics collector - #[tokio::test] - async fn test_metrics_collector() { - let mut collector = MetricsCollector::new(1, "127.0.0.1".to_string()); - let metrics = collector.collect_metrics(); - println!("Collected Metrics: {:?}", metrics); - - // Validate basic metrics ranges - assert!( - metrics.cpu_load >= 0.0 && metrics.cpu_load <= 100.0, - "CPU load should be 0-100%" - ); - assert!( - metrics.ram_load >= 0.0 && metrics.ram_load <= 100.0, - "RAM load should be 0-100%" - ); - assert!(metrics.ram_size > 0.0, "RAM size should be positive"); - } - - // Test registration flow (mock server needed for full test) - #[tokio::test] - async fn test_registration_flow() { - // Note: This would require a mock server for proper testing - // Currently just testing the hardware detection part - let hardware = HardwareInfo::collect().await.unwrap(); - let registration = RegistrationDto { - id: 1, - ip_address: hardware.ip_address.clone(), - cpu_type: hardware.cpu_type, - cpu_cores: hardware.cpu_cores, - gpu_type: hardware.gpu_type, - ram_size: hardware.ram_size, - }; - - println!("Registration DTO: {:?}", registration); - assert_eq!(registration.id, 1); - assert!(!registration.ip_address.is_empty()); - } - - // Test error cases - #[test] - fn test_error_handling() { - // Test with invalid paths - #[cfg(target_os = "linux")] - { - let temp = get_cpu_temp_with_path("/invalid/path"); - assert!(temp.is_none(), "Should handle invalid paths gracefully"); - } - } - - // Helper function for testing with custom paths - #[cfg(target_os = "linux")] - fn get_cpu_temp_with_path(path: &str) -> Option { - fs::read_to_string(path) - .ok()? - .trim() - .parse::() - .map(|t| t / 1000.0) - .ok() - } -} diff --git a/WatcherAgent/src/metrics.rs b/WatcherAgent/src/metrics.rs new file mode 100644 index 0000000..e379f12 --- /dev/null +++ b/WatcherAgent/src/metrics.rs @@ -0,0 +1,507 @@ +use std::error::Error; +use std::time::Duration; + +use sysinfo::{Components, Disks, System}; +use tokio::time::Instant; + +use crate::api; +use crate::models::{MetricDto, NetworkState}; +use nvml_wrapper::Nvml; + +pub struct Collector { + sys: System, + nvml: Option, + server_id: i32, + ip_address: String, + network_state: NetworkState, +} + +impl NetworkState { + fn new() -> Self { + Self { + prev_rx: 0, + prev_tx: 0, + last_update: Instant::now(), + } + } +} + +impl Collector { + pub fn new(server_id: i32, ip_address: String) -> Self { + Self { + sys: System::new(), + nvml: Nvml::init().ok(), + server_id, + ip_address, + network_state: NetworkState::new(), + } + } + + pub async fn run(&mut self, base_url: &str) -> anyhow::Result<(), Box> { + loop { + let metrics = self.collect(); + api::send_metrics(base_url, &metrics).await?; + tokio::time::sleep(Duration::from_secs(20)).await; + } + } + + pub fn collect(&mut self) -> MetricDto { + self.sys.refresh_all(); + + // CPU - updated for sysinfo 0.35 + let cpu_load = self.sys.global_cpu_usage() as f64; + let cpu_temp = get_cpu_temp().unwrap_or(0.0) as f64; + + // RAM - updated for sysinfo 0.35 + let total_memory = self.sys.total_memory() as f64; + let used_memory = self.sys.used_memory() as f64; + let ram_load = (used_memory / total_memory) * 100.0; + let ram_size = total_memory; + + // Disk - updated for sysinfo 0.35 + let (disk_size, disk_usage, disk_temp) = get_disk_info(); + // GPU (NVIDIA) + let (gpu_temp, gpu_load, vram_used, vram_total) = if let Some(nvml) = &self.nvml { + if let Ok(device) = nvml.device_by_index(0) { + let temp = device + .temperature(nvml_wrapper::enum_wrappers::device::TemperatureSensor::Gpu) + .unwrap_or(0) as f64; + let load = device + .utilization_rates() + .map(|u| u.gpu as f64) + .unwrap_or(0.0); + let mem = device.memory_info().ok(); + let used = mem.clone().map(|m| (m.used as f64)).unwrap_or(0.0); // B + let total = mem.map(|m| (m.total as f64)).unwrap_or(0.0); // B + (temp, load, used, total) + } else { + (0.0, 0.0, 0.0, 0.0) + } + } else { + (0.0, 0.0, 0.0, 0.0) + }; + + // Network metrics + let (current_rx, current_tx) = get_network_traffic().unwrap_or((0, 0)); + let elapsed_secs = self.network_state.last_update.elapsed().as_secs_f64(); + self.network_state.last_update = Instant::now(); + + // Calculate the difference since the last call + let net_in = if current_rx >= self.network_state.prev_rx { + ((current_rx - self.network_state.prev_rx) as f64 * 8.0) / elapsed_secs + } else { + 0.0 + }; + + let net_out = if current_tx >= self.network_state.prev_tx { + ((current_tx - self.network_state.prev_tx) as f64 * 8.0) / elapsed_secs + } else { + 0.0 + }; + + // Store the current values for the next call + self.network_state.prev_rx = current_rx; + self.network_state.prev_tx = current_tx; + + MetricDto { + server_id: self.server_id, + ip_address: self.ip_address.clone(), + cpu_load, + cpu_temp, + gpu_load, + gpu_temp, + gpu_vram_size: vram_total, + gpu_vram_usage: if vram_total > 0.0 { + (vram_used / vram_total) * 100.0 + } else { + 0.0 + }, + ram_load, + ram_size, + disk_size, + disk_usage: disk_usage, + disk_temp: disk_temp, // not supported + net_in, + net_out, + } + } +} + +pub fn get_cpu_temp() -> Option { + println!("Attempting to get CPU temperature..."); + + #[cfg(target_os = "linux")] + { + use std::fs; + use std::process::Command; + println!(""); + if let Ok(output) = Command::new("sensors").output() { + let stdout = String::from_utf8_lossy(&output.stdout); + for line in stdout.lines() { + if line.contains("Package id") || line.contains("Tdie") || line.contains("CPU Temp") + { + if let Some(temp_str) = line + .split('+') + .nth(1) + .and_then(|s| s.split_whitespace().next()) + { + if let Ok(temp) = temp_str.replace("°C", "").parse::() { + return Some(temp); + } + } + } + } + } + + // 2. Sysfs (Intel/AMD) + if let Ok(content) = fs::read_to_string("/sys/class/thermal/thermal_zone0/temp") { + if let Ok(temp) = content.trim().parse::() { + return Some(temp / 1000.0); + } + } + + // 3. Alternative Sysfs-Pfade + let paths = [ + "/sys/class/hwmon/hwmontemp1_input", + "/sys/class/hwmon/hwmondevice/temp1_input", + ]; + + for path_pattern in &paths { + if let Ok(paths) = glob::glob(path_pattern) { + for path in paths.flatten() { + if let Ok(content) = fs::read_to_string(&path) { + if let Ok(temp) = content.trim().parse::() { + return Some(temp / 1000.0); + } + } + } + } + } + + None + } + + #[cfg(target_os = "windows")] + fn failed(hr: winapi::shared::winerror::HRESULT) -> bool { + hr < 0 + } + + #[cfg(target_os = "windows")] + { + use com::runtime::init_runtime; + use com::sys::CLSCTX_INPROC_SERVER; + use widestring::U16CString; + use winapi::shared::rpcdce::*; + use winapi::shared::wtypes::VT_I4; + use winapi::um::oaidl::VARIANT; + use winapi::um::objidlbase::EOAC_NONE; + use winapi::um::{combaseapi, wbemcli}; + + init_runtime().ok()?; + + unsafe { + let mut locator: *mut wbemcli::IWbemLocator = std::ptr::null_mut(); + let hr = combaseapi::CoCreateInstance( + &wbemcli::CLSID_WbemLocator, + std::ptr::null_mut(), + CLSCTX_INPROC_SERVER, + &wbemcli::IID_IWbemLocator, + &mut locator as *mut _ as *mut _, + ); + + if hr != 0 { + eprintln!("Failed to create WbemLocator (HRESULT: {})", hr); + return None; + } + + let mut services: *mut wbemcli::IWbemServices = std::ptr::null_mut(); + let namespace = U16CString::from_str("root\\cimv2").unwrap(); // Changed to more common namespace + let hr = (*locator).ConnectServer( + namespace.as_ptr().cast_mut(), + std::ptr::null_mut(), + std::ptr::null_mut(), + std::ptr::null_mut(), + 0, + std::ptr::null_mut(), + std::ptr::null_mut(), + &mut services, + ); + + if hr != 0 { + eprintln!("Failed to connect to WMI (HRESULT: {})", hr); + (*locator).Release(); + return None; + } + + // Set security levels + let hr = combaseapi::CoSetProxyBlanket( + services as *mut _, + RPC_C_AUTHN_WINNT, + RPC_C_AUTHZ_NONE, + std::ptr::null_mut(), + RPC_C_AUTHN_LEVEL_CALL, + RPC_C_IMP_LEVEL_IMPERSONATE, + std::ptr::null_mut(), + EOAC_NONE, + ); + + if hr != 0 { + eprintln!("Failed to set proxy blanket (HRESULT: {})", hr); + (*services).Release(); + (*locator).Release(); + return None; + } + + // Try different temperature queries - some systems might have different WMI classes + let queries = [ + "SELECT * FROM Win32_PerfFormattedData_Counters_ThermalZoneInformation", + "SELECT * FROM MSAcpi_ThermalZoneTemperature", + "SELECT * FROM Win32_TemperatureProbe", + ]; + + let mut result = None; + + for query_str in queries.iter() { + let query = U16CString::from_str(query_str).unwrap(); + let mut enumerator: *mut wbemcli::IEnumWbemClassObject = std::ptr::null_mut(); + let hr = (*services).ExecQuery( + U16CString::from_str("WQL").unwrap().as_ptr().cast_mut(), + query.as_ptr().cast_mut(), + wbemcli::WBEM_FLAG_FORWARD_ONLY as i32, + std::ptr::null_mut(), + &mut enumerator, + ); + + if hr != 0 { + continue; // Try next query if this one fails + } + + let mut obj: *mut wbemcli::IWbemClassObject = std::ptr::null_mut(); + let mut returned = 0; + let hr = (*enumerator).Next( + wbemcli::WBEM_INFINITE as i32, // Fixed: cast directly to i32 + 1, + &mut obj, + &mut returned, + ); + + if failed(hr) { + eprintln!("Failed to enumerate WMI objects (HRESULT: {})", hr); + (*enumerator).Release(); + continue; + } + + if returned == 0 { + // No more items + (*enumerator).Release(); + continue; + } + + if hr == 0 && returned > 0 { + let mut variant = std::mem::zeroed::(); + // Try different possible property names + let property_names = ["CurrentTemperature", "Temperature", "CurrentReading"]; + + for prop in property_names.iter() { + let hr = (*obj).Get( + U16CString::from_str(prop).unwrap().as_ptr(), + 0, + &mut variant, + std::ptr::null_mut(), + std::ptr::null_mut(), + ); + + if hr == 0 && variant.n1.n2().vt as u32 == VT_I4 { + let temp_kelvin = *variant.n1.n2().n3.intVal() as f32 / 10.0; + result = Some(temp_kelvin - 273.15); // Convert to Celsius + break; + } + } + + (*obj).Release(); + (*enumerator).Release(); + if result.is_some() { + break; + } + } + + if !enumerator.is_null() { + (*enumerator).Release(); + } + } + + (*services).Release(); + (*locator).Release(); + + result + } + } + + #[cfg(not(any(target_os = "linux", target_os = "windows")))] + { + println!("CPU temperature retrieval not supported on this OS."); + None + } +} + +fn get_network_traffic() -> Option<(u64, u64)> { + #[cfg(target_os = "windows")] + { + use std::ptr::null_mut; + use winapi::shared::ifmib::MIB_IFTABLE; + use winapi::um::iphlpapi::GetIfTable; + + unsafe { + // Erste Abfrage zur Bestimmung der benötigten Puffergröße + let mut buffer_size = 0u32; + if GetIfTable(null_mut(), &mut buffer_size, 0) + != winapi::shared::winerror::ERROR_INSUFFICIENT_BUFFER + { + return None; + } + + // Puffer allozieren + let mut buffer = vec![0u8; buffer_size as usize]; + let if_table = buffer.as_mut_ptr() as *mut MIB_IFTABLE; + + // Tatsächliche Daten abrufen + if GetIfTable(if_table, &mut buffer_size, 0) != 0 { + return None; + } + + // Daten auswerten + let mut rx_total = 0u64; + let mut tx_total = 0u64; + + for i in 0..(*if_table).dwNumEntries { + let row = &*((*if_table).table.as_ptr().offset(i as isize)); + rx_total += row.dwInOctets as u64; + tx_total += row.dwOutOctets as u64; + } + + if rx_total == 0 && tx_total == 0 { + return None; + } else { + return Some((rx_total, tx_total)); + } + } + } + + #[cfg(target_os = "linux")] + { + use std::fs; + + let mut rx_total = 0u64; + let mut tx_total = 0u64; + if let Ok(dir) = fs::read_dir("/sys/class/net") { + for entry in dir.flatten() { + let iface = entry.file_name(); + let iface_name = iface.to_string_lossy(); + + // Ignoriere virtuelle Interfaces + if !iface_name.starts_with("lo") && !iface_name.starts_with("virbr") { + if let (Ok(rx), Ok(tx)) = ( + fs::read_to_string(entry.path().join("statistics/rx_bytes")), + fs::read_to_string(entry.path().join("statistics/tx_bytes")), + ) { + rx_total += rx.trim().parse::().unwrap_or(0); + tx_total += tx.trim().parse::().unwrap_or(0); + } + } + } + } + + if rx_total == 0 && tx_total == 0 { + return None; + } else { + return Some((rx_total, tx_total)); + } + } + + #[cfg(not(any(target_os = "windows", target_os = "linux")))] + None +} + +pub fn get_disk_info() -> (f64, f64, f64) { + let mut sys = System::new(); + sys.refresh_all(); + //sys.refresh_disks_list(); + + let mut total_size = 0u64; + let mut total_used = 0u64; + let mut count = 0; + + let disks = Disks::new_with_refreshed_list(); + for disk in disks.list() { + // Ignoriere CD-ROMs und kleine Systempartitionen + println!( + "Disk_Name: {:?}, Disk_Kind: {}, Total: {}, Available: {}", + disk.name(), + disk.kind(), + disk.total_space(), + disk.available_space() + ); + if disk.total_space() > 100 * 1024 * 1024 { + // > 100MB + total_size += disk.total_space(); + total_used += disk.total_space() - disk.available_space(); + count += 1; + } + } + let components = Components::new_with_refreshed_list(); + for component in &components { + if let Some(temperature) = component.temperature() { + println!( + "Component_Label: {}, Temperature: {}°C", + component.label(), + temperature + ); + } + } + + // Berechnungen + let size_b = if count > 0 { + total_size as f64 // in Bytes + } else { + // Fallback: Versuche df unter Linux + println!("Fallback: Using 'df' command to get disk info."); + #[cfg(target_os = "linux")] + { + use std::process::Command; + if let Ok(output) = Command::new("df") + .arg("-B1") + .arg("--output=size,used") + .output() + { + let stdout = String::from_utf8_lossy(&output.stdout); + for line in stdout.lines().skip(1) { + let parts: Vec<&str> = line.split_whitespace().collect(); + if parts.len() == 2 { + if let (Ok(size), Ok(used)) = + (parts[0].parse::(), parts[1].parse::()) + { + total_size += size; + total_used += used; + count += 1; + } + } + } + total_size as f64 // in Bytes + } else { + 0.0 + } + } + #[cfg(not(target_os = "linux"))] + { + 0.0 + } + }; + + let usage = if total_size > 0 { + (total_used as f64 / total_size as f64) * 100.0 + } else { + 0.0 + }; + + (size_b, usage, 0.0) // Disk-Temp bleibt 0.0 ohne spezielle Hardware +} diff --git a/WatcherAgent/src/models.rs b/WatcherAgent/src/models.rs new file mode 100644 index 0000000..4f47fda --- /dev/null +++ b/WatcherAgent/src/models.rs @@ -0,0 +1,81 @@ +use serde::{Deserialize, Serialize}; +use tokio::time::Instant; + +// Data structures matching the C# DTOs +#[derive(Serialize, Debug)] +pub struct RegistrationDto { + #[serde(rename = "id")] + pub id: i32, + #[serde(rename = "ipAddress")] + pub ip_address: String, + #[serde(rename = "cpuType")] + pub cpu_type: String, + #[serde(rename = "cpuCores")] + pub cpu_cores: i32, + #[serde(rename = "gpuType")] + pub gpu_type: String, + #[serde(rename = "ramSize")] + pub ram_size: f64, +} + +#[derive(Serialize, Debug)] +pub struct MetricDto { + #[serde(rename = "serverId")] + pub server_id: i32, + #[serde(rename = "ipAddress")] + pub ip_address: String, + #[serde(rename = "cpu_Load")] + pub cpu_load: f64, + #[serde(rename = "cpu_Temp")] + pub cpu_temp: f64, + #[serde(rename = "gpu_Load")] + pub gpu_load: f64, + #[serde(rename = "gpu_Temp")] + pub gpu_temp: f64, + #[serde(rename = "gpu_Vram_Size")] + pub gpu_vram_size: f64, + #[serde(rename = "gpu_Vram_Usage")] + pub gpu_vram_usage: f64, + #[serde(rename = "ram_Load")] + pub ram_load: f64, + #[serde(rename = "ram_Size")] + pub ram_size: f64, + #[serde(rename = "disk_Size")] + pub disk_size: f64, + #[serde(rename = "disk_Usage")] + pub disk_usage: f64, + #[serde(rename = "disk_Temp")] + pub disk_temp: f64, + #[serde(rename = "net_In")] + pub net_in: f64, + #[serde(rename = "net_Out")] + pub net_out: f64, +} + +#[derive(Deserialize)] +pub struct IdResponse { + pub id: i32, + #[serde(rename = "ipAddress")] + pub ip_address: String, +} + +#[derive(Serialize)] +pub struct HeartbeatDto { + #[serde(rename = "IpAddress")] + pub ip_address: String, +} + +#[derive(Serialize, Debug)] +pub struct HardwareDto { + pub cpu_type: String, + pub cpu_cores: i32, + pub gpu_type: String, + pub ram_size: f64, + pub ip_address: String, +} + +pub struct NetworkState { + pub prev_rx: u64, + pub prev_tx: u64, + pub last_update: Instant, +} From f0b89a9c3229a3437bdd2e35c6880c6679169ff8 Mon Sep 17 00:00:00 2001 From: donpat1to Date: Fri, 8 Aug 2025 14:27:21 +0200 Subject: [PATCH 32/49] buildtest for new file struc --- .github/workflows/build.yml | 2 +- WatcherAgent/src/hardware/cpu.rs | 0 WatcherAgent/src/hardware/disk.rs | 0 WatcherAgent/src/hardware/gpu.rs | 0 WatcherAgent/src/hardware/memory.rs | 0 WatcherAgent/src/hardware/network.rs | 0 6 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 WatcherAgent/src/hardware/cpu.rs create mode 100644 WatcherAgent/src/hardware/disk.rs create mode 100644 WatcherAgent/src/hardware/gpu.rs create mode 100644 WatcherAgent/src/hardware/memory.rs create mode 100644 WatcherAgent/src/hardware/network.rs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 42849d5..7f9b583 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,7 +3,7 @@ name: Rust Cross-Platform Build on: workflow_dispatch: push: - branches: [ "development", "main"] #"feature/*", bug/*, enhancement/* ] + branches: [ "development", "main", "feature/*", bug/*, enhancement/* ] pull_request: branches: [ "development", "main" ] diff --git a/WatcherAgent/src/hardware/cpu.rs b/WatcherAgent/src/hardware/cpu.rs new file mode 100644 index 0000000..e69de29 diff --git a/WatcherAgent/src/hardware/disk.rs b/WatcherAgent/src/hardware/disk.rs new file mode 100644 index 0000000..e69de29 diff --git a/WatcherAgent/src/hardware/gpu.rs b/WatcherAgent/src/hardware/gpu.rs new file mode 100644 index 0000000..e69de29 diff --git a/WatcherAgent/src/hardware/memory.rs b/WatcherAgent/src/hardware/memory.rs new file mode 100644 index 0000000..e69de29 diff --git a/WatcherAgent/src/hardware/network.rs b/WatcherAgent/src/hardware/network.rs new file mode 100644 index 0000000..e69de29 From abfa0b6fc03daa6a80aba28f7dc01f1026c1e0bb Mon Sep 17 00:00:00 2001 From: donpat1to Date: Fri, 8 Aug 2025 18:59:57 +0200 Subject: [PATCH 33/49] modulized everthing --- .github/workflows/build.yml | 2 +- WatcherAgent/src/api.rs | 11 +- WatcherAgent/src/hardware.rs | 79 ----- WatcherAgent/src/hardware/cpu.rs | 256 ++++++++++++++ WatcherAgent/src/hardware/disk.rs | 113 ++++++ WatcherAgent/src/hardware/gpu.rs | 41 +++ WatcherAgent/src/hardware/memory.rs | 25 ++ WatcherAgent/src/hardware/mod.rs | 35 ++ WatcherAgent/src/hardware/network.rs | 191 ++++++++++ WatcherAgent/src/metrics.rs | 497 ++------------------------- WatcherAgent/src/models.rs | 10 +- 11 files changed, 690 insertions(+), 570 deletions(-) delete mode 100644 WatcherAgent/src/hardware.rs create mode 100644 WatcherAgent/src/hardware/mod.rs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7f9b583..f60912c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -123,7 +123,7 @@ jobs: docker-build: name: Build Linux Docker Image - needs: [native-build, windows-cross] + needs: [native-build, windows-cross, detect-project] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/WatcherAgent/src/api.rs b/WatcherAgent/src/api.rs index 044ce3f..baf7fe6 100644 --- a/WatcherAgent/src/api.rs +++ b/WatcherAgent/src/api.rs @@ -1,5 +1,6 @@ use std::time::Duration; +use crate::hardware::HardwareInfo; use crate::models::{HardwareDto, HeartbeatDto, IdResponse, MetricDto, RegistrationDto}; use anyhow::Result; use reqwest::{Client, StatusCode}; @@ -20,16 +21,16 @@ pub async fn register_with_server(base_url: &str) -> Result<(i32, String), Box anyhow::Result { - let mut sys = System::new_all(); - sys.refresh_cpu_all(); - sys.refresh_memory(); - - let cpus = sys.cpus(); - let cpu_type = if !cpus.is_empty() { - cpus[0].brand().to_string() - } else { - "Unknown CPU".to_string() - }; - let cpu_cores = cpus.len() as i32; - let ram_bytes = sys.total_memory() as f64; - let gpu_type = Self::detect_gpu_name(); - let ip_address = local_ip_address::local_ip()?.to_string(); - - Ok(Self { - cpu_type, - cpu_cores, - gpu_type, - ram_size: ram_bytes, - ip_address, - }) - } - - fn detect_gpu_name() -> String { - // First try NVML (NVIDIA Management Library) - if let Some(name) = Self::try_nvml_gpu_name() { - return name; - } - - // Fallback to OS-specific commands - #[cfg(target_os = "linux")] - { - if let Ok(output) = std::process::Command::new("lshw") - .args(&["-C", "display"]) - .output() - { - if let Some(name) = String::from_utf8_lossy(&output.stdout) - .lines() - .find(|l| l.contains("product:")) - .map(|l| l.trim().replace("product:", "").trim().to_string()) - { - return name; - } - } - } - - #[cfg(target_os = "windows")] - { - if let Ok(output) = std::process::Command::new("wmic") - .args(&["path", "win32_VideoController", "get", "name"]) - .output() - { - if let Some(name) = String::from_utf8_lossy(&output.stdout) - .lines() - .nth(1) - .map(|s| s.trim().to_string()) - { - return name; - } - } - } - - // If all else fails - "Unknown GPU".to_string() - } - - fn try_nvml_gpu_name() -> Option { - let nvml = Nvml::init().ok()?; - let device = nvml.device_by_index(0).ok()?; - device.name().ok().map(|s| s.to_string()) - } -} diff --git a/WatcherAgent/src/hardware/cpu.rs b/WatcherAgent/src/hardware/cpu.rs index e69de29..1774bc3 100644 --- a/WatcherAgent/src/hardware/cpu.rs +++ b/WatcherAgent/src/hardware/cpu.rs @@ -0,0 +1,256 @@ +use anyhow::Result; +use std::error::Error; +//use std::result::Result; +use sysinfo::System; + +#[derive(Debug)] +pub struct CpuInfo { + pub name: Option, + pub cores: Option, + pub current_load: Option, + pub current_temp: Option, +} + +pub async fn get_cpu_info() -> Result> { + let mut sys = System::new(); + sys.refresh_cpu_all(); + + let cpus = sys.cpus(); + Ok(CpuInfo { + name: Some( + cpus.first() + .map(|c| c.brand().to_string()) + .unwrap_or_default(), + ), + cores: Some(cpus.len() as i32), + current_load: get_cpu_load(&mut sys).await.ok(), + current_temp: get_cpu_temp().await.ok(), + }) +} + +pub async fn get_cpu_load(sys: &mut System) -> Result> { + sys.refresh_cpu_all(); + tokio::task::yield_now().await; // Allow other tasks to run + Ok(sys.global_cpu_usage() as f64) +} + +pub async fn get_cpu_temp() -> Result> { + println!("Attempting to get CPU temperature..."); + + #[cfg(target_os = "linux")] + { + use std::fs; + use std::process::Command; + println!(""); + if let Ok(output) = Command::new("sensors").output() { + let stdout = String::from_utf8_lossy(&output.stdout); + for line in stdout.lines() { + if line.contains("Package id") || line.contains("Tdie") || line.contains("CPU Temp") + { + if let Some(temp_str) = line + .split('+') + .nth(1) + .and_then(|s| s.split_whitespace().next()) + { + if let Ok(temp) = temp_str.replace("°C", "").parse::() { + return Some(temp); + } + } + } + } + } + + // 2. Sysfs (Intel/AMD) + if let Ok(content) = fs::read_to_string("/sys/class/thermal/thermal_zone0/temp") { + if let Ok(temp) = content.trim().parse::() { + return Some(temp / 1000.0); + } + } + + // 3. Alternative Sysfs-Pfade + let paths = [ + "/sys/class/hwmon/hwmontemp1_input", + "/sys/class/hwmon/hwmondevice/temp1_input", + ]; + + for path_pattern in &paths { + if let Ok(paths) = glob::glob(path_pattern) { + for path in paths.flatten() { + if let Ok(content) = fs::read_to_string(&path) { + if let Ok(temp) = content.trim().parse::() { + return Some(temp / 1000.0); + } + } + } + } + } + + Err(anyhow::anyhow!( + "Could not find CPU temperature using sensors or sysfs" + )) + } + + #[cfg(target_os = "windows")] + fn failed(hr: winapi::shared::winerror::HRESULT) -> bool { + hr < 0 + } + + #[cfg(target_os = "windows")] + { + use com::runtime::init_runtime; + use com::sys::CLSCTX_INPROC_SERVER; + use widestring::U16CString; + use winapi::shared::rpcdce::*; + use winapi::shared::wtypes::VT_I4; + use winapi::um::oaidl::VARIANT; + use winapi::um::objidlbase::EOAC_NONE; + use winapi::um::{combaseapi, wbemcli}; + + init_runtime().ok(); + + unsafe { + use anyhow::Ok; + + let mut locator: *mut wbemcli::IWbemLocator = std::ptr::null_mut(); + let hr = combaseapi::CoCreateInstance( + &wbemcli::CLSID_WbemLocator, + std::ptr::null_mut(), + CLSCTX_INPROC_SERVER, + &wbemcli::IID_IWbemLocator, + &mut locator as *mut _ as *mut _, + ); + + if hr != 0 { + eprintln!("Failed to create WbemLocator (HRESULT: {})", hr); + return Err(("Failed to create WbemLocator").into()); + } + + let mut services: *mut wbemcli::IWbemServices = std::ptr::null_mut(); + let namespace = U16CString::from_str("root\\cimv2").unwrap(); // Changed to more common namespace + let hr = (*locator).ConnectServer( + namespace.as_ptr().cast_mut(), + std::ptr::null_mut(), + std::ptr::null_mut(), + std::ptr::null_mut(), + 0, + std::ptr::null_mut(), + std::ptr::null_mut(), + &mut services, + ); + + if hr != 0 { + eprintln!("Failed to connect to WMI (HRESULT: {})", hr); + (*locator).Release(); + return Err(("Failed to connect to WMI").into()); + } + + // Set security levels + let hr = combaseapi::CoSetProxyBlanket( + services as *mut _, + RPC_C_AUTHN_WINNT, + RPC_C_AUTHZ_NONE, + std::ptr::null_mut(), + RPC_C_AUTHN_LEVEL_CALL, + RPC_C_IMP_LEVEL_IMPERSONATE, + std::ptr::null_mut(), + EOAC_NONE, + ); + + if hr != 0 { + eprintln!("Failed to set proxy blanket (HRESULT: {})", hr); + (*services).Release(); + (*locator).Release(); + return Err(("Failed to set proxy blanket").into()); + } + + // Try different temperature queries - some systems might have different WMI classes + let queries = [ + "SELECT * FROM Win32_PerfFormattedData_Counters_ThermalZoneInformation", + "SELECT * FROM MSAcpi_ThermalZoneTemperature", + "SELECT * FROM Win32_TemperatureProbe", + ]; + + let mut result = None; + + for query_str in queries.iter() { + let query = U16CString::from_str(query_str).unwrap(); + let mut enumerator: *mut wbemcli::IEnumWbemClassObject = std::ptr::null_mut(); + let hr = (*services).ExecQuery( + U16CString::from_str("WQL").unwrap().as_ptr().cast_mut(), + query.as_ptr().cast_mut(), + wbemcli::WBEM_FLAG_FORWARD_ONLY as i32, + std::ptr::null_mut(), + &mut enumerator, + ); + + if hr != 0 { + continue; // Try next query if this one fails + } + + let mut obj: *mut wbemcli::IWbemClassObject = std::ptr::null_mut(); + let mut returned = 0; + let hr = (*enumerator).Next( + wbemcli::WBEM_INFINITE as i32, // Fixed: cast directly to i32 + 1, + &mut obj, + &mut returned, + ); + + if failed(hr) { + eprintln!("Failed to enumerate WMI objects (HRESULT: {})", hr); + (*enumerator).Release(); + continue; + } + + if returned == 0 { + // No more items + (*enumerator).Release(); + continue; + } + + if hr == 0 && returned > 0 { + let mut variant = std::mem::zeroed::(); + // Try different possible property names + let property_names = ["CurrentTemperature", "Temperature", "CurrentReading"]; + + for prop in property_names.iter() { + let hr = (*obj).Get( + U16CString::from_str(prop).unwrap().as_ptr(), + 0, + &mut variant, + std::ptr::null_mut(), + std::ptr::null_mut(), + ); + + if hr == 0 && variant.n1.n2().vt as u32 == VT_I4 { + let temp_kelvin = *variant.n1.n2().n3.intVal() as f32 / 10.0; + result = Some(temp_kelvin - 273.15); // Convert to Celsius + break; + } + } + + (*obj).Release(); + (*enumerator).Release(); + if result.is_some() { + break; + } + } + + if !enumerator.is_null() { + (*enumerator).Release(); + } + } + + (*services).Release(); + (*locator).Release(); + + return Ok(result.unwrap() as f64).map_err(|e| e.into()); + } + } + + #[cfg(not(any(target_os = "linux", target_os = "windows")))] + { + println!("CPU temperature retrieval not supported on this OS."); + Err(anyhow::anyhow!("CPU temperature retrieval not supported on this OS").into()) + } +} diff --git a/WatcherAgent/src/hardware/disk.rs b/WatcherAgent/src/hardware/disk.rs index e69de29..bbf7aec 100644 --- a/WatcherAgent/src/hardware/disk.rs +++ b/WatcherAgent/src/hardware/disk.rs @@ -0,0 +1,113 @@ +use anyhow::Result; +use sysinfo::{Components, Disks, System}; + +#[derive(Debug)] +pub struct DiskInfo { + pub total: Option, + pub used: Option, + pub free: Option, +} + +pub async fn get_disk_info() -> Result { + let disks = Disks::new_with_refreshed_list(); + let mut total = 0; + let mut used = 0; + + for disk in disks.list() { + if disk.total_space() > 100 * 1024 * 1024 { + // > 100MB + total += disk.total_space(); + used += disk.total_space() - disk.available_space(); + } + } + + Ok(DiskInfo { + total: Some(total as f64), + used: Some(used as f64), + free: Some((total - used) as f64), + }) +} + +pub fn get_disk_usage() -> (f64, f64, f64) { + let mut sys = System::new(); + sys.refresh_all(); + //sys.refresh_disks_list(); + + let mut total_size = 0u64; + let mut total_used = 0u64; + let mut count = 0; + + let disks = Disks::new_with_refreshed_list(); + for disk in disks.list() { + // Ignoriere CD-ROMs und kleine Systempartitionen + println!( + "Disk_Name: {:?}, Disk_Kind: {}, Total: {}, Available: {}", + disk.name(), + disk.kind(), + disk.total_space(), + disk.available_space() + ); + if disk.total_space() > 100 * 1024 * 1024 { + // > 100MB + total_size += disk.total_space(); + total_used += disk.total_space() - disk.available_space(); + count += 1; + } + } + let components = Components::new_with_refreshed_list(); + for component in &components { + if let Some(temperature) = component.temperature() { + println!( + "Component_Label: {}, Temperature: {}°C", + component.label(), + temperature + ); + } + } + + // Berechnungen + let size_b = if count > 0 { + total_size as f64 // in Bytes + } else { + // Fallback: Versuche df unter Linux + println!("Fallback: Using 'df' command to get disk info."); + #[cfg(target_os = "linux")] + { + use std::process::Command; + if let Ok(output) = Command::new("df") + .arg("-B1") + .arg("--output=size,used") + .output() + { + let stdout = String::from_utf8_lossy(&output.stdout); + for line in stdout.lines().skip(1) { + let parts: Vec<&str> = line.split_whitespace().collect(); + if parts.len() == 2 { + if let (Ok(size), Ok(used)) = + (parts[0].parse::(), parts[1].parse::()) + { + total_size += size; + total_used += used; + count += 1; + } + } + } + total_size as f64 // in Bytes + } else { + 0.0 + } + } + #[cfg(not(target_os = "linux"))] + { + 0.0 + } + }; + + let usage = if total_size > 0 { + (total_used as f64 / total_size as f64) * 100.0 + } else { + 0.0 + }; + + (size_b, usage, 0.0) // Disk-Temp bleibt 0.0 ohne spezielle Hardware +} diff --git a/WatcherAgent/src/hardware/gpu.rs b/WatcherAgent/src/hardware/gpu.rs index e69de29..e49c3c0 100644 --- a/WatcherAgent/src/hardware/gpu.rs +++ b/WatcherAgent/src/hardware/gpu.rs @@ -0,0 +1,41 @@ +use anyhow::Result; +use nvml_wrapper::Nvml; +use std::error::Error; + +#[derive(Debug)] +pub struct GpuInfo { + pub name: Option, + pub current_load: Option, + pub current_temp: Option, + pub vram_total: Option, + pub vram_used: Option, +} + +pub async fn get_gpu_info() -> Result> { + let nvml = Nvml::init()?; + let device = nvml.device_by_index(0)?; + + let (used, total) = get_gpu_vram_usage(&device)?; + Ok(GpuInfo { + name: device.name().ok(), + current_load: get_gpu_load(&device).ok(), + current_temp: get_gpu_temp(&device).ok(), + vram_total: Some(total as f64), + vram_used: Some(used as f64), + }) +} + +pub fn get_gpu_load(device: &nvml_wrapper::Device) -> Result> { + Ok(device.utilization_rates().unwrap().gpu as f64) +} + +pub fn get_gpu_temp(device: &nvml_wrapper::Device) -> Result> { + Ok(device + .temperature(nvml_wrapper::enum_wrappers::device::TemperatureSensor::Gpu) + .unwrap() as f64) +} + +pub fn get_gpu_vram_usage(device: &nvml_wrapper::Device) -> Result<(f64, f64), Box> { + let mem_info = device.memory_info().unwrap(); + Ok((mem_info.used as f64, mem_info.total as f64)) +} diff --git a/WatcherAgent/src/hardware/memory.rs b/WatcherAgent/src/hardware/memory.rs index e69de29..65be545 100644 --- a/WatcherAgent/src/hardware/memory.rs +++ b/WatcherAgent/src/hardware/memory.rs @@ -0,0 +1,25 @@ +use anyhow::Result; +use sysinfo::System; + +#[derive(Debug)] +pub struct MemoryInfo { + pub total: Option, + pub used: Option, + pub free: Option, +} + +pub async fn get_memory_info() -> Result { + let mut sys = System::new(); + sys.refresh_memory(); + + Ok(MemoryInfo { + total: Some(sys.total_memory() as f64), + used: Some(sys.used_memory() as f64), + free: Some(sys.free_memory() as f64), + }) +} + +pub fn get_memory_usage(sys: &mut System) -> f64 { + sys.refresh_memory(); + (sys.used_memory() as f64 / sys.total_memory() as f64) * 100.0 +} diff --git a/WatcherAgent/src/hardware/mod.rs b/WatcherAgent/src/hardware/mod.rs new file mode 100644 index 0000000..72fe525 --- /dev/null +++ b/WatcherAgent/src/hardware/mod.rs @@ -0,0 +1,35 @@ +//use anyhow::Result; +use std::error::Error; + +mod cpu; +mod disk; +mod gpu; +mod memory; +mod network; + +pub use cpu::get_cpu_info; +pub use disk::get_disk_info; +pub use gpu::get_gpu_info; +pub use memory::get_memory_info; +pub use network::get_network_info; + +#[derive(Debug)] +pub struct HardwareInfo { + pub cpu: cpu::CpuInfo, + pub gpu: gpu::GpuInfo, + pub memory: memory::MemoryInfo, + pub disk: disk::DiskInfo, + pub network: network::NetworkInfo, +} + +impl HardwareInfo { + pub async fn collect() -> anyhow::Result> { + Ok(Self { + cpu: get_cpu_info().await?, + gpu: get_gpu_info().await?, + memory: get_memory_info().await?, + disk: get_disk_info().await?, + network: get_network_info().await?, + }) + } +} diff --git a/WatcherAgent/src/hardware/network.rs b/WatcherAgent/src/hardware/network.rs index e69de29..2fb3066 100644 --- a/WatcherAgent/src/hardware/network.rs +++ b/WatcherAgent/src/hardware/network.rs @@ -0,0 +1,191 @@ +use std::error::Error; +use std::result::Result; +use std::time::Instant; + +#[derive(Debug)] +pub struct NetworkInfo { + pub interfaces: Option>, + pub rx_bytes: Option, + pub tx_bytes: Option, +} + +pub struct NetworkMonitor { + prev_rx: u64, + prev_tx: u64, + last_update: Instant, +} + +impl NetworkMonitor { + pub fn new() -> Self { + Self { + prev_rx: 0, + prev_tx: 0, + last_update: Instant::now(), + } + } + + pub fn get_usage(&mut self) -> Result<(f64, f64), Box> { + let (current_rx, current_tx) = get_network_bytes()?; + let elapsed = self.last_update.elapsed().as_secs_f64(); + self.last_update = Instant::now(); + + let rx_rate = if current_rx >= self.prev_rx { + (current_rx - self.prev_rx) as f64 / elapsed + } else { + 0.0 + }; + + let tx_rate = if current_tx >= self.prev_tx { + (current_tx - self.prev_tx) as f64 / elapsed + } else { + 0.0 + }; + + self.prev_rx = current_rx; + self.prev_tx = current_tx; + + Ok((rx_rate, tx_rate)) + } +} + +pub async fn get_network_info() -> Result> { + let (rx, tx) = get_network_bytes()?; + Ok(NetworkInfo { + interfaces: Some(get_network_interfaces()), + rx_bytes: Some(rx as f64), + tx_bytes: Some(tx as f64), + }) +} + +fn get_network_bytes() -> Result<(u64, u64), Box> { + #[cfg(target_os = "windows")] + { + use std::ptr::null_mut; + use winapi::shared::ifmib::MIB_IFTABLE; + use winapi::um::iphlpapi::GetIfTable; + + unsafe { + // Erste Abfrage zur Bestimmung der benötigten Puffergröße + let mut buffer_size = 0u32; + if GetIfTable(null_mut(), &mut buffer_size, 0) + != winapi::shared::winerror::ERROR_INSUFFICIENT_BUFFER + { + return Err( + anyhow::anyhow!("Failed to get buffer size for network interfaces").into(), + ); + } + + // Puffer allozieren + let mut buffer = vec![0u8; buffer_size as usize]; + let if_table = buffer.as_mut_ptr() as *mut MIB_IFTABLE; + + // Tatsächliche Daten abrufen + if GetIfTable(if_table, &mut buffer_size, 0) != 0 { + return Err(anyhow::anyhow!("Failed to get network interface table").into()); + } + + // Daten auswerten + let mut rx_total = 0u64; + let mut tx_total = 0u64; + + for i in 0..(*if_table).dwNumEntries { + let row = &*((*if_table).table.as_ptr().offset(i as isize)); + rx_total += row.dwInOctets as u64; + tx_total += row.dwOutOctets as u64; + } + + if rx_total == 0 && tx_total == 0 { + return Err(anyhow::anyhow!("No network data available").into()); + } else { + Ok((rx_total, tx_total)) + } + } + } + + #[cfg(target_os = "linux")] + { + use std::fs; + + let mut rx_total = 0u64; + let mut tx_total = 0u64; + if let Ok(dir) = fs::read_dir("/sys/class/net") { + for entry in dir.flatten() { + let iface = entry.file_name(); + let iface_name = iface.to_string_lossy(); + + // Ignoriere virtuelle Interfaces + if !iface_name.starts_with("lo") && !iface_name.starts_with("virbr") { + if let (Ok(rx), Ok(tx)) = ( + fs::read_to_string(entry.path().join("statistics/rx_bytes")), + fs::read_to_string(entry.path().join("statistics/tx_bytes")), + ) { + rx_total += rx.trim().parse::().unwrap_or(0); + tx_total += tx.trim().parse::().unwrap_or(0); + } + } + } + } + + if rx_total == 0 && tx_total == 0 { + return Err(anyhow::anyhow!("No network data available").into()); + } else { + return Ok((rx_total, tx_total)); + } + } + + #[cfg(not(any(target_os = "windows", target_os = "linux")))] + Err("No network data available for this OS".into()) +} + +fn get_network_interfaces() -> Vec { + #[cfg(target_os = "windows")] + { + use std::ffi::CStr; + use std::ptr::null_mut; + use winapi::shared::ifmib::MIB_IFTABLE; + use winapi::um::iphlpapi::GetIfTable; + + unsafe { + let mut buffer_size = 0u32; + if GetIfTable(null_mut(), &mut buffer_size, 0) + != winapi::shared::winerror::ERROR_INSUFFICIENT_BUFFER + { + return vec![]; + } + + let mut buffer = vec![0u8; buffer_size as usize]; + let if_table = buffer.as_mut_ptr() as *mut MIB_IFTABLE; + + if GetIfTable(if_table, &mut buffer_size, 0) != 0 { + return vec![]; + } + + (0..(*if_table).dwNumEntries) + .map(|i| { + let row = &*((*if_table).table.as_ptr().offset(i as isize)); + let descr = CStr::from_ptr(row.bDescr.as_ptr() as *const i8) + .to_string_lossy() + .into_owned(); + descr.trim().to_string() + }) + .collect() + } + } + + #[cfg(target_os = "linux")] + { + use std::fs; + + let mut interfaces = vec![]; + if let Ok(dir) = fs::read_dir("/sys/class/net") { + for entry in dir.flatten() { + let iface = entry.file_name(); + interfaces.push(iface.to_string_lossy().to_string()); + } + } + interfaces + } + + #[cfg(not(any(target_os = "windows", target_os = "linux")))] + vec![] +} diff --git a/WatcherAgent/src/metrics.rs b/WatcherAgent/src/metrics.rs index e379f12..b5ccbb1 100644 --- a/WatcherAgent/src/metrics.rs +++ b/WatcherAgent/src/metrics.rs @@ -1,507 +1,50 @@ use std::error::Error; use std::time::Duration; -use sysinfo::{Components, Disks, System}; -use tokio::time::Instant; - use crate::api; -use crate::models::{MetricDto, NetworkState}; -use nvml_wrapper::Nvml; +use crate::hardware::HardwareInfo; +use crate::models::MetricDto; pub struct Collector { - sys: System, - nvml: Option, server_id: i32, ip_address: String, - network_state: NetworkState, -} - -impl NetworkState { - fn new() -> Self { - Self { - prev_rx: 0, - prev_tx: 0, - last_update: Instant::now(), - } - } } impl Collector { pub fn new(server_id: i32, ip_address: String) -> Self { Self { - sys: System::new(), - nvml: Nvml::init().ok(), server_id, ip_address, - network_state: NetworkState::new(), } } pub async fn run(&mut self, base_url: &str) -> anyhow::Result<(), Box> { loop { - let metrics = self.collect(); + let metrics = self.collect().await?; api::send_metrics(base_url, &metrics).await?; tokio::time::sleep(Duration::from_secs(20)).await; } } - pub fn collect(&mut self) -> MetricDto { - self.sys.refresh_all(); + pub async fn collect(&mut self) -> Result> { + let hardware = HardwareInfo::collect().await?; - // CPU - updated for sysinfo 0.35 - let cpu_load = self.sys.global_cpu_usage() as f64; - let cpu_temp = get_cpu_temp().unwrap_or(0.0) as f64; - - // RAM - updated for sysinfo 0.35 - let total_memory = self.sys.total_memory() as f64; - let used_memory = self.sys.used_memory() as f64; - let ram_load = (used_memory / total_memory) * 100.0; - let ram_size = total_memory; - - // Disk - updated for sysinfo 0.35 - let (disk_size, disk_usage, disk_temp) = get_disk_info(); - // GPU (NVIDIA) - let (gpu_temp, gpu_load, vram_used, vram_total) = if let Some(nvml) = &self.nvml { - if let Ok(device) = nvml.device_by_index(0) { - let temp = device - .temperature(nvml_wrapper::enum_wrappers::device::TemperatureSensor::Gpu) - .unwrap_or(0) as f64; - let load = device - .utilization_rates() - .map(|u| u.gpu as f64) - .unwrap_or(0.0); - let mem = device.memory_info().ok(); - let used = mem.clone().map(|m| (m.used as f64)).unwrap_or(0.0); // B - let total = mem.map(|m| (m.total as f64)).unwrap_or(0.0); // B - (temp, load, used, total) - } else { - (0.0, 0.0, 0.0, 0.0) - } - } else { - (0.0, 0.0, 0.0, 0.0) - }; - - // Network metrics - let (current_rx, current_tx) = get_network_traffic().unwrap_or((0, 0)); - let elapsed_secs = self.network_state.last_update.elapsed().as_secs_f64(); - self.network_state.last_update = Instant::now(); - - // Calculate the difference since the last call - let net_in = if current_rx >= self.network_state.prev_rx { - ((current_rx - self.network_state.prev_rx) as f64 * 8.0) / elapsed_secs - } else { - 0.0 - }; - - let net_out = if current_tx >= self.network_state.prev_tx { - ((current_tx - self.network_state.prev_tx) as f64 * 8.0) / elapsed_secs - } else { - 0.0 - }; - - // Store the current values for the next call - self.network_state.prev_rx = current_rx; - self.network_state.prev_tx = current_tx; - - MetricDto { + Ok(MetricDto { server_id: self.server_id, ip_address: self.ip_address.clone(), - cpu_load, - cpu_temp, - gpu_load, - gpu_temp, - gpu_vram_size: vram_total, - gpu_vram_usage: if vram_total > 0.0 { - (vram_used / vram_total) * 100.0 - } else { - 0.0 - }, - ram_load, - ram_size, - disk_size, - disk_usage: disk_usage, - disk_temp: disk_temp, // not supported - net_in, - net_out, - } + cpu_load: hardware.cpu.current_load.unwrap_or_default(), + cpu_temp: hardware.cpu.current_temp.unwrap_or_default(), + gpu_load: hardware.gpu.current_load.unwrap_or_default(), + gpu_temp: hardware.gpu.current_temp.unwrap_or_default(), + gpu_vram_size: hardware.gpu.vram_total.unwrap_or_default(), + gpu_vram_usage: hardware.gpu.vram_used.unwrap_or_default(), + ram_load: hardware.memory.used.unwrap_or_default(), + ram_size: hardware.memory.total.unwrap_or_default(), + disk_size: hardware.disk.total.unwrap_or_default(), + disk_usage: hardware.disk.used.unwrap_or_default(), + disk_temp: 0.0, // not supported + net_rx: hardware.network.rx_bytes.unwrap_or_default(), + net_tx: hardware.network.tx_bytes.unwrap_or_default(), + }) } } - -pub fn get_cpu_temp() -> Option { - println!("Attempting to get CPU temperature..."); - - #[cfg(target_os = "linux")] - { - use std::fs; - use std::process::Command; - println!(""); - if let Ok(output) = Command::new("sensors").output() { - let stdout = String::from_utf8_lossy(&output.stdout); - for line in stdout.lines() { - if line.contains("Package id") || line.contains("Tdie") || line.contains("CPU Temp") - { - if let Some(temp_str) = line - .split('+') - .nth(1) - .and_then(|s| s.split_whitespace().next()) - { - if let Ok(temp) = temp_str.replace("°C", "").parse::() { - return Some(temp); - } - } - } - } - } - - // 2. Sysfs (Intel/AMD) - if let Ok(content) = fs::read_to_string("/sys/class/thermal/thermal_zone0/temp") { - if let Ok(temp) = content.trim().parse::() { - return Some(temp / 1000.0); - } - } - - // 3. Alternative Sysfs-Pfade - let paths = [ - "/sys/class/hwmon/hwmontemp1_input", - "/sys/class/hwmon/hwmondevice/temp1_input", - ]; - - for path_pattern in &paths { - if let Ok(paths) = glob::glob(path_pattern) { - for path in paths.flatten() { - if let Ok(content) = fs::read_to_string(&path) { - if let Ok(temp) = content.trim().parse::() { - return Some(temp / 1000.0); - } - } - } - } - } - - None - } - - #[cfg(target_os = "windows")] - fn failed(hr: winapi::shared::winerror::HRESULT) -> bool { - hr < 0 - } - - #[cfg(target_os = "windows")] - { - use com::runtime::init_runtime; - use com::sys::CLSCTX_INPROC_SERVER; - use widestring::U16CString; - use winapi::shared::rpcdce::*; - use winapi::shared::wtypes::VT_I4; - use winapi::um::oaidl::VARIANT; - use winapi::um::objidlbase::EOAC_NONE; - use winapi::um::{combaseapi, wbemcli}; - - init_runtime().ok()?; - - unsafe { - let mut locator: *mut wbemcli::IWbemLocator = std::ptr::null_mut(); - let hr = combaseapi::CoCreateInstance( - &wbemcli::CLSID_WbemLocator, - std::ptr::null_mut(), - CLSCTX_INPROC_SERVER, - &wbemcli::IID_IWbemLocator, - &mut locator as *mut _ as *mut _, - ); - - if hr != 0 { - eprintln!("Failed to create WbemLocator (HRESULT: {})", hr); - return None; - } - - let mut services: *mut wbemcli::IWbemServices = std::ptr::null_mut(); - let namespace = U16CString::from_str("root\\cimv2").unwrap(); // Changed to more common namespace - let hr = (*locator).ConnectServer( - namespace.as_ptr().cast_mut(), - std::ptr::null_mut(), - std::ptr::null_mut(), - std::ptr::null_mut(), - 0, - std::ptr::null_mut(), - std::ptr::null_mut(), - &mut services, - ); - - if hr != 0 { - eprintln!("Failed to connect to WMI (HRESULT: {})", hr); - (*locator).Release(); - return None; - } - - // Set security levels - let hr = combaseapi::CoSetProxyBlanket( - services as *mut _, - RPC_C_AUTHN_WINNT, - RPC_C_AUTHZ_NONE, - std::ptr::null_mut(), - RPC_C_AUTHN_LEVEL_CALL, - RPC_C_IMP_LEVEL_IMPERSONATE, - std::ptr::null_mut(), - EOAC_NONE, - ); - - if hr != 0 { - eprintln!("Failed to set proxy blanket (HRESULT: {})", hr); - (*services).Release(); - (*locator).Release(); - return None; - } - - // Try different temperature queries - some systems might have different WMI classes - let queries = [ - "SELECT * FROM Win32_PerfFormattedData_Counters_ThermalZoneInformation", - "SELECT * FROM MSAcpi_ThermalZoneTemperature", - "SELECT * FROM Win32_TemperatureProbe", - ]; - - let mut result = None; - - for query_str in queries.iter() { - let query = U16CString::from_str(query_str).unwrap(); - let mut enumerator: *mut wbemcli::IEnumWbemClassObject = std::ptr::null_mut(); - let hr = (*services).ExecQuery( - U16CString::from_str("WQL").unwrap().as_ptr().cast_mut(), - query.as_ptr().cast_mut(), - wbemcli::WBEM_FLAG_FORWARD_ONLY as i32, - std::ptr::null_mut(), - &mut enumerator, - ); - - if hr != 0 { - continue; // Try next query if this one fails - } - - let mut obj: *mut wbemcli::IWbemClassObject = std::ptr::null_mut(); - let mut returned = 0; - let hr = (*enumerator).Next( - wbemcli::WBEM_INFINITE as i32, // Fixed: cast directly to i32 - 1, - &mut obj, - &mut returned, - ); - - if failed(hr) { - eprintln!("Failed to enumerate WMI objects (HRESULT: {})", hr); - (*enumerator).Release(); - continue; - } - - if returned == 0 { - // No more items - (*enumerator).Release(); - continue; - } - - if hr == 0 && returned > 0 { - let mut variant = std::mem::zeroed::(); - // Try different possible property names - let property_names = ["CurrentTemperature", "Temperature", "CurrentReading"]; - - for prop in property_names.iter() { - let hr = (*obj).Get( - U16CString::from_str(prop).unwrap().as_ptr(), - 0, - &mut variant, - std::ptr::null_mut(), - std::ptr::null_mut(), - ); - - if hr == 0 && variant.n1.n2().vt as u32 == VT_I4 { - let temp_kelvin = *variant.n1.n2().n3.intVal() as f32 / 10.0; - result = Some(temp_kelvin - 273.15); // Convert to Celsius - break; - } - } - - (*obj).Release(); - (*enumerator).Release(); - if result.is_some() { - break; - } - } - - if !enumerator.is_null() { - (*enumerator).Release(); - } - } - - (*services).Release(); - (*locator).Release(); - - result - } - } - - #[cfg(not(any(target_os = "linux", target_os = "windows")))] - { - println!("CPU temperature retrieval not supported on this OS."); - None - } -} - -fn get_network_traffic() -> Option<(u64, u64)> { - #[cfg(target_os = "windows")] - { - use std::ptr::null_mut; - use winapi::shared::ifmib::MIB_IFTABLE; - use winapi::um::iphlpapi::GetIfTable; - - unsafe { - // Erste Abfrage zur Bestimmung der benötigten Puffergröße - let mut buffer_size = 0u32; - if GetIfTable(null_mut(), &mut buffer_size, 0) - != winapi::shared::winerror::ERROR_INSUFFICIENT_BUFFER - { - return None; - } - - // Puffer allozieren - let mut buffer = vec![0u8; buffer_size as usize]; - let if_table = buffer.as_mut_ptr() as *mut MIB_IFTABLE; - - // Tatsächliche Daten abrufen - if GetIfTable(if_table, &mut buffer_size, 0) != 0 { - return None; - } - - // Daten auswerten - let mut rx_total = 0u64; - let mut tx_total = 0u64; - - for i in 0..(*if_table).dwNumEntries { - let row = &*((*if_table).table.as_ptr().offset(i as isize)); - rx_total += row.dwInOctets as u64; - tx_total += row.dwOutOctets as u64; - } - - if rx_total == 0 && tx_total == 0 { - return None; - } else { - return Some((rx_total, tx_total)); - } - } - } - - #[cfg(target_os = "linux")] - { - use std::fs; - - let mut rx_total = 0u64; - let mut tx_total = 0u64; - if let Ok(dir) = fs::read_dir("/sys/class/net") { - for entry in dir.flatten() { - let iface = entry.file_name(); - let iface_name = iface.to_string_lossy(); - - // Ignoriere virtuelle Interfaces - if !iface_name.starts_with("lo") && !iface_name.starts_with("virbr") { - if let (Ok(rx), Ok(tx)) = ( - fs::read_to_string(entry.path().join("statistics/rx_bytes")), - fs::read_to_string(entry.path().join("statistics/tx_bytes")), - ) { - rx_total += rx.trim().parse::().unwrap_or(0); - tx_total += tx.trim().parse::().unwrap_or(0); - } - } - } - } - - if rx_total == 0 && tx_total == 0 { - return None; - } else { - return Some((rx_total, tx_total)); - } - } - - #[cfg(not(any(target_os = "windows", target_os = "linux")))] - None -} - -pub fn get_disk_info() -> (f64, f64, f64) { - let mut sys = System::new(); - sys.refresh_all(); - //sys.refresh_disks_list(); - - let mut total_size = 0u64; - let mut total_used = 0u64; - let mut count = 0; - - let disks = Disks::new_with_refreshed_list(); - for disk in disks.list() { - // Ignoriere CD-ROMs und kleine Systempartitionen - println!( - "Disk_Name: {:?}, Disk_Kind: {}, Total: {}, Available: {}", - disk.name(), - disk.kind(), - disk.total_space(), - disk.available_space() - ); - if disk.total_space() > 100 * 1024 * 1024 { - // > 100MB - total_size += disk.total_space(); - total_used += disk.total_space() - disk.available_space(); - count += 1; - } - } - let components = Components::new_with_refreshed_list(); - for component in &components { - if let Some(temperature) = component.temperature() { - println!( - "Component_Label: {}, Temperature: {}°C", - component.label(), - temperature - ); - } - } - - // Berechnungen - let size_b = if count > 0 { - total_size as f64 // in Bytes - } else { - // Fallback: Versuche df unter Linux - println!("Fallback: Using 'df' command to get disk info."); - #[cfg(target_os = "linux")] - { - use std::process::Command; - if let Ok(output) = Command::new("df") - .arg("-B1") - .arg("--output=size,used") - .output() - { - let stdout = String::from_utf8_lossy(&output.stdout); - for line in stdout.lines().skip(1) { - let parts: Vec<&str> = line.split_whitespace().collect(); - if parts.len() == 2 { - if let (Ok(size), Ok(used)) = - (parts[0].parse::(), parts[1].parse::()) - { - total_size += size; - total_used += used; - count += 1; - } - } - } - total_size as f64 // in Bytes - } else { - 0.0 - } - } - #[cfg(not(target_os = "linux"))] - { - 0.0 - } - }; - - let usage = if total_size > 0 { - (total_used as f64 / total_size as f64) * 100.0 - } else { - 0.0 - }; - - (size_b, usage, 0.0) // Disk-Temp bleibt 0.0 ohne spezielle Hardware -} diff --git a/WatcherAgent/src/models.rs b/WatcherAgent/src/models.rs index 4f47fda..56880ca 100644 --- a/WatcherAgent/src/models.rs +++ b/WatcherAgent/src/models.rs @@ -47,9 +47,9 @@ pub struct MetricDto { #[serde(rename = "disk_Temp")] pub disk_temp: f64, #[serde(rename = "net_In")] - pub net_in: f64, + pub net_rx: f64, #[serde(rename = "net_Out")] - pub net_out: f64, + pub net_tx: f64, } #[derive(Deserialize)] @@ -73,9 +73,3 @@ pub struct HardwareDto { pub ram_size: f64, pub ip_address: String, } - -pub struct NetworkState { - pub prev_rx: u64, - pub prev_tx: u64, - pub last_update: Instant, -} From 93a40fe584b11565c965e03a77b57cc78bcb636d Mon Sep 17 00:00:00 2001 From: donpat1to Date: Fri, 8 Aug 2025 23:28:05 +0200 Subject: [PATCH 34/49] all functions established; needed to remove orphan functions --- WatcherAgent/src/api.rs | 2 +- WatcherAgent/src/hardware/memory.rs | 2 +- WatcherAgent/src/hardware/mod.rs | 7 +++++-- WatcherAgent/src/hardware/network.rs | 15 ++++++++------- WatcherAgent/src/metrics.rs | 9 +++++++-- WatcherAgent/src/models.rs | 1 - 6 files changed, 22 insertions(+), 14 deletions(-) diff --git a/WatcherAgent/src/api.rs b/WatcherAgent/src/api.rs index baf7fe6..1c8df20 100644 --- a/WatcherAgent/src/api.rs +++ b/WatcherAgent/src/api.rs @@ -1,7 +1,7 @@ use std::time::Duration; use crate::hardware::HardwareInfo; -use crate::models::{HardwareDto, HeartbeatDto, IdResponse, MetricDto, RegistrationDto}; +use crate::models::{HeartbeatDto, IdResponse, MetricDto, RegistrationDto}; use anyhow::Result; use reqwest::{Client, StatusCode}; use std::error::Error; diff --git a/WatcherAgent/src/hardware/memory.rs b/WatcherAgent/src/hardware/memory.rs index 65be545..6a5222e 100644 --- a/WatcherAgent/src/hardware/memory.rs +++ b/WatcherAgent/src/hardware/memory.rs @@ -19,7 +19,7 @@ pub async fn get_memory_info() -> Result { }) } -pub fn get_memory_usage(sys: &mut System) -> f64 { +pub fn _get_memory_usage(sys: &mut System) -> f64 { sys.refresh_memory(); (sys.used_memory() as f64 / sys.total_memory() as f64) * 100.0 } diff --git a/WatcherAgent/src/hardware/mod.rs b/WatcherAgent/src/hardware/mod.rs index 72fe525..f629912 100644 --- a/WatcherAgent/src/hardware/mod.rs +++ b/WatcherAgent/src/hardware/mod.rs @@ -5,7 +5,7 @@ mod cpu; mod disk; mod gpu; mod memory; -mod network; +pub(crate) mod network; pub use cpu::get_cpu_info; pub use disk::get_disk_info; @@ -20,16 +20,19 @@ pub struct HardwareInfo { pub memory: memory::MemoryInfo, pub disk: disk::DiskInfo, pub network: network::NetworkInfo, + network_monitor: network::NetworkMonitor, } impl HardwareInfo { pub async fn collect() -> anyhow::Result> { + let mut network_monitor = network::NetworkMonitor::new(); Ok(Self { cpu: get_cpu_info().await?, gpu: get_gpu_info().await?, memory: get_memory_info().await?, disk: get_disk_info().await?, - network: get_network_info().await?, + network: get_network_info(&mut network_monitor).await?, + network_monitor, }) } } diff --git a/WatcherAgent/src/hardware/network.rs b/WatcherAgent/src/hardware/network.rs index 2fb3066..c7c1e6e 100644 --- a/WatcherAgent/src/hardware/network.rs +++ b/WatcherAgent/src/hardware/network.rs @@ -5,10 +5,11 @@ use std::time::Instant; #[derive(Debug)] pub struct NetworkInfo { pub interfaces: Option>, - pub rx_bytes: Option, - pub tx_bytes: Option, + pub rx_rate: Option, + pub tx_rate: Option, } +#[derive(Debug)] pub struct NetworkMonitor { prev_rx: u64, prev_tx: u64, @@ -24,7 +25,7 @@ impl NetworkMonitor { } } - pub fn get_usage(&mut self) -> Result<(f64, f64), Box> { + pub fn update_usage(&mut self) -> Result<(f64, f64), Box> { let (current_rx, current_tx) = get_network_bytes()?; let elapsed = self.last_update.elapsed().as_secs_f64(); self.last_update = Instant::now(); @@ -48,12 +49,12 @@ impl NetworkMonitor { } } -pub async fn get_network_info() -> Result> { - let (rx, tx) = get_network_bytes()?; +pub async fn get_network_info(monitor: &mut NetworkMonitor) -> Result> { + let (rx_rate, tx_rate) = monitor.update_usage()?; Ok(NetworkInfo { interfaces: Some(get_network_interfaces()), - rx_bytes: Some(rx as f64), - tx_bytes: Some(tx as f64), + rx_rate: Some(rx_rate as f64), + tx_rate: Some(tx_rate as f64), }) } diff --git a/WatcherAgent/src/metrics.rs b/WatcherAgent/src/metrics.rs index b5ccbb1..a19502c 100644 --- a/WatcherAgent/src/metrics.rs +++ b/WatcherAgent/src/metrics.rs @@ -2,10 +2,12 @@ use std::error::Error; use std::time::Duration; use crate::api; +use crate::hardware::network::NetworkMonitor; use crate::hardware::HardwareInfo; use crate::models::MetricDto; pub struct Collector { + network_monitor: NetworkMonitor, server_id: i32, ip_address: String, } @@ -13,6 +15,7 @@ pub struct Collector { impl Collector { pub fn new(server_id: i32, ip_address: String) -> Self { Self { + network_monitor: NetworkMonitor::new(), server_id, ip_address, } @@ -28,6 +31,8 @@ impl Collector { pub async fn collect(&mut self) -> Result> { let hardware = HardwareInfo::collect().await?; + // Collect network usage + let (_, _) = self.network_monitor.update_usage().unwrap_or((0.0, 0.0)); Ok(MetricDto { server_id: self.server_id, @@ -43,8 +48,8 @@ impl Collector { disk_size: hardware.disk.total.unwrap_or_default(), disk_usage: hardware.disk.used.unwrap_or_default(), disk_temp: 0.0, // not supported - net_rx: hardware.network.rx_bytes.unwrap_or_default(), - net_tx: hardware.network.tx_bytes.unwrap_or_default(), + net_rx: hardware.network.rx_rate.unwrap_or_default(), + net_tx: hardware.network.tx_rate.unwrap_or_default(), }) } } diff --git a/WatcherAgent/src/models.rs b/WatcherAgent/src/models.rs index 56880ca..32ab76c 100644 --- a/WatcherAgent/src/models.rs +++ b/WatcherAgent/src/models.rs @@ -1,5 +1,4 @@ use serde::{Deserialize, Serialize}; -use tokio::time::Instant; // Data structures matching the C# DTOs #[derive(Serialize, Debug)] From 2e4d17f964cd5e61a3f866b59ca25ba622f5ce1d Mon Sep 17 00:00:00 2001 From: donpat1to Date: Sat, 9 Aug 2025 12:43:21 +0200 Subject: [PATCH 35/49] fixed returns --- WatcherAgent/src/hardware/cpu.rs | 10 +++--- WatcherAgent/src/hardware/disk.rs | 60 ++++++++++++++++++++++++++----- 2 files changed, 56 insertions(+), 14 deletions(-) diff --git a/WatcherAgent/src/hardware/cpu.rs b/WatcherAgent/src/hardware/cpu.rs index 1774bc3..46c445f 100644 --- a/WatcherAgent/src/hardware/cpu.rs +++ b/WatcherAgent/src/hardware/cpu.rs @@ -53,7 +53,7 @@ pub async fn get_cpu_temp() -> Result> { .and_then(|s| s.split_whitespace().next()) { if let Ok(temp) = temp_str.replace("°C", "").parse::() { - return Some(temp); + return Ok(temp); } } } @@ -63,7 +63,7 @@ pub async fn get_cpu_temp() -> Result> { // 2. Sysfs (Intel/AMD) if let Ok(content) = fs::read_to_string("/sys/class/thermal/thermal_zone0/temp") { if let Ok(temp) = content.trim().parse::() { - return Some(temp / 1000.0); + return Ok(temp / 1000.0); } } @@ -78,16 +78,14 @@ pub async fn get_cpu_temp() -> Result> { for path in paths.flatten() { if let Ok(content) = fs::read_to_string(&path) { if let Ok(temp) = content.trim().parse::() { - return Some(temp / 1000.0); + return Ok(temp / 1000.0); } } } } } - Err(anyhow::anyhow!( - "Could not find CPU temperature using sensors or sysfs" - )) + Err(anyhow::anyhow!("Could not find CPU temperature using sensors or sysfs").into()) } #[cfg(target_os = "windows")] diff --git a/WatcherAgent/src/hardware/disk.rs b/WatcherAgent/src/hardware/disk.rs index bbf7aec..0bf60ef 100644 --- a/WatcherAgent/src/hardware/disk.rs +++ b/WatcherAgent/src/hardware/disk.rs @@ -1,5 +1,8 @@ +use std::error::Error; + use anyhow::Result; -use sysinfo::{Components, Disks, System}; +use sysinfo::DiskUsage; +use sysinfo::{Component, Components, Disk, Disks, System}; #[derive(Debug)] pub struct DiskInfo { @@ -10,6 +13,14 @@ pub struct DiskInfo { pub async fn get_disk_info() -> Result { let disks = Disks::new_with_refreshed_list(); + let disk_types = [ + sysinfo::DiskKind::HDD, + sysinfo::DiskKind::SSD, + sysinfo::DiskKind::Unknown(0), + ]; + + let (_, _, _, _) = get_disk_utitlization().unwrap(); + let mut total = 0; let mut used = 0; @@ -28,28 +39,31 @@ pub async fn get_disk_info() -> Result { }) } -pub fn get_disk_usage() -> (f64, f64, f64) { +pub fn get_disk_utitlization() -> Result<(f64, f64, f64, f64), Box> { let mut sys = System::new(); sys.refresh_all(); - //sys.refresh_disks_list(); + let mut count = 0; let mut total_size = 0u64; let mut total_used = 0u64; - let mut count = 0; + let mut total_available = 0u64; let disks = Disks::new_with_refreshed_list(); for disk in disks.list() { // Ignoriere CD-ROMs und kleine Systempartitionen + println!( "Disk_Name: {:?}, Disk_Kind: {}, Total: {}, Available: {}", disk.name(), disk.kind(), disk.total_space(), - disk.available_space() + disk.available_space(), ); + println!("[{:?}] {:?}", disk.name(), disk.mount_point()); if disk.total_space() > 100 * 1024 * 1024 { // > 100MB total_size += disk.total_space(); + total_available += disk.available_space(); total_used += disk.total_space() - disk.available_space(); count += 1; } @@ -66,7 +80,7 @@ pub fn get_disk_usage() -> (f64, f64, f64) { } // Berechnungen - let size_b = if count > 0 { + let total_size = if count > 0 { total_size as f64 // in Bytes } else { // Fallback: Versuche df unter Linux @@ -103,11 +117,41 @@ pub fn get_disk_usage() -> (f64, f64, f64) { } }; - let usage = if total_size > 0 { + let usage = if total_size > 0.0 { (total_used as f64 / total_size as f64) * 100.0 } else { 0.0 }; - (size_b, usage, 0.0) // Disk-Temp bleibt 0.0 ohne spezielle Hardware + Ok(( + total_size, + total_used as f64, + total_available as f64, + usage as f64, + )) // Disk-Temp bleibt 0.0 ohne spezielle Hardware +} + +pub fn get_disk_temp_for_component(component: &Component) -> Option { + if let Some(temp) = component.temperature() { + Some(temp as f64) + } else { + None + } +} + +pub fn get_disk_load_for_disk(disk: &Disk) -> Result<(f64, f64, f64, f64), Box> { + let usage: DiskUsage = disk.usage(); + + // Assuming DiskUsage has these methods: + let total_written_bytes = usage.total_written_bytes as f64; + let written_bytes = usage.written_bytes as f64; + let total_read_bytes = usage.total_read_bytes as f64; + let read_bytes = usage.read_bytes as f64; + + Ok(( + total_written_bytes, + written_bytes, + total_read_bytes, + read_bytes, + )) } From 2b9e1eccb557901714c33cb498ddadc25694ca29 Mon Sep 17 00:00:00 2001 From: donpat1to Date: Sat, 9 Aug 2025 12:47:50 +0200 Subject: [PATCH 36/49] fixed returns --- WatcherAgent/src/hardware/cpu.rs | 6 +++--- WatcherAgent/src/hardware/disk.rs | 2 +- WatcherAgent/src/hardware/memory.rs | 6 ++++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/WatcherAgent/src/hardware/cpu.rs b/WatcherAgent/src/hardware/cpu.rs index 46c445f..f876292 100644 --- a/WatcherAgent/src/hardware/cpu.rs +++ b/WatcherAgent/src/hardware/cpu.rs @@ -53,7 +53,7 @@ pub async fn get_cpu_temp() -> Result> { .and_then(|s| s.split_whitespace().next()) { if let Ok(temp) = temp_str.replace("°C", "").parse::() { - return Ok(temp); + return Ok(temp.into()); } } } @@ -63,7 +63,7 @@ pub async fn get_cpu_temp() -> Result> { // 2. Sysfs (Intel/AMD) if let Ok(content) = fs::read_to_string("/sys/class/thermal/thermal_zone0/temp") { if let Ok(temp) = content.trim().parse::() { - return Ok(temp / 1000.0); + return Ok((temp / 1000.0).into()); } } @@ -78,7 +78,7 @@ pub async fn get_cpu_temp() -> Result> { for path in paths.flatten() { if let Ok(content) = fs::read_to_string(&path) { if let Ok(temp) = content.trim().parse::() { - return Ok(temp / 1000.0); + return Ok((temp / 1000.0).into()); } } } diff --git a/WatcherAgent/src/hardware/disk.rs b/WatcherAgent/src/hardware/disk.rs index 0bf60ef..fa67966 100644 --- a/WatcherAgent/src/hardware/disk.rs +++ b/WatcherAgent/src/hardware/disk.rs @@ -13,7 +13,7 @@ pub struct DiskInfo { pub async fn get_disk_info() -> Result { let disks = Disks::new_with_refreshed_list(); - let disk_types = [ + let _disk_types = [ sysinfo::DiskKind::HDD, sysinfo::DiskKind::SSD, sysinfo::DiskKind::Unknown(0), diff --git a/WatcherAgent/src/hardware/memory.rs b/WatcherAgent/src/hardware/memory.rs index 6a5222e..35a71cb 100644 --- a/WatcherAgent/src/hardware/memory.rs +++ b/WatcherAgent/src/hardware/memory.rs @@ -1,3 +1,5 @@ +use std::error::Error; + use anyhow::Result; use sysinfo::System; @@ -19,7 +21,7 @@ pub async fn get_memory_info() -> Result { }) } -pub fn _get_memory_usage(sys: &mut System) -> f64 { +pub fn _get_memory_usage(sys: &mut System) -> Result> { sys.refresh_memory(); - (sys.used_memory() as f64 / sys.total_memory() as f64) * 100.0 + Ok((sys.used_memory() as f64 / sys.total_memory() as f64) * 100.0) } From 868941a06508df5e97de0b6e8b2f6b18edd2b902 Mon Sep 17 00:00:00 2001 From: donpat1to Date: Sat, 9 Aug 2025 13:14:02 +0200 Subject: [PATCH 37/49] fixed gpu detection - bc unknown lib --- WatcherAgent/src/hardware/disk.rs | 4 +-- WatcherAgent/src/hardware/gpu.rs | 47 ++++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/WatcherAgent/src/hardware/disk.rs b/WatcherAgent/src/hardware/disk.rs index fa67966..92ff559 100644 --- a/WatcherAgent/src/hardware/disk.rs +++ b/WatcherAgent/src/hardware/disk.rs @@ -131,7 +131,7 @@ pub fn get_disk_utitlization() -> Result<(f64, f64, f64, f64), Box> { )) // Disk-Temp bleibt 0.0 ohne spezielle Hardware } -pub fn get_disk_temp_for_component(component: &Component) -> Option { +pub fn _get_disk_temp_for_component(component: &Component) -> Option { if let Some(temp) = component.temperature() { Some(temp as f64) } else { @@ -139,7 +139,7 @@ pub fn get_disk_temp_for_component(component: &Component) -> Option { } } -pub fn get_disk_load_for_disk(disk: &Disk) -> Result<(f64, f64, f64, f64), Box> { +pub fn _get_disk_load_for_disk(disk: &Disk) -> Result<(f64, f64, f64, f64), Box> { let usage: DiskUsage = disk.usage(); // Assuming DiskUsage has these methods: diff --git a/WatcherAgent/src/hardware/gpu.rs b/WatcherAgent/src/hardware/gpu.rs index e49c3c0..a041759 100644 --- a/WatcherAgent/src/hardware/gpu.rs +++ b/WatcherAgent/src/hardware/gpu.rs @@ -16,8 +16,9 @@ pub async fn get_gpu_info() -> Result> { let device = nvml.device_by_index(0)?; let (used, total) = get_gpu_vram_usage(&device)?; + let gpu_name = detect_gpu_name(); Ok(GpuInfo { - name: device.name().ok(), + name: Some(gpu_name), current_load: get_gpu_load(&device).ok(), current_temp: get_gpu_temp(&device).ok(), vram_total: Some(total as f64), @@ -39,3 +40,47 @@ pub fn get_gpu_vram_usage(device: &nvml_wrapper::Device) -> Result<(f64, f64), B let mem_info = device.memory_info().unwrap(); Ok((mem_info.used as f64, mem_info.total as f64)) } + +fn detect_gpu_name() -> String { + try_nvml_gpu_name() + .or_else(fallback_gpu_name) + .unwrap_or_else(|| "Unknown GPU".to_string()) +} + +fn try_nvml_gpu_name() -> Option { + let nvml = Nvml::init().ok()?; + let device = nvml.device_by_index(0).ok()?; + device.name().ok().map(|s| s.to_string()) +} + +fn fallback_gpu_name() -> Option { + #[cfg(target_os = "linux")] + { + let output = std::process::Command::new("lshw") + .args(&["-C", "display"]) + .output() + .ok()?; + Some( + String::from_utf8_lossy(&output.stdout) + .lines() + .find(|l| l.contains("product:")) + .map(|l| l.trim().replace("product:", "").trim().to_string()) + .unwrap_or("Unknown GPU".to_string()), + ) + } + + #[cfg(target_os = "windows")] + { + let output = std::process::Command::new("wmic") + .args(&["path", "win32_VideoController", "get", "name"]) + .output() + .ok()?; + Some( + String::from_utf8_lossy(&output.stdout) + .lines() + .nth(1) + .map(|s| s.trim().to_string()) + .unwrap_or("Unknown GPU".to_string()), + ) + } +} From 30c390c9fbf1fd895424241e3fd9bb6abc873ec1 Mon Sep 17 00:00:00 2001 From: donpat1to Date: Sat, 9 Aug 2025 13:32:53 +0200 Subject: [PATCH 38/49] fixed linux gpu detection - bc unknown lib --- WatcherAgent/Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/WatcherAgent/Cargo.toml b/WatcherAgent/Cargo.toml index 8c46c74..e653915 100644 --- a/WatcherAgent/Cargo.toml +++ b/WatcherAgent/Cargo.toml @@ -15,7 +15,8 @@ reqwest = { version = "0.11", default-features = false, features = ["json", "blo sysinfo = "0.36.1" metrics = "0.24.2" chrono = "0.4" -nvml-wrapper = "0.10" +nvml-wrapper = "0.11" +nvml-wrapper-sys = "0.9.0" anyhow = "1.0.98" [target.'cfg(windows)'.dependencies] From f777221405cdb90b9f05f82e24eec6f6e0c56dff Mon Sep 17 00:00:00 2001 From: donpat1to Date: Sat, 9 Aug 2025 13:44:21 +0200 Subject: [PATCH 39/49] added libs to find installed libs --- WatcherAgent/Cargo.toml | 6 +++++- WatcherAgent/src/hardware/mod.rs | 8 ++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/WatcherAgent/Cargo.toml b/WatcherAgent/Cargo.toml index e653915..35c2b05 100644 --- a/WatcherAgent/Cargo.toml +++ b/WatcherAgent/Cargo.toml @@ -26,4 +26,8 @@ com = "0.2" widestring = "0.5" [target.'cfg(unix)'.dependencies] -glob = "0.3" \ No newline at end of file +glob = "0.3" +libloading = "0.8" + +[build-dependencies] +pkg-config = { version = "0.3", optional = true } # Für Library-Detektion \ No newline at end of file diff --git a/WatcherAgent/src/hardware/mod.rs b/WatcherAgent/src/hardware/mod.rs index f629912..4f20a62 100644 --- a/WatcherAgent/src/hardware/mod.rs +++ b/WatcherAgent/src/hardware/mod.rs @@ -1,10 +1,10 @@ //use anyhow::Result; use std::error::Error; -mod cpu; -mod disk; -mod gpu; -mod memory; +pub(crate) mod cpu; +pub(crate) mod disk; +pub(crate) mod gpu; +pub(crate) mod memory; pub(crate) mod network; pub use cpu::get_cpu_info; From a8cfbbf7667606bcdd6af723872ff1ee60818c71 Mon Sep 17 00:00:00 2001 From: donpat1to Date: Sat, 9 Aug 2025 17:41:31 +0200 Subject: [PATCH 40/49] wilde jagd nach der library --- WatcherAgent/src/hardware/mod.rs | 2 +- WatcherAgent/src/main.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/WatcherAgent/src/hardware/mod.rs b/WatcherAgent/src/hardware/mod.rs index 4f20a62..779be3d 100644 --- a/WatcherAgent/src/hardware/mod.rs +++ b/WatcherAgent/src/hardware/mod.rs @@ -3,7 +3,7 @@ use std::error::Error; pub(crate) mod cpu; pub(crate) mod disk; -pub(crate) mod gpu; +pub mod gpu; pub(crate) mod memory; pub(crate) mod network; diff --git a/WatcherAgent/src/main.rs b/WatcherAgent/src/main.rs index b32c3d4..d7539b5 100644 --- a/WatcherAgent/src/main.rs +++ b/WatcherAgent/src/main.rs @@ -6,6 +6,7 @@ mod hardware; mod metrics; mod models; +use crate::hardware::gpu; use anyhow::Result; use std::error::Error; From cfd9fef3e95561b0bd3dc97f25a06e260cb71817 Mon Sep 17 00:00:00 2001 From: donpat1to Date: Sat, 9 Aug 2025 17:52:01 +0200 Subject: [PATCH 41/49] =?UTF-8?q?trying=20different=20st=C3=B6ff?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- WatcherAgent/src/hardware/cpu.rs | 2 +- WatcherAgent/src/hardware/disk.rs | 6 +----- WatcherAgent/src/hardware/gpu.rs | 6 +++--- WatcherAgent/src/hardware/mod.rs | 8 ++++---- WatcherAgent/src/hardware/network.rs | 6 +++--- WatcherAgent/src/main.rs | 10 +++++----- 6 files changed, 17 insertions(+), 21 deletions(-) diff --git a/WatcherAgent/src/hardware/cpu.rs b/WatcherAgent/src/hardware/cpu.rs index f876292..59684b2 100644 --- a/WatcherAgent/src/hardware/cpu.rs +++ b/WatcherAgent/src/hardware/cpu.rs @@ -242,7 +242,7 @@ pub async fn get_cpu_temp() -> Result> { (*services).Release(); (*locator).Release(); - return Ok(result.unwrap() as f64).map_err(|e| e.into()); + Ok(result.unwrap() as f64).map_err(|e| e.into()) } } diff --git a/WatcherAgent/src/hardware/disk.rs b/WatcherAgent/src/hardware/disk.rs index 92ff559..30a6cbc 100644 --- a/WatcherAgent/src/hardware/disk.rs +++ b/WatcherAgent/src/hardware/disk.rs @@ -132,11 +132,7 @@ pub fn get_disk_utitlization() -> Result<(f64, f64, f64, f64), Box> { } pub fn _get_disk_temp_for_component(component: &Component) -> Option { - if let Some(temp) = component.temperature() { - Some(temp as f64) - } else { - None - } + component.temperature().map(|temp| temp as f64) } pub fn _get_disk_load_for_disk(disk: &Disk) -> Result<(f64, f64, f64, f64), Box> { diff --git a/WatcherAgent/src/hardware/gpu.rs b/WatcherAgent/src/hardware/gpu.rs index a041759..f11db0b 100644 --- a/WatcherAgent/src/hardware/gpu.rs +++ b/WatcherAgent/src/hardware/gpu.rs @@ -21,8 +21,8 @@ pub async fn get_gpu_info() -> Result> { name: Some(gpu_name), current_load: get_gpu_load(&device).ok(), current_temp: get_gpu_temp(&device).ok(), - vram_total: Some(total as f64), - vram_used: Some(used as f64), + vram_total: Some(total), + vram_used: Some(used), }) } @@ -72,7 +72,7 @@ fn fallback_gpu_name() -> Option { #[cfg(target_os = "windows")] { let output = std::process::Command::new("wmic") - .args(&["path", "win32_VideoController", "get", "name"]) + .args(["path", "win32_VideoController", "get", "name"]) .output() .ok()?; Some( diff --git a/WatcherAgent/src/hardware/mod.rs b/WatcherAgent/src/hardware/mod.rs index 779be3d..50039cb 100644 --- a/WatcherAgent/src/hardware/mod.rs +++ b/WatcherAgent/src/hardware/mod.rs @@ -1,11 +1,11 @@ //use anyhow::Result; use std::error::Error; -pub(crate) mod cpu; -pub(crate) mod disk; +pub mod cpu; +pub mod disk; pub mod gpu; -pub(crate) mod memory; -pub(crate) mod network; +pub mod memory; +pub mod network; pub use cpu::get_cpu_info; pub use disk::get_disk_info; diff --git a/WatcherAgent/src/hardware/network.rs b/WatcherAgent/src/hardware/network.rs index c7c1e6e..b90921b 100644 --- a/WatcherAgent/src/hardware/network.rs +++ b/WatcherAgent/src/hardware/network.rs @@ -53,8 +53,8 @@ pub async fn get_network_info(monitor: &mut NetworkMonitor) -> Result Result<(u64, u64), Box> { } if rx_total == 0 && tx_total == 0 { - return Err(anyhow::anyhow!("No network data available").into()); + Err(anyhow::anyhow!("No network data available").into()) } else { Ok((rx_total, tx_total)) } diff --git a/WatcherAgent/src/main.rs b/WatcherAgent/src/main.rs index d7539b5..3ace2f6 100644 --- a/WatcherAgent/src/main.rs +++ b/WatcherAgent/src/main.rs @@ -1,12 +1,12 @@ /// WatcherAgent - A Rust-based system monitoring agent /// This agent collects hardware metrics and sends them to a backend server. /// It supports CPU, GPU, RAM, disk, and network metrics. -mod api; -mod hardware; -mod metrics; -mod models; +pub mod api; +pub mod hardware; +pub mod metrics; +pub mod models; -use crate::hardware::gpu; +pub use crate::hardware::gpu; use anyhow::Result; use std::error::Error; From f570f3c67d0e30a3109d10e77b8bd3079dab2f4f Mon Sep 17 00:00:00 2001 From: donpat1to Date: Sat, 9 Aug 2025 18:28:12 +0200 Subject: [PATCH 42/49] back to the past --- WatcherAgent/src/hardware/gpu.rs | 44 ++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/WatcherAgent/src/hardware/gpu.rs b/WatcherAgent/src/hardware/gpu.rs index f11db0b..5fbda4e 100644 --- a/WatcherAgent/src/hardware/gpu.rs +++ b/WatcherAgent/src/hardware/gpu.rs @@ -12,20 +12,21 @@ pub struct GpuInfo { } pub async fn get_gpu_info() -> Result> { - let nvml = Nvml::init()?; - let device = nvml.device_by_index(0)?; + //let nvml = Nvml::init()?; + //let device = nvml.device_by_index(0)?; - let (used, total) = get_gpu_vram_usage(&device)?; + //let (used, total) = get_gpu_vram_usage(&device)?; + let (gpu_temp, gpu_load, vram_used, vram_total) = get_gpu_metrics()?; let gpu_name = detect_gpu_name(); Ok(GpuInfo { name: Some(gpu_name), - current_load: get_gpu_load(&device).ok(), - current_temp: get_gpu_temp(&device).ok(), - vram_total: Some(total), - vram_used: Some(used), + current_load: Some(gpu_load), + current_temp: Some(gpu_temp), + vram_total: Some(vram_total), + vram_used: Some(vram_used), }) } - +/* pub fn get_gpu_load(device: &nvml_wrapper::Device) -> Result> { Ok(device.utilization_rates().unwrap().gpu as f64) } @@ -39,6 +40,33 @@ pub fn get_gpu_temp(device: &nvml_wrapper::Device) -> Result pub fn get_gpu_vram_usage(device: &nvml_wrapper::Device) -> Result<(f64, f64), Box> { let mem_info = device.memory_info().unwrap(); Ok((mem_info.used as f64, mem_info.total as f64)) +}*/ + +pub fn get_gpu_metrics() -> Result<(f64, f64, f64, f64), Box> { + let nvml = Nvml::init()?; + let (gpu_temp, gpu_load, vram_used, vram_total) = if let nvml = nvml { + let device = nvml.device_by_index(0).ok().unwrap(); + let temp = device + .temperature(nvml_wrapper::enum_wrappers::device::TemperatureSensor::Gpu) + .unwrap_or(0) as f64; + let load = device + .utilization_rates() + .map(|u| u.gpu as f64) + .unwrap_or(0.0); + let mem = device.memory_info().ok(); + let used = mem.clone().map(|m| (m.used as f64)).unwrap_or(0.0); // B + let total = mem.map(|m| (m.total as f64)).unwrap_or(0.0); // B + (temp, load, used, total) + } else { + return Err(anyhow::anyhow!("Failed to initialize NVML").into()); + }; + + Ok(( + gpu_temp as f64, + gpu_load as f64, + vram_used as f64, + vram_total as f64, + )) } fn detect_gpu_name() -> String { From 41b56d165e8e0c75f00af3e66f96cdc9e450f02a Mon Sep 17 00:00:00 2001 From: donpat1to Date: Sat, 9 Aug 2025 18:48:48 +0200 Subject: [PATCH 43/49] added graceful fallback if nvml doesnt hit --- WatcherAgent/src/hardware/gpu.rs | 7 +------ WatcherAgent/src/hardware/mod.rs | 1 + 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/WatcherAgent/src/hardware/gpu.rs b/WatcherAgent/src/hardware/gpu.rs index 5fbda4e..733daf9 100644 --- a/WatcherAgent/src/hardware/gpu.rs +++ b/WatcherAgent/src/hardware/gpu.rs @@ -61,12 +61,7 @@ pub fn get_gpu_metrics() -> Result<(f64, f64, f64, f64), Box> { return Err(anyhow::anyhow!("Failed to initialize NVML").into()); }; - Ok(( - gpu_temp as f64, - gpu_load as f64, - vram_used as f64, - vram_total as f64, - )) + Ok((gpu_temp, gpu_load, vram_used, vram_total)) } fn detect_gpu_name() -> String { diff --git a/WatcherAgent/src/hardware/mod.rs b/WatcherAgent/src/hardware/mod.rs index 50039cb..c8c3b34 100644 --- a/WatcherAgent/src/hardware/mod.rs +++ b/WatcherAgent/src/hardware/mod.rs @@ -12,6 +12,7 @@ pub use disk::get_disk_info; pub use gpu::get_gpu_info; pub use memory::get_memory_info; pub use network::get_network_info; +pub use network::NetworkMonitor; #[derive(Debug)] pub struct HardwareInfo { From f997ba385bdcb024d7249bc94f4684b56d1161e6 Mon Sep 17 00:00:00 2001 From: donpat1to Date: Sat, 9 Aug 2025 18:58:32 +0200 Subject: [PATCH 44/49] added graceful fallback if nvml doesnt hit for the name --- WatcherAgent/src/hardware/gpu.rs | 116 +++++++++++++++---------------- 1 file changed, 56 insertions(+), 60 deletions(-) diff --git a/WatcherAgent/src/hardware/gpu.rs b/WatcherAgent/src/hardware/gpu.rs index 733daf9..836f95a 100644 --- a/WatcherAgent/src/hardware/gpu.rs +++ b/WatcherAgent/src/hardware/gpu.rs @@ -12,56 +12,52 @@ pub struct GpuInfo { } pub async fn get_gpu_info() -> Result> { - //let nvml = Nvml::init()?; - //let device = nvml.device_by_index(0)?; - - //let (used, total) = get_gpu_vram_usage(&device)?; - let (gpu_temp, gpu_load, vram_used, vram_total) = get_gpu_metrics()?; - let gpu_name = detect_gpu_name(); - Ok(GpuInfo { - name: Some(gpu_name), - current_load: Some(gpu_load), - current_temp: Some(gpu_temp), - vram_total: Some(vram_total), - vram_used: Some(vram_used), - }) + match get_gpu_metrics() { + Ok((gpu_temp, gpu_load, vram_used, vram_total)) => { + let gpu_name = detect_gpu_name(); + Ok(GpuInfo { + name: Some(gpu_name), + current_load: Some(gpu_load), + current_temp: Some(gpu_temp), + vram_total: Some(vram_total), + vram_used: Some(vram_used), + }) + } + Err(e) => { + // Graceful fallback: log error, return empty/None values + eprintln!("GPU info unavailable: {e}"); + Ok(GpuInfo { + name: Some(detect_gpu_name()), + current_load: None, + current_temp: None, + vram_total: None, + vram_used: None, + }) + } + } } -/* -pub fn get_gpu_load(device: &nvml_wrapper::Device) -> Result> { - Ok(device.utilization_rates().unwrap().gpu as f64) -} - -pub fn get_gpu_temp(device: &nvml_wrapper::Device) -> Result> { - Ok(device - .temperature(nvml_wrapper::enum_wrappers::device::TemperatureSensor::Gpu) - .unwrap() as f64) -} - -pub fn get_gpu_vram_usage(device: &nvml_wrapper::Device) -> Result<(f64, f64), Box> { - let mem_info = device.memory_info().unwrap(); - Ok((mem_info.used as f64, mem_info.total as f64)) -}*/ pub fn get_gpu_metrics() -> Result<(f64, f64, f64, f64), Box> { - let nvml = Nvml::init()?; - let (gpu_temp, gpu_load, vram_used, vram_total) = if let nvml = nvml { - let device = nvml.device_by_index(0).ok().unwrap(); - let temp = device - .temperature(nvml_wrapper::enum_wrappers::device::TemperatureSensor::Gpu) - .unwrap_or(0) as f64; - let load = device - .utilization_rates() - .map(|u| u.gpu as f64) - .unwrap_or(0.0); - let mem = device.memory_info().ok(); - let used = mem.clone().map(|m| (m.used as f64)).unwrap_or(0.0); // B - let total = mem.map(|m| (m.total as f64)).unwrap_or(0.0); // B - (temp, load, used, total) + let nvml = Nvml::init(); + if let Ok(nvml) = nvml { + if let Ok(device) = nvml.device_by_index(0) { + let temp = device + .temperature(nvml_wrapper::enum_wrappers::device::TemperatureSensor::Gpu) + .unwrap_or(0) as f64; + let load = device + .utilization_rates() + .map(|u| u.gpu as f64) + .unwrap_or(0.0); + let mem = device.memory_info().ok(); + let used = mem.clone().map(|m| m.used as f64).unwrap_or(0.0); + let total = mem.map(|m| m.total as f64).unwrap_or(0.0); + Ok((temp, load, used, total)) + } else { + Err(anyhow::anyhow!("No NVIDIA GPU found").into()) + } } else { - return Err(anyhow::anyhow!("Failed to initialize NVML").into()); - }; - - Ok((gpu_temp, gpu_load, vram_used, vram_total)) + Err(anyhow::anyhow!("Failed to initialize NVML").into()) + } } fn detect_gpu_name() -> String { @@ -83,13 +79,10 @@ fn fallback_gpu_name() -> Option { .args(&["-C", "display"]) .output() .ok()?; - Some( - String::from_utf8_lossy(&output.stdout) - .lines() - .find(|l| l.contains("product:")) - .map(|l| l.trim().replace("product:", "").trim().to_string()) - .unwrap_or("Unknown GPU".to_string()), - ) + String::from_utf8_lossy(&output.stdout) + .lines() + .find(|l| l.contains("product:")) + .map(|l| l.trim().replace("product:", "").trim().to_string()) } #[cfg(target_os = "windows")] @@ -98,12 +91,15 @@ fn fallback_gpu_name() -> Option { .args(["path", "win32_VideoController", "get", "name"]) .output() .ok()?; - Some( - String::from_utf8_lossy(&output.stdout) - .lines() - .nth(1) - .map(|s| s.trim().to_string()) - .unwrap_or("Unknown GPU".to_string()), - ) + String::from_utf8_lossy(&output.stdout) + .lines() + .skip(1) // Skip header + .find(|s| !s.trim().is_empty()) + .map(|s| s.trim().to_string()) + } + + #[cfg(not(any(target_os = "linux", target_os = "windows")))] + { + None } } From a1bbbedd75a574f68d05b70e132b7e4ffd943048 Mon Sep 17 00:00:00 2001 From: donpat1to Date: Sat, 9 Aug 2025 19:52:21 +0200 Subject: [PATCH 45/49] added debugging for metrics collection --- WatcherAgent/src/metrics.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/WatcherAgent/src/metrics.rs b/WatcherAgent/src/metrics.rs index a19502c..278ebf3 100644 --- a/WatcherAgent/src/metrics.rs +++ b/WatcherAgent/src/metrics.rs @@ -21,8 +21,12 @@ impl Collector { } } - pub async fn run(&mut self, base_url: &str) -> anyhow::Result<(), Box> { + pub async fn run(&mut self, base_url: &str) -> Result<(), Box> { loop { + println!( + "[{}] Starting metrics collection...", + chrono::Local::now().format("%H:%M:%S") + ); let metrics = self.collect().await?; api::send_metrics(base_url, &metrics).await?; tokio::time::sleep(Duration::from_secs(20)).await; From ac2fce75a0c4a52530cceca8dc8da7b64eb26593 Mon Sep 17 00:00:00 2001 From: donpat1to Date: Sat, 9 Aug 2025 22:00:42 +0200 Subject: [PATCH 46/49] running parallel tokio tasks for heartbeat and metrics --- WatcherAgent/src/api.rs | 17 +++++++-- WatcherAgent/src/hardware/cpu.rs | 6 +-- WatcherAgent/src/hardware/gpu.rs | 4 +- WatcherAgent/src/hardware/memory.rs | 2 +- WatcherAgent/src/hardware/mod.rs | 14 ++++++- WatcherAgent/src/main.rs | 57 +++++++++++++++++++++++------ WatcherAgent/src/metrics.rs | 21 +++++++++-- 7 files changed, 93 insertions(+), 28 deletions(-) diff --git a/WatcherAgent/src/api.rs b/WatcherAgent/src/api.rs index 1c8df20..87234aa 100644 --- a/WatcherAgent/src/api.rs +++ b/WatcherAgent/src/api.rs @@ -7,7 +7,9 @@ use reqwest::{Client, StatusCode}; use std::error::Error; use tokio::time::{interval, sleep}; -pub async fn register_with_server(base_url: &str) -> Result<(i32, String), Box> { +pub async fn register_with_server( + base_url: &str, +) -> Result<(i32, String), Box> { // First get local IP let ip = local_ip_address::local_ip()?.to_string(); println!("Local IP address detected: {}", ip); @@ -58,7 +60,10 @@ pub async fn register_with_server(base_url: &str) -> Result<(i32, String), Box Result<(i32, String), Box> { +async fn get_server_id_by_ip( + base_url: &str, + ip: &str, +) -> Result<(i32, String), Box> { let client = Client::builder() .danger_accept_invalid_certs(true) .build()?; @@ -106,7 +111,7 @@ async fn get_server_id_by_ip(base_url: &str, ip: &str) -> Result<(i32, String), } } -pub async fn heartbeat_loop(base_url: &str, ip: &str) -> Result<(), Box> { +pub async fn heartbeat_loop(base_url: &str, ip: &str) -> Result<(), Box> { let client = Client::builder() .danger_accept_invalid_certs(true) .build()?; @@ -129,7 +134,10 @@ pub async fn heartbeat_loop(base_url: &str, ip: &str) -> Result<(), Box Result<(), Box> { +pub async fn send_metrics( + base_url: &str, + metrics: &MetricDto, +) -> Result<(), Box> { let client = Client::new(); let url = format!("{}/monitoring/metric", base_url); let mut interval = interval(Duration::from_secs(20)); @@ -137,6 +145,7 @@ pub async fn send_metrics(base_url: &str, metrics: &MetricDto) -> Result<(), Box loop { interval.tick().await; let metric = metrics; + eprintln!("Sending metrics: {:?}", metric); match client.post(&url).json(&metric).send().await { Ok(res) => println!( diff --git a/WatcherAgent/src/hardware/cpu.rs b/WatcherAgent/src/hardware/cpu.rs index 59684b2..73efa06 100644 --- a/WatcherAgent/src/hardware/cpu.rs +++ b/WatcherAgent/src/hardware/cpu.rs @@ -11,7 +11,7 @@ pub struct CpuInfo { pub current_temp: Option, } -pub async fn get_cpu_info() -> Result> { +pub async fn get_cpu_info() -> Result> { let mut sys = System::new(); sys.refresh_cpu_all(); @@ -28,13 +28,13 @@ pub async fn get_cpu_info() -> Result> { }) } -pub async fn get_cpu_load(sys: &mut System) -> Result> { +pub async fn get_cpu_load(sys: &mut System) -> Result> { sys.refresh_cpu_all(); tokio::task::yield_now().await; // Allow other tasks to run Ok(sys.global_cpu_usage() as f64) } -pub async fn get_cpu_temp() -> Result> { +pub async fn get_cpu_temp() -> Result> { println!("Attempting to get CPU temperature..."); #[cfg(target_os = "linux")] diff --git a/WatcherAgent/src/hardware/gpu.rs b/WatcherAgent/src/hardware/gpu.rs index 836f95a..9e667e6 100644 --- a/WatcherAgent/src/hardware/gpu.rs +++ b/WatcherAgent/src/hardware/gpu.rs @@ -11,7 +11,7 @@ pub struct GpuInfo { pub vram_used: Option, } -pub async fn get_gpu_info() -> Result> { +pub async fn get_gpu_info() -> Result> { match get_gpu_metrics() { Ok((gpu_temp, gpu_load, vram_used, vram_total)) => { let gpu_name = detect_gpu_name(); @@ -37,7 +37,7 @@ pub async fn get_gpu_info() -> Result> { } } -pub fn get_gpu_metrics() -> Result<(f64, f64, f64, f64), Box> { +pub fn get_gpu_metrics() -> Result<(f64, f64, f64, f64), Box> { let nvml = Nvml::init(); if let Ok(nvml) = nvml { if let Ok(device) = nvml.device_by_index(0) { diff --git a/WatcherAgent/src/hardware/memory.rs b/WatcherAgent/src/hardware/memory.rs index 35a71cb..758c57a 100644 --- a/WatcherAgent/src/hardware/memory.rs +++ b/WatcherAgent/src/hardware/memory.rs @@ -21,7 +21,7 @@ pub async fn get_memory_info() -> Result { }) } -pub fn _get_memory_usage(sys: &mut System) -> Result> { +pub fn _get_memory_usage(sys: &mut System) -> Result> { sys.refresh_memory(); Ok((sys.used_memory() as f64 / sys.total_memory() as f64) * 100.0) } diff --git a/WatcherAgent/src/hardware/mod.rs b/WatcherAgent/src/hardware/mod.rs index c8c3b34..0eba3c2 100644 --- a/WatcherAgent/src/hardware/mod.rs +++ b/WatcherAgent/src/hardware/mod.rs @@ -25,14 +25,24 @@ pub struct HardwareInfo { } impl HardwareInfo { - pub async fn collect() -> anyhow::Result> { + pub async fn collect() -> Result> { let mut network_monitor = network::NetworkMonitor::new(); Ok(Self { cpu: get_cpu_info().await?, gpu: get_gpu_info().await?, memory: get_memory_info().await?, disk: get_disk_info().await?, - network: get_network_info(&mut network_monitor).await?, + network: match get_network_info(&mut network_monitor).await { + Ok(info) => info, + Err(e) => { + eprintln!("Error collecting network info: {}", e); + network::NetworkInfo { + interfaces: None, + rx_rate: None, + tx_rate: None, + } + } + }, network_monitor, }) } diff --git a/WatcherAgent/src/main.rs b/WatcherAgent/src/main.rs index 3ace2f6..a990253 100644 --- a/WatcherAgent/src/main.rs +++ b/WatcherAgent/src/main.rs @@ -7,33 +7,66 @@ pub mod metrics; pub mod models; pub use crate::hardware::gpu; -use anyhow::Result; use std::error::Error; +use std::marker::Send; +use std::marker::Sync; +use std::result::Result; +use tokio::task::JoinHandle; + +async fn flatten( + handle: JoinHandle>>, +) -> Result> { + match handle.await { + Ok(Ok(result)) => Ok(result), + Ok(Err(err)) => Err(err), + Err(_err) => Err("handling failed".into()), + } +} #[tokio::main] -async fn main() -> Result<(), Box> { +async fn main() -> Result<(), Box> { let server_url = "http://localhost:5000"; // Registration - let (server_id, ip) = api::register_with_server(server_url).await?; + let (server_id, ip) = match api::register_with_server(server_url).await { + Ok((id, ip)) => (id, ip), + Err(e) => { + eprintln!("Fehler bei der Registrierung am Server: {e}"); + return Err(e.into()); + } + }; // Start background tasks // Start heartbeat in background let heartbeat_handle = tokio::spawn({ let ip = ip.clone(); - async move { - if let Err(e) = api::heartbeat_loop(server_url, &ip).await { - eprintln!("Heartbeat loop failed: {}", e); - } - } + let server_url = server_url.to_string(); + async move { api::heartbeat_loop(&server_url, &ip).await } }); - heartbeat_handle.await?; - // Main metrics loop println!("Starting metrics collection..."); - let mut collector = metrics::Collector::new(server_id, ip); - collector.run(server_url).await?; + let metrics_handle = tokio::spawn({ + let ip = ip.clone(); + let server_url = server_url.to_string(); + async move { + let mut collector = metrics::Collector::new(server_id, ip); + collector.run(&server_url).await + } + }); + + // Warte auf beide Tasks und prüfe explizit auf Fehler + let (heartbeat_handle, metrics_handle) = + tokio::try_join!(flatten(heartbeat_handle), flatten(metrics_handle))?; + + match (heartbeat_handle, metrics_handle) { + (heartbeat, metrics) => println!( + "All tasks completed successfully: {:?}, {:?}.", + heartbeat, metrics + ), + } + + println!("All tasks completed successfully."); Ok(()) } diff --git a/WatcherAgent/src/metrics.rs b/WatcherAgent/src/metrics.rs index 278ebf3..5ca541b 100644 --- a/WatcherAgent/src/metrics.rs +++ b/WatcherAgent/src/metrics.rs @@ -21,20 +21,33 @@ impl Collector { } } - pub async fn run(&mut self, base_url: &str) -> Result<(), Box> { + pub async fn run(&mut self, base_url: &str) -> Result<(), Box> { loop { println!( "[{}] Starting metrics collection...", chrono::Local::now().format("%H:%M:%S") ); - let metrics = self.collect().await?; + let metrics = match self.collect().await { + Ok(metrics) => metrics, + Err(e) => { + eprintln!("Error collecting metrics: {}", e); + tokio::time::sleep(Duration::from_secs(10)).await; + continue; + } + }; api::send_metrics(base_url, &metrics).await?; tokio::time::sleep(Duration::from_secs(20)).await; } } - pub async fn collect(&mut self) -> Result> { - let hardware = HardwareInfo::collect().await?; + pub async fn collect(&mut self) -> Result> { + let hardware = match HardwareInfo::collect().await { + Ok(hw) => hw, + Err(e) => { + eprintln!("Fehler beim Sammeln der Hardware-Infos: {e}"); + return Err(e.into()); + } + }; // Collect network usage let (_, _) = self.network_monitor.update_usage().unwrap_or((0.0, 0.0)); From cab92df37eb3c9ad21f1ffb901d67a55d6a7f6ea Mon Sep 17 00:00:00 2001 From: donpat1to Date: Sat, 9 Aug 2025 22:08:04 +0200 Subject: [PATCH 47/49] removed drecks loop --- WatcherAgent/src/api.rs | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/WatcherAgent/src/api.rs b/WatcherAgent/src/api.rs index 87234aa..7374da2 100644 --- a/WatcherAgent/src/api.rs +++ b/WatcherAgent/src/api.rs @@ -124,7 +124,7 @@ pub async fn heartbeat_loop(base_url: &str, ip: &str) -> Result<(), Box { - println!("Heartbeat sent successfully."); + println!("✅ Heartbeat sent successfully."); } Ok(res) => eprintln!("Server responded with status: {}", res.status()), Err(e) => eprintln!("Heartbeat error: {}", e), @@ -140,20 +140,15 @@ pub async fn send_metrics( ) -> Result<(), Box> { let client = Client::new(); let url = format!("{}/monitoring/metric", base_url); - let mut interval = interval(Duration::from_secs(20)); - loop { - interval.tick().await; - let metric = metrics; - eprintln!("Sending metrics: {:?}", metric); - - match client.post(&url).json(&metric).send().await { - Ok(res) => println!( - "✅ Sent metrics for server {} | Status: {}", - metric.server_id, - res.status() - ), - Err(err) => eprintln!("❌ Failed to send metrics: {}", err), - } + match client.post(&url).json(&metrics).send().await { + Ok(res) => println!( + "✅ Sent metrics for server {} | Status: {}", + metrics.server_id, + res.status() + ), + Err(err) => eprintln!("❌ Failed to send metrics: {}", err), } + + Ok(()) } From 72a260c65f98dff5a209d49ce70ebbba3884ed51 Mon Sep 17 00:00:00 2001 From: donpat1to Date: Sat, 9 Aug 2025 22:12:49 +0200 Subject: [PATCH 48/49] satisfied cargo clippy --- WatcherAgent/src/api.rs | 2 +- WatcherAgent/src/hardware/mod.rs | 2 +- WatcherAgent/src/hardware/network.rs | 6 ++++++ WatcherAgent/src/main.rs | 13 ++++++------- WatcherAgent/src/metrics.rs | 2 +- 5 files changed, 15 insertions(+), 10 deletions(-) diff --git a/WatcherAgent/src/api.rs b/WatcherAgent/src/api.rs index 7374da2..4339cfa 100644 --- a/WatcherAgent/src/api.rs +++ b/WatcherAgent/src/api.rs @@ -5,7 +5,7 @@ use crate::models::{HeartbeatDto, IdResponse, MetricDto, RegistrationDto}; use anyhow::Result; use reqwest::{Client, StatusCode}; use std::error::Error; -use tokio::time::{interval, sleep}; +use tokio::time::sleep; pub async fn register_with_server( base_url: &str, diff --git a/WatcherAgent/src/hardware/mod.rs b/WatcherAgent/src/hardware/mod.rs index 0eba3c2..0b94094 100644 --- a/WatcherAgent/src/hardware/mod.rs +++ b/WatcherAgent/src/hardware/mod.rs @@ -21,7 +21,7 @@ pub struct HardwareInfo { pub memory: memory::MemoryInfo, pub disk: disk::DiskInfo, pub network: network::NetworkInfo, - network_monitor: network::NetworkMonitor, + pub network_monitor: network::NetworkMonitor, } impl HardwareInfo { diff --git a/WatcherAgent/src/hardware/network.rs b/WatcherAgent/src/hardware/network.rs index b90921b..c5bd603 100644 --- a/WatcherAgent/src/hardware/network.rs +++ b/WatcherAgent/src/hardware/network.rs @@ -16,6 +16,12 @@ pub struct NetworkMonitor { last_update: Instant, } +impl Default for NetworkMonitor { + fn default() -> Self { + Self::new() + } +} + impl NetworkMonitor { pub fn new() -> Self { Self { diff --git a/WatcherAgent/src/main.rs b/WatcherAgent/src/main.rs index a990253..fd1fcdc 100644 --- a/WatcherAgent/src/main.rs +++ b/WatcherAgent/src/main.rs @@ -32,7 +32,7 @@ async fn main() -> Result<(), Box> { Ok((id, ip)) => (id, ip), Err(e) => { eprintln!("Fehler bei der Registrierung am Server: {e}"); - return Err(e.into()); + return Err(e); } }; @@ -59,12 +59,11 @@ async fn main() -> Result<(), Box> { let (heartbeat_handle, metrics_handle) = tokio::try_join!(flatten(heartbeat_handle), flatten(metrics_handle))?; - match (heartbeat_handle, metrics_handle) { - (heartbeat, metrics) => println!( - "All tasks completed successfully: {:?}, {:?}.", - heartbeat, metrics - ), - } + let (heartbeat, metrics) = (heartbeat_handle, metrics_handle); + println!( + "All tasks completed successfully: {:?}, {:?}.", + heartbeat, metrics + ); println!("All tasks completed successfully."); diff --git a/WatcherAgent/src/metrics.rs b/WatcherAgent/src/metrics.rs index 5ca541b..381141b 100644 --- a/WatcherAgent/src/metrics.rs +++ b/WatcherAgent/src/metrics.rs @@ -45,7 +45,7 @@ impl Collector { Ok(hw) => hw, Err(e) => { eprintln!("Fehler beim Sammeln der Hardware-Infos: {e}"); - return Err(e.into()); + return Err(e); } }; // Collect network usage From 5900dfb21760bfcd3c08a264fc2240f7200f0cf6 Mon Sep 17 00:00:00 2001 From: donpat1to Date: Sun, 10 Aug 2025 13:10:26 +0200 Subject: [PATCH 49/49] clean-up finished --- WatcherAgent/src/hardware/disk.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WatcherAgent/src/hardware/disk.rs b/WatcherAgent/src/hardware/disk.rs index 30a6cbc..0365bff 100644 --- a/WatcherAgent/src/hardware/disk.rs +++ b/WatcherAgent/src/hardware/disk.rs @@ -50,7 +50,7 @@ pub fn get_disk_utitlization() -> Result<(f64, f64, f64, f64), Box> { let disks = Disks::new_with_refreshed_list(); for disk in disks.list() { - // Ignoriere CD-ROMs und kleine Systempartitionen + // Ignoriere kleine Systempartitionen println!( "Disk_Name: {:?}, Disk_Kind: {}, Total: {}, Available: {}",