@gazzehamine/armada-watch-agent 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +10 -0
- package/README.md +138 -0
- package/dist/collector.js +81 -0
- package/dist/index.js +120 -0
- package/package.json +48 -0
package/.env.example
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Agent Configuration
|
|
2
|
+
SERVER_URL=http://your-monitoring-server:4000
|
|
3
|
+
INSTANCE_NAME=My EC2 Instance
|
|
4
|
+
REGION=us-east-1
|
|
5
|
+
|
|
6
|
+
# Collection interval in seconds
|
|
7
|
+
COLLECTION_INTERVAL=10
|
|
8
|
+
|
|
9
|
+
# Optional: Set custom instance ID (defaults to hostname)
|
|
10
|
+
# INSTANCE_ID=i-1234567890abcdef0
|
package/README.md
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Armada Watch Agent
|
|
2
|
+
|
|
3
|
+
Lightweight monitoring agent for EC2 instances. Collects and sends system metrics to your Armada Watch monitoring server.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
### Via NPM (Recommended)
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g @gazzehamine/armada-watch-agent
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
### 1. Set Environment Variables
|
|
16
|
+
|
|
17
|
+
Create a `.env` file:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
SERVER_URL=http://your-monitoring-server:4000
|
|
21
|
+
INSTANCE_NAME=Production Web Server
|
|
22
|
+
REGION=us-east-1
|
|
23
|
+
COLLECTION_INTERVAL=10
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Or set them directly:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
export SERVER_URL=http://your-monitoring-server:4000
|
|
30
|
+
export INSTANCE_NAME="Production Web Server"
|
|
31
|
+
export REGION=us-east-1
|
|
32
|
+
export COLLECTION_INTERVAL=10
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### 2. Run the Agent
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# If installed via npm
|
|
39
|
+
armada-watch-agent
|
|
40
|
+
|
|
41
|
+
# Or with npx
|
|
42
|
+
npx @gazzehamine/armada-watch-agent
|
|
43
|
+
|
|
44
|
+
# If built from source
|
|
45
|
+
npm start
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### 3. Run as a Service (Production)
|
|
49
|
+
|
|
50
|
+
**Using PM2:**
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npm install -g pm2
|
|
54
|
+
pm2 start armada-watch-agent --name armada-watch
|
|
55
|
+
pm2 save
|
|
56
|
+
pm2 startup
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**Using systemd:**
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
sudo tee /etc/systemd/system/armada-watch.service > /dev/null <<EOF
|
|
63
|
+
[Unit]
|
|
64
|
+
Description=Armada Watch Monitoring Agent
|
|
65
|
+
After=network.target
|
|
66
|
+
|
|
67
|
+
[Service]
|
|
68
|
+
Type=simple
|
|
69
|
+
User=$USER
|
|
70
|
+
WorkingDirectory=$HOME
|
|
71
|
+
Environment="SERVER_URL=http://your-server:4000"
|
|
72
|
+
Environment="INSTANCE_NAME=My Instance"
|
|
73
|
+
Environment="REGION=us-east-1"
|
|
74
|
+
ExecStart=$(which armada-watch-agent)
|
|
75
|
+
Restart=always
|
|
76
|
+
RestartSec=10
|
|
77
|
+
|
|
78
|
+
[Install]
|
|
79
|
+
WantedBy=multi-user.target
|
|
80
|
+
EOF
|
|
81
|
+
|
|
82
|
+
sudo systemctl daemon-reload
|
|
83
|
+
sudo systemctl enable armada-watch
|
|
84
|
+
sudo systemctl start armada-watch
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Configuration
|
|
88
|
+
|
|
89
|
+
| Variable | Required | Default | Description |
|
|
90
|
+
|----------|----------|---------|-------------|
|
|
91
|
+
| `SERVER_URL` | Yes | - | URL of your monitoring server |
|
|
92
|
+
| `INSTANCE_NAME` | No | hostname | Friendly name for this instance |
|
|
93
|
+
| `REGION` | No | `unknown` | AWS region (e.g., us-east-1) |
|
|
94
|
+
| `INSTANCE_ID` | No | hostname | EC2 instance ID (auto-detected on EC2) |
|
|
95
|
+
| `COLLECTION_INTERVAL` | No | `10` | Interval in seconds between metric collections |
|
|
96
|
+
|
|
97
|
+
## Collected Metrics
|
|
98
|
+
|
|
99
|
+
The agent collects:
|
|
100
|
+
|
|
101
|
+
- **CPU Usage** - Overall CPU utilization percentage
|
|
102
|
+
- **Memory Usage** - RAM usage percentage and amounts
|
|
103
|
+
- **Disk Usage** - Disk space utilization
|
|
104
|
+
- **Network Traffic** - Network RX/TX bytes
|
|
105
|
+
- **System Info** - Platform, architecture, CPU model
|
|
106
|
+
- **Top Processes** - Top 10 processes by CPU/Memory
|
|
107
|
+
|
|
108
|
+
## Requirements
|
|
109
|
+
|
|
110
|
+
- Node.js >= 18.0.0
|
|
111
|
+
- Network access to monitoring server on port 4000
|
|
112
|
+
|
|
113
|
+
## Troubleshooting
|
|
114
|
+
|
|
115
|
+
### Agent can't connect to server
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
# Test connectivity
|
|
119
|
+
curl http://your-server:4000/health
|
|
120
|
+
|
|
121
|
+
# Check agent logs (PM2)
|
|
122
|
+
pm2 logs armada-watch
|
|
123
|
+
|
|
124
|
+
# Check agent logs (systemd)
|
|
125
|
+
sudo journalctl -u armada-watch -f
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Permission errors
|
|
129
|
+
|
|
130
|
+
The agent needs permission to read system metrics. Run with appropriate user permissions.
|
|
131
|
+
|
|
132
|
+
## License
|
|
133
|
+
|
|
134
|
+
MIT
|
|
135
|
+
|
|
136
|
+
## Repository
|
|
137
|
+
|
|
138
|
+
https://github.com/gazzehamine/armada-watch
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getSystemInfo = getSystemInfo;
|
|
7
|
+
exports.collectMetrics = collectMetrics;
|
|
8
|
+
exports.collectProcesses = collectProcesses;
|
|
9
|
+
const systeminformation_1 = __importDefault(require("systeminformation"));
|
|
10
|
+
const os_1 = __importDefault(require("os"));
|
|
11
|
+
let lastNetworkStats = null;
|
|
12
|
+
async function getSystemInfo() {
|
|
13
|
+
const cpu = await systeminformation_1.default.cpu();
|
|
14
|
+
return {
|
|
15
|
+
platform: os_1.default.platform(),
|
|
16
|
+
arch: os_1.default.arch(),
|
|
17
|
+
hostname: os_1.default.hostname(),
|
|
18
|
+
cpuModel: cpu.manufacturer + " " + cpu.brand,
|
|
19
|
+
cpuCores: cpu.cores,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
async function collectMetrics() {
|
|
23
|
+
// CPU Usage
|
|
24
|
+
const cpuLoad = await systeminformation_1.default.currentLoad();
|
|
25
|
+
const cpuUsage = cpuLoad.currentLoad;
|
|
26
|
+
// Memory Usage
|
|
27
|
+
const mem = await systeminformation_1.default.mem();
|
|
28
|
+
const memoryUsage = (mem.used / mem.total) * 100;
|
|
29
|
+
const memoryTotal = mem.total;
|
|
30
|
+
const memoryUsed = mem.used;
|
|
31
|
+
// Disk Usage
|
|
32
|
+
const fsSize = await systeminformation_1.default.fsSize();
|
|
33
|
+
const mainDisk = fsSize[0];
|
|
34
|
+
const diskUsage = mainDisk ? (mainDisk.used / mainDisk.size) * 100 : 0;
|
|
35
|
+
const diskTotal = mainDisk ? mainDisk.size : 0;
|
|
36
|
+
const diskUsed = mainDisk ? mainDisk.used : 0;
|
|
37
|
+
// Network Usage (calculate rate per second)
|
|
38
|
+
const networkStats = await systeminformation_1.default.networkStats();
|
|
39
|
+
const mainInterface = networkStats[0];
|
|
40
|
+
let networkRx = 0;
|
|
41
|
+
let networkTx = 0;
|
|
42
|
+
if (mainInterface && lastNetworkStats) {
|
|
43
|
+
const timeDiff = (Date.now() - lastNetworkStats.timestamp) / 1000; // seconds
|
|
44
|
+
networkRx = (mainInterface.rx_bytes - lastNetworkStats.rx) / timeDiff;
|
|
45
|
+
networkTx = (mainInterface.tx_bytes - lastNetworkStats.tx) / timeDiff;
|
|
46
|
+
}
|
|
47
|
+
if (mainInterface) {
|
|
48
|
+
lastNetworkStats = {
|
|
49
|
+
rx: mainInterface.rx_bytes,
|
|
50
|
+
tx: mainInterface.tx_bytes,
|
|
51
|
+
timestamp: Date.now(),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
// Uptime
|
|
55
|
+
const uptime = os_1.default.uptime();
|
|
56
|
+
return {
|
|
57
|
+
cpuUsage,
|
|
58
|
+
memoryUsage,
|
|
59
|
+
memoryTotal,
|
|
60
|
+
memoryUsed,
|
|
61
|
+
diskUsage,
|
|
62
|
+
diskTotal,
|
|
63
|
+
diskUsed,
|
|
64
|
+
networkRx,
|
|
65
|
+
networkTx,
|
|
66
|
+
uptime,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
async function collectProcesses() {
|
|
70
|
+
const processes = await systeminformation_1.default.processes();
|
|
71
|
+
return processes.list
|
|
72
|
+
.filter((p) => p.cpu > 0 || p.mem > 0)
|
|
73
|
+
.sort((a, b) => b.cpu - a.cpu)
|
|
74
|
+
.slice(0, 20)
|
|
75
|
+
.map((p) => ({
|
|
76
|
+
pid: p.pid,
|
|
77
|
+
name: p.name,
|
|
78
|
+
cpu: p.cpu,
|
|
79
|
+
memory: p.mem,
|
|
80
|
+
}));
|
|
81
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const dotenv_1 = __importDefault(require("dotenv"));
|
|
8
|
+
const axios_1 = __importDefault(require("axios"));
|
|
9
|
+
const os_1 = __importDefault(require("os"));
|
|
10
|
+
const collector_1 = require("./collector");
|
|
11
|
+
// Load environment variables
|
|
12
|
+
dotenv_1.default.config();
|
|
13
|
+
const SERVER_URL = process.env.SERVER_URL || "http://localhost:4000";
|
|
14
|
+
const INSTANCE_NAME = process.env.INSTANCE_NAME || os_1.default.hostname();
|
|
15
|
+
const REGION = process.env.REGION || "unknown";
|
|
16
|
+
const INSTANCE_ID = process.env.INSTANCE_ID || os_1.default.hostname();
|
|
17
|
+
const COLLECTION_INTERVAL = parseInt(process.env.COLLECTION_INTERVAL || "10") * 1000;
|
|
18
|
+
let instanceInfo = null;
|
|
19
|
+
async function initializeAgent() {
|
|
20
|
+
try {
|
|
21
|
+
console.log("š Initializing Armada Watch Agent...");
|
|
22
|
+
console.log(`š” Server: ${SERVER_URL}`);
|
|
23
|
+
console.log(`š„ļø Instance: ${INSTANCE_NAME}`);
|
|
24
|
+
console.log(`š Region: ${REGION}`);
|
|
25
|
+
// Get system info once at startup
|
|
26
|
+
const sysInfo = await (0, collector_1.getSystemInfo)();
|
|
27
|
+
instanceInfo = {
|
|
28
|
+
instanceId: INSTANCE_ID,
|
|
29
|
+
name: INSTANCE_NAME,
|
|
30
|
+
privateIp: getLocalIpAddress(),
|
|
31
|
+
publicIp: await getPublicIpAddress(),
|
|
32
|
+
region: REGION,
|
|
33
|
+
platform: sysInfo.platform,
|
|
34
|
+
arch: sysInfo.arch,
|
|
35
|
+
hostname: sysInfo.hostname,
|
|
36
|
+
cpuModel: sysInfo.cpuModel,
|
|
37
|
+
cpuCores: sysInfo.cpuCores,
|
|
38
|
+
};
|
|
39
|
+
console.log("ā
Agent initialized successfully");
|
|
40
|
+
console.log(`š Collection interval: ${COLLECTION_INTERVAL / 1000}s`);
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
console.error("ā Failed to initialize agent:", error);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async function sendMetrics() {
|
|
48
|
+
try {
|
|
49
|
+
const metrics = await (0, collector_1.collectMetrics)();
|
|
50
|
+
const processes = await (0, collector_1.collectProcesses)();
|
|
51
|
+
const payload = {
|
|
52
|
+
instanceInfo,
|
|
53
|
+
metrics: {
|
|
54
|
+
instanceId: INSTANCE_ID,
|
|
55
|
+
...metrics,
|
|
56
|
+
},
|
|
57
|
+
processes,
|
|
58
|
+
};
|
|
59
|
+
await axios_1.default.post(`${SERVER_URL}/api/metrics`, payload, {
|
|
60
|
+
timeout: 5000,
|
|
61
|
+
});
|
|
62
|
+
console.log(`ā Metrics sent successfully - CPU: ${metrics.cpuUsage.toFixed(1)}% | Memory: ${metrics.memoryUsage.toFixed(1)}%`);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
if (axios_1.default.isAxiosError(error)) {
|
|
66
|
+
console.error(`ā Failed to send metrics: ${error.message}`);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
console.error("ā Failed to collect metrics:", error);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function getLocalIpAddress() {
|
|
74
|
+
const networkInterfaces = os_1.default.networkInterfaces();
|
|
75
|
+
for (const name of Object.keys(networkInterfaces)) {
|
|
76
|
+
const interfaces = networkInterfaces[name];
|
|
77
|
+
if (!interfaces)
|
|
78
|
+
continue;
|
|
79
|
+
for (const iface of interfaces) {
|
|
80
|
+
// Skip internal and non-IPv4 addresses
|
|
81
|
+
if (iface.family === "IPv4" && !iface.internal) {
|
|
82
|
+
return iface.address;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return "127.0.0.1";
|
|
87
|
+
}
|
|
88
|
+
async function getPublicIpAddress() {
|
|
89
|
+
try {
|
|
90
|
+
// Try AWS metadata service first (for EC2 instances)
|
|
91
|
+
const response = await axios_1.default.get("http://169.254.169.254/latest/meta-data/public-ipv4", {
|
|
92
|
+
timeout: 1000,
|
|
93
|
+
});
|
|
94
|
+
return response.data;
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// If not on AWS or no public IP, return undefined
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async function main() {
|
|
102
|
+
await initializeAgent();
|
|
103
|
+
// Send metrics immediately
|
|
104
|
+
await sendMetrics();
|
|
105
|
+
// Set up periodic collection
|
|
106
|
+
setInterval(sendMetrics, COLLECTION_INTERVAL);
|
|
107
|
+
// Handle graceful shutdown
|
|
108
|
+
process.on("SIGINT", () => {
|
|
109
|
+
console.log("\nš Shutting down agent...");
|
|
110
|
+
process.exit(0);
|
|
111
|
+
});
|
|
112
|
+
process.on("SIGTERM", () => {
|
|
113
|
+
console.log("\nš Shutting down agent...");
|
|
114
|
+
process.exit(0);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
main().catch((error) => {
|
|
118
|
+
console.error("Fatal error:", error);
|
|
119
|
+
process.exit(1);
|
|
120
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gazzehamine/armada-watch-agent",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Monitoring agent for Armada Watch - EC2 instance monitoring",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"armada-watch-agent": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"start": "node dist/index.js",
|
|
12
|
+
"dev": "ts-node-dev --respawn src/index.ts",
|
|
13
|
+
"prepublishOnly": "npm run build"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"monitoring",
|
|
17
|
+
"ec2",
|
|
18
|
+
"aws",
|
|
19
|
+
"metrics",
|
|
20
|
+
"devops",
|
|
21
|
+
"observability"
|
|
22
|
+
],
|
|
23
|
+
"author": "Gazzeh Amine",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://github.com/gazzehamine/armada-watch.git",
|
|
28
|
+
"directory": "agent"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist",
|
|
32
|
+
"README.md",
|
|
33
|
+
".env.example"
|
|
34
|
+
],
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"axios": "^1.6.0",
|
|
37
|
+
"dotenv": "^16.3.1",
|
|
38
|
+
"systeminformation": "^5.21.20"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/node": "^20.8.10",
|
|
42
|
+
"ts-node-dev": "^2.0.0",
|
|
43
|
+
"typescript": "^5.9.3"
|
|
44
|
+
},
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=18.0.0"
|
|
47
|
+
}
|
|
48
|
+
}
|