whatap 0.5.25 → 0.5.27

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.
@@ -0,0 +1,77 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(grep -n \"ECONNREFUSED\" /Users/seunghunlee/agent_workspace/nodejs_agent/lib/observers/*.js /Users/seunghunlee/agent_workspace/nodejs_agent/lib/core/agent.js 2>/dev/null)",
5
+ "WebSearch",
6
+ "Bash(go env *)",
7
+ "Bash(ls -l \"$\\(go env GOPATH\\)/bin/govulncheck\")",
8
+ "Bash(brew list *)",
9
+ "Bash(brew search *)",
10
+ "Bash(brew info *)",
11
+ "Bash(echo \"--- exit $? ---\")",
12
+ "Bash(go version *)",
13
+ "WebFetch(domain:github.com)",
14
+ "Read(//Users/seunghunlee/workspace/**)",
15
+ "Read(//Users/seunghunlee/workspace/nodejs_docker_sample/**)",
16
+ "Bash(npm i *)",
17
+ "Bash(chmod +x /Users/seunghunlee/workspace/nodejs_docker_sample/repro.sh)",
18
+ "Bash(docker exec 273976be9e71 *)",
19
+ "Bash(docker rm *)",
20
+ "Bash(docker build *)",
21
+ "Bash(docker run *)",
22
+ "Bash(docker exec zombie-test *)",
23
+ "Bash(curl -s http://localhost:3000/)",
24
+ "Bash(curl -s http://localhost:3000/health)",
25
+ "Bash(curl -s http://localhost:3000/slow)",
26
+ "Bash(curl -s -o /dev/null -w \"status: %{http_code}\\\\n\" http://localhost:3000/error)",
27
+ "Bash(curl -s -o /dev/null -w \"%{http_code} \" http://localhost:3000/)",
28
+ "Bash(/usr/local/bin/docker exec *)",
29
+ "Bash(/usr/local/bin/docker build *)",
30
+ "Bash(/usr/local/bin/docker rm *)",
31
+ "Bash(/usr/local/bin/docker run *)",
32
+ "Bash(/bin/sleep 30)",
33
+ "Bash(echo \"[$\\(date +%T\\)] zombies=$\\(/usr/local/bin/docker exec zombie-test sh -c \"ps -eo stat | awk '\\\\$1 ~ /^Z/' | wc -l\"\\)\")",
34
+ "Bash(/bin/sleep 5)",
35
+ "Bash(/usr/local/bin/docker ps *)",
36
+ "Bash(/bin/sleep 6)",
37
+ "Bash(/usr/local/bin/docker logs *)",
38
+ "Bash(/usr/bin/curl -s http://localhost:3000/)",
39
+ "Bash(/usr/bin/curl -s http://localhost:3000/health)",
40
+ "Bash(/bin/sleep 7)",
41
+ "Bash(cat agent/index.js)",
42
+ "Bash(cat agent/writer.js)",
43
+ "Bash(node -e \"const d=require\\('./config/defaults.js'\\); console.log\\('flushInterval=', d.flushInterval\\)\")",
44
+ "Bash(cd *)",
45
+ "Bash(ls /Users/seunghunlee/workspace/nodejs_docker_sample/node_modules/whatap/lib)",
46
+ "Bash(ls /Users/seunghunlee/workspace/nodejs_docker_sample/node_modules/whatap/index.js)",
47
+ "Bash(node -e ' *)",
48
+ "Bash(export WHATAP_HOME=/tmp)",
49
+ "Bash(node --expose-gc -e ' *)",
50
+ "Bash(node -p \"process.platform+'/'+process.arch+' node '+process.version\")",
51
+ "Read(//Users/seunghunlee/agent_workspace/apm-go-agent/**)",
52
+ "Bash(node -e \"require\\('./lib/counter/task/metering-info'\\); console.log\\('module load OK'\\);\")",
53
+ "Bash(node --check lib/counter/task/metering-info.js)",
54
+ "Bash(node --check lib/util/cgroup-cpu.js)",
55
+ "Bash(node -e \"var m=require\\('./index.js'\\); console.log\\('exports keys:', Object.keys\\(m\\)\\);\")",
56
+ "Bash(docker version *)",
57
+ "Bash(rm -rf whatap-local)",
58
+ "Bash(rsync -a --exclude=.git --exclude=node_modules --exclude=logs --exclude=*.log /Users/seunghunlee/agent_workspace/nodejs_agent/ ./whatap-local/)",
59
+ "Bash(node *)",
60
+ "Bash(npm view *)",
61
+ "Bash(rsync -a --delete --exclude=.git --exclude=node_modules --exclude=logs --exclude=*.log /Users/seunghunlee/agent_workspace/nodejs_agent/ ./whatap-local/)",
62
+ "Bash(docker exec *)",
63
+ "Bash(curl -s --max-time 5 http://localhost:3001/health)",
64
+ "Bash(curl -s --max-time 5 http://localhost:3002/health)",
65
+ "Bash(sed -n '66,71p' lib/counter/counter-manager.js)",
66
+ "Bash(sed -n '166,168p' lib/conf/configure.js)",
67
+ "Bash(sed -n '40,70p' lib/kube/kube-client.js)",
68
+ "Bash(docker info *)",
69
+ "Bash(« stray *)",
70
+ "Bash(awk '{print $1}')",
71
+ "Bash(xargs -r docker rm -f)",
72
+ "Read(//Users/seunghunlee/agent_workspace/**)",
73
+ "Bash(net_udp_packet_queue_size=500 node -e ' *)",
74
+ "Bash(max_send_queue_size=500 node -e ' *)"
75
+ ]
76
+ }
77
+ }
@@ -284,7 +284,7 @@ var ConfigDefault = {
284
284
  "txtext_txname_enabled": bool("txtext_txname_enabled", true),
285
285
  "ignore_env_variable_set": str("ignore_env_variable_set", ""),
286
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
+ "perfx_nodejs_gc_enabled": bool("perfx_nodejs_gc_enabled", false)
288
288
 
289
289
  };
290
290
 
@@ -54,31 +54,41 @@ GCAction.prototype.nodejs_memory_gc = function (p) {
54
54
 
55
55
  try {
56
56
  // PerformanceObserver를 사용해서 현재까지의 GC 데이터 수집
57
+ // 콜백은 비동기로 실행되어 바깥 try/catch가 못 잡으므로, 예외가 고객 앱의
58
+ // uncaughtException으로 전파되지 않도록 콜백 전체를 보호한다.
57
59
  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
- });
60
+ try {
61
+ list.getEntries().forEach((entry) => {
62
+ if (entry.entryType === 'gc') {
63
+ const gcName = self.getGCTypeName(entry.kind);
64
+
65
+ if (!gcStats.has(gcName)) {
66
+ gcStats.set(gcName, {
67
+ name: gcName,
68
+ collectionCount: 0,
69
+ collectionTime: 0
70
+ });
71
+ }
72
+
73
+ const stats = gcStats.get(gcName);
74
+ stats.collectionCount++;
75
+ stats.collectionTime += entry.duration;
68
76
  }
77
+ });
69
78
 
70
- const stats = gcStats.get(gcName);
71
- stats.collectionCount++;
72
- stats.collectionTime += entry.duration;
73
- }
74
- });
75
-
76
- // 데이터 수집 완료 후 즉시 처리
77
- observer.disconnect();
79
+ // 데이터 수집 완료 후 즉시 처리
80
+ observer.disconnect();
78
81
 
79
- // 수집된 데이터가 있으면 전송
80
- if (gcStats.size > 0) {
81
- self.sendGCData(p, gcStats);
82
+ // 수집된 데이터가 있으면 전송
83
+ if (gcStats.size > 0) {
84
+ self.sendGCData(p, gcStats);
85
+ }
86
+ } catch (e) {
87
+ try {
88
+ observer.disconnect();
89
+ } catch (e2) {
90
+ }
91
+ Logger.printError("WHATAP-GC-005", "GC observer callback error", e, false);
82
92
  }
83
93
  });
84
94
 
@@ -13,6 +13,8 @@ var CounterTask = require('./counter-task'),
13
13
  TagCountPack = require('../../pack/tagcount-pack'),
14
14
  OType = require('../../pack/otype'),
15
15
  APEnum = require('../../pack/apenum'),
16
+ SecurityMaster = require('../../net/security-master'),
17
+ CgroupCpu = require('../../util/cgroup-cpu'),
16
18
  Util = require('../../util/index');
17
19
 
18
20
  function MeteringInfo() {
@@ -28,7 +30,7 @@ MeteringInfo.prototype = new CounterTask();
28
30
  MeteringInfo.prototype.constructor = MeteringInfo;
29
31
 
30
32
  MeteringInfo.prototype.process = function (p) {
31
- if (!conf.getProperty('metering_tagcount_enabled', false)) {
33
+ if (!this.isMeteringEnabled()) {
32
34
  return;
33
35
  }
34
36
 
@@ -47,8 +49,40 @@ MeteringInfo.prototype.process = function (p) {
47
49
  this.doProcess(p);
48
50
  };
49
51
 
52
+ // metering_container_enabled=true이면 (host product_uuid 없이도) 메터링을 전송한다.
53
+ MeteringInfo.prototype.isMeteringEnabled = function () {
54
+ return conf.getProperty('metering_tagcount_enabled', false) === true
55
+ || this.isContainerMeteringEnabled();
56
+ };
57
+
58
+ MeteringInfo.prototype.isContainerMeteringEnabled = function () {
59
+ return CgroupCpu.isContainerMeteringEnabled();
60
+ };
61
+
62
+ // metering_container_enabled=true이면 root 권한이 필요한 host product_uuid 대신
63
+ // OID hex를 컨테이너별 고유 식별자로 사용한다.
64
+ // (Go 에이전트 SecurityMaster: fmt.Sprintf("%08x", uint32(OID)) 와 동일)
65
+ MeteringInfo.prototype.resolveHostUUID = function () {
66
+ if (this.isContainerMeteringEnabled()) {
67
+ return this.getContainerUUID();
68
+ }
69
+ return this.getHostUUID(this.hostUUIDFileName);
70
+ };
71
+
72
+ MeteringInfo.prototype.getContainerUUID = function () {
73
+ const oid = SecurityMaster.OID;
74
+ if (!oid) {
75
+ return null;
76
+ }
77
+ var hex = (oid >>> 0).toString(16);
78
+ while (hex.length < 8) {
79
+ hex = '0' + hex;
80
+ }
81
+ return hex;
82
+ };
83
+
50
84
  MeteringInfo.prototype.doProcess = function (p) {
51
- const host_uuid = this.getHostUUID(this.hostUUIDFileName);
85
+ const host_uuid = this.resolveHostUUID();
52
86
  if(!host_uuid)
53
87
  return;
54
88
 
@@ -67,7 +101,8 @@ MeteringInfo.prototype.doProcess = function (p) {
67
101
  pk.putTagString('csp', csp)
68
102
  }
69
103
 
70
- pk.putTagFloat('mcore', p.metering)
104
+ // Go TagTaskMetering.go: p.Put("mcore", sys.GetCPUNum())
105
+ pk.putTagFloat('mcore', CgroupCpu.getCpuNum())
71
106
 
72
107
  DataPackSender.sendTagCounterPack(pk);
73
108
  };
@@ -7,6 +7,7 @@
7
7
  var CounterTask = require('./counter-task'),
8
8
  ResourceProfile = require('./../../util/resourceprofile'),
9
9
  KubeUtil = require('./../../util/kube-util'),
10
+ CgroupCpu = require('./../../util/cgroup-cpu'),
10
11
  SecurityMaster = require('../../net/security-master');
11
12
 
12
13
  function SystemPerf(){
@@ -30,7 +31,9 @@ SystemPerf.prototype.process = function(p) {
30
31
  if(p.version <= 1) p.mem = ResourceProfile.getMemory().usage;
31
32
  else p.mem = ResourceProfile.getMemoryV2().usage;
32
33
 
33
- p.cpu_cores = cpu.core;
34
+ // Go TaskSystemPerf.go: p.CpuCores = sys.GetCPUNum()
35
+ // (getCpuNum 내부에서 metering_container_enabled 판정: true+cgroup한도 → ceil, 그 외 → 호스트 CPU 개수)
36
+ p.cpu_cores = CgroupCpu.getCpuNum();
34
37
  p.host_ip = SecurityMaster.IP;
35
38
  p.pid=process.pid;
36
39
 
@@ -182,6 +182,19 @@ HttpObserver.prototype.__createTransactionObserver = function(callback, isHttps,
182
182
  return callback(req, res);
183
183
  }
184
184
 
185
+ if(ctx.isStaticContents !== true && conf._trace_ignore_url_set[ctx.service_hash]){
186
+ ctx.isStaticContents = true;
187
+ }
188
+
189
+ if(ctx.isStaticContents !== true && conf._is_trace_ignore_url_prefix === true && ctx.service_name.startsWith(conf.trace_ignore_url_prefix) === true){
190
+ ctx.isStaticContents = true;
191
+ }
192
+
193
+ if(ctx.isStaticContents){
194
+ TraceContextManager.end(ctx._id);
195
+ return callback(req, res);
196
+ }
197
+
185
198
  PluginLoaderManager.do('httpservicestart', ctx, req, res);
186
199
  if(trace_origin_url === true){
187
200
  var originUrlHash = HashUtil.hashFromString('Origin url');
@@ -604,14 +617,6 @@ HttpObserver.prototype.__endTransaction = function(error, ctx, req, res) {
604
617
 
605
618
  // if(ctx == null || TraceContextManager.isExist(ctx._id) === false) { return; }
606
619
 
607
- if(ctx.isStaticContents !== true && conf._trace_ignore_url_set[ctx.service_hash]){
608
- ctx.isStaticContents = true;
609
- }
610
-
611
- if(conf._is_trace_ignore_url_prefix === true && ctx.service_name.startsWith(conf.trace_ignore_url_prefix) === true){
612
- ctx.isStaticContents = true;
613
- }
614
-
615
620
  if(ctx.isStaticContents){
616
621
  TraceContextManager.end(ctx._id);
617
622
  ctx = null;
@@ -27,7 +27,7 @@ ProcessObserver.prototype.inject = function (mod, moduleName) {
27
27
  this._hookNextTick(mod);
28
28
  this._hookStdOutWrite();
29
29
  this._hookStdErrWrite();
30
- this._hookProcessExit(mod);
30
+ // this._hookProcessExit(mod);
31
31
  };
32
32
 
33
33
  ProcessObserver.prototype._hookNextTick = function (mod) {
@@ -116,27 +116,27 @@ ProcessObserver.prototype._hookProcessExit = function (mod) {
116
116
  });
117
117
 
118
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
- });
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
140
 
141
141
  this.exitHandlersRegistered = true;
142
142
  };
@@ -64,7 +64,8 @@ function writeArr( dout, arr) {
64
64
  len = len + 1;
65
65
  dout.writeByte(len);
66
66
  for (var i = 0; i < len; i++) {
67
- dout.writeUInt16BE(arr[i]);
67
+ var value = arr[i] > 65535 ? 65535 : arr[i];
68
+ dout.writeUInt16BE(value);
68
69
  }
69
70
  }
70
71
  }catch (e) {
@@ -0,0 +1,78 @@
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
+ /**
8
+ * 컨테이너(cgroup) CPU limit 조회.
9
+ * Go 에이전트 util/sys/CpuUtil.go 의 getCgroupCpuLimitFloat() / GetCPUNum() 와 동일한 로직.
10
+ */
11
+ var fs = require('fs'),
12
+ os = require('os');
13
+
14
+ // cgroup CPU limit 을 코어수(float)로 반환한다. 한도 미설정/감지 실패 시 0.
15
+ // cgroup v2: /sys/fs/cgroup/cpu.max -> "$MAX $PERIOD" (예: "200000 100000" = 2 cores), "max 100000" = 무제한
16
+ // cgroup v1: cpu.cfs_quota_us / cpu.cfs_period_us (quota -1 = 무제한)
17
+ function getCgroupCpuLimitFloat() {
18
+ if (process.platform !== 'linux') {
19
+ return 0;
20
+ }
21
+
22
+ // cgroup v2
23
+ try {
24
+ var data = fs.readFileSync('/sys/fs/cgroup/cpu.max', 'utf8').trim();
25
+ var fields = data.split(/\s+/);
26
+ if (fields.length === 2 && fields[0] !== 'max') {
27
+ var quotaV2 = parseFloat(fields[0]);
28
+ var periodV2 = parseFloat(fields[1]);
29
+ if (!isNaN(quotaV2) && !isNaN(periodV2) && periodV2 > 0) {
30
+ return quotaV2 / periodV2;
31
+ }
32
+ }
33
+ // v2 파일은 읽혔으나 한도 미설정("max")이거나 형식 불일치 -> 한도 없음
34
+ return 0;
35
+ } catch (e) {
36
+ // v2 파일이 없으면 v1 으로 폴백
37
+ }
38
+
39
+ // cgroup v1
40
+ try {
41
+ var quotaData = fs.readFileSync('/sys/fs/cgroup/cpu/cpu.cfs_quota_us', 'utf8').trim();
42
+ var periodData = fs.readFileSync('/sys/fs/cgroup/cpu/cpu.cfs_period_us', 'utf8').trim();
43
+ var quota = parseFloat(quotaData);
44
+ var period = parseFloat(periodData);
45
+ if (isNaN(quota) || isNaN(period) || period <= 0 || quota <= 0) {
46
+ return 0;
47
+ }
48
+ return quota / period;
49
+ } catch (e) {
50
+ return 0;
51
+ }
52
+ }
53
+
54
+ // metering_container_enabled 옵션 여부 (config 또는 env). 컨테이너 메터링 공통 판정.
55
+ function isContainerMeteringEnabled() {
56
+ var conf = require('../conf/configure');
57
+ return conf.getProperty('metering_container_enabled', false) === true
58
+ || process.env.WHATAP_METERING_CONTAINER_ENABLED === 'true';
59
+ }
60
+
61
+ // 미터링/CPU 코어수. Go sys.GetCPUNum() 와 1:1 동일:
62
+ // metering_container_enabled=true 이고 cgroup 한도가 있으면 ceil 한 정수,
63
+ // 그 외(플래그 off 또는 한도 없음)는 호스트 CPU 개수(= runtime.NumCPU()).
64
+ function getCpuNum() {
65
+ if (isContainerMeteringEnabled()) {
66
+ var cgroupCores = getCgroupCpuLimitFloat();
67
+ if (cgroupCores > 0) {
68
+ return Math.ceil(cgroupCores);
69
+ }
70
+ }
71
+ return os.cpus().length;
72
+ }
73
+
74
+ module.exports = {
75
+ getCgroupCpuLimitFloat: getCgroupCpuLimitFloat,
76
+ getCpuNum: getCpuNum,
77
+ isContainerMeteringEnabled: isContainerMeteringEnabled
78
+ };
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.25",
5
- "releaseDate": "20250821",
4
+ "version": "0.5.27",
5
+ "releaseDate": "20260611",
6
6
  "description": "Monitoring and Profiling Service",
7
7
  "main": "index.js",
8
8
  "scripts": {},