whatap 0.5.24 → 0.5.26
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/lib/conf/config-default.js +2 -1
- package/lib/counter/counter-manager.js +4 -2
- package/lib/counter/task/gc-action.js +153 -0
- package/lib/observers/process-observer.js +72 -1
- package/lib/pack/tagcount-pack.js +1 -1
- package/lib/util/nodeutil.js +47 -2
- package/lib/value/map-value.js +11 -2
- package/package.json +2 -2
|
@@ -283,7 +283,8 @@ var ConfigDefault = {
|
|
|
283
283
|
"trace_mtrace_traceparent_key": str("trace_mtrace_traceparent_key", "traceparent"),
|
|
284
284
|
"txtext_txname_enabled": bool("txtext_txname_enabled", true),
|
|
285
285
|
"ignore_env_variable_set": str("ignore_env_variable_set", ""),
|
|
286
|
-
"profile_httpc_start_step_enabled": bool("profile_httpc_start_step_enabled", false)
|
|
286
|
+
"profile_httpc_start_step_enabled": bool("profile_httpc_start_step_enabled", false),
|
|
287
|
+
"perfx_nodejs_gc_enabled": bool("perfx_nodejs_gc_enabled", true)
|
|
287
288
|
|
|
288
289
|
};
|
|
289
290
|
|
|
@@ -18,6 +18,7 @@ var CounterPack = require('../pack/counter-pack'),
|
|
|
18
18
|
Sql = require('./task/sql'),
|
|
19
19
|
HttpC = require('./task/httpc'),
|
|
20
20
|
MeteringInfo = require('./task/metering-info'),
|
|
21
|
+
GCAction = require('./task/gc-action'),
|
|
21
22
|
StatError = require('../stat/stat-error'),
|
|
22
23
|
TagCounterPack = require('../pack/tagcount-pack'),
|
|
23
24
|
TraceContextManager = require('../trace/trace-context-manager'),
|
|
@@ -32,7 +33,7 @@ var CounterPack = require('../pack/counter-pack'),
|
|
|
32
33
|
function CounterManager(agent) {
|
|
33
34
|
this.agent = agent;
|
|
34
35
|
this.time = Date.now();
|
|
35
|
-
|
|
36
|
+
|
|
36
37
|
this.intervalIndex = undefined;
|
|
37
38
|
};
|
|
38
39
|
CounterManager.plugin={}
|
|
@@ -53,11 +54,12 @@ CounterManager.prototype.run = function () {
|
|
|
53
54
|
tasks.push(new GCStat());
|
|
54
55
|
tasks.push(new Sql());
|
|
55
56
|
tasks.push(new HttpC());
|
|
57
|
+
tasks.push(new GCAction());
|
|
56
58
|
|
|
57
59
|
// metering
|
|
58
60
|
tasks.push(new MeteringInfo());
|
|
59
61
|
self.intervalIndex = setInterval(function(){
|
|
60
|
-
self.process(tasks);
|
|
62
|
+
self.process(tasks);
|
|
61
63
|
},5000);
|
|
62
64
|
self.process(tasks);
|
|
63
65
|
});
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2025 the WHATAP project authors. All rights reserved.
|
|
3
|
+
* Use of this source code is governed by a license that
|
|
4
|
+
* can be found in the LICENSE file.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
var CounterTask = require('./counter-task'),
|
|
8
|
+
DataPackSender = require('../../data/datapack-sender'),
|
|
9
|
+
conf = require('../../conf/configure'),
|
|
10
|
+
DateUtil = require('../../util/dateutil'),
|
|
11
|
+
Logger = require('../../logger'),
|
|
12
|
+
TagCountPack = require('../../pack/tagcount-pack'),
|
|
13
|
+
HashUtil = require('../../util/hashutil'),
|
|
14
|
+
SecurityMaster = require('../../../lib/net/security-master'),
|
|
15
|
+
NodeUtil = require('../../util/nodeutil'),
|
|
16
|
+
IPUtil = require('../../util/iputil');
|
|
17
|
+
|
|
18
|
+
const { PerformanceObserver } = require('perf_hooks');
|
|
19
|
+
|
|
20
|
+
function GCAction() {
|
|
21
|
+
CounterTask.call(this);
|
|
22
|
+
this.isObserverSupported = false;
|
|
23
|
+
this.checkGCObserverSupport();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
GCAction.prototype = new CounterTask();
|
|
27
|
+
GCAction.prototype.constructor = GCAction;
|
|
28
|
+
|
|
29
|
+
GCAction.prototype.checkGCObserverSupport = function () {
|
|
30
|
+
if (NodeUtil.hasStableGCObserver()) {
|
|
31
|
+
this.isObserverSupported = true;
|
|
32
|
+
Logger.print("WHATAP-GC-001", `GC Observer supported (Node.js ${NodeUtil.getNodeVersion()})`, false);
|
|
33
|
+
} else {
|
|
34
|
+
this.isObserverSupported = false;
|
|
35
|
+
Logger.print("WHATAP-GC-002", `GC Observer not supported (Node.js ${NodeUtil.getNodeVersion()}, requires 16.7+ or 18+)`, false);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
GCAction.prototype.process = function (p) {
|
|
40
|
+
try {
|
|
41
|
+
if (!conf.getProperty('perfx_nodejs_gc_enabled', false) || !this.isObserverSupported) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
this.nodejs_memory_gc(p);
|
|
46
|
+
} catch (e) {
|
|
47
|
+
Logger.printError("WHATAP-GC-003", "NodeJS GC process error", e, false);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
GCAction.prototype.nodejs_memory_gc = function (p) {
|
|
52
|
+
const gcStats = new Map();
|
|
53
|
+
const self = this;
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
// PerformanceObserver를 사용해서 현재까지의 GC 데이터 수집
|
|
57
|
+
const observer = new PerformanceObserver((list) => {
|
|
58
|
+
list.getEntries().forEach((entry) => {
|
|
59
|
+
if (entry.entryType === 'gc') {
|
|
60
|
+
const gcName = self.getGCTypeName(entry.kind);
|
|
61
|
+
|
|
62
|
+
if (!gcStats.has(gcName)) {
|
|
63
|
+
gcStats.set(gcName, {
|
|
64
|
+
name: gcName,
|
|
65
|
+
collectionCount: 0,
|
|
66
|
+
collectionTime: 0
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const stats = gcStats.get(gcName);
|
|
71
|
+
stats.collectionCount++;
|
|
72
|
+
stats.collectionTime += entry.duration;
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// 데이터 수집 완료 후 즉시 처리
|
|
77
|
+
observer.disconnect();
|
|
78
|
+
|
|
79
|
+
// 수집된 데이터가 있으면 전송
|
|
80
|
+
if (gcStats.size > 0) {
|
|
81
|
+
self.sendGCData(p, gcStats);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
observer.observe({ type: 'gc' });
|
|
86
|
+
} catch (e) {
|
|
87
|
+
Logger.printError("WHATAP-GC-004", "Failed to collect GC data", e, false);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
GCAction.prototype.sendGCData = function (p, gc_stats) {
|
|
92
|
+
const pk = new TagCountPack();
|
|
93
|
+
pk.time = Math.floor(Date.now() / 1000) * 1000;
|
|
94
|
+
pk.category = "nodejsx_memory_gc";
|
|
95
|
+
|
|
96
|
+
pk.putTagString("oname", SecurityMaster.ONAME);
|
|
97
|
+
pk.putTagString("host_ip", IPUtil.getIp());
|
|
98
|
+
pk.putTagInt("pid", process.pid);
|
|
99
|
+
pk.putTagInt("!rectype", 2);
|
|
100
|
+
|
|
101
|
+
const idLv = pk.fields.internList("@id");
|
|
102
|
+
const nameLv = pk.fields.internList("name");
|
|
103
|
+
const countLv = pk.fields.internList("CollectionCount");
|
|
104
|
+
const timeLv = pk.fields.internList("CollectionTime");
|
|
105
|
+
|
|
106
|
+
// GC 통계 데이터 추가
|
|
107
|
+
for (const [gc_name, stats] of gc_stats) {
|
|
108
|
+
idLv.addLong(HashUtil.hashFromString(gc_name));
|
|
109
|
+
nameLv.addString(gc_name);
|
|
110
|
+
countLv.addLong(stats.collectionCount);
|
|
111
|
+
timeLv.addLong(Math.round(stats.collectionTime));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
DataPackSender.sendTagCounterPack(pk);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
GCAction.prototype.getGCTypeName = function (kind) {
|
|
118
|
+
const major = NodeUtil.getNodeMajorVersion();
|
|
119
|
+
|
|
120
|
+
if (major >= 22) {
|
|
121
|
+
switch (kind) {
|
|
122
|
+
case 1: return 'V8 Scavenger';
|
|
123
|
+
case 2: return 'V8 Minor Mark Sweep';
|
|
124
|
+
case 4: return 'V8 Mark Sweep Compact';
|
|
125
|
+
case 8: return 'V8 Incremental Marking';
|
|
126
|
+
case 16: return 'V8 Process Weak Callbacks';
|
|
127
|
+
case 31: return 'V8 All';
|
|
128
|
+
default: return `V8 GC Type ${kind}`;
|
|
129
|
+
}
|
|
130
|
+
} else if (major >= 18) {
|
|
131
|
+
switch (kind) {
|
|
132
|
+
case 1: return 'V8 Scavenger';
|
|
133
|
+
case 2: return 'V8 Minor Mark Compact';
|
|
134
|
+
case 4: return 'V8 Mark Sweep Compact';
|
|
135
|
+
case 8: return 'V8 Incremental Marking';
|
|
136
|
+
case 16: return 'V8 Process Weak Callbacks';
|
|
137
|
+
case 31: return 'V8 All';
|
|
138
|
+
default: return `V8 GC Type ${kind}`;
|
|
139
|
+
}
|
|
140
|
+
} else {
|
|
141
|
+
switch (kind) {
|
|
142
|
+
case 1: return 'V8 Scavenger';
|
|
143
|
+
case 2: return 'V8 Mark Sweep Compact';
|
|
144
|
+
case 4: return 'V8 Incremental Marking';
|
|
145
|
+
case 8: return 'V8 Process Weak Callbacks';
|
|
146
|
+
case 15: return 'V8 All';
|
|
147
|
+
default: return `V8 GC Type ${kind}`;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
module.exports = GCAction;
|
|
@@ -7,11 +7,12 @@
|
|
|
7
7
|
const TraceContextManager = require('../trace/trace-context-manager');
|
|
8
8
|
const conf = require('../conf/configure');
|
|
9
9
|
const LogTracer = require('../logsink/log-tracer');
|
|
10
|
+
const Logger = require('../logger');
|
|
10
11
|
|
|
11
12
|
let logsink_enabled = conf.getProperty('logsink_enabled', false);
|
|
12
13
|
let logTracer = logsink_enabled ? new LogTracer() : null;
|
|
13
14
|
|
|
14
|
-
conf.on('logsink_enabled', function(newProperty) {
|
|
15
|
+
conf.on('logsink_enabled', function (newProperty) {
|
|
15
16
|
logsink_enabled = newProperty;
|
|
16
17
|
logTracer = logsink_enabled ? new LogTracer() : null;
|
|
17
18
|
});
|
|
@@ -19,12 +20,14 @@ conf.on('logsink_enabled', function(newProperty) {
|
|
|
19
20
|
const ProcessObserver = function (agent) {
|
|
20
21
|
this.agent = agent;
|
|
21
22
|
this.packages = ['process'];
|
|
23
|
+
this.exitHandlersRegistered = false;
|
|
22
24
|
};
|
|
23
25
|
|
|
24
26
|
ProcessObserver.prototype.inject = function (mod, moduleName) {
|
|
25
27
|
this._hookNextTick(mod);
|
|
26
28
|
this._hookStdOutWrite();
|
|
27
29
|
this._hookStdErrWrite();
|
|
30
|
+
this._hookProcessExit(mod);
|
|
28
31
|
};
|
|
29
32
|
|
|
30
33
|
ProcessObserver.prototype._hookNextTick = function (mod) {
|
|
@@ -70,4 +73,72 @@ ProcessObserver.prototype._hookStdErrWrite = function () {
|
|
|
70
73
|
});
|
|
71
74
|
};
|
|
72
75
|
|
|
76
|
+
ProcessObserver.prototype._hookProcessExit = function (mod) {
|
|
77
|
+
if (this.exitHandlersRegistered) {
|
|
78
|
+
Logger.print("WHATAP-901", "[ProcessObserver] Exit handlers already registered, skipping...", false)
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const self = this;
|
|
82
|
+
|
|
83
|
+
// SIGTERM (graceful shutdown)
|
|
84
|
+
process.on('SIGTERM', function () {
|
|
85
|
+
const message = 'Process termination requested by system or process manager (SIGTERM) - Usually from service stop or container shutdown';
|
|
86
|
+
Logger.print('WHATAP-902', message, false);
|
|
87
|
+
setTimeout(() => process.exit(0), 100);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// SIGINT (Ctrl+C)
|
|
91
|
+
process.on('SIGINT', function () {
|
|
92
|
+
const message = 'Process interrupted by user (SIGINT) - Typically Ctrl+C or kill command';
|
|
93
|
+
Logger.print('WHATAP-903', message, false);
|
|
94
|
+
setTimeout(() => process.exit(0), 100);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// SIGHUP (hang up)
|
|
98
|
+
process.on('SIGHUP', function () {
|
|
99
|
+
const message = 'Process hangup signal received (SIGHUP) - Terminal disconnection or parent process terminated';
|
|
100
|
+
Logger.print('WHATAP-904', message, false);
|
|
101
|
+
setTimeout(() => process.exit(0), 100);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// SIGUSR1 (user-defined signal 1)
|
|
105
|
+
process.on('SIGUSR1', function () {
|
|
106
|
+
const message = 'User-defined signal 1 received (SIGUSR1) - Custom application signal';
|
|
107
|
+
Logger.print('WHATAP-905', message, false);
|
|
108
|
+
setTimeout(() => process.exit(0), 100);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// SIGUSR2 (user-defined signal 2)
|
|
112
|
+
process.on('SIGUSR2', function () {
|
|
113
|
+
const message = 'User-defined signal 2 received (SIGUSR2) - Custom application signal';
|
|
114
|
+
Logger.print('WHATAP-906', message, false);
|
|
115
|
+
setTimeout(() => process.exit(0), 100);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// 처리되지 않은 예외
|
|
119
|
+
// process.on('uncaughtException', function (err) {
|
|
120
|
+
// const message = `Unhandled exception caused process crash - Error: ${err.message}, File: ${err.stack ? err.stack.split('\n')[1] || 'unknown' : 'unknown'}`;
|
|
121
|
+
// Logger.print('WHATAP-907', message, err, false);
|
|
122
|
+
//
|
|
123
|
+
// // 로그 남긴 후 프로세스 종료
|
|
124
|
+
// setTimeout(() => {
|
|
125
|
+
// process.exit(1);
|
|
126
|
+
// }, 100);
|
|
127
|
+
// });
|
|
128
|
+
//
|
|
129
|
+
// // 처리되지 않은 Promise rejection
|
|
130
|
+
// process.on('unhandledRejection', function (reason, promise) {
|
|
131
|
+
// const message = `Unhandled Promise rejection may cause process termination - Reason: ${reason instanceof Error ? reason.message : String(reason)}`;
|
|
132
|
+
// const err = reason instanceof Error ? reason : new Error(String(reason));
|
|
133
|
+
// Logger.print('WHATAP-908', message, err, false);
|
|
134
|
+
//
|
|
135
|
+
// // 로그 남긴 후 프로세스 종료
|
|
136
|
+
// setTimeout(() => {
|
|
137
|
+
// process.exit(1);
|
|
138
|
+
// }, 100);
|
|
139
|
+
// });
|
|
140
|
+
|
|
141
|
+
this.exitHandlersRegistered = true;
|
|
142
|
+
};
|
|
143
|
+
|
|
73
144
|
module.exports.ProcessObserver = ProcessObserver;
|
package/lib/util/nodeutil.js
CHANGED
|
@@ -25,6 +25,7 @@ function getPackageJson() {
|
|
|
25
25
|
}
|
|
26
26
|
NodeUtil.packageJson = {};
|
|
27
27
|
}
|
|
28
|
+
|
|
28
29
|
function recursiveCall(fullPath, file, cb){
|
|
29
30
|
if(fullPath === undefined) return;
|
|
30
31
|
|
|
@@ -66,7 +67,51 @@ var NodeUtil = {
|
|
|
66
67
|
},
|
|
67
68
|
recursiveCall: recursiveCall,
|
|
68
69
|
getPackageJson : getPackageJson,
|
|
69
|
-
packageJson : {}
|
|
70
|
+
packageJson : {},
|
|
71
|
+
|
|
72
|
+
// Node.js 버전 정보 함수들 추가
|
|
73
|
+
getNodeVersion: function() {
|
|
74
|
+
return process.version;
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
getNodeMajorVersion: function() {
|
|
78
|
+
return parseInt(process.version.split('.')[0].substring(1));
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
getNodeMinorVersion: function() {
|
|
82
|
+
return parseInt(process.version.split('.')[1]);
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
getNodePatchVersion: function() {
|
|
86
|
+
return parseInt(process.version.split('.')[2]);
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
// GC Observer 지원 여부 체크
|
|
90
|
+
hasStableGCObserver: function() {
|
|
91
|
+
const major = this.getNodeMajorVersion();
|
|
92
|
+
const minor = this.getNodeMinorVersion();
|
|
93
|
+
|
|
94
|
+
// Node.js 18+ 또는 16.7+에서 안정적
|
|
95
|
+
return major >= 18 || (major === 16 && minor >= 7);
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
// 버전 비교 함수
|
|
99
|
+
compareNodeVersion: function(targetMajor, targetMinor, targetPatch) {
|
|
100
|
+
const major = this.getNodeMajorVersion();
|
|
101
|
+
const minor = this.getNodeMinorVersion();
|
|
102
|
+
const patch = this.getNodePatchVersion();
|
|
103
|
+
|
|
104
|
+
if (major > targetMajor) return 1;
|
|
105
|
+
if (major < targetMajor) return -1;
|
|
106
|
+
|
|
107
|
+
if (minor > targetMinor) return 1;
|
|
108
|
+
if (minor < targetMinor) return -1;
|
|
109
|
+
|
|
110
|
+
if (patch > targetPatch) return 1;
|
|
111
|
+
if (patch < targetPatch) return -1;
|
|
112
|
+
|
|
113
|
+
return 0; // 동일
|
|
114
|
+
}
|
|
70
115
|
};
|
|
71
116
|
|
|
72
|
-
module.exports = NodeUtil;
|
|
117
|
+
module.exports = NodeUtil;
|
package/lib/value/map-value.js
CHANGED
|
@@ -132,7 +132,7 @@ MapValue.prototype.getValueType = function() {
|
|
|
132
132
|
};
|
|
133
133
|
MapValue.prototype.write = function(dout) {
|
|
134
134
|
dout.writeDecimal(this.table.size());
|
|
135
|
-
var en = this.table.keys();
|
|
135
|
+
var en = this.table.keys();
|
|
136
136
|
while(en.hasMoreElements()) {
|
|
137
137
|
var key = en.nextElement();
|
|
138
138
|
dout.writeText(key);
|
|
@@ -160,7 +160,7 @@ MapValue.prototype.toObject = function() {
|
|
|
160
160
|
return this.table;
|
|
161
161
|
};
|
|
162
162
|
MapValue.prototype.putAllMap = function(m) {
|
|
163
|
-
var en = m.keys();
|
|
163
|
+
var en = m.keys();
|
|
164
164
|
while(en.hasMoreElements()) {
|
|
165
165
|
var key = en.nextElement();
|
|
166
166
|
var value =m.get(key);
|
|
@@ -173,4 +173,13 @@ MapValue.prototype.putAllMapValue = function(m) {
|
|
|
173
173
|
this.putAllMap(m);
|
|
174
174
|
};
|
|
175
175
|
|
|
176
|
+
MapValue.prototype.internList = function (key) {
|
|
177
|
+
let lv = this.table.get(key);
|
|
178
|
+
if(!lv){
|
|
179
|
+
lv = new ListValue()
|
|
180
|
+
this.table.put(key, lv);
|
|
181
|
+
}
|
|
182
|
+
return lv;
|
|
183
|
+
}
|
|
184
|
+
|
|
176
185
|
module.exports = MapValue;
|
package/package.json
CHANGED