Skip to content

Commit c9321af

Browse files
murrantStyleCIBot
andauthored
Modern discovery (librenms#17813)
* Implement lnms device:discover * refactor and translation * proper output * update ./discovery.php references except python in scripts * Handle lnms device:discover new * Apply fixes from StyleCI * Refactor a bit * Apply fixes from StyleCI * Refactor ModuleList so process type is not hard-coded * Push connectivity check into modules * Fix use statements * Update docs more flexible modules * Rector fixes * Style fix * fix bad dump string * Fix os re-intialization after core * Fix output during test data generation * Pass ModuleList around, add convenient user input parsing Add lots of types in ModuleTestHelper * Core module should always be enabled * style * Don't allow core to be disabled * $module variable used by some discovery apparently --------- Co-authored-by: StyleCI Bot <[email protected]>
1 parent aa21468 commit c9321af

40 files changed

+879
-413
lines changed

.github/ISSUE_TEMPLATE/device_bug.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ body:
6161
label: Discovery output
6262
description: |
6363
Paste the result of:
64-
./discovery.php -d -h HOSTNAME
64+
lnms device:discover -vv HOSTNAME
6565
render: txt
6666

6767
- type: textarea
@@ -71,5 +71,5 @@ body:
7171
label: Poller output
7272
description: |
7373
Paste the result of:
74-
lnms device:poll HOSTNAME -vv --no-data
74+
lnms device:poll -vv HOSTNAME --no-data
7575
render: txt

LibreNMS/Enum/ProcessType.php

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
/**
4+
* ProcessType.php
5+
*
6+
* -Description-
7+
*
8+
* This program is free software: you can redistribute it and/or modify
9+
* it under the terms of the GNU General Public License as published by
10+
* the Free Software Foundation, either version 3 of the License, or
11+
* (at your option) any later version.
12+
*
13+
* This program is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
16+
* GNU General Public License for more details.
17+
*
18+
* You should have received a copy of the GNU General Public License
19+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
20+
*
21+
* @link https://www.librenms.org
22+
*
23+
* @copyright 2025 Tony Murray
24+
* @author Tony Murray <[email protected]>
25+
*/
26+
27+
namespace LibreNMS\Enum;
28+
29+
enum ProcessType
30+
{
31+
case poller;
32+
case discovery;
33+
34+
public function verbPast(): string
35+
{
36+
return match ($this) {
37+
ProcessType::discovery => 'discovered',
38+
ProcessType::poller => 'polled',
39+
};
40+
}
41+
42+
public function verbPresent(): string
43+
{
44+
return match ($this) {
45+
ProcessType::discovery => 'discovering',
46+
ProcessType::poller => 'polling',
47+
};
48+
}
49+
50+
public function verb(): string
51+
{
52+
return match ($this) {
53+
ProcessType::discovery => 'discover',
54+
ProcessType::poller => 'poll',
55+
};
56+
}
57+
}

LibreNMS/Modules/Core.php

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -77,24 +77,6 @@ public function discover(OS $os): void
7777

7878
// detect OS
7979
$device->os = self::detectOS($device, false);
80-
81-
if ($device->isDirty('os')) {
82-
Eventlog::log('Device OS changed: ' . $device->getOriginal('os') . ' -> ' . $device->os, $device, 'system', Severity::Notice);
83-
$os->getDeviceArray()['os'] = $device->os;
84-
85-
Log::info('OS Changed ');
86-
}
87-
88-
// Set type to a predefined type for the OS if it's not already set
89-
$loaded_os_type = LibrenmsConfig::get("os.$device->os.type");
90-
if (! $device->getAttrib('override_device_type') && $loaded_os_type != $device->type) {
91-
$device->type = $loaded_os_type;
92-
Log::debug("Device type changed to $loaded_os_type!");
93-
}
94-
95-
$device->save();
96-
97-
Log::notice('OS: ' . LibrenmsConfig::getOsSetting($device->os, 'text') . " ($device->os)\n");
9880
}
9981

10082
public function shouldPoll(OS $os, ModuleStatus $status): bool

LibreNMS/Modules/LegacyModule.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ public function discover(OS $os): void
7676
}
7777

7878
$device = &$os->getDeviceArray();
79+
$module = $this->name;
7980
Debug::disableErrorReporting(); // ignore errors in legacy code
8081

8182
include_once base_path('includes/dbFacile.php');

LibreNMS/Polling/ModuleStatus.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public function __construct(
3535
public ?bool $os = null,
3636
public ?bool $device = null,
3737
public ?bool $manual = null,
38+
public ?array $submodules = null,
3839
) {
3940
}
4041

@@ -85,6 +86,11 @@ public function isEnabledAndDeviceUp(Device $device, bool $check_snmp = true): b
8586
return $this->isEnabled() && $device->status;
8687
}
8788

89+
public function hasSubModules(): bool
90+
{
91+
return ! empty($this->submodules);
92+
}
93+
8894
public function __toString(): string
8995
{
9096
return sprintf('Module %s: Global %s | OS %s | Device %s | Manual %s',

LibreNMS/Util/Module.php

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,12 @@
2626

2727
namespace LibreNMS\Util;
2828

29+
use App\Facades\DeviceCache;
2930
use App\Facades\LibrenmsConfig;
30-
use App\Models\Device;
31+
use Illuminate\Support\Facades\Log;
32+
use LibreNMS\Enum\ProcessType;
3133
use LibreNMS\Modules\LegacyModule;
32-
use LibreNMS\Polling\ModuleStatus;
34+
use LibreNMS\RRD\RrdDefinition;
3335

3436
class Module
3537
{
@@ -59,30 +61,23 @@ public static function legacyPollingExists(string $module_name): bool
5961
return is_file(base_path("includes/polling/$module_name.inc.php"));
6062
}
6163

62-
public static function pollingStatus(string $module_name, Device $device, ?bool $manual = null): ModuleStatus
64+
public static function savePerformance(string $module, ProcessType $type, float $start_time, int $start_memory): void
6365
{
64-
return new ModuleStatus(
65-
LibrenmsConfig::get("poller_modules.$module_name"),
66-
LibrenmsConfig::get("os.{$device->os}.poller_modules.$module_name"),
67-
$device->getAttrib("poll_$module_name"),
68-
$manual,
69-
);
70-
}
66+
$module_time = microtime(true) - $start_time;
67+
$module_mem = (memory_get_usage() - $start_memory);
7168

72-
public static function parseUserOverrides(array $overrides): array
73-
{
74-
$modules = [];
69+
Log::info(sprintf(">> Runtime for %s module '%s': %.4f seconds with %s bytes", $type->name, $module, $module_time, $module_mem));
7570

76-
foreach ($overrides as $module) {
77-
// parse submodules (only supported by some modules)
78-
if (str_contains((string) $module, '/')) {
79-
[$module, $submodule] = explode('/', (string) $module, 2);
80-
$modules[$module][] = $submodule;
81-
} elseif (self::exists($module)) {
82-
$modules[$module] = true;
83-
}
71+
if ($type == ProcessType::discovery) {
72+
return; // do not record for now as there is currently no rrd during discovery
8473
}
8574

86-
return $modules;
75+
app('Datastore')->put(DeviceCache::getPrimary()->toArray(), 'poller-perf', [
76+
'module' => $module,
77+
'rrd_def' => RrdDefinition::make()->addDataset('poller', 'GAUGE', 0),
78+
'rrd_name' => ['poller-perf', $module],
79+
], [
80+
'poller' => $module_time,
81+
]);
8782
}
8883
}

LibreNMS/Util/ModuleList.php

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
<?php
2+
3+
/**
4+
* ModuleList.php
5+
*
6+
* -Description-
7+
*
8+
* This program is free software: you can redistribute it and/or modify
9+
* it under the terms of the GNU General Public License as published by
10+
* the Free Software Foundation, either version 3 of the License, or
11+
* (at your option) any later version.
12+
*
13+
* This program is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
16+
* GNU General Public License for more details.
17+
*
18+
* You should have received a copy of the GNU General Public License
19+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
20+
*
21+
* @link https://www.librenms.org
22+
*
23+
* @copyright 2025 Tony Murray
24+
* @author Tony Murray <[email protected]>
25+
*/
26+
27+
namespace LibreNMS\Util;
28+
29+
use App\Facades\LibrenmsConfig;
30+
use App\Models\Device;
31+
use Illuminate\Support\Facades\Log;
32+
use LibreNMS\Enum\ProcessType;
33+
use LibreNMS\Polling\ModuleStatus;
34+
35+
class ModuleList
36+
{
37+
/**
38+
* @param array<string, bool|array<string>> $overrides
39+
*/
40+
public function __construct(
41+
public readonly array $overrides = [],
42+
) {
43+
}
44+
45+
/**
46+
* @param array<string> $overrides
47+
*/
48+
public static function fromUserOverrides(array $overrides): self
49+
{
50+
$modules = [];
51+
$flattened = array_merge(...array_map(fn ($item) => explode(',', $item), $overrides));
52+
53+
foreach ($flattened as $module) {
54+
$enabled = true;
55+
56+
if (str_contains($module, '/')) {
57+
[$module, $submodule] = explode('/', $module, 2);
58+
$enabled = $modules[$module] ?? []; // load existing submodules
59+
$enabled[] = $submodule;
60+
}
61+
62+
if (Module::exists($module)) {
63+
$modules[$module] = $enabled;
64+
}
65+
}
66+
67+
return new self($modules);
68+
}
69+
70+
public function hasOverride(): bool
71+
{
72+
return ! empty($this->overrides);
73+
}
74+
75+
public function moduleHasOverride(string $module_name): bool
76+
{
77+
return isset($this->overrides[$module_name]);
78+
}
79+
80+
public function printOverrides(ProcessType $type): void
81+
{
82+
if ($this->hasOverride()) {
83+
$modules = array_map(fn ($module, $status) => $module . (is_array($status) ? '(' . implode(',', $status) . ')' : ''), array_keys($this->overrides), array_values($this->overrides));
84+
85+
Log::debug(sprintf('Override %s modules: %s', $type->name, implode(', ', $modules)));
86+
}
87+
}
88+
89+
/**
90+
* @return array<string, ModuleStatus>
91+
*/
92+
public function modulesWithStatus(ProcessType $type, Device $device): array
93+
{
94+
$default_modules = match ($type) {
95+
ProcessType::poller => LibrenmsConfig::get('poller_modules', []),
96+
ProcessType::discovery => LibrenmsConfig::get('discovery_modules', []),
97+
};
98+
99+
$modules_with_overrides = empty($this->overrides)
100+
? array_keys($default_modules)
101+
: array_keys(array_intersect_key($default_modules, $this->overrides)); // ensure order
102+
103+
$module_status = [];
104+
foreach ($modules_with_overrides as $module_name) {
105+
$module_status[$module_name] = $this->moduleStatus($type, $module_name, $device);
106+
}
107+
108+
// core is always enabled
109+
return ['core' => new ModuleStatus(true)] + $module_status;
110+
}
111+
112+
private function moduleStatus(ProcessType $type, string $module_name, Device $device): ModuleStatus
113+
{
114+
$override = $this->overrides[$module_name] ?? null;
115+
116+
return match ($type) {
117+
ProcessType::discovery => new ModuleStatus(
118+
LibrenmsConfig::get("discovery_modules.$module_name"),
119+
LibrenmsConfig::get("os.{$device->os}.discovery_modules.$module_name"),
120+
$device->getAttrib("discover_$module_name"),
121+
$override == null ? null : true,
122+
is_array($override) ? $override : null,
123+
),
124+
ProcessType::poller => new ModuleStatus(
125+
LibrenmsConfig::get("poller_modules.$module_name"),
126+
LibrenmsConfig::get("os.{$device->os}.poller_modules.$module_name"),
127+
$device->getAttrib("poll_$module_name"),
128+
$override == null ? null : true,
129+
is_array($override) ? $override : null,
130+
),
131+
};
132+
}
133+
}

0 commit comments

Comments
 (0)