Skip to main content

System Architecture

NMIS 9 is a distributed network management system with a modular architecture:

Core Components

NMIS Daemon (nmisd)

The central polling and data collection engine. Location: /usr/local/nmis9/bin/nmisd Responsibilities:
  • SNMP polling of network devices
  • Data collection and storage
  • Threshold evaluation
  • Event generation
  • Job queue management
Process Management:
# Start daemon
systemctl start nmisd

# Check status
systemctl status nmisd

# View process
ps aux | grep nmisd
The daemon runs continuously and spawns worker processes for polling:
# From nmisd source
my $config = NMISNG::Util::loadConfTable();
my $nmisng = NMISNG->new(
    config => $config,
    log => $log
);
The daemon automatically restarts failed workers and manages resource limits.

Web Interface (nmisx)

Mojolicious-based web application providing the user interface. Location: /usr/local/nmis9/script/nmisx Port: 8080 (default) Features:
  • Real-time device dashboards
  • Performance graphs
  • Event management
  • Configuration interface
  • RESTful API
Startup:
# Production mode
./nmisx daemon -m production -l http://*:8080

# Development mode with auto-reload
./nmisx daemon -m development -l http://*:8080
Technology Stack:
  • Framework: Mojolicious 8.11+
  • Template Engine: Embedded Perl (EP)
  • JavaScript: jQuery, Chart.js
  • CSS: Bootstrap-based custom theme

Database (MongoDB)

Document database storing configuration and dynamic data. Version: 6.0 or 7.0 Collections:
CollectionPurposeTypical Size
nodesNode configuration~50 KB per node
inventoryInterface and component inventory~10 KB per interface
eventsActive and historical events~2 KB per event
opstatusOperational status cache~5 KB per node
queueJob queueVariable
logsActivity logsVariable
Connection:
# From NMISNG::DB
my $client = MongoDB::MongoClient->new(
    host => "mongodb://localhost:27017",
    username => $config->{db_username},
    password => $config->{db_password},
    auth_mechanism => 'SCRAM-SHA-256'
);
Indexes: NMIS creates indexes automatically on startup:
// nodes collection
db.nodes.createIndex({"name": 1}, {unique: true})
db.nodes.createIndex({"cluster_id": 1, "name": 1})
db.nodes.createIndex({"group": 1})

// inventory collection
db.inventory.createIndex({"node_uuid": 1, "concept": 1})
db.inventory.createIndex({"cluster_id": 1, "enabled": 1})

// events collection
db.events.createIndex({"node_uuid": 1, "event": 1})
db.events.createIndex({"time": -1})
db.events.createIndex({"level": 1, "current": 1})

RRD Storage

Round-Robin Database files store time-series performance data. Location: /usr/local/nmis9/var Directory Structure:
var/
├── nmis_system/         # System-level RRDs
│   └── health/
├── router1/             # Per-node directories
│   ├── interface/       # Interface metrics
│   │   ├── GigabitEthernet0-0.rrd
│   │   └── GigabitEthernet0-1.rrd
│   ├── health/          # System health
│   │   ├── cpu.rrd
│   │   └── memory.rrd
│   └── pkts/            # Packet statistics
└── router2/
RRD Schema: NMIS creates RRDs with multiple RRAs (Round-Robin Archives):
# From rrdfunc.pm
my @rra = (
    "RRA:AVERAGE:0.5:1:600",     # 5 min for 2 days
    "RRA:AVERAGE:0.5:6:700",     # 30 min for 14 days  
    "RRA:AVERAGE:0.5:24:775",    # 2 hour for 2 months
    "RRA:AVERAGE:0.5:288:1460", # 1 day for 4 years
    "RRA:MAX:0.5:1:600",
    "RRA:MAX:0.5:6:700",
    "RRA:MAX:0.5:24:775",
    "RRA:MAX:0.5:288:1460"
);
This provides:
  • Raw data: 2 days at 5-minute resolution
  • 30-minute averages: 14 days
  • 2-hour averages: 2 months
  • Daily averages: 4 years

Configuration Files

NMIS uses a hierarchical configuration system. Main Config: /usr/local/nmis9/conf/Config.nmis
%hash = (
  'system' => {
    'db_name' => 'nmis',
    'db_server' => 'localhost:27017',
    'db_username' => 'nmis',
  },
  'directories' => {
    'base' => '/usr/local/nmis9',
    'var' => '/usr/local/nmis9/var',
    'database' => '/usr/local/nmis9/database',
  },
  'polling' => {
    'poll_interval' => 300,  # 5 minutes
    'max_threads' => 10,
  }
);
Precedence:
  1. conf/Config.nmis (custom)
  2. conf-default/Config.nmis (defaults)
Custom configuration overrides defaults.

Data Flow

Polling Cycle

Step-by-Step:
  1. Job Scheduling: Scheduler adds poll job to queue every 5 minutes (default)
  2. Job Pickup: nmisd worker picks up job from queue
  3. SNMP Collection:
    # Simplified polling code
    my $snmp = Compat::NMIS::snmp_open(
        host => $node->configuration->{host},
        version => $node->configuration->{version},
        community => $node->configuration->{community}
    );
    
    my $result = $snmp->get_request(-varbindlist => \@oids);
    
  4. Data Storage:
    • Performance metrics → RRD files
    • Configuration/inventory → MongoDB
  5. Threshold Evaluation:
    if ($cpu_utilization > $threshold->{high}) {
        $nmisng->events->eventAdd(
            event => "High CPU",
            level => "Major",
            element => $node->name,
            details => "CPU at $cpu_utilization%"
        );
    }
    

Web Request Flow

Authentication:
# From NMISNG::Auth
my $auth = NMISNG::Auth->new(config => $config);
my $user = $auth->authenticate(
    username => $username,
    password => $password
);
Authorization: Privilege levels (0-5) control access:
  • 5: View only
  • 3: Operator (acknowledge events)
  • 1: Administrator (full access)
  • 0: Super admin

Event Flow

Event Lifecycle:
  1. Creation: Threshold exceeded or manual creation
  2. Storage: Written to events collection
  3. Escalation: Matched against escalation policies
  4. Notification: Email, SMS, webhook, etc.
  5. Acknowledgment: User acknowledges event
  6. Closure: Event resolved or auto-closed

Distributed Architecture

NMIS supports distributed polling for large networks: Roles:
  • Master Server:
    • Central MongoDB database
    • Web interface
    • Event aggregation
    • Reporting
  • Poller Nodes:
    • SNMP polling only
    • Local RRD storage
    • Send inventory updates to master
    • Can operate independently if master is unavailable
Configuration:
# Master server
'server' => {
    'server_name' => 'master',
    'server_role' => 'MASTER'
}

# Poller node
'server' => {
    'server_name' => 'poller1',
    'server_role' => 'POLLER',
    'server_master' => 'master.example.com'
}

Process Management

Daemon Processes

# Check all NMIS processes
ps aux | grep nmis

# Typical output
nmis  1234  nmisd           # Main daemon
nmis  1235  nmisd worker    # Polling worker 1
nmis  1236  nmisd worker    # Polling worker 2  
nmis  1240  nmisx           # Web interface

Resource Limits

NMIS respects system limits:
# From nmisd
my $max_workers = $config->{polling}->{max_threads} // 10;
my $memory_limit = $config->{system}->{memory_limit} // '2G';
Tuning:
# Config.nmis
'polling' => {
    'max_threads' => 20,           # Concurrent pollers
    'max_repetitions' => 25,       # SNMP GETBULK size
    'snmp_timeout' => 5,           # SNMP timeout (seconds)
    'max_msg_size' => 65535,       # Max SNMP message size
}

Scalability

Vertical Scaling

Single Server Capacity:
  • Up to 5,000 devices with:
    • 16 CPU cores
    • 32 GB RAM
    • SSD storage
    • 5-minute polling interval
Bottlenecks:
  1. CPU: SNMP processing and RRD updates
  2. Disk I/O: RRD file writes
  3. Network: SNMP polling throughput

Horizontal Scaling

For > 5,000 devices:
  1. Add Poller Nodes: Each poller handles 2,000-5,000 devices
  2. Dedicated Database Server: Move MongoDB to dedicated hardware
  3. Load Balancer: Distribute web interface traffic

High Availability

Database Replication

MongoDB replica set:
# MongoDB replica set config
replication:
  replSetName: nmis-rs
// Initialize replica set
rs.initiate({
  _id: "nmis-rs",
  members: [
    { _id: 0, host: "nmis1:27017" },
    { _id: 1, host: "nmis2:27017" },
    { _id: 2, host: "nmis3:27017" }
  ]
})

Daemon Failover

Use keepalived or Pacemaker for daemon failover:
# keepalived config
vrrp_script check_nmisd {
    script "/usr/local/nmis9/bin/check_daemon.sh"
    interval 10
}

Performance Monitoring

Internal Metrics

NMIS monitors its own performance:
# View polling statistics
/usr/local/nmis9/admin/polling_summary9.pl

# Output
Total Polls: 1,234
Successful: 1,230 (99.7%)
Failed: 4 (0.3%)
Avg Duration: 12.3 seconds
Max Duration: 45.2 seconds

Database Statistics

// MongoDB performance
db.nodes.stats()
db.events.stats()

// Query profiling
db.setProfilingLevel(1, { slowms: 100 })
db.system.profile.find().limit(5).sort({ ts: -1 })

Security Architecture

Authentication Flow

Supported Methods:
  • Local htpasswd file
  • LDAP/Active Directory
  • RADIUS
  • TACACS+
  • PAM
  • Atlassian Crowd
  • ConnectWise

Data Encryption

  • SNMP: SNMPv3 with AES-256
  • Web: HTTPS with TLS 1.2+
  • Database: MongoDB encryption at rest
  • Passwords: bcrypt hashing

See Also

Build docs developers (and LLMs) love