diff --git a/benchtests/scripts/plot_strings.py b/benchtests/scripts/plot_strings.py
new file mode 100755
index 0000000..34d39b9
--- /dev/null
+++ b/benchtests/scripts/plot_strings.py
@@ -0,0 +1,263 @@
+#!/usr/bin/python3
+# Plot GNU C Library string microbenchmark output.
+# Copyright (C) 2019 Free Software Foundation, Inc.
+# This file is part of the GNU C Library.
+#
+# The GNU C Library 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 2.1 of the License, or (at your option) any later version.
+#
+# The GNU C Library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with the GNU C Library; if not, see
+# <https://www.gnu.org/licenses/>.
+"""Plot string microbenchmark results.
+
+Given a benchmark results file in json format and a benchmark schema file,
+plot either absolute or relative benchmark timings.
+
+Separate figure is generated and saved to a file for each 'results' array
+found in the input file.
+"""
+import matplotlib as mpl
+mpl.use("Agg")      # Remove this line to use (default) GUI backend
+
+import argparse
+from collections import defaultdict
+import json
+import matplotlib.pyplot as plt
+import numpy as np
+import os
+
+try:
+    import jsonschema as validator
+except ImportError:
+    print("Could not find jsonschema module.")
+    raise
+
+# Use pre-selected markers for plotting lines to improve readability
+markers = [".", "s", "x", "o", "^", "|", "*", "1", "+", "D"]
+
+# Benchmark variants for which the x-axis scale should be logarithmic
+log_variants = {"powers of 2"}
+
+
+def gmean(numbers, axis=None):
+    """Compute geometric mean.
+
+    Args:
+        numbers: numbers to compute geometric mean of
+    Return:
+        geometric mean of numbers
+    """
+    a = np.array(numbers, dtype=np.complex)
+    means = a.prod(axis) ** (1.0 / len(a))
+    return np.real(means)
+
+
+def relativeDifference(x, x_reference):
+    """Compute relative difference.
+
+    Args:
+        x: values
+        x_reference: reference (baseline) values
+    Return:
+        relative difference between x and x_reference (in %)
+    """
+    abs_diff = np.subtract(x, x_reference)
+    return np.divide(np.multiply(abs_diff, 100.0), x_reference)
+
+
+def plotRecursive(json_iter, routine, ifuncs, bench_variant, title, outpath,
+                  x_scale):
+    """Plot benchmark timings.
+
+    Args:
+        json_iter: reference to json object
+        routine: benchmarked string routine
+        ifuncs: list of ifuncs tested
+        bench_variant: top-level benchmark variant name
+        title: generated figure's title
+        outpath: output file path (without extension)
+        x_scale: x-axis scale
+    """
+
+    # RECURSIVE CASE:
+    # 'variants' array found, continue recursive search for 'results' array
+    if "variants" in json_iter:
+        for variant in json_iter["variants"]:
+            new_title = "%s%s, " % (title, variant["name"])
+            new_outpath = "%s_%s" % (outpath, variant["name"].replace(" ", "_"))
+            new_x_scale = "log" if variant["name"] in log_variants else x_scale
+
+            plotRecursive(variant, routine, ifuncs, bench_variant, new_title,
+                          new_outpath, new_x_scale)
+        return
+
+    # BASE CASE:
+    # 'results' array found, collect and plot timings
+    domain = []
+    timings = defaultdict(list)
+
+    for result in json_iter["results"]:
+        domain.append(result[args.param])
+        timings[result[args.param]].append(result["timings"])
+
+    domain = np.unique(np.array(domain))
+    averages = []
+
+    # Use geometric mean if there are multple timings for each parameter
+    # value.
+    for parameter in domain:
+        averages.append(gmean(timings[parameter], axis=0))
+
+    averages = np.array(averages).transpose()
+
+    # Choose ifuncs to plot
+    if args.ifuncs:
+        plotted_ifuncs = [x.replace("__", "") for x in args.ifuncs]
+    else:
+        plotted_ifuncs = ifuncs
+
+    plotted_indices = [ifuncs.index(x) for x in plotted_ifuncs]
+
+    # Plot relative timings
+    if args.relative:
+
+        # Choose the baseline ifunc
+        if args.baseline:
+            baseline = args.baseline.replace("__", "")
+        else:
+            baseline = ifuncs[0]
+
+        baseline_index = ifuncs.index(baseline)
+
+        # Compare the timings against baseline
+        codomain = relativeDifference(averages[plotted_indices,:],
+                                      averages[baseline_index])
+
+        # Start plotting relative timings
+        plt.figure()
+        plt.axhspan(-args.threshold, args.threshold, color="lightgray",
+                    alpha=0.3)
+        plt.axhline(0, color="k", linestyle="--", linewidth=0.4)
+        plt.ylabel("Relative timing (in %)")
+        title = "Relative timing comparison against %s\nfor %s benchmark, %s" \
+                % (baseline, bench_variant, title)
+        outpath = os.path.join(args.outdir, "%s_%s%s" % \
+                  (baseline, bench_variant, outpath))
+
+    # Plot absolute timings
+    else:
+        codomain = averages[plotted_indices,:]
+        plt.figure()
+
+        if not args.values:
+            plt.axes().yaxis.set_major_formatter(plt.NullFormatter())
+
+        plt.ylabel("Timing")
+        title = "%s %s benchmark results\n%s" % (routine, bench_variant, title)
+        outpath = os.path.join(args.outdir, "%s_%s%s" \
+                               % (routine, bench_variant, outpath))
+
+
+    # Finish generating Figure
+    plt.xlabel(args.param)
+    plt.xscale(x_scale)
+    plt.title(title)
+
+    if args.grid:
+        plt.grid(color="k", linestyle=":", linewidth=0.5, alpha=0.5)
+
+    for i in range(len(plotted_ifuncs)):
+        plt.plot(domain, codomain[i], marker=markers[i % len(markers)], label=plotted_ifuncs[i])
+
+    plt.legend(loc="best")
+    plt.savefig("%s_%s.%s" % (outpath, x_scale, args.extension),
+                format=args.extension)
+
+    if args.display:
+        plt.show()
+
+    plt.close()
+
+
+def main(args):
+    """Program Entry Point.
+
+    Args:
+      args: command line arguments (excluding program name)
+    """
+
+    schema = None
+
+    with open(args.schema, "r") as f:
+        schema = json.load(f)
+
+    for filename in args.bench:
+        bench = None
+
+        with open(filename, "r") as f:
+            bench = json.load(f)
+
+        validator.validate(bench, schema)
+
+        for function in bench["functions"]:
+            bench_variant = bench["functions"][function]["bench-variant"]
+            x_scale = "log" if args.logarithmic else "linear"
+            ifuncs = bench["functions"][function]["ifuncs"]
+            ifuncs_trimmed = [x.replace("__", "") for x in ifuncs]
+
+            plotRecursive(bench["functions"][function], function,
+                          ifuncs_trimmed, bench_variant, "", "", x_scale)
+
+
+""" main() """
+if __name__ == "__main__":
+
+    parser = argparse.ArgumentParser(description=
+            "Plot string microbenchmark timings",
+            formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+
+    # Required parameter
+    parser.add_argument("bench", nargs="+",
+                        help="benchmark results file(s) in json format")
+
+    # Optional parameters
+    parser.add_argument("-b", "--baseline", type=str,
+                        help="baseline ifunc for relative plot")
+    parser.add_argument("-d", "--display", action="store_true",
+                        help="display figures (requires window manager)")
+    parser.add_argument("-e", "--extension", type=str, default="png",
+                        choices=["png", "pdf", "svg"],
+                        help="output file(s) extension")
+    parser.add_argument("-g", "--grid", action="store_true", help="show grid")
+    parser.add_argument("-i", "--ifuncs", nargs="+", help="ifuncs to plot")
+    parser.add_argument("-l", "--logarithmic", action="store_true",
+                        help="use logarithmic x-axis scale")
+    parser.add_argument("-o", "--outdir", type=str, default=os.getcwd(),
+                        help="output directory")
+    parser.add_argument("-p", "--param", type=str, default="length",
+                        help="varied parameter name")
+    parser.add_argument("-r", "--relative", action="store_true",
+                        help="plot relative timing differences")
+    parser.add_argument("-s", "--schema", type=str,
+                        default=os.path.join(os.path.dirname(
+                        os.path.realpath(__file__)),
+                        "benchout_strings.schema.json"),
+                        help="schema file to validate the result file.")
+    parser.add_argument("-t", "--threshold", type=int, default=5,
+                        help="diference threshold to mark in graph (in %%)")
+    parser.add_argument("-v", "--values", action="store_true",
+                        help="show actual timing values")
+
+    args = parser.parse_args()
+    if (not args.relative and args.baseline) or (args.relative and args.values):
+        raise ValueError("Conflicting command line arguments")
+
+    main(args)
