Skip to content

Conversation

@amir0ff
Copy link
Contributor

@amir0ff amir0ff commented Dec 26, 2025

Internet Speed Plugin (INTRSPD)

Description

This PR optimizes the Internet Speed Plugin (INTRSPD) by introducing a Python-First Hybrid execution model. It addresses CPU-bottleneck issues associated with single-threaded Python on high-speed connections while satisfying requirements for long-term maintainability and system compatibility.

Engine Architecture

  1. Baseline Engine (Default): Uses the speedtest-cli Python library. This ensures 100% universal compatibility across all platforms for a stable "out-of-the-box" experience.
  2. Native Engine (Opt-in): Uses the official Ookla Speedtest binary. This engine is multi-threaded and highly optimized, capable of fully saturating Gigabit fiber connections where Python might be CPU-bottlenecked.

Usage & Installation

To enable the high-performance native engine (recommended for connections > 100 Mbps):

  1. Download: Get the official binary for your architecture from the Speedtest CLI Homepage.
  2. Place & Prepare: Place the binary on your host machine (e.g., in /opt/netalertx/) and ensure it has executable permissions:
   chmod +x /opt/netalertx/speedtest
  1. Docker Mapping: Map the host file to exactly /usr/bin/speedtest inside the container via your docker-compose.yml:
services:
  netalertx:
    volumes:
      - /opt/netalertx/speedtest:/usr/bin/speedtest:ro

Why this mapping?

Inside the container, a Python version of speedtest often exists in the virtual environment (/opt/venv/bin/speedtest). Mapping your native binary specifically to /usr/bin/speedtest allows the plugin to prioritize the high-performance native engine over the baseline library.

The plugin will automatically detect the binary and switch to the native engine for subsequent tests. If the binary is not detected, the system seamlessly falls back to the baseline library.

Verified Performance (Native Opt-in Mode)

Testing performed on a Raspberry Pi 4 (High-speed Fiber)

Metric Original (Python) Optimized (Native Opt-in)
Download ~175.36 Mbps ~634.46 Mbps
Upload ~51.05 Mbps ~634.86 Mbps

List of speed valeus during testing and debugging:

Screenshot 2025-12-25 181542

The missing download and Upload speeds of 332 and 336 were caused by parsing bug that was later fixed

Data Mapping

  • Watched_Value1: Download Speed (Mbps).
  • Watched_Value2: Upload Speed (Mbps).
  • Watched_Value3: Full JSON payload (optimized for n8n or secondary webhook parsing).

Summary by CodeRabbit

Release Notes

  • New Features

    • Added native speedtest binary support as an optimized engine for high-speed connections (>100 Mbps).
    • Full JSON speedtest results now available in watched values.
    • Automatic fallback to baseline engine if native binary is unavailable.
  • Documentation

    • Updated guide with native engine setup and configuration instructions.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 26, 2025

📝 Walkthrough

Walkthrough

Adds optional native Ookla speedtest binary execution (configurable path) with timeout-aware subprocess and JSON parsing; falls back to the existing Python CLI on failures/timeouts; plugin results now include a propagated full_json field and config/README localization updated for the dual-engine behavior.

Changes

Cohort / File(s) Summary
Backend const
server/const.py
Adds NATIVE_SPEEDTEST_PATH = os.getenv("NATIVE_SPEEDTEST_PATH", "/usr/bin/speedtest").
Backend Speedtest Endpoint
server/api_server/nettools_endpoint.py
Attempts native binary first using NATIVE_SPEEDTEST_PATH with JSON output and a 60s timeout; on success parses download/upload/latency/ISP/server/packet loss and returns structured output; on missing binary, timeout, non‑zero exit, or parse errors logs and falls back to speedtest-cli (60s); returns 200/504/503/500 as appropriate.
Frontend Plugin Script
front/plugins/internet_speedtest/script.py
Tries native binary via timeout-aware subprocess and JSON parsing; computes Mbps and returns download_speed, upload_speed, and full_json on success; robust fallback to baseline engine on native timeouts/errors/parse failures; baseline path also populates full_json (result or error JSON string).
Frontend Plugin Configuration
front/plugins/internet_speedtest/config.json
Updates Watched_Value3 label to "Full JSON" and localizes descriptions (en_us, es_es, de_de) to indicate a webhook JSON payload (schema varies by engine).
Documentation
front/plugins/internet_speedtest/README.md
Documents dual-engine operation (Baseline and Native), native binary path/config (default /usr/bin/speedtest), opt-in mounting instructions, data mapping for Watched_Value1/2/3 (Download/Upload/Full JSON), and fallback recommendations.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Client
    participant API as "API Server\n(nettools_endpoint)"
    participant Native as "Native Binary\n(NATIVE_SPEEDTEST_PATH)"
    participant CLI as "speedtest-cli\n(baseline)"
    Client->>API: GET /speedtest
    Note right of API `#f2f4f8`: Attempt native binary first (60s timeout)
    API->>Native: exec --format=json --accept-license --accept-gdpr (60s)
    alt Native returns valid JSON (exit 0)
        Native-->>API: JSON output
        API->>API: parse → download/upload/latency/ISP/server, full_json
        API-->>Client: 200 OK + structured output (includes full_json)
    else Native missing / timeout / non-zero / parse error
        Native--xAPI: Error / Timeout / Invalid JSON
        Note right of API `#fff4e6`: Log error and fallback to CLI (60s)
        API->>CLI: exec --secure --simple (60s)
        alt CLI returns success
            CLI-->>API: CLI output lines
            API->>API: build result (download/upload, full_json if available)
            API-->>Client: 200 OK + output_lines (full_json may be baseline JSON or error string)
        else CLI timeout
            CLI--xAPI: TimeoutExpired
            API-->>Client: 504 Gateway Timeout
        end
    end
Loading

Poem

I hop to test your broadband trail,
Native first, then CLI prevails,
A JSON carrot hugged so tight,
Speeds in Mbps by morning light—
Rabbit logs each byte and tale. 🐰📶

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and clearly describes the main change: a hybrid speedtest implementation that uses Python by default with native binary support as an opt-in feature, which accurately reflects the core objective of the changeset.
✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
front/plugins/internet_speedtest/script.py (3)

53-54: Consider making the binary path configurable.

The binary path is hardcoded to /usr/bin/speedtest. While acceptable for the default installation, consider using get_setting_value() to allow users to override the path if they've installed the binary elsewhere.

🔎 Proposed change to support configurable path
 def run_speedtest():
-    native_bin = '/usr/bin/speedtest'
+    native_bin = get_setting_value('INTRSPD_BINARY_PATH') or '/usr/bin/speedtest'
     cmd = [native_bin, "--format=json", "--accept-license", "--accept-gdpr"]

Then add to the plugin's configuration manifest or server/initialise.py:

ccd(
    'INTRSPD_BINARY_PATH',
    '/usr/bin/speedtest',
    'str',
    'Path to native Ookla speedtest binary'
)

57-62: Adjust log levels for important messages.

Per coding guidelines, use 'none' level for the most important messages that should always appear, such as test results and exceptions. Reserve 'verbose' for extra context.

Current usage logs critical information (results, errors) at 'verbose', which may be missed in default configurations.

🔎 Proposed log level adjustments
     try:
-        mylog('verbose', [f"[INTRSPD] Executing native binary: {native_bin}"])
+        mylog('debug', [f"[INTRSPD] Executing native binary: {native_bin}"])
         result = subprocess.run(cmd, capture_output=True, text=True)
         
         if result.returncode != 0:
-            mylog('verbose', [f"[INTRSPD] Native binary failed: {result.stderr}"])
+            mylog('none', [f"[INTRSPD] Native binary failed (exit {result.returncode}): {result.stderr}"])
             return {'down': 0.0, 'up': 0.0, 'json': '{}'}
 
         o = json.loads(result.stdout)
         
         down_mbps = round((o['download']['bandwidth'] * 8) / 10**6, 2)
         up_mbps = round((o['upload']['bandwidth'] * 8) / 10**6, 2)
         
         # Payload optimized for n8n/webhooks
         payload = {
             "download": int(o['download']['bandwidth'] * 8),
             "upload": int(o['upload']['bandwidth'] * 8),
             "ping": o['ping']['latency'],
             "server": {"name": o['server']['name']},
             "timestamp": o['timestamp']
         }
         
-        mylog('verbose', [f"[INTRSPD] Result (down|up): {down_mbps} Mbps | {up_mbps} Mbps"])
+        mylog('none', [f"[INTRSPD] Result: ↓ {down_mbps} Mbps | ↑ {up_mbps} Mbps | Ping: {payload['ping']} ms"])
         
         return {
             'down': down_mbps, 
             'up': up_mbps, 
             'json': json.dumps(payload)
         }
 
     except Exception as e:
-        mylog('verbose', [f"[INTRSPD] Error: {str(e)}"])
+        mylog('none', [f"[INTRSPD] Error: {str(e)}"])
         return {'down': 0.0, 'up': 0.0, 'json': json.dumps({"error": str(e)})}

Also applies to: 78-78, 87-87


86-88: Refine exception handling to catch specific exceptions.

The broad except Exception catches all exceptions, including KeyboardInterrupt and SystemExit. Catching specific exceptions improves debuggability and allows unexpected errors to surface properly.

🔎 Proposed refinement with specific exception types
-    except Exception as e:
-        mylog('none', [f"[INTRSPD] Error: {str(e)}"])
+    except subprocess.TimeoutExpired:
+        mylog('none', [f"[INTRSPD] Speedtest timed out after {timeout_sec}s"])
+        return {'down': 0.0, 'up': 0.0, 'json': json.dumps({"error": "timeout"})}
+    except subprocess.SubprocessError as e:
+        mylog('none', [f"[INTRSPD] Subprocess error: {e}"])
+        return {'down': 0.0, 'up': 0.0, 'json': json.dumps({"error": f"subprocess: {e}"})}
+    except (json.JSONDecodeError, KeyError) as e:
+        mylog('none', [f"[INTRSPD] Failed to parse speedtest output: {e}"])
+        return {'down': 0.0, 'up': 0.0, 'json': json.dumps({"error": f"parse: {e}"})}
+    except Exception as e:
+        mylog('none', [f"[INTRSPD] Unexpected error: {e}"])
         return {'down': 0.0, 'up': 0.0, 'json': json.dumps({"error": str(e)})}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aef1f89 and 85f0afb.

📒 Files selected for processing (5)
  • Dockerfile
  • front/plugins/internet_speedtest/script.py
  • install/proxmox/requirements.txt
  • install/ubuntu24/requirements.txt
  • requirements.txt
💤 Files with no reviewable changes (3)
  • install/proxmox/requirements.txt
  • install/ubuntu24/requirements.txt
  • requirements.txt
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.py: Use mylog(level, [message]) for logging; levels are: none/minimal/verbose/debug/trace. Use none for most important messages that should always appear, such as exceptions.
Always set explicit timeouts on subprocess calls. Default to 60s minimum unless plugin config specifies otherwise. Nested subprocess calls need their own timeout.
Never hardcode ports or secrets; always use get_setting_value() to retrieve configuration values.
Use environment variables (NETALERTX_DB, NETALERTX_LOG, etc.) everywhere instead of hardcoding paths like /data/db or relative paths.
Use helper.py functions (timeNowDB, normalize_mac, sanitizers) for time/MAC/string operations. Validate MACs before DB writes.
Add/modify settings via ccd() in server/initialise.py or per-plugin manifest. Define config once and read it via helpers everywhere.
Always leave logging enabled. If there is a possibility it will be difficult to debug with current logging, add more logging.

Files:

  • front/plugins/internet_speedtest/script.py
front/plugins/*/script.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

front/plugins/*/script.py: For plugins: define database_column_definitions when creating/updating devices; watched fields trigger notifications.
Plugin scripts must write results to /tmp/log/plugins/last_result.<PREF>.log with pipe-delimited format (9 required cols + optional 4). Use Plugin_Objects to sanitize text and normalize MACs, then call write_result_file() exactly once at the end.
Collect plugin results with Plugin_Objects.add_object(...) during processing and call plugin_objects.write_result_file() exactly once at the end of the script. Do not write ad-hoc files for results.
Always check relevant logs first. Use logging as shown in other plugins. Prefer to log a brief summary before writing results to aid troubleshooting; keep logs concise at info level and use verbose or debug for extra context.

Files:

  • front/plugins/internet_speedtest/script.py
🧠 Learnings (6)
📚 Learning: 2025-09-20T03:01:19.912Z
Learnt from: adamoutler
Repo: jokob-sk/NetAlertX PR: 1184
File: .devcontainer/Dockerfile:18-19
Timestamp: 2025-09-20T03:01:19.912Z
Learning: In the NetAlertX repository, .devcontainer/Dockerfile is auto-generated and should not be reviewed directly. Review comments about dependencies and build steps should be directed at the root Dockerfile where the actual source commands are located.

Applied to files:

  • Dockerfile
📚 Learning: 2025-09-20T14:13:33.635Z
Learnt from: adamoutler
Repo: jokob-sk/NetAlertX PR: 1184
File: .devcontainer/Dockerfile:11-16
Timestamp: 2025-09-20T14:13:33.635Z
Learning: In the NetAlertX devcontainer setup, the `python -m venv /opt/venv` command works successfully on Alpine 3.22 despite the typical Alpine behavior of not providing a /usr/bin/python symlink by default. The build completes successfully and pytest runs without issues.

Applied to files:

  • Dockerfile
📚 Learning: 2025-09-20T14:13:33.635Z
Learnt from: adamoutler
Repo: jokob-sk/NetAlertX PR: 1184
File: .devcontainer/Dockerfile:11-16
Timestamp: 2025-09-20T14:13:33.635Z
Learning: In the NetAlertX repository with Alpine 3.22 base image, the `python -m venv` command works correctly in the devcontainer setup, likely due to symlink creation in the root Dockerfile that makes `python` available as an alias to `python3`.

Applied to files:

  • Dockerfile
📚 Learning: 2025-12-08T01:59:00.903Z
Learnt from: CR
Repo: jokob-sk/NetAlertX PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-08T01:59:00.903Z
Learning: Applies to **/*.py : Use environment variables (`NETALERTX_DB`, `NETALERTX_LOG`, etc.) everywhere instead of hardcoding paths like `/data/db` or relative paths.

Applied to files:

  • Dockerfile
📚 Learning: 2025-09-20T14:09:29.159Z
Learnt from: adamoutler
Repo: jokob-sk/NetAlertX PR: 1184
File: .devcontainer/scripts/setup.sh:103-116
Timestamp: 2025-09-20T14:09:29.159Z
Learning: In NetAlertX devcontainer setup, the netalertx user has write permissions to /var/log/nginx/ directory as it's explicitly chowned to netalertx:www-data in the Dockerfile, so setup.sh can write to nginx log files without sudo.

Applied to files:

  • Dockerfile
📚 Learning: 2025-10-26T17:09:18.621Z
Learnt from: adamoutler
Repo: jokob-sk/NetAlertX PR: 1235
File: .devcontainer/scripts/setup.sh:146-148
Timestamp: 2025-10-26T17:09:18.621Z
Learning: In `.devcontainer/scripts/setup.sh` and other devcontainer setup scripts for NetAlertX, chmod 666 on /var/run/docker.sock is acceptable because devcontainer environments are single-user development contexts where convenience can take priority over strict permission hardening.

Applied to files:

  • Dockerfile
🧬 Code graph analysis (1)
front/plugins/internet_speedtest/script.py (3)
server/logger.py (1)
  • mylog (79-84)
front/plugins/plugin_helper.py (2)
  • Plugin_Objects (251-310)
  • add_object (262-292)
server/utils/datetime_utils.py (1)
  • timeNowDB (41-61)
🪛 Ruff (0.14.10)
front/plugins/internet_speedtest/script.py

58-58: subprocess call: check for execution of untrusted input

(S603)


86-86: Do not catch blind exception: Exception

(BLE001)


87-87: Use explicit conversion flag

Replace with conversion flag

(RUF010)

🔇 Additional comments (3)
Dockerfile (1)

135-135: LGTM! Appropriate runtime dependency for glibc compatibility.

Adding gcompat enables running the glibc-linked Ookla binary on Alpine's musl libc without switching to a heavier base image.

front/plugins/internet_speedtest/script.py (2)

5-6: LGTM! Required imports for native binary invocation.

The subprocess and json imports are necessary for calling the native speedtest binary and parsing its JSON output.


32-49: LGTM! Clean integration with Plugin_Objects.

The main function correctly consumes the new speed_data structure and maps download/upload speeds and webhook payload to the appropriate watched fields.

@amir0ff amir0ff force-pushed the optimize-speedtest-native branch from 85f0afb to 1d8f03c Compare December 26, 2025 12:21
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
front/plugins/internet_speedtest/script.py (2)

67-69: Use 'none' log level for error conditions.

Per coding guidelines, mylog('none', ...) should be used for important messages that should always appear, such as exceptions and errors. The binary failure is an error condition worth logging at the highest priority.

🔎 Proposed fix
         if result.returncode != 0:
-            mylog('verbose', [f"[INTRSPD] Native binary failed: {result.stderr}"])
+            mylog('none', [f"[INTRSPD] Native binary failed: {result.stderr}"])
             return {'down': 0.0, 'up': 0.0, 'json': '{}'}

96-104: Consider using f-string conversion flags for consistency.

The static analysis suggests using explicit conversion flags ({e!s}) instead of str(e) for cleaner f-string syntax. This is a minor style improvement.

🔎 Proposed fix
     except json.JSONDecodeError as e:
-        mylog('none', [f"[INTRSPD] Failed to parse JSON output: {str(e)}"])
+        mylog('none', [f"[INTRSPD] Failed to parse JSON output: {e!s}"])
         return {'down': 0.0, 'up': 0.0, 'json': json.dumps({"error": "json_parse_error"})}
     except subprocess.SubprocessError as e:
-        mylog('none', [f"[INTRSPD] Subprocess error: {str(e)}"])
+        mylog('none', [f"[INTRSPD] Subprocess error: {e!s}"])
         return {'down': 0.0, 'up': 0.0, 'json': json.dumps({"error": str(e)})}
     except Exception as e:
-        mylog('none', [f"[INTRSPD] Unexpected error: {str(e)}"])
+        mylog('none', [f"[INTRSPD] Unexpected error: {e!s}"])
         return {'down': 0.0, 'up': 0.0, 'json': json.dumps({"error": str(e)})}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 85f0afb and 1d8f03c.

📒 Files selected for processing (6)
  • Dockerfile
  • front/plugins/internet_speedtest/config.json
  • front/plugins/internet_speedtest/script.py
  • install/proxmox/requirements.txt
  • install/ubuntu24/requirements.txt
  • requirements.txt
💤 Files with no reviewable changes (3)
  • requirements.txt
  • install/proxmox/requirements.txt
  • install/ubuntu24/requirements.txt
🚧 Files skipped from review as they are similar to previous changes (1)
  • Dockerfile
🧰 Additional context used
📓 Path-based instructions (3)
front/plugins/*/config.json

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

front/plugins/*/config.json: Plugin manifest lives at front/plugins/<code_name>/config.json; code_name must equal folder name, and unique_prefix drives settings and filenames (e.g., ARPSCAN).
Control plugins via settings: <PREF>_RUN (phase), <PREF>_RUN_SCHD (cron-like), <PREF>_CMD (script path), <PREF>_RUN_TIMEOUT, <PREF>_WATCH (diff columns).

Files:

  • front/plugins/internet_speedtest/config.json
**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.py: Use mylog(level, [message]) for logging; levels are: none/minimal/verbose/debug/trace. Use none for most important messages that should always appear, such as exceptions.
Always set explicit timeouts on subprocess calls. Default to 60s minimum unless plugin config specifies otherwise. Nested subprocess calls need their own timeout.
Never hardcode ports or secrets; always use get_setting_value() to retrieve configuration values.
Use environment variables (NETALERTX_DB, NETALERTX_LOG, etc.) everywhere instead of hardcoding paths like /data/db or relative paths.
Use helper.py functions (timeNowDB, normalize_mac, sanitizers) for time/MAC/string operations. Validate MACs before DB writes.
Add/modify settings via ccd() in server/initialise.py or per-plugin manifest. Define config once and read it via helpers everywhere.
Always leave logging enabled. If there is a possibility it will be difficult to debug with current logging, add more logging.

Files:

  • front/plugins/internet_speedtest/script.py
front/plugins/*/script.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

front/plugins/*/script.py: For plugins: define database_column_definitions when creating/updating devices; watched fields trigger notifications.
Plugin scripts must write results to /tmp/log/plugins/last_result.<PREF>.log with pipe-delimited format (9 required cols + optional 4). Use Plugin_Objects to sanitize text and normalize MACs, then call write_result_file() exactly once at the end.
Collect plugin results with Plugin_Objects.add_object(...) during processing and call plugin_objects.write_result_file() exactly once at the end of the script. Do not write ad-hoc files for results.
Always check relevant logs first. Use logging as shown in other plugins. Prefer to log a brief summary before writing results to aid troubleshooting; keep logs concise at info level and use verbose or debug for extra context.

Files:

  • front/plugins/internet_speedtest/script.py
🧠 Learnings (2)
📚 Learning: 2025-12-08T01:59:00.903Z
Learnt from: CR
Repo: jokob-sk/NetAlertX PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-08T01:59:00.903Z
Learning: Applies to front/plugins/*/config.json : Control plugins via settings: `<PREF>_RUN` (phase), `<PREF>_RUN_SCHD` (cron-like), `<PREF>_CMD` (script path), `<PREF>_RUN_TIMEOUT`, `<PREF>_WATCH` (diff columns).

Applied to files:

  • front/plugins/internet_speedtest/config.json
📚 Learning: 2025-12-08T01:59:00.903Z
Learnt from: CR
Repo: jokob-sk/NetAlertX PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-08T01:59:00.903Z
Learning: Applies to **/*.py : Always set explicit timeouts on subprocess calls. Default to 60s minimum unless plugin config specifies otherwise. Nested subprocess calls need their own timeout.

Applied to files:

  • front/plugins/internet_speedtest/script.py
🧬 Code graph analysis (1)
front/plugins/internet_speedtest/script.py (4)
server/logger.py (1)
  • mylog (79-84)
front/plugins/plugin_helper.py (2)
  • Plugin_Objects (251-310)
  • add_object (262-292)
server/utils/datetime_utils.py (1)
  • timeNowDB (41-61)
server/helper.py (1)
  • get_setting_value (200-257)
🪛 Ruff (0.14.10)
front/plugins/internet_speedtest/script.py

65-65: subprocess call: check for execution of untrusted input

(S603)


97-97: Use explicit conversion flag

Replace with conversion flag

(RUF010)


100-100: Use explicit conversion flag

Replace with conversion flag

(RUF010)


102-102: Do not catch blind exception: Exception

(BLE001)


103-103: Use explicit conversion flag

Replace with conversion flag

(RUF010)

🔇 Additional comments (4)
front/plugins/internet_speedtest/config.json (1)

557-593: LGTM - New TIMEOUT setting is well-structured.

The setting correctly defines the configurable timeout for the native speedtest binary, with appropriate localization and a sensible default of 60 seconds. The minimum enforcement is handled in the script code.

Minor observation: Line 592 contains an extra blank line before the closing brace, which is atypical JSON formatting but syntactically valid.

front/plugins/internet_speedtest/script.py (3)

5-6: LGTM - Clean import updates for native binary approach.

The replacement of the speedtest-cli library with subprocess and json imports aligns with the PR objective to use the native Ookla binary.


32-49: LGTM - main() follows plugin guidelines.

The function correctly uses Plugin_Objects, maps the speedtest results to the appropriate watched values as documented in the PR objectives, and calls write_result_file() exactly once at the end per the coding guidelines.


52-65: Timeout handling and subprocess call are well-implemented.

The timeout configuration with minimum 60-second enforcement addresses the previous review concern. The explicit timeout parameter in subprocess.run() follows the coding guidelines requiring explicit timeouts on subprocess calls.

Note: The static analysis warning S603 about "untrusted input" is a false positive since the command is entirely hardcoded with no user-controlled components.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
front/plugins/internet_speedtest/config.json (1)

627-636: Update outdated description for Watched_Value3.

The description states Watched_Value3 is "unused", but according to the PR objectives and script implementation, it now contains the optimized JSON payload for webhooks and n8n consumers (download/upload bandwidth, ping, server, timestamp).

🔎 Proposed fix to update the description
       "description": [
         {
           "language_code": "en_us",
-          "string": "Send a notification if selected values change. Use <code>CTRL + Click</code> to select/deselect. <ul> <li><code>Watched_Value1</code> is Download speed (not recommended)</li><li><code>Watched_Value2</code> is Upload speed (not recommended)</li><li><code>Watched_Value3</code> unused </li><li><code>Watched_Value4</code> unused </li></ul>"
+          "string": "Send a notification if selected values change. Use <code>CTRL + Click</code> to select/deselect. <ul> <li><code>Watched_Value1</code> is Download speed (not recommended)</li><li><code>Watched_Value2</code> is Upload speed (not recommended)</li><li><code>Watched_Value3</code> is JSON payload for webhooks </li><li><code>Watched_Value4</code> unused </li></ul>"
         },
         {
           "language_code": "de_de",
-          "string": "Sende eine Benachrichtigung, wenn ein ausgwählter Wert sich ändert. <code>STRG + klicken</code> zum aus-/abwählen. <ul> <li><code>Watched_Value1</code> ist die Download-Geschwindigkeit (nicht empfohlen)</li><li><code>Watched_Value2</code> ist die Upload-Geschwindigkeit (nicht empfohlen)</li><li><code>Watched_Value3</code> ist nicht in Verwendung </li><li><code>Watched_Value4</code> ist nicht in Verwendung </li></ul>"
+          "string": "Sende eine Benachrichtigung, wenn ein ausgwählter Wert sich ändert. <code>STRG + klicken</code> zum aus-/abwählen. <ul> <li><code>Watched_Value1</code> ist die Download-Geschwindigkeit (nicht empfohlen)</li><li><code>Watched_Value2</code> ist die Upload-Geschwindigkeit (nicht empfohlen)</li><li><code>Watched_Value3</code> ist JSON-Payload für Webhooks </li><li><code>Watched_Value4</code> ist nicht in Verwendung </li></ul>"
         }
       ]
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1d8f03c and 77ee815.

📒 Files selected for processing (2)
  • front/plugins/internet_speedtest/config.json
  • front/plugins/internet_speedtest/script.py
🧰 Additional context used
📓 Path-based instructions (3)
front/plugins/*/config.json

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

front/plugins/*/config.json: Plugin manifest lives at front/plugins/<code_name>/config.json; code_name must equal folder name, and unique_prefix drives settings and filenames (e.g., ARPSCAN).
Control plugins via settings: <PREF>_RUN (phase), <PREF>_RUN_SCHD (cron-like), <PREF>_CMD (script path), <PREF>_RUN_TIMEOUT, <PREF>_WATCH (diff columns).

Files:

  • front/plugins/internet_speedtest/config.json
**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.py: Use mylog(level, [message]) for logging; levels are: none/minimal/verbose/debug/trace. Use none for most important messages that should always appear, such as exceptions.
Always set explicit timeouts on subprocess calls. Default to 60s minimum unless plugin config specifies otherwise. Nested subprocess calls need their own timeout.
Never hardcode ports or secrets; always use get_setting_value() to retrieve configuration values.
Use environment variables (NETALERTX_DB, NETALERTX_LOG, etc.) everywhere instead of hardcoding paths like /data/db or relative paths.
Use helper.py functions (timeNowDB, normalize_mac, sanitizers) for time/MAC/string operations. Validate MACs before DB writes.
Add/modify settings via ccd() in server/initialise.py or per-plugin manifest. Define config once and read it via helpers everywhere.
Always leave logging enabled. If there is a possibility it will be difficult to debug with current logging, add more logging.

Files:

  • front/plugins/internet_speedtest/script.py
front/plugins/*/script.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

front/plugins/*/script.py: For plugins: define database_column_definitions when creating/updating devices; watched fields trigger notifications.
Plugin scripts must write results to /tmp/log/plugins/last_result.<PREF>.log with pipe-delimited format (9 required cols + optional 4). Use Plugin_Objects to sanitize text and normalize MACs, then call write_result_file() exactly once at the end.
Collect plugin results with Plugin_Objects.add_object(...) during processing and call plugin_objects.write_result_file() exactly once at the end of the script. Do not write ad-hoc files for results.
Always check relevant logs first. Use logging as shown in other plugins. Prefer to log a brief summary before writing results to aid troubleshooting; keep logs concise at info level and use verbose or debug for extra context.

Files:

  • front/plugins/internet_speedtest/script.py
🧠 Learnings (2)
📚 Learning: 2025-12-08T01:59:00.903Z
Learnt from: CR
Repo: jokob-sk/NetAlertX PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-08T01:59:00.903Z
Learning: Applies to front/plugins/*/config.json : Control plugins via settings: `<PREF>_RUN` (phase), `<PREF>_RUN_SCHD` (cron-like), `<PREF>_CMD` (script path), `<PREF>_RUN_TIMEOUT`, `<PREF>_WATCH` (diff columns).

Applied to files:

  • front/plugins/internet_speedtest/config.json
📚 Learning: 2025-12-08T01:59:00.903Z
Learnt from: CR
Repo: jokob-sk/NetAlertX PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-08T01:59:00.903Z
Learning: Applies to **/*.py : Always set explicit timeouts on subprocess calls. Default to 60s minimum unless plugin config specifies otherwise. Nested subprocess calls need their own timeout.

Applied to files:

  • front/plugins/internet_speedtest/script.py
🧬 Code graph analysis (1)
front/plugins/internet_speedtest/script.py (4)
server/logger.py (1)
  • mylog (79-84)
front/plugins/plugin_helper.py (2)
  • Plugin_Objects (251-310)
  • add_object (262-292)
server/utils/datetime_utils.py (1)
  • timeNowDB (41-61)
server/helper.py (1)
  • get_setting_value (200-257)
🪛 Ruff (0.14.10)
front/plugins/internet_speedtest/script.py

65-65: subprocess call: check for execution of untrusted input

(S603)


106-106: Do not catch blind exception: Exception

(BLE001)

🔇 Additional comments (4)
front/plugins/internet_speedtest/script.py (4)

32-50: LGTM! Clean integration with new data structure.

The main() function correctly consumes the new structured return format from run_speedtest(), mapping the down, up, and json keys to the appropriate watched values. The log message clearly indicates the native binary optimization, and the Plugin_Objects pattern is used correctly per coding guidelines.


52-62: Excellent timeout implementation per coding guidelines.

The timeout configuration correctly:

  • Uses get_setting_value() to retrieve the configurable timeout
  • Enforces the minimum 60-second requirement as mandated by coding guidelines
  • Handles type conversion errors gracefully with appropriate fallback
  • Addresses the previous review feedback about explicit timeout handling

The native binary path and command flags are appropriate for the controlled container environment.

As per coding guidelines:

Always set explicit timeouts on subprocess calls. Default to 60s minimum unless plugin config specifies otherwise.


63-95: Robust implementation with comprehensive data processing.

The success path implementation is excellent:

  • Subprocess execution (line 65): Correctly includes timeout, capture_output, and text parameters. The static analysis warning about untrusted input (S603) is a false positive—the binary path and flags are hardcoded and safe.

  • Error detection (lines 67-69): Non-zero return codes are properly logged at the none level (most important messages) and return safe fallback values.

  • JSON parsing (lines 71-87): The nested try-except structure correctly addresses the previous review feedback about KeyError handling. Catches KeyError, ValueError, and TypeError to handle unexpected JSON structures gracefully.

  • Data transformations (lines 74-75, 78-84): Bandwidth conversions are mathematically correct (bytes/sec × 8 ÷ 10⁶ = Mbps), and the webhook payload includes all relevant fields (bandwidth in bits/sec as integers, ping, server, timestamp).

  • Error diagnostics (line 86): Including raw output in error payloads aids debugging without security concerns (speedtest output is benign).


97-108: Comprehensive error handling with appropriate fail-safe.

The error handling is well-structured with specific exceptions for different failure modes:

  • TimeoutExpired (lines 97-99): Handles subprocess timeouts with clear messaging
  • JSONDecodeError (lines 100-102): Specific to JSON parsing failures
  • SubprocessError (lines 103-105): Catches subprocess-specific issues

The final generic Exception catch (lines 106-108) is flagged by the linter (BLE001) but is appropriate in this context because:

  • It serves as a terminal fail-safe to prevent plugin crashes
  • The exception is fully logged with {e!s} conversion for diagnostics
  • Returns a safe, consistent structure that won't break downstream processing
  • All error cases use the none log level as mandated by coding guidelines for exceptions

All error paths maintain consistent return structure with descriptive error information preserved in the json field for webhook consumers.

As per coding guidelines:

Use none for most important messages that should always appear, such as exceptions.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 77ee815 and 0e817d9.

📒 Files selected for processing (1)
  • front/plugins/internet_speedtest/config.json
🧰 Additional context used
📓 Path-based instructions (1)
front/plugins/*/config.json

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

front/plugins/*/config.json: Plugin manifest lives at front/plugins/<code_name>/config.json; code_name must equal folder name, and unique_prefix drives settings and filenames (e.g., ARPSCAN).
Control plugins via settings: <PREF>_RUN (phase), <PREF>_RUN_SCHD (cron-like), <PREF>_CMD (script path), <PREF>_RUN_TIMEOUT, <PREF>_WATCH (diff columns).

Files:

  • front/plugins/internet_speedtest/config.json
🧠 Learnings (1)
📚 Learning: 2025-12-08T01:59:00.903Z
Learnt from: CR
Repo: jokob-sk/NetAlertX PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-08T01:59:00.903Z
Learning: Applies to front/plugins/*/config.json : Control plugins via settings: `<PREF>_RUN` (phase), `<PREF>_RUN_SCHD` (cron-like), `<PREF>_CMD` (script path), `<PREF>_RUN_TIMEOUT`, `<PREF>_WATCH` (diff columns).

Applied to files:

  • front/plugins/internet_speedtest/config.json
🔇 Additional comments (1)
front/plugins/internet_speedtest/config.json (1)

557-600: Spanish localization has been added.

The past review comment flagged missing Spanish localization for this TIMEOUT setting. The current code shows that Spanish (es_es) localization has been added for both the name (lines 578-579) and description (lines 592-593), resolving the previous concern.

@jokob-sk
Copy link
Collaborator

Hi @amir0ff ,

Thanks a lot for the PR.

I'm on the fence of accepting it as it adds unnecessary maintenance overhead, complicates teh isntallation scripts further, it breaks the /speedtest API endpoint (See failed unit tests) and adds only small improvements, which I didn't see any issues reported about.

The preference is to use python packages which are easier to include and maintain in the long term than binaries.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
server/api_server/nettools_endpoint.py (1)

119-182: Major: Missing logging for debugging and monitoring.

The speedtest() function lacks logging via mylog(), making it difficult to debug failures or track usage. Per coding guidelines: "Use mylog(level, [message]) for logging" and "Always leave logging enabled. If there is a possibility it will be difficult to debug with current logging, add more logging."

Consider adding logging for:

  • Binary detection result (native vs. python)
  • Speedtest execution start/completion
  • JSON parsing failures (currently silent fallback)
  • Any errors before returning 503/500 responses
🔎 Example logging additions
# After line 135 (detection)
mylog('verbose', f'[Speedtest] Using {"native Ookla" if is_native else "python-based"} binary at {SPEEDTEST_CLI_PATH}')

# After line 141 (before execution)
mylog('debug', f'[Speedtest] Executing: {" ".join(cmd)}')

# After line 147 (success)
mylog('verbose', f'[Speedtest] Completed successfully')

# In except block at line 158 (JSON parse failure)
mylog('minimal', f'[Speedtest] JSON parse failed, using raw output: {str(e)}')

# In error handlers (lines 165, 174)
mylog('none', f'[Speedtest] Failed: {str(e)}')
🧹 Nitpick comments (1)
server/api_server/nettools_endpoint.py (1)

135-135: Fragile binary detection heuristic.

Using os.path.basename(SPEEDTEST_CLI_PATH) == "speedtest" to detect the native binary is brittle. A renamed or symlinked python speedtest-cli binary named "speedtest" would be misidentified, causing incorrect argument usage and JSON parsing.

🔎 More robust detection approach

Consider detecting the binary type by inspecting its behavior or metadata:

# Option 1: Check version output format
def _is_native_ookla(path):
    """Detect if the binary is native Ookla by checking version output."""
    try:
        result = subprocess.run(
            [path, "--version"],
            capture_output=True,
            text=True,
            timeout=5,
        )
        # Native Ookla returns "Speedtest by Ookla"
        return "Ookla" in result.stdout
    except Exception:
        return False

# Then use it:
is_native = _is_native_ookla(SPEEDTEST_CLI_PATH)

Alternatively, check for the presence of --format flag in help output, or cache the detection result during module initialization rather than relying on filename alone.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dae561f and 523d595.

📒 Files selected for processing (1)
  • server/api_server/nettools_endpoint.py
🧰 Additional context used
📓 Path-based instructions (3)
**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.py: Use mylog(level, [message]) for logging; levels are: none/minimal/verbose/debug/trace. Use none for most important messages that should always appear, such as exceptions.
Always set explicit timeouts on subprocess calls. Default to 60s minimum unless plugin config specifies otherwise. Nested subprocess calls need their own timeout.
Never hardcode ports or secrets; always use get_setting_value() to retrieve configuration values.
Use environment variables (NETALERTX_DB, NETALERTX_LOG, etc.) everywhere instead of hardcoding paths like /data/db or relative paths.
Use helper.py functions (timeNowDB, normalize_mac, sanitizers) for time/MAC/string operations. Validate MACs before DB writes.
Add/modify settings via ccd() in server/initialise.py or per-plugin manifest. Define config once and read it via helpers everywhere.
Always leave logging enabled. If there is a possibility it will be difficult to debug with current logging, add more logging.

Files:

  • server/api_server/nettools_endpoint.py
server/api_server/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

server/api_server/*.py: All API responses must return "success":<False|True> and if False must include an "error" message, e.g. {"success": False, "error": "No stored open ports for Device"}
All API routes must expect header Authorization: Bearer <API_TOKEN> retrieved via get_setting_value('API_TOKEN') for authorization.

Files:

  • server/api_server/nettools_endpoint.py
server/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Prefer server/db/db_helper.py functions (e.g., get_table_json, device condition helpers) over raw SQL in new code paths.

Files:

  • server/api_server/nettools_endpoint.py
🪛 Ruff (0.14.10)
server/api_server/nettools_endpoint.py

25-28: Avoid specifying long messages outside the exception class

(TRY003)


142-142: subprocess call: check for execution of untrusted input

(S603)

🔇 Additional comments (1)
server/api_server/nettools_endpoint.py (1)

17-33: LGTM! Binary resolution logic is sound.

The preference for the native Ookla binary with fallback to speedtest-cli is correctly implemented using shutil.which(). Error messages are clear and the fail-fast approach at module load is appropriate.

@amir0ff
Copy link
Contributor Author

amir0ff commented Dec 27, 2025

Hi @amir0ff ,

Thanks a lot for the PR.

I'm on the fence of accepting it as it adds unnecessary maintenance overhead, complicates teh isntallation scripts further, it breaks the /speedtest API endpoint (See failed unit tests) and adds only small improvements, which I didn't see any issues reported about.

The preference is to use python packages which are easier to include and maintain in the long term than binaries.

Hi @jokob-sk,

I appreciate the candid feedback. I completely understand your concerns.

The primary motivation for this change is that the current Python package (speedtest-cli) is essentially 'legacy'—it's single-threaded and consistently bottlenecks on connections over 200-300Mbps. For users with Gigabit fiber (like me), the current implementation reports significantly inaccurate data.

To address your concerns, I’d like to propose a refactored approach:

  1. Optional Binary: I will modify the PR so the Ookla binary is not required. I'll implement a fallback: if the binary isn't found, the code will automatically use the existing Python package. This ensures the /speedtest API and unit tests continue to pass in standard environments.

  2. No Install Overhead: I won't touch the installation scripts. Instead, I’ll add a 'manual path' setting. Power users who want the accuracy can simply map the binary into their container (like I did with mine for now), and NetAlertX will detect and use it.

  3. API Fix: I'll refactor the endpoint logic to ensure the data structure returned to the API remains consistent regardless of which 'engine' performed the test.

Would you be open to reviewing a revised version?"

P.S. I recently moved away from my proprietary Fingbox for this project! I’m really invested in making NetAlertX the best open-source alternative for high-speed home networks 💪🏼

@jokob-sk
Copy link
Collaborator

Hi @amir0ff,

Thank you for understanding.

The proposed solution sounds much more acceptable, if there is no better suited python library. You could theoretically write one (e. G. I created a lightweight unifi-sm-api wrapper, it's pretty straightforward https://github.com/jokob-sk/unifi-sm-api ) if that's possible, but unsure since we are dealing with proprietary binaries. Still, your outlined approach sounds like a good compromise.

Glad to hear that you find the project useful and looking forward to your contributions 🙏

@jokob-sk
Copy link
Collaborator

Ideally the plugin should call the API endpoint - I'm in the middle of refactoring the API layer and doing a clean up of the messy parts of the code.

@amir0ff amir0ff force-pushed the optimize-speedtest-native branch from 523d595 to ba8e3c6 Compare December 28, 2025 16:00
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
front/plugins/internet_speedtest/script.py (1)

5-6: Remove unused subprocess import.

The subprocess module is no longer used directly in this file since speedtest execution is now delegated to run_speedtest_core(). The json import is still needed.

🔎 Proposed fix
 import os
 import sys
-import subprocess
 import json
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 523d595 and ba8e3c6.

📒 Files selected for processing (7)
  • Dockerfile
  • front/plugins/internet_speedtest/config.json
  • front/plugins/internet_speedtest/script.py
  • install/proxmox/requirements.txt
  • install/ubuntu24/requirements.txt
  • requirements.txt
  • server/api_server/nettools_endpoint.py
🚧 Files skipped from review as they are similar to previous changes (4)
  • front/plugins/internet_speedtest/config.json
  • install/ubuntu24/requirements.txt
  • install/proxmox/requirements.txt
  • Dockerfile
🧰 Additional context used
📓 Path-based instructions (4)
**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.py: Use mylog(level, [message]) for logging; levels are: none/minimal/verbose/debug/trace. Use none for most important messages that should always appear, such as exceptions.
Always set explicit timeouts on subprocess calls. Default to 60s minimum unless plugin config specifies otherwise. Nested subprocess calls need their own timeout.
Never hardcode ports or secrets; always use get_setting_value() to retrieve configuration values.
Use environment variables (NETALERTX_DB, NETALERTX_LOG, etc.) everywhere instead of hardcoding paths like /data/db or relative paths.
Use helper.py functions (timeNowDB, normalize_mac, sanitizers) for time/MAC/string operations. Validate MACs before DB writes.
Add/modify settings via ccd() in server/initialise.py or per-plugin manifest. Define config once and read it via helpers everywhere.
Always leave logging enabled. If there is a possibility it will be difficult to debug with current logging, add more logging.

Files:

  • front/plugins/internet_speedtest/script.py
  • server/api_server/nettools_endpoint.py
front/plugins/*/script.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

front/plugins/*/script.py: For plugins: define database_column_definitions when creating/updating devices; watched fields trigger notifications.
Plugin scripts must write results to /tmp/log/plugins/last_result.<PREF>.log with pipe-delimited format (9 required cols + optional 4). Use Plugin_Objects to sanitize text and normalize MACs, then call write_result_file() exactly once at the end.
Collect plugin results with Plugin_Objects.add_object(...) during processing and call plugin_objects.write_result_file() exactly once at the end of the script. Do not write ad-hoc files for results.
Always check relevant logs first. Use logging as shown in other plugins. Prefer to log a brief summary before writing results to aid troubleshooting; keep logs concise at info level and use verbose or debug for extra context.

Files:

  • front/plugins/internet_speedtest/script.py
server/api_server/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

server/api_server/*.py: All API responses must return "success":<False|True> and if False must include an "error" message, e.g. {"success": False, "error": "No stored open ports for Device"}
All API routes must expect header Authorization: Bearer <API_TOKEN> retrieved via get_setting_value('API_TOKEN') for authorization.

Files:

  • server/api_server/nettools_endpoint.py
server/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Prefer server/db/db_helper.py functions (e.g., get_table_json, device condition helpers) over raw SQL in new code paths.

Files:

  • server/api_server/nettools_endpoint.py
🧠 Learnings (1)
📚 Learning: 2025-12-08T01:59:00.903Z
Learnt from: CR
Repo: jokob-sk/NetAlertX PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-08T01:59:00.903Z
Learning: Applies to **/*.py : Always set explicit timeouts on subprocess calls. Default to 60s minimum unless plugin config specifies otherwise. Nested subprocess calls need their own timeout.

Applied to files:

  • front/plugins/internet_speedtest/script.py
  • server/api_server/nettools_endpoint.py
🪛 Ruff (0.14.10)
front/plugins/internet_speedtest/script.py

20-20: Unused noqa directive (non-enabled: E402)

Remove unused noqa directive

(RUF100)

server/api_server/nettools_endpoint.py

25-28: Avoid specifying long messages outside the exception class

(TRY003)


140-140: subprocess call: check for execution of untrusted input

(S603)


157-157: Consider moving this statement to an else block

(TRY300)


173-173: Do not catch blind exception: Exception

(BLE001)

🔇 Additional comments (6)
requirements.txt (1)

19-20: LGTM — cosmetic reordering only.

The speedtest-cli dependency is retained for fallback support when the native Ookla binary is unavailable, which aligns with the PR discussion about maintaining backward compatibility.

server/api_server/nettools_endpoint.py (3)

16-33: Well-structured binary resolution with proper fallback.

The preference for native Ookla binary with fallback to speedtest-cli is cleanly implemented. The executability check prevents cryptic errors at runtime.


119-179: Core speedtest logic is well-structured with proper timeout and error handling.

Good improvements:

  • Timeout is now properly set (addressing previous review feedback)
  • Structured error responses with appropriate HTTP status codes
  • Native binary detection and backward-compatible output formatting
  • KeyError/JSONDecodeError handled for malformed native output

The catch-all Exception at line 173 is acceptable as a safety net, though it could be narrowed to OSError if you want to be more specific.


182-189: Clean API endpoint wrapper.

The delegation to run_speedtest_core() and status code extraction pattern is clean. Popping status_code before returning prevents internal fields from leaking to API consumers.

front/plugins/internet_speedtest/script.py (2)

34-51: LGTM — clean plugin main function.

The plugin correctly follows the pattern: creates Plugin_Objects, collects results via run_speedtest(), maps to watched fields, and calls write_result_file() exactly once at the end.


54-117: Well-implemented speedtest wrapper with proper error handling.

Good implementation:

  • Timeout enforcement with 60s minimum (lines 57-60) per coding guidelines
  • Defensive parsing with try/except for speed extraction (lines 74-82)
  • KeyError/ValueError/TypeError handling for native payload construction (line 98)
  • Consistent return structure with fallback values

The parsing of output lines (e.g., "Download: 100.5 Mbit/s") relies on specific formatting from run_speedtest_core. This coupling is acceptable since both are under the same codebase control.

@amir0ff amir0ff force-pushed the optimize-speedtest-native branch from ba8e3c6 to 1d73075 Compare December 28, 2025 16:07
@amir0ff amir0ff changed the title Optimize INTRSPD plugin with native Ookla Speedtest binary Hybrid Speedtest implementation (Python-First with Native Opt-in) Dec 28, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
front/plugins/internet_speedtest/script.py (1)

19-19: Remove unused noqa directive.

Static analysis indicates the noqa: E402 directive on this line is not needed as the linting rule isn't triggered. Keeping unused directives can mask future issues.

🔎 Proposed fix
-from api_server.nettools_endpoint import run_speedtest_core  # noqa: E402 [flake8 lint suppression]
+from api_server.nettools_endpoint import run_speedtest_core
server/api_server/nettools_endpoint.py (1)

50-51: Pre-existing: Other subprocess calls in this file lack timeouts.

While not introduced by this PR, several other subprocess calls in this file (wakeonlan, traceroute, nslookup, nmap_scan, internet_info, network_interfaces) are missing explicit timeouts. Per coding guidelines, all subprocess calls should have a minimum 60-second timeout to prevent indefinite hangs.

Consider addressing these in a follow-up commit to align the entire file with the timeout requirements.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ba8e3c6 and 1d73075.

📒 Files selected for processing (6)
  • front/plugins/internet_speedtest/config.json
  • front/plugins/internet_speedtest/script.py
  • install/proxmox/requirements.txt
  • install/ubuntu24/requirements.txt
  • requirements.txt
  • server/api_server/nettools_endpoint.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • install/proxmox/requirements.txt
  • front/plugins/internet_speedtest/config.json
  • install/ubuntu24/requirements.txt
🧰 Additional context used
📓 Path-based instructions (4)
**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.py: Use mylog(level, [message]) for logging; levels are: none/minimal/verbose/debug/trace. Use none for most important messages that should always appear, such as exceptions.
Always set explicit timeouts on subprocess calls. Default to 60s minimum unless plugin config specifies otherwise. Nested subprocess calls need their own timeout.
Never hardcode ports or secrets; always use get_setting_value() to retrieve configuration values.
Use environment variables (NETALERTX_DB, NETALERTX_LOG, etc.) everywhere instead of hardcoding paths like /data/db or relative paths.
Use helper.py functions (timeNowDB, normalize_mac, sanitizers) for time/MAC/string operations. Validate MACs before DB writes.
Add/modify settings via ccd() in server/initialise.py or per-plugin manifest. Define config once and read it via helpers everywhere.
Always leave logging enabled. If there is a possibility it will be difficult to debug with current logging, add more logging.

Files:

  • server/api_server/nettools_endpoint.py
  • front/plugins/internet_speedtest/script.py
server/api_server/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

server/api_server/*.py: All API responses must return "success":<False|True> and if False must include an "error" message, e.g. {"success": False, "error": "No stored open ports for Device"}
All API routes must expect header Authorization: Bearer <API_TOKEN> retrieved via get_setting_value('API_TOKEN') for authorization.

Files:

  • server/api_server/nettools_endpoint.py
server/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Prefer server/db/db_helper.py functions (e.g., get_table_json, device condition helpers) over raw SQL in new code paths.

Files:

  • server/api_server/nettools_endpoint.py
front/plugins/*/script.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

front/plugins/*/script.py: For plugins: define database_column_definitions when creating/updating devices; watched fields trigger notifications.
Plugin scripts must write results to /tmp/log/plugins/last_result.<PREF>.log with pipe-delimited format (9 required cols + optional 4). Use Plugin_Objects to sanitize text and normalize MACs, then call write_result_file() exactly once at the end.
Collect plugin results with Plugin_Objects.add_object(...) during processing and call plugin_objects.write_result_file() exactly once at the end of the script. Do not write ad-hoc files for results.
Always check relevant logs first. Use logging as shown in other plugins. Prefer to log a brief summary before writing results to aid troubleshooting; keep logs concise at info level and use verbose or debug for extra context.

Files:

  • front/plugins/internet_speedtest/script.py
🧠 Learnings (1)
📚 Learning: 2025-12-08T01:59:00.903Z
Learnt from: CR
Repo: jokob-sk/NetAlertX PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-08T01:59:00.903Z
Learning: Applies to **/*.py : Always set explicit timeouts on subprocess calls. Default to 60s minimum unless plugin config specifies otherwise. Nested subprocess calls need their own timeout.

Applied to files:

  • server/api_server/nettools_endpoint.py
  • front/plugins/internet_speedtest/script.py
🧬 Code graph analysis (1)
front/plugins/internet_speedtest/script.py (4)
server/api_server/nettools_endpoint.py (1)
  • run_speedtest_core (119-179)
front/plugins/plugin_helper.py (2)
  • Plugin_Objects (251-310)
  • add_object (262-292)
server/utils/datetime_utils.py (1)
  • timeNowDB (41-61)
server/helper.py (1)
  • get_setting_value (200-257)
🪛 Ruff (0.14.10)
server/api_server/nettools_endpoint.py

25-28: Avoid specifying long messages outside the exception class

(TRY003)


140-140: subprocess call: check for execution of untrusted input

(S603)


157-157: Consider moving this statement to an else block

(TRY300)


173-173: Do not catch blind exception: Exception

(BLE001)

front/plugins/internet_speedtest/script.py

19-19: Unused noqa directive (non-enabled: E402)

Remove unused noqa directive

(RUF100)

🔇 Additional comments (6)
requirements.txt (1)

19-20: LGTM!

The dependency reordering has no functional impact. Note that speedtest-cli is retained for the Python fallback path, which aligns with the PR's hybrid approach discussed in the comments.

front/plugins/internet_speedtest/script.py (2)

33-50: LGTM!

The main() function correctly follows plugin patterns: it uses Plugin_Objects, maps the speedtest results to the appropriate watched fields, calls timeNowDB() for timestamps, and writes the result file exactly once at the end.


53-116: Well-structured speedtest logic with proper error handling.

The function correctly retrieves the timeout setting and enforces a minimum of 60 seconds, delegating to run_speedtest_core(timeout) which handles the subprocess execution with proper timeout.

Key strengths:

  • Timeout enforcement at min 60s aligns with coding guidelines.
  • KeyError/ValueError/TypeError handling for native payload construction (lines 97-98) addresses prior review feedback.
  • Error logging uses 'none' level (line 109) ensuring failures are always visible.
  • Graceful degradation with fallback payload structure for Python CLI mode.
server/api_server/nettools_endpoint.py (3)

16-33: LGTM!

The path resolution correctly prioritizes the native Ookla speedtest binary and falls back to speedtest-cli. The executable permission check is a good defensive measure. The descriptive error message aids troubleshooting when neither binary is available.


182-189: LGTM!

Clean endpoint wrapper that delegates to the core function and properly extracts the status code for the HTTP response. The response structure with success key aligns with the API guidelines.


119-179: Well-implemented core speedtest logic with proper timeout handling and authorization enforcement.

The function correctly:

  • Accepts a configurable timeout (default 120s, exceeds the 60s minimum guideline) and passes it to subprocess.run
  • Detects native vs Python CLI and builds appropriate commands
  • Handles JSON parsing failures gracefully with fallback to raw stdout
  • Returns structured error responses with appropriate status codes

The API route wrapper (api_speedtest in api_server_start.py) properly enforces authorization via is_authorized() before calling the endpoint.

One minor consideration: The native binary detection (os.path.basename(SPEEDTEST_CLI_PATH) == "speedtest") assumes the binary is named exactly "speedtest". If the binary is installed or symlinked with a different name (e.g., "my-speedtest"), it would be misdetected as the Python CLI, causing incorrect command-line arguments to be used. While the function would still execute and likely return output (with fallback handling), consider validating the binary type via output inspection if this becomes a concern in deployment.

@amir0ff amir0ff force-pushed the optimize-speedtest-native branch from 1d73075 to 89ee2ab Compare December 28, 2025 16:18
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
front/plugins/internet_speedtest/script.py (1)

83-105: Payload structure differs between native and Python engines.

The native engine payload includes ping, server, and timestamp fields while the Python fallback only includes download, upload, and engine. This asymmetry is acceptable since the Python CLI's --simple mode doesn't provide this metadata, but consumers of Watched_Value3 should be aware that the payload schema varies by engine.

Consider documenting this in the WATCH description or adding a comment here for maintainability.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1d73075 and 89ee2ab.

📒 Files selected for processing (6)
  • front/plugins/internet_speedtest/config.json
  • front/plugins/internet_speedtest/script.py
  • install/proxmox/requirements.txt
  • install/ubuntu24/requirements.txt
  • requirements.txt
  • server/api_server/nettools_endpoint.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • install/ubuntu24/requirements.txt
  • requirements.txt
  • install/proxmox/requirements.txt
🧰 Additional context used
📓 Path-based instructions (5)
**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.py: Use mylog(level, [message]) for logging; levels are: none/minimal/verbose/debug/trace. Use none for most important messages that should always appear, such as exceptions.
Always set explicit timeouts on subprocess calls. Default to 60s minimum unless plugin config specifies otherwise. Nested subprocess calls need their own timeout.
Never hardcode ports or secrets; always use get_setting_value() to retrieve configuration values.
Use environment variables (NETALERTX_DB, NETALERTX_LOG, etc.) everywhere instead of hardcoding paths like /data/db or relative paths.
Use helper.py functions (timeNowDB, normalize_mac, sanitizers) for time/MAC/string operations. Validate MACs before DB writes.
Add/modify settings via ccd() in server/initialise.py or per-plugin manifest. Define config once and read it via helpers everywhere.
Always leave logging enabled. If there is a possibility it will be difficult to debug with current logging, add more logging.

Files:

  • server/api_server/nettools_endpoint.py
  • front/plugins/internet_speedtest/script.py
server/api_server/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

server/api_server/*.py: All API responses must return "success":<False|True> and if False must include an "error" message, e.g. {"success": False, "error": "No stored open ports for Device"}
All API routes must expect header Authorization: Bearer <API_TOKEN> retrieved via get_setting_value('API_TOKEN') for authorization.

Files:

  • server/api_server/nettools_endpoint.py
server/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Prefer server/db/db_helper.py functions (e.g., get_table_json, device condition helpers) over raw SQL in new code paths.

Files:

  • server/api_server/nettools_endpoint.py
front/plugins/*/script.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

front/plugins/*/script.py: For plugins: define database_column_definitions when creating/updating devices; watched fields trigger notifications.
Plugin scripts must write results to /tmp/log/plugins/last_result.<PREF>.log with pipe-delimited format (9 required cols + optional 4). Use Plugin_Objects to sanitize text and normalize MACs, then call write_result_file() exactly once at the end.
Collect plugin results with Plugin_Objects.add_object(...) during processing and call plugin_objects.write_result_file() exactly once at the end of the script. Do not write ad-hoc files for results.
Always check relevant logs first. Use logging as shown in other plugins. Prefer to log a brief summary before writing results to aid troubleshooting; keep logs concise at info level and use verbose or debug for extra context.

Files:

  • front/plugins/internet_speedtest/script.py
front/plugins/*/config.json

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

front/plugins/*/config.json: Plugin manifest lives at front/plugins/<code_name>/config.json; code_name must equal folder name, and unique_prefix drives settings and filenames (e.g., ARPSCAN).
Control plugins via settings: <PREF>_RUN (phase), <PREF>_RUN_SCHD (cron-like), <PREF>_CMD (script path), <PREF>_RUN_TIMEOUT, <PREF>_WATCH (diff columns).

Files:

  • front/plugins/internet_speedtest/config.json
🧠 Learnings (2)
📚 Learning: 2025-12-08T01:59:00.903Z
Learnt from: CR
Repo: jokob-sk/NetAlertX PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-08T01:59:00.903Z
Learning: Applies to **/*.py : Always set explicit timeouts on subprocess calls. Default to 60s minimum unless plugin config specifies otherwise. Nested subprocess calls need their own timeout.

Applied to files:

  • server/api_server/nettools_endpoint.py
  • front/plugins/internet_speedtest/script.py
📚 Learning: 2025-12-08T01:59:00.903Z
Learnt from: CR
Repo: jokob-sk/NetAlertX PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-08T01:59:00.903Z
Learning: Applies to front/plugins/*/config.json : Control plugins via settings: `<PREF>_RUN` (phase), `<PREF>_RUN_SCHD` (cron-like), `<PREF>_CMD` (script path), `<PREF>_RUN_TIMEOUT`, `<PREF>_WATCH` (diff columns).

Applied to files:

  • front/plugins/internet_speedtest/config.json
🧬 Code graph analysis (1)
front/plugins/internet_speedtest/script.py (3)
server/api_server/nettools_endpoint.py (1)
  • run_speedtest_core (120-180)
front/plugins/plugin_helper.py (1)
  • add_object (262-292)
server/helper.py (1)
  • get_setting_value (200-257)
🪛 Ruff (0.14.10)
server/api_server/nettools_endpoint.py

25-28: Avoid specifying long messages outside the exception class

(TRY003)


51-51: Starting a process with a partial executable path

(S607)


141-141: subprocess call: check for execution of untrusted input

(S603)


158-158: Consider moving this statement to an else block

(TRY300)


174-174: Do not catch blind exception: Exception

(BLE001)


207-207: Starting a process with a partial executable path

(S607)

🔇 Additional comments (12)
front/plugins/internet_speedtest/config.json (2)

557-600: LGTM! New TIMEOUT setting properly configured.

The new INTRSPD_TIMEOUT setting is well-structured with:

  • Correct integer dataType and numeric input element
  • Sensible default of 60 seconds matching the minimum enforcement in the script
  • Complete localizations for en_us, es_es, and de_de
  • Clear description explaining the 60-second minimum and behavior on timeout

635-648: WATCH description updates look complete.

The updated descriptions correctly document that Watched_Value3 now represents a JSON payload for webhooks, with all three language localizations (en_us, es_es, de_de) properly included.

front/plugins/internet_speedtest/script.py (3)

53-59: Timeout handling is well implemented.

The timeout retrieval correctly:

  • Uses INTRSPD_TIMEOUT which matches the config.json setting (prefix + function name)
  • Enforces a minimum of 60 seconds as documented
  • Handles ValueError/TypeError gracefully with fallback to 60s

33-50: LGTM! Main function follows plugin guidelines.

The implementation correctly:

  • Uses Plugin_Objects for result collection
  • Maps speedtest results to watched values with clear comments
  • Calls write_result_file() exactly once at the end
  • Includes informative logging

19-19: Import path is properly configured and will work correctly.

The import from api_server.nettools_endpoint import run_speedtest_core resolves correctly. Line 8 properly adds /app/server to sys.path, enabling Python to locate the api_server.nettools_endpoint module. The function exists at line 120 in ./server/api_server/nettools_endpoint.py and module resolution is consistent across execution contexts.

server/api_server/nettools_endpoint.py (7)

16-33: Path resolution updated to prefer native binary.

The function now correctly:

  • Prioritizes the native Ookla speedtest binary for higher accuracy
  • Falls back to speedtest-cli Python package
  • Validates executability before returning

This aligns with the PR's hybrid approach design.


120-130: Core function signature and error handling look good.

The run_speedtest_core(timeout=120) function:

  • Accepts a configurable timeout (used by the plugin with its own setting)
  • Returns a properly structured dict with success, output, optional raw, and status_code
  • Handles the case when no speedtest executable is found with a 503 status

149-163: Backward-compatible output format with enhanced data.

The implementation elegantly:

  • Parses native JSON and formats it to match --simple output for backward compatibility
  • Preserves raw data in the raw key for enhanced webhook payloads
  • Falls back gracefully if JSON parsing fails (returns stdout as-is)

The static analysis hint about moving the return to an else block (TRY300) is a false positive here—the return on line 158 is the success path after valid JSON parsing.


165-180: Comprehensive exception handling with structured responses.

The error handling covers:

  • TimeoutExpired → 504 Gateway Timeout (appropriate for slow external service)
  • CalledProcessError → 500 with stderr details (command execution failure)
  • Generic Exception → 500 with error message (catch-all for unexpected issues)

The static analysis warning about catching bare Exception (BLE001) is acceptable here—this is a top-level handler that needs to return a structured response rather than crash the API. The structured error responses follow the API guidelines requirement for "success": False with an "error" message.


50-51: Excellent: All subprocess calls now have explicit timeouts.

The addition of timeout=60 to all subprocess calls in the module (wakeonlan, traceroute, nslookup, nmap_scan, internet_info, network_interfaces) follows the coding guidelines requirement: "Always set explicit timeouts on subprocess calls. Default to 60s minimum."

This prevents any endpoint from hanging indefinitely due to a stalled external process.

Also applies to: 100-101, 206-207, 259-260, 288-289, 323-324


183-190: API endpoint correctly delegates to core function.

The speedtest() endpoint:

  • Uses run_speedtest_core() to avoid code duplication
  • Properly extracts and uses status_code for the HTTP response
  • Returns a JSON response with success key per API guidelines
  • Authorization is properly handled at the route registration level via is_authorized() check in the api_speedtest() wrapper

133-139: Native binary detection via filename is acceptable for standard installations.

The detection os.path.basename(SPEEDTEST_CLI_PATH) == "speedtest" is appropriate for typical setups where the official Ookla binary is mounted to /usr/bin/speedtest. While filename-based detection could theoretically break with custom renames or symlinks, this approach is practical and aligns with documented installation instructions.

@amir0ff amir0ff force-pushed the optimize-speedtest-native branch from 89ee2ab to 01084a9 Compare December 28, 2025 16:37
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
front/plugins/internet_speedtest/script.py (1)

68-81: Parsing logic is fragile but acceptable given the core's output format.

The string-splitting logic to extract download/upload speeds depends on the specific format returned by run_speedtest_core() (e.g., "Download: 123.45 Mbit/s"). While this is fragile and could break silently if the core's output format changes, the exception handling provides a safe fallback to 0.0.

Consider adding a brief comment referencing the expected format from run_speedtest_core() to make this dependency explicit, but this is optional given the tight coupling between the two functions.

server/api_server/nettools_endpoint.py (2)

149-160: Consider logging the JSON parsing failure for debugging.

When native binary JSON parsing fails (line 159-160), the function returns success=True with raw stdout, which may not contain the expected "Download: X Mbit/s" format. While the calling code handles this gracefully by defaulting to 0.0 values, this silent fallback could make troubleshooting difficult.

🔎 Suggested enhancement for debugging
         if is_native:
             try:
                 data = json.loads(result.stdout)
                 # Format to match --simple output for backward compatibility
                 output_lines = [
                     f"Ping: {data['ping']['latency']} ms",
                     f"Download: {round((data['download']['bandwidth'] * 8) / 10**6, 2)} Mbit/s",
                     f"Upload: {round((data['upload']['bandwidth'] * 8) / 10**6, 2)} Mbit/s"
                 ]
                 return {"success": True, "output": output_lines, "raw": data}
-            except (json.JSONDecodeError, KeyError):
+            except (json.JSONDecodeError, KeyError) as e:
+                print(f"Warning: Failed to parse native speedtest JSON: {e}. Raw output: {result.stdout[:200]}", file=sys.stderr)
                 return {"success": True, "output": [result.stdout.strip()]}

44-67: Consider adding explicit TimeoutExpired handling for clearer error messages.

While the current exception handlers will catch subprocess.TimeoutExpired, adding explicit handlers (similar to the one in run_speedtest_core at line 165-166) would provide more specific error messages and make timeout behavior more obvious to future maintainers.

Example for wakeonlan:

except subprocess.TimeoutExpired:
    return jsonify({"success": False, "error": "WOL command timed out"}), 504
except subprocess.CalledProcessError as e:
    # existing handler

This is optional since the current handlers will catch timeouts, but explicit handling improves clarity and allows for timeout-specific status codes (504 Gateway Timeout).

Also applies to: 70-117, 193-220, 223-275, 278-309, 312-397

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 89ee2ab and 01084a9.

📒 Files selected for processing (6)
  • front/plugins/internet_speedtest/config.json
  • front/plugins/internet_speedtest/script.py
  • install/proxmox/requirements.txt
  • install/ubuntu24/requirements.txt
  • requirements.txt
  • server/api_server/nettools_endpoint.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • install/ubuntu24/requirements.txt
  • install/proxmox/requirements.txt
🧰 Additional context used
📓 Path-based instructions (5)
front/plugins/*/config.json

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

front/plugins/*/config.json: Plugin manifest lives at front/plugins/<code_name>/config.json; code_name must equal folder name, and unique_prefix drives settings and filenames (e.g., ARPSCAN).
Control plugins via settings: <PREF>_RUN (phase), <PREF>_RUN_SCHD (cron-like), <PREF>_CMD (script path), <PREF>_RUN_TIMEOUT, <PREF>_WATCH (diff columns).

Files:

  • front/plugins/internet_speedtest/config.json
**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.py: Use mylog(level, [message]) for logging; levels are: none/minimal/verbose/debug/trace. Use none for most important messages that should always appear, such as exceptions.
Always set explicit timeouts on subprocess calls. Default to 60s minimum unless plugin config specifies otherwise. Nested subprocess calls need their own timeout.
Never hardcode ports or secrets; always use get_setting_value() to retrieve configuration values.
Use environment variables (NETALERTX_DB, NETALERTX_LOG, etc.) everywhere instead of hardcoding paths like /data/db or relative paths.
Use helper.py functions (timeNowDB, normalize_mac, sanitizers) for time/MAC/string operations. Validate MACs before DB writes.
Add/modify settings via ccd() in server/initialise.py or per-plugin manifest. Define config once and read it via helpers everywhere.
Always leave logging enabled. If there is a possibility it will be difficult to debug with current logging, add more logging.

Files:

  • front/plugins/internet_speedtest/script.py
  • server/api_server/nettools_endpoint.py
front/plugins/*/script.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

front/plugins/*/script.py: For plugins: define database_column_definitions when creating/updating devices; watched fields trigger notifications.
Plugin scripts must write results to /tmp/log/plugins/last_result.<PREF>.log with pipe-delimited format (9 required cols + optional 4). Use Plugin_Objects to sanitize text and normalize MACs, then call write_result_file() exactly once at the end.
Collect plugin results with Plugin_Objects.add_object(...) during processing and call plugin_objects.write_result_file() exactly once at the end of the script. Do not write ad-hoc files for results.
Always check relevant logs first. Use logging as shown in other plugins. Prefer to log a brief summary before writing results to aid troubleshooting; keep logs concise at info level and use verbose or debug for extra context.

Files:

  • front/plugins/internet_speedtest/script.py
server/api_server/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

server/api_server/*.py: All API responses must return "success":<False|True> and if False must include an "error" message, e.g. {"success": False, "error": "No stored open ports for Device"}
All API routes must expect header Authorization: Bearer <API_TOKEN> retrieved via get_setting_value('API_TOKEN') for authorization.

Files:

  • server/api_server/nettools_endpoint.py
server/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Prefer server/db/db_helper.py functions (e.g., get_table_json, device condition helpers) over raw SQL in new code paths.

Files:

  • server/api_server/nettools_endpoint.py
🧠 Learnings (2)
📚 Learning: 2025-12-08T01:59:00.903Z
Learnt from: CR
Repo: jokob-sk/NetAlertX PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-08T01:59:00.903Z
Learning: Applies to front/plugins/*/config.json : Control plugins via settings: `<PREF>_RUN` (phase), `<PREF>_RUN_SCHD` (cron-like), `<PREF>_CMD` (script path), `<PREF>_RUN_TIMEOUT`, `<PREF>_WATCH` (diff columns).

Applied to files:

  • front/plugins/internet_speedtest/config.json
📚 Learning: 2025-12-08T01:59:00.903Z
Learnt from: CR
Repo: jokob-sk/NetAlertX PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-08T01:59:00.903Z
Learning: Applies to **/*.py : Always set explicit timeouts on subprocess calls. Default to 60s minimum unless plugin config specifies otherwise. Nested subprocess calls need their own timeout.

Applied to files:

  • front/plugins/internet_speedtest/script.py
  • server/api_server/nettools_endpoint.py
🧬 Code graph analysis (1)
front/plugins/internet_speedtest/script.py (3)
server/api_server/nettools_endpoint.py (1)
  • run_speedtest_core (120-180)
front/plugins/plugin_helper.py (2)
  • Plugin_Objects (251-310)
  • add_object (262-292)
server/helper.py (1)
  • get_setting_value (200-257)
🪛 Ruff (0.14.10)
server/api_server/nettools_endpoint.py

25-28: Avoid specifying long messages outside the exception class

(TRY003)


51-51: Starting a process with a partial executable path

(S607)


141-141: subprocess call: check for execution of untrusted input

(S603)


158-158: Consider moving this statement to an else block

(TRY300)


174-174: Do not catch blind exception: Exception

(BLE001)


207-207: Starting a process with a partial executable path

(S607)

🔇 Additional comments (11)
front/plugins/internet_speedtest/config.json (2)

557-600: LGTM! Configuration follows plugin conventions.

The new TIMEOUT setting is well-structured with complete localizations for all three languages (en_us, es_es, de_de), a sensible default of 60 seconds, and clear descriptions of the minimum value and timeout behavior.


635-648: LGTM! WATCH descriptions updated correctly.

The updated descriptions properly reflect that Watched_Value3 now contains a JSON payload for webhooks, with all three language localizations (en_us, es_es, de_de) consistently updated. The note about schema variations by engine is helpful context for users.

front/plugins/internet_speedtest/script.py (5)

5-5: LGTM! Appropriate imports for the refactored implementation.

The json import and run_speedtest_core import are correctly added to support the new hybrid speedtest implementation that delegates to the shared core logic.

Also applies to: 19-19


35-50: LGTM! Main function correctly integrates the refactored speedtest logic.

The updated main() function properly calls run_speedtest() and maps the returned dictionary (down, up, json) to the appropriate watched values, aligning with the configuration changes that designate Watched_Value3 as the JSON payload for webhooks.


54-59: LGTM! Timeout handling follows coding guidelines.

The timeout configuration properly enforces a minimum of 60 seconds as required by the coding guidelines and handles type conversion errors gracefully with an appropriate fallback.

As per coding guidelines: "Always set explicit timeouts on subprocess calls. Default to 60s minimum unless plugin config specifies otherwise."


83-110: LGTM! Robust JSON payload construction with comprehensive error handling.

The payload construction logic properly handles both native and Python CLI data paths, with appropriate exception handling for missing keys or invalid data. The error logging at the 'none' level ensures visibility of failures, and fallback payloads maintain a consistent JSON structure for webhook consumers.

The bandwidth conversion (multiplying by 8 to convert bytes/s to bits/s) aligns with the native binary's output format.


112-116: LGTM! Return structure matches expected interface.

The function returns a well-structured dictionary with down, up, and json keys that align with the calling code in main() and the plugin's watched value mapping.

server/api_server/nettools_endpoint.py (4)

16-33: LGTM! Clear binary detection logic with appropriate fallback.

The updated _get_speedtest_cli_path() properly implements the "Python-First Hybrid" approach by preferring the native Ookla binary while falling back to speedtest-cli. The executability check and clear error messages enhance robustness.


120-163: LGTM! Core function properly implements dual-engine support with timeout.

The run_speedtest_core() function correctly:

  • Enforces configurable timeout on subprocess calls (line 146)
  • Detects and handles both native Ookla and Python CLI binaries
  • Parses native JSON output and formats it for backward compatibility
  • Returns a consistent structure with success, output, and optionally raw keys

The timeout handling and comprehensive exception catching ensure the function won't hang or crash unexpectedly.


183-190: LGTM! Endpoint properly delegates to core with clean response structure.

The speedtest() endpoint correctly uses the new core function and properly separates the HTTP status code from the JSON response body by popping it before returning. The default 120-second timeout provides a reasonable margin for slower connections.


51-51: LGTM! Timeout enforcement improves resilience across all network diagnostic functions.

The addition of explicit 60-second timeouts to wakeonlan, traceroute, nslookup, nmap_scan, internet_info, and network_interfaces subprocess calls follows the coding guidelines and prevents these endpoints from hanging indefinitely on network issues.

As per coding guidelines: "Always set explicit timeouts on subprocess calls. Default to 60s minimum unless plugin config specifies otherwise."

Also applies to: 101-101, 207-207, 260-260, 289-289, 324-324

@amir0ff amir0ff force-pushed the optimize-speedtest-native branch from 01084a9 to 4654a44 Compare December 28, 2025 16:45
@amir0ff
Copy link
Contributor Author

amir0ff commented Dec 28, 2025

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 28, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@amir0ff
Copy link
Contributor Author

amir0ff commented Dec 28, 2025

Ideally the plugin should call the API endpoint - I'm in the middle of refactoring the API layer and doing a clean up of the messy parts of the code.

Hi @jokob-sk,

Thank you for the guidance! I’ve updated the PR to fully align with the "Python-first hybrid" approach and your suggestions regarding the API layer.

Here is a summary of the finalized implementation:

  1. Universal baseline: I’ve restored speedtest-cli in all requirements.txt files and reverted the mandatory binary installation in the Dockerfile. This ensures the project remains 100% Python-compatible out-of-the-box and that all standard unit tests pass (200 OK on the speedtest endpoint).
  2. Native opt-in: The system now automatically detects if a user has provided the official Ookla binary at /usr/bin/speedtest. If found, it uses it for high-accuracy testing; otherwise, it seamlessly falls back to the Python library.
  3. API-first refactor: I’ve refactored the internet speed plugin to call the core logic from the API layer. This eliminates code duplication and ensures that both the UI and background tasks use the same standardized execution and parsing engine.

I believe this strikes the right balance between maintaining a lightweight baseline and offering a high-performance path for power users with high-speed fiber. Looking forward to your review!"


def run_speedtest():
# Get configurable timeout with minimum 60s enforcement
try:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary try/catch

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this section is only getting settings - unnecessary overhead

@jokob-sk
Copy link
Collaborator

Hi @amir0ff ,

Thanks a lot for incorporating teh changes. looks much better. A couple of questions:

  1. I can see binaries still installed in the Dockerfile - is that necessary?
  2. If there are additional instructions - can you update the speedtest plugin README.md?
  3. Why do we need gcompat? If it's needed we need to update other installations scripts (proxmox, debian etc)
  4. One unnecessary try / catch.

Once above is solved looks good to merge. If possible don't change the formatting on existing code blocks - makes the review more time consuming 🙏

@amir0ff amir0ff force-pushed the optimize-speedtest-native branch from 4654a44 to b0e7e0a Compare December 29, 2025 15:02
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
server/api_server/nettools_endpoint.py (1)

167-172: Add timeout to speedtest-cli subprocess call.

The fallback CLI path lacks an explicit timeout parameter, which can cause the endpoint to hang if speedtest-cli stalls. Per coding guidelines: "Always set explicit timeouts on subprocess calls. Default to 60s minimum unless plugin config specifies otherwise."

🔎 Proposed fix
         result = subprocess.run(
             [SPEEDTEST_CLI_PATH, "--secure", "--simple"],
             capture_output=True,
             text=True,
             check=True,
+            timeout=60,
         )

Also add handling for subprocess.TimeoutExpired:

except subprocess.TimeoutExpired:
    return jsonify(
        {
            "success": False,
            "error": "Speedtest timed out after 60 seconds",
        }
    ), 504

Based on coding guidelines and retrieved learnings.

♻️ Duplicate comments (1)
front/plugins/internet_speedtest/script.py (1)

53-53: Duplicate constant: Extract native path to shared configuration.

This hardcoded path "/usr/bin/speedtest" duplicates the constant defined in server/api_server/nettools_endpoint.py (line 14). Per coding guidelines: "Define config once and read it via helpers everywhere."

🔎 Recommended approach

Define the path once in a central location (e.g., via ccd() in server/initialise.py or in conf.py) and import it in both files to maintain a single source of truth.

Based on coding guidelines.

🧹 Nitpick comments (3)
front/plugins/internet_speedtest/README.md (1)

22-26: Consider rewording to improve readability.

Three consecutive list items begin with "Watched_Value," which slightly impacts readability. While this is consistent with the field naming, consider using a table format or alternative phrasing for better flow.

🔎 Optional alternative format
-### Data Mapping
-
-- **Watched_Value1**: Download Speed (Mbps).
-- **Watched_Value2**: Upload Speed (Mbps).
-- **Watched_Value3**: Full JSON payload (useful for n8n or detailed webhooks).
+### Data Mapping
+
+| Field | Description |
+|-------|-------------|
+| **Watched_Value1** | Download Speed (Mbps) |
+| **Watched_Value2** | Upload Speed (Mbps) |
+| **Watched_Value3** | Full JSON payload (useful for n8n or detailed webhooks) |
server/api_server/nettools_endpoint.py (1)

14-14: Verify consistency with plugin script's native path.

The constant NATIVE_SPEEDTEST_PATH is defined here as "/usr/bin/speedtest", and the same path is hardcoded in front/plugins/internet_speedtest/script.py (line 53). Consider extracting this to a shared configuration constant to maintain a single source of truth.

🔎 Suggested approach

Define the path once in a shared location (e.g., conf.py or via ccd() in server/initialise.py) and import it in both files:

# In server/initialise.py or conf.py
NATIVE_SPEEDTEST_PATH = "/usr/bin/speedtest"

Then import in both files:

from conf import NATIVE_SPEEDTEST_PATH

Based on coding guidelines: "Define config once and read it via helpers everywhere."

front/plugins/internet_speedtest/script.py (1)

64-64: Remove redundant str() conversion in f-string.

F-strings automatically convert expressions to strings, so str(download_speed) is redundant. This also applies to lines 73, 81, and 88.

🔎 Proposed fix
-                mylog('verbose', [f"[INTRSPD] Native Result (down|up): {str(download_speed)} Mbps|{upload_speed} Mbps"])
+                mylog('verbose', [f"[INTRSPD] Native Result (down|up): {download_speed} Mbps|{upload_speed} Mbps"])

Apply similar changes to lines 73, 81, and 88.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4654a44 and b0e7e0a.

📒 Files selected for processing (4)
  • front/plugins/internet_speedtest/README.md
  • front/plugins/internet_speedtest/config.json
  • front/plugins/internet_speedtest/script.py
  • server/api_server/nettools_endpoint.py
🧰 Additional context used
📓 Path-based instructions (5)
**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.py: Use mylog(level, [message]) for logging; levels are: none/minimal/verbose/debug/trace. Use none for most important messages that should always appear, such as exceptions.
Always set explicit timeouts on subprocess calls. Default to 60s minimum unless plugin config specifies otherwise. Nested subprocess calls need their own timeout.
Never hardcode ports or secrets; always use get_setting_value() to retrieve configuration values.
Use environment variables (NETALERTX_DB, NETALERTX_LOG, etc.) everywhere instead of hardcoding paths like /data/db or relative paths.
Use helper.py functions (timeNowDB, normalize_mac, sanitizers) for time/MAC/string operations. Validate MACs before DB writes.
Add/modify settings via ccd() in server/initialise.py or per-plugin manifest. Define config once and read it via helpers everywhere.
Always leave logging enabled. If there is a possibility it will be difficult to debug with current logging, add more logging.

Files:

  • server/api_server/nettools_endpoint.py
  • front/plugins/internet_speedtest/script.py
server/api_server/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

server/api_server/*.py: All API responses must return "success":<False|True> and if False must include an "error" message, e.g. {"success": False, "error": "No stored open ports for Device"}
All API routes must expect header Authorization: Bearer <API_TOKEN> retrieved via get_setting_value('API_TOKEN') for authorization.

Files:

  • server/api_server/nettools_endpoint.py
server/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Prefer server/db/db_helper.py functions (e.g., get_table_json, device condition helpers) over raw SQL in new code paths.

Files:

  • server/api_server/nettools_endpoint.py
front/plugins/*/script.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

front/plugins/*/script.py: For plugins: define database_column_definitions when creating/updating devices; watched fields trigger notifications.
Plugin scripts must write results to /tmp/log/plugins/last_result.<PREF>.log with pipe-delimited format (9 required cols + optional 4). Use Plugin_Objects to sanitize text and normalize MACs, then call write_result_file() exactly once at the end.
Collect plugin results with Plugin_Objects.add_object(...) during processing and call plugin_objects.write_result_file() exactly once at the end of the script. Do not write ad-hoc files for results.
Always check relevant logs first. Use logging as shown in other plugins. Prefer to log a brief summary before writing results to aid troubleshooting; keep logs concise at info level and use verbose or debug for extra context.

Files:

  • front/plugins/internet_speedtest/script.py
front/plugins/*/config.json

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

front/plugins/*/config.json: Plugin manifest lives at front/plugins/<code_name>/config.json; code_name must equal folder name, and unique_prefix drives settings and filenames (e.g., ARPSCAN).
Control plugins via settings: <PREF>_RUN (phase), <PREF>_RUN_SCHD (cron-like), <PREF>_CMD (script path), <PREF>_RUN_TIMEOUT, <PREF>_WATCH (diff columns).

Files:

  • front/plugins/internet_speedtest/config.json
🧠 Learnings (1)
📚 Learning: 2025-12-08T01:59:00.903Z
Learnt from: CR
Repo: jokob-sk/NetAlertX PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-08T01:59:00.903Z
Learning: Applies to **/*.py : Always set explicit timeouts on subprocess calls. Default to 60s minimum unless plugin config specifies otherwise. Nested subprocess calls need their own timeout.

Applied to files:

  • server/api_server/nettools_endpoint.py
  • front/plugins/internet_speedtest/script.py
🪛 LanguageTool
front/plugins/internet_speedtest/README.md

[style] ~26-~26: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...ched_Value2**: Upload Speed (Mbps). - Watched_Value3: Full JSON payload (useful for...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)

🪛 Ruff (0.14.10)
server/api_server/nettools_endpoint.py

123-123: subprocess call: check for execution of untrusted input

(S603)


151-153: try-except-pass detected, consider logging the exception

(S110)


151-151: Do not catch blind exception: Exception

(BLE001)


151-151: Local variable e is assigned to but never used

Remove assignment to unused variable e

(F841)

front/plugins/internet_speedtest/script.py

59-59: subprocess call: check for execution of untrusted input

(S603)


64-64: Use explicit conversion flag

Replace with conversion flag

(RUF010)


72-72: Do not catch blind exception: Exception

(BLE001)


73-73: Use explicit conversion flag

Replace with conversion flag

(RUF010)


81-81: Use explicit conversion flag

Replace with conversion flag

(RUF010)


87-87: Do not catch blind exception: Exception

(BLE001)


88-88: Use explicit conversion flag

Replace with conversion flag

(RUF010)

🔇 Additional comments (3)
front/plugins/internet_speedtest/config.json (2)

243-256: LGTM! Localization updates align with feature changes.

The Watched_Value3 name has been properly updated across all three languages to reflect that it now contains the full JSON payload. This aligns well with the documented behavior in the README and script changes.


591-604: LGTM! Comprehensive documentation for webhook payload.

The WATCH description accurately documents the new Watched_Value3 behavior, including the note that the schema varies by engine. All three localizations are present and consistent.

front/plugins/internet_speedtest/script.py (1)

87-93: The string 'null' for the error case is intentional and consistent with the codebase. Result files use pipe-delimited format (not JSON), and watched fields are treated as plain strings. Multiple other plugins use the same 'null' string sentinel for empty/error cases (website_monitor, _publisher_telegram, _publisher_email, etc.). This implementation is correct.

Likely an incorrect or invalid review comment.

@amir0ff amir0ff force-pushed the optimize-speedtest-native branch from b0e7e0a to c6dbc42 Compare December 29, 2025 15:13
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
server/api_server/nettools_endpoint.py (1)

120-159: Add logging for non-zero return code from native binary.

When result.returncode != 0 (line 129), the code silently falls through to the CLI fallback without logging. This makes it difficult to diagnose why the native binary failed. Per coding guidelines, add more logging when debugging might be difficult.

🔎 Proposed fix
             if result.returncode == 0:
                 try:
                     data = json.loads(result.stdout)
                     # ... parsing logic ...
                 except (json.JSONDecodeError, KeyError, TypeError) as parse_error:
                     print(f"Failed to parse native speedtest output: {parse_error}", file=sys.stderr)
                     # Fall through to CLI fallback
+            else:
+                print(f"Native speedtest exited with code {result.returncode}: {result.stderr}", file=sys.stderr)
+                # Fall through to CLI fallback
front/plugins/internet_speedtest/script.py (3)

60-69: Add specific handling for JSON parsing errors.

While the generic Exception handler on line 72 will catch JSONDecodeError and KeyError, the error message "Error running native speedtest" is misleading when the actual issue is malformed JSON output. This complicates debugging.

🔎 Proposed fix to distinguish parsing errors
             if result.returncode == 0:
+                try:
                     data = json.loads(result.stdout)
                     download_speed = round(data['download']['bandwidth'] * 8 / 10**6, 2)
                     upload_speed = round(data['upload']['bandwidth'] * 8 / 10**6, 2)
-                    mylog('verbose', [f"[INTRSPD] Native Result (down|up): {str(download_speed)} Mbps|{upload_speed} Mbps"])
+                except (json.JSONDecodeError, KeyError, TypeError) as parse_error:
+                    mylog('none', [f"[INTRSPD] Failed to parse native JSON: {parse_error}"])
+                    # Fall through to baseline
+                else:
+                    mylog('verbose', [f"[INTRSPD] Native Result (down|up): {download_speed} Mbps|{upload_speed} Mbps"])
                     return {
                         'download_speed': download_speed,
                         'upload_speed': upload_speed,
                         'full_json': result.stdout.strip()
                     }

70-73: Use 'none' log level for exceptions.

Per coding guidelines: "Use none for most important messages that should always appear, such as exceptions." The timeout and error conditions should be logged at 'none' level to ensure visibility when debugging.

🔎 Proposed fix
         except subprocess.TimeoutExpired:
-            mylog('verbose', ["[INTRSPD] Native speedtest timed out."])
+            mylog('none', ["[INTRSPD] Native speedtest timed out, falling back to baseline."])
         except Exception as e:
-            mylog('verbose', [f"[INTRSPD] Error running native speedtest: {str(e)}"])
+            mylog('none', [f"[INTRSPD] Error running native speedtest: {e!s}, falling back to baseline."])

Based on coding guidelines.


87-88: Use 'none' log level for baseline exception.

Same as above—exceptions in the baseline fallback should use 'none' level for visibility.

🔎 Proposed fix
     except Exception as e:
-        mylog('verbose', [f"[INTRSPD] Error running baseline speedtest: {str(e)}"])
+        mylog('none', [f"[INTRSPD] Error running baseline speedtest: {e!s}"])

Based on coding guidelines.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b0e7e0a and c6dbc42.

📒 Files selected for processing (4)
  • front/plugins/internet_speedtest/README.md
  • front/plugins/internet_speedtest/config.json
  • front/plugins/internet_speedtest/script.py
  • server/api_server/nettools_endpoint.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • front/plugins/internet_speedtest/config.json
🧰 Additional context used
📓 Path-based instructions (4)
**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.py: Use mylog(level, [message]) for logging; levels are: none/minimal/verbose/debug/trace. Use none for most important messages that should always appear, such as exceptions.
Always set explicit timeouts on subprocess calls. Default to 60s minimum unless plugin config specifies otherwise. Nested subprocess calls need their own timeout.
Never hardcode ports or secrets; always use get_setting_value() to retrieve configuration values.
Use environment variables (NETALERTX_DB, NETALERTX_LOG, etc.) everywhere instead of hardcoding paths like /data/db or relative paths.
Use helper.py functions (timeNowDB, normalize_mac, sanitizers) for time/MAC/string operations. Validate MACs before DB writes.
Add/modify settings via ccd() in server/initialise.py or per-plugin manifest. Define config once and read it via helpers everywhere.
Always leave logging enabled. If there is a possibility it will be difficult to debug with current logging, add more logging.

Files:

  • server/api_server/nettools_endpoint.py
  • front/plugins/internet_speedtest/script.py
server/api_server/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

server/api_server/*.py: All API responses must return "success":<False|True> and if False must include an "error" message, e.g. {"success": False, "error": "No stored open ports for Device"}
All API routes must expect header Authorization: Bearer <API_TOKEN> retrieved via get_setting_value('API_TOKEN') for authorization.

Files:

  • server/api_server/nettools_endpoint.py
server/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Prefer server/db/db_helper.py functions (e.g., get_table_json, device condition helpers) over raw SQL in new code paths.

Files:

  • server/api_server/nettools_endpoint.py
front/plugins/*/script.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

front/plugins/*/script.py: For plugins: define database_column_definitions when creating/updating devices; watched fields trigger notifications.
Plugin scripts must write results to /tmp/log/plugins/last_result.<PREF>.log with pipe-delimited format (9 required cols + optional 4). Use Plugin_Objects to sanitize text and normalize MACs, then call write_result_file() exactly once at the end.
Collect plugin results with Plugin_Objects.add_object(...) during processing and call plugin_objects.write_result_file() exactly once at the end of the script. Do not write ad-hoc files for results.
Always check relevant logs first. Use logging as shown in other plugins. Prefer to log a brief summary before writing results to aid troubleshooting; keep logs concise at info level and use verbose or debug for extra context.

Files:

  • front/plugins/internet_speedtest/script.py
🧠 Learnings (1)
📚 Learning: 2025-12-08T01:59:00.903Z
Learnt from: CR
Repo: jokob-sk/NetAlertX PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-08T01:59:00.903Z
Learning: Applies to **/*.py : Always set explicit timeouts on subprocess calls. Default to 60s minimum unless plugin config specifies otherwise. Nested subprocess calls need their own timeout.

Applied to files:

  • server/api_server/nettools_endpoint.py
  • front/plugins/internet_speedtest/script.py
🪛 LanguageTool
front/plugins/internet_speedtest/README.md

[style] ~26-~26: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...ched_Value2**: Upload Speed (Mbps). - Watched_Value3: Full JSON payload (useful for...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)

🪛 Ruff (0.14.10)
server/api_server/nettools_endpoint.py

123-123: subprocess call: check for execution of untrusted input

(S603)


157-157: Do not catch blind exception: Exception

(BLE001)

front/plugins/internet_speedtest/script.py

59-59: subprocess call: check for execution of untrusted input

(S603)


64-64: Use explicit conversion flag

Replace with conversion flag

(RUF010)


72-72: Do not catch blind exception: Exception

(BLE001)


73-73: Use explicit conversion flag

Replace with conversion flag

(RUF010)


81-81: Use explicit conversion flag

Replace with conversion flag

(RUF010)


87-87: Do not catch blind exception: Exception

(BLE001)


88-88: Use explicit conversion flag

Replace with conversion flag

(RUF010)

🔇 Additional comments (5)
server/api_server/nettools_endpoint.py (2)

14-14: LGTM on constant definition.

The native speedtest path constant is clearly defined and aligns with the documented opt-in flow for users mounting the binary.


173-191: LGTM on CLI timeout handling.

The CLI subprocess call now includes the required 60-second timeout, and the TimeoutExpired exception is properly handled with an appropriate 504 response.

front/plugins/internet_speedtest/script.py (1)

75-86: LGTM on baseline fallback.

The baseline path using the Python speedtest library correctly captures results and serializes full_json for webhook consumption.

front/plugins/internet_speedtest/README.md (2)

7-19: Clear documentation for opt-in native engine.

The usage instructions are well-structured with a concrete docker-compose example. The link to the Speedtest CLI homepage gives users a clear path to download the binary.


22-26: LGTM on data mapping documentation.

The data mapping section clearly describes the three watched values, which aligns with the script changes and will help users configure webhooks.

@amir0ff amir0ff force-pushed the optimize-speedtest-native branch from c6dbc42 to 9a8f071 Compare December 29, 2025 15:42
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
front/plugins/internet_speedtest/README.md (1)

24-26: Minor style: Consider varying the sentence structure in the Data Mapping list.

The three bullet points all begin with the dash-prefixed identifier pattern (**Watched_Value…**), creating repetitive sentence openings. While clear, a small rewording can improve readability.

🔎 Proposed style improvement
- **Watched_Value1**: Download Speed (Mbps).
- **Watched_Value2**: Upload Speed (Mbps).
- **Watched_Value3**: Full JSON payload (useful for n8n or detailed webhooks).
+ **Watched_Value1** — Download Speed (Mbps).
+ **Watched_Value2** — Upload Speed (Mbps).
+ **Watched_Value3** — Full JSON payload (useful for n8n or detailed webhooks).

This uses an em-dash instead of a colon, subtly breaking the repetition while maintaining list structure.

front/plugins/internet_speedtest/script.py (1)

53-53: Consider extracting the native binary path to a shared constant.

The path /usr/bin/speedtest is duplicated between this plugin script and server/api_server/nettools_endpoint.py (line 14). Consider extracting this to a shared location (e.g., a constants module) to ensure consistency and single point of maintenance.

🔎 Example approach

Create a shared constant in a common location:

# In a shared constants module (e.g., server/const.py or similar)
NATIVE_SPEEDTEST_PATH = "/usr/bin/speedtest"

Then import from both files:

-native_path = "/usr/bin/speedtest"
+from const import NATIVE_SPEEDTEST_PATH
+native_path = NATIVE_SPEEDTEST_PATH
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c6dbc42 and 9a8f071.

📒 Files selected for processing (4)
  • front/plugins/internet_speedtest/README.md
  • front/plugins/internet_speedtest/config.json
  • front/plugins/internet_speedtest/script.py
  • server/api_server/nettools_endpoint.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • front/plugins/internet_speedtest/config.json
🧰 Additional context used
📓 Path-based instructions (4)
**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.py: Use mylog(level, [message]) for logging; levels are: none/minimal/verbose/debug/trace. Use none for most important messages that should always appear, such as exceptions.
Always set explicit timeouts on subprocess calls. Default to 60s minimum unless plugin config specifies otherwise. Nested subprocess calls need their own timeout.
Never hardcode ports or secrets; always use get_setting_value() to retrieve configuration values.
Use environment variables (NETALERTX_DB, NETALERTX_LOG, etc.) everywhere instead of hardcoding paths like /data/db or relative paths.
Use helper.py functions (timeNowDB, normalize_mac, sanitizers) for time/MAC/string operations. Validate MACs before DB writes.
Add/modify settings via ccd() in server/initialise.py or per-plugin manifest. Define config once and read it via helpers everywhere.
Always leave logging enabled. If there is a possibility it will be difficult to debug with current logging, add more logging.

Files:

  • front/plugins/internet_speedtest/script.py
  • server/api_server/nettools_endpoint.py
front/plugins/*/script.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

front/plugins/*/script.py: For plugins: define database_column_definitions when creating/updating devices; watched fields trigger notifications.
Plugin scripts must write results to /tmp/log/plugins/last_result.<PREF>.log with pipe-delimited format (9 required cols + optional 4). Use Plugin_Objects to sanitize text and normalize MACs, then call write_result_file() exactly once at the end.
Collect plugin results with Plugin_Objects.add_object(...) during processing and call plugin_objects.write_result_file() exactly once at the end of the script. Do not write ad-hoc files for results.
Always check relevant logs first. Use logging as shown in other plugins. Prefer to log a brief summary before writing results to aid troubleshooting; keep logs concise at info level and use verbose or debug for extra context.

Files:

  • front/plugins/internet_speedtest/script.py
server/api_server/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

server/api_server/*.py: All API responses must return "success":<False|True> and if False must include an "error" message, e.g. {"success": False, "error": "No stored open ports for Device"}
All API routes must expect header Authorization: Bearer <API_TOKEN> retrieved via get_setting_value('API_TOKEN') for authorization.

Files:

  • server/api_server/nettools_endpoint.py
server/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Prefer server/db/db_helper.py functions (e.g., get_table_json, device condition helpers) over raw SQL in new code paths.

Files:

  • server/api_server/nettools_endpoint.py
🧠 Learnings (1)
📚 Learning: 2025-12-08T01:59:00.903Z
Learnt from: CR
Repo: jokob-sk/NetAlertX PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-08T01:59:00.903Z
Learning: Applies to **/*.py : Always set explicit timeouts on subprocess calls. Default to 60s minimum unless plugin config specifies otherwise. Nested subprocess calls need their own timeout.

Applied to files:

  • front/plugins/internet_speedtest/script.py
  • server/api_server/nettools_endpoint.py
🧬 Code graph analysis (1)
front/plugins/internet_speedtest/script.py (2)
server/helper.py (1)
  • get_setting_value (200-257)
server/api_server/nettools_endpoint.py (1)
  • speedtest (115-211)
🪛 LanguageTool
front/plugins/internet_speedtest/README.md

[style] ~26-~26: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...ched_Value2**: Upload Speed (Mbps). - Watched_Value3: Full JSON payload (useful for...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)

🪛 Ruff (0.14.10)
front/plugins/internet_speedtest/script.py

68-68: subprocess call: check for execution of untrusted input

(S603)


88-88: Do not catch blind exception: Exception

(BLE001)


103-103: Do not catch blind exception: Exception

(BLE001)

server/api_server/nettools_endpoint.py

123-123: subprocess call: check for execution of untrusted input

(S603)


159-159: Do not catch blind exception: Exception

(BLE001)

🔇 Additional comments (8)
front/plugins/internet_speedtest/README.md (1)

3-31: Excellent documentation addressing PR objectives.

The README clearly documents the dual-engine model, opt-in flow for the native binary, automatic fallback, and data mapping. The docker-compose volume example is practical and helpful. The content aligns well with the underlying implementation and directly addresses the maintainer's request for installation/use instructions.

server/api_server/nettools_endpoint.py (3)

14-14: LGTM: Native binary path constant.

The hardcoded path /usr/bin/speedtest aligns with the PR design for optional native binary detection. Users mount the Ookla binary to this location as documented.


120-161: LGTM: Native speedtest implementation with proper safeguards.

The implementation correctly:

  • Uses a 60-second timeout per coding guidelines
  • Catches JSON parsing errors (JSONDecodeError, KeyError, TypeError) with logging
  • Logs non-zero exit codes and timeout events
  • Falls back gracefully to CLI on any failure

The broad Exception catch on line 159 is appropriate here as the goal is graceful degradation to the CLI fallback.


173-193: LGTM: CLI path with timeout handling.

The CLI fallback now properly includes:

  • 60-second timeout per coding guidelines
  • Appropriate 504 Gateway Timeout response for timeout scenarios
  • Consistent error response format with success: False and error message
front/plugins/internet_speedtest/script.py (4)

6-7: LGTM: Added necessary imports.

The subprocess and json imports are required for the native speedtest binary execution and JSON parsing.


38-49: LGTM: Plugin result includes full JSON payload.

The watched3 field now properly exposes the full JSON payload for webhook consumers (n8n, etc.) as documented in the PR objectives. Results are correctly written via plugin_objects.write_result_file() per coding guidelines.


58-65: LGTM: Robust timeout parsing with minimum enforcement.

The timeout parsing is properly defensive:

  • Handles missing settings (raw_timeout being None)
  • Catches ValueError/TypeError for invalid values
  • Enforces 60-second minimum per coding guidelines

91-109: LGTM: Baseline fallback with consistent result structure.

The baseline path using the Python speedtest library:

  • Properly serializes results to JSON via st.results.dict()
  • Returns consistent result structure with full_json field
  • Uses appropriate error logging at none level for visibility
  • Returns sentinel values (-1) on failure for downstream handling

@amir0ff amir0ff force-pushed the optimize-speedtest-native branch from 9a8f071 to 3788c75 Compare December 29, 2025 15:54
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
server/api_server/nettools_endpoint.py (6)

46-47: Missing timeout on wakeonlan subprocess call.

Per coding guidelines, all subprocess calls require explicit timeouts (minimum 60s). The wakeonlan command could hang indefinitely without one.

🔎 Proposed fix
         result = subprocess.run(
-            ["wakeonlan", mac], capture_output=True, text=True, check=True
+            ["wakeonlan", mac], capture_output=True, text=True, check=True, timeout=60
         )

Based on coding guidelines.


92-97: Missing timeout on traceroute subprocess call.

Traceroute can take a long time or hang on unreachable hosts. Add a timeout per coding guidelines.

🔎 Proposed fix
         result = subprocess.run(
             ["traceroute", ip],  # Command and argument
             capture_output=True,  # Capture stdout/stderr
             text=True,  # Return output as string
             check=True,  # Raise CalledProcessError on non-zero exit
+            timeout=60,  # Minimum timeout per coding guidelines
         )

Based on coding guidelines.


227-228: Missing timeout on nslookup subprocess call.

DNS lookups can hang on network issues. Add a timeout for consistency with other subprocess calls.

🔎 Proposed fix
         result = subprocess.run(
-            ["nslookup", ip], capture_output=True, text=True, check=True
+            ["nslookup", ip], capture_output=True, text=True, check=True, timeout=60
         )

Based on coding guidelines.


276-281: Missing timeout on nmap subprocess call.

Nmap scans, especially detail mode (-A), can take several minutes. Consider a longer timeout or making it configurable.

🔎 Proposed fix
         cmd = ["nmap"] + mode_args[mode] + [ip]
         result = subprocess.run(
             cmd,
             capture_output=True,
             text=True,
             check=True,
+            timeout=300,  # Nmap scans can take longer, 5 min default
         )

Based on coding guidelines.


304-309: Missing timeout on curl subprocess call.

External HTTP requests can hang on network issues.

🔎 Proposed fix
         result = subprocess.run(
-            ["curl", "-s", "https://ipinfo.io/json"],
+            ["curl", "-s", "--max-time", "30", "https://ipinfo.io/json"],
             capture_output=True,
             text=True,
             check=True,
+            timeout=60,
         )

Based on coding guidelines.


338-343: Missing timeout on nmap --iflist subprocess call.

While this is a local operation, adding a timeout ensures consistent behavior.

🔎 Proposed fix
         nmap_output = subprocess.run(
             ["nmap", "--iflist"],
             capture_output=True,
             text=True,
             check=True,
+            timeout=60,
         ).stdout.strip()

Based on coding guidelines.

🧹 Nitpick comments (3)
front/plugins/internet_speedtest/script.py (2)

19-19: Clean up unused noqa directive.

The noqa: E402 directive on this line is flagged as unused by Ruff. Consider removing it to keep the codebase clean.

🔎 Proposed fix
-from const import logPath, NATIVE_SPEEDTEST_PATH  # noqa: E402 [flake8 lint suppression]
+from const import logPath, NATIVE_SPEEDTEST_PATH

105-108: Consider using None or empty JSON object instead of string 'null' for error cases.

The error case returns 'null' as a string literal for full_json, while successful cases return actual JSON strings. This inconsistency could cause issues for downstream consumers expecting valid JSON parsing.

🔎 Proposed fix
         return {
             'download_speed': -1,
             'upload_speed': -1,
-            'full_json': 'null'
+            'full_json': json.dumps({"error": str(e)})
         }

Alternatively, if 'null' is intentional for plugin result file compatibility, consider documenting this behavior.

front/plugins/internet_speedtest/README.md (1)

15-18: Consider adding a note about host binary requirements.

The volume mapping assumes the native speedtest binary is installed on the host at /usr/bin/speedtest. Adding a note clarifying this could help users troubleshoot.

🔎 Suggested addition
 ```yaml
 volumes:
   - /usr/bin/speedtest:/usr/bin/speedtest:ro

+- Ensure the native speedtest binary is installed on the host at the source path.

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used**: Path: .coderabbit.yaml

**Review profile**: CHILL

**Plan**: Pro

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 9a8f071cc8be3f24ffd40240506ff843e9424d4a and 3788c75fa8e6c524d208a127c71459dae2b15b14.

</details>

<details>
<summary>📒 Files selected for processing (4)</summary>

* `front/plugins/internet_speedtest/README.md`
* `front/plugins/internet_speedtest/config.json`
* `front/plugins/internet_speedtest/script.py`
* `server/api_server/nettools_endpoint.py`

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* front/plugins/internet_speedtest/config.json

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>📓 Path-based instructions (4)</summary>

<details>
<summary>**/*.py</summary>


**📄 CodeRabbit inference engine (.github/copilot-instructions.md)**

> `**/*.py`: Use `mylog(level, [message])` for logging; levels are: none/minimal/verbose/debug/trace. Use `none` for most important messages that should always appear, such as exceptions.
> Always set explicit timeouts on subprocess calls. Default to 60s minimum unless plugin config specifies otherwise. Nested subprocess calls need their own timeout.
> Never hardcode ports or secrets; always use `get_setting_value()` to retrieve configuration values.
> Use environment variables (`NETALERTX_DB`, `NETALERTX_LOG`, etc.) everywhere instead of hardcoding paths like `/data/db` or relative paths.
> Use `helper.py` functions (`timeNowDB`, `normalize_mac`, sanitizers) for time/MAC/string operations. Validate MACs before DB writes.
> Add/modify settings via `ccd()` in `server/initialise.py` or per-plugin manifest. Define config once and read it via helpers everywhere.
> Always leave logging enabled. If there is a possibility it will be difficult to debug with current logging, add more logging.

Files:
- `server/api_server/nettools_endpoint.py`
- `front/plugins/internet_speedtest/script.py`

</details>
<details>
<summary>server/api_server/*.py</summary>


**📄 CodeRabbit inference engine (.github/copilot-instructions.md)**

> `server/api_server/*.py`: All API responses must return `"success":<False|True>` and if `False` must include an `"error"` message, e.g. `{"success": False, "error": "No stored open ports for Device"}`
> All API routes must expect header `Authorization: Bearer <API_TOKEN>` retrieved via `get_setting_value('API_TOKEN')` for authorization.

Files:
- `server/api_server/nettools_endpoint.py`

</details>
<details>
<summary>server/**/*.py</summary>


**📄 CodeRabbit inference engine (.github/copilot-instructions.md)**

> Prefer `server/db/db_helper.py` functions (e.g., `get_table_json`, device condition helpers) over raw SQL in new code paths.

Files:
- `server/api_server/nettools_endpoint.py`

</details>
<details>
<summary>front/plugins/*/script.py</summary>


**📄 CodeRabbit inference engine (.github/copilot-instructions.md)**

> `front/plugins/*/script.py`: For plugins: define `database_column_definitions` when creating/updating devices; watched fields trigger notifications.
> Plugin scripts must write results to `/tmp/log/plugins/last_result.<PREF>.log` with pipe-delimited format (9 required cols + optional 4). Use `Plugin_Objects` to sanitize text and normalize MACs, then call `write_result_file()` exactly once at the end.
> Collect plugin results with `Plugin_Objects.add_object(...)` during processing and call `plugin_objects.write_result_file()` exactly once at the end of the script. Do not write ad-hoc files for results.
> Always check relevant logs first. Use logging as shown in other plugins. Prefer to log a brief summary before writing results to aid troubleshooting; keep logs concise at `info` level and use `verbose` or `debug` for extra context.

Files:
- `front/plugins/internet_speedtest/script.py`

</details>

</details><details>
<summary>🧠 Learnings (1)</summary>

<details>
<summary>📚 Learning: 2025-12-08T01:59:00.903Z</summary>

Learnt from: CR
Repo: jokob-sk/NetAlertX PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-08T01:59:00.903Z
Learning: Applies to **/*.py : Always set explicit timeouts on subprocess calls. Default to 60s minimum unless plugin config specifies otherwise. Nested subprocess calls need their own timeout.


**Applied to files:**
- `server/api_server/nettools_endpoint.py`
- `front/plugins/internet_speedtest/script.py`

</details>

</details><details>
<summary>🪛 LanguageTool</summary>

<details>
<summary>front/plugins/internet_speedtest/README.md</summary>

[style] ~26-~26: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...hed_Value2** — Upload Speed (Mbps). - **Watched_Value3** — Full JSON payload (useful fo...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)

</details>

</details>
<details>
<summary>🪛 Ruff (0.14.10)</summary>

<details>
<summary>server/api_server/nettools_endpoint.py</summary>

123-123: `subprocess` call: check for execution of untrusted input

(S603)

---

159-159: Do not catch blind exception: `Exception`

(BLE001)

</details>
<details>
<summary>front/plugins/internet_speedtest/script.py</summary>

19-19: Unused `noqa` directive (non-enabled: `E402`)

Remove unused `noqa` directive

(RUF100)

---

68-68: `subprocess` call: check for execution of untrusted input

(S603)

---

88-88: Do not catch blind exception: `Exception`

(BLE001)

---

103-103: Do not catch blind exception: `Exception`

(BLE001)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (8)</summary><blockquote>

<details>
<summary>server/api_server/nettools_endpoint.py (2)</summary><blockquote>

`14-14`: **LGTM — clear constant for native binary path.**

Using a module-level constant for the native speedtest path is clean and allows easy modification if needed.

---

`120-161`: **Well-implemented dual-engine speedtest with proper fallback.**

The native binary detection and execution logic is well-structured:
- Timeout of 60s correctly applied per coding guidelines
- JSON parsing errors handled gracefully with fallback to CLI
- Logging captures failure reasons for debugging
- Clean fallthrough to CLI on any native path failure

</blockquote></details>
<details>
<summary>front/plugins/internet_speedtest/script.py (3)</summary><blockquote>

`6-7`: **LGTM — necessary imports for native binary support.**

The subprocess and json imports enable the native speedtest execution path.

---

`52-90`: **Well-structured native speedtest path with robust error handling.**

The implementation addresses all previously raised concerns:
- Safe parsing of timeout with proper fallback to 60s minimum
- JSON parsing wrapped in try/except catching JSONDecodeError, KeyError, TypeError
- Timeout handling with clear fallback messaging
- Generic exception catch for unexpected failures with informative logging

The try/except/else pattern on lines 71-84 is clean and ensures the return only happens when parsing succeeds.

---

`91-109`: **Baseline fallback correctly implemented.**

The baseline path using the speedtest library:
- Properly wraps results in the expected format with `full_json`
- Handles errors gracefully returning -1 for speeds
- Uses `st.results.dict()` serialized to JSON for consistency with native path

</blockquote></details>
<details>
<summary>front/plugins/internet_speedtest/README.md (3)</summary><blockquote>

`7-19`: **Clear and actionable documentation for the dual-engine approach.**

The instructions for opting into the native engine are well-structured with the docker-compose volume mapping example. This aligns with the implementation in `script.py` that checks for the binary at `NATIVE_SPEEDTEST_PATH`.

---

`22-26`: **Data mapping documentation matches implementation.**

The Watched_Value fields correctly reflect what `script.py` populates:
- `watched1` → download_speed
- `watched2` → upload_speed  
- `watched3` → full_json

---

`30-31`: **Consider adding fallback behavior detail.**

The note mentions fallback to baseline library but doesn't mention timeout or parse error fallbacks that are implemented in `script.py`. This is optional since the current wording covers the main case.

</blockquote></details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

@amir0ff amir0ff force-pushed the optimize-speedtest-native branch from 3788c75 to b2678e9 Compare December 29, 2025 16:16
@amir0ff
Copy link
Contributor Author

amir0ff commented Dec 29, 2025

Hi @amir0ff ,

Thanks a lot for incorporating teh changes. looks much better. A couple of questions:

  1. I can see binaries still installed in the Dockerfile - is that necessary?
  2. If there are additional instructions - can you update the speedtest plugin README.md?
  3. Why do we need gcompat? If it's needed we need to update other installations scripts (proxmox, debian etc)
  4. One unnecessary try / catch.

Once above is solved looks good to merge. If possible don't change the formatting on existing code blocks - makes the review more time consuming 🙏

Hi @jokob-sk,

Thanks for the feedback! I’ve updated the PR to address your points. Let me know if there’s anything else!

- Introduce native Ookla Speedtest binary support for Gigabit connections

- Add intelligent engine detection with automatic fallback to python-cli version

- Map full JSON payload to Watched_Value3 for n8n integration

- Add Spanish (es_es) localizations and update README instructions
@amir0ff amir0ff force-pushed the optimize-speedtest-native branch from b2678e9 to 3b1b853 Compare December 29, 2025 16:33
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
front/plugins/internet_speedtest/README.md (1)

24-33: Data mapping documentation is clear.

The mapping of watched values is well-documented. The static analysis hint about repetitive sentence structure (lines 26-28) is a minor stylistic nitpick that doesn't affect clarity.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b2678e9 and 3b1b853.

📒 Files selected for processing (5)
  • front/plugins/internet_speedtest/README.md
  • front/plugins/internet_speedtest/config.json
  • front/plugins/internet_speedtest/script.py
  • server/api_server/nettools_endpoint.py
  • server/const.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • front/plugins/internet_speedtest/config.json
🧰 Additional context used
📓 Path-based instructions (4)
**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.py: Use mylog(level, [message]) for logging; levels are: none/minimal/verbose/debug/trace. Use none for most important messages that should always appear, such as exceptions.
Always set explicit timeouts on subprocess calls. Default to 60s minimum unless plugin config specifies otherwise. Nested subprocess calls need their own timeout.
Never hardcode ports or secrets; always use get_setting_value() to retrieve configuration values.
Use environment variables (NETALERTX_DB, NETALERTX_LOG, etc.) everywhere instead of hardcoding paths like /data/db or relative paths.
Use helper.py functions (timeNowDB, normalize_mac, sanitizers) for time/MAC/string operations. Validate MACs before DB writes.
Add/modify settings via ccd() in server/initialise.py or per-plugin manifest. Define config once and read it via helpers everywhere.
Always leave logging enabled. If there is a possibility it will be difficult to debug with current logging, add more logging.

Files:

  • server/const.py
  • front/plugins/internet_speedtest/script.py
  • server/api_server/nettools_endpoint.py
server/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Prefer server/db/db_helper.py functions (e.g., get_table_json, device condition helpers) over raw SQL in new code paths.

Files:

  • server/const.py
  • server/api_server/nettools_endpoint.py
front/plugins/*/script.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

front/plugins/*/script.py: For plugins: define database_column_definitions when creating/updating devices; watched fields trigger notifications.
Plugin scripts must write results to /tmp/log/plugins/last_result.<PREF>.log with pipe-delimited format (9 required cols + optional 4). Use Plugin_Objects to sanitize text and normalize MACs, then call write_result_file() exactly once at the end.
Collect plugin results with Plugin_Objects.add_object(...) during processing and call plugin_objects.write_result_file() exactly once at the end of the script. Do not write ad-hoc files for results.
Always check relevant logs first. Use logging as shown in other plugins. Prefer to log a brief summary before writing results to aid troubleshooting; keep logs concise at info level and use verbose or debug for extra context.

Files:

  • front/plugins/internet_speedtest/script.py
server/api_server/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

server/api_server/*.py: All API responses must return "success":<False|True> and if False must include an "error" message, e.g. {"success": False, "error": "No stored open ports for Device"}
All API routes must expect header Authorization: Bearer <API_TOKEN> retrieved via get_setting_value('API_TOKEN') for authorization.

Files:

  • server/api_server/nettools_endpoint.py
🧠 Learnings (1)
📚 Learning: 2025-12-08T01:59:00.903Z
Learnt from: CR
Repo: jokob-sk/NetAlertX PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-08T01:59:00.903Z
Learning: Applies to **/*.py : Always set explicit timeouts on subprocess calls. Default to 60s minimum unless plugin config specifies otherwise. Nested subprocess calls need their own timeout.

Applied to files:

  • front/plugins/internet_speedtest/script.py
  • server/api_server/nettools_endpoint.py
🧬 Code graph analysis (1)
front/plugins/internet_speedtest/script.py (2)
server/helper.py (1)
  • get_setting_value (200-257)
server/api_server/nettools_endpoint.py (1)
  • speedtest (116-212)
🪛 LanguageTool
front/plugins/internet_speedtest/README.md

[style] ~28-~28: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...hed_Value2** — Upload Speed (Mbps). - Watched_Value3 — Full JSON payload (useful fo...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)

🪛 Ruff (0.14.10)
front/plugins/internet_speedtest/script.py

67-67: subprocess call: check for execution of untrusted input

(S603)


87-87: Do not catch blind exception: Exception

(BLE001)


102-102: Do not catch blind exception: Exception

(BLE001)


103-103: Use explicit conversion flag

Replace with conversion flag

(RUF010)

server/api_server/nettools_endpoint.py

124-124: subprocess call: check for execution of untrusted input

(S603)


160-160: Do not catch blind exception: Exception

(BLE001)

🔇 Additional comments (12)
server/const.py (1)

48-49: LGTM: Environment-based configuration.

The constant follows coding guidelines by using an environment variable with a sensible default path.

front/plugins/internet_speedtest/README.md (1)

3-22: LGTM: Clear documentation for hybrid approach.

The README effectively documents both engines and provides clear opt-in instructions with a YAML example for mounting the native binary.

server/api_server/nettools_endpoint.py (4)

9-9: LGTM: Necessary import for native speedtest path.


122-163: Native speedtest implementation is robust.

The native execution path correctly implements:

  • 60s timeout (meets coding guidelines)
  • Comprehensive JSON parsing error handling (JSONDecodeError, KeyError, TypeError)
  • Appropriate logging for all error paths
  • Graceful fallback to speedtest-cli

Past review concerns have been addressed.


181-181: LGTM: Timeout added to speedtest-cli call.

The 60-second timeout meets coding guidelines and addresses the previous review concern.


188-194: LGTM: Appropriate timeout error handling.

The TimeoutExpired handler returns a proper 504 status with a descriptive error message.

front/plugins/internet_speedtest/script.py (6)

6-7: LGTM: Necessary imports for native speedtest support.


19-19: LGTM: Import of NATIVE_SPEEDTEST_PATH constant.


43-43: LGTM: Proper mapping of full JSON payload.

The watched3 field now correctly uses the full_json result, enabling detailed webhook integrations as documented in the README.


55-88: Native speedtest implementation handles errors correctly.

The implementation properly:

  • Checks for native binary existence
  • Parses timeout with appropriate defaults and minimum enforcement
  • Executes subprocess with timeout
  • Handles JSON parsing errors with try/except/else pattern
  • Logs all error conditions appropriately
  • Falls back to baseline on any failure

The timeout parsing (lines 57-61) provides defensive handling for invalid configuration values, which aligns with coding guidelines.

Based on coding guidelines.


91-101: LGTM: Baseline fallback correctly implements full_json.

The baseline path now properly serializes the complete speedtest results as JSON, maintaining consistency with the native path.


102-108: LGTM: Appropriate error handling for baseline fallback.

The broad Exception catch is intentional as the final fallback, ensuring the plugin always returns a valid result structure. The error is logged and encoded in the full_json field for downstream analysis.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
front/plugins/internet_speedtest/README.md (1)

34-38: Vary the structure of data mapping bullets to improve readability.

The three bullet points in the Data Mapping section all begin with "Watched_Value", which creates a repetitive pattern. Consider restructuring one or two bullets to vary the format while maintaining clarity.

🔎 Proposed improvement
 ### Data Mapping

 - **Watched_Value1** — Download Speed (Mbps).
 - **Watched_Value2** — Upload Speed (Mbps).
 - **Watched_Value3** — Full JSON payload (useful for n8n or detailed webhooks).
+
+Alternatively:
+
+- **Download Speed (Watched_Value1)** — Measured in Mbps.
+- **Upload Speed (Watched_Value2)** — Measured in Mbps.
+- **Full JSON Payload (Watched_Value3)** — Useful for n8n or detailed webhooks.

Pick whichever format feels more natural to you; the key is breaking up the repetition.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3b1b853 and 7edf857.

📒 Files selected for processing (1)
  • front/plugins/internet_speedtest/README.md
🧰 Additional context used
🪛 LanguageTool
front/plugins/internet_speedtest/README.md

[style] ~38-~38: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...hed_Value2** — Upload Speed (Mbps). - Watched_Value3 — Full JSON payload (useful fo...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)

🔇 Additional comments (1)
front/plugins/internet_speedtest/README.md (1)

1-43: Documentation is clear, well-structured, and aligns with the hybrid implementation.

The README effectively documents the Python-First dual-engine design:

  • Setup instructions for the optional native binary are detailed and accurate.
  • The Docker Compose mapping rationale and example are helpful.
  • Fallback behavior and performance recommendations are clearly stated.
  • Data mapping descriptions correctly reflect the plugin's output fields.

Excellent work bridging the gap between the optional native binary and baseline Python engine for users.

@jokob-sk
Copy link
Collaborator

Thanks a lot for incorporating the changes @amir0ff - it's almost perfect. Appreciate the code clean up and endpoint call instead of the old approach - I should have done that myself after creating the endpoint.

I noticed you are using the NATIVE_SPEEDTEST_PATH config variable - which is great, however I can't see it documented. I assume it is for changing the default location of your speediest binary. Do you think the user needs to be able to change this location? If not we can leave it out form the docs as well.

Happy to merge after the above is addressed.

- Added details for NATIVE_SPEEDTEST_PATH to the README under 'Usage'.
- Explained default behavior and included examples for overriding the binary location.
- Added a verbose log to print the binary path when the plugin starts up.
@amir0ff
Copy link
Contributor Author

amir0ff commented Dec 29, 2025

Thanks a lot for incorporating the changes @amir0ff - it's almost perfect. Appreciate the code clean up and endpoint call instead of the old approach - I should have done that myself after creating the endpoint.

I noticed you are using the NATIVE_SPEEDTEST_PATH config variable - which is great, however I can't see it documented. I assume it is for changing the default location of your speediest binary. Do you think the user needs to be able to change this location? If not we can leave it out form the docs as well.

Happy to merge after the above is addressed.

Thanks for pointing that out! The NATIVE_SPEEDTEST_PATH variable is mainly for advanced users who might need to configure it, for example: in custom containers or non-standard setups. I added a short note in the README, explaining its purpose, the default behavior, and an example for customization.

Additionally, I added a one-line verbose log to the plugin startup, which prints the resolved binary path for easier debugging. This ensures users have transparency and helps with troubleshooting. Let me know if anything else needs adjustment!

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
front/plugins/internet_speedtest/script.py (2)

70-84: Add logging for native binary non-zero exit codes.

When result.returncode != 0, the code silently falls through to baseline without logging the failure or stderr output. Per coding guidelines, logging should aid troubleshooting.

🔎 Proposed fix
             if result.returncode == 0:
                 try:
                     data = json.loads(result.stdout)
                     download_speed = round(data['download']['bandwidth'] * 8 / 10**6, 2)
                     upload_speed = round(data['upload']['bandwidth'] * 8 / 10**6, 2)
                 except (json.JSONDecodeError, KeyError, TypeError) as parse_error:
                     mylog('none', [f"[INTRSPD] Failed to parse native JSON: {parse_error}"])
                     # Fall through to baseline fallback
                 else:
                     mylog('verbose', [f"[INTRSPD] Native Result (down|up): {download_speed} Mbps|{upload_speed} Mbps"])
                     return {
                         'download_speed': download_speed,
                         'upload_speed': upload_speed,
                         'full_json': result.stdout.strip()
                     }
+            else:
+                mylog('none', [f"[INTRSPD] Native binary failed (exit {result.returncode}): {result.stderr.strip()}"])

103-108: Minor: Inconsistent string conversion style.

Line 104 uses str(e) while line 89 uses {e!s}. For consistency, prefer the f-string conversion flag.

🔎 Proposed fix
     except Exception as e:
-        mylog('verbose', [f"[INTRSPD] Error running speedtest: {str(e)}"])
+        mylog('verbose', [f"[INTRSPD] Error running speedtest: {e!s}"])
         return {
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7edf857 and 218c427.

📒 Files selected for processing (2)
  • front/plugins/internet_speedtest/README.md
  • front/plugins/internet_speedtest/script.py
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.py: Use mylog(level, [message]) for logging; levels are: none/minimal/verbose/debug/trace. Use none for most important messages that should always appear, such as exceptions.
Always set explicit timeouts on subprocess calls. Default to 60s minimum unless plugin config specifies otherwise. Nested subprocess calls need their own timeout.
Never hardcode ports or secrets; always use get_setting_value() to retrieve configuration values.
Use environment variables (NETALERTX_DB, NETALERTX_LOG, etc.) everywhere instead of hardcoding paths like /data/db or relative paths.
Use helper.py functions (timeNowDB, normalize_mac, sanitizers) for time/MAC/string operations. Validate MACs before DB writes.
Add/modify settings via ccd() in server/initialise.py or per-plugin manifest. Define config once and read it via helpers everywhere.
Always leave logging enabled. If there is a possibility it will be difficult to debug with current logging, add more logging.

Files:

  • front/plugins/internet_speedtest/script.py
front/plugins/*/script.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

front/plugins/*/script.py: For plugins: define database_column_definitions when creating/updating devices; watched fields trigger notifications.
Plugin scripts must write results to /tmp/log/plugins/last_result.<PREF>.log with pipe-delimited format (9 required cols + optional 4). Use Plugin_Objects to sanitize text and normalize MACs, then call write_result_file() exactly once at the end.
Collect plugin results with Plugin_Objects.add_object(...) during processing and call plugin_objects.write_result_file() exactly once at the end of the script. Do not write ad-hoc files for results.
Always check relevant logs first. Use logging as shown in other plugins. Prefer to log a brief summary before writing results to aid troubleshooting; keep logs concise at info level and use verbose or debug for extra context.

Files:

  • front/plugins/internet_speedtest/script.py
🧠 Learnings (1)
📚 Learning: 2025-12-08T01:59:00.903Z
Learnt from: CR
Repo: jokob-sk/NetAlertX PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-08T01:59:00.903Z
Learning: Applies to **/*.py : Always set explicit timeouts on subprocess calls. Default to 60s minimum unless plugin config specifies otherwise. Nested subprocess calls need their own timeout.

Applied to files:

  • front/plugins/internet_speedtest/script.py
🪛 LanguageTool
front/plugins/internet_speedtest/README.md

[style] ~48-~48: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...hed_Value2** — Upload Speed (Mbps). - Watched_Value3 — Full JSON payload (useful fo...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)

🪛 Ruff (0.14.10)
front/plugins/internet_speedtest/script.py

68-68: subprocess call: check for execution of untrusted input

(S603)


88-88: Do not catch blind exception: Exception

(BLE001)


103-103: Do not catch blind exception: Exception

(BLE001)


104-104: Use explicit conversion flag

Replace with conversion flag

(RUF010)

🔇 Additional comments (6)
front/plugins/internet_speedtest/script.py (2)

38-48: LGTM!

The watched3 field now correctly propagates the full_json payload, enabling detailed webhook data. The plugin correctly uses add_object() and write_result_file() per coding guidelines.


56-68: Good defensive timeout handling.

The safe parsing of INTRSPD_RUN_TIMEOUT with fallback to 60s and minimum enforcement aligns with coding guidelines requiring explicit timeouts (≥60s) on subprocess calls.

front/plugins/internet_speedtest/README.md (4)

7-19: Clear dual-engine documentation.

The documentation accurately describes the baseline and native engine options with proper configuration guidance for NATIVE_SPEEDTEST_PATH.


21-42: LGTM!

The setup instructions are clear with actionable steps, and the rationale for the /usr/bin/speedtest mapping is well-explained.


44-48: LGTM!

The data mapping accurately documents the watched values that align with the script implementation.


50-53: LGTM!

The notes accurately describe the performance recommendation threshold and fallback behavior.

@jokob-sk
Copy link
Collaborator

Thanks @amir0ff - looks great! Appreciate the help and contribution.

@jokob-sk jokob-sk merged commit 30294ef into netalertx:main Dec 29, 2025
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants