@bitcall/pm2-pulse-agent 1.0.1-beta.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/README.md ADDED
@@ -0,0 +1 @@
1
+ # MONITORING-SERVICE
package/agent.js ADDED
@@ -0,0 +1,179 @@
1
+ const os = require('os')
2
+ const { exec } = require('child_process')
3
+ const grpc = require('@grpc/grpc-js')
4
+ const protoLoader = require('@grpc/proto-loader')
5
+ require('dotenv').config()
6
+
7
+ // Fixed agent defaults (only token + thresholds are configurable)
8
+ const DEFAULT_SERVER = 'localhost:50051'
9
+ const DEFAULT_VPS_ID = 'server-' + os.hostname()
10
+ const DEFAULT_CHECK_INTERVAL = 10 // seconds (accurate CPU measurement)
11
+ const DEFAULT_CPU_THRESHOLD = 70
12
+ const DEFAULT_RAM_THRESHOLD = 80
13
+ const DEFAULT_AUTH_TOKEN = process.env.AUTH_TOKEN || ''
14
+
15
+ class MonitoringAgent {
16
+ static instance = null
17
+
18
+ static getInstance() {
19
+ if (!MonitoringAgent.instance) {
20
+ MonitoringAgent.instance = new MonitoringAgent()
21
+ }
22
+ return MonitoringAgent.instance
23
+ }
24
+
25
+ constructor() {
26
+ if (MonitoringAgent.instance) {
27
+ throw new Error('MonitoringAgent is a singleton. Use MonitoringAgent.getInstance().')
28
+ }
29
+
30
+ this.server = process.env.MONITOR_SERVER || DEFAULT_SERVER
31
+ this.vpsId = process.env.VPS_ID || DEFAULT_VPS_ID
32
+ this.checkInterval = DEFAULT_CHECK_INTERVAL
33
+ this.authToken = DEFAULT_AUTH_TOKEN
34
+ this.cpuThreshold = parseInt(process.env.CPU_THRESHOLD, 10) || DEFAULT_CPU_THRESHOLD
35
+ this.ramThreshold = parseInt(process.env.RAM_THRESHOLD, 10) || DEFAULT_RAM_THRESHOLD
36
+
37
+ const credentials = grpc.credentials.createInsecure()
38
+ this.client = new proto.MonitoringService(this.server, credentials)
39
+ }
40
+
41
+ getCpu() {
42
+ return new Promise(resolve => {
43
+ const startSnapshot = os.cpus()
44
+ const startTotals = startSnapshot.map(c => Object.values(c.times).reduce((a, b) => a + b))
45
+ const startIdle = startSnapshot.reduce((acc, c) => acc + c.times.idle, 0)
46
+
47
+ // Wait 2 seconds for accurate measurement
48
+ setTimeout(() => {
49
+ const endSnapshot = os.cpus()
50
+ const endTotals = endSnapshot.map(c => Object.values(c.times).reduce((a, b) => a + b))
51
+ const endIdle = endSnapshot.reduce((acc, c) => acc + c.times.idle, 0)
52
+
53
+ const totalStart = startTotals.reduce((a, b) => a + b, 0)
54
+ const totalEnd = endTotals.reduce((a, b) => a + b, 0)
55
+
56
+ const idleDiff = endIdle - startIdle
57
+ const totalDiff = totalEnd - totalStart
58
+
59
+ const usage = totalDiff > 0 ? 100 - (100 * idleDiff) / totalDiff : 0
60
+ resolve(Math.max(0, Math.min(100, Math.round(usage))))
61
+ }, 2000)
62
+ })
63
+ }
64
+
65
+ getRamStats() {
66
+ const totalBytes = os.totalmem()
67
+ const freeBytes = os.freemem()
68
+ const usedBytes = Math.max(0, totalBytes - freeBytes)
69
+ const percent = totalBytes > 0 ? Math.round((usedBytes / totalBytes) * 100) : 0
70
+ return {
71
+ percent,
72
+ usedMb: Math.round(usedBytes / 1024 / 1024),
73
+ totalMb: Math.round(totalBytes / 1024 / 1024)
74
+ }
75
+ }
76
+
77
+ getPm2() {
78
+ return new Promise(resolve => {
79
+ exec('pm2 jlist', (err, stdout) => {
80
+ if (err) return resolve([])
81
+ const list = JSON.parse(stdout)
82
+ resolve(
83
+ list.map(p => ({
84
+ name: p.name,
85
+ status: p.pm2_env?.status || 'unknown',
86
+ restarts: p.pm2_env?.restart_time || 0,
87
+ cpu_percent: p.monit?.cpu || 0,
88
+ memory_mb: p.monit?.memory ? Math.round(p.monit.memory / 1024 / 1024) : 0
89
+ }))
90
+ )
91
+ })
92
+ })
93
+ }
94
+
95
+ async sendMetrics(isUrgent = false) {
96
+ const cpu = await this.getCpu()
97
+ const ramStats = this.getRamStats()
98
+ const ramPercent = ramStats.percent
99
+ const processes = await this.getPm2()
100
+
101
+ const alerts = []
102
+ if (cpu > this.cpuThreshold) alerts.push(`High CPU: ${cpu}%`)
103
+ if (ramPercent > this.ramThreshold) alerts.push(`High RAM: ${ramPercent}%`)
104
+ processes.forEach(p => {
105
+ if (p.status !== 'online') alerts.push(`${p.name} is ${p.status}`)
106
+ })
107
+
108
+ const hasAlerts = alerts.length > 0
109
+ console.log(
110
+ `[${new Date().toLocaleTimeString()}] CPU: ${cpu}% | RAM: ${ramStats.usedMb}/${ramStats.totalMb}MB (${ramPercent}%) | Alerts: ${alerts.length}${
111
+ hasAlerts ? ' 🚨' : ''
112
+ }`
113
+ )
114
+
115
+ const metricsMessage = {
116
+ vps_id: this.vpsId,
117
+ cpu,
118
+ ram: ramPercent,
119
+ ram_used_mb: ramStats.usedMb,
120
+ ram_total_mb: ramStats.totalMb,
121
+ processes,
122
+ alerts,
123
+ urgent: isUrgent || hasAlerts,
124
+ timestamp: Date.now()
125
+ }
126
+
127
+ console.log('DEBUG - Sending message:', JSON.stringify(metricsMessage, null, 2))
128
+
129
+ // Add auth token to metadata if configured
130
+ const metadata = new grpc.Metadata()
131
+ if (this.authToken) {
132
+ metadata.add('authorization', `Bearer ${this.authToken}`)
133
+ }
134
+
135
+ this.client.SendMetrics(metricsMessage, metadata, (err, response) => {
136
+ if (err) console.error('Error:', err.message)
137
+ else if (hasAlerts) console.log('✓ Alert sent to server')
138
+ })
139
+ }
140
+
141
+ start() {
142
+ const intervalSeconds = parseInt(this.checkInterval, 10)
143
+ console.log('='.repeat(60))
144
+ console.log('🚀 PM2 Monitoring Agent Started')
145
+ console.log('='.repeat(60))
146
+ console.log(`VPS ID: ${this.vpsId}`)
147
+ console.log(`Server: ${this.server}`)
148
+ console.log(`Authentication: ${this.authToken ? '✓ Enabled' : '✗ Disabled'}`)
149
+ console.log(`Check interval: ${intervalSeconds} second(s)`)
150
+ console.log(`CPU Threshold: ${this.cpuThreshold}%`)
151
+ console.log(`RAM Threshold: ${this.ramThreshold}%`)
152
+ console.log('='.repeat(60) + '\n')
153
+
154
+ // Run once on start
155
+ this.sendMetrics()
156
+
157
+ // Then run on interval
158
+ setInterval(async () => {
159
+ await this.sendMetrics()
160
+ }, intervalSeconds * 1000)
161
+ }
162
+ }
163
+
164
+ // Load proto
165
+ const proto = grpc.loadPackageDefinition(
166
+ protoLoader.loadSync('monitoring.proto', {
167
+ keepCase: true,
168
+ longs: String,
169
+ enums: String,
170
+ defaults: true,
171
+ oneofs: true
172
+ })
173
+ ).monitoring
174
+
175
+ module.exports = MonitoringAgent
176
+
177
+ if (require.main === module) {
178
+ MonitoringAgent.getInstance().start()
179
+ }
@@ -0,0 +1,32 @@
1
+ syntax = "proto3";
2
+
3
+ package monitoring;
4
+
5
+ service MonitoringService {
6
+ rpc SendMetrics(MetricsData) returns (Response);
7
+ }
8
+
9
+ message MetricsData {
10
+ string vps_id = 1;
11
+ double cpu = 2;
12
+ double ram = 3;
13
+ double ram_used_mb = 8;
14
+ double ram_total_mb = 9;
15
+ repeated Process processes = 4;
16
+ repeated string alerts = 5;
17
+ bool urgent = 6;
18
+ int64 timestamp = 7;
19
+ }
20
+
21
+ message Process {
22
+ string name = 1;
23
+ string status = 2;
24
+ int32 restarts = 3;
25
+ double cpu_percent = 4;
26
+ int64 memory_mb = 5;
27
+ }
28
+
29
+ message Response {
30
+ bool success = 1;
31
+ string message = 2;
32
+ }
package/package.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "@bitcall/pm2-pulse-agent",
3
+ "version": "1.0.1-beta.0",
4
+ "scripts": {
5
+ "agent": "node agent.js",
6
+ "monitor": "node agent.js",
7
+ "setup-agent": "node scripts/setup-agent.js"
8
+ },
9
+ "dependencies": {
10
+ "@grpc/grpc-js": "^1.10.1",
11
+ "@grpc/proto-loader": "^0.7.10",
12
+ "dotenv": "^17.2.3"
13
+ }
14
+ }
@@ -0,0 +1,72 @@
1
+ const fs = require('fs')
2
+ const path = require('path')
3
+ const readline = require('readline')
4
+
5
+ const envPath = path.join(process.cwd(), '.env')
6
+
7
+ function loadEnvFile() {
8
+ if (!fs.existsSync(envPath)) return []
9
+ return fs.readFileSync(envPath, 'utf8').split(/\r?\n/)
10
+ }
11
+
12
+ function getEnvValue(lines, key) {
13
+ const prefix = `${key}=`
14
+ const line = lines.find(l => l.startsWith(prefix))
15
+ if (!line) return ''
16
+ return line.slice(prefix.length)
17
+ }
18
+
19
+ function upsertEnvValue(lines, key, value) {
20
+ const prefix = `${key}=`
21
+ const idx = lines.findIndex(l => l.startsWith(prefix))
22
+ const line = `${key}=${value}`
23
+ if (idx === -1) {
24
+ lines.push(line)
25
+ } else {
26
+ lines[idx] = line
27
+ }
28
+ }
29
+
30
+ function askQuestion(rl, prompt) {
31
+ return new Promise(resolve => {
32
+ rl.question(prompt, answer => resolve(answer.trim()))
33
+ })
34
+ }
35
+
36
+ async function run() {
37
+ const lines = loadEnvFile()
38
+ const currentToken = getEnvValue(lines, 'AUTH_TOKEN')
39
+ const currentServer = getEnvValue(lines, 'MONITOR_SERVER') || 'localhost:50051'
40
+ const currentVpsId = getEnvValue(lines, 'VPS_ID') || `server-${require('os').hostname()}`
41
+
42
+ const rl = readline.createInterface({
43
+ input: process.stdin,
44
+ output: process.stdout
45
+ })
46
+
47
+ const tokenPrompt = `Secret token (AUTH_TOKEN) [${currentToken ? 'set' : 'empty'}]: `
48
+ const serverPrompt = `Monitor server (MONITOR_SERVER) [${currentServer}]: `
49
+ const vpsPrompt = `Default VPS ID (VPS_ID) [${currentVpsId}]: `
50
+
51
+ const tokenInput = await askQuestion(rl, tokenPrompt)
52
+ const serverInput = await askQuestion(rl, serverPrompt)
53
+ const vpsInput = await askQuestion(rl, vpsPrompt)
54
+
55
+ rl.close()
56
+
57
+ const finalToken = tokenInput !== '' ? tokenInput : currentToken
58
+ const finalServer = serverInput !== '' ? serverInput : currentServer
59
+ const finalVpsId = vpsInput !== '' ? vpsInput : currentVpsId
60
+
61
+ upsertEnvValue(lines, 'AUTH_TOKEN', finalToken)
62
+ upsertEnvValue(lines, 'MONITOR_SERVER', finalServer)
63
+ upsertEnvValue(lines, 'VPS_ID', finalVpsId)
64
+
65
+ fs.writeFileSync(envPath, lines.join('\n'))
66
+ console.log(`Saved: ${envPath}`)
67
+ }
68
+
69
+ run().catch(err => {
70
+ console.error('Setup failed:', err.message)
71
+ process.exit(1)
72
+ })