diff --git a/examples/plot_tasks.py b/examples/plot_tasks.py old mode 100644 new mode 100755 index bf0002664aa04fb0efccf1c1cbeb86158cb17d23..3acdcc03593906ac251553ba54c8ad86b5acedd0 --- a/examples/plot_tasks.py +++ b/examples/plot_tasks.py @@ -1,122 +1,148 @@ -############################################################################### - # 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) - # - # 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/>. - # - ############################################################################## - +#!/usr/bin/env python +""" +Usage: + plot_tasks.py input.dat output.png [time-range-ms] + +where input.dat is a thread info file for a step. Use the '-y interval' +flag of the swift MPI commands to create these. The output plot will be +called 'output.png'. Use the time-range-ms in millisecs to produce +plots with the same time span. + +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) 2016 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 +import matplotlib.collections as collections matplotlib.use('Agg') import pylab as pl import numpy as np import sys +# CPU ticks per second. CPU_CLOCK = 2.7e9 -params = {'axes.labelsize': 10, -'axes.titlesize': 10, -'text.fontsize': 12, -'legend.fontsize': 12, -'xtick.labelsize': 10, -'ytick.labelsize': 10, -'text.usetex': True, -'figure.figsize' : (12., 4.), -'figure.subplot.left' : 0.03, -'figure.subplot.right' : 0.995 , -'figure.subplot.bottom' : 0.1 , -'figure.subplot.top' : 0.99 , -'figure.subplot.wspace' : 0. , -'figure.subplot.hspace' : 0. , -'lines.markersize' : 6, -'lines.linewidth' : 3., -'text.latex.unicode': True -} -pl.rcParams.update(params) -pl.rc('font',**{'family':'sans-serif','sans-serif':['Times']}) - - - -types = {"0": "task_none", - "1": "task_sort", - "2": "task_self", - "3": "task_pair", - "4": "task_sub", - "5": "task_ghost", - "6": "task_kick1", - "7": "task_kick2", - "8": "task_send", - "9": "task_recv", - "10": "task_grav_pp", - "11": "task_grav_mm", - "12": "task_grav_up", - "13": "task_grav_down", - "14": "task_psort", - "15": "task_split_cell", - "16": "task_rewait", - "17": "task_count"} - -subtypes = {"0": "subtask_none", - "1": "subtask_density", - "2": "subtask_force", - "3": "subtask_grav", - "4": "subtask_count"} - -# Assign colours for all types. -colors = ["red","blue","green","yellow","cyan","magenta","black"] -colors = colors + list(matplotlib.colors.cnames) -index = 0 - -subtypecolors = {} -for key in subtypes: - subtypecolors[subtypes[key]] = colors[index] - print subtypes[key], " = ", colors[index] - index = index + 1 - -taskcolors = {} -for key in types: - taskcolors[types[key]] = colors[index] - print types[key], " = ", colors[index] - index = index + 1 - -data = pl.loadtxt("thread_info.dat") +# 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" : (16., 4.), + "figure.subplot.left" : 0.03, + "figure.subplot.right" : 0.995, + "figure.subplot.bottom" : 0.1, + "figure.subplot.top" : 0.99, + "figure.subplot.wspace" : 0., + "figure.subplot.hspace" : 0., + "lines.markersize" : 6, + "lines.linewidth" : 3. + } +pl.rcParams.update(PLOT_PARAMS) + +# Tasks and subtypes. Indexed as in tasks.h. +TASKTYPES = ["none", "sort", "self", "pair", "sub", "ghost", "kick1", "kick2", + "send", "recv", "grav_pp", "grav_mm", "grav_up", "grav_down", + "psort", "split_cell", "rewait", "count"] + +TASKCOLOURS = {"none": "black", + "sort": "lightblue", + "self": "greenyellow", + "pair": "navy", + "sub": "hotpink", + "ghost": "cyan", + "kick1": "maroon", + "kick2": "green", + "send": "yellow", + "recv": "magenta", + "grav_pp": "mediumorchid", + "grav_mm": "mediumturquoise", + "grav_up": "mediumvioletred", + "grav_down": "mediumnightblue", + "psort": "steelblue", + "split_cell": "seagreen", + "rewait": "olive", + "count": "powerblue"} + +SUBTYPES = ["none", "density", "force", "grav", "count"] + +SUBCOLOURS = {"none": "black", + "density": "red", + "force": "blue", + "grav": "indigo", + "count": "purple"} + +# Show docs if help is requested. +if len( sys.argv ) == 2 and ( sys.argv[1][0:2] == "-h" or sys.argv[1][0:3] == "--h" ): + from pydoc import help + help( "__main__" ) + sys.exit( 0 ) + +# Handle command-line. +if len( sys.argv ) != 3 and len( sys.argv ) != 4: + print "Usage: ", sys.argv[0], "input.dat output.png [time-range-ms]" + sys.exit(1) + +infile = sys.argv[1] +outpng = sys.argv[2] +delta_t = 0 +if len( sys.argv ) == 4: + delta_t = int(sys.argv[3]) * CPU_CLOCK / 1000 + +# Read input. +data = pl.loadtxt( infile ) nthread = int(max(data[:,0])) + 1 print "Number of threads:", nthread -tasks = {} -tasks[-1] = [] -for i in range(nthread): - tasks[i] = [] +# Avoid start and end times of zero. +data = data[data[:,4] != 0] +data = data[data[:,5] != 0] +# Calculate the time range, it not given. +if delta_t == 0: + dt = max(data[:,5]) - min(data[:,4]) + if dt > delta_t: + delta_t = dt +# Once more doing the real gather and plots this time. start_t = min(data[:,4]) -end_t = max(data[:,5]) data[:,4] -= start_t data[:,5] -= start_t -num_lines = pl.size(data) / 8 + +tasks = {} +tasks[-1] = [] +for i in range(nthread): + tasks[i] = [] + +num_lines = pl.size(data) / 10 for line in range(num_lines): thread = int(data[line,0]) tasks[thread].append({}) - tasks[thread][-1]["type"] = types[ str(int(data[line,1])) ] - tasks[thread][-1]["subtype"] = subtypes[str(int(data[line,2]))] - tasks[thread][-1]["tic"] = int(data[line,4]) / CPU_CLOCK * 1000 - tasks[thread][-1]["toc"] = int(data[line,5]) / CPU_CLOCK * 1000 - tasks[thread][-1]["t"] = (tasks[thread][-1]["toc"]+tasks[thread][-1]["tic"]) / 2 + tasks[thread][-1]["type"] = TASKTYPES[int(data[line,1])] + tasks[thread][-1]["subtype"] = SUBTYPES[int(data[line,2])] + tic = int(data[line,4]) / CPU_CLOCK * 1000 + toc = int(data[line,5]) / CPU_CLOCK * 1000 + tasks[thread][-1]["tic"] = tic + tasks[thread][-1]["toc"] = toc + tasks[thread][-1]["t"] = (toc + tic)/ 2 combtasks = {} combtasks[-1] = [] @@ -136,22 +162,68 @@ for thread in range(nthread): combtasks[thread][-1]["subtype"] = task["subtype"] combtasks[thread][-1]["tic"] = task["tic"] combtasks[thread][-1]["toc"] = task["toc"] - if task["type"] == 'task_self' or task["type"] == 'task_pair' or task["type"] == 'task_sub': - combtasks[thread][-1]["color"] = subtypecolors[task["subtype"]] + if task["type"] == "self" or task["type"] == "pair" or task["type"] == "sub": + combtasks[thread][-1]["colour"] = SUBCOLOURS[task["subtype"]] else: - combtasks[thread][-1]["color"] = taskcolors[task["type"]] + combtasks[thread][-1]["colour"] = TASKCOLOURS[task["type"]] lasttype = task["type"] else: combtasks[thread][-1]["toc"] = task["toc"] +typesseen = [] +fig = pl.figure() +ax = fig.add_subplot(1,1,1) +ax.set_xlim(0, delta_t * 1.03 * 1000 / CPU_CLOCK) +ax.set_ylim(0, nthread) +tictoc = np.zeros(2) for i in range(nthread): - for task in combtasks[i]: - pl.fill_between([task["tic"], task["toc"]], i+0.05, i+0.95, facecolor=task["color"]) - -pl.xlabel("Wall clock time [ms]") -pl.xlim(0, (end_t - start_t)*1.03 * 1000 / CPU_CLOCK) -pl.ylabel("Thread ID") -pl.yticks(pl.array(range(nthread)) + 0.5, pl.array(range(nthread))) -pl.savefig("task_graph.png") - + # Collect ranges and colours into arrays. + tictocs = np.zeros(len(combtasks[i])*2) + colours = np.empty(len(combtasks[i])*2, dtype='object') + coloursseen = [] + j = 0 + for task in combtasks[i]: + tictocs[j] = task["tic"] + tictocs[j+1] = task["toc"] + colours[j] = task["colour"] + colours[j+1] = task["colour"] + j = j + 2 + if task["colour"] not in coloursseen: + coloursseen.append(task["colour"]) + + # Legend support, collections don't add to this. + if task["subtype"] != "none": + qtask = task["type"] + "/" + task["subtype"] + else: + qtask = task["type"] + if qtask not in typesseen: + pl.plot([], [], color=task["colour"], label=qtask) + typesseen.append(qtask) + + # Now plot each colour, faster to use a mask to select colour ranges. + for colour in coloursseen: + collection = collections.BrokenBarHCollection.span_where(tictocs, ymin=i+0.05, ymax=i+0.95, + where=colours == colour, + facecolor=colour, + linewidths=0) + ax.add_collection(collection) + + +# Legend and room for it. +nrow = len(typesseen) / 5 +if len(typesseen) * 5 < nrow: + nrow = nrow + 1 +ax.fill_between([0, 0], nthread+0.5, nthread + nrow + 0.5, facecolor="white") +ax.set_ylim(0, nthread + nrow + 1) +ax.legend(loc=1, shadow=True, mode="expand", ncol=5) + +ax.set_xlabel("Wall clock time [ms]") +ax.set_ylabel("Thread ID" ) +ax.set_yticks(pl.array(range(nthread)), True) + +pl.show() +pl.savefig(outpng) +print "Graphics done, output written to", outpng + +sys.exit(0) diff --git a/examples/process_plot_tasks b/examples/process_plot_tasks new file mode 100755 index 0000000000000000000000000000000000000000..cf19401b582c29f7e35073be93569ea8039f958d --- /dev/null +++ b/examples/process_plot_tasks @@ -0,0 +1,89 @@ +#!/bin/bash +# +# Usage: +# process_plot_tasks nprocess time-range-ms +# +# Description: +# Process all the thread info files in the current directory +# creating task graphs for steps and threads. +# +# The input files are created by a run using the "-y interval" flag and +# should be named "thread_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) 2016 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 thread_info-step*.dat) +if test $? != 0; then + echo "Failed to find any thread 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,thread_info-step\(.*\).dat,\1,') + list="$list $f $s step${s}r" +done + +# And process them, +echo "Processing thread info files..." +echo $list | xargs -P $NPROCS -n 3 /bin/bash -c "./plot_tasks.py \$0 \$2 $TIMERANGE" + +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="step${s}r${i}.png"><img src="step${s}r${i}.png" width=400px/></a> +EOF +done + +cat <<EOF >> index.html + </body> +</html> +EOF + +echo "Finished" + +exit