diff --git a/examples/analyse_tasks.py b/examples/analyse_tasks.py new file mode 100755 index 0000000000000000000000000000000000000000..0dbd08630f148e9d038a55f2d383593e91a2b6a2 --- /dev/null +++ b/examples/analyse_tasks.py @@ -0,0 +1,180 @@ +#!/usr/bin/env python +""" +Usage: + analsyse_tasks.py [options] input.dat + +where input.dat is a thread info file for a step. Use the '-y interval' flag +of the swift command to create these. + +The output is an analysis of the task timings, including deadtime per thread +and step, total amount of time spent for each task type, for the whole step +and per thread and the minimum and maximum times spent per task type. + +This file is part of SWIFT. +Copyright (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="Analyse task dumps") + +parser.add_argument("input", help="Thread data file (-y output)") +parser.add_argument("-v", "--verbose", dest="verbose", + help="Verbose output (default: False)", + default=False, action="store_true") + +args = parser.parse_args() +infile = args.input + +# Tasks and subtypes. Indexed as in tasks.h. +TASKTYPES = ["none", "sort", "self", "pair", "sub_self", "sub_pair", + "init_grav", "ghost", "extra_ghost", "drift_part", + "drift_gpart", "kick1", "kick2", "timestep", "send", "recv", + "grav_top_level", "grav_long_range", "grav_mm", "grav_down", + "cooling", "sourceterms", "count"] + +SUBTYPES = ["none", "density", "gradient", "force", "grav", "external_grav", + "tend", "xv", "rho", "gpart", "multipole", "spart", "count"] + +# Read input. +data = pl.loadtxt( infile ) + +nthread = int(max(data[:,0])) + 1 +print "Number of threads:", nthread + +# Recover the start and end time +full_step = data[0,:] +tic_step = int(full_step[4]) +toc_step = int(full_step[5]) +CPU_CLOCK = float(full_step[-1]) / 1000.0 +data = data[1:,:] +if args.verbose: + print "CPU frequency:", CPU_CLOCK * 1000.0 + +# Avoid start and end times of zero. +data = data[data[:,4] != 0] +data = data[data[:,5] != 0] + +# Calculate the time range. +total_t = (toc_step - tic_step)/ CPU_CLOCK +print "Data range: ", total_t, "ms" + +# Correct times to relative values. +start_t = float(tic_step) +data[:,4] -= start_t +data[:,5] -= start_t + +tasks = {} +tasks[-1] = [] +for i in range(nthread): + tasks[i] = [] + +# Gather into by thread data. +num_lines = pl.size(data) / 10 +for line in range(num_lines): + thread = int(data[line,0]) + tic = int(data[line,4]) / CPU_CLOCK + toc = int(data[line,5]) / CPU_CLOCK + tasktype = int(data[line,1]) + subtype = int(data[line,2]) + + tasks[thread].append([tic,toc,tasktype,subtype]) + +# Sort by tic. +for i in range(nthread): + tasks[i] = sorted(tasks[i], key=lambda task: task[0]) + +# Times per task. +print "# Task times:" +print "# {0:<16s}: {1:>7s} {2:>9s} {3:>9s} {4:>9s} {5:>9s} {6:>9s}"\ + .format("type/subtype", "count","minimum", "maximum", + "sum", "mean", "percent") +alltasktimes = {} +for i in range(nthread): + tasktimes = {} + for task in tasks[i]: + key = TASKTYPES[task[2]] + "/" + SUBTYPES[task[3]] + dt = task[1] - task[0] + if not key in tasktimes: + tasktimes[key] = [] + tasktimes[key].append(dt) + + if not key in alltasktimes: + alltasktimes[key] = [] + alltasktimes[key].append(dt) + + print "# Thread : ", i + for key in sorted(tasktimes.keys()): + taskmin = min(tasktimes[key]) + taskmax = max(tasktimes[key]) + tasksum = sum(tasktimes[key]) + print "{0:18s}: {1:7d} {2:9.4f} {3:9.4f} {4:9.4f} {5:9.4f} {6:9.2f}"\ + .format(key, len(tasktimes[key]), taskmin, taskmax, tasksum, + tasksum / len(tasktimes[key]), tasksum / total_t * 100.0) + print + +print "# All threads : " +for key in sorted(alltasktimes.keys()): + taskmin = min(alltasktimes[key]) + taskmax = max(alltasktimes[key]) + tasksum = sum(alltasktimes[key]) + print "{0:18s}: {1:7d} {2:9.4f} {3:9.4f} {4:9.4f} {5:9.4f} {6:9.2f}"\ + .format(key, len(alltasktimes[key]), taskmin, taskmax, tasksum, + tasksum / len(alltasktimes[key]), + tasksum / (nthread * total_t) * 100.0) +print + +# Dead times. +print "# Deadtimes:" +print "# no. : {0:>9s} {1:>9s} {2:>9s} {3:>9s} {4:>9s} {5:>9s}"\ + .format("count", "minimum", "maximum", "sum", "mean", "percent") +alldeadtimes = [] +for i in range(nthread): + deadtimes = [] + last = 0 + for task in tasks[i]: + dt = task[0] - last + deadtimes.append(dt) + last = task[1] + dt = total_t - last + deadtimes.append(dt) + + deadmin = min(deadtimes) + deadmax = max(deadtimes) + deadsum = sum(deadtimes) + print "thread {0:2d}: {1:9d} {2:9.4f} {3:9.4f} {4:9.4f} {5:9.4f} {6:9.2f}"\ + .format(i, len(deadtimes), deadmin, deadmax, deadsum, + deadsum / len(deadtimes), deadsum / total_t * 100.0) + alldeadtimes.extend(deadtimes) + +deadmin = min(alldeadtimes) +deadmax = max(alldeadtimes) +deadsum = sum(alldeadtimes) +print "all : {0:9d} {1:9.4f} {2:9.4f} {3:9.4f} {4:9.4f} {5:9.2f}"\ + .format(len(alldeadtimes), deadmin, deadmax, deadsum, + deadsum / len(alldeadtimes), + deadsum / (nthread * total_t ) * 100.0) +print + + +sys.exit(0) diff --git a/examples/analyse_tasks_MPI.py b/examples/analyse_tasks_MPI.py new file mode 100755 index 0000000000000000000000000000000000000000..7063308787b64874c02be159a2153d8d1018a166 --- /dev/null +++ b/examples/analyse_tasks_MPI.py @@ -0,0 +1,195 @@ +#!/usr/bin/env python +""" +Usage: + analsyse_tasks_MPI.py [options] input.dat + +where input.dat is a thread info file for an MPI step. Use the '-y interval' +flag of the swift command to create these. + +The output is an analysis of the task timings, including deadtime per thread +and step, total amount of time spent for each task type, for the whole step +and per thread and the minimum and maximum times spent per task type. + +This file is part of SWIFT. +Copyright (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="Analyse task dumps") + +parser.add_argument("input", help="Thread data file (-y output)") +parser.add_argument("-v", "--verbose", dest="verbose", + help="Verbose output (default: False)", + default=False, action="store_true") + +args = parser.parse_args() +infile = args.input + +# Tasks and subtypes. Indexed as in tasks.h. +TASKTYPES = ["none", "sort", "self", "pair", "sub_self", "sub_pair", + "init_grav", "ghost", "extra_ghost", "drift_part", + "drift_gpart", "kick1", "kick2", "timestep", "send", "recv", + "grav_top_level", "grav_long_range", "grav_mm", "grav_down", + "cooling", "sourceterms", "count"] + +SUBTYPES = ["none", "density", "gradient", "force", "grav", "external_grav", + "tend", "xv", "rho", "gpart", "multipole", "spart", "count"] + +# Read input. +data = pl.loadtxt( infile ) + +# Get the CPU clock to convert ticks into milliseconds. +full_step = data[0,:] +CPU_CLOCK = float(full_step[-1]) / 1000.0 +if args.verbose: + print "# CPU frequency:", CPU_CLOCK * 1000.0 + +nranks = int(max(data[:,0])) + 1 +print "# Number of ranks:", nranks +nthread = int(max(data[:,1])) + 1 +print "# Number of threads:", nthread + +# Avoid start and end times of zero. +sdata = data[data[:,5] != 0] +sdata = data[data[:,6] != 0] + +# Now we process all the ranks. +for rank in range(nranks): + print "# Rank", rank + data = sdata[sdata[:,0] == rank] + + # Recover the start and end time + full_step = data[0,:] + tic_step = int(full_step[5]) + toc_step = int(full_step[6]) + data = data[1:,:] + + # Avoid start and end times of zero. + data = data[data[:,5] != 0] + data = data[data[:,6] != 0] + + # Calculate the time range. + total_t = (toc_step - tic_step)/ CPU_CLOCK + print "# Data range: ", total_t, "ms" + + # Correct times to relative values. + start_t = float(tic_step) + data[:,5] -= start_t + data[:,6] -= start_t + end_t = (toc_step - start_t) / CPU_CLOCK + + tasks = {} + tasks[-1] = [] + for i in range(nthread): + tasks[i] = [] + + # Gather into by thread data. + num_lines = pl.size(data) / 12 + for line in range(num_lines): + thread = int(data[line,1]) + tic = int(data[line,5]) / CPU_CLOCK + toc = int(data[line,6]) / CPU_CLOCK + tasktype = int(data[line,2]) + subtype = int(data[line,3]) + + tasks[thread].append([tic,toc,tasktype,subtype]) + + # Sort by tic. + for i in range(nthread): + tasks[i] = sorted(tasks[i], key=lambda task: task[0]) + + # Times per task. + print "# Task times:" + print "# {0:<16s}: {1:>7s} {2:>9s} {3:>9s} {4:>9s} {5:>9s} {6:>9s}"\ + .format("type/subtype", "count","minimum", "maximum", + "sum", "mean", "percent") + alltasktimes = {} + for i in range(nthread): + tasktimes = {} + for task in tasks[i]: + key = TASKTYPES[task[2]] + "/" + SUBTYPES[task[3]] + dt = task[1] - task[0] + if not key in tasktimes: + tasktimes[key] = [] + tasktimes[key].append(dt) + + if not key in alltasktimes: + alltasktimes[key] = [] + alltasktimes[key].append(dt) + + print "# Thread : ", i + for key in sorted(tasktimes.keys()): + taskmin = min(tasktimes[key]) + taskmax = max(tasktimes[key]) + tasksum = sum(tasktimes[key]) + print "{0:18s}: {1:7d} {2:9.4f} {3:9.4f} {4:9.4f} {5:9.4f} {6:9.2f}"\ + .format(key, len(tasktimes[key]), taskmin, taskmax, tasksum, + tasksum / len(tasktimes[key]), tasksum / total_t * 100.0) + print + + print "# All threads : " + for key in sorted(alltasktimes.keys()): + taskmin = min(alltasktimes[key]) + taskmax = max(alltasktimes[key]) + tasksum = sum(alltasktimes[key]) + print "{0:18s}: {1:7d} {2:9.4f} {3:9.4f} {4:9.4f} {5:9.4f} {6:9.2f}"\ + .format(key, len(alltasktimes[key]), taskmin, taskmax, tasksum, + tasksum / len(alltasktimes[key]), + tasksum / (nthread * total_t) * 100.0) + print + + # Dead times. + print "# Deadtimes:" + print "# no. : {0:>9s} {1:>9s} {2:>9s} {3:>9s} {4:>9s} {5:>9s}"\ + .format("count", "minimum", "maximum", "sum", "mean", "percent") + alldeadtimes = [] + for i in range(nthread): + deadtimes = [] + last = 0 + for task in tasks[i]: + dt = task[0] - last + deadtimes.append(dt) + last = task[1] + dt = total_t - last + deadtimes.append(dt) + + deadmin = min(deadtimes) + deadmax = max(deadtimes) + deadsum = sum(deadtimes) + print "thread {0:2d}: {1:9d} {2:9.4f} {3:9.4f} {4:9.4f} {5:9.4f} {6:9.2f}"\ + .format(i, len(deadtimes), deadmin, deadmax, deadsum, + deadsum / len(deadtimes), deadsum / total_t * 100.0) + alldeadtimes.extend(deadtimes) + + deadmin = min(alldeadtimes) + deadmax = max(alldeadtimes) + deadsum = sum(alldeadtimes) + print "all : {0:9d} {1:9.4f} {2:9.4f} {3:9.4f} {4:9.4f} {5:9.2f}"\ + .format(len(alldeadtimes), deadmin, deadmax, deadsum, + deadsum / len(alldeadtimes), + deadsum / (nthread * total_t ) * 100.0) + print + + +sys.exit(0)