Repository: microsoft/ptvsd

Issue:
Capture usage of the experimental debugger
This can be done via an `OutputEvent` with a category of `telemetry` .
https://github.com/Microsoft/vscode-debugadapter-node/blob/master/debugProtocol.json#L278

This is something that would be useful to both PTVS as well as VS Code.


Discussion:
@DonJayamanne, can you figure out what's the most convenient way to report this info from VSC adapter perspective (since it's the primary beneficiary, let's implement it in a way that keeps it simple), and what info needs to be in the event aside from debugger ID.

Resolving patch:
diff --git a/ptvsd/wrapper.py b/ptvsd/wrapper.py
--- a/ptvsd/wrapper.py
+++ b/ptvsd/wrapper.py
@@ -7,6 +7,7 @@
 import atexit
 import contextlib
 import errno
+import getpass
 import os
 import platform
 import re
@@ -630,6 +631,12 @@ def __init__(self, socket, pydevd, logfile=None,
         self.event_loop_thread.start()
         self.wait_on_exit_func = waitonexitfunc
 
+        self.send_event(
+            'output',
+            category='telemetry',
+            output='ptvsd',
+            data={'version': __version__})
+
     def _handle_exit(self):
         wait_on_normal_exit = self.debug_options.get(
             'WAIT_ON_NORMAL_EXIT', False)
@@ -1385,6 +1392,52 @@ def on_exceptionInfo(self, request, args):
                      'source': exc.source},
         )
 
+    # Custom ptvsd message
+    def on_ptvsd_systemInfo(self, request, args):
+        try:
+            pid = os.getpid()
+        except AttributeError:
+            pid = None
+
+        try:
+            username = getpass.getuser()
+        except AttributeError:
+            username = None
+
+        try:
+            impl_desc = platform.python_implementation()
+        except AttributeError:
+            try:
+                impl_desc = sys.implementation.name
+            except AttributeError:
+                impl_desc = 'Python'
+
+        sys_info = {
+            'ptvsd': {
+                'version': __version__,
+            },
+            'python': {
+                'version': list(sys.version_info),
+                'implementation': {
+                    'name': sys.implementation.name,
+                    'version': list(sys.implementation.version),
+                    'description': impl_desc,
+                },
+            },
+            'platform': {
+                'name': sys.platform,
+            },
+            'process': {
+                'pid': pid,
+                'executable': sys.executable,
+                'bitness': 64 if sys.maxsize > 2**32 else 32,
+            },
+            'user': {
+                'name': username,
+            },
+        }
+        self.send_response(request, **sys_info)
+
     @pydevd_events.handler(pydevd_comm.CMD_THREAD_CREATE)
     def on_pydevd_thread_create(self, seq, args):
         # If this is the first thread reported, report process creation


Test patch:
diff --git a/tests/helpers/socket.py b/tests/helpers/socket.py
--- a/tests/helpers/socket.py
+++ b/tests/helpers/socket.py
@@ -57,7 +57,10 @@ def connect():
 
 def close(sock):
     """Shutdown and close the socket."""
-    sock.shutdown(socket.SHUT_RDWR)
+    try:
+        sock.shutdown(socket.SHUT_RDWR)
+    except Exception:
+        pass
     sock.close()
 
 
diff --git a/tests/highlevel/__init__.py b/tests/highlevel/__init__.py
--- a/tests/highlevel/__init__.py
+++ b/tests/highlevel/__init__.py
@@ -345,10 +345,15 @@ def _started(self, start, port, hidedisconnect=False):
     def _handshake(self, command, threadnames=None, config=None,
                    default_threads=True, process=True, reset=True,
                    **kwargs):
+        with self._hidden():
+            with self._fix.wait_for_event('output'):
+                pass
+
         initargs = dict(
             kwargs.pop('initargs', None) or {},
             disconnect=kwargs.pop('disconnect', True),
         )
+
         with self._fix.wait_for_event('initialized'):
             self._initialize(**initargs)
             self._fix.send_request(command, **kwargs)
diff --git a/tests/highlevel/test_lifecycle.py b/tests/highlevel/test_lifecycle.py
--- a/tests/highlevel/test_lifecycle.py
+++ b/tests/highlevel/test_lifecycle.py
@@ -1,5 +1,4 @@
-#import os
-#import sys
+import ptvsd
 import unittest
 
 from _pydevd_bundle.pydevd_comm import (
@@ -8,7 +7,11 @@
     CMD_VERSION,
 )
 
-from . import OS_ID, HighlevelTest, HighlevelFixture
+from . import (
+    OS_ID,
+    HighlevelTest,
+    HighlevelFixture,
+)
 
 
 # TODO: Make sure we are handling the following properly:
@@ -30,6 +33,9 @@ def test_attach(self):
         version = self.debugger.VERSION
         addr = (None, 8888)
         with self.vsc.start(addr):
+            with self.vsc.wait_for_event('output'):
+                pass
+
             with self.vsc.wait_for_event('initialized'):
                 # initialize
                 self.set_debugger_response(CMD_VERSION, version)
@@ -50,6 +56,11 @@ def test_attach(self):
         # An "exited" event comes once self.vsc closes.
 
         self.assert_received(self.vsc, [
+            self.new_event(
+                'output',
+                category='telemetry',
+                output='ptvsd',
+                data={'version': ptvsd.__version__}),
             self.new_response(req_initialize, **dict(
                 supportsExceptionInfoRequest=True,
                 supportsConfigurationDoneRequest=True,
@@ -95,6 +106,9 @@ def test_launch(self):
         version = self.debugger.VERSION
         addr = (None, 8888)
         with self.vsc.start(addr):
+            with self.vsc.wait_for_event('output'):
+                pass
+
             with self.vsc.wait_for_event('initialized'):
                 # initialize
                 self.set_debugger_response(CMD_VERSION, version)
@@ -115,6 +129,11 @@ def test_launch(self):
         # An "exited" event comes once self.vsc closes.
 
         self.assert_received(self.vsc, [
+            self.new_event(
+                'output',
+                category='telemetry',
+                output='ptvsd',
+                data={'version': ptvsd.__version__}),
             self.new_response(req_initialize, **dict(
                 supportsExceptionInfoRequest=True,
                 supportsConfigurationDoneRequest=True,
diff --git a/tests/highlevel/test_live_pydevd.py b/tests/highlevel/test_live_pydevd.py
--- a/tests/highlevel/test_live_pydevd.py
+++ b/tests/highlevel/test_live_pydevd.py
@@ -1,9 +1,14 @@
 import contextlib
 import unittest
 
+import ptvsd
 from tests.helpers.pydevd._live import LivePyDevd
 from tests.helpers.workspace import PathEntry
-from . import VSCFixture, VSCTest
+
+from . import (
+    VSCFixture,
+    VSCTest,
+)
 
 
 class Fixture(VSCFixture):
@@ -104,6 +109,11 @@ def test_launch(self):
             received = self.vsc.received
 
         self.assert_vsc_received(received, [
+            self.new_event(
+                'output',
+                category='telemetry',
+                output='ptvsd',
+                data={'version': ptvsd.__version__}),
             self.new_response(req_initialize, **dict(
                 supportsExceptionInfoRequest=True,
                 supportsConfigurationDoneRequest=True,