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.
- package/lib/conf/config-default.js +2 -0
- package/lib/core/agent.js +4 -1
- package/lib/counter/counter-manager.js +4 -0
- package/lib/counter/task/metering-info.js +118 -0
- package/lib/observers/http-observer.js +1 -0
- package/lib/observers/oracle-observer.js +656 -0
- package/lib/observers/prisma-observer.js +20 -269
- package/lib/observers/socket.io-observer.js +213 -74
- package/lib/observers/websocket-observer.js +164 -67
- package/lib/pack/apenum.js +8 -0
- package/lib/pack/otype.js +7 -0
- package/lib/pack/tagcount-pack.js +51 -50
- package/lib/util/dateutil.js +33 -1
- package/lib/util/escape-literal-sql.js +5 -5
- package/package.json +2 -2
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
|
|
178
|
-
ctx.
|
|
310
|
+
// 리소스 측정은 샘플링된 경우에만 수행
|
|
311
|
+
ctx.shouldProfileResource = shouldProfileResource;
|
|
312
|
+
if (shouldProfileResource) {
|
|
313
|
+
ctx.start_malloc = ResourceProfile.getUsedHeapSize();
|
|
314
|
+
ctx.start_cpu = ResourceProfile.getCPUTime();
|
|
315
|
+
}
|
|
179
316
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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;
|