whatap 2.0.0-canary.0 → 2.0.0

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.
@@ -7,7 +7,8 @@
7
7
  "Bash(ls -l \"$\\(go env GOPATH\\)/bin/govulncheck\")",
8
8
  "Bash(brew list *)",
9
9
  "Bash(brew search *)",
10
- "Bash(brew info *)"
10
+ "Bash(brew info *)",
11
+ "Bash(echo \"--- exit $? ---\")"
11
12
  ]
12
13
  }
13
14
  }
@@ -289,7 +289,6 @@ var ConfigDefault = {
289
289
 
290
290
  // LLM monitoring
291
291
  "llm_enabled": bool("llm_enabled", false),
292
- "trace_llm_log_enabled": bool("trace_llm_log_enabled", false),
293
292
  "llm_net_udp_port": num("llm_net_udp_port", 0),
294
293
  "llm_model_pricing": str("llm_model_pricing", "")
295
294
 
package/lib/core/agent.js CHANGED
@@ -997,7 +997,7 @@ NodeAgent.prototype.startGoAgent = function(opts = {}) {
997
997
  if (pid) {
998
998
  try {
999
999
  if (this.isGoAgentRunning(pid)) {
1000
- // Windows: 기존 프로세스가 실행 중이면 스킵 (Python agent와 동일한 동작)
1000
+ // Windows: 기존 프로세스가 실행 중이면 스킵
1001
1001
  // Unix/Linux: 기존 프로세스 종료 후 새로 시작
1002
1002
  if (process.platform === 'win32') {
1003
1003
  Logger.print("WHATAP-106", `Agent already running (PID: ${pid}). Skipping duplicate execution.`, false);
@@ -1252,7 +1252,6 @@ NodeAgent.prototype.startGoAgent = function(opts = {}) {
1252
1252
 
1253
1253
  /**
1254
1254
  * Start a separate Go Agent for LLM monitoring (with --llm flag)
1255
- * Python equivalent: go(llm=True)
1256
1255
  */
1257
1256
  NodeAgent.prototype.startLlmGoAgent = function () {
1258
1257
  var self = this;
@@ -1299,7 +1298,7 @@ NodeAgent.prototype.startLlmGoAgent = function () {
1299
1298
  } catch (e) {}
1300
1299
  }
1301
1300
 
1302
- // Build environment (python equivalent: WHATAP_NET_UDP_PORT, WHATAP_PID_FILE, *_PARENT_APP_PID)
1301
+ // Build environment
1303
1302
  var newEnv = Object.assign({}, process.env);
1304
1303
  newEnv['WHATAP_NET_UDP_PORT'] = String(llmPort);
1305
1304
  newEnv['WHATAP_PID_FILE'] = AGENT_NAME + '.pid.llm';
@@ -1315,7 +1314,7 @@ NodeAgent.prototype.startLlmGoAgent = function () {
1315
1314
  cwd: whatapHome,
1316
1315
  env: newEnv,
1317
1316
  stdio: ['pipe', 'pipe', 'pipe'],
1318
- detached: true, // python: start_new_session=True
1317
+ detached: true, //
1319
1318
  };
1320
1319
 
1321
1320
  if (platform === 'win32') {
@@ -22,7 +22,6 @@ CounterManager.prototype.run = function () {
22
22
  var tasks = [];
23
23
  tasks.push(new GCAction());
24
24
 
25
- // LLM 메트릭 6종 (5초 주기). llm_meter는 python-apm과 일치 — 제거됨.
26
25
  // 호출 카운트는 ACTIVE_STATS 꼬리의 "LLM:count=N"으로 전송 (_llmMeterCount).
27
26
  tasks.push(new LlmStatTasks.LLMActiveStatTask());
28
27
  tasks.push(new LlmStatTasks.LLMApiStatusTask());
@@ -26,7 +26,6 @@ const { getLlmPcode } = require('../../llm/llm-pcode');
26
26
  const _PID = process.pid;
27
27
 
28
28
  function _enabled() {
29
- // python-apm과 일치: llm_enabled 단일 gate
30
29
  return conf.getProperty('llm_enabled', false);
31
30
  }
32
31
 
@@ -256,7 +255,6 @@ LLMTokenUsageTask.prototype.process = function () {
256
255
  });
257
256
  };
258
257
 
259
- // NOTE: llm_meter TagCountPack은 python-apm에서도 제거됨.
260
258
  // LLM 호출 카운트는 _llmMeterCount로 유지되어 ACTIVE_STATS 패킷 꼬리에
261
259
  // "LLM:count=N" 형태로 append 송신됨 (udp_session.js:161).
262
260
 
@@ -275,7 +275,7 @@ DataOutputX.prototype.writeInt40BE = function(value) {
275
275
  return this.writeLong5(value);
276
276
  };
277
277
 
278
- // 새롭게 구현된 writeLong - Python 구현과 동일하게 작동
278
+ // 새롭게 구현된 writeLong
279
279
  DataOutputX.prototype.writeLong = function(value) {
280
280
  if (isNumOk(value) == false) {
281
281
  value = 0;
@@ -137,7 +137,7 @@ function _accumulateTxSummary(ctx, pack) {
137
137
 
138
138
  function sendLlmTxStatus(ctx) {
139
139
  if (!ctx) return;
140
- if (!conf.getProperty('trace_llm_log_enabled', false)) return;
140
+ if (!conf.getProperty('llm_enabled', false)) return;
141
141
  const summary = ctx._llm_tx_summary;
142
142
  if (!summary || summary.call_count === 0) return;
143
143
 
@@ -270,7 +270,7 @@ function _processPack(pack) {
270
270
  // active count 감소
271
271
  statCollector.onCallEnd(model, opType, provider, url, ctx);
272
272
 
273
- // perf — TPOT 공식 python-apm 일치: (latency - ttft) / (output_tokens - 1)
273
+ // perf — TPOT 공식: (latency - ttft) / (output_tokens - 1)
274
274
  // 조건: ttft/latency non-null AND output_tokens > 1. stream 여부 무관.
275
275
  let tpot = null;
276
276
  if (pack.ttft != null && pack.latency != null && (pack.output_tokens || 0) > 1) {
@@ -40,7 +40,7 @@ CronObserver.prototype.__createCronJobObserver = function (callback, cronExpress
40
40
  '0.0.0.0',
41
41
  '',
42
42
  '',
43
- String(0),
43
+ String(ctx.userid || ""),
44
44
  String(false),
45
45
  ''
46
46
  ];
@@ -161,7 +161,7 @@ CronObserver.prototype.__initCtx = function (cronExpression, jobName) {
161
161
 
162
162
  ctx.service_name = jobName || '/';
163
163
  ctx.remoteIp = 0;
164
- ctx.userid = 0;
164
+ ctx.userid = "";
165
165
  ctx.userAgentString = '';
166
166
  ctx.referer = '';
167
167
  ctx.status = 200;
@@ -88,8 +88,8 @@ GlobalObserver.prototype.inject = function (mod, moduleName) {
88
88
  } catch (e) {
89
89
  }
90
90
 
91
- // python equivalent: if not ctx.step_id: ctx.step_id = random.getrandbits(63)
92
- // 모든 httpc 호출에 step_id 생성 (python-apm 동작과 일치).
91
+ // if not ctx.step_id: ctx.step_id = random.getrandbits(63)
92
+ // 모든 httpc 호출에 step_id 생성
93
93
  if (!ctx.step_id) {
94
94
  ctx.step_id = KeyGen.next();
95
95
  }
@@ -214,7 +214,7 @@ function endHttpc(ctx) {
214
214
 
215
215
  ctx.active_httpc_hash = false;
216
216
  let urls = `${ctx.httpc_host}:${ctx.httpc_port}${ctx.httpc_url}`;
217
- // python equivalent: datas = [ctx.httpc_url, ctx.step_id, ctx.driver]
217
+ // datas = [ctx.httpc_url, ctx.step_id, ctx.driver]
218
218
  // 모든 httpc에 step_id 사용 (mcallee는 mtrace 외에는 Long.ZERO라 의미 없음).
219
219
  let stepIdRaw = ctx.step_id || 0;
220
220
  let stepIdStr = LlmIdUtil.stepIdStr(stepIdRaw);
@@ -224,11 +224,11 @@ function endHttpc(ctx) {
224
224
  TraceHttpc.isSlowHttpc(ctx);
225
225
  AsyncSender.send_packet(PacketTypeEnum.TX_HTTPC, ctx, httpcDatas);
226
226
 
227
- // python equivalent: if ctx.is_llm: ctx._llm_step_id = ctx.step_id
227
+ // if ctx.is_llm: ctx._llm_step_id = ctx.step_id
228
228
  if (ctx.is_llm) {
229
229
  ctx._llm_step_id = ctx.step_id;
230
230
  }
231
- // python equivalent: ctx.active_httpc_hash=0; ctx.httpc_url=None; ctx.step_id=0
231
+ // ctx.active_httpc_hash=0; ctx.httpc_url=None; ctx.step_id=0
232
232
  ctx.driver = '';
233
233
  ctx.httpc_url = '';
234
234
  ctx.step_id = 0;
@@ -94,7 +94,7 @@ function wrapHandler(handler, methodName, type) {
94
94
  ctx.remoteIp || '0.0.0.0',
95
95
  '', // userAgent
96
96
  '', // referer
97
- '',
97
+ String(ctx.userid || ""),
98
98
  'false', // isStaticContents
99
99
  method_name
100
100
  ];
@@ -193,7 +193,7 @@ function wrapStreamHandler(handler, methodName, type) {
193
193
  ctx.remoteIp || '0.0.0.0',
194
194
  '', // userAgent
195
195
  '', // referer
196
- '',
196
+ String(ctx.userid || ""),
197
197
  'false', // isStaticContents
198
198
  method_name
199
199
  ];
@@ -320,6 +320,8 @@ function initCtx(call, methodName) {
320
320
  // Remote IP 추출 시도
321
321
  const metadata = call.metadata.getMap();
322
322
  ctx.remoteIp = metadata['x-forwarded-for'] || metadata['x-real-ip'] || '0.0.0.0';
323
+ // WClientId를 빈 문자열로 보내 go-agent가 int64(RemoteIp) 분기를 타도록 함.
324
+ ctx.userid = "";
323
325
  }
324
326
  } catch (e) {
325
327
  Logger.printError('WHATAP-264', 'gRPC metadata parsing error: ' + e, false);
@@ -155,7 +155,7 @@ HttpObserver.prototype.__createTransactionObserver = function (callback, isHttps
155
155
  ctx.remoteIp,
156
156
  ctx.userAgentString,
157
157
  ctx.referer,
158
- "",
158
+ String(ctx.userid || ""),
159
159
  String(ctx.isStaticContents),
160
160
  req.method
161
161
  ];
@@ -278,7 +278,9 @@ function initCtx(req, res) {
278
278
  ctx.remoteIp = IPUtil.checkIp4(remote_addr);
279
279
  if(conf.getProperty('trace_user_enabled', true)){
280
280
  if(conf.getProperty('trace_user_using_ip', true)){
281
- ctx.userid = ctx.remoteIp;
281
+ // WClientId를 빈 문자열로 보내 go-agent가 int64(RemoteIp) 분기를 타도록 함.
282
+ // Java agent와 동일한 키 공간 유지 → cross-agent merge 정상화.
283
+ ctx.userid = "";
282
284
  }else{
283
285
  ctx.userid = UserIdUtil.getUserId(req, res, 0);
284
286
  }
@@ -601,7 +603,7 @@ HttpObserver.prototype.inject = function (mod, moduleName) {
601
603
  ctx.httpc_port = 80
602
604
  }
603
605
 
604
- // LLM API detection (Python equivalent: _detect_llm_api in httpc/util.py)
606
+ // LLM API detection
605
607
  try {
606
608
  // collector key 폭발 방지: query string 제거된 path만 _llm_httpc_url에 저장
607
609
  var rawUrl = ctx.httpc_url || '';
@@ -621,8 +623,8 @@ HttpObserver.prototype.inject = function (mod, moduleName) {
621
623
  // ignore LLM detection errors
622
624
  }
623
625
 
624
- // python equivalent: if not ctx.step_id: ctx.step_id = random.getrandbits(63)
625
- // 모든 httpc 호출에 step_id 생성 (python-apm 동작과 일치).
626
+ // if not ctx.step_id: ctx.step_id = random.getrandbits(63)
627
+ // 모든 httpc 호출에 step_id 생성
626
628
  if (!ctx.step_id) {
627
629
  ctx.step_id = KeyGen.next();
628
630
  }
@@ -631,7 +633,6 @@ HttpObserver.prototype.inject = function (mod, moduleName) {
631
633
  if (conf.getProperty('profile_httpc_start_step_enabled', false)) {
632
634
  ctx.active_httpc_hash = false;
633
635
  let urls = `${ctx.httpc_host}:${ctx.httpc_port}${ctx.httpc_url}`;
634
- // python equivalent: [urls, step_id, driver]
635
636
  let httpcDatas = [urls, ctx.step_id || 0, ctx.driver || ''];
636
637
  ctx.elapsed = 0;
637
638
  TraceHttpc.isSlowHttpc(ctx);
@@ -770,7 +771,6 @@ HttpObserver.prototype.inject = function (mod, moduleName) {
770
771
 
771
772
  ctx.active_httpc_hash = false;
772
773
  let urls = `${ctx.httpc_host}:${ctx.httpc_port}${ctx.httpc_url}`;
773
- // python equivalent: datas = [ctx.httpc_url, ctx.step_id, ctx.driver]
774
774
  let stepIdRaw = ctx.step_id || 0;
775
775
  let stepIdStr = LlmIdUtil.stepIdStr(stepIdRaw);
776
776
  ctx._stepIdStr = stepIdStr;
@@ -119,7 +119,7 @@ IORedisObserver.prototype.inject = function (mod, modName) {
119
119
  var command_start_time = Date.now();
120
120
  ctx.footprint(`IORedis ${normalizedCommand} Start`);
121
121
 
122
- // IORedis에서는 command.args에서 쿼리 조합 (Python APM 방식 참고)
122
+ // IORedis에서는 command.args에서 쿼리 조합
123
123
  var queryParts = [normalizedCommand];
124
124
  if (commandArgs && commandArgs.length > 0) {
125
125
  // 각 인자를 문자열로 변환하고 20자로 제한
@@ -151,7 +151,7 @@ KafkaObserver.prototype.inject = function (mod, moduleName) {
151
151
 
152
152
  // 기본값 설정
153
153
  ctx.remoteIp = 0;
154
- ctx.userid = 0;
154
+ ctx.userid = "";
155
155
  ctx.userAgentString = '';
156
156
  ctx.referer = '';
157
157
  ctx.host = '';
@@ -181,7 +181,7 @@ KafkaObserver.prototype.inject = function (mod, moduleName) {
181
181
  '0.0.0.0', // remoteIp
182
182
  '', // userAgent
183
183
  '', // referer
184
- '', // wclientid
184
+ String(ctx.userid || ""), // wclientid
185
185
  String(false), // is_static
186
186
  '' // extra
187
187
  ];
@@ -18,7 +18,7 @@ OpenAIObserver.prototype.inject = function (mod, moduleName) {
18
18
  if (mod.__whatap_observe__) return;
19
19
  mod.__whatap_observe__ = true;
20
20
 
21
- // NOTE: trace_llm_log_enabled 게이트는 conf가 비동기 로드되므로 inject 시점이 아닌
21
+ // NOTE: llm_enabled 게이트는 conf가 비동기 로드되므로 inject 시점이 아닌
22
22
  // 런타임 (wrappedCreate)에서 체크. 패칭 자체는 무조건 걸어둠.
23
23
 
24
24
  Logger.print('WHATAP-LLM-010', 'OpenAIObserver injecting', false);
@@ -59,7 +59,7 @@ function _patchChatCompletions(mod) {
59
59
  shimmer.wrap(CompletionsProto, 'create', function (original) {
60
60
  return function wrappedCreate() {
61
61
  // 런타임 게이트: conf가 늦게 로드되더라도 호출 시점에 체크
62
- if (!conf.getProperty('trace_llm_log_enabled', false)) {
62
+ if (!conf.getProperty('llm_enabled', false)) {
63
63
  return original.apply(this, arguments);
64
64
  }
65
65
  var args = Array.prototype.slice.call(arguments);
@@ -148,7 +148,6 @@ function _buildChatContext(body) {
148
148
  tool_call_id: msg.tool_call_id || '',
149
149
  content: _extractContentText(content),
150
150
  });
151
- // python equivalent: sanitized = dict(msg); preserve all extra keys
152
151
  var sanitizedTool = Object.assign({}, msg);
153
152
  if (Array.isArray(content)) sanitizedTool.content = _extractContentText(content);
154
153
  promptMsgs.push(sanitizedTool);
@@ -161,7 +160,6 @@ function _buildChatContext(body) {
161
160
  }
162
161
  }
163
162
  }
164
- // python equivalent: sanitized = dict(msg); preserve name/tool_calls/function_call 등 모든 키
165
163
  var sanitized = Object.assign({}, msg);
166
164
  if (Array.isArray(content)) sanitized.content = _extractContentText(content);
167
165
  promptMsgs.push(sanitized);
@@ -375,7 +373,7 @@ function _patchEmbeddings(mod) {
375
373
 
376
374
  shimmer.wrap(EmbeddingsProto, 'create', function (original) {
377
375
  return function wrappedCreate() {
378
- if (!conf.getProperty('trace_llm_log_enabled', false)) {
376
+ if (!conf.getProperty('llm_enabled', false)) {
379
377
  return original.apply(this, arguments);
380
378
  }
381
379
  var args = Array.prototype.slice.call(arguments);
@@ -451,7 +449,7 @@ function _patchLegacyCompletions(mod) {
451
449
 
452
450
  shimmer.wrap(LegacyProto, 'create', function (original) {
453
451
  return function wrappedCreate() {
454
- if (!conf.getProperty('trace_llm_log_enabled', false)) {
452
+ if (!conf.getProperty('llm_enabled', false)) {
455
453
  return original.apply(this, arguments);
456
454
  }
457
455
  var args = Array.prototype.slice.call(arguments);
@@ -623,7 +621,7 @@ function _patchResponses() {
623
621
  if (!proto || !proto.create) return;
624
622
  shimmer.wrap(proto, 'create', function (original) {
625
623
  return function wrappedCreate() {
626
- if (!conf.getProperty('trace_llm_log_enabled', false)) {
624
+ if (!conf.getProperty('llm_enabled', false)) {
627
625
  return original.apply(this, arguments);
628
626
  }
629
627
  var args = Array.prototype.slice.call(arguments);
@@ -117,7 +117,6 @@ RedisObserver.prototype.inject = function (mod, modName) {
117
117
  var command_start_time = Date.now();
118
118
  ctx.footprint(`Redis ${commandName} Start`);
119
119
 
120
- // Redis에서는 args에서 쿼리 조합 (Python APM 방식 참고)
121
120
  var queryParts = [commandName];
122
121
  if (args && args.length > 0) {
123
122
  // 콜백 함수 제외하고 실제 Redis 인자들만 처리
@@ -385,7 +385,7 @@ SocketIOObserver.prototype.__handleSocketEmitEvent = function(socket, event, arg
385
385
  ctx.remoteIp || 0,
386
386
  ctx.userAgentString || '',
387
387
  ctx.referer || '',
388
- '',
388
+ String(ctx.userid || ""),
389
389
  String(false), // isStaticContents
390
390
  ''
391
391
  ];
@@ -535,7 +535,8 @@ SocketIOObserver.prototype.__initCtx = function(socket, args, emitType, contextI
535
535
  if(remote_addr){
536
536
  ctx.remoteIp = IPUtil.checkIp4(remote_addr);
537
537
  if(conf.getProperty('trace_user_enabled', true)){
538
- ctx.userid = ctx.remoteIp;
538
+ // WClientId를 빈 문자열로 보내 go-agent가 int64(RemoteIp) 분기를 타도록 함.
539
+ ctx.userid = "";
539
540
  }
540
541
  }
541
542
 
@@ -233,7 +233,7 @@ WebsocketObserver.prototype.__wrapWebSocketConnection = function(connection, req
233
233
  ctx.remoteIp || 0,
234
234
  ctx.userAgentString || '',
235
235
  ctx.referer || '',
236
- String(ctx.userid || 0),
236
+ String(ctx.userid || ""),
237
237
  String(false), // isStaticContents
238
238
  emitType
239
239
  ];
@@ -401,7 +401,8 @@ WebsocketObserver.prototype.__initCtx = function(connection, data, emitType, con
401
401
  if(remote_addr){
402
402
  ctx.remoteIp = IPUtil.checkIp4(remote_addr);
403
403
  if(conf.getProperty('trace_user_enabled', true)){
404
- ctx.userid = ctx.remoteIp;
404
+ // WClientId를 빈 문자열로 보내 go-agent가 int64(RemoteIp) 분기를 타도록 함.
405
+ ctx.userid = "";
405
406
  }
406
407
  }
407
408
 
@@ -1,7 +1,3 @@
1
- /**
2
- * JavaScript implementation of Python's UdpSession
3
- */
4
-
5
1
  const dgram = require('dgram');
6
2
  const os = require('os');
7
3
  const fs = require('fs');
@@ -168,11 +164,10 @@ class UdpSession {
168
164
  self.send_packet(PacketTypeEnum.ACTIVE_STATS, null, datas);
169
165
  // LLM meter용: LLM Go agent(--llm, LLMMode=true)의 TaskLLM이
170
166
  // MeterLLM bucket을 소비하려면 같은 ACTIVE_STATS를 llmSocket에도 보내야 함.
171
- // (python-apm의 udp_session.py:182 에서 동일 목적으로 양 소켓 복제 송신)
172
167
  try {
173
168
  if (self.llmSocket) {
174
169
  self._sendActiveStatsToLlm(statsStr);
175
- } else if (conf.getProperty('llm_enabled', false) || conf.getProperty('trace_llm_log_enabled', false)) {
170
+ } else if (conf.getProperty('llm_enabled', false)) {
176
171
  // lazy init
177
172
  self.udp_llm();
178
173
  if (self.llmSocket) self._sendActiveStatsToLlm(statsStr);
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "whatap",
3
3
  "homepage": "http://www.whatap.io",
4
- "version": "2.0.0-canary.0",
5
- "releaseDate": "20260423",
4
+ "version": "2.0.0",
5
+ "releaseDate": "20260427",
6
6
  "description": "Monitoring and Profiling Service",
7
7
  "main": "index.js",
8
8
  "scripts": {},