Skip to content

Commit e87577b

Browse files
authored
produce userdata for VSCode to consume (microsoft#40)
* produce userdata for VSCode to consume * fix for counting request number * variable name consistency * aggregate data before sending to VSCode * repair log * fix according to code review * handle potential NPE * fix GUID for simultaneous debug sessions * dismiss possible exceptions
1 parent 61f6bae commit e87577b

File tree

11 files changed

+337
-2
lines changed

11 files changed

+337
-2
lines changed

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/Configuration.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,6 @@
1313

1414
public class Configuration {
1515
public static final String LOGGER_NAME = "java-debug";
16+
public static final String USAGE_DATA_LOGGER_NAME = "java-debug-usage-data";
1617

1718
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2017 Microsoft Corporation and others.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* http://www.eclipse.org/legal/epl-v10.html
7+
*
8+
* Contributors:
9+
* Microsoft Corporation - initial API and implementation
10+
*******************************************************************************/
11+
12+
package com.microsoft.java.debug.core;
13+
14+
import java.util.HashMap;
15+
import java.util.Map;
16+
import java.util.logging.Level;
17+
import java.util.logging.Logger;
18+
19+
import com.google.gson.JsonElement;
20+
import com.microsoft.java.debug.core.adapter.AdapterUtils;
21+
import com.microsoft.java.debug.core.adapter.JsonUtils;
22+
import com.microsoft.java.debug.core.adapter.Messages.Request;
23+
import com.microsoft.java.debug.core.adapter.Messages.Response;
24+
25+
public class UsageDataSession {
26+
private static final Logger usageDataLogger = Logger.getLogger(Configuration.USAGE_DATA_LOGGER_NAME);
27+
private static final long RESPONSE_MAX_DELAY_MS = 1000;
28+
private static final ThreadLocal<String> sessionGuid = new InheritableThreadLocal<>();
29+
30+
private long startAt = -1;
31+
private long stopAt = -1;
32+
private Map<String, Integer> commandCountMap = new HashMap<>();
33+
private Map<String, Integer> breakpointCountMap = new HashMap<>();
34+
private Map<Integer, RequestEvent> requestEventMap = new HashMap<>();
35+
36+
public static void setSessionGuid(String guid) {
37+
sessionGuid.set(guid);
38+
}
39+
40+
public static String getSessionGuid() {
41+
return sessionGuid.get();
42+
}
43+
44+
class RequestEvent {
45+
Request request;
46+
long timestamp;
47+
48+
RequestEvent(Request request, long timestamp) {
49+
this.request = request;
50+
this.timestamp = timestamp;
51+
}
52+
}
53+
54+
public void reportStart() {
55+
startAt = System.currentTimeMillis();
56+
}
57+
58+
public void reportStop() {
59+
stopAt = System.currentTimeMillis();
60+
}
61+
62+
/**
63+
* Record usage data from request.
64+
*/
65+
public void recordRequest(Request request) {
66+
try {
67+
requestEventMap.put(request.seq, new RequestEvent(request, System.currentTimeMillis()));
68+
69+
// cmd count
70+
commandCountMap.put(request.command, commandCountMap.getOrDefault(request.command, 0) + 1);
71+
72+
// bp count
73+
if ("setBreakpoints".equals(request.command)) {
74+
String fileIdentifier = "unknown file";
75+
JsonElement pathElement = request.arguments.get("source").getAsJsonObject().get("path");
76+
JsonElement nameElement = request.arguments.get("source").getAsJsonObject().get("name");
77+
if (pathElement != null) {
78+
fileIdentifier = pathElement.getAsString();
79+
} else if (nameElement != null) {
80+
fileIdentifier = nameElement.getAsString();
81+
}
82+
String filenameHash = AdapterUtils.getSHA256HexDigest(fileIdentifier);
83+
int bpCount = request.arguments.get("breakpoints").getAsJsonArray().size();
84+
breakpointCountMap.put(filenameHash, breakpointCountMap.getOrDefault(filenameHash, 0) + bpCount);
85+
}
86+
} catch (Throwable e) {
87+
// ignore it
88+
}
89+
}
90+
91+
/**
92+
* Record usage data from response.
93+
*/
94+
public void recordResponse(Response response) {
95+
try {
96+
long responseMillis = System.currentTimeMillis();
97+
long requestMillis = responseMillis;
98+
String command = null;
99+
100+
RequestEvent requestEvent = requestEventMap.getOrDefault(response.request_seq, null);
101+
if (requestEvent != null) {
102+
command = requestEvent.request.command;
103+
requestMillis = requestEvent.timestamp;
104+
requestEventMap.remove(response.request_seq);
105+
}
106+
long duration = responseMillis - requestMillis;
107+
108+
if (!response.success || duration > RESPONSE_MAX_DELAY_MS) {
109+
Map<String, Object> props = new HashMap<>();
110+
props.put("duration", duration);
111+
props.put("command", command);
112+
props.put("success", response.success);
113+
// directly report abnormal response.
114+
usageDataLogger.log(Level.WARNING, "abnormal response", props);
115+
}
116+
} catch (Throwable e) {
117+
// ignore it
118+
}
119+
}
120+
121+
/**
122+
* Submit summary of usage data in current session.
123+
*/
124+
public void submitUsageData() {
125+
Map<String, String> props = new HashMap<>();
126+
127+
props.put("sessionStartAt", String.valueOf(startAt));
128+
props.put("sessionStopAt", String.valueOf(stopAt));
129+
props.put("commandCount", JsonUtils.toJson(commandCountMap));
130+
props.put("breakpointCount", JsonUtils.toJson(breakpointCountMap));
131+
usageDataLogger.log(Level.INFO, "session usage data summary", props);
132+
}
133+
134+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2017 Microsoft Corporation and others.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* http://www.eclipse.org/legal/epl-v10.html
7+
*
8+
* Contributors:
9+
* Microsoft Corporation - initial API and implementation
10+
*******************************************************************************/
11+
12+
package com.microsoft.java.debug.core;
13+
14+
import java.io.PrintWriter;
15+
import java.io.StringWriter;
16+
import java.util.HashMap;
17+
import java.util.Map;
18+
import java.util.concurrent.ConcurrentLinkedQueue;
19+
20+
public class UsageDataStore {
21+
private ConcurrentLinkedQueue<Object> queue;
22+
private static final int QUEUE_MAX_SIZE = 10000;
23+
24+
/**
25+
* Constructor.
26+
*/
27+
private UsageDataStore() {
28+
queue = new ConcurrentLinkedQueue<>();
29+
}
30+
31+
private static final class SingletonHolder {
32+
private static final UsageDataStore INSTANCE = new UsageDataStore();
33+
}
34+
35+
/**
36+
* Fetch all pending user data records
37+
* @return List of user data Object.
38+
*/
39+
public synchronized Object[] fetchAll() {
40+
Object[] ret = queue.toArray();
41+
queue.clear();
42+
return ret;
43+
}
44+
45+
public static UsageDataStore getInstance() {
46+
return SingletonHolder.INSTANCE;
47+
}
48+
49+
/**
50+
* Log user data Object into the queue.
51+
*/
52+
public void logSessionData(String desc, Map<String, String> props) {
53+
if (queue == null) {
54+
return;
55+
}
56+
Map<String, String> sessionEntry = new HashMap<>();
57+
sessionEntry.put("scope", "session");
58+
sessionEntry.put("debugSessionId", UsageDataSession.getSessionGuid());
59+
if (desc != null) {
60+
sessionEntry.put("description", desc);
61+
}
62+
if (props != null) {
63+
sessionEntry.putAll(props);
64+
}
65+
enqueue(sessionEntry);
66+
}
67+
68+
/**
69+
* Log Exception details into queue.
70+
*/
71+
public void logErrorData(String desc, Throwable th) {
72+
if (queue == null) {
73+
return;
74+
}
75+
Map<String, String> errorEntry = new HashMap<>();
76+
errorEntry.put("scope", "exception");
77+
errorEntry.put("deubgSessionId", UsageDataSession.getSessionGuid());
78+
if (desc != null) {
79+
errorEntry.put("description", desc);
80+
}
81+
if (th != null) {
82+
errorEntry.put("message", th.getMessage());
83+
StringWriter sw = new StringWriter();
84+
th.printStackTrace(new PrintWriter(sw));
85+
errorEntry.put("stackTrace", sw.toString());
86+
}
87+
enqueue(errorEntry);
88+
}
89+
90+
private synchronized void enqueue(Object object) {
91+
if (queue.size() > QUEUE_MAX_SIZE) {
92+
queue.poll();
93+
}
94+
queue.add(object);
95+
}
96+
}

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/AdapterUtils.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,14 @@
1313

1414
import java.net.URI;
1515
import java.net.URISyntaxException;
16+
import java.nio.charset.StandardCharsets;
1617
import java.nio.file.FileSystemNotFoundException;
1718
import java.nio.file.Files;
1819
import java.nio.file.InvalidPathException;
1920
import java.nio.file.Path;
2021
import java.nio.file.Paths;
22+
import java.security.MessageDigest;
23+
import java.security.NoSuchAlgorithmException;
2124
import java.util.regex.Matcher;
2225
import java.util.regex.Pattern;
2326

@@ -179,4 +182,27 @@ public static void setErrorResponse(Response response, ErrorCode errorCode, Exce
179182
response.message = errorMessage;
180183
response.success = false;
181184
}
185+
186+
/**
187+
* Calculate SHA-256 Digest of given string.
188+
* @param content
189+
*
190+
* string to digest
191+
* @return string of Hex digest
192+
*/
193+
public static String getSHA256HexDigest(String content) {
194+
byte[] hashBytes = null;
195+
try {
196+
hashBytes = MessageDigest.getInstance("SHA-256").digest(content.getBytes(StandardCharsets.UTF_8));
197+
} catch (NoSuchAlgorithmException e) {
198+
// ignore it.
199+
}
200+
StringBuffer buf = new StringBuffer();
201+
if (hashBytes != null) {
202+
for (byte b : hashBytes) {
203+
buf.append(Integer.toHexString((b & 0xFF) + 0x100).substring(1));
204+
}
205+
}
206+
return buf.toString();
207+
}
182208
}

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ProtocolServer.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,10 @@
3131
import java.util.regex.Pattern;
3232

3333
import com.microsoft.java.debug.core.Configuration;
34+
import com.microsoft.java.debug.core.UsageDataSession;
3435

3536
public class ProtocolServer {
3637
private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME);
37-
3838
private static final int BUFFER_SIZE = 4096;
3939
private static final String TWO_CRLF = "\r\n\r\n";
4040
private static final Pattern CONTENT_LENGTH_MATCHER = Pattern.compile("Content-Length: (\\d+)");
@@ -53,6 +53,8 @@ public class ProtocolServer {
5353

5454
private IDebugAdapter debugAdapter;
5555

56+
private UsageDataSession usageDataSession = new UsageDataSession();
57+
5658
/**
5759
* Constructs a protocol server instance based on the given input stream and output stream.
5860
* @param input
@@ -84,6 +86,7 @@ public ProtocolServer(InputStream input, OutputStream output, IProviderContext c
8486
* A while-loop to parse input data and send output data constantly.
8587
*/
8688
public void start() {
89+
usageDataSession.reportStart();
8790
char[] buffer = new char[BUFFER_SIZE];
8891
try {
8992
while (!this.terminateSession) {
@@ -104,7 +107,9 @@ public void start() {
104107
* Sets terminateSession flag to true. And the dispatcher loop will be terminated after current dispatching operation finishes.
105108
*/
106109
public void stop() {
110+
usageDataSession.reportStop();
107111
this.terminateSession = true;
112+
usageDataSession.submitUsageData();
108113
}
109114

110115
/**
@@ -193,6 +198,7 @@ private void dispatchRequest(String request) {
193198
try {
194199
logger.info("\n[REQUEST]\n" + request);
195200
Messages.Request message = JsonUtils.fromJson(request, Messages.Request.class);
201+
usageDataSession.recordRequest(message);
196202
if (message.type.equals("request")) {
197203
synchronized (this) {
198204
this.isDispatchingData = true;
@@ -204,6 +210,7 @@ private void dispatchRequest(String request) {
204210
this.stop();
205211
}
206212
sendMessage(response);
213+
usageDataSession.recordResponse(response);
207214
} catch (Exception e) {
208215
logger.log(Level.SEVERE, String.format("Dispatch debug protocol error: %s", e.toString()), e);
209216
}

com.microsoft.java.debug.plugin/plugin.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
<command id="vscode.java.startDebugSession"/>
77
<command id="vscode.java.resolveClasspath"/>
88
<command id="vscode.java.buildWorkspace"/>
9+
<command id="vscode.java.fetchUsageData"/>
910
</delegateCommandHandler>
1011
</extension>
1112
</plugin>

com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaDebugDelegateCommandHandler.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,12 @@
1616
import org.eclipse.core.runtime.IProgressMonitor;
1717
import org.eclipse.jdt.ls.core.internal.IDelegateCommandHandler;
1818

19+
import com.microsoft.java.debug.core.UsageDataStore;
20+
1921
public class JavaDebugDelegateCommandHandler implements IDelegateCommandHandler {
2022

23+
public static String FETCH_USER_DATA = "vscode.java.fetchUsageData";
24+
2125
public static String DEBUG_STARTSESSION = "vscode.java.startDebugSession";
2226

2327
public static String RESOLVE_CLASSPATH = "vscode.java.resolveClasspath";
@@ -35,7 +39,10 @@ public Object executeCommand(String commandId, List<Object> arguments, IProgress
3539
return handler.resolveClasspaths(arguments);
3640
} else if (BUILD_WORKSPACE.equals(commandId)) {
3741
// TODO
42+
} else if (FETCH_USER_DATA.equals(commandId)) {
43+
return UsageDataStore.getInstance().fetchAll();
3844
}
45+
3946
throw new UnsupportedOperationException(String.format("Java debug plugin doesn't support the command '%s'.", commandId));
4047
}
4148

com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaDebugServer.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import java.io.IOException;
1515
import java.net.ServerSocket;
1616
import java.net.Socket;
17+
import java.util.UUID;
1718
import java.util.concurrent.ExecutorService;
1819
import java.util.concurrent.SynchronousQueue;
1920
import java.util.concurrent.ThreadPoolExecutor;
@@ -22,6 +23,7 @@
2223
import java.util.logging.Logger;
2324

2425
import com.microsoft.java.debug.core.Configuration;
26+
import com.microsoft.java.debug.core.UsageDataSession;
2527
import com.microsoft.java.debug.core.adapter.ProtocolServer;
2628

2729
public class JavaDebugServer implements IDebugServer {
@@ -130,6 +132,7 @@ private Runnable createConnectionTask(Socket connection) {
130132
@Override
131133
public void run() {
132134
try {
135+
UsageDataSession.setSessionGuid(UUID.randomUUID().toString());
133136
ProtocolServer protocolServer = new ProtocolServer(connection.getInputStream(), connection.getOutputStream(),
134137
JdtProviderContextFactory.createProviderContext());
135138
// protocol server will dispatch request and send response in a while-loop.

0 commit comments

Comments
 (0)