Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gevent
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
gevent
Commits
d56054cf
Commit
d56054cf
authored
Feb 27, 2018
by
Jason Madden
Committed by
GitHub
Feb 27, 2018
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1123 from gevent/locals-debug
Add a GreenletTree for more organized, clearer output of greenlets
parents
f3620ac0
5d4bdad6
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
393 additions
and
39 deletions
+393
-39
CHANGES.rst
CHANGES.rst
+2
-0
src/gevent/util.py
src/gevent/util.py
+299
-38
src/greentest/test__util.py
src/greentest/test__util.py
+92
-1
No files found.
CHANGES.rst
View file @
d56054cf
...
@@ -123,6 +123,8 @@
...
@@ -123,6 +123,8 @@
- `gevent.Greenlet` objects now have a `gevent.Greenlet.name`
- `gevent.Greenlet` objects now have a `gevent.Greenlet.name`
attribute that is included in the default repr.
attribute that is included in the default repr.
- Add `gevent.util.GreenletTree` to visualize the greenlet tree. This
is used by `gevent.util.format_run_info`.
1.3a1 (2018-01-27)
1.3a1 (2018-01-27)
==================
==================
...
...
src/gevent/util.py
View file @
d56054cf
...
@@ -5,13 +5,27 @@ Low-level utilities.
...
@@ -5,13 +5,27 @@ Low-level utilities.
from
__future__
import
absolute_import
,
print_function
,
division
from
__future__
import
absolute_import
,
print_function
,
division
import
gc
import
functools
import
functools
import
pprint
import
traceback
from
greenlet
import
getcurrent
from
greenlet
import
greenlet
as
RawGreenlet
from
gevent.local
import
all_local_dicts_for_greenlet
__all__
=
[
__all__
=
[
'wrap_errors'
,
'wrap_errors'
,
'format_run_info'
,
'format_run_info'
,
'GreenletTree'
,
]
]
def
_noop
():
return
None
def
_ready
():
return
False
class
wrap_errors
(
object
):
class
wrap_errors
(
object
):
"""
"""
...
@@ -90,7 +104,6 @@ def format_run_info():
...
@@ -90,7 +104,6 @@ def format_run_info():
def
_format_thread_info
(
lines
):
def
_format_thread_info
(
lines
):
import
threading
import
threading
import
sys
import
sys
import
traceback
threads
=
{
th
.
ident
:
th
.
name
for
th
in
threading
.
enumerate
()}
threads
=
{
th
.
ident
:
th
.
name
for
th
in
threading
.
enumerate
()}
...
@@ -112,49 +125,297 @@ def _format_thread_info(lines):
...
@@ -112,49 +125,297 @@ def _format_thread_info(lines):
del
threads
del
threads
def
_format_greenlet_info
(
lines
):
def
_format_greenlet_info
(
lines
):
# pylint:disable=too-many-locals
from
greenlet
import
greenlet
import
pprint
import
traceback
import
gc
from
gevent.local
import
all_local_dicts_for_greenlet
def
_noop
():
return
None
# Use the gc module to inspect all objects to find the greenlets
# Use the gc module to inspect all objects to find the greenlets
# since there isn't a global registry
# since there isn't a global registry
lines
.
append
(
'*'
*
80
)
lines
.
append
(
'*'
*
80
)
lines
.
append
(
'* Greenlets'
)
lines
.
append
(
'* Greenlets'
)
seen_locals
=
set
()
# {id}
lines
.
append
(
'*'
*
80
)
for
ob
in
gc
.
get_objects
():
for
tree
in
GreenletTree
.
forest
():
if
not
isinstance
(
ob
,
greenlet
):
lines
.
extend
(
tree
.
format_lines
(
details
=
True
))
continue
if
not
ob
:
del
lines
continue
# not running anymore or not started
lines
.
append
(
'*'
*
80
)
dump_stacks
=
format_run_info
lines
.
append
(
'Greenlet %s
\
n
'
%
ob
)
lines
.
append
(
''
.
join
(
traceback
.
format_stack
(
ob
.
gr_frame
)))
def
_line
(
f
):
spawning_stack
=
getattr
(
ob
,
'spawning_stack'
,
None
)
@
functools
.
wraps
(
f
)
if
spawning_stack
:
def
w
(
self
,
*
args
,
**
kwargs
):
lines
.
append
(
"Spawned at: "
)
r
=
f
(
self
,
*
args
,
**
kwargs
)
lines
.
append
(
''
.
join
(
traceback
.
format_stack
(
spawning_stack
)))
self
.
lines
.
append
(
r
)
parent
=
getattr
(
ob
,
'spawning_greenlet'
,
_noop
)()
if
parent
is
not
None
:
return
w
lines
.
append
(
"Parent greenlet: %s
\
n
"
%
(
parent
,))
spawn_tree_locals
=
getattr
(
ob
,
'spawn_tree_locals'
,
None
)
class
_TreeFormatter
(
object
):
if
spawn_tree_locals
and
id
(
spawn_tree_locals
)
not
in
seen_locals
:
UP_AND_RIGHT
=
'+'
seen_locals
.
add
(
id
(
spawn_tree_locals
))
HORIZONTAL
=
'-'
lines
.
append
(
"Spawn Tree Locals:
\
n
"
)
VERTICAL
=
'|'
lines
.
append
(
pprint
.
pformat
(
spawn_tree_locals
))
VERTICAL_AND_RIGHT
=
'+'
gr_locals
=
all_local_dicts_for_greenlet
(
ob
)
DATA
=
':'
label_space
=
1
horiz_width
=
3
indent
=
1
def
__init__
(
self
,
details
,
depth
=
0
):
self
.
lines
=
[]
self
.
depth
=
depth
self
.
details
=
details
if
not
details
:
self
.
child_data
=
lambda
*
args
,
**
kwargs
:
None
def
deeper
(
self
):
return
type
(
self
)(
self
.
details
,
self
.
depth
+
1
)
@
_line
def
node_label
(
self
,
text
):
return
text
@
_line
def
child_head
(
self
,
label
,
right
=
VERTICAL_AND_RIGHT
):
return
(
' '
*
self
.
indent
+
right
+
self
.
HORIZONTAL
*
self
.
horiz_width
+
' '
*
self
.
label_space
+
label
)
def
last_child_head
(
self
,
label
):
return
self
.
child_head
(
label
,
self
.
UP_AND_RIGHT
)
@
_line
def
child_tail
(
self
,
line
,
vertical
=
VERTICAL
):
return
(
' '
*
self
.
indent
+
vertical
+
' '
*
self
.
horiz_width
+
line
)
def
last_child_tail
(
self
,
line
):
return
self
.
child_tail
(
line
,
vertical
=
' '
*
len
(
self
.
VERTICAL
))
@
_line
def
child_data
(
self
,
data
,
data_marker
=
DATA
):
# pylint:disable=method-hidden
return
((
' '
*
self
.
indent
+
(
data_marker
if
not
self
.
depth
else
' '
)
+
' '
*
self
.
horiz_width
+
' '
*
self
.
label_space
+
data
),)
def
last_child_data
(
self
,
data
):
return
self
.
child_data
(
data
,
' '
)
def
child_multidata
(
self
,
data
):
# Remove embedded newlines
for
l
in
data
.
splitlines
():
self
.
child_data
(
l
)
class
GreenletTree
(
object
):
"""
Represents a tree of greenlets.
In gevent, the *parent* of a greenlet is usually the hub, so this
tree is primarily arganized along the *spawning_greenlet* dimension.
This object has a small str form showing this hierarchy. The `format`
method can output more details. The exact output is unspecified but is
intended to be human readable.
Use the `forest` method to get the root greenlet trees for
all threads, and the `current_tree` to get the root greenlet tree for
the current thread.
"""
#: The greenlet this tree represents.
greenlet
=
None
def
__init__
(
self
,
greenlet
):
self
.
greenlet
=
greenlet
self
.
child_trees
=
[]
def
add_child
(
self
,
tree
):
if
tree
is
self
:
return
self
.
child_trees
.
append
(
tree
)
@
property
def
root
(
self
):
return
self
.
greenlet
.
parent
is
None
def
__getattr__
(
self
,
name
):
return
getattr
(
self
.
greenlet
,
name
)
def
format_lines
(
self
,
details
=
True
):
"""
Return a sequence of lines for the greenlet tree.
:keyword bool details: If true (the default),
then include more informative details in the output.
"""
if
not
isinstance
(
details
,
dict
):
if
not
details
:
details
=
{}
else
:
details
=
{
'stacks'
:
True
,
'locals'
:
True
}
tree
=
_TreeFormatter
(
details
,
depth
=
0
)
lines
=
[
l
[
0
]
if
isinstance
(
l
,
tuple
)
else
l
for
l
in
self
.
_render
(
tree
)]
return
lines
def
format
(
self
,
details
=
True
):
"""
Like `format_lines` but returns a string.
"""
lines
=
self
.
format_lines
(
details
)
return
'
\
n
'
.
join
(
lines
)
def
__str__
(
self
):
return
self
.
format
(
False
)
@
staticmethod
def
__render_tb
(
tree
,
label
,
frame
):
tree
.
child_data
(
label
)
tb
=
''
.
join
(
traceback
.
format_stack
(
frame
))
tree
.
child_multidata
(
tb
)
def
__render_locals
(
self
,
tree
):
gr_locals
=
all_local_dicts_for_greenlet
(
self
.
greenlet
)
if
gr_locals
:
if
gr_locals
:
lines
.
append
(
"Greenlet Locals:
\
n
"
)
tree
.
child_data
(
"Greenlet Locals:
"
)
for
(
kind
,
idl
),
vals
in
gr_locals
:
for
(
kind
,
idl
),
vals
in
gr_locals
:
lines
.
append
(
"
\
t
Local %s at %s
\
n
"
%
(
kind
,
hex
(
idl
)))
tree
.
child_data
(
" Local %s at %s"
%
(
kind
,
hex
(
idl
)))
lines
.
append
(
"
\
t
"
+
pprint
.
pformat
(
vals
))
tree
.
child_multidata
(
" "
+
pprint
.
pformat
(
vals
))
def
_render
(
self
,
tree
):
label
=
repr
(
self
.
greenlet
)
if
not
self
.
greenlet
:
# Not running or dead
# raw greenlets do not have ready
if
getattr
(
self
.
greenlet
,
'ready'
,
_ready
)():
label
+=
'; finished'
if
self
.
greenlet
.
value
is
not
None
:
label
+=
' with value '
+
repr
(
self
.
greenlet
.
value
)[:
30
]
elif
getattr
(
self
.
greenlet
,
'exception'
,
None
)
is
not
None
:
label
+=
' with exception '
+
repr
(
self
.
greenlet
.
exception
)
else
:
label
+=
'; not running'
tree
.
node_label
(
label
)
if
self
.
greenlet
.
parent
is
not
None
:
tree
.
child_data
(
'Parent: '
+
repr
(
self
.
greenlet
.
parent
))
if
self
.
greenlet
and
tree
.
details
and
tree
.
details
[
'stacks'
]:
self
.
__render_tb
(
tree
,
'Running:'
,
self
.
greenlet
.
gr_frame
)
spawning_stack
=
getattr
(
self
.
greenlet
,
'spawning_stack'
,
None
)
if
spawning_stack
and
tree
.
details
and
tree
.
details
[
'stacks'
]:
self
.
__render_tb
(
tree
,
'Spawned at:'
,
spawning_stack
)
spawning_parent
=
getattr
(
self
.
greenlet
,
'spawning_greenlet'
,
_noop
)()
tree_locals
=
getattr
(
self
.
greenlet
,
'spawn_tree_locals'
,
None
)
if
tree_locals
and
tree_locals
is
not
getattr
(
spawning_parent
,
'spawn_tree_locals'
,
None
):
tree
.
child_data
(
'Spawn Tree Locals'
)
tree
.
child_multidata
(
pprint
.
pformat
(
tree_locals
))
self
.
__render_locals
(
tree
)
self
.
__render_children
(
tree
)
return
tree
.
lines
def
__render_children
(
self
,
tree
):
children
=
sorted
(
self
.
child_trees
,
key
=
lambda
c
:
(
# raw greenlets first
getattr
(
c
,
'minimal_ident'
,
-
1
),
# running greenlets first
getattr
(
c
,
'ready'
,
_ready
)(),
id
(
c
.
parent
)))
for
n
,
child
in
enumerate
(
children
):
child_tree
=
child
.
_render
(
tree
.
deeper
())
head
=
tree
.
child_head
tail
=
tree
.
child_tail
data
=
tree
.
child_data
if
n
==
len
(
children
)
-
1
:
# last child does not get the line drawn
head
=
tree
.
last_child_head
tail
=
tree
.
last_child_tail
data
=
tree
.
last_child_data
head
(
child_tree
.
pop
(
0
))
for
child_data
in
child_tree
:
if
isinstance
(
child_data
,
tuple
):
data
(
child_data
[
0
])
else
:
tail
(
child_data
)
return
tree
.
lines
@
staticmethod
def
_root_greenlet
(
greenlet
):
while
greenlet
.
parent
is
not
None
:
greenlet
=
greenlet
.
parent
return
greenlet
@
classmethod
def
_forest
(
cls
):
main_greenlet
=
cls
.
_root_greenlet
(
getcurrent
())
trees
=
{}
roots
=
{}
current_tree
=
roots
[
main_greenlet
]
=
trees
[
main_greenlet
]
=
cls
(
main_greenlet
)
for
ob
in
gc
.
get_objects
():
if
not
isinstance
(
ob
,
RawGreenlet
):
continue
spawn_parent
=
getattr
(
ob
,
'spawning_greenlet'
,
_noop
)()
if
spawn_parent
is
None
:
root
=
cls
.
_root_greenlet
(
ob
)
try
:
tree
=
roots
[
root
]
except
KeyError
:
# pragma: no cover
tree
=
GreenletTree
(
root
)
roots
[
root
]
=
trees
[
root
]
=
tree
else
:
try
:
tree
=
trees
[
spawn_parent
]
except
KeyError
:
# pragma: no cover
tree
=
trees
[
spawn_parent
]
=
cls
(
spawn_parent
)
try
:
child_tree
=
trees
[
ob
]
except
KeyError
:
trees
[
ob
]
=
child_tree
=
cls
(
ob
)
tree
.
add_child
(
child_tree
)
del
lines
return
roots
,
current_tree
dump_stacks
=
format_run_info
@
classmethod
def
forest
(
cls
):
"""
forest() -> sequence
Return a sequence of `GreenletTree`, one for each running
native thread.
"""
return
list
(
cls
.
_forest
()[
0
].
values
())
@
classmethod
def
current_tree
(
cls
):
"""
current_tree() -> GreenletTree
Returns the `GreenletTree` for the current thread.
"""
return
cls
.
_forest
()[
1
]
src/greentest/test__util.py
View file @
d56054cf
...
@@ -6,6 +6,8 @@ from __future__ import absolute_import
...
@@ -6,6 +6,8 @@ from __future__ import absolute_import
from
__future__
import
division
from
__future__
import
division
from
__future__
import
print_function
from
__future__
import
print_function
import
gc
import
greentest
import
greentest
import
gevent
import
gevent
...
@@ -36,6 +38,7 @@ class TestFormat(greentest.TestCase):
...
@@ -36,6 +38,7 @@ class TestFormat(greentest.TestCase):
rl
.
foo
=
1
rl
.
foo
=
1
def
root
():
def
root
():
l
=
MyLocal
(
42
)
l
=
MyLocal
(
42
)
assert
l
gevent
.
getcurrent
().
spawn_tree_locals
[
'a value'
]
=
42
gevent
.
getcurrent
().
spawn_tree_locals
[
'a value'
]
=
42
g
=
gevent
.
spawn
(
util
.
format_run_info
)
g
=
gevent
.
spawn
(
util
.
format_run_info
)
g
.
join
()
g
.
join
()
...
@@ -47,12 +50,100 @@ class TestFormat(greentest.TestCase):
...
@@ -47,12 +50,100 @@ class TestFormat(greentest.TestCase):
value
=
'
\
n
'
.
join
(
g
.
value
)
value
=
'
\
n
'
.
join
(
g
.
value
)
self
.
assertIn
(
"Spawned at"
,
value
)
self
.
assertIn
(
"Spawned at"
,
value
)
self
.
assertIn
(
"Parent
greenlet
"
,
value
)
self
.
assertIn
(
"Parent
:
"
,
value
)
self
.
assertIn
(
"Spawn Tree Locals"
,
value
)
self
.
assertIn
(
"Spawn Tree Locals"
,
value
)
self
.
assertIn
(
"Greenlet Locals:"
,
value
)
self
.
assertIn
(
"Greenlet Locals:"
,
value
)
self
.
assertIn
(
'MyLocal'
,
value
)
self
.
assertIn
(
'MyLocal'
,
value
)
self
.
assertIn
(
"Printer"
,
value
)
# The name is printed
self
.
assertIn
(
"Printer"
,
value
)
# The name is printed
@
greentest
.
skipOnPyPy
(
"See TestFormat"
)
class
TestTree
(
greentest
.
TestCase
):
@
greentest
.
ignores_leakcheck
def
test_tree
(
self
):
# pylint:disable=too-many-locals
# Python 2.7 on Travis seems to show unexpected greenlet objects
# so perhaps we need a GC?
gc
.
collect
()
gc
.
collect
()
import
re
glets
=
[]
l
=
MyLocal
(
42
)
assert
l
def
s
(
f
):
g
=
gevent
.
spawn
(
f
)
# Access this in spawning order for consistent sorting
# at print time in the test case.
getattr
(
g
,
'minimal_ident'
)
return
g
def
t1
():
raise
greentest
.
ExpectedException
()
def
t2
():
l
=
MyLocal
(
16
)
assert
l
return
s
(
t1
)
s1
=
s
(
t2
)
s1
.
join
()
glets
.
append
(
s
(
t2
))
def
t3
():
return
s
(
t2
)
s3
=
s
(
t3
)
s3
.
spawn_tree_locals
[
'stl'
]
=
'STL'
s3
.
join
()
s4
=
s
(
util
.
GreenletTree
.
current_tree
)
s4
.
join
()
tree
=
s4
.
value
self
.
assertTrue
(
tree
.
root
)
self
.
assertNotIn
(
'Parent'
,
str
(
tree
))
# Simple output
value
=
tree
.
format
(
details
=
{
'stacks'
:
False
})
hexobj
=
re
.
compile
(
'0x[0123456789abcdef]+L?'
,
re
.
I
)
value
=
hexobj
.
sub
(
'X'
,
value
)
value
=
value
.
replace
(
'epoll'
,
'select'
)
value
=
value
.
replace
(
'select'
,
'default'
)
value
=
value
.
replace
(
'test__util'
,
'__main__'
)
value
=
re
.
compile
(
' fileno=.'
).
sub
(
''
,
value
)
value
=
value
.
replace
(
'ref=-1'
,
'ref=0'
)
self
.
maxDiff
=
None
expected
=
"""
\
<greenlet.greenlet object at X>
: Greenlet Locals:
: Local <class '__main__.MyLocal'> at X
: {'foo': 42}
+--- <QuietHub at X default default pending=0 ref=0>
: Parent: <greenlet.greenlet object at X>
+--- <Greenlet "Greenlet-1" at X: _run>; finished with value <Greenlet "Greenlet-0" at X
: Parent: <QuietHub at X default default pending=0 ref=0>
| +--- <Greenlet "Greenlet-0" at X: _run>; finished with exception ExpectedException()
: Parent: <QuietHub at X default default pending=0 ref=0>
+--- <Greenlet "Greenlet-2" at X: _run>; finished with value <Greenlet "Greenlet-4" at X
: Parent: <QuietHub at X default default pending=0 ref=0>
| +--- <Greenlet "Greenlet-4" at X: _run>; finished with exception ExpectedException()
: Parent: <QuietHub at X default default pending=0 ref=0>
+--- <Greenlet "Greenlet-3" at X: _run>; finished with value <Greenlet "Greenlet-5" at X
: Parent: <QuietHub at X default default pending=0 ref=0>
: Spawn Tree Locals
: {'stl': 'STL'}
| +--- <Greenlet "Greenlet-5" at X: _run>; finished with value <Greenlet "Greenlet-6" at X
: Parent: <QuietHub at X default default pending=0 ref=0>
| +--- <Greenlet "Greenlet-6" at X: _run>; finished with exception ExpectedException()
: Parent: <QuietHub at X default default pending=0 ref=0>
+--- <Greenlet "Greenlet-7" at X: _run>; finished with value <gevent.util.GreenletTree obje
Parent: <QuietHub at X default default pending=0 ref=0>
"""
.
strip
()
self
.
assertEqual
(
value
,
expected
)
if
__name__
==
'__main__'
:
if
__name__
==
'__main__'
:
greentest
.
main
()
greentest
.
main
()
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment