diff mbox series

[v2,01/38,DO-NOT-MERGE] qapi: add debugging tools

Message ID 20200922210101.4081073-2-jsnow@redhat.com
State Superseded
Headers show
Series qapi: static typing conversion, pt1 | expand

Commit Message

John Snow Sept. 22, 2020, 9 p.m. UTC
This adds some really childishly simple debugging tools. Maybe they're
interesting for someone else, too?

Signed-off-by: John Snow <jsnow@redhat.com>
---
 scripts/qapi/debug.py | 78 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 78 insertions(+)
 create mode 100644 scripts/qapi/debug.py

Comments

Cleber Rosa Sept. 22, 2020, 11:43 p.m. UTC | #1
On Tue, Sep 22, 2020 at 05:00:24PM -0400, John Snow wrote:
> This adds some really childishly simple debugging tools. Maybe they're

> interesting for someone else, too?

> 

> Signed-off-by: John Snow <jsnow@redhat.com>

> ---

>  scripts/qapi/debug.py | 78 +++++++++++++++++++++++++++++++++++++++++++

>  1 file changed, 78 insertions(+)

>  create mode 100644 scripts/qapi/debug.py

> 

> diff --git a/scripts/qapi/debug.py b/scripts/qapi/debug.py

> new file mode 100644

> index 0000000000..bacf5ee180

> --- /dev/null

> +++ b/scripts/qapi/debug.py

> @@ -0,0 +1,78 @@

> +"""

> +Small debugging facilities for mypy static analysis work.

> +(C) 2020 John Snow, for Red Hat, Inc.

> +"""

> +

> +import inspect

> +import json

> +from typing import Dict, List, Any

> +from types import FrameType

> +

> +

> +OBSERVED_TYPES: Dict[str, List[str]] = {}

> +

> +

> +# You have no idea how long it took to find this return type...

> +def caller_frame() -> FrameType:

> +    """

> +    Returns the stack frame of the caller's caller.

> +    e.g. foo() -> caller() -> caller_frame() return's foo's stack frame.

> +    """

> +    stack = inspect.stack()

> +    caller = stack[2].frame

> +    if caller is None:

> +        msg = "Python interpreter does not support stack frame inspection"

> +        raise RuntimeError(msg)

> +    return caller

> +

> +

> +def _add_type_record(name: str, typestr: str) -> None:

> +    seen = OBSERVED_TYPES.setdefault(name, [])

> +    if typestr not in seen:

> +        seen.append(typestr)

> +

> +

> +def record_type(name: str, value: Any, dict_names: bool = False) -> None:

> +    """

> +    Record the type of a variable.

> +

> +    :param name: The name of the variable

> +    :param value: The value of the variable

> +    """

> +    _add_type_record(name, str(type(value)))

> +

> +    try:

> +        for key, subvalue in value.items():

> +            subname = f"{name}.{key}" if dict_names else f"{name}.[dict_value]"

> +            _add_type_record(subname, str(type(subvalue)))

> +        return

> +    except AttributeError:

> +        # (Wasn't a dict or anything resembling one.)

> +        pass

> +

> +    # str is iterable, but not in the way we want!

> +    if isinstance(value, str):

> +        return

> +

> +    try:

> +        for elem in value:

> +            _add_type_record(f"{name}.[list_elem]", str(type(elem)))

> +    except TypeError:

> +        # (Wasn't a list or anything else iterable.)

> +        pass

> +

> +

> +def show_types() -> None:

> +    """

> +    Print all of the currently known variable types to stdout.

> +    """

> +    print(json.dumps(OBSERVED_TYPES, indent=2))

> +


Maybe the following will be cheaper (no json conversion):

   pprint.pprint(OBSERVED_TYPES, indent=2)

Other than that, I'd vote for including this if there's a bit more
documentation on how to use it, or an example script.  Maybe there
already is, and I did not get to it yet.

- Cleber.

> +

> +def record_locals(show: bool = False, dict_names: bool = False) -> None:

> +    caller = caller_frame()

> +    name = caller.f_code.co_name

> +    for key, value in caller.f_locals.items():

> +        record_type(f"{name}.{key}", value, dict_names=dict_names)

> +    if show:

> +        show_types()

> -- 

> 2.26.2

>
John Snow Sept. 23, 2020, 4:48 p.m. UTC | #2
On 9/22/20 7:43 PM, Cleber Rosa wrote:
> On Tue, Sep 22, 2020 at 05:00:24PM -0400, John Snow wrote:
>> This adds some really childishly simple debugging tools. Maybe they're
>> interesting for someone else, too?
>>
>> Signed-off-by: John Snow <jsnow@redhat.com>
>> ---
>>   scripts/qapi/debug.py | 78 +++++++++++++++++++++++++++++++++++++++++++
>>   1 file changed, 78 insertions(+)
>>   create mode 100644 scripts/qapi/debug.py
>>
>> diff --git a/scripts/qapi/debug.py b/scripts/qapi/debug.py
>> new file mode 100644
>> index 0000000000..bacf5ee180
>> --- /dev/null
>> +++ b/scripts/qapi/debug.py
>> @@ -0,0 +1,78 @@
>> +"""
>> +Small debugging facilities for mypy static analysis work.
>> +(C) 2020 John Snow, for Red Hat, Inc.
>> +"""
>> +
>> +import inspect
>> +import json
>> +from typing import Dict, List, Any
>> +from types import FrameType
>> +
>> +
>> +OBSERVED_TYPES: Dict[str, List[str]] = {}
>> +
>> +
>> +# You have no idea how long it took to find this return type...
>> +def caller_frame() -> FrameType:
>> +    """
>> +    Returns the stack frame of the caller's caller.
>> +    e.g. foo() -> caller() -> caller_frame() return's foo's stack frame.
>> +    """
>> +    stack = inspect.stack()
>> +    caller = stack[2].frame
>> +    if caller is None:
>> +        msg = "Python interpreter does not support stack frame inspection"
>> +        raise RuntimeError(msg)
>> +    return caller
>> +
>> +
>> +def _add_type_record(name: str, typestr: str) -> None:
>> +    seen = OBSERVED_TYPES.setdefault(name, [])
>> +    if typestr not in seen:
>> +        seen.append(typestr)
>> +
>> +
>> +def record_type(name: str, value: Any, dict_names: bool = False) -> None:
>> +    """
>> +    Record the type of a variable.
>> +
>> +    :param name: The name of the variable
>> +    :param value: The value of the variable
>> +    """
>> +    _add_type_record(name, str(type(value)))
>> +
>> +    try:
>> +        for key, subvalue in value.items():
>> +            subname = f"{name}.{key}" if dict_names else f"{name}.[dict_value]"
>> +            _add_type_record(subname, str(type(subvalue)))
>> +        return
>> +    except AttributeError:
>> +        # (Wasn't a dict or anything resembling one.)
>> +        pass
>> +
>> +    # str is iterable, but not in the way we want!
>> +    if isinstance(value, str):
>> +        return
>> +
>> +    try:
>> +        for elem in value:
>> +            _add_type_record(f"{name}.[list_elem]", str(type(elem)))
>> +    except TypeError:
>> +        # (Wasn't a list or anything else iterable.)
>> +        pass
>> +
>> +
>> +def show_types() -> None:
>> +    """
>> +    Print all of the currently known variable types to stdout.
>> +    """
>> +    print(json.dumps(OBSERVED_TYPES, indent=2))
>> +
> 
> Maybe the following will be cheaper (no json conversion):
> 
>     pprint.pprint(OBSERVED_TYPES, indent=2)
> 
> Other than that, I'd vote for including this if there's a bit more
> documentation on how to use it, or an example script.  Maybe there
> already is, and I did not get to it yet.
> 
> - Cleber.
> 

Nope, this is just a dumb script I did to observe types in flight.

There are apparently bigger, beefier tools that I don't know how to use 
yet: https://github.com/dropbox/pyannotate

I just included my own little tool as a reference thing to be archived 
on list, I have no desire to spruce it up. I'd rather spend my time 
learning pyannotate.

--js
diff mbox series

Patch

diff --git a/scripts/qapi/debug.py b/scripts/qapi/debug.py
new file mode 100644
index 0000000000..bacf5ee180
--- /dev/null
+++ b/scripts/qapi/debug.py
@@ -0,0 +1,78 @@ 
+"""
+Small debugging facilities for mypy static analysis work.
+(C) 2020 John Snow, for Red Hat, Inc.
+"""
+
+import inspect
+import json
+from typing import Dict, List, Any
+from types import FrameType
+
+
+OBSERVED_TYPES: Dict[str, List[str]] = {}
+
+
+# You have no idea how long it took to find this return type...
+def caller_frame() -> FrameType:
+    """
+    Returns the stack frame of the caller's caller.
+    e.g. foo() -> caller() -> caller_frame() return's foo's stack frame.
+    """
+    stack = inspect.stack()
+    caller = stack[2].frame
+    if caller is None:
+        msg = "Python interpreter does not support stack frame inspection"
+        raise RuntimeError(msg)
+    return caller
+
+
+def _add_type_record(name: str, typestr: str) -> None:
+    seen = OBSERVED_TYPES.setdefault(name, [])
+    if typestr not in seen:
+        seen.append(typestr)
+
+
+def record_type(name: str, value: Any, dict_names: bool = False) -> None:
+    """
+    Record the type of a variable.
+
+    :param name: The name of the variable
+    :param value: The value of the variable
+    """
+    _add_type_record(name, str(type(value)))
+
+    try:
+        for key, subvalue in value.items():
+            subname = f"{name}.{key}" if dict_names else f"{name}.[dict_value]"
+            _add_type_record(subname, str(type(subvalue)))
+        return
+    except AttributeError:
+        # (Wasn't a dict or anything resembling one.)
+        pass
+
+    # str is iterable, but not in the way we want!
+    if isinstance(value, str):
+        return
+
+    try:
+        for elem in value:
+            _add_type_record(f"{name}.[list_elem]", str(type(elem)))
+    except TypeError:
+        # (Wasn't a list or anything else iterable.)
+        pass
+
+
+def show_types() -> None:
+    """
+    Print all of the currently known variable types to stdout.
+    """
+    print(json.dumps(OBSERVED_TYPES, indent=2))
+
+
+def record_locals(show: bool = False, dict_names: bool = False) -> None:
+    caller = caller_frame()
+    name = caller.f_code.co_name
+    for key, value in caller.f_locals.items():
+        record_type(f"{name}.{key}", value, dict_names=dict_names)
+    if show:
+        show_types()