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.
@@ -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;
@@ -18,7 +18,7 @@ function TagCountPack() {
18
18
  Pack.call(this);
19
19
  this.category = ''
20
20
  this.tagHash = 0
21
- this.originOid = 0
21
+ this.originOid = null;
22
22
  this.tags = new MapValue()
23
23
  this.fields = new MapValue()
24
24
  this.read_size
@@ -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;
@@ -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
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "whatap",
3
3
  "homepage": "http://www.whatap.io",
4
- "version": "0.5.24",
5
- "releaseDate": "20250805",
4
+ "version": "0.5.26",
5
+ "releaseDate": "20250825",
6
6
  "description": "Monitoring and Profiling Service",
7
7
  "main": "index.js",
8
8
  "scripts": {},