Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@

public class Configuration {
public static final String LOGGER_NAME = "java-debug";
public static final String USAGE_DATA_LOGGER_NAME = "java-debug-usage-data";

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*******************************************************************************
* 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.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

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;

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<String> sessionGuid = new InheritableThreadLocal<>();

private long startAt = -1;
private long stopAt = -1;
private Map<String, Integer> commandCountMap = new HashMap<>();
private Map<String, Integer> breakpointCountMap = new HashMap<>();
private Map<Integer, RequestEvent> 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;

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) {
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);
}
} catch (Throwable e) {
// ignore it
}
}

/**
* Record usage data from response.
*/
public void recordResponse(Response response) {
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<String, Object> 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
}
}

/**
* Submit summary of usage data in current session.
*/
public void submitUsageData() {
Map<String, String> props = new HashMap<>();

props.put("sessionStartAt", String.valueOf(startAt));
props.put("sessionStopAt", String.valueOf(stopAt));
props.put("commandCount", JsonUtils.toJson(commandCountMap));
props.put("breakpointCount", JsonUtils.toJson(breakpointCountMap));
usageDataLogger.log(Level.INFO, "session usage data summary", props);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*******************************************************************************
* 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.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentLinkedQueue;

public class UsageDataStore {
private ConcurrentLinkedQueue<Object> queue;
private static final int QUEUE_MAX_SIZE = 10000;

/**
* Constructor.
*/
private UsageDataStore() {
queue = new ConcurrentLinkedQueue<>();
}

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 synchronized Object[] fetchAll() {
Object[] ret = queue.toArray();
queue.clear();
return ret;
}

public static UsageDataStore getInstance() {
return SingletonHolder.INSTANCE;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we have multiple sessions going on at the same time, they will share the same session id, which is not expected.

}

/**
* Log user data Object into the queue.
*/
public void logSessionData(String desc, Map<String, String> props) {
if (queue == null) {
return;
}
Map<String, String> sessionEntry = new HashMap<>();
sessionEntry.put("scope", "session");
sessionEntry.put("debugSessionId", UsageDataSession.getSessionGuid());
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<String, String> errorEntry = new HashMap<>();
errorEntry.put("scope", "exception");
errorEntry.put("deubgSessionId", UsageDataSession.getSessionGuid());
if (desc != null) {
errorEntry.put("description", desc);
}
if (th != null) {
errorEntry.put("message", th.getMessage());
StringWriter sw = new StringWriter();
th.printStackTrace(new PrintWriter(sw));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

better to close StringWriter PrintWriter

errorEntry.put("stackTrace", sw.toString());
}
enqueue(errorEntry);
}

private synchronized void enqueue(Object object) {
if (queue.size() > QUEUE_MAX_SIZE) {
queue.poll();
}
queue.add(object);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -179,4 +182,27 @@ 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();
if (hashBytes != null) {
for (byte b : hashBytes) {
buf.append(Integer.toHexString((b & 0xFF) + 0x100).substring(1));
}
}
return buf.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@
import java.util.regex.Pattern;

import com.microsoft.java.debug.core.Configuration;
import com.microsoft.java.debug.core.UsageDataSession;

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+)");
Expand All @@ -53,6 +53,8 @@ public class ProtocolServer {

private IDebugAdapter debugAdapter;

private UsageDataSession usageDataSession = new UsageDataSession();

/**
* Constructs a protocol server instance based on the given input stream and output stream.
* @param input
Expand Down Expand Up @@ -84,6 +86,7 @@ public ProtocolServer(InputStream input, OutputStream output, IProviderContext c
* A while-loop to parse input data and send output data constantly.
*/
public void start() {
usageDataSession.reportStart();
char[] buffer = new char[BUFFER_SIZE];
try {
while (!this.terminateSession) {
Expand All @@ -104,7 +107,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;
usageDataSession.submitUsageData();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm not sure, but could it possibly be wrapped in sth like a finally block? namely should we report when exception thrown during the session?

}

/**
Expand Down Expand Up @@ -193,6 +198,7 @@ private void dispatchRequest(String request) {
try {
logger.info("\n[REQUEST]\n" + request);
Messages.Request message = JsonUtils.fromJson(request, Messages.Request.class);
usageDataSession.recordRequest(message);
if (message.type.equals("request")) {
synchronized (this) {
this.isDispatchingData = true;
Expand All @@ -204,6 +210,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);
}
Expand Down
1 change: 1 addition & 0 deletions com.microsoft.java.debug.plugin/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<command id="vscode.java.startDebugSession"/>
<command id="vscode.java.resolveClasspath"/>
<command id="vscode.java.buildWorkspace"/>
<command id="vscode.java.fetchUsageData"/>
</delegateCommandHandler>
</extension>
</plugin>
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@
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.fetchUsageData";

public static String DEBUG_STARTSESSION = "vscode.java.startDebugSession";

public static String RESOLVE_CLASSPATH = "vscode.java.resolveClasspath";
Expand All @@ -35,7 +39,10 @@ public Object executeCommand(String commandId, List<Object> arguments, IProgress
return handler.resolveClasspaths(arguments);
} else if (BUILD_WORKSPACE.equals(commandId)) {
// TODO
} else if (FETCH_USER_DATA.equals(commandId)) {
return UsageDataStore.getInstance().fetchAll();
}

throw new UnsupportedOperationException(String.format("Java debug plugin doesn't support the command '%s'.", commandId));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -22,6 +23,7 @@
import java.util.logging.Logger;

import com.microsoft.java.debug.core.Configuration;
import com.microsoft.java.debug.core.UsageDataSession;
import com.microsoft.java.debug.core.adapter.ProtocolServer;

public class JavaDebugServer implements IDebugServer {
Expand Down Expand Up @@ -130,6 +132,7 @@ private Runnable createConnectionTask(Socket connection) {
@Override
public void run() {
try {
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.
Expand Down
Loading