diff --git a/examples/plot_threadpool.py b/examples/plot_threadpool.py new file mode 100755 index 0000000000000000000000000000000000000000..268a659fcb32c02c020feeca2ebc7891826f80bf --- /dev/null +++ b/examples/plot_threadpool.py @@ -0,0 +1,250 @@ +#!/usr/bin/env python +""" +Usage: + plot_threadpool.py [options] input.dat output.png + +where input.dat is a threadpool info file for a step. Use the '-Y interval' +flag of the swift command to create these. The output plot will be called +'output.png'. The --limit option can be used to produce plots with the same +time span and the --expand option to expand each thread line into '*expand' +lines, so that adjacent tasks of the same type can be distinguished. Other +options can be seen using the --help flag. + +This file is part of SWIFT. +Copyright (c) 2015 Pedro Gonnet (pedro.gonnet@durham.ac.uk), + Bert Vandenbroucke (bert.vandenbroucke@ugent.be) + Matthieu Schaller (matthieu.schaller@durham.ac.uk) + (c) 2017 Peter W. Draper (p.w.draper@durham.ac.uk) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +""" + +import matplotlib +matplotlib.use("Agg") +import matplotlib.collections as collections +import matplotlib.ticker as plticker +import pylab as pl +import sys +import argparse + +# Handle the command line. +parser = argparse.ArgumentParser(description="Plot threadpool function graphs") + +parser.add_argument("input", help="Threadpool data file (-Y output)") +parser.add_argument("outpng", help="Name for output graphic file (PNG)") +parser.add_argument("-l", "--limit", dest="limit", + help="Upper time limit in millisecs (def: depends on data)", + default=0, type=int) +parser.add_argument("-e", "--expand", dest="expand", + help="Thread expansion factor (def: 1)", + default=1, type=int) +parser.add_argument("--height", dest="height", + help="Height of plot in inches (def: 4)", + default=4., type=float) +parser.add_argument("--width", dest="width", + help="Width of plot in inches (def: 16)", + default=16., type=float) +parser.add_argument("--nolegend", dest="nolegend", + help="Whether to show the legend (def: False)", + default=False, action="store_true") +parser.add_argument("-v", "--verbose", dest="verbose", + help="Show colour assignments and other details (def: False)", + default=False, action="store_true") + +args = parser.parse_args() +infile = args.input +outpng = args.outpng +delta_t = args.limit +expand = args.expand + +# Basic plot configuration. +PLOT_PARAMS = {"axes.labelsize": 10, + "axes.titlesize": 10, + "font.size": 12, + "legend.fontsize": 12, + "xtick.labelsize": 10, + "ytick.labelsize": 10, + "figure.figsize" : (args.width, args.height), + "figure.subplot.left" : 0.03, + "figure.subplot.right" : 0.995, + "figure.subplot.bottom" : 0.09, + "figure.subplot.top" : 0.99, + "figure.subplot.wspace" : 0., + "figure.subplot.hspace" : 0., + "lines.markersize" : 6, + "lines.linewidth" : 3. + } +pl.rcParams.update(PLOT_PARAMS) + +# A number of colours for the various types. Recycled when there are +# more task types than colours... +colours = ["cyan", "lightgray", "darkblue", "yellow", "tan", "dodgerblue", + "sienna", "aquamarine", "bisque", "blue", "green", "lightgreen", + "brown", "purple", "moccasin", "olivedrab", "chartreuse", + "darksage", "darkgreen", "green", "mediumseagreen", + "mediumaquamarine", "darkslategrey", "mediumturquoise", + "black", "cadetblue", "skyblue", "red", "slategray", "gold", + "slateblue", "blueviolet", "mediumorchid", "firebrick", + "magenta", "hotpink", "pink", "orange", "lightgreen"] +maxcolours = len(colours) + +# Read input. +data = pl.genfromtxt(infile, dtype=None, delimiter=" ") + +# Mixed types, so need to separate. +tics = [] +tocs = [] +funcs = [] +threads = [] +chunks = [] +for i in data: + if i[0] != "#": + funcs.append(i[0]) + threads.append(i[1]) + chunks.append(i[2]) + tics.append(i[3]) + tocs.append(i[4]) +tics = pl.array(tics) +tocs = pl.array(tocs) +funcs = pl.array(funcs) +threads = pl.array(threads) +chunks = pl.array(chunks) + +nthread = int(max(threads)) + 1 +print "Number of threads:", nthread + +# Recover the start and end time +tic_step = min(tics) +toc_step = max(tocs) + +# Not known. +CPU_CLOCK = 2200067.0 +if args.verbose: + print "CPU frequency:", CPU_CLOCK * 1000.0 + +# Calculate the time range, if not given. +delta_t = delta_t * CPU_CLOCK +if delta_t == 0: + dt = toc_step - tic_step + if dt > delta_t: + delta_t = dt + print "Data range: ", delta_t / CPU_CLOCK, "ms" + +# Once more doing the real gather and plots this time. +start_t = float(tic_step) +tics -= tic_step +tocs -= tic_step +end_t = (toc_step - start_t) / CPU_CLOCK + +# Get all "task" names and assign colours. +TASKTYPES = pl.unique(funcs) +print TASKTYPES + +# Set colours of task/subtype. +TASKCOLOURS = {} +ncolours = 0 +for task in TASKTYPES: + TASKCOLOURS[task] = colours[ncolours] + ncolours = (ncolours + 1) % maxcolours + +# For fiddling with colours... +if args.verbose: + print "#Selected colours:" + for task in sorted(TASKCOLOURS.keys()): + print "# " + task + ": " + TASKCOLOURS[task] + for task in sorted(SUBCOLOURS.keys()): + print "# " + task + ": " + SUBCOLOURS[task] + +tasks = {} +tasks[-1] = [] +for i in range(nthread*expand): + tasks[i] = [] + +# Counters for each thread when expanding. +ecounter = [] +for i in range(nthread): + ecounter.append(0) + +for i in range(len(threads)): + thread = threads[i] + + # Expand to cover extra lines if expanding. + ethread = thread * expand + (ecounter[thread] % expand) + ecounter[thread] = ecounter[thread] + 1 + thread = ethread + + tasks[thread].append({}) + tasks[thread][-1]["type"] = funcs[i] + tic = tics[i] / CPU_CLOCK + toc = tocs[i] / CPU_CLOCK + tasks[thread][-1]["tic"] = tic + tasks[thread][-1]["toc"] = toc + tasks[thread][-1]["colour"] = TASKCOLOURS[funcs[i]] + +# Use expanded threads from now on. +nthread = nthread * expand + +typesseen = [] +fig = pl.figure() +ax = fig.add_subplot(1,1,1) +ax.set_xlim(-delta_t * 0.01 / CPU_CLOCK, delta_t * 1.01 / CPU_CLOCK) +ax.set_ylim(0, nthread) +for i in range(nthread): + + # Collect ranges and colours into arrays. + tictocs = [] + colours = [] + j = 0 + for task in tasks[i]: + tictocs.append((task["tic"], task["toc"] - task["tic"])) + colours.append(task["colour"]) + + # Legend support, collections don't add to this. + qtask = task["type"] + if qtask not in typesseen: + pl.plot([], [], color=task["colour"], label=qtask) + typesseen.append(qtask) + + # Now plot. + ax.broken_barh(tictocs, [i+0.05,0.90], facecolors = colours, linewidth=0) + +# Legend and room for it. +nrow = len(typesseen) / 5 +if not args.nolegend: + ax.fill_between([0, 0], nthread+0.5, nthread + nrow + 0.5, facecolor="white") + ax.set_ylim(0, nthread + 0.5) + ax.legend(loc=1, shadow=True, bbox_to_anchor=(0., 1.05 ,1., 0.2), mode="expand", ncol=5) + box = ax.get_position() + ax.set_position([box.x0, box.y0, box.width, box.height*0.8]) + +# Start and end of time-step +ax.plot([0, 0], [0, nthread + nrow + 1], 'k--', linewidth=1) +ax.plot([end_t, end_t], [0, nthread + nrow + 1], 'k--', linewidth=1) + +ax.set_xlabel("Wall clock time [ms]", labelpad=0.) +if expand == 1: + ax.set_ylabel("Thread ID", labelpad=0 ) +else: + ax.set_ylabel("Thread ID * " + str(expand), labelpad=0 ) +ax.set_yticks(pl.array(range(nthread)), True) + +loc = plticker.MultipleLocator(base=expand) +ax.yaxis.set_major_locator(loc) +ax.grid(True, which='major', axis="y", linestyle="-") + +pl.show() +pl.savefig(outpng) +print "Graphics done, output written to", outpng + +sys.exit(0) diff --git a/examples/process_plot_threadpool b/examples/process_plot_threadpool new file mode 100755 index 0000000000000000000000000000000000000000..d7bef48d313d50a2627de69263202fa4782efa74 --- /dev/null +++ b/examples/process_plot_threadpool @@ -0,0 +1,96 @@ +#!/bin/bash +# +# Usage: +# process_plot_threadpool nprocess time-range-ms +# +# Description: +# Process all the threadpool info files in the current directory +# creating function graphs for steps and threads. +# +# The input files are created by a run using the "-Y interval" flag and +# should be named "threadpool_info-step<n>.dat" in the current directory. +# All located files will be processed using "nprocess" concurrent +# processes and all plots will have the given time range. An output +# HTML file "index.html" will be created to view all the plots. +# +# +# This file is part of SWIFT: +# +# Copyright (C) 2017 Peter W. Draper (p.w.draper@durham.ac.uk) +# All Rights Reserved. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# Handle command-line +if test "$2" == ""; then + echo "Usage: $0 nprocess time-range-ms" + exit 1 +fi +NPROCS=$1 +TIMERANGE=$2 + +# Find all thread info files. Use version sort to get into correct order. +files=$(ls -v threadpool_info-step*.dat) +if test $? != 0; then + echo "Failed to find any threadpool info files" + exit 1 +fi + +# Construct list of names, the step no and names for the graphics. +list="" +for f in $files; do + s=$(echo $f| sed 's,threadpool_info-step\(.*\).dat,\1,') + list="$list $f $s poolstep${s}r" +done + +# And process them, +echo "Processing threadpool info files..." +echo $list | xargs -P $NPROCS -n 3 /bin/bash -c "./plot_threadpool.py --expand 3 --limit $TIMERANGE --width 16 --height 4 \$0 \$2 " + +echo "Writing output index.html file" +# Construct document - serial. +cat <<EOF > index.html + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html> + <head> + <title>SWIFT task graphs</title> + </head> + <body> + <h1>SWIFT task graphs</h1> +EOF + +echo $list | xargs -n 3 | while read f s g; do + cat <<EOF >> index.html +<h2>Step $s</h2> +EOF + cat <<EOF >> index.html +<a href="poolstep${s}r${i}.html"><img src="poolstep${s}r${i}.png" width=400px/></a> +EOF + cat <<EOF > poolstep${s}r${i}.html + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html> +<body> +<img src="poolstep${s}r${i}.png"> +<pre> +EOF +done + +cat <<EOF >> index.html + </body> +</html> +EOF + +echo "Finished" + +exit