Scope alarm tracking to selected templates and surface endpoint/security state on the dashboard so operators can deploy in large galaxies without drowning clients in irrelevant alarms or guessing what the server is advertising
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -35,6 +35,70 @@ The `IsAlarm` flag originates from the `AlarmExtension` primitive in the Galaxy
|
||||
|
||||
For each alarm attribute, the code verifies that a corresponding `InAlarm` sub-attribute variable node exists in `_tagToVariableNode` (constructed from `FullTagReference + ".InAlarm"`). If the variable node is missing, the alarm is skipped -- this prevents creating orphaned alarm conditions for attributes whose extension primitives were not published.
|
||||
|
||||
## Template-Based Alarm Object Filter
|
||||
|
||||
When large galaxies contain more alarm-bearing objects than clients need, `OpcUa.AlarmFilter.ObjectFilters` restricts alarm condition creation to a subset of objects selected by **template name pattern**. The filter is applied at both alarm creation sites -- the full build in `BuildAddressSpace` and the subtree rebuild path triggered by Galaxy redeployment -- so the included set is recomputed on every rebuild against the fresh hierarchy.
|
||||
|
||||
### Matching rules
|
||||
|
||||
- `*` is the only wildcard (glob-style, zero or more characters). All other regex metacharacters are escaped and matched literally.
|
||||
- Matching is case-insensitive.
|
||||
- The leading `$` used by Galaxy template `tag_name` values is normalized away on both the stored chain entry and the operator pattern, so `TestMachine*` matches the stored `$TestMachine`.
|
||||
- Each configured entry may itself be comma-separated for operator convenience (`"TestMachine*, Pump_*"`).
|
||||
- An empty list disables the filter and restores the prior behavior: every alarm-bearing object is tracked when `AlarmTrackingEnabled=true`.
|
||||
|
||||
### What gets included
|
||||
|
||||
Every Galaxy object whose **template derivation chain** contains any template matching any pattern is included. The chain walks `gobject.derived_from_gobject_id` from the instance through its immediate template and each ancestor template, up to `$Object`. An instance of `TestCoolMachine` whose chain is `$TestCoolMachine -> $TestMachine -> $UserDefined` matches the pattern `TestMachine` via the ancestor hit.
|
||||
|
||||
Inclusion propagates down the **containment hierarchy**: if an object matches, all of its descendants are included as well, regardless of their own template chains. This lets operators target a parent and pick up all its alarm-bearing children with one pattern.
|
||||
|
||||
Each object is evaluated exactly once. Overlapping matches (multiple patterns hit, or both an ancestor and descendant match independently) never produce duplicate alarm condition subscriptions -- the filter operates on object identity via a `HashSet<int>` of included `GobjectId` values.
|
||||
|
||||
### Resolution algorithm
|
||||
|
||||
`AlarmObjectFilter.ResolveIncludedObjects(hierarchy)` runs once per build:
|
||||
|
||||
1. Compile each pattern into a regex with `IgnoreCase | CultureInvariant | Compiled`.
|
||||
2. Build a `parent -> children` map from the hierarchy. Orphans (parent id not in the hierarchy) are treated as roots.
|
||||
3. BFS from each root with a `(nodeId, parentIncluded)` queue and a `visited` set for cycle defense.
|
||||
4. At each node: if the parent was included OR any chain entry matches any pattern, add the node and mark its subtree as included.
|
||||
5. Return the `HashSet<int>` of included object IDs. When no patterns are configured the filter is disabled and the method returns `null`, which the alarm loop treats as "no filtering".
|
||||
|
||||
After each resolution, `UnmatchedPatterns` exposes any raw pattern that matched zero objects so the startup log can warn about operator typos without failing startup.
|
||||
|
||||
### How the alarm loop applies the filter
|
||||
|
||||
```csharp
|
||||
// LmxNodeManager.BuildAddressSpace (and the subtree rebuild path)
|
||||
if (_alarmTrackingEnabled)
|
||||
{
|
||||
var includedIds = ResolveAlarmFilterIncludedIds(sorted); // null if no filter
|
||||
foreach (var obj in sorted)
|
||||
{
|
||||
if (obj.IsArea) continue;
|
||||
if (includedIds != null && !includedIds.Contains(obj.GobjectId)) continue;
|
||||
// ... existing alarm-attribute collection + AlarmConditionState creation
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`ResolveAlarmFilterIncludedIds` also emits a one-line summary (`Alarm filter: X of Y objects included (Z pattern(s))`) and per-pattern warnings for patterns that matched nothing. The included count is published to the dashboard via `AlarmFilterIncludedObjectCount`.
|
||||
|
||||
### Runtime telemetry
|
||||
|
||||
`LmxNodeManager` exposes three read-only properties populated by the filter:
|
||||
|
||||
- `AlarmFilterEnabled` -- true when patterns are configured.
|
||||
- `AlarmFilterPatternCount` -- number of compiled patterns.
|
||||
- `AlarmFilterIncludedObjectCount` -- number of objects in the most recent included set.
|
||||
|
||||
`StatusReportService` reads these into `AlarmStatusInfo.FilterEnabled`, `FilterPatternCount`, and `FilterIncludedObjectCount`. The Alarms panel on the dashboard renders `Filter: N pattern(s), M object(s) included` only when the filter is enabled. See [Status Dashboard](StatusDashboard.md#alarms).
|
||||
|
||||
### Validator warning
|
||||
|
||||
`ConfigurationValidator.ValidateAndLog()` logs the effective filter at startup and emits a `Warning` if `AlarmFilter.ObjectFilters` is non-empty while `AlarmTrackingEnabled` is `false`, because the filter would have no effect.
|
||||
|
||||
## AlarmConditionState Creation
|
||||
|
||||
Each detected alarm attribute produces an `AlarmConditionState` node:
|
||||
|
||||
Reference in New Issue
Block a user