From c2ffccd55a2dbbcf2ef5792d9feaf01b6bb86459 Mon Sep 17 00:00:00 2001 From: yanzh Date: Wed, 20 Sep 2017 17:12:14 +0800 Subject: [PATCH 1/9] produce userdata for VSCode to consume --- .../java/debug/core/Configuration.java | 1 + .../debug/core/adapter/ProtocolServer.java | 19 ++++++ com.microsoft.java.debug.plugin/plugin.xml | 1 + .../JavaDebugDelegateCommandHandler.java | 5 ++ .../internal/JavaDebuggerServerPlugin.java | 4 ++ .../plugin/internal/UserDataLogHandler.java | 42 +++++++++++++ .../debug/plugin/internal/UserDataPool.java | 63 +++++++++++++++++++ 7 files changed, 135 insertions(+) create mode 100644 com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/UserDataLogHandler.java create mode 100644 com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/UserDataPool.java diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/Configuration.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/Configuration.java index 0236f07fe..84aced7ba 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/Configuration.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/Configuration.java @@ -13,5 +13,6 @@ public class Configuration { public static final String LOGGER_NAME = "java-debug"; + public static final String USERDATA_LOGGER_NAME = "java-debug-userdata"; } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ProtocolServer.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ProtocolServer.java index 85d8f7b63..115c58379 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ProtocolServer.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ProtocolServer.java @@ -23,6 +23,8 @@ import java.io.Writer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; @@ -31,10 +33,13 @@ import java.util.regex.Pattern; import com.microsoft.java.debug.core.Configuration; +import com.microsoft.java.debug.core.adapter.Messages.Request; public class ProtocolServer { private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME); + private static final Logger loggerUserdata = Logger.getLogger(Configuration.USERDATA_LOGGER_NAME); + private static final int BUFFER_SIZE = 4096; private static final String TWO_CRLF = "\r\n\r\n"; private static final Pattern CONTENT_LENGTH_MATCHER = Pattern.compile("Content-Length: (\\d+)"); @@ -53,6 +58,16 @@ public class ProtocolServer { private IDebugAdapter debugAdapter; + /* userdataMap Schema: + * command: { timestamp: Request JSON String } */ + private Map> userdataMap = new HashMap<>(); + + private void recordRequest(Request message) { + userdataMap.putIfAbsent(message.command, new HashMap()); + userdataMap.get(message.command).put(System.currentTimeMillis(), + message.arguments != null ? message.arguments.toString() : null); + } + /** * Constructs a protocol server instance based on the given input stream and output stream. * @param input @@ -84,6 +99,7 @@ public ProtocolServer(InputStream input, OutputStream output, IProviderContext c * A while-loop to parse input data and send output data constantly. */ public void start() { + userdataMap.clear(); char[] buffer = new char[BUFFER_SIZE]; try { while (!this.terminateSession) { @@ -105,6 +121,8 @@ public void start() { */ public void stop() { this.terminateSession = true; + // telemetry + loggerUserdata.log(Level.INFO, null, userdataMap); } /** @@ -193,6 +211,7 @@ private void dispatchRequest(String request) { try { logger.info("\n[REQUEST]\n" + request); Messages.Request message = JsonUtils.fromJson(request, Messages.Request.class); + recordRequest(message); if (message.type.equals("request")) { synchronized (this) { this.isDispatchingData = true; diff --git a/com.microsoft.java.debug.plugin/plugin.xml b/com.microsoft.java.debug.plugin/plugin.xml index a0f62d0d7..37555231b 100644 --- a/com.microsoft.java.debug.plugin/plugin.xml +++ b/com.microsoft.java.debug.plugin/plugin.xml @@ -6,6 +6,7 @@ + diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaDebugDelegateCommandHandler.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaDebugDelegateCommandHandler.java index 2f4624de4..0ee5b8f30 100644 --- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaDebugDelegateCommandHandler.java +++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaDebugDelegateCommandHandler.java @@ -18,6 +18,8 @@ public class JavaDebugDelegateCommandHandler implements IDelegateCommandHandler { + public static String FETCH_USER_DATA = "vscode.java.fetchUserData"; + public static String DEBUG_STARTSESSION = "vscode.java.startDebugSession"; public static String RESOLVE_CLASSPATH = "vscode.java.resolveClasspath"; @@ -35,7 +37,10 @@ public Object executeCommand(String commandId, List arguments, IProgress return handler.resolveClasspaths(arguments); } else if (BUILD_WORKSPACE.equals(commandId)) { // TODO + } else if (FETCH_USER_DATA.equals(commandId)) { + return UserDataPool.getInstance().fetchAll(); } + throw new UnsupportedOperationException(String.format("Java debug plugin doesn't support the command '%s'.", commandId)); } diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaDebuggerServerPlugin.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaDebuggerServerPlugin.java index 719217be8..43d83262e 100644 --- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaDebuggerServerPlugin.java +++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaDebuggerServerPlugin.java @@ -11,6 +11,7 @@ package com.microsoft.java.debug.plugin.internal; +import java.util.logging.Level; import java.util.logging.Logger; import org.osgi.framework.BundleActivator; @@ -20,6 +21,7 @@ public class JavaDebuggerServerPlugin implements BundleActivator { private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME); + private static final Logger loggerUserdata = Logger.getLogger(Configuration.USERDATA_LOGGER_NAME); public static final String PLUGIN_ID = "com.microsoft.java.debug"; public static BundleContext context = null; @@ -28,6 +30,8 @@ public class JavaDebuggerServerPlugin implements BundleActivator { public void start(BundleContext context) throws Exception { JavaDebuggerServerPlugin.context = context; logger.addHandler(new JdtLogHandler()); + logger.addHandler(new UserDataLogHandler(Level.SEVERE)); + loggerUserdata.addHandler(new UserDataLogHandler(Level.ALL)); logger.info("Starting " + PLUGIN_ID); } diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/UserDataLogHandler.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/UserDataLogHandler.java new file mode 100644 index 000000000..10748395d --- /dev/null +++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/UserDataLogHandler.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (c) 2017 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.debug.plugin.internal; + +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogRecord; + +public class UserDataLogHandler extends Handler { + Level thresholdLevel = Level.SEVERE; + + public UserDataLogHandler(Level level) { + thresholdLevel = level; + } + + @Override + public void publish(LogRecord record) { + if (record.getLevel().intValue() >= thresholdLevel.intValue()) { + UserDataPool.getInstance().logUserdata(record.getMessage(), record.getParameters()); + } + } + + @Override + public void flush() { + // do nothing + } + + @Override + public void close() throws SecurityException { + // do nothing + } + +} diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/UserDataPool.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/UserDataPool.java new file mode 100644 index 000000000..9c7977a44 --- /dev/null +++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/UserDataPool.java @@ -0,0 +1,63 @@ +/******************************************************************************* +* Copyright (c) 2017 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.plugin.internal; + +import java.util.ArrayList; + +import java.util.List; +import java.util.concurrent.ConcurrentLinkedQueue; + +public class UserDataPool { + private ConcurrentLinkedQueue queue; + + public UserDataPool() { + queue = new ConcurrentLinkedQueue<>(); + } + + private static final class SingletonHolder { + private static final UserDataPool INSTANCE = new UserDataPool(); + } + + /** + * Fetch all pending user data records + * @return List of user data Object. + */ + public List fetchAll() { + List ret = new ArrayList<>(); + while (!queue.isEmpty()) { + ret.add(queue.poll()); + } + return ret; + } + + public static UserDataPool getInstance() { + return SingletonHolder.INSTANCE; + } + + /** + * Log user data Object into the queue. + * @param message Error message. + * @param parameters Session-based user data Objects. + */ + public void logUserdata(String message, Object[] parameters) { + if (queue == null) { + return; + } + if (parameters != null) { + for (Object entry : parameters) { + queue.add(entry); + } + } else { + queue.add(message); + } + } +} From f2351797360956aec8f97f598ac74887a28487dd Mon Sep 17 00:00:00 2001 From: yanzh Date: Wed, 20 Sep 2017 18:47:47 +0800 Subject: [PATCH 2/9] fix for counting request number --- .../com/microsoft/java/debug/core/adapter/ProtocolServer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ProtocolServer.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ProtocolServer.java index 115c58379..27a926a28 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ProtocolServer.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ProtocolServer.java @@ -65,7 +65,7 @@ public class ProtocolServer { private void recordRequest(Request message) { userdataMap.putIfAbsent(message.command, new HashMap()); userdataMap.get(message.command).put(System.currentTimeMillis(), - message.arguments != null ? message.arguments.toString() : null); + message.arguments != null ? message.arguments.toString() : ""); } /** From c4bbc5e50aec0f706e5116c37d9649946d518eda Mon Sep 17 00:00:00 2001 From: yanzh Date: Thu, 21 Sep 2017 10:06:07 +0800 Subject: [PATCH 3/9] variable name consistency --- .../java/debug/core/Configuration.java | 2 +- .../debug/core/adapter/ProtocolServer.java | 18 ++++++++++-------- .../internal/JavaDebuggerServerPlugin.java | 2 +- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/Configuration.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/Configuration.java index 84aced7ba..5f71268e8 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/Configuration.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/Configuration.java @@ -13,6 +13,6 @@ public class Configuration { public static final String LOGGER_NAME = "java-debug"; - public static final String USERDATA_LOGGER_NAME = "java-debug-userdata"; + public static final String USER_DATA_LOGGER_NAME = "java-debug-userdata"; } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ProtocolServer.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ProtocolServer.java index 27a926a28..11693cb2d 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ProtocolServer.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ProtocolServer.java @@ -38,7 +38,7 @@ public class ProtocolServer { private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME); - private static final Logger loggerUserdata = Logger.getLogger(Configuration.USERDATA_LOGGER_NAME); + private static final Logger userDataLogger = Logger.getLogger(Configuration.USER_DATA_LOGGER_NAME); private static final int BUFFER_SIZE = 4096; private static final String TWO_CRLF = "\r\n\r\n"; @@ -58,13 +58,15 @@ public class ProtocolServer { private IDebugAdapter debugAdapter; - /* userdataMap Schema: - * command: { timestamp: Request JSON String } */ - private Map> userdataMap = new HashMap<>(); + /** + * userDataMap Schema. + * command: { timestamp: Request JSON String } + */ + private Map> userDataMap = new HashMap<>(); private void recordRequest(Request message) { - userdataMap.putIfAbsent(message.command, new HashMap()); - userdataMap.get(message.command).put(System.currentTimeMillis(), + userDataMap.putIfAbsent(message.command, new HashMap()); + userDataMap.get(message.command).put(System.currentTimeMillis(), message.arguments != null ? message.arguments.toString() : ""); } @@ -99,7 +101,7 @@ public ProtocolServer(InputStream input, OutputStream output, IProviderContext c * A while-loop to parse input data and send output data constantly. */ public void start() { - userdataMap.clear(); + userDataMap.clear(); char[] buffer = new char[BUFFER_SIZE]; try { while (!this.terminateSession) { @@ -122,7 +124,7 @@ public void start() { public void stop() { this.terminateSession = true; // telemetry - loggerUserdata.log(Level.INFO, null, userdataMap); + userDataLogger.log(Level.INFO, null, userDataMap); } /** diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaDebuggerServerPlugin.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaDebuggerServerPlugin.java index 43d83262e..7306a6d53 100644 --- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaDebuggerServerPlugin.java +++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaDebuggerServerPlugin.java @@ -21,7 +21,7 @@ public class JavaDebuggerServerPlugin implements BundleActivator { private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME); - private static final Logger loggerUserdata = Logger.getLogger(Configuration.USERDATA_LOGGER_NAME); + private static final Logger loggerUserdata = Logger.getLogger(Configuration.USER_DATA_LOGGER_NAME); public static final String PLUGIN_ID = "com.microsoft.java.debug"; public static BundleContext context = null; From db145d54dc1cdfc0e7281b43152cb3a7a3006a1f Mon Sep 17 00:00:00 2001 From: yanzh Date: Fri, 22 Sep 2017 16:29:09 +0800 Subject: [PATCH 4/9] aggregate data before sending to VSCode --- .../java/debug/core/Configuration.java | 2 +- .../java/debug/core/UsageDataSession.java | 120 +++++++++++++++++ .../java/debug/core/UsageDataStore.java | 121 ++++++++++++++++++ .../debug/core/adapter/BreakpointManager.java | 4 +- .../java/debug/core/adapter/DebugAdapter.java | 2 +- .../debug/core/adapter/ProtocolServer.java | 27 +--- com.microsoft.java.debug.plugin/plugin.xml | 2 +- .../JavaDebugDelegateCommandHandler.java | 6 +- .../plugin/internal/JavaDebugServer.java | 3 + .../internal/JavaDebuggerServerPlugin.java | 6 +- .../internal/ResolveClasspathsHandler.java | 3 +- ...gHandler.java => UsageDataLogHandler.java} | 19 ++- .../debug/plugin/internal/UserDataPool.java | 63 --------- 13 files changed, 281 insertions(+), 97 deletions(-) create mode 100644 com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/UsageDataSession.java create mode 100644 com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/UsageDataStore.java rename com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/{UserDataLogHandler.java => UsageDataLogHandler.java} (57%) delete mode 100644 com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/UserDataPool.java diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/Configuration.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/Configuration.java index 5f71268e8..6e6b34064 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/Configuration.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/Configuration.java @@ -13,6 +13,6 @@ public class Configuration { public static final String LOGGER_NAME = "java-debug"; - public static final String USER_DATA_LOGGER_NAME = "java-debug-userdata"; + public static final String USAGE_DATA_LOGGER_NAME = "java-debug-usage-data"; } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/UsageDataSession.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/UsageDataSession.java new file mode 100644 index 000000000..bad7ac48d --- /dev/null +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/UsageDataSession.java @@ -0,0 +1,120 @@ +/******************************************************************************* +* Copyright (c) 2017 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.google.gson.Gson; +import com.microsoft.java.debug.core.adapter.Messages.Request; +import com.microsoft.java.debug.core.adapter.Messages.Response; + +public class UsageDataSession { + private static final Logger usageDataLogger = Logger.getLogger(Configuration.USAGE_DATA_LOGGER_NAME); + private static final long RESPONSE_MAX_DELAY_MS = 1000; + private long startAt = -1; + private long stopAt = -1; + private Map commandCountMap = new HashMap<>(); + private Map breakpointCountMap = new HashMap<>(); + private Map requestEventMap = new HashMap<>(); + + class RequestEvent { + Request request; + long timestamp; + + RequestEvent(Request request, long timestamp) { + this.request = request; + this.timestamp = timestamp; + } + } + + public void reportStart() { + startAt = System.currentTimeMillis(); + } + + public void reportStop() { + stopAt = System.currentTimeMillis(); + } + + /** + * Record usage data from request. + */ + public void recordRequest(Request request) { + requestEventMap.put(request.seq, new RequestEvent(request, System.currentTimeMillis())); + + // cmd count + commandCountMap.put(request.command, commandCountMap.getOrDefault(request.command, 0) + 1); + + // bp count + if ("setBreakpoints".equals(request.command)) { + String filename = request.arguments.get("source").getAsJsonObject().get("path").getAsString(); + String filenameHash = "error in filename hashing"; + try { + byte[] hashBytes = MessageDigest.getInstance("SHA-256").digest(filename.getBytes(StandardCharsets.UTF_8)); + StringBuffer buf = new StringBuffer(); + for (byte b : hashBytes) { + buf.append(Integer.toHexString((b & 0xFF) + 0x100).substring(1)); + } + filenameHash = buf.toString(); + } catch (NoSuchAlgorithmException e) { + // ignore it. + } + int bpCount = request.arguments.get("breakpoints").getAsJsonArray().size(); + breakpointCountMap.put(filenameHash, breakpointCountMap.getOrDefault(filenameHash, 0) + bpCount); + } + } + + /** + * Record usage data from response. + */ + public void recordResponse(Response response) { + long responseMillis = System.currentTimeMillis(); + long requestMillis = responseMillis; + String requestCommand = null; + + RequestEvent requestEvent = requestEventMap.getOrDefault(response.request_seq, null); + if (requestEvent != null) { + requestCommand = requestEvent.request.command; + requestMillis = requestEvent.timestamp; + requestEventMap.remove(response.request_seq); + } + long respondingTime = responseMillis - requestMillis; + + if (!response.success || respondingTime > RESPONSE_MAX_DELAY_MS) { + Map props = new HashMap<>(); + props.put("respondingTime", respondingTime); + props.put("requestCommand", requestCommand); + props.put("success", response.success); + // directly report abnormal response. + usageDataLogger.log(Level.WARNING, "abnormal response", props); + } + } + + /** + * Submit summary of usage data in current session. + */ + public void submitUsageData() { + Map props = new HashMap<>(); + + props.put("sessionStartAt", String.valueOf(startAt)); + props.put("sessionStopAt", String.valueOf(stopAt)); + props.put("commandCount", new Gson().toJson(commandCountMap)); + props.put("breakpointCount", new Gson().toJson(breakpointCountMap)); + usageDataLogger.log(Level.INFO, "session usage data summary", props); + } + +} diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/UsageDataStore.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/UsageDataStore.java new file mode 100644 index 000000000..e0b4e71a7 --- /dev/null +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/UsageDataStore.java @@ -0,0 +1,121 @@ +/******************************************************************************* +* Copyright (c) 2017 Microsoft Corporation and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v10.html +* +* Contributors: +* Microsoft Corporation - initial API and implementation +*******************************************************************************/ + +package com.microsoft.java.debug.core; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicReference; + +public class UsageDataStore { + private ConcurrentLinkedQueue queue; + private AtomicReference sessionGuid; + private static final int QUEUE_MAX_SIZE = 10000; + + /** + * Constructor. + */ + public UsageDataStore() { + queue = new ConcurrentLinkedQueue<>(); + sessionGuid = new AtomicReference<>(); + sessionGuid.set(null); + } + + private static final class SingletonHolder { + private static final UsageDataStore INSTANCE = new UsageDataStore(); + } + + /** + * Fetch all pending user data records + * @return List of user data Object. + */ + public List fetchAll() { + List ret = new ArrayList<>(); + while (!queue.isEmpty()) { + ret.add(queue.poll()); + } + return ret; + } + + public static UsageDataStore getInstance() { + return SingletonHolder.INSTANCE; + } + + /** + * Log user data Object into the queue. + */ + public void logSessionData(String desc, Map props) { + if (queue == null) { + return; + } + Map sessionEntry = new HashMap<>(); + sessionEntry.put("scope", "session"); + sessionEntry.put("sessionId", sessionGuid.get()); + if (desc != null) { + sessionEntry.put("description", desc); + } + if (props != null) { + sessionEntry.putAll(props); + } + enqueue(sessionEntry); + } + + /** + * Log Exception details into queue. + */ + public void logErrorData(String desc, Throwable th) { + if (queue == null) { + return; + } + Map errorEntry = new HashMap<>(); + errorEntry.put("scope", "exception"); + errorEntry.put("deubgSessionId", sessionGuid.get()); + if (desc != null) { + errorEntry.put("description", desc); + } + if (th != null) { + errorEntry.put("message", th.getMessage()); + StringWriter sw = new StringWriter(); + th.printStackTrace(new PrintWriter(sw)); + errorEntry.put("stackTrace", sw.toString()); + } + enqueue(errorEntry); + } + + /** + * Assign a GUID for debug session. + */ + public String createSessionGUID() { + if (sessionGuid.get() != null) { + // TODO: last session not disconnected. + } else { + sessionGuid.set(UUID.randomUUID().toString()); + } + return sessionGuid.get(); + } + + public void resetSessionGUID() { + sessionGuid.set(null); + } + + private synchronized void enqueue(Object object) { + if (queue.size() > QUEUE_MAX_SIZE) { + queue.poll(); + } + queue.add(object); + } +} diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/BreakpointManager.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/BreakpointManager.java index 049f43fb5..fbc339970 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/BreakpointManager.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/BreakpointManager.java @@ -75,7 +75,7 @@ public IBreakpoint[] setBreakpoints(String source, IBreakpoint[] breakpoints, bo // Destroy the breakpoint on the debugee VM. bp.close(); } catch (Exception e) { - logger.log(Level.SEVERE, String.format("Remove breakpoint exception: %s", e.toString()), e); + logger.log(Level.SEVERE, "Remove breakpoint exception", e); } this.breakpoints.remove(bp); } @@ -145,7 +145,7 @@ private void removeBreakpointsInternally(String source, IBreakpoint[] breakpoint this.breakpoints.remove(breakpoint); breakpointMap.remove(String.valueOf(breakpoint.lineNumber())); } catch (Exception e) { - logger.log(Level.SEVERE, String.format("Remove breakpoint exception: %s", e.toString()), e); + logger.log(Level.SEVERE, "Remove breakpoint exception", e); } } } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapter.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapter.java index d09e1e0da..f52c2baa7 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapter.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapter.java @@ -77,7 +77,7 @@ public Messages.Response dispatchRequest(Messages.Request request) { String.format("Unrecognized request: { _request: %s }", request.command)); } } catch (Exception e) { - logger.log(Level.SEVERE, String.format("DebugSession dispatch exception: %s", e.toString()), e); + logger.log(Level.SEVERE, "DebugSession dispatch exception", e); AdapterUtils.setErrorResponse(response, ErrorCode.UNKNOWN_FAILURE, e.getMessage() != null ? e.getMessage() : e.toString()); } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ProtocolServer.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ProtocolServer.java index 11693cb2d..a2867b309 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ProtocolServer.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ProtocolServer.java @@ -23,8 +23,6 @@ import java.io.Writer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.Map; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; @@ -33,13 +31,11 @@ import java.util.regex.Pattern; import com.microsoft.java.debug.core.Configuration; -import com.microsoft.java.debug.core.adapter.Messages.Request; +import com.microsoft.java.debug.core.UsageDataSession; public class ProtocolServer { private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME); - private static final Logger userDataLogger = Logger.getLogger(Configuration.USER_DATA_LOGGER_NAME); - private static final int BUFFER_SIZE = 4096; private static final String TWO_CRLF = "\r\n\r\n"; private static final Pattern CONTENT_LENGTH_MATCHER = Pattern.compile("Content-Length: (\\d+)"); @@ -58,17 +54,7 @@ public class ProtocolServer { private IDebugAdapter debugAdapter; - /** - * userDataMap Schema. - * command: { timestamp: Request JSON String } - */ - private Map> userDataMap = new HashMap<>(); - - private void recordRequest(Request message) { - userDataMap.putIfAbsent(message.command, new HashMap()); - userDataMap.get(message.command).put(System.currentTimeMillis(), - message.arguments != null ? message.arguments.toString() : ""); - } + private UsageDataSession usageDataSession = new UsageDataSession(); /** * Constructs a protocol server instance based on the given input stream and output stream. @@ -101,7 +87,7 @@ public ProtocolServer(InputStream input, OutputStream output, IProviderContext c * A while-loop to parse input data and send output data constantly. */ public void start() { - userDataMap.clear(); + usageDataSession.reportStart(); char[] buffer = new char[BUFFER_SIZE]; try { while (!this.terminateSession) { @@ -122,9 +108,9 @@ public void start() { * Sets terminateSession flag to true. And the dispatcher loop will be terminated after current dispatching operation finishes. */ public void stop() { + usageDataSession.reportStop(); this.terminateSession = true; - // telemetry - userDataLogger.log(Level.INFO, null, userDataMap); + usageDataSession.submitUsageData(); } /** @@ -213,7 +199,7 @@ private void dispatchRequest(String request) { try { logger.info("\n[REQUEST]\n" + request); Messages.Request message = JsonUtils.fromJson(request, Messages.Request.class); - recordRequest(message); + usageDataSession.recordRequest(message); if (message.type.equals("request")) { synchronized (this) { this.isDispatchingData = true; @@ -225,6 +211,7 @@ private void dispatchRequest(String request) { this.stop(); } sendMessage(response); + usageDataSession.recordResponse(response); } catch (Exception e) { logger.log(Level.SEVERE, String.format("Dispatch debug protocol error: %s", e.toString()), e); } diff --git a/com.microsoft.java.debug.plugin/plugin.xml b/com.microsoft.java.debug.plugin/plugin.xml index 37555231b..7a17b1d91 100644 --- a/com.microsoft.java.debug.plugin/plugin.xml +++ b/com.microsoft.java.debug.plugin/plugin.xml @@ -6,7 +6,7 @@ - + diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaDebugDelegateCommandHandler.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaDebugDelegateCommandHandler.java index 0ee5b8f30..50e27aaba 100644 --- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaDebugDelegateCommandHandler.java +++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaDebugDelegateCommandHandler.java @@ -16,9 +16,11 @@ import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jdt.ls.core.internal.IDelegateCommandHandler; +import com.microsoft.java.debug.core.UsageDataStore; + public class JavaDebugDelegateCommandHandler implements IDelegateCommandHandler { - public static String FETCH_USER_DATA = "vscode.java.fetchUserData"; + public static String FETCH_USER_DATA = "vscode.java.fetchUsageData"; public static String DEBUG_STARTSESSION = "vscode.java.startDebugSession"; @@ -38,7 +40,7 @@ public Object executeCommand(String commandId, List arguments, IProgress } else if (BUILD_WORKSPACE.equals(commandId)) { // TODO } else if (FETCH_USER_DATA.equals(commandId)) { - return UserDataPool.getInstance().fetchAll(); + return UsageDataStore.getInstance().fetchAll(); } throw new UnsupportedOperationException(String.format("Java debug plugin doesn't support the command '%s'.", commandId)); diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaDebugServer.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaDebugServer.java index a1aa5a8e2..3ab34d14a 100644 --- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaDebugServer.java +++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaDebugServer.java @@ -22,6 +22,7 @@ import java.util.logging.Logger; import com.microsoft.java.debug.core.Configuration; +import com.microsoft.java.debug.core.UsageDataStore; import com.microsoft.java.debug.core.adapter.ProtocolServer; public class JavaDebugServer implements IDebugServer { @@ -130,6 +131,7 @@ private Runnable createConnectionTask(Socket connection) { @Override public void run() { try { + UsageDataStore.getInstance().createSessionGUID(); ProtocolServer protocolServer = new ProtocolServer(connection.getInputStream(), connection.getOutputStream(), JdtProviderContextFactory.createProviderContext()); // protocol server will dispatch request and send response in a while-loop. @@ -138,6 +140,7 @@ public void run() { logger.log(Level.SEVERE, String.format("Socket connection exception: %s", e.toString()), e); } finally { logger.info("Debug connection closed"); + UsageDataStore.getInstance().resetSessionGUID(); } } }; diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaDebuggerServerPlugin.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaDebuggerServerPlugin.java index 7306a6d53..ef6796ae5 100644 --- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaDebuggerServerPlugin.java +++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaDebuggerServerPlugin.java @@ -21,7 +21,7 @@ public class JavaDebuggerServerPlugin implements BundleActivator { private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME); - private static final Logger loggerUserdata = Logger.getLogger(Configuration.USER_DATA_LOGGER_NAME); + private static final Logger loggerUserdata = Logger.getLogger(Configuration.USAGE_DATA_LOGGER_NAME); public static final String PLUGIN_ID = "com.microsoft.java.debug"; public static BundleContext context = null; @@ -30,8 +30,8 @@ public class JavaDebuggerServerPlugin implements BundleActivator { public void start(BundleContext context) throws Exception { JavaDebuggerServerPlugin.context = context; logger.addHandler(new JdtLogHandler()); - logger.addHandler(new UserDataLogHandler(Level.SEVERE)); - loggerUserdata.addHandler(new UserDataLogHandler(Level.ALL)); + logger.addHandler(new UsageDataLogHandler(Level.SEVERE)); + loggerUserdata.addHandler(new UsageDataLogHandler(Level.ALL)); logger.info("Starting " + PLUGIN_ID); } diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveClasspathsHandler.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveClasspathsHandler.java index 942b3c156..0b93910ea 100644 --- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveClasspathsHandler.java +++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveClasspathsHandler.java @@ -13,6 +13,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.logging.Level; import java.util.logging.Logger; import org.eclipse.core.resources.IProject; @@ -48,7 +49,7 @@ public String[] resolveClasspaths(List arguments) throws Exception { try { return computeClassPath((String) arguments.get(0), (String) arguments.get(1)); } catch (CoreException e) { - logger.severe("Failed to resolve classpath: " + e.getMessage()); + logger.log(Level.SEVERE, "Failed to resolve classpath: " + e.getMessage(), e); throw new Exception("Failed to resolve classpath: " + e.getMessage(), e); } } diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/UserDataLogHandler.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/UsageDataLogHandler.java similarity index 57% rename from com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/UserDataLogHandler.java rename to com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/UsageDataLogHandler.java index 10748395d..c1d9e9d32 100644 --- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/UserDataLogHandler.java +++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/UsageDataLogHandler.java @@ -11,21 +11,34 @@ package com.microsoft.java.debug.plugin.internal; +import java.util.Map; import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.LogRecord; -public class UserDataLogHandler extends Handler { +import com.microsoft.java.debug.core.UsageDataStore; + +public class UsageDataLogHandler extends Handler { Level thresholdLevel = Level.SEVERE; - public UserDataLogHandler(Level level) { + public UsageDataLogHandler(Level level) { thresholdLevel = level; } @Override public void publish(LogRecord record) { if (record.getLevel().intValue() >= thresholdLevel.intValue()) { - UserDataPool.getInstance().logUserdata(record.getMessage(), record.getParameters()); + if (record.getThrown() != null) { + // error message + UsageDataStore.getInstance().logErrorData(record.getMessage(), record.getThrown()); + } else if (record.getParameters() != null) { + // debug session details + Object[] params = record.getParameters(); + if (params.length == 1 && params[0].getClass() != null + && Map.class.isAssignableFrom(params[0].getClass())) { + UsageDataStore.getInstance().logSessionData(record.getMessage(), (Map) params[0]); + } + } } } diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/UserDataPool.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/UserDataPool.java deleted file mode 100644 index 9c7977a44..000000000 --- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/UserDataPool.java +++ /dev/null @@ -1,63 +0,0 @@ -/******************************************************************************* -* Copyright (c) 2017 Microsoft Corporation and others. -* All rights reserved. This program and the accompanying materials -* are made available under the terms of the Eclipse Public License v1.0 -* which accompanies this distribution, and is available at -* http://www.eclipse.org/legal/epl-v10.html -* -* Contributors: -* Microsoft Corporation - initial API and implementation -*******************************************************************************/ - -package com.microsoft.java.debug.plugin.internal; - -import java.util.ArrayList; - -import java.util.List; -import java.util.concurrent.ConcurrentLinkedQueue; - -public class UserDataPool { - private ConcurrentLinkedQueue queue; - - public UserDataPool() { - queue = new ConcurrentLinkedQueue<>(); - } - - private static final class SingletonHolder { - private static final UserDataPool INSTANCE = new UserDataPool(); - } - - /** - * Fetch all pending user data records - * @return List of user data Object. - */ - public List fetchAll() { - List ret = new ArrayList<>(); - while (!queue.isEmpty()) { - ret.add(queue.poll()); - } - return ret; - } - - public static UserDataPool getInstance() { - return SingletonHolder.INSTANCE; - } - - /** - * Log user data Object into the queue. - * @param message Error message. - * @param parameters Session-based user data Objects. - */ - public void logUserdata(String message, Object[] parameters) { - if (queue == null) { - return; - } - if (parameters != null) { - for (Object entry : parameters) { - queue.add(entry); - } - } else { - queue.add(message); - } - } -} From 6777dc975cf9b9cca297126f08cf8829ae424f05 Mon Sep 17 00:00:00 2001 From: yanzh Date: Fri, 22 Sep 2017 16:38:43 +0800 Subject: [PATCH 5/9] repair log --- .../microsoft/java/debug/core/adapter/BreakpointManager.java | 4 ++-- .../com/microsoft/java/debug/core/adapter/DebugAdapter.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/BreakpointManager.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/BreakpointManager.java index fbc339970..049f43fb5 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/BreakpointManager.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/BreakpointManager.java @@ -75,7 +75,7 @@ public IBreakpoint[] setBreakpoints(String source, IBreakpoint[] breakpoints, bo // Destroy the breakpoint on the debugee VM. bp.close(); } catch (Exception e) { - logger.log(Level.SEVERE, "Remove breakpoint exception", e); + logger.log(Level.SEVERE, String.format("Remove breakpoint exception: %s", e.toString()), e); } this.breakpoints.remove(bp); } @@ -145,7 +145,7 @@ private void removeBreakpointsInternally(String source, IBreakpoint[] breakpoint this.breakpoints.remove(breakpoint); breakpointMap.remove(String.valueOf(breakpoint.lineNumber())); } catch (Exception e) { - logger.log(Level.SEVERE, "Remove breakpoint exception", e); + logger.log(Level.SEVERE, String.format("Remove breakpoint exception: %s", e.toString()), e); } } } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapter.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapter.java index f52c2baa7..d09e1e0da 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapter.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapter.java @@ -77,7 +77,7 @@ public Messages.Response dispatchRequest(Messages.Request request) { String.format("Unrecognized request: { _request: %s }", request.command)); } } catch (Exception e) { - logger.log(Level.SEVERE, "DebugSession dispatch exception", e); + logger.log(Level.SEVERE, String.format("DebugSession dispatch exception: %s", e.toString()), e); AdapterUtils.setErrorResponse(response, ErrorCode.UNKNOWN_FAILURE, e.getMessage() != null ? e.getMessage() : e.toString()); } From d1ff793c93f87a5dfa02782489844be5496960ee Mon Sep 17 00:00:00 2001 From: yanzh Date: Sat, 23 Sep 2017 01:41:26 +0800 Subject: [PATCH 6/9] fix according to code review --- .../java/debug/core/UsageDataSession.java | 42 +++++++++---------- .../java/debug/core/UsageDataStore.java | 10 ++--- .../java/debug/core/adapter/AdapterUtils.java | 24 +++++++++++ .../internal/JavaDebuggerServerPlugin.java | 4 +- 4 files changed, 48 insertions(+), 32 deletions(-) diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/UsageDataSession.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/UsageDataSession.java index bad7ac48d..604711449 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/UsageDataSession.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/UsageDataSession.java @@ -11,15 +11,14 @@ package com.microsoft.java.debug.core; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; import java.util.HashMap; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; -import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.microsoft.java.debug.core.adapter.AdapterUtils; +import com.microsoft.java.debug.core.adapter.JsonUtils; import com.microsoft.java.debug.core.adapter.Messages.Request; import com.microsoft.java.debug.core.adapter.Messages.Response; @@ -61,18 +60,15 @@ public void recordRequest(Request request) { // bp count if ("setBreakpoints".equals(request.command)) { - String filename = request.arguments.get("source").getAsJsonObject().get("path").getAsString(); - String filenameHash = "error in filename hashing"; - try { - byte[] hashBytes = MessageDigest.getInstance("SHA-256").digest(filename.getBytes(StandardCharsets.UTF_8)); - StringBuffer buf = new StringBuffer(); - for (byte b : hashBytes) { - buf.append(Integer.toHexString((b & 0xFF) + 0x100).substring(1)); - } - filenameHash = buf.toString(); - } catch (NoSuchAlgorithmException e) { - // ignore it. + String fileIdentifier = "unknown file"; + JsonElement pathElement = request.arguments.get("source").getAsJsonObject().get("path"); + JsonElement nameElement = request.arguments.get("source").getAsJsonObject().get("name"); + if (pathElement != null) { + fileIdentifier = pathElement.getAsString(); + } else if (nameElement != null) { + fileIdentifier = nameElement.getAsString(); } + String filenameHash = AdapterUtils.getSHA256HexDigest(fileIdentifier); int bpCount = request.arguments.get("breakpoints").getAsJsonArray().size(); breakpointCountMap.put(filenameHash, breakpointCountMap.getOrDefault(filenameHash, 0) + bpCount); } @@ -84,20 +80,20 @@ public void recordRequest(Request request) { public void recordResponse(Response response) { long responseMillis = System.currentTimeMillis(); long requestMillis = responseMillis; - String requestCommand = null; + String command = null; RequestEvent requestEvent = requestEventMap.getOrDefault(response.request_seq, null); if (requestEvent != null) { - requestCommand = requestEvent.request.command; + command = requestEvent.request.command; requestMillis = requestEvent.timestamp; requestEventMap.remove(response.request_seq); } - long respondingTime = responseMillis - requestMillis; + long duration = responseMillis - requestMillis; - if (!response.success || respondingTime > RESPONSE_MAX_DELAY_MS) { + if (!response.success || duration > RESPONSE_MAX_DELAY_MS) { Map props = new HashMap<>(); - props.put("respondingTime", respondingTime); - props.put("requestCommand", requestCommand); + props.put("duration", duration); + props.put("command", command); props.put("success", response.success); // directly report abnormal response. usageDataLogger.log(Level.WARNING, "abnormal response", props); @@ -112,8 +108,8 @@ public void submitUsageData() { props.put("sessionStartAt", String.valueOf(startAt)); props.put("sessionStopAt", String.valueOf(stopAt)); - props.put("commandCount", new Gson().toJson(commandCountMap)); - props.put("breakpointCount", new Gson().toJson(breakpointCountMap)); + props.put("commandCount", JsonUtils.toJson(commandCountMap)); + props.put("breakpointCount", JsonUtils.toJson(breakpointCountMap)); usageDataLogger.log(Level.INFO, "session usage data summary", props); } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/UsageDataStore.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/UsageDataStore.java index e0b4e71a7..10cb78159 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/UsageDataStore.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/UsageDataStore.java @@ -13,9 +13,7 @@ import java.io.PrintWriter; import java.io.StringWriter; -import java.util.ArrayList; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentLinkedQueue; @@ -43,11 +41,9 @@ private static final class SingletonHolder { * Fetch all pending user data records * @return List of user data Object. */ - public List fetchAll() { - List ret = new ArrayList<>(); - while (!queue.isEmpty()) { - ret.add(queue.poll()); - } + public synchronized Object[] fetchAll() { + Object[] ret = queue.toArray(); + queue.clear(); return ret; } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/AdapterUtils.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/AdapterUtils.java index 7ae06689d..7823a6f7f 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/AdapterUtils.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/AdapterUtils.java @@ -13,11 +13,14 @@ import java.net.URI; import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; import java.nio.file.FileSystemNotFoundException; import java.nio.file.Files; import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.nio.file.Paths; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -179,4 +182,25 @@ public static void setErrorResponse(Response response, ErrorCode errorCode, Exce response.message = errorMessage; response.success = false; } + + /** + * Calculate SHA-256 Digest of given string. + * @param content + * + * string to digest + * @return string of Hex digest + */ + public static String getSHA256HexDigest(String content) { + byte[] hashBytes = null; + try { + hashBytes = MessageDigest.getInstance("SHA-256").digest(content.getBytes(StandardCharsets.UTF_8)); + } catch (NoSuchAlgorithmException e) { + // ignore it. + } + StringBuffer buf = new StringBuffer(); + for (byte b : hashBytes) { + buf.append(Integer.toHexString((b & 0xFF) + 0x100).substring(1)); + } + return buf.toString(); + } } diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaDebuggerServerPlugin.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaDebuggerServerPlugin.java index ef6796ae5..750402b8a 100644 --- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaDebuggerServerPlugin.java +++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaDebuggerServerPlugin.java @@ -21,7 +21,7 @@ public class JavaDebuggerServerPlugin implements BundleActivator { private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME); - private static final Logger loggerUserdata = Logger.getLogger(Configuration.USAGE_DATA_LOGGER_NAME); + private static final Logger usageDataLogger = Logger.getLogger(Configuration.USAGE_DATA_LOGGER_NAME); public static final String PLUGIN_ID = "com.microsoft.java.debug"; public static BundleContext context = null; @@ -31,7 +31,7 @@ public void start(BundleContext context) throws Exception { JavaDebuggerServerPlugin.context = context; logger.addHandler(new JdtLogHandler()); logger.addHandler(new UsageDataLogHandler(Level.SEVERE)); - loggerUserdata.addHandler(new UsageDataLogHandler(Level.ALL)); + usageDataLogger.addHandler(new UsageDataLogHandler(Level.ALL)); logger.info("Starting " + PLUGIN_ID); } From 129f59812dab4e89bb383490d52fa8d005c5a0f0 Mon Sep 17 00:00:00 2001 From: yanzh Date: Mon, 25 Sep 2017 09:34:54 +0800 Subject: [PATCH 7/9] handle potential NPE --- .../com/microsoft/java/debug/core/adapter/AdapterUtils.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/AdapterUtils.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/AdapterUtils.java index 7823a6f7f..b97847e70 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/AdapterUtils.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/AdapterUtils.java @@ -198,8 +198,10 @@ public static String getSHA256HexDigest(String content) { // ignore it. } StringBuffer buf = new StringBuffer(); - for (byte b : hashBytes) { - buf.append(Integer.toHexString((b & 0xFF) + 0x100).substring(1)); + if (hashBytes != null) { + for (byte b : hashBytes) { + buf.append(Integer.toHexString((b & 0xFF) + 0x100).substring(1)); + } } return buf.toString(); } From 62de46031ac5121023e97d060dc3f8546b570f1a Mon Sep 17 00:00:00 2001 From: yanzh Date: Mon, 25 Sep 2017 14:59:55 +0800 Subject: [PATCH 8/9] fix GUID for simultaneous debug sessions --- .../java/debug/core/UsageDataSession.java | 10 +++++++ .../java/debug/core/UsageDataStore.java | 27 +++---------------- .../debug/core/adapter/ProtocolServer.java | 1 - .../plugin/internal/JavaDebugServer.java | 6 ++--- 4 files changed, 16 insertions(+), 28 deletions(-) diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/UsageDataSession.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/UsageDataSession.java index 604711449..f9ab5de77 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/UsageDataSession.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/UsageDataSession.java @@ -25,12 +25,22 @@ public class UsageDataSession { private static final Logger usageDataLogger = Logger.getLogger(Configuration.USAGE_DATA_LOGGER_NAME); private static final long RESPONSE_MAX_DELAY_MS = 1000; + private static final ThreadLocal sessionGuid = new InheritableThreadLocal<>(); + private long startAt = -1; private long stopAt = -1; private Map commandCountMap = new HashMap<>(); private Map breakpointCountMap = new HashMap<>(); private Map requestEventMap = new HashMap<>(); + public static void setSessionGuid(String guid) { + sessionGuid.set(guid); + } + + public static String getSessionGuid() { + return sessionGuid.get(); + } + class RequestEvent { Request request; long timestamp; diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/UsageDataStore.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/UsageDataStore.java index 10cb78159..f0de300cd 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/UsageDataStore.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/UsageDataStore.java @@ -15,22 +15,17 @@ import java.io.StringWriter; import java.util.HashMap; import java.util.Map; -import java.util.UUID; import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.atomic.AtomicReference; public class UsageDataStore { private ConcurrentLinkedQueue queue; - private AtomicReference sessionGuid; private static final int QUEUE_MAX_SIZE = 10000; /** * Constructor. */ - public UsageDataStore() { + private UsageDataStore() { queue = new ConcurrentLinkedQueue<>(); - sessionGuid = new AtomicReference<>(); - sessionGuid.set(null); } private static final class SingletonHolder { @@ -60,7 +55,7 @@ public void logSessionData(String desc, Map props) { } Map sessionEntry = new HashMap<>(); sessionEntry.put("scope", "session"); - sessionEntry.put("sessionId", sessionGuid.get()); + sessionEntry.put("debugSessionId", UsageDataSession.getSessionGuid()); if (desc != null) { sessionEntry.put("description", desc); } @@ -79,7 +74,7 @@ public void logErrorData(String desc, Throwable th) { } Map errorEntry = new HashMap<>(); errorEntry.put("scope", "exception"); - errorEntry.put("deubgSessionId", sessionGuid.get()); + errorEntry.put("deubgSessionId", UsageDataSession.getSessionGuid()); if (desc != null) { errorEntry.put("description", desc); } @@ -92,22 +87,6 @@ public void logErrorData(String desc, Throwable th) { enqueue(errorEntry); } - /** - * Assign a GUID for debug session. - */ - public String createSessionGUID() { - if (sessionGuid.get() != null) { - // TODO: last session not disconnected. - } else { - sessionGuid.set(UUID.randomUUID().toString()); - } - return sessionGuid.get(); - } - - public void resetSessionGUID() { - sessionGuid.set(null); - } - private synchronized void enqueue(Object object) { if (queue.size() > QUEUE_MAX_SIZE) { queue.poll(); diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ProtocolServer.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ProtocolServer.java index a2867b309..11f2b57b5 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ProtocolServer.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ProtocolServer.java @@ -35,7 +35,6 @@ public class ProtocolServer { private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME); - private static final int BUFFER_SIZE = 4096; private static final String TWO_CRLF = "\r\n\r\n"; private static final Pattern CONTENT_LENGTH_MATCHER = Pattern.compile("Content-Length: (\\d+)"); diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaDebugServer.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaDebugServer.java index 3ab34d14a..d57e56b76 100644 --- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaDebugServer.java +++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaDebugServer.java @@ -14,6 +14,7 @@ import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; +import java.util.UUID; import java.util.concurrent.ExecutorService; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; @@ -22,7 +23,7 @@ import java.util.logging.Logger; import com.microsoft.java.debug.core.Configuration; -import com.microsoft.java.debug.core.UsageDataStore; +import com.microsoft.java.debug.core.UsageDataSession; import com.microsoft.java.debug.core.adapter.ProtocolServer; public class JavaDebugServer implements IDebugServer { @@ -131,7 +132,7 @@ private Runnable createConnectionTask(Socket connection) { @Override public void run() { try { - UsageDataStore.getInstance().createSessionGUID(); + UsageDataSession.setSessionGuid(UUID.randomUUID().toString()); ProtocolServer protocolServer = new ProtocolServer(connection.getInputStream(), connection.getOutputStream(), JdtProviderContextFactory.createProviderContext()); // protocol server will dispatch request and send response in a while-loop. @@ -140,7 +141,6 @@ public void run() { logger.log(Level.SEVERE, String.format("Socket connection exception: %s", e.toString()), e); } finally { logger.info("Debug connection closed"); - UsageDataStore.getInstance().resetSessionGUID(); } } }; From 9718c2049ff4b2945bac3318acd44e3be7d03db6 Mon Sep 17 00:00:00 2001 From: yanzh Date: Mon, 25 Sep 2017 16:18:54 +0800 Subject: [PATCH 9/9] dismiss possible exceptions --- .../java/debug/core/UsageDataSession.java | 80 ++++++++++--------- 1 file changed, 44 insertions(+), 36 deletions(-) diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/UsageDataSession.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/UsageDataSession.java index f9ab5de77..4481a7e87 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/UsageDataSession.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/UsageDataSession.java @@ -63,24 +63,28 @@ public void reportStop() { * Record usage data from request. */ public void recordRequest(Request request) { - requestEventMap.put(request.seq, new RequestEvent(request, System.currentTimeMillis())); - - // cmd count - commandCountMap.put(request.command, commandCountMap.getOrDefault(request.command, 0) + 1); - - // bp count - if ("setBreakpoints".equals(request.command)) { - String fileIdentifier = "unknown file"; - JsonElement pathElement = request.arguments.get("source").getAsJsonObject().get("path"); - JsonElement nameElement = request.arguments.get("source").getAsJsonObject().get("name"); - if (pathElement != null) { - fileIdentifier = pathElement.getAsString(); - } else if (nameElement != null) { - fileIdentifier = nameElement.getAsString(); + try { + requestEventMap.put(request.seq, new RequestEvent(request, System.currentTimeMillis())); + + // cmd count + commandCountMap.put(request.command, commandCountMap.getOrDefault(request.command, 0) + 1); + + // bp count + if ("setBreakpoints".equals(request.command)) { + String fileIdentifier = "unknown file"; + JsonElement pathElement = request.arguments.get("source").getAsJsonObject().get("path"); + JsonElement nameElement = request.arguments.get("source").getAsJsonObject().get("name"); + if (pathElement != null) { + fileIdentifier = pathElement.getAsString(); + } else if (nameElement != null) { + fileIdentifier = nameElement.getAsString(); + } + String filenameHash = AdapterUtils.getSHA256HexDigest(fileIdentifier); + int bpCount = request.arguments.get("breakpoints").getAsJsonArray().size(); + breakpointCountMap.put(filenameHash, breakpointCountMap.getOrDefault(filenameHash, 0) + bpCount); } - String filenameHash = AdapterUtils.getSHA256HexDigest(fileIdentifier); - int bpCount = request.arguments.get("breakpoints").getAsJsonArray().size(); - breakpointCountMap.put(filenameHash, breakpointCountMap.getOrDefault(filenameHash, 0) + bpCount); + } catch (Throwable e) { + // ignore it } } @@ -88,25 +92,29 @@ public void recordRequest(Request request) { * Record usage data from response. */ public void recordResponse(Response response) { - long responseMillis = System.currentTimeMillis(); - long requestMillis = responseMillis; - String command = null; - - RequestEvent requestEvent = requestEventMap.getOrDefault(response.request_seq, null); - if (requestEvent != null) { - command = requestEvent.request.command; - requestMillis = requestEvent.timestamp; - requestEventMap.remove(response.request_seq); - } - long duration = responseMillis - requestMillis; - - if (!response.success || duration > RESPONSE_MAX_DELAY_MS) { - Map props = new HashMap<>(); - props.put("duration", duration); - props.put("command", command); - props.put("success", response.success); - // directly report abnormal response. - usageDataLogger.log(Level.WARNING, "abnormal response", props); + try { + long responseMillis = System.currentTimeMillis(); + long requestMillis = responseMillis; + String command = null; + + RequestEvent requestEvent = requestEventMap.getOrDefault(response.request_seq, null); + if (requestEvent != null) { + command = requestEvent.request.command; + requestMillis = requestEvent.timestamp; + requestEventMap.remove(response.request_seq); + } + long duration = responseMillis - requestMillis; + + if (!response.success || duration > RESPONSE_MAX_DELAY_MS) { + Map props = new HashMap<>(); + props.put("duration", duration); + props.put("command", command); + props.put("success", response.success); + // directly report abnormal response. + usageDataLogger.log(Level.WARNING, "abnormal response", props); + } + } catch (Throwable e) { + // ignore it } }