whatap 0.5.11 → 0.5.13

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.
@@ -21,27 +21,121 @@ const MeterUsers = require("../counter/meter/meter-users");
21
21
  const MeterService = require('../counter/meter/meter-service').MeterService;
22
22
  const shimmer = require('../core/shimmer');
23
23
 
24
- var trace_background_socket_enabled = conf.getProperty('trace_background_socket_enabled', true);
24
+ // 설정을 객체로 통합하여 관리 (참조 성능 향상)
25
+ var config = {
26
+ trace_background_socket_enabled: conf.getProperty('trace_background_socket_enabled', true),
27
+ trace_sampling_enabled: conf.getProperty('trace_sampling_enabled', true),
28
+ trace_sampling_tps: conf.getProperty('trace_sampling_tps', 1000),
29
+ resource_sampling_rate: 0.1, // 리소스 프로파일링을 10%만 수행
30
+ profile_batch_size: 20, // 프로파일 배치 처리 크기
31
+ flush_interval: 500 // 프로파일 플러시 간격 (ms)
32
+ };
33
+
34
+ // 설정 변경 감지를 단일 리스너로 통합
25
35
  conf.on('trace_background_socket_enabled', function (newProps) {
26
- trace_background_socket_enabled = newProps;
27
- })
28
- var trace_sampling_enabled = conf.getProperty('trace_sampling_enabled', true);
36
+ config.trace_background_socket_enabled = newProps;
37
+ });
38
+
29
39
  conf.on('trace_sampling_enabled', function (newProps) {
30
- trace_sampling_enabled = newProps;
31
- })
32
- var trace_sampling_tps = conf.getProperty('trace_sampling_tps', 1000);
40
+ config.trace_sampling_enabled = newProps;
41
+ });
42
+
33
43
  conf.on('trace_sampling_tps', function (newProps) {
34
- trace_sampling_tps = newProps;
35
- })
44
+ config.trace_sampling_tps = newProps;
45
+ });
36
46
 
37
47
  var SocketIOObserver = function(agent){
38
48
  this.agent = agent;
39
49
  this.packages = ['socket.io'];
50
+
51
+ this.socketCounter = {
52
+ count: 0,
53
+ start_time: Date.now(),
54
+ window_size: 5000,
55
+
56
+ checkSampling: function() {
57
+ const now = Date.now();
58
+ if ((now - this.start_time) >= this.window_size) {
59
+ this.start_time = now;
60
+ this.count = 0;
61
+ return true;
62
+ }
63
+
64
+ this.count++;
65
+ return this.count <= config.trace_sampling_tps;
66
+ }
67
+ };
68
+
69
+ // 프로파일 데이터 버퍼링 (배치 처리)
70
+ this.profileBuffer = [];
71
+
72
+ // 주기적 플러시 설정
73
+ setInterval(() => this.flushProfileBuffer(), config.flush_interval);
74
+
75
+ // IP 주소 캐싱 (성능 최적화)
76
+ this.ipCache = new Map();
40
77
  };
41
78
 
42
- var socket_count = {
43
- count: 0,
44
- start_time: null
79
+ SocketIOObserver.prototype.flushProfileBuffer = function() {
80
+ if (this.profileBuffer.length === 0) return;
81
+
82
+ const now = Date.now();
83
+ // 100ms 이상 지난 항목만 처리
84
+ const bufferToFlush = this.profileBuffer.filter(item => (now - item.timestamp) >= 100);
85
+ if (bufferToFlush.length === 0) return;
86
+
87
+ // 최대 batch_size까지만 처리
88
+ let batchToProcess;
89
+ if(bufferToFlush.length > config.profile_batch_size)
90
+ batchToProcess = bufferToFlush.slice(0, config.profile_batch_size);
91
+ else
92
+ batchToProcess = bufferToFlush;
93
+
94
+ // 버퍼에서 처리할 항목 제거
95
+ this.profileBuffer = this.profileBuffer.filter(item =>
96
+ !batchToProcess.some(batch => batch.ctx._id === item.ctx._id)
97
+ );
98
+
99
+ // 배치 처리
100
+ batchToProcess.forEach(item => {
101
+ try {
102
+ DataProfileAgent.sendProfile(item.ctx, item.profile, false);
103
+ // 컨텍스트 정리 (메모리 누수 방지)
104
+ item.ctx = null;
105
+ } catch (e) {
106
+ Logger.printError('WHATAP-615', 'Socket.io buffer flush error', e, false);
107
+ }
108
+ });
109
+ };
110
+
111
+ // IP 주소 처리 최적화 함수
112
+ SocketIOObserver.prototype.getProcessedIp = function(address) {
113
+ if (!address) return null;
114
+
115
+ if (this.ipCache.has(address)) {
116
+ return this.ipCache.get(address);
117
+ }
118
+
119
+ let host = address;
120
+ if (address.includes(':')) {
121
+ host = address.substring(address.lastIndexOf(':') + 1);
122
+ }
123
+
124
+ host = IPUtil.checkIp4(host);
125
+ const ipInt = IPUtil.stringToInt(host);
126
+ const ipBytes = Buffer.from(IPUtil.stringToBytes(host));
127
+
128
+ const result = { host, ipInt, ipBytes };
129
+ this.ipCache.set(address, result);
130
+
131
+ // 캐시 크기 관리 (메모리 누수 방지)
132
+ if (this.ipCache.size > 10000) {
133
+ // 오래된 항목부터 20% 제거
134
+ const keysToDelete = Array.from(this.ipCache.keys()).slice(0, Math.floor(this.ipCache.size * 0.2));
135
+ keysToDelete.forEach(key => this.ipCache.delete(key));
136
+ }
137
+
138
+ return result;
45
139
  };
46
140
 
47
141
  SocketIOObserver.prototype.inject = function (mod, moduleName) {
@@ -57,15 +151,34 @@ SocketIOObserver.prototype.inject = function (mod, moduleName) {
57
151
  return function (event, listener) {
58
152
  if (event === 'connection') {
59
153
  return original.call(this, event, function (socket) {
60
- shimmer.wrap(socket, 'emit', function (origEmit) {
61
- return function (emitEvent, ...args) {
62
- if (trace_background_socket_enabled) {
63
- self.__handleSocketEmitEvent(socket, emitEvent, args);
154
+ // 효율적인 이벤트 핸들러 래핑
155
+ const wrappedEmit = function (origEmit) {
156
+ // 클로저 최소화 (메모리 사용 감소)
157
+ const emitFn = function (emitEvent, ...args) {
158
+ // 빠른 조건 검사 (조기 반환)
159
+ if (!config.trace_background_socket_enabled) {
160
+ return origEmit.apply(this, [emitEvent, ...args]);
64
161
  }
65
162
 
163
+ self.__handleSocketEmitEvent(socket, emitEvent, args);
66
164
  return origEmit.apply(this, [emitEvent, ...args]);
67
165
  };
68
- });
166
+
167
+ // 원본 함수 속성 유지
168
+ Object.defineProperties(emitFn, {
169
+ length: { value: origEmit.length },
170
+ name: { value: origEmit.name }
171
+ });
172
+
173
+ return emitFn;
174
+ };
175
+
176
+ // emit 함수에만 래핑 적용 (불필요한 래핑 제거)
177
+ if (typeof socket.emit === 'function' && !socket.emit.__wrapped__) {
178
+ const original = socket.emit;
179
+ socket.emit = wrappedEmit(original);
180
+ socket.emit.__wrapped__ = true;
181
+ }
69
182
 
70
183
  return listener.apply(this, [socket]);
71
184
  });
@@ -76,59 +189,57 @@ SocketIOObserver.prototype.inject = function (mod, moduleName) {
76
189
  });
77
190
  };
78
191
 
192
+ // 소켓 이벤트 처리 최적화 (CPU 및 메모리 사용 감소)
79
193
  SocketIOObserver.prototype.__handleSocketEmitEvent = function(socket, event, args) {
80
- if (trace_sampling_enabled) {
81
- var now = Date.now();
82
- // if ((now - socket_count.start_time) > 1000) {
83
- // socket_count.start_time = now;
84
- // socket_count.count = 0;
85
- // }
86
- if (!socket_count.start_time || (now - socket_count.start_time) >= 1000) {
87
- socket_count.start_time = now;
88
- socket_count.count = 0;
89
- }
90
-
91
- socket_count.count++;
92
- if (socket_count.count > trace_sampling_tps) {
93
- MeterService.add(0, 1, 0, SecurityMaster.PCODE, SecurityMaster.OKIND, SecurityMaster.OID);
94
- return;
95
- }
194
+ // 빠른 샘플링 체크 (조기 반환으로 성능 향상)
195
+ if (config.trace_sampling_enabled && !this.socketCounter.checkSampling()) {
196
+ MeterService.add(0, 1, 0, SecurityMaster.PCODE, SecurityMaster.OKIND, SecurityMaster.OID);
197
+ return;
96
198
  }
97
199
 
98
- TraceContextManager._asyncLocalStorage.run(initCtx(socket, args), () => {
200
+ // 리소스 프로파일링 샘플링 (전체 이벤트의 일부만 측정)
201
+ const shouldProfileResource = Math.random() < config.resource_sampling_rate;
202
+
203
+ // 컨텍스트 생성 및 실행
204
+ TraceContextManager._asyncLocalStorage.run(this.__initCtx(socket, args, shouldProfileResource), () => {
99
205
  try {
100
206
  var ctx = TraceContextManager._asyncLocalStorage.getStore();
101
207
  if (!ctx) return;
102
208
 
103
- ctx.footprint('Socket Emit Event: ' + event);
104
-
105
- var host;
106
- if (socket.conn && socket.conn.remoteAddress && socket.conn.remoteAddress.includes(':')) {
107
- host = socket.conn.remoteAddress.substring(socket.conn.remoteAddress.lastIndexOf(':') + 1);
209
+ // IP 주소 처리 최적화
210
+ var host = null;
211
+ if (socket.conn && socket.conn.remoteAddress) {
212
+ const ipInfo = this.getProcessedIp(socket.conn.remoteAddress);
213
+ if (ipInfo) {
214
+ host = ipInfo.host;
215
+ // 이미 처리된 IP 바이트 사용
216
+ var step = new SocketStep();
217
+ step.start_time = ctx.getElapsedTime();
218
+ step.ipaddr = ipInfo.ipBytes;
219
+ step.elapsed = 0; // 즉시 종료 (측정 최소화)
220
+ ctx.profile.push(step);
221
+ }
108
222
  }
109
223
 
110
- ctx.socket_connecting = true;
111
- ctx.footprint('Socket Connecting: ' + host);
112
-
113
- var step = new SocketStep();
114
- step.start_time = ctx.getElapsedTime();
115
- step.ipaddr = Buffer.from(IPUtil.stringToBytes(host));
116
- // step.port = port;
117
-
118
- ctx.socket_connecting = false;
119
- step.elapsed = ctx.getElapsedTime() - step.start_time;
120
- ctx.profile.push(step)
121
-
224
+ // 트랜잭션 종료 및 데이터 전송
122
225
  this.__endTransaction(null, ctx);
123
226
 
124
227
  } catch (e) {
125
- Logger.printError('WHATAP-616', 'socket.io emit transaction error..', e, false);
228
+ Logger.printError('WHATAP-616', 'socket.io emit transaction error', e, false);
229
+
230
+ // 에러 시 컨텍스트 정리 (메모리 누수 방지)
231
+ var ctx = TraceContextManager._asyncLocalStorage.getStore();
232
+ if (ctx) {
233
+ TraceContextManager.end(ctx._id);
234
+ }
126
235
  }
127
236
  });
128
237
  };
129
238
 
239
+ // 트랜잭션 종료 최적화 (배치 처리 및 메모리 사용 감소)
130
240
  SocketIOObserver.prototype.__endTransaction = function(error, ctx) {
131
241
  try {
242
+ // 기본 성능 데이터 수집
132
243
  var profile = new ProfilePack();
133
244
  var wtx = new TxRecord();
134
245
  wtx.endTime = DateUtil.currentTime();
@@ -139,50 +250,78 @@ SocketIOObserver.prototype.__endTransaction = function(error, ctx) {
139
250
 
140
251
  wtx.seq = ctx.txid;
141
252
  wtx.service = ctx.service_hash;
142
- wtx.cpuTime = ResourceProfile.getCPUTime() - ctx.start_cpu;
143
- wtx.malloc = ResourceProfile.getUsedHeapSize()-ctx.start_malloc;
144
- if(wtx.malloc < 0) { wtx.malloc = 0; }
145
- wtx.status = 2;
146
253
 
254
+ // 리소스 프로파일링은 샘플링된 경우에만 수행
255
+ if (ctx.shouldProfileResource) {
256
+ wtx.cpuTime = ResourceProfile.getCPUTime() - ctx.start_cpu;
257
+ wtx.malloc = ResourceProfile.getUsedHeapSize() - ctx.start_malloc;
258
+ if(wtx.malloc < 0) { wtx.malloc = 0; }
259
+ } else {
260
+ wtx.cpuTime = 0;
261
+ wtx.malloc = 0;
262
+ }
263
+
264
+ wtx.status = 2;
147
265
  wtx.ipaddr = ctx.remoteIp;
148
266
 
267
+ // 기본 측정 데이터 기록
149
268
  MeterService.add(0, wtx.elapsed, 0, SecurityMaster.PCODE, SecurityMaster.OKIND, SecurityMaster.OID);
150
269
 
151
270
  profile.oid = SecurityMaster.OID;
152
271
  profile.service = wtx;
153
272
 
154
- setTimeout(function () {
155
- DataProfileAgent.sendProfile(ctx, profile, false);
156
- TraceContextManager.end(ctx._id);
157
- ctx = null;
158
- }, 100);
273
+ // 컨텍스트 ID 보존 후 컨텍스트 종료 (메모리 누수 방지)
274
+ const ctxId = ctx._id;
275
+ TraceContextManager.end(ctxId);
276
+
277
+ // 프로파일 데이터 버퍼에 추가 (비동기 처리 최적화)
278
+ this.profileBuffer.push({
279
+ ctx: ctx,
280
+ profile: profile,
281
+ timestamp: Date.now()
282
+ });
283
+
284
+ if (this.profileBuffer.length > 1000) {
285
+ // 가장 오래된 항목부터 제거
286
+ this.profileBuffer = this.profileBuffer.slice(-500);
287
+ }
288
+
159
289
  } catch (e) {
160
- Logger.printError('WHATAP-615', 'Socket.io end transaction error..', e, false);
290
+ Logger.printError('WHATAP-615', 'Socket.io end transaction error', e, false);
291
+ // 에러 시에도 컨텍스트 정리
161
292
  TraceContextManager.end(ctx._id);
162
293
  ctx = null;
163
294
  }
164
-
165
295
  };
166
296
 
167
- function initCtx(socket, args) {
297
+ // 컨텍스트 초기화 최적화
298
+ SocketIOObserver.prototype.__initCtx = function(socket, args, shouldProfileResource) {
168
299
  const ctx = TraceContextManager.start();
169
300
  if (!ctx) {return;}
170
301
 
171
302
  var remote_addr;
172
- const address = socket.conn.remoteAddress;
173
- if(address && address.includes(':')){
174
- remote_addr = address.substring(address.lastIndexOf(':')+1);
303
+ if (socket.conn && socket.conn.remoteAddress) {
304
+ const address = socket.conn.remoteAddress;
305
+ if(address && address.includes(':')){
306
+ remote_addr = address.substring(address.lastIndexOf(':')+1);
307
+ }
175
308
  }
176
309
 
177
- ctx.start_malloc = ResourceProfile.getUsedHeapSize();
178
- ctx.start_cpu = ResourceProfile.getCPUTime();
310
+ // 리소스 측정은 샘플링된 경우에만 수행
311
+ ctx.shouldProfileResource = shouldProfileResource;
312
+ if (shouldProfileResource) {
313
+ ctx.start_malloc = ResourceProfile.getUsedHeapSize();
314
+ ctx.start_cpu = ResourceProfile.getCPUTime();
315
+ }
179
316
 
180
- remote_addr=IPUtil.checkIp4(remote_addr);
181
- ctx.remoteIp = IPUtil.stringToInt(remote_addr);
182
- ctx.userid = Long.fromNumber(ctx.remoteIp);
183
- MeterUsers.add(ctx.userid);
317
+ if (remote_addr) {
318
+ remote_addr = IPUtil.checkIp4(remote_addr);
319
+ ctx.remoteIp = IPUtil.stringToInt(remote_addr);
320
+ ctx.userid = Long.fromNumber(ctx.remoteIp);
321
+ MeterUsers.add(ctx.userid);
322
+ }
184
323
 
185
324
  return ctx;
186
- }
325
+ };
187
326
 
188
327
  exports.SocketIOObserver = SocketIOObserver;