from __future__ import print_function
import os, sys, inspect
import logging; log = logging.getLogger(__name__)
PY3 = sys.version_info >= (3, 0)
import gdb
from mcgdb.toolbox import my_gdb
from mcgdb.toolbox import aspect
on_update = set()
@my_gdb.internal
[docs]def initialize():
aspect.register("sequence", sequence_aspects, pre_aspect)
@my_gdb.internal
[docs]def activate():
seq = cmd_omp_sequence()
from . import graph
param_graph_auto()
param_graph_auto.graph_cmd = seq
[docs]class param_graph_auto (gdb.Parameter):
auto_graph_enabled = False
graph_cmd = None
@staticmethod
[docs] def on_stop(evt=None):
assert param_graph_auto.graph_cmd is not None
if param_graph_auto.graph_cmd.has_changed():
param_graph_auto.graph_cmd.invoke("--no-gen --async --no-write", from_tty=False)
def __init__ (self):
gdb.Parameter.__init__(self, "omp-auto-sequence",
gdb.COMMAND_OBSCURE,
gdb.PARAM_BOOLEAN)
self.value = False
old_prompt = gdb.prompt_hook
def auto_graph_prompt(prompt):
if param_graph_auto.auto_graph_enabled:
param_graph_auto.on_stop()
if old_prompt:
prompt = old_prompt(prompt)
return prompt
gdb.prompt_hook = auto_graph_prompt
[docs] def get_set_string(self):
if not param_graph_auto.auto_graph_enabled and self.value:
# enable
gdb.events.stop.connect(param_graph_auto.on_stop)
assert param_graph_auto.graph_cmd is not None
#param_graph_auto.graph_cmd.invoke("--open", from_tty=False)
msg = "OpenMP auto graph enabled"
elif param_graph_auto.auto_graph_enabled and not self.value:
# disable
gdb.events.stop.disconnect(param_graph_auto.on_stop)
msg = "OpenMP auto graph disabled"
else:
# nothing to do, value unchanged
msg = "Nothing changed, auto graph is {}".format(self.value)
param_graph_auto.auto_graph_enabled = self.value
return msg
[docs] def get_show_string (self, svalue):
return "OpenMP auto graph is currently {}".format(svalue)
top_block = None
[docs]class cmd_omp_sequence(gdb.Command):
@classmethod
[docs] def has_changed(clazz):
return \
clazz.changed \
or clazz.sched_locked != gdb.parameter("scheduler-locking") \
or clazz.thread is not gdb.selected_thread()
self = None
changed = True
thread_num = None
sched_locked = None
def __init__ (self):
gdb.Command.__init__(self, "omp sequence", gdb.COMMAND_OBSCURE)
self.count = 0
[docs] def invoke (self, args, from_tty):
self.dont_repeat()
only_current = "--all" not in args
do_print = "--print" in args or "--show" in args
do_open = "--open" in args
do_not_gen = "--no-gen" in args
do_not_write = "--no-write" in args
do_sync = "--sync" in args
do_async = "--async" in args
do_gen_png = True # for now
if do_print: do_not_gen = True
if do_open: do_sync = True
if do_async: do_sync = True # for now
Block.print_only_current = only_current
seqdiag = str(top_block) if PY3 else \
u"{}".format(top_block).encode('UTF-8')
Block.print_only_current = False
cmd_omp_sequence.changed = False
cmd_omp_sequence.sched_locked = gdb.parameter("scheduler-locking")
cmd_omp_sequence.thread = gdb.selected_thread()
if seqdiag == "None":
seqdiag = """{\n 1;\n 1 -> 1 [here];\n 1 <- 1 [narrow]\n}"""
if do_print:
print(seqdiag)
if not do_not_write:
with open("run", "w") as f:
print(seqdiag, file=f)
for update_listener in on_update:
update_listener(seqdiag)
if not do_not_gen:
svg_command = "seqdiag run -T svg"
png_command = "convert run.svg run.png" if do_not_gen else "echo ''"
sync = "" if do_sync else "&"
os.system('sh -c "{} ; {}" {}'.format(svg_command, png_command, sync))
if do_open:
os.system("eog run.png >/dev/null 2>/dev/null &")
[docs]def pre_aspect(help_tracking, self, args):
cmd_omp_sequence.changed = True
[docs]class Edge:
def __init__(self, from_node, to_node,
backwards=False,
**kwargs):
self.from_node = from_node
self.to_node = to_node
self.forwards = not backwards
self.attribs = kwargs
self.zindex = 0
def __str__(self):
edge = "{from} {dir} {to}".format(**{
"from": self.from_node.name,
"to": self.to_node.name,
"dir": "->" if self.forwards else "<-"
})
if self.attribs:
attr = ", ".join([name for name, value in self.attribs.items() \
if value is None] +\
[u'{}="{}"'.format(name, value) \
for name, value in self.attribs.items() \
if value is not None] )
attr = u" [{}]".format(attr)
else:
attr = ""
return u"{}{}".format(edge, attr)
[docs]def elipse_str(string):
MAX_LEN = 12
if len(string) > MAX_LEN:
string = string[:MAX_LEN - 1]
string += u"\u2026" # single char '...'
return string
[docs]class Node:
first = None
all_nodes = set()
def __init__(self, name, worker):
Node.all_nodes.add(self)
self.name = name
self.block = None
self.worker = worker
if Node.first is None:
Node.first = self
[docs] def msg(self, **kwargs):
self.block.edges.append(Edge(self, self, msg=None, **kwargs))
def __str__(self):
return "{}".format(self.name)
[docs]class Block:
print_only_current = False
def __init__(self, parent, _type, label=None, zindex=0):
self.type = _type
self.nodes = set()
self.working = set()
self.label = label
self.edges = []
self.parent = parent
self.zindex = zindex
if parent is not None:
parent.edges.append(self)
@property
def zordered_edges(self):
return sorted(self.edges, key=lambda e: e.zindex, reverse=True)
[docs] def add_node(self, node, **kwargs):
self.nodes.add(node)
node.block = self
[docs] def flow(self, from_node, to_node, **kwargs):
self.edges.append(Edge(from_node, to_node, **kwargs))
self.working.add(from_node)
self.working.add(to_node)
[docs] def enter(self, node, **kwargs):
self.edges.append(Edge(node, node, **kwargs))
self.working.add(node)
[docs] def exit(self, node, no_insert=False, **kwargs):
edge = Edge(node, node, backwards=True, **kwargs)
if not no_insert:
self.edges.append(edge)
try:
self.working.remove(node)
except:
log.error("Node {} doesn't belong to {}/{}".format(node, self, self.working))
return edge
[docs] def leave(self, node):
node.block = self.parent
[docs] def finish(self):
for node in self.nodes:
if node.block is self:
node.block.leave(node)
# ret: <has_worker>, <child_has_worker>
[docs] def has_worker(self, recursive=False):
# has_worker ?
for node in self.nodes:
if node.block is self:
return True, None
# don't dive, so no
if not recursive:
return False, None
# no worker, child_has_worker ?
for block in self.edges:
if not isinstance(block, Block):
continue
if any(block.has_worker(recursive=True)):
return False, True
return False, False
def __str__(self):
def want_edge(edge):
if Block.print_only_current:
if not isinstance(edge, Block):
# only blocks should be hidden
return True
block = edge
has_worker, child_has_worker = block.has_worker(recursive=True)
return has_worker or child_has_worker
return True
type_ = "{} ".format(self.type) if self.type is not None else ""
if self.type and self.label:
label = '"{}"'.format(self.label) if " " in self.label else self.label
type_ += '{} '.format(label)
node_list = self.nodes if self.parent is not None \
else sorted(Node.all_nodes, key=lambda n : int(n.name))
nodes = "; ".join(map(str, node_list))+";"
edges = u";\n ".join(u"{}".format(edge).replace("\n", "\n ") \
for edge in self.zordered_edges if want_edge(edge))
# check if we have to set the "here" attribute
for node in self.nodes:
if node.block is not self:
continue
edges += u"\n {};".format(Edge(node, node, here=None))
if node in self.working and not self.parent is None:
edges += "\n {};".format(self.exit(node, no_insert=True, narrow=None))
# root block sets some more attributes ...
if self.parent is None:
# implicite return from main
if True or node not in self.working:
edges += "\n "+str(Edge(Node.first, Node.first, backwards=True, narrow=None))
# put the active worker(s) in a group to highlight it
cur_worker = Node.first.worker.__class__.get_current_worker()
def is_active(node):
return node.worker is cur_worker
active = [str(node) for node in Node.all_nodes if is_active(node)]
if active:
attribs = ""
if gdb.parameter("scheduler-locking") == "on":
attribs += "color=lightcoral;"
if attribs: attribs = "\n" + attribs
nodes += """\n
group {{{}
{}
}}""".format(attribs, "; ".join(active)+";").replace("\n", "\n ")
return u"""{}{{
{}
{}
}}""".format(type_, nodes, edges)
[docs]class Barrier(Block):
def __str__(self):
has_body = False
barrier = "=== <{}> ===".format(self.type)
edges = u""
if self.edges:
edges += u"\n".join(u"{}".format(edge) for edge in self.zordered_edges)
has_body = True
for node in self.nodes:
if node.block is self:
edges += u"\n{};".format(Edge(node, node, here=None))
has_body = True
if has_body:
stop = "=== </{}> ===".format(self.type)
barrier = u"\n".join((barrier, edges, stop))
return barrier
[docs]def sequence_aspects(Tracks):
from .. import representation
@Tracks(representation.Worker)
class WorkerTracker:
def __init__(this):
this.node = Node(str(this.self.number), this.self)
if this.self.number == 1:
global top_block
top_block = Block(None, None)
top_block.add_node(this.node)
top_block.enter(this.node, leftnote="main")
@Tracks(representation.ParallelJob)
class ParallelJobTracker:
def __init__(this):
this.args.num_workers, this.args.parent_worker = this.meth_args
this.block = Block(top_block, "parallel{}".format(" for" if this.self.is_for else ""))
def start_working(this):
node = this.self_to_this[this.args.worker].node
this.block.add_node(node)
def stop_working(this):
pass
def completed(this):
this.block.finish()
@Tracks(representation.SingleJob)
class SingleTracker:
def __init__(this, before=False):
this.args.worker, this.args.parallel_job = this.meth_args
if not before:
this.barrier = this.self_to_this[this.args.parallel_job.barrier].barrier
return
node = this.self_to_this[this.args.worker].node
this.parallel_block = this.self_to_this[this.args.parallel_job].block
this.block = Block(this.parallel_block, "single")
def enter(this):
node = this.self_to_this[this.args.worker].node
this.block.add_node(node)
if this.args.inside:
this.block.enter(node, label="enter")
def finished(this):
node = this.self_to_this[this.self.visitor].node
this.block.exit(node, label="exit")
def completed(this):
this.block.finish()
@Tracks(representation.TaskJob)
class TaskJobTracker:
def __init__(this):
this.args.worker, = this.meth_args
if not this.self.masterTask and this.args.worker:
node = this.self_to_this[this.args.worker].node
node.msg(label="Task {}".format(this.self.number))
def start(this):
this.node = this.self_to_this[this.args.worker].node
# 'task' type is special, there can be several on the same line,
# but they're not handled correctly at the end of a block
this.block = Block(this.node.block if this.node.block is not None else top_block,
*(("single", "master") if this.self.masterTask \
else ("task", None)))
this.block.add_node(this.node)
#for dep in this.self.in_dependencies:
# this.node.msg(label="With dependency #{}".format(dep.number))
this.block.enter(this.node, label="Task {}".format(this.self.number))
def finish(this):
this.block.exit(this.node)
#for dep in this.self.out_dependencies:
# this.node.msg(label="Dependency #{} ready".format(dep.number))
this.block.leave(this.node)
@Tracks(representation.CriticalJob)
class CriticalJobTracker:
def __init__(this):
this.args.worker, this.args.parallel_job = this.meth_args
node = this.self_to_this[this.args.worker].node
this.parallel_block = this.self_to_this[this.args.parallel_job].block
this.block = Block(this.parallel_block, "critical")
this.inside = None
def try_enter(this):
node = this.self_to_this[this.args.worker].node
this.block.add_node(node)
def entered(this):
prev_inside = this.inside
this.inside = this.self_to_this[this.args.worker].node
if prev_inside:
this.block.flow(prev_inside, this.inside)
else:
this.block.enter(this.inside, label="enter")
def left(this):
pass
def completed(this):
this.block.exit(this.inside, label="finish")
this.block.finish()
@Tracks(representation.Barrier)
class BarrierTracker:
def __init__(this):
this.args.parallel_job, this.args.worker = this.meth_args
if "single" in this.args:
parent_zone = this.args.single
zindex = -1
else:
parent_zone = this.args.parallel_job
zindex = 0
this.parent_block = this.self_to_this[parent_zone].block
this.barrier = Barrier(this.parent_block,
"Barrier {}".format(this.self.number),
zindex=zindex)
def reach_barrier(this):
node = this.self_to_this[this.args.worker].node
this.barrier.add_node(node)
def leave_barrier(this):
node = this.self_to_this[this.args.worker].node
this.barrier.leave(node)
if this.self.single:
single = this.self_to_this[this.self.single].block
single.leave(node)
@Tracks(representation.SectionJob)
class SectionJobTracker:
def __init__(this):
this.args.parallel_zone, this.args.worker, this.args.count = \
this.meth_args
parallel_block = this.self_to_this[this.args.parallel_zone].block
this.block = Block(parallel_block, "section")
this.working = set()
node = this.self_to_this[this.args.worker].node
this.block.add_node(node)
def work_on_section(this):
node = this.self_to_this[this.args.worker].node
this.block.add_node(node)
if node in this.working:
this.working.remove(node)
this.block.exit(node)
if this.args.section_id != 0:
this.block.enter(node, label="Section #{}".format(this.args.section_id))
this.working.add(node)
def completed(this):
this.block.finish()