The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
.gitignore 113
Changes 476
FileHandle.h 414
FileHandle.xs 4110
HACKING 818
INSTALL 10
MANIFEST 2654
META.json 060
META.yml 3329
Makefile.PL 312
NYTProf.h 20
NYTProf.xs 210423
README.md 026
benchmark.pl 1250
bin/flamegraph.pl 0360
bin/nytprofcalls 0222
bin/nytprofcg 20
bin/nytprofcsv 66
bin/nytprofhtml 874
bin/nytprofmerge 737
demo/benchmark.pl 1250
lib/Devel/NYTProf/Apache.pm 20
lib/Devel/NYTProf/Core.pm 31
lib/Devel/NYTProf/Data.pm 613
lib/Devel/NYTProf/FileInfo.pm 11
lib/Devel/NYTProf/ReadStream.pm 06
lib/Devel/NYTProf/Reader.pm 86
lib/Devel/NYTProf/Run.pm 31
lib/Devel/NYTProf/Util.pm 33
lib/Devel/NYTProf.pm 2464
t/22-readstream.t 1130
t/30-util.t 11
t/42-global.t 12
t/lib/NYTProfTest.pm 4389
t/test01.calls 014
t/test02.calls 014
t/test03.calls 08
t/test05.calls 012
t/test06.calls 09
t/test07.calls 01
t/test08.calls 00
t/test09.calls 03
t/test10.calls 02
t/test11.calls 01
t/test12.calls 00
t/test13.calls 09
t/test16.calls 06
t/test17-goto.calls 06
t/test18-goto2.calls 03
t/test20-streval.calls 02
t/test21-streval3.calls 02
t/test22-strevala.calls 06
t/test22-strevala.t 02
t/test23-strevall.calls 00
t/test24-strevalc.calls 01
t/test30-fork-0.calls 011
t/test40pmc.calls 01
t/test50-disable.calls 01
t/test51-enable.calls 03
t/test51-enable.p 02
t/test51-enable.x 02
t/test60-subname.calls 05
t/test61-submerge.calls 03
t/test62-subcaller1.calls 011
t/test70-subexcl.calls 06
t/test70-subexcl.p 11
t/test80-recurs.calls 06
t/test90-strsubref.t 045
68 files changed (This is a version diff) 6761949
@@ -1,11 +1,22 @@
 # /
+FileHandle.c
+FileHandle.o
 MANIFEST.bak
 MYMETA.json
 MYMETA.yml
 Makefile
 Makefile.old
+NYTProf.bs
+NYTProf.c
+NYTProf.o
 blib/
-# /t/
+*.tar.gz
+*.o
+*.c
+*.bs
+*.out
+.*.swp
+nytprof-50-errno.out
 /t/*.new
 /t/*.newp
 /t/*.out
@@ -14,3 +25,4 @@ blib/
 /t/nytprof_test30-fork-*.out.*
 /t/*_outdir
 /t/auto
+pm_to_blib
@@ -1,13 +1,85 @@
 =head1 NAME
 
-Changes - List of significant changes to Devel::NYTProf
+Changes - History of significant changes in Devel::NYTProf
 
 =cut
 
-TODO: study http://www.postgresql.org/docs/9.2/static/pgtesttiming.html
-and reference in our docs.
+=head2 Changes in Devel::NYTProf 5.06 - 12th Sept 2013
 
-http://stackoverflow.com/questions/4132270/how-to-ignore-some-subroutine-calls-in-nytprof-reporting/14085984#14085984
+  Fixed for perl 5.19.4. RT#88288 thanks to sprout.
+  Fixed test for change in perl 5.18.x error message text.
+  Fixed to no longer open a file when start=no. RT#86497/RT#87404.
+  Fixed compiler warnings. RT#86728 thanks to Alexander Bluhm.
+
+  Document that Devel::NYTProf needs to be loaded as early as possible
+    even when using start=no. PR#10 thanks to moritz.
+  Removed unused keyword $Id$. PR#9 thanks to dsteinbrunner.
+  Removed old benchmark.pl files. RT#86704.
+  Corrected assorted typos. PR#8 thanks to dsteinbrunner.
+  Added meta-spec to META_MERGE. PR#12 thanks to dsteinbrunner.
+
+=head2 Changes in Devel::NYTProf 5.05 - 2nd July 2013
+
+  Fixed crash on "Can't use string as a subroutine ref" error,
+    and probably other die-at-pp_entersub cases,
+    with thanks to Zefram. RT#86638
+  Fixed crash with libcexit=1, thanks to Zefram. RT#86548
+
+=head2 Changes in Devel::NYTProf 5.04 - 20th June 2013
+
+  Allow negative times in tests for systems with unstable clocks
+    thanks to Gisle Aas, RT#85556.
+  Added libcexit=1 option thanks to Zefram, RT#75912.
+  Added documentation for endatexit and libcexit options.
+  Added documentation for nytprofhtml --minimal
+    thanks to Mike Doherty, RT#86039.
+
+=head2 Changes in Devel::NYTProf 5.03 - 20th May 2013
+
+  Fix windows to use flamegraph.bat [Christian Walde]
+  Generates META.yml which mentions github repo [Christian Walde]
+  Add meta robots noindex to html pages [Tokuhiro Matsuno]
+
+=head2 Changes in Devel::NYTProf 5.02 - 21st April 2013
+
+  Fix Windows build, properly RT#84738.
+
+=head2 Changes in Devel::NYTProf 5.01 - 19th April 2013
+
+  Fix Windows build RT#84738.
+
+  Can't rely on #!-line to always work, PR#3
+    thanks to Gisle Aas.
+
+  Avoid triggering "gcc internal compiler error" PR#4
+    thanks to Gisle Aas.
+
+=head2 Changes in Devel::NYTProf 5.00 - 8th April 2013
+
+  Added subroutine entry and return event stream,
+    controlled via the calls=N option. Default calls=1.
+
+  Added nytprofcalls command to process the call event stream to
+    generate timings for distinct call stacks (experimental).
+
+  Added Flame Graph visualization SVG using the call stack data.
+
+  Changed blocks=N option to be 0 (disabled) by default.
+
+  Fixed test for perl 5.17+ hash randomization.
+  Fixed nytprofhtml for Windows thanks to Jan Dubois. PR#2
+  Fixed assorted nits thanks to Steve Peters. PR#1
+  Deprecated nytprofcsv - speak up if you use it!
+  No longer warn about $&, $` and $' being slow if $] >= 5.017008.
+
+=head2 Changes in Devel::NYTProf 4.25 - 6th Feb 2013
+
+  Fix u2time clock (ie Time::HiRes, used by Windows)
+
+=head2 Changes in Devel::NYTProf 4.24 - 3rd Feb 2013
+
+  Clarify sigexit option docs.
+  Loosen test timing constraints (for slow cpantester VMs).
 
 =head2 Changes in Devel::NYTProf 4.23 - 31st Dec 2012
 
@@ -12,8 +12,6 @@
  * Steve Peters, steve at fisharerojo.org
  *
  * ************************************************************************
- * $Id$
- * ************************************************************************
  */
 
 /* Arguably this header is naughty, as it's not self contained, because it
@@ -45,6 +43,7 @@ const char *NYTP_type_of_offset(NYTP_file file);
 
 #define NYTP_TAG_NO_TAG          '\0'   /* Used as a flag to mean "no tag" */
 #define NYTP_TAG_ATTRIBUTE       ':'    /* :name=value\n */
+#define NYTP_TAG_OPTION          '!'    /* !name=value\n */
 #define NYTP_TAG_COMMENT         '#'    /* till newline */
 #define NYTP_TAG_TIME_BLOCK      '*'
 #define NYTP_TAG_TIME_LINE       '+'
@@ -58,12 +57,15 @@ const char *NYTP_type_of_offset(NYTP_file file);
 #define NYTP_TAG_STRING          '\'' 
 #define NYTP_TAG_STRING_UTF8     '"' 
 #define NYTP_TAG_START_DEFLATE   'z' 
+#define NYTP_TAG_SUB_ENTRY       '>'
+#define NYTP_TAG_SUB_RETURN      '<'
 /* also add new items to nytp_tax_index below */
 
-typedef enum {
+typedef enum {      /* XXX keep in sync with various *_callback strucures */
     nytp_no_tag,
     nytp_version,   /* Not actually a tag, but needed by the perl callback */
     nytp_attribute,
+    nytp_option,
     nytp_comment,
     nytp_time_block,
     nytp_time_line,
@@ -77,7 +79,9 @@ typedef enum {
     nytp_string,
     nytp_string_utf8,
     nytp_start_deflate,
-    nytp_tag_max
+    nytp_sub_entry,
+    nytp_sub_return,
+    nytp_tag_max /* keep last */
 } nytp_tax_index;
 
 void NYTProf_croak_if_not_stdio(NYTP_file file, const char *function);
@@ -93,6 +97,9 @@ size_t NYTP_write_attribute_unsigned(NYTP_file ofile, const char *key,
                                      size_t key_len, unsigned long value);
 size_t NYTP_write_attribute_nv(NYTP_file ofile, const char *key,
                                      size_t key_len, NV value);
+size_t NYTP_write_option_pv(NYTP_file ofile, const char *key,
+                                    const char *value, size_t value_len);
+size_t NYTP_write_option_iv(NYTP_file ofile, const char *key, IV value);
 size_t NYTP_start_deflate_write_tag_comment(NYTP_file ofile, int compression_level);
 size_t NYTP_write_process_start(NYTP_file ofile, U32 pid, U32 ppid, NV time_of_day);
 size_t NYTP_write_process_end(NYTP_file ofile, U32 pid, NV time_of_day);
@@ -113,6 +120,9 @@ size_t NYTP_write_sub_callers(NYTP_file ofile, U32 fid, U32 line,
 size_t NYTP_write_src_line(NYTP_file ofile, U32 fid,
                         U32 line, const char *text, I32 text_len);
 size_t NYTP_write_discount(NYTP_file ofile);
+size_t NYTP_write_call_entry(NYTP_file ofile, U32 caller_fid, U32 caller_line);
+size_t NYTP_write_call_return(NYTP_file ofile, U32 prof_depth, const char *called_subname_pv,
+                        NV incl_subr_ticks, NV excl_subr_ticks);
 
 
 /* XXX
@@ -738,7 +738,7 @@ read_u32(NYTP_file ifile)
             newint = d & 0xF;
             length = 3;
         }
-        else if (d == 0xFF) {                    /* = 32 bits */
+        else { /* d == 0xFF */                   /* = 32 bits */
             newint = 0;
             length = 4;
         }
@@ -846,15 +846,15 @@ NYTP_write_comment(NYTP_file ofile, const char *format, ...) {
     return retval + 2;
 }
 
-size_t
-NYTP_write_attribute_string(NYTP_file ofile,
+static size_t
+NYTP_write_plain_kv(NYTP_file ofile, const char prefix,
                             const char *key, size_t key_len,
                             const char *value, size_t value_len)
 {
     size_t total;
     size_t retval;
 
-    total = retval = NYTP_write(ofile, ":", 1);
+    total = retval = NYTP_write(ofile, &prefix, 1);
     if (retval != 1)
         return retval;
 
@@ -877,6 +877,14 @@ NYTP_write_attribute_string(NYTP_file ofile,
     return total;
 }
 
+size_t
+NYTP_write_attribute_string(NYTP_file ofile,
+                            const char *key, size_t key_len,
+                            const char *value, size_t value_len)
+{
+    return NYTP_write_plain_kv(ofile, ':', key, key_len, value, value_len);
+}
+
 #ifndef CHAR_BIT
 #  define CHAR_BIT          8
 #endif
@@ -914,6 +922,28 @@ NYTP_write_attribute_nv(NYTP_file ofile, const char *key,
     return NYTP_write_attribute_string(ofile, key, key_len, buffer, len);
 }
 
+/* options */
+
+size_t
+NYTP_write_option_pv(NYTP_file ofile,
+                    const char *key,
+                    const char *value, size_t value_len)
+{
+    return NYTP_write_plain_kv(ofile, '!', key, strlen(key), value, value_len);
+}
+
+size_t
+NYTP_write_option_iv(NYTP_file ofile, const char *key, IV value)
+{
+    /* 3: 1 for rounding errors, 1 for the sign, 1 for the '\0'  */
+    char buffer[(int)(sizeof (IV) * CHAR_BIT * LOG_2_OVER_LOG_10 + 3)];
+    const size_t len = my_snprintf(buffer, sizeof(buffer), "%ld", value);
+
+    return NYTP_write_option_pv(ofile, key, buffer, len);
+}
+
+/* other */
+
 #ifdef HAS_ZLIB
 
 size_t
@@ -1093,6 +1123,53 @@ NYTP_write_time_line(NYTP_file ofile, I32 elapsed, U32 overflow,
     return write_time_common(ofile, NYTP_TAG_TIME_LINE, elapsed, overflow, fid, line);
 }
 
+
+size_t
+NYTP_write_call_entry(NYTP_file ofile, U32 caller_fid, U32 caller_line)
+{
+    size_t total;
+    size_t retval;
+
+    total = retval = output_tag_u32(ofile, NYTP_TAG_SUB_ENTRY, caller_fid);
+    if (retval < 1)
+        return retval;
+
+    total += retval = output_u32(ofile, caller_line);
+    if (retval < 1)
+        return retval;
+
+    return total;
+}
+
+size_t
+NYTP_write_call_return(NYTP_file ofile, U32 prof_depth, const char *called_subname_pv,
+    NV incl_subr_ticks, NV excl_subr_ticks)
+{
+    size_t total;
+    size_t retval;
+
+    total = retval = output_tag_u32(ofile, NYTP_TAG_SUB_RETURN, prof_depth);
+    if (retval < 1)
+        return retval;
+
+    total += retval = output_nv(ofile, incl_subr_ticks);
+    if (retval < 1)
+        return retval;
+
+    total += retval = output_nv(ofile, excl_subr_ticks);
+    if (retval < 1)
+        return retval;
+
+    if (!called_subname_pv)
+        called_subname_pv = "(null)";
+    total += retval = output_str(ofile, called_subname_pv, strlen(called_subname_pv));
+    if (retval < 1)
+        return retval;
+
+    return total;
+}
+
+
 size_t
 NYTP_write_sub_info(NYTP_file ofile, U32 fid,
                     const char *name, I32 len,
@@ -1289,6 +1366,21 @@ SV *value
         RETVAL
 
 size_t
+NYTP_write_option(handle, key, value)
+NYTP_file handle
+SV *key
+SV *value
+    PREINIT:
+        STRLEN key_len;
+        const char *const key_p = SvPVbyte(key, key_len);
+        STRLEN value_len;
+        const char *const value_p = SvPVbyte(value, value_len);
+    CODE:
+        RETVAL = NYTP_write_option_pv(handle, key_p, value_p, value_len);
+    OUTPUT:
+        RETVAL
+
+size_t
 NYTP_write_process_start(handle, pid, ppid, time_of_day)
 NYTP_file handle
 U32 pid
@@ -1340,6 +1432,20 @@ U32 fid
 U32 line
 
 size_t
+NYTP_write_call_entry(handle, caller_fid, caller_line)
+NYTP_file handle
+U32 caller_fid
+U32 caller_line
+
+size_t
+NYTP_write_call_return(handle, prof_depth, called_subname_pv, incl_subr_ticks, excl_subr_ticks)
+NYTP_file handle
+U32 prof_depth
+const char *called_subname_pv
+NV incl_subr_ticks
+NV excl_subr_ticks
+
+size_t
 NYTP_write_sub_info(handle, fid, name, first_line, last_line)
 NYTP_file handle
 U32 fid
@@ -1,5 +1,4 @@
 # vim: ts=8 sw=2 sts=0 noexpandtab:
-# $Id$
 
 HACKING Devel::NYTProf
 ======================
@@ -357,11 +356,22 @@ We need a start=runtime option to start at the _end_ of any INIT subs.
 (The current start=init option is the closest we have but it's not very
 useful if lots of other work is done in INIT blocks.)
 
-We need an option to discount the time spent in CORE:accept so that time
-pure-perl webservers spend waiting for the next request doesn't distort the
-profile. Possibly straight-forward to implement. The trick is to add the time
-spent in the sub to the statement profiler overhead time, thus getting it
-subtracted from the higher level sub time. Maybe.
+We need an option to discount the time spent in specific subs, like we do for
+CORE:accept so that time pure-perl webservers spend waiting for the next
+request doesn't distort the profile. Probably just needs a hash lookup.
+http://stackoverflow.com/questions/4132270/how-to-ignore-some-subroutine-calls-in-nytprof-reporting/14085984#14085984
+
+Remove nytprofcsv
+
+Add "goto skip_sub_profile;" to this?: (and/or figure out why already_counted++ isn't enough)
+    /* catch profile_subs being turned off by disable_profile call */
+    if (!profile_subs)
+        subr_entry->already_counted++;
+
+study http://www.postgresql.org/docs/9.2/static/pgtesttiming.html
+See pg contrib/pg_test_timing/pg_test_timing.c
+
+Show /sys/devices/system/clocksource/clocksource0/available_clocksource
+and /sys/devices/system/clocksource/clocksource0/current_clocksource
+in the Makefile.PL and test output, if present
 
-Generate Flame Graphs from the subroutine profiler data:
-http://dtrace.org/blogs/brendan/2011/12/16/flame-graphs/
@@ -1,5 +1,4 @@
 # vim: ts=8 sw=2 sts=0 noexpandtab:
-# $Id$
 
 Devel::NYTProf Installation Notes
 =================================
@@ -2,24 +2,30 @@
 .gitignore
 .indent.pro
 .perltidyrc
-benchmark.pl
+Changes
+FileHandle.h
+FileHandle.xs
+HACKING
+INSTALL
+MANIFEST			This list of files
+Makefile.PL
+MemoryProfiling.pod
+NYTProf.h
+NYTProf.xs
+README.md
+bin/flamegraph.pl
+bin/nytprofcalls
 bin/nytprofcg
 bin/nytprofcsv
 bin/nytprofhtml
 bin/nytprofmerge
-Changes
 demo/1m_stmts.pl
-demo/benchmark.pl
+demo/README
 demo/closure.pl
 demo/cpucache.pl
 demo/demo-code.pl
 demo/demo-run.pl
 demo/exclusive-sub-time.pl
-demo/README
-FileHandle.h
-FileHandle.xs
-HACKING
-INSTALL
 lib/Devel/NYTProf.pm
 lib/Devel/NYTProf/Apache.pm
 lib/Devel/NYTProf/Constants.pm
@@ -27,9 +33,17 @@ lib/Devel/NYTProf/Core.pm
 lib/Devel/NYTProf/Data.pm
 lib/Devel/NYTProf/FileHandle.pm
 lib/Devel/NYTProf/FileInfo.pm
+lib/Devel/NYTProf/ReadStream.pm
+lib/Devel/NYTProf/Reader.pm
+lib/Devel/NYTProf/Run.pm
+lib/Devel/NYTProf/SubCallInfo.pm
+lib/Devel/NYTProf/SubInfo.pm
+lib/Devel/NYTProf/Test.pm
+lib/Devel/NYTProf/Util.pm
 lib/Devel/NYTProf/js/asc.png
 lib/Devel/NYTProf/js/bg.png
 lib/Devel/NYTProf/js/desc.png
+lib/Devel/NYTProf/js/jit/Treemap.css
 lib/Devel/NYTProf/js/jit/gradient-cushion1.png
 lib/Devel/NYTProf/js/jit/gradient.png
 lib/Devel/NYTProf/js/jit/gradient20.png
@@ -38,27 +52,10 @@ lib/Devel/NYTProf/js/jit/gradient40.png
 lib/Devel/NYTProf/js/jit/gradient50.png
 lib/Devel/NYTProf/js/jit/jit-yc.js
 lib/Devel/NYTProf/js/jit/jit.js
-lib/Devel/NYTProf/js/jit/Treemap.css
 lib/Devel/NYTProf/js/jquery-min.js
 lib/Devel/NYTProf/js/jquery-tablesorter-min.js
 lib/Devel/NYTProf/js/style-tablesorter.css
-lib/Devel/NYTProf/Reader.pm
-lib/Devel/NYTProf/ReadStream.pm
-lib/Devel/NYTProf/Run.pm
-lib/Devel/NYTProf/SubCallInfo.pm
-lib/Devel/NYTProf/SubInfo.pm
-lib/Devel/NYTProf/Test.pm
-lib/Devel/NYTProf/Util.pm
-Makefile
-Makefile.PL
-MANIFEST			This list of files
-MemoryProfiling.pod
-MYMETA.json
-MYMETA.yml
-NYTProf.h
-NYTProf.xs
 ppport.h
-README
 slowops.h
 t/00-load.t
 t/10-run.t
@@ -79,51 +76,63 @@ t/90-pod.t
 t/91-pod_coverage.t
 t/92-file_port.t
 t/lib/NYTProfTest.pm
+t/test01.calls
 t/test01.p
 t/test01.rdt
 t/test01.t
 t/test01.x
+t/test02.calls
 t/test02.p
 t/test02.rdt
 t/test02.t
 t/test02.x
+t/test03.calls
 t/test03.p
 t/test03.rdt
 t/test03.t
 t/test03.x
+t/test05.calls
 t/test05.p
 t/test05.rdt
 t/test05.t
 t/test05.x
+t/test06.calls
 t/test06.p
 t/test06.rdt
 t/test06.t
 t/test06.x
+t/test07.calls
 t/test07.p
 t/test07.rdt
 t/test07.t
 t/test07.x
+t/test08.calls
 t/test08.p
 t/test08.rdt
 t/test08.t
 t/test08.x
+t/test09.calls
 t/test09.p
 t/test09.rdt
 t/test09.t
 t/test09.x
+t/test10.calls
 t/test10.p
 t/test10.rdt
 t/test10.t
 t/test10.x
+t/test11.calls
 t/test11.p
 t/test11.rdt
 t/test11.t
 t/test11.x
+t/test12.calls
 t/test12.p
 t/test12.pl
 t/test12.rdt
 t/test12.t
 t/test12.x
+t/test13.calls
 t/test13.p
 t/test13.rdt
 t/test13.t
@@ -134,40 +143,50 @@ t/test14.pm_x
 t/test14.rdt
 t/test14.t
 t/test14.x
+t/test16.calls
 t/test16.p
 t/test16.rdt
 t/test16.t
 t/test16.x
+t/test17-goto.calls
 t/test17-goto.p
 t/test17-goto.rdt
 t/test17-goto.t
+t/test18-goto2.calls
 t/test18-goto2.p
 t/test18-goto2.pm
 t/test18-goto2.t
+t/test20-streval.calls
 t/test20-streval.p
 t/test20-streval.rdt
 t/test20-streval.t
 t/test20-streval.x
+t/test21-streval3.calls
 t/test21-streval3.p
 t/test21-streval3.rdt
 t/test21-streval3.t
 t/test21-streval3.x
+t/test22-strevala.calls
 t/test22-strevala.p
 t/test22-strevala.rdt
 t/test22-strevala.t
+t/test23-strevall.calls
 t/test23-strevall.p
 t/test23-strevall.rdt
 t/test23-strevall.t
+t/test24-strevalc.calls
 t/test24-strevalc.p
 t/test24-strevalc.rdt
 t/test24-strevalc.t
 t/test25-strevalb.t
+t/test30-fork-0.calls
 t/test30-fork-0.p
 t/test30-fork-0.rdt
 t/test30-fork-0.t
 t/test30-fork-0.x
 t/test30-fork-1.rdt
 t/test30-fork-1.x
+t/test40pmc.calls
 t/test40pmc.p
 t/test40pmc.pm
 t/test40pmc.pm_x
@@ -175,30 +194,38 @@ t/test40pmc.pmc
 t/test40pmc.rdt
 t/test40pmc.t
 t/test40pmc.x
+t/test50-disable.calls
 t/test50-disable.p
 t/test50-disable.rdt
 t/test50-disable.t
 t/test50-disable.x
+t/test51-enable.calls
 t/test51-enable.p
 t/test51-enable.rdt
 t/test51-enable.t
 t/test51-enable.x
+t/test60-subname.calls
 t/test60-subname.p
 t/test60-subname.rdt
 t/test60-subname.t
+t/test61-submerge.calls
 t/test61-submerge.p
 t/test61-submerge.rdt
 t/test61-submerge.t
+t/test62-subcaller1.calls
 t/test62-subcaller1.p
 t/test62-subcaller1.rdt
 t/test62-subcaller1.t
+t/test70-subexcl.calls
 t/test70-subexcl.p
 t/test70-subexcl.t
+t/test80-recurs.calls
 t/test80-recurs.p
 t/test80-recurs.rdt
 t/test80-recurs.t
 t/test81-swash.t
 t/test82-version.t
+t/test90-strsubref.t
 t/zzz.t
 typemap
 xt/61-cputime.t
@@ -209,4 +236,5 @@ xt/test45-overload.p
 xt/test71-while.p
 xt/test82-stress.t
 xt/test90-stress.p
-META.yml                                 Module meta-data (added by MakeMaker)
+META.yml                                 Module YAML meta-data (added by MakeMaker)
+META.json                                Module JSON meta-data (added by MakeMaker)
@@ -0,0 +1,60 @@
+{
+   "abstract" : "Powerful fast feature-rich Perl source code profiler",
+   "author" : [
+      "Tim Bunce <timb@cpan.org>"
+   ],
+   "dynamic_config" : 1,
+   "generated_by" : "ExtUtils::MakeMaker version 6.68, CPAN::Meta::Converter version 2.131560",
+   "license" : [
+      "perl_5"
+   ],
+   "meta-spec" : {
+      "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec",
+      "version" : "2"
+   },
+   "name" : "Devel-NYTProf",
+   "no_index" : {
+      "directory" : [
+         "t",
+         "inc"
+      ],
+      "package" : [
+         "SVG"
+      ]
+   },
+   "prereqs" : {
+      "build" : {
+         "requires" : {
+            "ExtUtils::MakeMaker" : "0"
+         }
+      },
+      "runtime" : {
+         "requires" : {
+            "Getopt::Long" : "0",
+            "JSON::Any" : "0",
+            "List::Util" : "0",
+            "Test::Differences" : "0.60",
+            "Test::More" : "0.84",
+            "XSLoader" : "0"
+         }
+      }
+   },
+   "release_status" : "stable",
+   "resources" : {
+      "bugtracker" : {
+         "mailto" : "bug-devel-nytprof@rt.cpan.org",
+         "web" : "https://github.com/timbunce/devel-nytprof/issues"
+      },
+      "homepage" : "https://code.google.com/p/perl-devel-nytprof/",
+      "license" : [
+         "http://dev.perl.org/licenses/"
+      ],
+      "repository" : {
+         "type" : "git",
+         "url" : "git://github.com/timbunce/devel-nytprof.git",
+         "web" : "https://github.com/timbunce/devel-nytprof"
+      },
+      "x_MailingList" : "http://groups.google.com/group/develnytprof-dev"
+   },
+   "version" : "5.06"
+}
@@ -1,37 +1,33 @@
---- #YAML:1.0
-name:               Devel-NYTProf
-version:            4.23
-abstract:           Powerful fast feature-rich Perl source code profiler
+---
+abstract: 'Powerful fast feature-rich Perl source code profiler'
 author:
-    - Tim Bunce <timb@cpan.org>
-license:            perl
-distribution_type:  module
-configure_requires:
-    ExtUtils::MakeMaker:  0
+  - 'Tim Bunce <timb@cpan.org>'
 build_requires:
-    ExtUtils::MakeMaker:  0
+  ExtUtils::MakeMaker: 0
+dynamic_config: 1
+generated_by: 'ExtUtils::MakeMaker version 6.68, CPAN::Meta::Converter version 2.131560'
+license: perl
+meta-spec:
+  url: http://module-build.sourceforge.net/META-spec-v1.4.html
+  version: 1.4
+name: Devel-NYTProf
+no_index:
+  directory:
+    - t
+    - inc
+  package:
+    - SVG
 requires:
-    Getopt::Long:  0
-    JSON::Any:     0
-    List::Util:    0
-    Test::More:    0.84
-    XSLoader:      0
+  Getopt::Long: 0
+  JSON::Any: 0
+  List::Util: 0
+  Test::Differences: 0.60
+  Test::More: 0.84
+  XSLoader: 0
 resources:
-    bugtracker:
-        mailto:  bug-devel-nytprof@rt.cpan.org
-        web:     https://github.com/timbunce/devel-nytprof/issues
-    homepage:     https://code.google.com/p/perl-devel-nytprof/
-    license:      http://dev.perl.org/licenses/
-    MailingList:  http://groups.google.com/group/develnytprof-dev
-    repository:
-        type:  git
-        url:   git://github.com/timbunce/devel-nytprof.git
-        web:   https://github.com/timbunce/devel-nytprof
-no_index:
-    directory:
-        - t
-        - inc
-generated_by:       ExtUtils::MakeMaker version 6.56
-meta-spec:
-    url:      http://module-build.sourceforge.net/META-spec-v1.4.html
-    version:  1.4
+  MailingList: http://groups.google.com/group/develnytprof-dev
+  bugtracker: https://github.com/timbunce/devel-nytprof/issues
+  homepage: https://code.google.com/p/perl-devel-nytprof/
+  license: http://dev.perl.org/licenses/
+  repository: git://github.com/timbunce/devel-nytprof.git
+version: 5.06
@@ -7,8 +7,6 @@
 ## http://search.cpan.org/dist/Devel-NYTProf/
 ##
 ###########################################################
-# $Id$
-###########################################################
 
 use 5.008001;
 use warnings;
@@ -32,6 +30,7 @@ if ($ENV{PERL_CORE}) {
         'bin/nytprofhtml' => '$(INST_MAN1DIR)/nytprofhtml.1',
         'bin/nytprofmerge'=> '$(INST_MAN1DIR)/nytprofmerge.1',
         'bin/nytprofcsv'  => '$(INST_MAN1DIR)/nytprofcsv.1',
+        'bin/nytprofcalls'=> '$(INST_MAN1DIR)/nytprofcalls.1',
         'bin/nytprofcg'   => '$(INST_MAN1DIR)/nytprofcg.1',
     } );
 }
@@ -109,6 +108,10 @@ $mm_opts{CCFLAGS}  = "-pg"  if $opt_pg;
 
 if( $ExtUtils::MakeMaker::VERSION >= 6.45 ) {
     $mm_opts{META_MERGE} = {
+    	"meta-spec" => { version => 2 },
+	no_index => {
+	    package => [ 'SVG' ], # in bin/flamegraph.pl
+	},
         resources => {
             license     => 'http://dev.perl.org/licenses/',
             homepage    => 'https://code.google.com/p/perl-devel-nytprof/',
@@ -151,13 +154,14 @@ WriteMakefile(
     PREREQ_PM => {
         'List::Util'   => 0,
         'Test::More'   => '0.84',
+        'Test::Differences' => '0.60',
         'XSLoader'     => 0,
         'Getopt::Long' => 0,
         'JSON::Any'    => 0,
     },
     LIBS      => [join ' ', @libs],
     OBJECT    => q/$(O_FILES)/,
-    EXE_FILES => ['bin/nytprofhtml', 'bin/nytprofcsv', 'bin/nytprofcg', 'bin/nytprofmerge'],
+    EXE_FILES => ['bin/nytprofhtml', 'bin/flamegraph.pl', 'bin/nytprofcsv', 'bin/nytprofcalls', 'bin/nytprofcg', 'bin/nytprofmerge'],
     @man,
     INC   => $INCLUDE,
     clean => {
@@ -173,6 +177,11 @@ WriteMakefile(
         COMPRESS     => 'gzip -v9',
         SUFFIX       => 'gz',
     },
+    META_MERGE => {
+      resources => {
+        repository => 'https://github.com/timbunce/devel-nytprof',
+      },
+    },
     %mm_opts,
 );
 
@@ -12,7 +12,5 @@
  * Steve Peters, steve at fisharerojo.org
  *
  * ************************************************************************
- * $Id$
- * ************************************************************************
  */
 
@@ -13,8 +13,6 @@
  * Steve Peters, steve at fisharerojo.org
  *
  * ************************************************************************
- * $Id$
- * ************************************************************************
  */
 #ifndef WIN32
 #define PERL_NO_GET_CONTEXT                       /* we want efficiency */
@@ -110,7 +108,7 @@ Perl_gv_fetchfile_flags(pTHX_ const char *const name, const STRLEN namelen, cons
 #define ZLIB_VERSION "0"
 #endif
 
-#define NYTP_FILE_MAJOR_VERSION 4
+#define NYTP_FILE_MAJOR_VERSION 5
 #define NYTP_FILE_MINOR_VERSION 0
 
 #define NYTP_START_NO            0
@@ -180,40 +178,53 @@ Perl_gv_fetchfile_flags(pTHX_ const char *const name, const STRLEN namelen, cons
 #define NYTP_SCi_CALLING_SUB     7   /* name of calling sub */
 #define NYTP_SCi_elements        8   /* highest index, plus 1 */
 
-#define MAX_HASH_SIZE 512
-
-static unsigned int next_fid = 1;         /* 0 is reserved */
 
 /* we're not thread-safe (or even multiplicity safe) yet, so detect and bail */
 #ifdef MULTIPLICITY
 static PerlInterpreter *orig_my_perl;
 #endif
 
-typedef struct hash_entry
-{
+
+#define MAX_HASH_SIZE 512
+
+typedef struct hash_entry Hash_entry;
+
+struct hash_entry {
     unsigned int id;
-    void* next_entry;
     char* key;
-    unsigned int key_len;
+    int key_len;
+    Hash_entry* next_entry;
+    Hash_entry* next_inserted;  /* linked list in insertion order */
+};
+
+typedef struct hash_table {
+    Hash_entry** table;
+    char *name;
+    unsigned int size;
+    unsigned int entry_struct_size;
+    Hash_entry* first_inserted;
+    Hash_entry* prior_inserted; /* = last_inserted before the last insertion */
+    Hash_entry* last_inserted;
+    unsigned int next_id;       /* starts at 1, 0 is reserved */
+} Hash_table;
+
+typedef struct {
+    Hash_entry he;
     unsigned int eval_fid;
     unsigned int eval_line_num;
     unsigned int file_size;
     unsigned int file_mtime;
     unsigned int fid_flags;
     char *key_abs;
-    void* next_inserted;                          /* linked list in insertion order */
     /* update autosplit logic in get_file_id if fields are added or changed */
-} Hash_entry;
+} fid_hash_entry;
 
-typedef struct hash_table
-{
-    Hash_entry** table;
-    unsigned int size;
-    Hash_entry* first_inserted;
-    Hash_entry* last_inserted;
-} Hash_table;
+static Hash_table fidhash = { NULL, "fid", MAX_HASH_SIZE, sizeof(fid_hash_entry), NULL, NULL, NULL, 1 };
 
-static Hash_table hashtable = { NULL, MAX_HASH_SIZE, NULL, NULL };
+typedef struct {
+    Hash_entry he;
+} str_hash_entry;
+static Hash_table strhash = { NULL, "str", MAX_HASH_SIZE, sizeof(str_hash_entry), NULL, NULL, NULL, 1 };
 /* END Hash table definitions */
 
 
@@ -225,48 +236,64 @@ static char PROF_output_file[MAXPATHLEN+1] = "nytprof.out";
 static unsigned int profile_opts = NYTP_OPTf_OPTIMIZE | NYTP_OPTf_SAVESRC;
 static int profile_start = NYTP_START_BEGIN;      /* when to start profiling */
 
-struct NYTP_int_options_t {
-  const char *option_name;
-  int option_value;
+struct NYTP_options_t {
+    const char *option_name;
+    IV    option_iv;
+    char *option_pv;    /* strdup'd */
 };
 
 /* XXX boolean options should be moved into profile_opts */
-static struct NYTP_int_options_t options[] = {
-#define profile_usecputime options[0].option_value
-    { "usecputime", 0 },
-#define profile_subs options[1].option_value
-    { "subs", 1 },                                /* subroutine times */
-#define profile_blocks options[2].option_value
-    { "blocks", 1 },                              /* block and sub *exclusive* times */
-#define profile_leave options[3].option_value
-    { "leave", 1 },                               /* correct block end timing */
-#define embed_fid_line options[4].option_value
-    { "expand", 0 },
-#define trace_level options[5].option_value
-    { "trace", 0 },
-#define opt_use_db_sub options[6].option_value
-    { "use_db_sub", 0 },
-#define compression_level options[7].option_value
-    { "compress", default_compression_level },
-#define profile_clock options[8].option_value
-    { "clock", -1 },
-#define profile_stmts options[9].option_value
-    { "stmts", 1 },                              /* statement exclusive times */
-#define profile_slowops options[10].option_value
-    { "slowops", 2 },                            /* slow opcodes, typically system calls */
-#define profile_findcaller options[11].option_value
-    { "findcaller", 0 },                         /* find sub caller instead of trusting outer */
-#define profile_forkdepth options[12].option_value
-    { "forkdepth", -1 },                         /* how many generations of kids to profile */
-#define opt_perldb options[13].option_value
-    { "perldb", 0 },                             /* force certain PL_perldb value */
-#define opt_nameevals options[14].option_value
-    { "nameevals", 1 },                          /* change $^P 0x100 bit */
-#define opt_nameanonsubs options[15].option_value
-    { "nameanonsubs", 1 },                       /* change $^P 0x200 bit */
-#define opt_evals options[16].option_value
-    { "evals", 0 }                               /* handling of string evals - TBD XXX */
+static struct NYTP_options_t options[] = {
+#define profile_usecputime options[0].option_iv
+    { "usecputime", 0, NULL },
+#define profile_subs options[1].option_iv
+    { "subs", 1, NULL },                                /* subroutine times */
+#define profile_blocks options[2].option_iv
+    { "blocks", 0, NULL },                              /* block and sub *exclusive* times */
+#define profile_leave options[3].option_iv
+    { "leave", 1, NULL },                               /* correct block end timing */
+#define embed_fid_line options[4].option_iv
+    { "expand", 0, NULL },
+#define trace_level options[5].option_iv
+    { "trace", 0, NULL },
+#define opt_use_db_sub options[6].option_iv
+    { "use_db_sub", 0, NULL },
+#define compression_level options[7].option_iv
+    { "compress", default_compression_level, NULL },
+#define profile_clock options[8].option_iv
+    { "clock", -1, NULL },
+#define profile_stmts options[9].option_iv
+    { "stmts", 1, NULL },                              /* statement exclusive times */
+#define profile_slowops options[10].option_iv
+    { "slowops", 2, NULL },                            /* slow opcodes, typically system calls */
+#define profile_findcaller options[11].option_iv
+    { "findcaller", 0, NULL },                         /* find sub caller instead of trusting outer */
+#define profile_forkdepth options[12].option_iv
+    { "forkdepth", -1, NULL },                         /* how many generations of kids to profile */
+#define opt_perldb options[13].option_iv
+    { "perldb", 0, NULL },                             /* force certain PL_perldb value */
+#define opt_nameevals options[14].option_iv
+    { "nameevals", 1, NULL },                          /* change $^P 0x100 bit */
+#define opt_nameanonsubs options[15].option_iv
+    { "nameanonsubs", 1, NULL },                       /* change $^P 0x200 bit */
+#define opt_calls options[16].option_iv
+    { "calls", 1, NULL },                              /* output call/return event stream */
+#define opt_evals options[17].option_iv
+    { "evals", 0, NULL }                               /* handling of string evals - TBD XXX */
 };
+/* XXX TODO: add these to options:
+    if (strEQ(option, "file")) {
+        strncpy(PROF_output_file, value, MAXPATHLEN);
+    else if (strEQ(option, "log")) {
+    else if (strEQ(option, "start")) {
+    else if (strEQ(option, "addpid")) {
+    else if (strEQ(option, "optimize") || strEQ(option, "optimise")) {
+    else if (strEQ(option, "savesrc")) {
+    else if (strEQ(option, "endatexit")) {
+    else if (strEQ(option, "libcexit")) {
+and write the options to the stream when profiling starts.
+*/
+
 
 /* time tracking */
 
@@ -319,9 +346,9 @@ static int (*u2time)(pTHX_ UV *) = 0;
 typedef UV time_of_day_t[2];
 #  define TICKS_PER_SEC 1000000                 /* 1 million */
 #  define get_time_of_day(into) (*u2time)(aTHX_ into)
-#  define get_ticks_between(s, e, ticks, overflow)  STMT_START { \
+#  define get_ticks_between(typ, s, e, ticks, overflow)  STMT_START { \
     overflow = 0; \
-    ticks = ((e[0] - s[0]) * TICKS_PER_SEC + e[1] - s[1]); \
+    ticks = ((e[0] - s[0]) * (typ)TICKS_PER_SEC + e[1] - s[1]); \
 } STMT_END
 
 #endif
@@ -346,7 +373,7 @@ static SV *DB_CHECK_cv;
 static SV *DB_INIT_cv;
 static SV *DB_END_cv;
 static SV *DB_fin_cv;
-static char *class_mop_evaltag     = " defined at ";
+static const char *class_mop_evaltag     = " defined at ";
 static int   class_mop_evaltag_len = 12;
 
 static unsigned int ticks_per_sec = 0;            /* 0 forces error if not set */
@@ -362,6 +389,7 @@ static void set_option(pTHX_ const char*, const char*);
 static int enable_profile(pTHX_ char *file);
 static int disable_profile(pTHX);
 static void finish_profile(pTHX);
+static void finish_profile_nocontext(void);
 static void open_output_file(pTHX_ char *);
 static int reinit_if_forked(pTHX);
 static int parse_DBsub_value(pTHX_ SV *sv, STRLEN *filename_len_p, UV *first_line_p, UV *last_line_p, char *sub_name);
@@ -386,7 +414,7 @@ static HV *pkg_fids_hv;     /* currently just package names */
 #if (PERL_VERSION < 17) || ((PERL_VERSION == 17) && (PERL_SUBVERSION < 7)) || defined(PERL_SAWAMPERSAND)
 static U8 last_sawampersand;
 #define CHECK_SAWAMPERSAND(fid,line) STMT_START { \
-    if ((U8)PL_sawampersand != last_sawampersand) { \
+    if (PL_sawampersand != last_sawampersand) { \
         if (trace_level >= 1) \
             logwarn("Slow regex match variable seen (0x%x->0x%x at %u:%u)\n", PL_sawampersand, last_sawampersand, fid, line); \
         /* XXX this is a hack used by test14 to avoid different behaviour \
@@ -478,15 +506,27 @@ output_header(pTHX)
 
     /* XXX add options, $0, etc, but beware of embedded newlines */
     /* XXX would be good to adopt a proper charset & escaping for these */
-    /* $^T */
-    NYTP_write_attribute_unsigned(out, STR_WITH_LEN("basetime"), (unsigned long)PL_basetime);
-    NYTP_write_attribute_string(out, STR_WITH_LEN("xs_version"), STR_WITH_LEN(XS_VERSION));
+    NYTP_write_attribute_unsigned(out, STR_WITH_LEN("basetime"), (unsigned long)PL_basetime); /* $^T */
+    NYTP_write_attribute_string(out, STR_WITH_LEN("application"), argv0, len);
+    /* perl constants: */
     NYTP_write_attribute_string(out, STR_WITH_LEN("perl_version"), version, sizeof(version) - 1);
-    NYTP_write_attribute_signed(out, STR_WITH_LEN("clock_id"), profile_clock);
-    NYTP_write_attribute_unsigned(out, STR_WITH_LEN("ticks_per_sec"), ticks_per_sec);
     NYTP_write_attribute_unsigned(out, STR_WITH_LEN("nv_size"), sizeof(NV));
+    /* sanity checks: */
+    NYTP_write_attribute_string(out, STR_WITH_LEN("xs_version"), STR_WITH_LEN(XS_VERSION));
     NYTP_write_attribute_unsigned(out, STR_WITH_LEN("PL_perldb"), PL_perldb);
-    NYTP_write_attribute_string(out, STR_WITH_LEN("application"), argv0, len);
+    /* these are really options: */
+    NYTP_write_attribute_signed(out, STR_WITH_LEN("clock_id"), profile_clock);
+    NYTP_write_attribute_unsigned(out, STR_WITH_LEN("ticks_per_sec"), ticks_per_sec);
+
+    if (1) {
+        struct NYTP_options_t *opt_p = options;
+        const struct NYTP_options_t *const opt_end
+            = options + sizeof(options) / sizeof (struct NYTP_options_t);
+        do {
+            NYTP_write_option_iv(out, opt_p->option_name, opt_p->option_iv);
+        } while (++opt_p < opt_end);
+    }
+
 
 #ifdef HAS_ZLIB
     if (compression_level) {
@@ -532,7 +572,7 @@ read_str(pTHX_ NYTP_file ifile, SV *sv) {
 
     if (trace_level >= 19) {
         STRLEN len2 = len;
-        char *newline = "";
+        const char *newline = "";
         if (buf[len2-1] == '\n') {
             --len2;
             newline = "\\n";
@@ -606,36 +646,36 @@ filename_is_eval(const char *filename, STRLEN filename_len)
  * hash_entry in table, insert IGNORED: returns pointer to the actual hash entry
  */
 static char
-hash_op (Hash_entry entry, Hash_entry** retval, bool insert)
+hash_op(Hash_table *hashtable, char *key, int key_len, Hash_entry** retval, bool insert)
 {
-    unsigned long h = hash(entry.key, entry.key_len) % hashtable.size;
+    unsigned long h = hash(key, key_len) % hashtable->size;
 
-    Hash_entry* found = hashtable.table[h];
+    Hash_entry* found = hashtable->table[h];
     while(NULL != found) {
 
-        if (found->key_len == entry.key_len
-        && memEQ(found->key, entry.key, entry.key_len)
+        if (found->key_len == key_len
+        && memEQ(found->key, key, key_len)
         ) {
             *retval = found;
             return 0;
         }
 
-        if (NULL == (Hash_entry*)found->next_entry) {
+        if (NULL == found->next_entry) {
             if (insert) {
 
                 Hash_entry* e;
-                Newz(0, e, 1, Hash_entry);
-                e->id = next_fid++;
+                Newc(0, e, hashtable->entry_struct_size, char, Hash_entry);
+                memzero(e, hashtable->entry_struct_size);
+                e->id = hashtable->next_id++;
                 e->next_entry = NULL;
-                e->key_len = entry.key_len;
-                e->key = (char*)safemalloc(sizeof(char) * e->key_len + 1);
-                e->key[e->key_len] = '\0';
-                memcpy(e->key, entry.key, e->key_len);
+                e->key_len = key_len;
+                e->key = (char*)safemalloc(sizeof(char) * key_len + 1);
+                e->key[key_len] = '\0';
+                memcpy(e->key, key, key_len);
                 found->next_entry = e;
-                *retval = (Hash_entry*)found->next_entry;
-                if (hashtable.last_inserted)
-                    hashtable.last_inserted->next_inserted = e;
-                hashtable.last_inserted = e;
+                *retval = found->next_entry;
+                hashtable->prior_inserted = hashtable->last_inserted;
+                hashtable->last_inserted = e;
                 return 1;
             }
             else {
@@ -643,26 +683,26 @@ hash_op (Hash_entry entry, Hash_entry** retval, bool insert)
                 return -1;
             }
         }
-        found = (Hash_entry*)found->next_entry;
+        found = found->next_entry;
     }
 
     if (insert) {
         Hash_entry* e;
-        Newz(0, e, 1, Hash_entry);
-        e->id = next_fid++;
+        Newc(0, e, hashtable->entry_struct_size, char, Hash_entry);
+        memzero(e, hashtable->entry_struct_size);
+        e->id = hashtable->next_id++;
         e->next_entry = NULL;
-        e->key_len = entry.key_len;
+        e->key_len = key_len;
         e->key = (char*)safemalloc(sizeof(char) * e->key_len + 1);
         e->key[e->key_len] = '\0';
-        memcpy(e->key, entry.key, e->key_len);
+        memcpy(e->key, key, key_len);
 
-        *retval =   hashtable.table[h] = e;
+        *retval =   hashtable->table[h] = e;
 
-        if (!hashtable.first_inserted)
-            hashtable.first_inserted = e;
-        if (hashtable.last_inserted)
-            hashtable.last_inserted->next_inserted = e;
-        hashtable.last_inserted = e;
+        if (!hashtable->first_inserted)
+            hashtable->first_inserted = e;
+        hashtable->prior_inserted = hashtable->last_inserted;
+        hashtable->last_inserted = e;
 
         return 1;
     }
@@ -671,12 +711,50 @@ hash_op (Hash_entry entry, Hash_entry** retval, bool insert)
     return -1;
 }
 
+static void
+hash_stats(Hash_table *hashtable, int verbosity)
+{
+    int idx = 0;
+    int max_chain_len = 0;
+    int buckets = 0;
+    int items = 0;
+
+    if (verbosity)
+        warn("%s hash: size %d\n", hashtable->name, hashtable->size);
+    if (!hashtable->table)
+        return;
+
+    for (idx=0; idx < hashtable->size; ++idx) {
+        int chain_len = 0;
+
+        Hash_entry *found = hashtable->table[idx];
+        if (!found)
+            continue;
+
+        ++buckets;
+        while (NULL != found) {
+            ++chain_len;
+            ++items;
+            found = found->next_entry;
+        }
+        if (verbosity)
+            warn("%s hash[%3d]: %d items\n", hashtable->name, idx, chain_len);
+        if (chain_len > max_chain_len)
+            max_chain_len = chain_len;
+    }
+    /* XXX would be nice to show a histogram of chain lenths */
+    warn("%s hash: %d of %d buckets used, %d items, max chain %d\n",
+        hashtable->name, buckets, hashtable->size, items, max_chain_len);
+}
+
 
 static void
-emit_fid (Hash_entry *fid_info)
+emit_fid (fid_hash_entry *fid_info)
 {
-    char  *file_name     = fid_info->key;
-    STRLEN file_name_len = fid_info->key_len;
+    char  *file_name     = fid_info->he.key;
+    STRLEN file_name_len = fid_info->he.key_len;
+    char *file_name_copy = NULL;
+
     if (fid_info->key_abs) {
         file_name = fid_info->key_abs;
         file_name_len = strlen(file_name);
@@ -686,34 +764,32 @@ emit_fid (Hash_entry *fid_info)
     /* Make sure we only use forward slashes in filenames */
     if (memchr(file_name, '\\', file_name_len)) {
         STRLEN i;
-        char *file_name_copy = (char*)safemalloc(file_name_len);
+        file_name_copy = (char*)safemalloc(file_name_len);
         for (i=0; i<file_name_len; ++i) {
             char ch = file_name[i];
             file_name_copy[i] = ch == '\\' ? '/' : ch;
         }
-        NYTP_write_new_fid(out, fid_info->id, fid_info->eval_fid,
-                           fid_info->eval_line_num, fid_info->fid_flags,
-                           fid_info->file_size, fid_info->file_mtime,
-                           file_name_copy, (I32)file_name_len);
-        Safefree(file_name_copy);
-        return;
+        file_name = file_name_copy;
     }
 #endif
 
-    NYTP_write_new_fid(out, fid_info->id, fid_info->eval_fid,
+    NYTP_write_new_fid(out, fid_info->he.id, fid_info->eval_fid,
                        fid_info->eval_line_num, fid_info->fid_flags,
                        fid_info->file_size, fid_info->file_mtime,
                        file_name, (I32)file_name_len);
+
+    if (file_name_copy)
+        Safefree(file_name_copy);
 }
 
 
 /* return true if file is a .pm that was actually loaded as a .pmc */
 static int
-fid_is_pmc(pTHX_ Hash_entry *fid_info)
+fid_is_pmc(pTHX_ fid_hash_entry *fid_info)
 {
     int is_pmc = 0;
-    char  *file_name     = fid_info->key;
-    STRLEN len = fid_info->key_len;
+    char  *file_name     = fid_info->he.key;
+    STRLEN len = fid_info->he.key_len;
     if (fid_info->key_abs) {
         file_name = fid_info->key_abs;
         len = strlen(file_name);
@@ -769,23 +845,23 @@ fmt_fid_flags(pTHX_ int fid_flags, char *buf, Size_t len) {
 static void
 write_cached_fids()
 {
-    Hash_entry *e = hashtable.first_inserted;
+    fid_hash_entry *e = (fid_hash_entry*)fidhash.first_inserted;
     while (e) {
         if ( !(e->fid_flags & NYTP_FIDf_IS_ALIAS) )
             emit_fid(e);
-        e = (Hash_entry *)e->next_inserted;
+        e = (fid_hash_entry*)e->he.next_inserted;
     }
 }
 
 
-static Hash_entry *
+static fid_hash_entry *
 find_autosplit_parent(pTHX_ char* file_name)
 {
     /* extract basename from file_name, then search for most recent entry
-     * in hashtable that has the same basename
+     * in fidhash that has the same basename
      */
-    Hash_entry *e = hashtable.first_inserted;
-    Hash_entry *match = NULL;
+    fid_hash_entry *e = (fid_hash_entry*)fidhash.first_inserted;
+    fid_hash_entry *match = NULL;
     const char *sep = "/";
     char *base_end   = strstr(file_name, " (autosplit");
     char *base_start = rninstr(file_name, base_end, sep, sep+1);
@@ -797,28 +873,28 @@ find_autosplit_parent(pTHX_ char* file_name)
         logwarn("find_autosplit_parent of '%.*s' (%s)\n",
             (int)base_len, base_start, file_name);
 
-    for ( ; e; e = (Hash_entry *)e->next_inserted) {
+    for ( ; e; e = (fid_hash_entry*)e->he.next_inserted) {
         char *e_name;
 
         if (e->fid_flags & NYTP_FIDf_IS_AUTOSPLIT)
             continue;
         if (trace_level >= 4)
-            logwarn("find_autosplit_parent: checking '%.*s'\n", e->key_len, e->key);
+            logwarn("find_autosplit_parent: checking '%.*s'\n", e->he.key_len, e->he.key);
 
         /* skip if key is too small to match */
-        if (e->key_len < base_len)
+        if (e->he.key_len < base_len)
             continue;
         /* skip if the last base_len bytes don't match the base name */
-        e_name = e->key + e->key_len - base_len;
+        e_name = e->he.key + e->he.key_len - base_len;
         if (memcmp(e_name, base_start, base_len) != 0)
             continue;
         /* skip if the char before the matched key isn't a separator */
-        if (e->key_len > base_len && *(e_name-1) != *sep)
+        if (e->he.key_len > base_len && *(e_name-1) != *sep)
             continue;
 
         if (trace_level >= 3)
             logwarn("matched autosplit '%.*s' to parent fid %d '%.*s' (%c|%c)\n",
-                (int)base_len, base_start, e->id, e->key_len, e->key, *(e_name-1),*sep);
+                (int)base_len, base_start, e->he.id, e->he.key_len, e->he.key, *(e_name-1),*sep);
         match = e;
         /* keep looking, so we'll return the most recently profiled match */
     }
@@ -834,7 +910,7 @@ lookup_file_entry(pTHX_ char* file_name, STRLEN file_name_len) {
 
     entry.key = file_name;
     entry.key_len = (unsigned int)file_name_len;
-    if (hash_op(entry, &found, 0) == 0)
+    if (hash_op(fidhash, &entry, &found, 0) == 0)
         return found;
 
     return NULL;
@@ -856,23 +932,21 @@ static unsigned int
 get_file_id(pTHX_ char* file_name, STRLEN file_name_len, int created_via)
 {
 
-    Hash_entry entry, *found, *parent_entry;
+    fid_hash_entry *found, *parent_entry;
     AV *src_av = Nullav;
 
-    if (0) memset(&entry, 0, sizeof(entry)); /* handy if debugging */
-    entry.key = file_name;
-    entry.key_len = (unsigned int)file_name_len;
-
-    if (1 != hash_op(entry, &found, (bool)(created_via ? 1 : 0))) {
+    if (1 != hash_op(&fidhash, file_name, file_name_len, (Hash_entry**)&found, (bool)(created_via ? 1 : 0))) {
         /* found existing entry or else didn't but didn't create new one either */
         if (trace_level >= 7) {
             if (found)
-                 logwarn("fid %d: %.*s\n",  found->id, found->key_len, found->key);
-            else logwarn("fid -: %.*s not profiled\n",  entry.key_len,  entry.key);
+                 logwarn("fid %d: %.*s\n", found->he.id, found->he.key_len, found->he.key);
+            else logwarn("fid -: %.*s not profiled\n", (int)file_name_len, file_name);
         }
-        return (found) ? found->id : 0;
+        return (found) ? found->he.id : 0;
     }
     /* inserted new entry */
+    if (fidhash.prior_inserted)
+        fidhash.prior_inserted->next_inserted = fidhash.last_inserted;
 
     /* if this is a synthetic filename for a string eval
      * ie "(eval 42)[/some/filename.pl:line]"
@@ -942,7 +1016,7 @@ get_file_id(pTHX_ char* file_name, STRLEN file_name_len, int created_via)
         && (parent_entry = find_autosplit_parent(aTHX_ file_name))
     ) {
         /* copy some details from parent_entry to found */
-        found->id            = parent_entry->id;
+        found->he.id         = parent_entry->he.id;
         found->eval_fid      = parent_entry->eval_fid;
         found->eval_line_num = parent_entry->eval_line_num;
         found->file_size     = parent_entry->file_size;
@@ -951,15 +1025,15 @@ get_file_id(pTHX_ char* file_name, STRLEN file_name_len, int created_via)
         /* prevent write_cached_fids() from writing this fid */
         found->fid_flags |= NYTP_FIDf_IS_ALIAS;
         /* avoid a gap in the fid sequence */
-        --next_fid;
+        --fidhash.next_id;
         /* write a log message if tracing */
         if (trace_level >= 2)
             logwarn("Use fid %2u (after %2u:%-4u) %x e%u:%u %.*s %s\n",
-                found->id, last_executed_fid, last_executed_line,
+                found->he.id, last_executed_fid, last_executed_line,
                 found->fid_flags, found->eval_fid, found->eval_line_num,
-                found->key_len, found->key, (found->key_abs) ? found->key_abs : "");
+                found->he.key_len, found->he.key, (found->key_abs) ? found->key_abs : "");
         /* bail out without calling emit_fid() */
-        return found->id;
+        return found->he.id;
     }
 
     /* determine absolute path if file_name is relative */
@@ -1018,13 +1092,13 @@ get_file_id(pTHX_ char* file_name, STRLEN file_name_len, int created_via)
     /* is source code available? */
     /* source only available if PERLDB_LINE or PERLDB_SAVESRC is true */
     /* which we set if savesrc option is enabled */
-    if ( (src_av = GvAV(gv_fetchfile_flags(found->key, found->key_len, 0))) )
+    if ( (src_av = GvAV(gv_fetchfile_flags(found->he.key, found->he.key_len, 0))) )
         if (av_len(src_av) > -1)
             found->fid_flags |= NYTP_FIDf_HAS_SRC;
 
     /* flag "perl -e '...'" and "perl -" as string evals */
-    if (found->key[0] == '-' && (found->key_len == 1 ||
-                                (found->key[1] == 'e' && found->key_len == 2)))
+    if (found->he.key[0] == '-' && (found->he.key_len == 1 ||
+                                   (found->he.key[1] == 'e' && found->he.key_len == 2)))
         found->fid_flags |= NYTP_FIDf_IS_EVAL;
 
     /* if it's a string eval or a synthetic filename from CODE ref in @INC,
@@ -1033,7 +1107,7 @@ get_file_id(pTHX_ char* file_name, STRLEN file_name_len, int created_via)
     if (found->eval_fid
     || (found->fid_flags & NYTP_FIDf_IS_EVAL)
     || (profile_opts & NYTP_OPTf_SAVESRC)
-    || (found->key_len > 10 && found->key[9] == 'x' && strnEQ(found->key, "/loader/0x", 10))
+    || (found->he.key_len > 10 && found->he.key[9] == 'x' && strnEQ(found->he.key, "/loader/0x", 10))
     ) {
         found->fid_flags |= NYTP_FIDf_SAVE_SRC;
     }
@@ -1045,14 +1119,26 @@ get_file_id(pTHX_ char* file_name, STRLEN file_name_len, int created_via)
         /* including last_executed_fid can be handy for tracking down how
             * a file got loaded */
         logwarn("New fid %2u (after %2u:%-4u) 0x%02x e%u:%u %.*s %s %s\n",
-            found->id, last_executed_fid, last_executed_line,
+            found->he.id, last_executed_fid, last_executed_line,
             found->fid_flags, found->eval_fid, found->eval_line_num,
-            found->key_len, found->key, (found->key_abs) ? found->key_abs : "",
+            found->he.key_len, found->he.key, (found->key_abs) ? found->key_abs : "",
             fmt_fid_flags(aTHX_ found->fid_flags, buf, sizeof(buf))
         );
     }
 
-    return found->id;
+    return found->he.id;
+}
+
+
+/**
+ * Return a unique persistent id number for a string.
+ */
+static unsigned int
+get_str_id(pTHX_ char* str, STRLEN len)
+{
+    str_hash_entry *found;
+    hash_op(&strhash, str, len, (Hash_entry**)&found, 1);
+    return found->he.id;
 }
 
 static UV
@@ -1568,6 +1654,10 @@ DB_leave(pTHX_ OP *op, OP *prev_op)
 static void
 set_option(pTHX_ const char* option, const char* value)
 {
+    if (!value || !*value)
+        croak("%s: invalid option", "NYTProf set_option");
+    if (!value || !*value)
+        croak("%s: '%s' has no value", "NYTProf set_option", option);
 
     if (strEQ(option, "file")) {
         strncpy(PROF_output_file, value, MAXPATHLEN);
@@ -1607,14 +1697,19 @@ set_option(pTHX_ const char* option, const char* value)
         if (atoi(value))
             PL_exit_flags |= PERL_EXIT_DESTRUCT_END;
     }
+    else if (strEQ(option, "libcexit")) {
+        if (atoi(value))
+	    atexit(finish_profile_nocontext);
+    }
     else {
-        struct NYTP_int_options_t *opt_p = options;
-        const struct NYTP_int_options_t *const opt_end
-            = options + sizeof(options) / sizeof (struct NYTP_int_options_t);
+
+        struct NYTP_options_t *opt_p = options;
+        const struct NYTP_options_t *const opt_end
+            = options + sizeof(options) / sizeof (struct NYTP_options_t);
         bool found = FALSE;
         do {
             if (strEQ(option, opt_p->option_name)) {
-                opt_p->option_value = strtol(value, NULL, 0);
+                opt_p->option_iv = (IV)strtol(value, NULL, 0);
                 found = TRUE;
                 break;
             }
@@ -1671,7 +1766,7 @@ open_output_file(pTHX_ char *filename)
             filename, fopen_errno, strerror(fopen_errno), hint);
     }
     if (trace_level >= 1)
-        logwarn("~ opened %s\n", filename);
+        logwarn("~ opened %s at %.6f\n", filename, gettimeofday_nv());
 
     output_header(aTHX);
 }
@@ -1701,7 +1796,7 @@ close_output_file(pTHX) {
     out = NULL;
 
     if (trace_level >= 1)
-        logwarn("~ closed file.\n");
+        logwarn("~ closed file at %.6f\n", timeofday);
 }
 
 
@@ -1715,7 +1810,7 @@ reinit_if_forked(pTHX)
 
     /* we're now the child process */
     if (trace_level >= 1)
-        logwarn("~ new pid %d (was %d) forkdepth %d\n", getpid(), last_pid, profile_forkdepth);
+        logwarn("~ new pid %d (was %d) forkdepth %ld\n", getpid(), last_pid, profile_forkdepth);
 
     /* reset state */
     last_pid = getpid();
@@ -1775,7 +1870,7 @@ new_sub_call_info_av(pTHX)
 typedef struct subr_entry_st subr_entry_t;
 struct subr_entry_st {
     unsigned int  already_counted;
-    unsigned int  subr_prof_depth;
+    U32  subr_prof_depth;
     long unsigned subr_call_seqn;
     I32 prev_subr_entry_ix; /* ix to callers subr_entry */
 
@@ -1871,8 +1966,8 @@ subr_entry_destroy(pTHX_ subr_entry_t *subr_entry)
         /* ignore the typical second (fallback) destroy */
         && !(subr_entry->prev_subr_entry_ix == subr_entry_ix && subr_entry->already_counted==1)
     ) {
-        logwarn("%2d <<     %s::%s done %s\n",
-            subr_entry->subr_prof_depth,
+        logwarn("%2u <<     %s::%s done %s\n",
+            (unsigned int)subr_entry->subr_prof_depth,
             subr_entry->called_subpkg_pv,
             (subr_entry->called_subnam_sv && SvOK(subr_entry->called_subnam_sv))
                 ? SvPV_nolen(subr_entry->called_subnam_sv)
@@ -1908,8 +2003,11 @@ incr_sub_inclusive_time(pTHX_ subr_entry_t *subr_entry)
     NV  incl_subr_ticks, excl_subr_ticks;
     SV *sv_tmp;
     AV *subr_call_av;
+    time_of_day_t sub_end_time;
+    long ticks, overflow;
 
-    if (subr_entry->called_subnam_sv == &PL_sv_undef) {
+    /* an undef SV is a special marker used by subr_entry_setup */
+    if (subr_entry->called_subnam_sv && !SvOK(subr_entry->called_subnam_sv)) {
         if (trace_level)
             logwarn("Don't know name of called sub, assuming xsub/builtin exited via an exception (which isn't handled yet)\n");
         subr_entry->already_counted++;
@@ -1927,12 +2025,9 @@ incr_sub_inclusive_time(pTHX_ subr_entry_t *subr_entry)
 
     /* statement overheads we've accumulated since we entered the sub */
     overhead_ticks = cumulative_overhead_ticks - subr_entry->initial_overhead_ticks;
-    /* seconds spent in subroutines called by this subroutine */
+    /* ticks spent in subroutines called by this subroutine */
     called_sub_ticks = cumulative_subr_ticks - subr_entry->initial_subr_ticks;
 
-    time_of_day_t sub_end_time;
-    long ticks, overflow;
-
     /* calculate ticks since we entered the sub */
     get_time_of_day(sub_end_time);
     get_ticks_between(NV, subr_entry->initial_call_timeofday, sub_end_time, ticks, overflow);
@@ -2039,19 +2134,17 @@ incr_sub_inclusive_time(pTHX_ subr_entry_t *subr_entry)
         sv_inc(AvARRAY(subr_call_av)[NYTP_SCi_CALL_COUNT]);
     }
 
-    if (trace_level >= 5)
-        logwarn("%2d <-     %s %"NVff"s excl = %"NVff"s incl - %"NVff"s (%"NVff"-%"NVff"), oh %"NVff"-%"NVff"=%"NVff"t, d%d @%d:%d #%lu %p\n",
-            subr_entry->subr_prof_depth,
-            called_subname_pv,
-            excl_subr_ticks/ticks_per_sec,
-            incl_subr_ticks/ticks_per_sec,
-            called_sub_ticks/ticks_per_sec,
-            cumulative_subr_ticks/ticks_per_sec,
-            subr_entry->initial_subr_ticks/ticks_per_sec,
+    if (trace_level >= 5) {
+        logwarn("%2u <-     %s %"NVgf" excl = %"NVgf"t incl - %"NVgf"t (%"NVgf"-%"NVgf"), oh %"NVff"-%"NVff"=%"NVff"t, d%d @%d:%d #%lu %p\n",
+            (unsigned int)subr_entry->subr_prof_depth, called_subname_pv,
+            excl_subr_ticks, incl_subr_ticks,
+            called_sub_ticks,
+            cumulative_subr_ticks, subr_entry->initial_subr_ticks,
             cumulative_overhead_ticks, subr_entry->initial_overhead_ticks, overhead_ticks,
             (int)subr_entry->called_cv_depth,
             subr_entry->caller_fid, subr_entry->caller_line,
             subr_entry->subr_call_seqn, (void*)subr_entry);
+    }
 
     /* only count inclusive time for the outer-most calls */
     if (subr_entry->called_cv_depth <= 1) {
@@ -2070,6 +2163,10 @@ incr_sub_inclusive_time(pTHX_ subr_entry_t *subr_entry)
     excl_time_sv = *av_fetch(subr_call_av, NYTP_SCi_EXCL_TICKS, 1);
     sv_setnv(excl_time_sv, SvNV(excl_time_sv)+excl_subr_ticks);
 
+    if (opt_calls && out) {
+        NYTP_write_call_return(out, subr_entry->subr_prof_depth, called_subname_pv, incl_subr_ticks, excl_subr_ticks);
+    }
+
     subr_entry_destroy(aTHX_ subr_entry);
 
     cumulative_subr_ticks += excl_subr_ticks;
@@ -2254,7 +2351,14 @@ subr_entry_setup(pTHX_ COP *prev_cop, subr_entry_t *clone_subr_entry, OPCODE op_
             }
         }
         else {
-            subr_entry->called_subnam_sv = newSV(0); /* see incr_sub_inclusive_time */
+            /* resolve_sub_to_cv couldn't work out what's being called,
+             * possibly because it's something that'll cause pp_entersub to croak
+             * anyway.  So we mark the subr_entry in a particular way and hope that
+             * pp_subcall_profiler() can fill in the details.
+             * If there is an exception then we'll wind up in incr_sub_inclusive_time
+             * which will see this mark and ignore the call.
+             */
+            subr_entry->called_subnam_sv = newSV(0);
         }
         subr_entry->called_is_xs = NULL; /* work it out later */
     }
@@ -2403,8 +2507,8 @@ subr_entry_setup(pTHX_ COP *prev_cop, subr_entry_t *clone_subr_entry, OPCODE op_
     }
 
     if (trace_level >= 4) {
-        logwarn("%2d >> %s at %u:%d from %s::%s %s %s\n",
-            subr_entry->subr_prof_depth,
+        logwarn("%2u >> %s at %u:%d from %s::%s %s %s\n",
+            (unsigned int)subr_entry->subr_prof_depth,
             PL_op_name[op_type],
             subr_entry->caller_fid, subr_entry->caller_line,
             subr_entry->caller_subpkg_pv,
@@ -2421,6 +2525,10 @@ subr_entry_setup(pTHX_ COP *prev_cop, subr_entry_t *clone_subr_entry, OPCODE op_
      */
     save_destructor_x(incr_sub_inclusive_time_ix, INT2PTR(void *, (IV)subr_entry_ix));
 
+    if (opt_calls >= 2 && out) {
+        NYTP_write_call_entry(out, subr_entry->caller_fid, subr_entry->caller_line);
+    }
+
     SETERRNO(saved_errno, 0);
 
     return subr_entry_ix;
@@ -2569,7 +2677,7 @@ pp_subcall_profiler(pTHX_ int is_slowop)
     subr_entry = subr_entry_ix_ptr(this_subr_entry_ix);
 
     /* detect wierdness/corruption */
-    assert(subr_entry->caller_fid < next_fid);
+    assert(subr_entry->caller_fid < fidhash.next_id);
 
     /* Check if this call has already been counted because the op performed
      * a leave_scope(). E.g., OP_SUBSTCONT at end of s/.../\1/
@@ -2577,8 +2685,8 @@ pp_subcall_profiler(pTHX_ int is_slowop)
      */
     if (subr_entry->already_counted) {
         if (trace_level >= 9)
-            logwarn("%2d --     %s::%s already counted %s\n",
-                subr_entry->subr_prof_depth,
+            logwarn("%2u --     %s::%s already counted %s\n",
+                (unsigned int)subr_entry->subr_prof_depth,
                 subr_entry->called_subpkg_pv,
                 (subr_entry->called_subnam_sv && SvOK(subr_entry->called_subnam_sv))
                     ? SvPV_nolen(subr_entry->called_subnam_sv)
@@ -2692,8 +2800,8 @@ pp_subcall_profiler(pTHX_ int is_slowop)
         subr_entry->already_counted++;
 
     if (trace_level >= 4) {
-        logwarn("%2d ->%4s %s::%s from %s::%s @%u:%u (d%d, oh %"NVff"t, sub %"NVff"s) #%lu\n",
-            subr_entry->subr_prof_depth,
+        logwarn("%2u ->%4s %s::%s from %s::%s @%u:%u (d%d, oh %"NVff"t, sub %"NVff"s) #%lu\n",
+            (unsigned int)subr_entry->subr_prof_depth,
             (subr_entry->called_is_xs) ? subr_entry->called_is_xs : "sub",
             subr_entry->called_subpkg_pv,
             subr_entry->called_subnam_sv ? SvPV_nolen(subr_entry->called_subnam_sv) : "(null)",
@@ -2828,7 +2936,7 @@ disable_profile(pTHX)
         is_profiling = 0;
     }
     if (trace_level)
-        logwarn("~ disable_profile (previously %s, pid %d, trace %d)\n",
+        logwarn("~ disable_profile (previously %s, pid %d, trace %ld)\n",
             prev_is_profiling ? "enabled" : "disabled", getpid(), trace_level);
     return prev_is_profiling;
 }
@@ -2837,6 +2945,7 @@ disable_profile(pTHX)
 static void
 finish_profile(pTHX)
 {
+    /* can be called after the perl interp is destroyed, via libcexit */
     int saved_errno = errno;
 #ifdef MULTIPLICITY
     if (orig_my_perl && my_perl != orig_my_perl)
@@ -2847,8 +2956,8 @@ finish_profile(pTHX)
 #endif
 
     if (trace_level >= 1)
-        logwarn("~ finish_profile (overhead %"NVff"s, is_profiling %d)\n",
-            cumulative_overhead_ticks/ticks_per_sec, is_profiling);
+        logwarn("~ finish_profile (overhead %gt, is_profiling %d)\n",
+            cumulative_overhead_ticks, is_profiling);
 
     /* write data for final statement, unless DB_leave has already */
     if (!profile_leave || opt_use_db_sub)
@@ -2858,8 +2967,17 @@ finish_profile(pTHX)
 
     close_output_file(aTHX);
 
+    if (trace_level >= 2) {
+        hash_stats(&fidhash, 0);
+        hash_stats(&strhash, 0);
+    }
+
     /* reset sub profiler data  */
-    hv_clear(sub_callers_hv);
+    if (HvKEYS(sub_callers_hv)) {
+        /* HvKEYS check avoids hv_clear() if interp has been destroyed RT#86548 */
+        hv_clear(sub_callers_hv);
+    }
+
     /* reset other state */
     cumulative_overhead_ticks = 0;
     cumulative_subr_ticks = 0;
@@ -2869,6 +2987,15 @@ finish_profile(pTHX)
 
 
 static void
+finish_profile_nocontext()
+{
+    /* can be called after the perl interp is destroyed, via libcexit */
+    dTHX;
+    finish_profile(aTHX);
+}
+
+
+static void
 _init_profiler_clock(pTHX)
 {
 #ifdef HAS_CLOCK_GETTIME
@@ -2882,8 +3009,8 @@ _init_profiler_clock(pTHX)
     /* downgrade to CLOCK_REALTIME if desired clock not available */
     if (clock_gettime(profile_clock, &start_time) != 0) {
         if (trace_level)
-            logwarn("~ clock_gettime clock %d not available (%s) using CLOCK_REALTIME instead\n",
-                profile_clock, strerror(errno));
+            logwarn("~ clock_gettime clock %ld not available (%s) using CLOCK_REALTIME instead\n",
+                (long)profile_clock, strerror(errno));
         profile_clock = CLOCK_REALTIME;
         /* check CLOCK_REALTIME as well, just in case */
         if (clock_gettime(profile_clock, &start_time) != 0)
@@ -2892,7 +3019,7 @@ _init_profiler_clock(pTHX)
     }
 #else
     if (profile_clock != -1) {  /* user tried to select different clock */
-        logwarn("clock %d not available (clock_gettime not supported on this system)\n", profile_clock);
+        logwarn("clock %ld not available (clock_gettime not supported on this system)\n", (long)profile_clock);
         profile_clock = -1;
     }
 #endif
@@ -2952,8 +3079,8 @@ init_profiler(pTHX)
     _init_profiler_clock(aTHX);
 
     if (trace_level)
-        logwarn("~ init_profiler for pid %d, clock %d, start %d, perldb 0x%lx, exitf 0x%lx\n",
-            last_pid, profile_clock, profile_start,
+        logwarn("~ init_profiler for pid %d, clock %ld, tps %d, start %d, perldb 0x%lx, exitf 0x%lx\n",
+            last_pid, (long)profile_clock, ticks_per_sec, profile_start,
             (long unsigned)PL_perldb, (long unsigned)PL_exit_flags);
 
     if (get_hv("DB::sub", 0) == NULL) {
@@ -2971,10 +3098,8 @@ init_profiler(pTHX)
 #endif
 
     /* create file id mapping hash */
-    hashtable.table = (Hash_entry**)safemalloc(sizeof(Hash_entry*) * hashtable.size);
-    memset(hashtable.table, 0, sizeof(Hash_entry*) * hashtable.size);
-
-    open_output_file(aTHX_ PROF_output_file);
+    fidhash.table = (Hash_entry**)safemalloc(sizeof(Hash_entry*) * fidhash.size);
+    memset(fidhash.table, 0, sizeof(Hash_entry*) * fidhash.size);
 
     /* redirect opcodes for statement profiling */
     Newxc(PL_ppaddr_orig, OP_max, void *, orig_ppaddr_t);
@@ -3173,7 +3298,7 @@ parse_DBsub_value(pTHX_ SV *sv, STRLEN *filename_len_p, UV *first_line_p, UV *la
     if ('-' == *++last) { /* skip past dash, is next char a minus? */
         warn("Negative last line number in %%DB::sub entry '%s' for %s\n",
             filename, sub_name);
-        last = "0";
+        last = (char *)"0";
     }
     if (last_line_p)
         *last_line_p = atoi(last);
@@ -3300,7 +3425,7 @@ write_sub_line_ranges(pTHX)
         /* get name of file that contained first profiled sub in 'main::' */
         SV *pkg_filename_sv = sub_pkg_filename_sv(aTHX_ runtime, runtime_len);
         if (!pkg_filename_sv) { /* no subs in main, so guess */
-            sv_setpvn(sv, hashtable.first_inserted->key, hashtable.first_inserted->key_len);
+            sv_setpvn(sv, fidhash.first_inserted->key, fidhash.first_inserted->key_len);
         }
         else if (SvOK(pkg_filename_sv)) {
             sv_setsv(sv, pkg_filename_sv);
@@ -3461,8 +3586,8 @@ write_sub_callers(pTHX)
         }
     }
     if (negative_time_calls) {
-        logwarn("Warning: %d subroutine calls had negative time! See TROUBLESHOOTING in the documentation. (Clock %d)\n",
-            negative_time_calls, profile_clock);
+        logwarn("Warning: %d subroutine calls had negative time! See TROUBLESHOOTING in the documentation. (Clock %ld)\n",
+            negative_time_calls, (long)profile_clock);
     }
 }
 
@@ -3470,7 +3595,7 @@ write_sub_callers(pTHX)
 static void
 write_src_of_files(pTHX)
 {
-    Hash_entry *e;
+    fid_hash_entry *e;
     int t_has_src  = 0;
     int t_save_src = 0;
     int t_no_src = 0;
@@ -3479,25 +3604,25 @@ write_src_of_files(pTHX)
     if (trace_level >= 1)
         logwarn("~ writing file source code\n");
 
-    for (e = hashtable.first_inserted; e; e = (Hash_entry *)e->next_inserted) {
+    for (e = (fid_hash_entry*)fidhash.first_inserted; e; e = (fid_hash_entry*)e->he.next_inserted) {
         I32 lines;
         int line;
-        AV *src_av = GvAV(gv_fetchfile_flags(e->key, e->key_len, 0));
+        AV *src_av = GvAV(gv_fetchfile_flags(e->he.key, e->he.key_len, 0));
 
         if ( !(e->fid_flags & NYTP_FIDf_HAS_SRC) ) {
-            char *hint = "";
+            const char *hint = "";
             ++t_no_src;
             if (src_av && av_len(src_av) > -1) /* sanity check */
                 hint = " (NYTP_FIDf_HAS_SRC not set but src available!)";
             if (trace_level >= 3 || *hint)
                 logwarn("fid %d has no src saved for %.*s%s\n",
-                    e->id, e->key_len, e->key, hint);
+                    e->he.id, e->he.key_len, e->he.key, hint);
             continue;
         }
         if (!src_av) { /* sanity check */
             ++t_no_src;
             logwarn("fid %d has no src but NYTP_FIDf_HAS_SRC is set! (%.*s)\n",
-                e->id, e->key_len, e->key);
+                e->he.id, e->he.key_len, e->he.key);
             continue;
         }
         ++t_has_src;
@@ -3510,16 +3635,16 @@ write_src_of_files(pTHX)
         lines = av_len(src_av); /* -1 is empty, 1 is 1 line etc, 0 shouldn't happen */
         if (trace_level >= 3)
             logwarn("fid %d has %ld src lines for %.*s\n",
-                e->id, (long)lines, e->key_len, e->key);
+                e->he.id, (long)lines, e->he.key_len, e->he.key);
         for (line = 1; line <= lines; ++line) { /* lines start at 1 */
             SV **svp = av_fetch(src_av, line, 0);
             STRLEN len = 0;
             const char *src = (svp) ? SvPV(*svp, len) : "";
             /* outputting the tag and fid for each (non empty) line
              * is a little inefficient, but not enough to worry about */
-            NYTP_write_src_line(out, e->id, line, src, (I32)len);    /* includes newline */
+            NYTP_write_src_line(out, e->he.id, line, src, (I32)len);    /* includes newline */
             if (trace_level >= 8) {
-                logwarn("fid %d src line %d: %s%s", e->id, line, src,
+                logwarn("fid %d src line %d: %s%s", e->he.id, line, src,
                     (len && src[len-1]=='\n') ? "" : "\n");
             }
             ++t_lines;
@@ -3708,6 +3833,7 @@ typedef struct loader_state_profiler {
     HV *sub_subinfo_hv;
     HV *live_pids_hv;
     HV *attr_hv;
+    HV *option_hv;
     HV *file_info_stash;
     /* these times don't reflect profile_enable & profile_disable calls */
     NV profiler_start_time;
@@ -3719,6 +3845,7 @@ static void
 load_discount_callback(Loader_state_base *cb_data, const nytp_tax_index tag, ...)
 {
     Loader_state_profiler *state = (Loader_state_profiler *)cb_data;
+    PERL_UNUSED_ARG(tag);
 
     if (trace_level >= 8)
         logwarn("discounting next statement after %u:%d\n",
@@ -3873,7 +4000,7 @@ load_new_fid_callback(Loader_state_base *cb_data, const nytp_tax_index tag, ...)
                 SvPV_nolen(filename_sv), file_num,
                 fmt_fid_flags(aTHX_ fid_flags, buf, sizeof(buf)), eval_file_num);
             /* so make it look like a real file instead of an eval */
-            av_store(av, NYTP_FIDi_EVAL_FI,   &PL_sv_undef);
+            av_store(av, NYTP_FIDi_EVAL_FI,   NULL);
             eval_file_num = 0;
             eval_line_num = 0;
         }
@@ -3887,7 +4014,7 @@ load_new_fid_callback(Loader_state_base *cb_data, const nytp_tax_index tag, ...)
         }
     }
     else {
-        av_store(av, NYTP_FIDi_EVAL_FI,   &PL_sv_undef);
+        av_store(av, NYTP_FIDi_EVAL_FI,   NULL);
     }
     av_store(av, NYTP_FIDi_EVAL_FID,  (eval_file_num) ? newSVuv(eval_file_num) : &PL_sv_no);
     av_store(av, NYTP_FIDi_EVAL_LINE, (eval_file_num) ? newSVuv(eval_line_num) : &PL_sv_no);
@@ -3895,8 +4022,8 @@ load_new_fid_callback(Loader_state_base *cb_data, const nytp_tax_index tag, ...)
     av_store(av, NYTP_FIDi_FLAGS,     newSVuv(fid_flags));
     av_store(av, NYTP_FIDi_FILESIZE,  newSVuv(file_size));
     av_store(av, NYTP_FIDi_FILEMTIME, newSVuv(file_mtime));
-    av_store(av, NYTP_FIDi_PROFILE,   &PL_sv_undef);
-    av_store(av, NYTP_FIDi_HAS_EVALS, &PL_sv_undef);
+    av_store(av, NYTP_FIDi_PROFILE,   NULL);
+    av_store(av, NYTP_FIDi_HAS_EVALS, NULL);
     av_store(av, NYTP_FIDi_SUBS_DEFINED, newRV_noinc((SV*)newHV()));
     av_store(av, NYTP_FIDi_SUBS_CALLED,  newRV_noinc((SV*)newHV()));
 }
@@ -4197,7 +4324,7 @@ load_pid_end_callback(Loader_state_base *cb_data, const nytp_tax_index tag, ...)
 
     store_attrib_sv(aTHX_ state->attr_hv, STR_WITH_LEN("profiler_end_time"),
                     newSVnv(end_time));
-    state->profiler_duration += end_time - state->profiler_start_time;
+    state->profiler_duration += state->profiler_end_time - state->profiler_start_time;
     store_attrib_sv(aTHX_ state->attr_hv, STR_WITH_LEN("profiler_duration"),
                     newSVnv(state->profiler_duration));
 
@@ -4234,6 +4361,38 @@ load_attribute_callback(Loader_state_base *cb_data, const nytp_tax_index tag, ..
                                    value_utf8 ? SVf_UTF8 : 0));
 }
 
+static void
+load_option_callback(Loader_state_base *cb_data, const nytp_tax_index tag, ...)
+{
+    Loader_state_profiler *state = (Loader_state_profiler *)cb_data;
+    dTHXa(state->interp);
+    va_list args;
+    char *key;
+    unsigned long key_len;
+    unsigned int key_utf8;
+    char *value;
+    unsigned long value_len;
+    unsigned int value_utf8;
+    SV *value_sv;
+
+    va_start(args, tag);
+
+    key = va_arg(args, char *);
+    key_len = va_arg(args, unsigned long);
+    key_utf8 = va_arg(args, unsigned int);
+
+    value = va_arg(args, char *);
+    value_len = va_arg(args, unsigned long);
+    value_utf8 = va_arg(args, unsigned int);
+
+    va_end(args);
+
+    value_sv = newSVpvn_flags(value, value_len, value_utf8 ? SVf_UTF8 : 0);
+    (void)hv_store(state->option_hv, key, key_utf8 ? -(I32)key_len : key_len, value_sv, 0);
+    if (trace_level >= 1)
+        logwarn("! %.*s = '%s'\n", (int) key_len, key, SvPV_nolen(value_sv));
+}
+
 struct perl_callback_info_t {
     const char *description;
     STRLEN len;
@@ -4245,6 +4404,7 @@ static struct perl_callback_info_t callback_info[nytp_tag_max] =
     {STR_WITH_LEN("[no tag]"), NULL},
     {STR_WITH_LEN("VERSION"), "uu"},
     {STR_WITH_LEN("ATTRIBUTE"), "33"},
+    {STR_WITH_LEN("OPTION"), "33"},
     {STR_WITH_LEN("COMMENT"), "3"},
     {STR_WITH_LEN("TIME_BLOCK"), "iuuuu"},
     {STR_WITH_LEN("TIME_LINE"),  "iuu"},
@@ -4257,7 +4417,9 @@ static struct perl_callback_info_t callback_info[nytp_tag_max] =
     {STR_WITH_LEN("PID_END"), "un"},
     {STR_WITH_LEN("[string]"), NULL},
     {STR_WITH_LEN("[string utf8]"), NULL},
-    {STR_WITH_LEN("START_DEFLATE"), ""}
+    {STR_WITH_LEN("START_DEFLATE"), ""},
+    {STR_WITH_LEN("SUB_ENTRY"), "uu"},
+    {STR_WITH_LEN("SUB_RETURN"), "unns"}
 };
 
 static void
@@ -4381,6 +4543,9 @@ static loader_callback perl_callbacks[nytp_tag_max] =
     load_perl_callback,
     load_perl_callback,
     load_perl_callback,
+    load_perl_callback,
+    load_perl_callback,
+    load_perl_callback,
     load_perl_callback
 };
 static loader_callback processing_callbacks[nytp_tag_max] =
@@ -4388,6 +4553,7 @@ static loader_callback processing_callbacks[nytp_tag_max] =
     0,
     0, /* version */
     load_attribute_callback,
+    load_option_callback,
     0, /* comment */
     load_time_callback,
     load_time_callback,
@@ -4400,6 +4566,8 @@ static loader_callback processing_callbacks[nytp_tag_max] =
     load_pid_end_callback,
     0, /* string */
     0, /* string utf8 */
+    0, /* sub entry */
+    0, /* sub return */
     0  /* start deflate */
 };
 
@@ -4483,8 +4651,7 @@ load_profile_data_from_stream(loader_callback *callbacks,
                 if (c == NYTP_TAG_TIME_BLOCK) {
                     block_line_num = read_u32(in);
                     sub_line_num = read_u32(in);
-                    if (profile_blocks)
-                        tag = nytp_time_block;
+                    tag = nytp_time_block;
                 }
 
                 /* Because it happens that the two "optional" arguments are
@@ -4524,6 +4691,28 @@ load_profile_data_from_stream(loader_callback *callbacks,
                 break;
             }
 
+            case NYTP_TAG_SUB_ENTRY:
+            {
+                unsigned int file_num = read_u32(in);
+                unsigned int line_num = read_u32(in);
+
+                if (callbacks[nytp_sub_entry])
+                    callbacks[nytp_sub_entry](state, nytp_sub_entry, file_num, line_num);
+                break;
+            }
+
+            case NYTP_TAG_SUB_RETURN:
+            {
+                unsigned int depth = read_u32(in);
+                NV incl_time       = read_nv(in);
+                NV excl_time       = read_nv(in);
+                SV *subname = read_str(aTHX_ in, tmp_str1_sv);
+
+                if (callbacks[nytp_sub_return])
+                    callbacks[nytp_sub_return](state, nytp_sub_return, depth, incl_time, excl_time, subname);
+                break;
+            }
+
             case NYTP_TAG_SUB_INFO:
             {
                 unsigned int fid        = read_u32(in);
@@ -4607,6 +4796,27 @@ load_profile_data_from_stream(loader_callback *callbacks,
                 break;
             }
 
+            case NYTP_TAG_OPTION:
+            {
+                char *value, *key_end;
+                char *end = NYTP_gets(in, &buffer, &buffer_len);
+                if (NULL == end)
+                    /* probably EOF */
+                    croak("Profile format error reading attribute");
+                --end; /* end, as returned, points 1 after the \n  */
+                if ((NULL == (value = (char *)memchr(buffer, '=', end - buffer)))) {
+                    logwarn("option malformed '%s'\n", buffer);
+                    continue;
+                }
+                key_end = value++;
+
+                callbacks[nytp_option](state, nytp_option, buffer,
+                                          (unsigned long)(key_end - buffer),
+                                          0, value,
+                                          (unsigned long)(end - value), 0);
+                break;
+            }
+
             case NYTP_TAG_COMMENT:
             {
                 char *end = NYTP_gets(in, &buffer, &buffer_len);
@@ -4670,6 +4880,7 @@ load_profile_to_hv(pTHX_ NYTP_file in)
     state.sub_subinfo_hv = newHV();
     state.live_pids_hv = newHV();
     state.attr_hv = newHV();
+    state.option_hv = newHV();
     state.file_info_stash = gv_stashpv("Devel::NYTProf::FileInfo", GV_ADDWARN);
 
     av_extend(state.fid_fileinfo_av, 64);   /* grow them up front. */
@@ -4729,6 +4940,8 @@ load_profile_to_hv(pTHX_ NYTP_file in)
     profile_modes = newHV();
     (void)hv_stores(profile_hv, "attribute",         
                     newRV_noinc((SV*)state.attr_hv));
+    (void)hv_stores(profile_hv, "option",         
+                    newRV_noinc((SV*)state.option_hv));
     (void)hv_stores(profile_hv, "fid_fileinfo",
                     newRV_noinc((SV*)state.fid_fileinfo_av));
     (void)hv_stores(profile_hv, "fid_srclines",
@@ -0,0 +1,26 @@
+# Devel::NYTProf
+
+Devel::NYTProf is a powerful feature-rich perl source code profiler.
+
+[![Build Status](https://secure.travis-ci.org/timbunce/devel-nytprof.png)](http://travis-ci.org/timbunce/devel-nytprof)
+
+For more information see:
+
+    http://www.slideshare.net/Tim.Bunce/develnytprof-v4-at-yapceu-201008-4906467
+    http://blog.timbunce.org/tag/nytprof/
+
+## DOWNLOAD AND INSTALLATION
+
+Download a release from CPAN using your favorite tool, such as cpanm.  Or else
+from https://metacpan.org/release/Devel-NYTProf and then unpack the tar.gz file.
+
+You're most welcome to contribute, in which case cloning or forking the git
+repo is a good place to start.
+
+To build and install, just incant the typical mantra:
+
+     perl Makefile.PL
+     make
+     make test
+     make install
+
@@ -1,125 +0,0 @@
-#! /usr/bin/env perl
-# vim: ts=8 sw=4 sts=4 expandtab:
-##########################################################
-## This script is part of the Devel::NYTProf distribution
-##
-## Copyright, contact and other information can be found
-## at the bottom of this file, or by going to:
-## http://search.cpan.org/perldoc?Devel::NYTProf
-##
-###########################################################
-## $Id$
-###########################################################
-use warnings;
-use strict;
-
-use Carp;
-use Config;
-use Getopt::Long;
-use Benchmark qw(:hireswallclock timethese cmpthese);
-use Devel::NYTProf::Data; # just to print path
-
-GetOptions(
-    'v|verbose' => \my $opt_verbose,
-) or exit 1;
-
-my $regex = shift;
-
-my $subs_count = shift || 2000;
-my $loop_count = shift || 1000;
-
-# simple benchmark script to measure profiling overhead
-my $test_script = "benchmark_code.pl";
-open my $fh, ">", $test_script or die "Can't write to $test_script: $!\n";
-print $fh q{
-    my $subs_count = shift || die "No subs count";
-    my $loop_count = shift || die "No loop count";
-    sub foo {
-        my $loop = shift;
-        my $a = 0;
-        while ($loop-- > 0) { ++$a; ++$a; ++$a; }
-    }
-    while ($subs_count-- > 0) {
-        foo($loop_count)
-    }
-};
-close $fh or die "Error writing to $test_script: $!\n";
-END { unlink $test_script };
-
-
-my %tests = (
-    baseline => {
-        perlargs => '',
-    },
-    dprof => {
-        perlargs => '-d:DProf',
-        datafile => 'tmon.out',
-    },
-    fastprof => {
-        perlargs => '-MDevel::FastProf',
-        datafile => 'fastprof.out',
-    },
-    profit => {
-        perlargs => '-MDevel::Profit',
-        datafile => 'profit.out',
-    },
-    nytprof_o => {
-        env => [ NYTPROF => 'use_db_sub=0:file=nytprof_o.out' ],
-        perlargs => '-d:NYTProf',
-        datafile => 'nytprof_o.out',
-    },
-    nytprof_s => {
-        env => [ NYTPROF => 'use_db_sub=1:file=nytprof_s.out' ],
-        perlargs => '-d:NYTProf',
-        datafile => 'nytprof_s.out',
-    },
-    nytprof_ob => {
-        env => [ NYTPROF => 'blocks:file=nytprof_ob.out' ],
-        perlargs => '-d:NYTProf',
-        datafile => 'nytprof_ob.out',
-    },
-);
-
-my %test_subs;
-while ( my ($testname, $testinfo) = each %tests ) {
-    if ($regex && $testname ne 'baseline' && $testname !~ m/$regex/o) {
-        warn "Skipped $testname\n";
-        next;
-    }
-    if (!run_test($testinfo, 1, 1)) {
-        warn "Can't run $testname profiler - skipped\n";
-        next;
-    }
-    $testinfo->{testname} = $testname;
-    $test_subs{$testname} = sub { run_test($testinfo, $subs_count, $loop_count) };
-}
-
-printf "Profiler performance using perl %8s %s (%s %s %s)\n",
-    $], $Config{archname},
-    $Config{gccversion} ? 'gcc' : $Config{cc},
-    (split / /, $Config{gccversion}||$Config{ccversion}||'')[0]||'',
-    $Config{optimize};
-printf "NYTProf is $INC{'Devel/NYTProf/Data.pm'}\n";
-
-cmpthese(4, \%test_subs, 'nop');
-
-for my $testname (sort keys %test_subs) {
-    my $testinfo = $tests{$testname};
-    if ($testinfo->{datafile}) {
-        printf "%10s: %6dKB %s\n",
-            $testname, (-s $testinfo->{datafile})/1024, $testinfo->{datafile};
-        unlink $testinfo->{datafile};
-    }
-}
-
-exit 0;
-
-sub run_test {
-    my($testinfo, $subs_count, $loop_count) = @_;
-
-    my $env = $testinfo->{env};
-    local $ENV{$env->[0]} = $env->[1] if $env;
-
-    my $cmd = "perl $testinfo->{perlargs} $test_script $subs_count $loop_count";
-    system($cmd) == 0;
-}
@@ -0,0 +1,360 @@
+#!/usr/bin/perl -w
+#
+# flamegraph.pl		flame stack grapher.
+#
+# This takes stack samples and renders a call graph, allowing hot functions
+# and codepaths to be quickly identified.
+#
+# USAGE: ./flamegraph.pl input.txt > graph.svg
+#
+#        grep funcA input.txt | ./flamegraph.pl > graph.svg
+#
+# The input is stack frames and sample counts formatted as single lines.  Each
+# frame in the stack is semicolon separated, with a space and count at the end
+# of the line.  These can be generated using DTrace with stackcollapse.pl.
+#
+# The output graph shows relative presence of functions in stack samples.  The
+# ordering on the x-axis has no meaning; since the data is samples, time order
+# of events is not known.  The order used sorts function names alphabetically.
+#
+# HISTORY
+#
+# This was inspired by Neelakanth Nadgir's excellent function_call_graph.rb
+# program, which visualized function entry and return trace events.  As Neel
+# wrote: "The output displayed is inspired by Roch's CallStackAnalyzer which
+# was in turn inspired by the work on vftrace by Jan Boerhout".  See:
+# https://blogs.oracle.com/realneel/entry/visualizing_callstacks_via_dtrace_and
+#
+# Copyright 2011 Joyent, Inc.  All rights reserved.
+# Copyright 2011 Brendan Gregg.  All rights reserved.
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://opensource.org/licenses/CDDL-1.0.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+# 17-Mar-2013   Tim Bunce       Added options and more tunables.
+# 15-Dec-2011	Dave Pacheco	Support for frames with whitespace.
+# 10-Sep-2011	Brendan Gregg	Created this.
+
+use strict;
+
+use Getopt::Long;
+
+# tunables
+my $fonttype = "Verdana";
+my $imagewidth = 1200;		# max width, pixels
+my $frameheight = 16;		# max height is dynamic
+my $fontsize = 12;		# base text size
+my $fontwidth = 0.55;           # avg width relative to fontsize
+my $minwidth = 0.1;		# min function width, pixels
+my $titletext = "Flame Graph";  # centered heading
+my $nametype = "Function:";     # what are the names in the data?
+my $countname = "samples";      # what are the counts in the data?
+my $nameattrfile;               # file holding function attributes
+my $timemax;                    # (override the) sum of the counts
+my $factor = 1;                 # factor to scale counts by
+
+GetOptions(
+    'fonttype=s'   => \$fonttype,
+    'width=i'      => \$imagewidth,
+    'height=i'     => \$frameheight,
+    'fontsize=f'   => \$fontsize,
+    'fontwidth=f'  => \$fontwidth,
+    'minwidth=f'   => \$minwidth,
+    'title=s'      => \$titletext,
+    'nametype=s'   => \$nametype,
+    'countname=s'  => \$countname,
+    'nameattr=s'   => \$nameattrfile,
+    'total=s'      => \$timemax,
+    'factor=f'     => \$factor,
+) or exit 1;
+
+
+# internals
+my $ypad1 = $fontsize * 4;	# pad top, include title
+my $ypad2 = $fontsize * 2 + 10;	# pad bottom, include labels
+my $xpad = 10;			# pad left and right
+my $depthmax = 0;
+my %Events;
+my %nameattr;
+
+if ($nameattrfile) {
+    # The name-attribute file format is a function name followed by a tab then
+    # a sequence of tab separated name=value pairs.
+    open my $attrfh, $nameattrfile or die "Can't read $nameattrfile: $!\n";
+    while (<$attrfh>) {
+        chomp;
+        my ($funcname, $attrstr) = split /\t/, $_, 2;
+        die "Invalid format in $nameattrfile" unless defined $attrstr;
+        $nameattr{$funcname} = { map { split /=/, $_, 2 } split /\t/, $attrstr };
+    }
+}
+
+# SVG functions
+{ package SVG;
+	sub new {
+		my $class = shift;
+		my $self = {};
+		bless ($self, $class);
+		return $self;
+	}
+
+	sub header {
+		my ($self, $w, $h) = @_;
+		$self->{svg} .= <<SVG;
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" width="$w" height="$h" onload="init(evt)" viewBox="0 0 $w $h" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+SVG
+	}
+
+	sub include {
+		my ($self, $content) = @_;
+		$self->{svg} .= $content;
+	}
+
+	sub colorAllocate {
+		my ($self, $r, $g, $b) = @_;
+		return "rgb($r,$g,$b)";
+	}
+
+	sub group_start {
+		my ($self, $attr) = @_;
+
+		my @g_attr = map {
+			exists $attr->{$_} ? sprintf(qq/$_="%s"/, $attr->{$_}) : ()
+		} qw(class style onmouseover onmouseout);
+		push @g_attr, $attr->{g_extra} if $attr->{g_extra};
+		$self->{svg} .= sprintf qq/<g %s>\n/, join(' ', @g_attr);
+
+		$self->{svg} .= sprintf qq/<title>%s<\/title>/, $attr->{title}
+			if $attr->{title}; # should be first element within g container
+
+		if ($attr->{href}) {
+			my @a_attr;
+			push @a_attr, sprintf qq/xlink:href="%s"/, $attr->{href} if $attr->{href};
+                        # default target=_top else links will open within SVG <object>
+			push @a_attr, sprintf qq/target="%s"/, $attr->{target} || "_top";
+			push @a_attr, $attr->{a_extra}                           if $attr->{a_extra};
+			$self->{svg} .= sprintf qq/<a %s>/, join(' ', @a_attr);
+		}
+	}
+
+	sub group_end {
+		my ($self, $attr) = @_;
+		$self->{svg} .= qq/<\/a>\n/ if $attr->{href};
+		$self->{svg} .= qq/<\/g>\n/;
+	}
+
+	sub filledRectangle {
+		my ($self, $x1, $y1, $x2, $y2, $fill, $extra) = @_;
+		$x1 = sprintf "%0.1f", $x1;
+		$x2 = sprintf "%0.1f", $x2;
+		my $w = sprintf "%0.1f", $x2 - $x1;
+		my $h = sprintf "%0.1f", $y2 - $y1;
+		$extra = defined $extra ? $extra : "";
+		$self->{svg} .= qq/<rect x="$x1" y="$y1" width="$w" height="$h" fill="$fill" $extra \/>\n/;
+	}
+
+	sub stringTTF {
+		my ($self, $color, $font, $size, $angle, $x, $y, $str, $loc, $extra) = @_;
+		$loc = defined $loc ? $loc : "left";
+		$extra = defined $extra ? $extra : "";
+		$self->{svg} .= qq/<text text-anchor="$loc" x="$x" y="$y" font-size="$size" font-family="$font" fill="$color" $extra >$str<\/text>\n/;
+	}
+
+	sub svg {
+		my $self = shift;
+		return "$self->{svg}</svg>\n";
+	}
+	1;
+}
+
+sub color {
+	my $type = shift;
+	if (defined $type and $type eq "hot") {
+		my $r = 205 + int(rand(50));
+		my $g = 0 + int(rand(230));
+		my $b = 0 + int(rand(55));
+		return "rgb($r,$g,$b)";
+	}
+	return "rgb(0,0,0)";
+}
+
+my %Node;
+my %Tmp;
+
+sub flow {
+	my ($last, $this, $v) = @_;
+
+	my $len_a = @$last - 1;
+	my $len_b = @$this - 1;
+
+	my $i = 0;
+	my $len_same;
+	for (; $i <= $len_a; $i++) {
+		last if $i > $len_b;
+		last if $last->[$i] ne $this->[$i];
+	}
+	$len_same = $i;
+
+	for ($i = $len_a; $i >= $len_same; $i--) {
+		my $k = "$last->[$i];$i";
+		# a unique ID is constructed from "func;depth;etime";
+		# func-depth isn't unique, it may be repeated later.
+		$Node{"$k;$v"}->{stime} = delete $Tmp{$k}->{stime};
+		delete $Tmp{$k};
+	}
+
+	for ($i = $len_same; $i <= $len_b; $i++) {
+		my $k = "$this->[$i];$i";
+		$Tmp{$k}->{stime} = $v;
+	}
+
+        return $this;
+}
+
+# Parse input
+my @Data = <>;
+my $last = [];
+my $time = 0;
+my $ignored = 0;
+foreach (sort @Data) {
+	chomp;
+	my ($stack, $samples) = (/^(.*)\s+(\d+(?:\.\d*)?)$/);
+	unless (defined $samples) {
+            ++$ignored;
+            next;
+        }
+	$stack =~ tr/<>/()/;
+	$last = flow($last, [ '', split ";", $stack ], $time);
+	$time += $samples;
+}
+flow($last, [], $time);
+warn "Ignored $ignored lines with invalid format\n" if $ignored;
+die "ERROR: No stack counts found\n" unless $time;
+
+if ($timemax and $timemax < $time) {
+    warn "Specified --total $timemax is less than actual total $time, so ignored\n"
+        if $timemax/$time > 0.02; # only warn is significant (e.g., not rounding etc)
+    undef $timemax;
+}
+$timemax ||= $time;
+
+my $widthpertime = ($imagewidth - 2 * $xpad) / $timemax;
+my $minwidth_time = $minwidth / $widthpertime;
+
+# prune blocks that are too narrow and determine max depth
+while (my ($id, $node) = each %Node) {
+	my ($func, $depth, $etime) = split ";", $id;
+	my $stime = $node->{stime};
+	die "missing start for $id" if not defined $stime;
+
+	if (($etime-$stime) < $minwidth_time) {
+	    delete $Node{$id};
+	    next;
+	}
+	$depthmax = $depth if $depth > $depthmax;
+}
+
+# Draw canvas
+my $imageheight = ($depthmax * $frameheight) + $ypad1 + $ypad2;
+my $im = SVG->new();
+$im->header($imagewidth, $imageheight);
+my $inc = <<INC;
+<defs >
+	<linearGradient id="background" y1="0" y2="1" x1="0" x2="0" >
+		<stop stop-color="#eeeeee" offset="5%" />
+		<stop stop-color="#eeeeb0" offset="95%" />
+	</linearGradient>
+</defs>
+<style type="text/css">
+	.func_g:hover { stroke:black; stroke-width:0.5; }
+</style>
+<script type="text/ecmascript">
+<![CDATA[
+	var details;
+	function init(evt) { details = document.getElementById("details").firstChild; }
+	function s(info) { details.nodeValue = "$nametype " + info; }
+	function c() { details.nodeValue = ' '; }
+]]>
+</script>
+INC
+$im->include($inc);
+$im->filledRectangle(0, 0, $imagewidth, $imageheight, 'url(#background)');
+my ($white, $black, $vvdgrey, $vdgrey) = (
+	$im->colorAllocate(255, 255, 255),
+	$im->colorAllocate(0, 0, 0),
+	$im->colorAllocate(40, 40, 40),
+	$im->colorAllocate(160, 160, 160),
+    );
+$im->stringTTF($black, $fonttype, $fontsize + 5, 0.0, int($imagewidth / 2), $fontsize * 2, $titletext, "middle");
+$im->stringTTF($black, $fonttype, $fontsize, 0.0, $xpad, $imageheight - ($ypad2 / 2), " ", "", 'id="details"');
+
+# Draw frames
+
+while (my ($id, $node) = each %Node) {
+	my ($func, $depth, $etime) = split ";", $id;
+	my $stime = $node->{stime};
+
+	$etime = $timemax if $func eq "" and $depth == 0;
+
+	my $x1 = $xpad + $stime * $widthpertime;
+	my $x2 = $xpad + $etime * $widthpertime;
+	my $y1 = $imageheight - $ypad2 - ($depth + 1) * $frameheight + 1;
+	my $y2 = $imageheight - $ypad2 - $depth * $frameheight;
+
+	my $samples = sprintf "%.0f", ($etime - $stime) * $factor;
+        (my $samples_txt = $samples) # add commas per perlfaq5
+            =~ s/(^[-+]?\d+?(?=(?>(?:\d{3})+)(?!\d))|\G\d{3}(?=\d))/$1,/g;
+
+	my $info;
+	if ($func eq "" and $depth == 0) {
+		$info = "all ($samples_txt $countname, 100%)";
+	} else {
+		my $pct = sprintf "%.2f", ((100 * $samples) / ($timemax * $factor));
+		my $escaped_func = $func;
+		$escaped_func =~ s/&/&amp;/g;
+		$escaped_func =~ s/</&lt;/g;
+		$escaped_func =~ s/>/&gt;/g;
+		$info = "$escaped_func ($samples_txt $countname, $pct%)";
+	}
+
+        my $nameattr = { %{ $nameattr{$func}||{} } }; # shallow clone
+        $nameattr->{class}       ||= "func_g";
+        $nameattr->{onmouseover} ||= "s('".$info."')";
+        $nameattr->{onmouseout}  ||= "c()";
+        $nameattr->{title}       ||= $info;
+        $im->group_start($nameattr);
+
+	$im->filledRectangle($x1, $y1, $x2, $y2, color("hot"), 'rx="2" ry="2"');
+
+	my $chars = int( ($x2 - $x1) / ($fontsize * $fontwidth));
+	if ($chars >= 3) { # room for one char plus two dots
+		my $text = substr $func, 0, $chars;
+		substr($text, -2, 2) = ".." if $chars < length $func;
+		$text =~ s/&/&amp;/g;
+		$text =~ s/</&lt;/g;
+		$text =~ s/>/&gt;/g;
+		$im->stringTTF($black, $fonttype, $fontsize, 0.0, $x1 + 3, 3 + ($y1 + $y2) / 2, $text, "");
+	}
+
+        $im->group_end($nameattr);
+}
+
+print $im->svg;
@@ -0,0 +1,222 @@
+#!/usr/bin/perl
+##########################################################
+# This script is part of the Devel::NYTProf distribution
+#
+# Copyright, contact and other information can be found
+# at the bottom of this file, or by going to:
+# http://search.cpan.org/dist/Devel-NYTProf/
+#
+##########################################################
+
+use warnings;
+use strict;
+
+use Devel::NYTProf::Core;
+require Devel::NYTProf::Data;
+
+our $VERSION = '5.06';
+    
+use Data::Dumper;
+use Getopt::Long;
+use Carp;
+
+GetOptions(
+    'help|h'    => \&usage,
+    'verbose|v' => \my $opt_verbose,
+    'calls!'    => \my $opt_calls, # sum calls instead of time
+    'debug|d'   => \my $opt_debug,
+    'stable'    => \my $opt_stable, # used for testing (stability)
+) or usage();
+
+$opt_verbose++ if $opt_debug;
+$|++ if $opt_verbose;
+
+usage() unless @ARGV;
+
+
+# We're building a tree structure from a stream of "subroutine returned" events.
+# (We use these because the subroutine entry events don't have reliable
+# value for the subroutine name, and obviously don't have timings.)
+#
+# Building a call tree from return events is a little tricky because they don't
+# appear in natural order. The code can return from a call at any depth
+# deeper than the last seen depth.
+#
+my $root = {};
+my @stack = ($root);
+my $total_in = 0;
+
+my $last_subid = 0;
+my %subname2id;
+
+my $sibling_avoided = 0;
+my $siblings_max = 0;
+
+my %option;
+my %attribute;
+
+
+my $callbacks = {
+
+    OPTION    => sub { my (undef, $k, $v) = @_; $option{$k} = $v },
+    ATTRIBUTE => sub { my (undef, $k, $v) = @_; $attribute{$k} = $v },
+
+    SUB_ENTRY => sub {
+        my (undef, $fid, $line) = @_;
+        warn "> at $fid:$line\n" if $opt_verbose;
+    },
+
+    SUB_RETURN => sub {
+        # $retn_depth is the call stack depth of the sub call we're returning from
+        my (undef, $retn_depth, undef, $excl_time, $subname) = @_;
+
+        warn sprintf "< %2d %-10s %s (stack %d)\n", $retn_depth, $subname, $excl_time, scalar @stack
+            if $opt_verbose;
+
+        my $v = ($opt_calls) ? 1 : $excl_time;
+        $total_in += $v;
+
+        # normalize and merge sibling string evals by setting eval seqn to 0
+        $subname =~ s/\( (\w*eval)\s\d+ \) (?= \[ .+? :\d+ \] )/($1 0)/gx;
+        # assign an id to the subname for memory efficiency
+        my $subid = $subname2id{$subname} ||= ++$last_subid;
+
+        # Either...
+        # a) we're returning from some sub deeper than the current stack
+        #    in which case we push unnamed sub calls ("0") onto the stack
+        #    till we get to the right depth, then fall through to:
+        # b) we're returning from the sub on top of the stack.
+
+        while (@stack <= $retn_depth) { # build out the tree if needed
+            my $crnt_node = $stack[-1];
+            die "panic" if $crnt_node->{0};
+            push @stack, ($crnt_node->{0} = {});
+        }
+
+        # top of stack:  sub we're returning from
+        # next on stack: sub that was the caller
+        my $sub_return = pop @stack;
+        my $sub_caller = $stack[-1] || die "panic";
+
+        die "panic" unless $sub_return == $sub_caller->{0};
+        delete $sub_caller->{0} or die "panic"; # == $sub_return
+
+        # {
+        #   0 - as-yet un-returned subs
+        #   'v' - cumulative excl_time in this sub
+        #   $subid1 => {...} # calls to $subid1 made by this sub
+        #   $subid2 => {...}
+        # }
+
+        $sub_return->{v} += $v;
+        _merge_sub_return_into_caller($sub_caller->{$subid} ||= {}, $sub_return);
+    },
+};
+
+
+foreach my $input (@ARGV) {
+    warn "Reading $input...\n" if $opt_verbose;
+    Devel::NYTProf::Data->new({
+        filename => $input,
+        quiet => 1,
+        callback => $callbacks
+    });
+}
+
+
+# transform tree into a simple hash of call paths "subid;subid;subid" => value
+my %subidpaths;
+visit_node($root, [], sub {
+    my ($node, $path) = @_;
+    $subidpaths{ join(";", @$path) } += $node->{v}
+        if @$path;
+});
+
+# output the totals without scaling, so they're in ticks_per_sec units
+my $val_scale_factor = 1; # ($opt_calls) ? 1 : 1_000_000 / $attribute{ticks_per_sec};
+my $val_format = ($opt_calls || $val_scale_factor==1) ? "%s %d\n" : "%s %.1f\n";
+my $total_out = 0;
+
+# ensure subnames don't contain ";" or " "
+tr/; /??/ for values %subname2id;
+my %subid2name = reverse %subname2id;
+
+# output the subidpaths hash using subroutine names
+my @subidpaths = keys %subidpaths;
+@subidpaths = sort @subidpaths if $opt_stable;
+for my $subidpath (@subidpaths) {
+    my @path = map { $subid2name{$_} } split ";", $subidpath;
+    my $path = join(";", @path);
+    my $v = $subidpaths{$subidpath};
+    printf $val_format, join(";", @path), $v * $val_scale_factor;
+    $total_out += $v;
+}
+
+warn "nytprofcalls inconsistency: total in $total_in doesn't match total out $total_out\n"
+    if $total_in != $total_out;
+
+warn sprintf "Done Total $total_in (siblings: avoided $sibling_avoided, max $siblings_max)\n"
+    if $opt_verbose;
+
+exit 0;
+
+
+sub _merge_sub_return_into_caller {
+    my ($dest, $new, $recurse) = @_;
+    $dest->{v} += delete $new->{v};
+    while ( my ($new_called_subid, $new_called_node) = each %$new ) {
+        if ($dest->{$new_called_subid}) {
+            _merge_sub_return_into_caller($dest->{$new_called_subid}, $new_called_node);
+        }
+        else {
+            $dest->{$new_called_subid} = $new_called_node;
+        }
+    }
+}
+
+
+sub visit_node { # depth first
+    my $node = shift;
+    my $path = shift;
+    my $sub  = shift;
+
+    warn "visit_node: @{[ %$node ]}\n" if $opt_debug;
+
+    push @$path, undef;
+    while ( my ($subid, $childnode) = each %$node) {
+        next if $subid eq 'v';
+        die "panic" if $subid eq '0';
+
+        $path->[-1] = $subid;
+        warn "node @$path: @{[ %$childnode ]}\n" if $opt_debug;
+        visit_node($childnode, $path, $sub);
+    }
+    pop @$path;
+
+    $sub->($node, $path);
+
+    %$node = (); # reclaim memory as we go
+}
+
+
+sub usage {
+    print <<END;
+usage: [perl] nytprofcalls [opts] nytprof-file [...]
+
+ --help,        -h         Print this message
+ --verbose,     -v         Be more verbose
+
+This script of part of the Devel::NYTProf distribution.
+See https://metacpan.org/release/Devel-NYTProf for details and copyright.
+END
+    exit 0;
+}
+
+__END__
+
+=head1 NAME
+
+nytprofcalls - experimental
+
+=cut
+# vim:ts=8:sw=4:et
@@ -5,8 +5,6 @@
 ## See http://search.cpan.org/dist/Devel-NYTProf/
 ##
 ##########################################################
-# $Id: /mirror/devel-nytprof/bin/nytprofhtml 13295 2009-04-06T20:34:49.946854Z tim.bunce  $
-###########################################################
 use warnings;
 use strict;
 
@@ -7,8 +7,6 @@
 # http://search.cpan.org/dist/Devel-NYTProf/
 #
 ##########################################################
-# $Id$
-##########################################################
 
 use warnings;
 use strict;
@@ -50,7 +48,7 @@ if (!-r $opt{file}) {
     die "$0: Unable to access $opt{file}\n";
 }
 
-# handle handle output location
+# handle output location
 if (!-e $opt{out}) {
 
     # will be created
@@ -131,7 +129,7 @@ __END__
 
 =head1 NAME
 
-nytprofcsv - L<Devel::NYTProf::Reader> CSV format implementation
+nytprofcsv - (DEPRECATED) L<Devel::NYTProf::Reader> CSV format implementation
 
 =head1 SYNOPSIS
 
@@ -141,6 +139,10 @@ nytprofcsv - L<Devel::NYTProf::Reader> CSV format implementation
  nytprofcsv
  Generating CSV Output...
 
+=head1 NOTE
+
+B<nytprofcsv is deprecated and will be removed in a future release.>
+
 =head1 HISTORY
 
 A bit of history and a shameless plug...
@@ -195,8 +197,6 @@ Print the help message
  0,0,0,#--------------------------------------------------------------------
  0,0,0,# My New Source File!
  0,0,0,#--------------------------------------------------------------------
- 0,0,0,# $Id$
- 0,0,0,#--------------------------------------------------------------------
  0,0,0,
  0,0,0,package NYT::Feeds::Util;
  0.00047,3,0.000156666666666667,use Date::Calc qw(Add_Delta_DHMS);
@@ -59,7 +59,7 @@ use Devel::NYTProf::Util qw(
 );
 use Devel::NYTProf::Constants qw(NYTP_SCi_CALLING_SUB);
 
-our $VERSION = '4.23';
+our $VERSION = '5.06';
 
 if ($VERSION != $Devel::NYTProf::Core::VERSION) {
     die "$0 version '$VERSION' doesn't match version '$Devel::NYTProf::Core::VERSION' of $INC{'Devel/NYTProf/Core.pm'}\n";
@@ -88,6 +88,7 @@ GetOptions(
     'open!'     => \my $opt_open,
     'help|h'    => sub { exit usage() },
     'minimal|m!'=> \my $opt_minimal,
+    'flame!'    => \(my $opt_flame = 1),
     'mergeevals!'=> \(my $opt_mergeevals = 1),
     'profself!'     => sub { }, # handled in BEGIN above
     'debug!'        => \my $opt_debug,
@@ -104,6 +105,7 @@ usage: [perl] nytprofhtml [opts]
  --delete,      -d         Delete any old report files in <dir> first
  --open                    Open the generated report in a web browser
  --lib <lib>,   -l <lib>   Add <lib> to the beginning of \@INC
+ --no-flame                Disable flame graph (and call stacks processing)
  --minimal,     -m         Don't generate graphviz .dot files or block/sub-level reports
  --no-mergeevals           Disable merging of string evals
  --help,        -h         Print this message
@@ -115,7 +117,7 @@ END
 }
 
 
-# handle handle output location
+# handle output location
 if (!-e $opt_out) {
     # will be created
 }
@@ -243,7 +245,7 @@ modules.</p>
 
 </div>\n}
     }
-);
+) if $] < 5.017008;
 
 $reporter->set_param(
     'merged_fids',
@@ -757,10 +759,12 @@ sub output_index_page {
     my $summary = sprintf "Profile of %s for %s (of %s),", $application,
         fmt_time($profile->{attribute}{profiler_active}),
         fmt_time($profile->{attribute}{profiler_duration});
-    $summary .= sprintf " executing %d statements",
+    $summary .= " executing";
+    $summary .= sprintf " %d statements and",
          $profile->{attribute}{total_stmts_measured}
-        -$profile->{attribute}{total_stmts_discounted};
-    $summary .= sprintf " and %d subroutine calls",
+        -$profile->{attribute}{total_stmts_discounted}
+        if $profile->{option}{stmts};
+    $summary .= sprintf " %d subroutine calls",
          $profile->{attribute}{total_sub_calls};
     $summary .= sprintf " in %d source files",
         @all_fileinfos - $eval_fileinfos;
@@ -780,6 +784,50 @@ sub output_index_page {
         print $fh "</select></form></div>\n";
     }
 
+    my $call_stacks_file = "all_stacks_by_time.calls";
+    my $call_stacks_svg  = "all_stacks_by_time.svg";
+    if ($profile->{option}{calls} && $opt_flame) {
+        my $mk_flamegraph = sub {
+
+            warn sprintf "Extracting subroutine call data%s ...\n",
+                ($profile->{attribute}{total_sub_calls} <= 1_000_000) ? ""
+                : " (there were $profile->{attribute}{total_sub_calls} of them, so this may take some time)";
+            system("nytprofcalls $opt_file > $opt_out/$call_stacks_file") == 0
+                or die "Generating $opt_out/$call_stacks_file failed\n";
+
+            my %subname_subinfo_map = %{ $profile->subname_subinfo_map };
+            warn "Extracting subroutine links\n";
+            my $subattr = "$opt_out/flamegraph_subattr.txt";
+            open my $subattrfh, ">", $subattr
+                or die "Error creating $subattr: $!\n";
+            while ( my ($subname, $si) = each %subname_subinfo_map ) {
+                next unless $si->incl_time;
+                print $subattrfh join("\t", $subname,
+                    q{href=}.$reporter->url_for_sub($subname),
+                )."\n";
+            }
+            close $subattrfh or die "Error writing $subattr: $!\n";
+
+            warn "Generating subroutine stack flame graph ...\n";
+            # factor to scale the values to microseconds
+            my $factor = 1_000_000 / $profile->{attribute}{ticks_per_sec};
+            # total (width) for flamegraph is profiler_active in ticks
+            my $run_us = $profile->{attribute}{profiler_active} * $profile->{attribute}{ticks_per_sec};
+            my $extension = $^O eq "MSWin32" ? "" : ".pl";
+            my $fg_cmd = "flamegraph$extension --nametype=sub --countname=microseconds";
+            system("$fg_cmd --factor=$factor --nameattr=$subattr --total=$run_us $opt_out/$call_stacks_file > $opt_out/$call_stacks_svg") == 0
+                or die "Generating $opt_out/$call_stacks_svg failed\n";
+
+            print $fh qq{<div class="flamegraph">\n};
+            print $fh qq{<object data="$call_stacks_svg" width="1200" type="image/svg+xml" >SVG not supported</object>\n};
+            print $fh qq{<p>The <a href="http://dtrace.org/blogs/brendan/2011/12/16/flame-graphs/">Flame Graph</a> above is a visualization of the time spent in <em>distinct call stacks</em>. The colors and x-axis position are not meaningful.</p>\n};
+            print $fh qq{</div>\n};
+            1;
+        };
+        eval { $mk_flamegraph->() }
+            or warn $@;
+    }
+
     # Show top subs across all files
     my $max_subs = 15; # keep it less than a page so users can see the file table
     my $all_subs = keys %{$profile->{sub_subinfo}};
@@ -1356,7 +1404,10 @@ sub output_file_table {
             $eval_time  = sum(map { $_->sum_of_stmts_time  } @has_evals);
         }
         # is this file one where we sawampersand (or contains an eval that is)?
-        if ($sawampersand_fi && $fi == ($sawampersand_fi->outer || $sawampersand_fi)) {
+        if ($sawampersand_fi
+            && $] < 5.017008
+            && $fi == ($sawampersand_fi->outer || $sawampersand_fi)
+        ) {
             my $in_eval = ($fi == $sawampersand_fi)
                 ? 'here'
                 : sprintf q{<a %s>in eval here</a>}, $reporter->href_for_file($sawampersand_fi, undef, 'line');
@@ -1557,6 +1608,7 @@ This file was generated by Devel::NYTProf version $Devel::NYTProf::Core::VERSION
 <head>
     <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
     <meta http-equiv="Content-Language" content="en-us" />
+    <meta name="robots" content="noindex,nofollow" />
     <title>$title</title>
 EOD
 
@@ -1587,7 +1639,7 @@ EOD
             // console.log(orig);
             var val = orig.replace(/ns/,'');
             if (val != orig) { return val / (1000*1000*1000); } 
-            val = orig.replace(/[µ\xB5]s/,''); /* allow for browser encoding XXX use &micro; ? */
+            val = orig.replace(/[µ\xB5]s/,''); /* micro */
             if (val != orig) { return val / (1000*1000); } 
             val = orig.replace(/ms/,'');
             if (val != orig) { return val / (1000); }
@@ -1855,6 +1907,10 @@ pre,.s {
     padding-top: 5px; color: gray;
 }
 
+.flamegraph {
+    margin: 20px 0px;
+}
+
 EOD
 }
 
@@ -1909,6 +1965,16 @@ Make your web browser visit the report after it has been generated.
 
 If this doesn't work well for you, try installing the L<Browser::Open> module.
 
+=item -m, --minimal
+
+Don't generate graphviz .dot files or block/sub-level reports.
+
+=item --no-flame
+
+Disable generation of the framegraph on the index page.
+Also disables calculation of distinct call stacks that are used to produce the
+flamegraph.
+
 =item -h, --help
 
 Print the help message.
@@ -7,8 +7,6 @@
 # http://search.cpan.org/dist/Devel-NYTProf/
 #
 ##########################################################
-# $Id$
-##########################################################
 
 use warnings;
 use strict;
@@ -18,7 +16,7 @@ require Devel::NYTProf::FileHandle;
 require Devel::NYTProf::Data;
 use List::Util qw(min sum);
 
-our $VERSION = '4.23';
+our $VERSION = '5.06';
     
 if ($VERSION != $Devel::NYTProf::Core::VERSION) {
     die "$0 version '$VERSION' doesn't match version '$Devel::NYTProf::Core::VERSION' of $INC{'Devel/NYTProf/Core.pm'}\n";
@@ -82,9 +80,11 @@ my %attr_should_be_identical = map {$_, 1} qw(
     PL_perldb clock_id nv_size perl_version ticks_per_sec xs_version
 );
 
+
 # Effectively, these are global variables. Sorry.
 our $input;
 our %attributes;
+our %options;
 our $deflating;
 
 my %dispatcher =
@@ -112,6 +112,7 @@ my %dispatcher =
          return if $text =~ /\ACompressed at level \d with zlib [0-9.]+\z/;
          $out->write_comment($text)
      },
+
      ATTRIBUTE => sub {
          my (undef, $key, $value) = @_;
          if ($attr_should_be_identical{$key}) {
@@ -128,6 +129,19 @@ my %dispatcher =
          }
      },
 
+     OPTION => sub {
+         my (undef, $key, $value) = @_;
+
+        if (exists $options{$key}) {
+            if ($options{$key} ne $value) {
+                warn("Option '$key' has value '$value' in $input which differs from the previous value '$options{$key}'; this implies inconsistent profiles and thus garbage results\n");
+            }
+        } else {
+            $options{$key} = $value;
+            $out->write_option($key, $value);
+        }
+    },
+
      START_DEFLATE => sub {
          if (!$deflating && $out->can('start_deflate_write_tag_comment')) {
              $out->start_deflate_write_tag_comment;
@@ -193,6 +207,18 @@ my %dispatcher =
                  };
          }
      },
+
+    SUB_ENTRY => sub {
+        my (undef, $fid, $line) = @_;
+        confess("No mapping for $fid") unless defined $fids{$fid};
+        $fid = $fids{$fid};
+        $out->write_call_entry($fid, $line);
+    },
+    SUB_RETURN => sub {
+        my (undef, $retn_depth, $incl_time, $excl_time, $subname) = @_;
+        $out->write_call_return($retn_depth, $subname, $incl_time, $excl_time);
+    },
+
      SRC_LINE => sub {
          my (undef, $fid, $line, $text) = @_;
          confess("No mapping for $fid") unless defined $fids{$fid};
@@ -202,13 +228,14 @@ my %dispatcher =
          $fid = $mapped_fid if defined $mapped_fid;
          $out->write_src_line($fid, $line, $text);
      },
-    );
+);
 
 foreach $input (@ARGV) {
     print "Reading $input...\n" if $opt_verbose;
     @pending_fids = ();
     %pending_subs = ();
 
+    # first pass
     Devel::NYTProf::Data->new({filename => $input, callback => {
         NEW_FID => sub {
             my (undef, $fid, $eval_fid, $eval_line, $flags, $size, $mtime, $name) = @_;
@@ -279,7 +306,7 @@ foreach $input (@ARGV) {
                     # to the sub are collated.
 
                     # Have to use the mapped fid as the key to this hash, as
-                    # only the mapped fids are are unique
+                    # only the mapped fids are unique
                     my $mapped_fid = $fids{$fid};
                     $map_range{$mapped_fid}[$_] = $folded
                         for $first_line .. $last_line;
@@ -294,6 +321,7 @@ foreach $input (@ARGV) {
         }
     }});
 
+    # second pass
     print "Re-reading $input...\n" if $opt_verbose;
     Devel::NYTProf::Data->new({filename => $input, callback => \%dispatcher});
 }
@@ -340,12 +368,14 @@ foreach my $key (sort grep {!$attr_should_be_identical{$_}} keys %attributes) {
         $out->write_attribute($key, $values[0]);
     }
     else {
-        warn sprintf "Unknown attribute %s has %d values passed through unmerged\n",
-            $key, scalar @values;
+        warn sprintf "Attribute %s has %d distinct values passed through unmerged\n",
+                $key, scalar @values
+            if @values > 1;
         $out->write_attribute($key, $_) foreach @values;
     }
 }
 
+
 print "Done.\n" if $opt_verbose;
 exit 0;
 
@@ -1,125 +0,0 @@
-#! /usr/bin/env perl
-# vim: ts=8 sw=4 sts=4 expandtab:
-##########################################################
-## This script is part of the Devel::NYTProf distribution
-##
-## Copyright, contact and other information can be found
-## at the bottom of this file, or by going to:
-## http://search.cpan.org/perldoc?Devel::NYTProf
-##
-###########################################################
-## $Id: benchmark.pl 322 2008-07-15 04:33:35Z tim.bunce $
-###########################################################
-use warnings;
-use strict;
-
-use Carp;
-use Config;
-use Getopt::Long;
-use Benchmark qw(:hireswallclock timethese cmpthese);
-use Devel::NYTProf::Data; # just to print path
-
-GetOptions(
-    'v|verbose' => \my $opt_verbose,
-) or exit 1;
-
-my $regex = shift;
-
-my $subs_count = shift || 2000;
-my $loop_count = shift || 1000;
-
-# simple benchmark script to measure profiling overhead
-my $test_script = "benchmark_code.pl";
-open my $fh, ">", $test_script or die "Can't write to $test_script: $!\n";
-print $fh q{
-    my $subs_count = shift || die "No subs count";
-    my $loop_count = shift || die "No loop count";
-    sub foo {
-        my $loop = shift;
-        my $a = 0;
-        while ($loop-- > 0) { ++$a; ++$a; ++$a; }
-    }
-    while ($subs_count-- > 0) {
-        foo($loop_count)
-    }
-};
-close $fh or die "Error writing to $test_script: $!\n";
-END { unlink $test_script };
-
-
-my %tests = (
-    baseline => {
-        perlargs => '',
-    },
-    dprof => {
-        perlargs => '-d:DProf',
-        datafile => 'tmon.out',
-    },
-    fastprof => {
-        perlargs => '-MDevel::FastProf',
-        datafile => 'fastprof.out',
-    },
-    profit => {
-        perlargs => '-MDevel::Profit',
-        datafile => 'profit.out',
-    },
-    nytprof_o => {
-        env => [ NYTPROF => 'use_db_sub=0:file=nytprof_o.out' ],
-        perlargs => '-d:NYTProf',
-        datafile => 'nytprof_o.out',
-    },
-    nytprof_s => {
-        env => [ NYTPROF => 'use_db_sub=1:file=nytprof_s.out' ],
-        perlargs => '-d:NYTProf',
-        datafile => 'nytprof_s.out',
-    },
-    nytprof_ob => {
-        env => [ NYTPROF => 'blocks:file=nytprof_ob.out' ],
-        perlargs => '-d:NYTProf',
-        datafile => 'nytprof_ob.out',
-    },
-);
-
-my %test_subs;
-while ( my ($testname, $testinfo) = each %tests ) {
-    if ($regex && $testname ne 'baseline' && $testname !~ m/$regex/o) {
-        warn "Skipped $testname\n";
-        next;
-    }
-    if (!run_test($testinfo, 1, 1)) {
-        warn "Can't run $testname profiler - skipped\n";
-        next;
-    }
-    $testinfo->{testname} = $testname;
-    $test_subs{$testname} = sub { run_test($testinfo, $subs_count, $loop_count) };
-}
-
-printf "Profiler performance using perl %8s %s (%s %s %s)\n",
-    $], $Config{archname},
-    $Config{gccversion} ? 'gcc' : $Config{cc},
-    (split / /, $Config{gccversion}||$Config{ccversion}||'')[0]||'',
-    $Config{optimize};
-printf "NYTProf is $INC{'Devel/NYTProf/Data.pm'}\n";
-
-cmpthese(4, \%test_subs, 'nop');
-
-for my $testname (sort keys %test_subs) {
-    my $testinfo = $tests{$testname};
-    if ($testinfo->{datafile}) {
-        printf "%10s: %6dKB %s\n",
-            $testname, (-s $testinfo->{datafile})/1024, $testinfo->{datafile};
-        unlink $testinfo->{datafile};
-    }
-}
-
-exit 0;
-
-sub run_test {
-    my($testinfo, $subs_count, $loop_count) = @_;
-
-    my $env = $testinfo->{env};
-    local $ENV{$env->[0]} = $env->[1] if $env;
-
-    my $cmd = "perl $testinfo->{perlargs} $test_script $subs_count $loop_count";
-    system($cmd) == 0;
-}
@@ -7,8 +7,6 @@
 # http://search.cpan.org/dist/Devel-NYTProf/
 #
 ###########################################################
-# $Id$
-###########################################################
 package Devel::NYTProf::Apache;
 
 our $VERSION = '4.00';
@@ -7,14 +7,12 @@
 # http://search.cpan.org/dist/Devel-NYTProf/
 #
 ###########################################################
-# $Id$
-###########################################################
 package Devel::NYTProf::Core;
 
 
 use XSLoader;
 
-our $VERSION = '4.23';    # increment with XS changes too
+our $VERSION = '5.06';    # increment with XS changes too
 
 XSLoader::load('Devel::NYTProf', $VERSION);
 
@@ -7,8 +7,6 @@
 # http://search.cpan.org/dist/Devel-NYTProf/
 #
 ###########################################################
-# $Id$
-###########################################################
 package Devel::NYTProf::Data;
 
 =head1 NAME
@@ -223,6 +221,10 @@ sub attributes {
     return shift->{attribute} || {};
 }
 
+sub options {
+    return shift->{option} || {};
+}
+
 sub subname_subinfo_map {
     return { %{ shift->{sub_subinfo} } }; # shallow copy
 }
@@ -309,7 +311,7 @@ sub packages_at_depth_subinfo {
         my @parts = split /::/, $fullpkgname; # drops empty trailing part
 
         # accumulate @$subinfos for the full package name
-        # and also for each succesive truncation of the package name
+        # and also for each successive truncation of the package name
         for (my $depth; $depth = @parts; pop @parts) {
             my $pkgname = join('::', @parts, '');
 
@@ -396,7 +398,7 @@ sub inc {
       separator  => "",
   } );
 
-Writes the profile data in a reasonably human friendly format to the sepcified
+Writes the profile data in a reasonably human friendly format to the specified
 C<filehandle> (default STDOUT).
 
 For non-trivial profiles the output can be very large. As a guide, there'll be
@@ -408,7 +410,7 @@ The types of data present can depend on the options used when profiling.
 
 If C<separator> is true then instead of whitespace, each item of data is
 indented with the I<path> through the structure with C<separator> used to
-separarate the elements of the path.
+separate the elements of the path.
 This format is especially useful for grep'ing and diff'ing.
 
 =cut
@@ -587,7 +589,12 @@ filenames: eval sequence numbers, like "(re_eval 2)" are changed to 0
 
 
 sub normalize_variables {
-    my $self       = shift;
+    my ($self, $normalize_options) = @_;
+
+    if ($normalize_options) {
+        %{ $self->options } = ();
+    }
+
     my $attributes = $self->attributes;
 
     for my $attr (qw(
@@ -172,7 +172,7 @@ hashes with file id integers as keys and FileInfo objects as values.
 sub evals_by_line {
     my ($self) = @_;
 
-    # find all fids that have have this fid as an eval_fid
+    # find all fids that have this fid as an eval_fid
     # { line => { fid_of_eval_at_line => $fi, ... } }
 
     my %evals_by_line;
@@ -144,6 +144,12 @@ The path to the program that ran; same as C<$0> in the program itself.
 
 =back
 
+=item OPTION => $key, $value
+
+This chunk type is repeated at the beginning of the file and used to record the
+options, e.g. set via the NYTPROF env var, that were effect during the
+profiling run.
+
 =item START_DEFLATE
 
 This chunk just say that from now on all chunks have been compressed
@@ -7,8 +7,6 @@
 ## http://search.cpan.org/dist/Devel-NYTProf/
 ##
 ###########################################################
-## $Id$
-###########################################################
 package Devel::NYTProf::Reader;
 
 our $VERSION = '4.06';
@@ -552,15 +550,15 @@ sub _generate_report {
 sub url_for_file {
     my ($self, $file, $anchor, $level) = @_;
     confess "No file specified" unless $file;
+    $level ||= '';
 
-    my $fi = $self->{profile}->fileinfo_of($file);
-    #return "" if $fi->is_fake;
-    $level = 'line' if $fi->is_eval;
+    my $url = $self->{_cache}{"url_for_file,$file,$level"} ||= do {
+        my $fi = $self->{profile}->fileinfo_of($file);
+        $level = 'line' if $fi->is_eval;
+        $self->fname_for_fileinfo($fi, $level) . ".html";
+    };
 
-    my $url = $self->fname_for_fileinfo($fi, $level);
-    $url .= '.html';
     $url .= "#$anchor" if defined $anchor;
-
     return $url;
 }
 
@@ -9,8 +9,6 @@ package Devel::NYTProf::Run;
 # http://search.cpan.org/dist/Devel-NYTProf/
 #
 ###########################################################
-# $Id: Util.pm 809 2009-07-07 13:24:31Z tim.bunce $
-###########################################################
 
 =head1 NAME
 
@@ -61,7 +59,7 @@ sub perl_command_words {
 
 # croaks on failure to execute
 # carps, not croak, if process has non-zero exit status
-# Devel::NYTProf::Data->new may croak, e.g., if data trucated
+# Devel::NYTProf::Data->new may croak, e.g., if data truncated
 sub profile_this {
     my %opt = @_;
 
@@ -7,8 +7,6 @@
 # http://search.cpan.org/dist/Devel-NYTProf/
 #
 ###########################################################
-# $Id$
-###########################################################
 package Devel::NYTProf::Util;
 
 =head1 NAME
@@ -27,6 +25,8 @@ B<Note:> The documentation for this module is currently incomplete and out of da
 
 =head1 FUNCTIONS
 
+=encoding ISO8859-1
+
 =cut
 
 
@@ -181,7 +181,7 @@ sub fmt_time {
     return sprintf $fmt_time_opt, $sec if $fmt_time_opt;
     return sprintf "%$width.0fs", 0    unless $sec;
     return sprintf "%$width.0fns",                              $sec * 1e9 if $sec < 1e-6;
-    return sprintf "%$width.0f&micro;s",                        $sec * 1e6 if $sec < 1e-3;
+    return sprintf "%$width.0fµs",                              $sec * 1e6 if $sec < 1e-3;
     return sprintf "%$width.*fms", 3 - length(int($sec * 1e3)), $sec * 1e3 if $sec < 1;
     return sprintf "%$width.*fs",  3 - length(int($sec      )), $sec       if $sec < 100;
     return sprintf "%$width.0fs", $sec;
@@ -7,11 +7,9 @@
 ## http://search.cpan.org/dist/Devel-NYTProf/
 ##
 ###########################################################
-## $Id$
-###########################################################
 package Devel::NYTProf;
 
-our $VERSION = '4.23'; # also change in Devel::NYTProf::Core
+our $VERSION = '5.06'; # also change in Devel::NYTProf::Core
 
 package    # hide the package from the PAUSE indexer
     DB;
@@ -182,7 +180,7 @@ statement executed in foo()>! Here's another example:
   }
 
 After the first time around the loop, any further time spent evaluating the
-condition (waiting for input in this example) would be be recorded as having
+condition (waiting for input in this example) would be recorded as having
 been spent I<on the last statement executed in the loop>! (Until perl bug
 #60954 is fixed this problem still applies to some loops. For more information
 see L<http://rt.perl.org/rt3/Ticket/Display.html?id=60954>)
@@ -204,8 +202,8 @@ For each subroutine called, separate counts and durations are stored I<for each
 location that called the subroutine>.
 
 Subroutine entry is detected by intercepting the C<entersub> opcode. Subroutine
-exit is detected via perl's internal save stack. The result is both extremely
-fast and very robust.
+exit is detected via perl's internal save stack. As a result the subroutine
+profiler is both fast and robust.
 
 =head3 Subroutine Recursion
 
@@ -370,13 +368,12 @@ If you find any other examples of the effect of optimizer on NYTProf output
 
 Set to 0 to disable the collection of subroutine caller and timing details.
 
-=head2 blocks=0
+=head2 blocks=1
 
-Set to 0 to disable the determination of block and subroutine location per statement.
-This makes the profiler about 50% faster (as of July 2008) and produces smaller
-output files, but you lose some valuable information. The extra cost is likely
-to be reduced in later versions anyway, as little optimization has been done on
-that part of the code.
+Set to 1 to enable the determination of block and subroutine location per statement.
+This makes the profiler about 50% slower (as of July 2008) and produces larger
+output files, but you gain some valuable insight in where time is spent in the
+blocks within large subroutines and scripts.
 
 =head2 stmts=0
 
@@ -387,6 +384,25 @@ This significantly reduces the overhead of the profiler and can also be useful
 for profiling large applications that would normally generate a very large
 profile data file.
 
+=head2 calls=N
+
+This option is I<new and experimental>.
+
+With calls=1 (the default) subroutine call I<return> events are emitted into
+the data stream as they happen.  With calls=2 subroutine call I<entry> events
+are also emitted. With calls=0 no subroutine call events are produced.
+This option depends on the C<subs> option being enabled, which it is by default.
+
+The L<nytprofcalls> utility can be used to process this data. It too is I<new
+and experimental> and so likely to change.
+
+The subroutine profiler normally gathers data in memory and outputs a summary
+when the profile data is being finalized, usually when the program has finished.
+The summary contains aggregate information for all the calls from one location
+to another, but the details of individual calls have been lost.
+The calls option enables the recording of individual call events and thus
+more detailed analysis and reporting of that data.
+
 =head2 leave=0
 
 Set to 0 to disable the extra work done by the statement profiler
@@ -496,9 +512,10 @@ When perl exits normally it runs any code defined in C<END> blocks.
 NYTProf defines an END block that finishes profiling and writes out the final
 profile data.
 
-If the process ends due to a signal then END blocks are not executed.
-The C<sigexit> option tells NYTProf to catch some signals (e.g. INT, HUP, PIPE,
-SEGV, BUS) and ensure a usable by executing:
+If the process ends due to a signal then END blocks are not executed so the
+profile will be incomplete and unusable.  The C<sigexit> option tells NYTProf
+to catch some signals (e.g. INT, HUP, PIPE, SEGV, BUS) and ensure a usable
+profile by executing:
 
     DB::finish_profile();
     exit 1;
@@ -518,6 +535,19 @@ When using the C<subs=0> option to disable the subroutine profiler the
 C<posix_exit> option can be used to tell NYTProf to take other steps to arrange
 for C<DB::finish_profile()> to be called before C<POSIX::_exit()>.
 
+=head2 libcexit=1
+
+Arranges for L</finish_profile> to be called via the C library C<atexit()> function.
+This may help some tricky cases where the process may exit without perl
+executing the C<END> block that NYTProf uses to call /finish_profile().
+
+=head2 endatexit=1
+
+Sets the PERL_EXIT_DESTRUCT_END flag in the PL_exit_flags of the perl interpreter.
+This makes perl run C<END> blocks in perl_destruct() instead of perl_run()
+which may help in cases, like Apache, where perl is embedded but perl_run()
+isn't called.
+
 =head2 forkdepth=N
 
 When a perl process that is being profiled executes a fork() the child process
@@ -567,7 +597,9 @@ to stop collecting profile data, and calling DB::enable_profile() to start
 collecting profile data.
 
 Using the C<start=no> option lets you leave the profiler disabled initially
-until you call DB::enable_profile() at the right moment.
+until you call DB::enable_profile() at the right moment. You still need to
+load Devel::NYTProf as early as possible, even if you don't call
+DB::enable_profile() until much later. See also L</use_db_sub=1>.
 
 The profile output file can't be used until it's been properly completed and
 closed.  Calling DB::disable_profile() doesn't do that.  To make a profile file
@@ -767,20 +799,20 @@ You can reduce the cost of profiling by adjusting some options. The trade-off
 is reduced detail and/or accuracy in reports.
 
 If you don't need statement-level profiling then you can disable it via L</stmts=0>.
-If you do want it but don't mind loosing block-level timings then set L</blocks=0>.
 To further boost statement-level profiling performance try L</leave=0> but note that
 I<will> apportion timings for some kinds of statements less accurate).
 
+If you don't need call stacks or flamegraph then disable it via L</calls=0>.
 If you don't need subroutine profiling then you can disable it via L</subs=0>.
 If you do need it but don't need timings for perl opcodes then set L</slowops=0>.
 
-Generally speaking, setting blocks=0 and slowops=0 will give you a useful boost
+Generally speaking, setting calls=0 and slowops=0 will give you a useful boost
 with the least loss of detail.
 
 Another approach is to only enable NYTProf in the sections of code that
 interest you. See L</RUN-TIME CONTROL OF PROFILING> for more details.
 
-To speed up L<nytprofhtml> try using the --minimal (-m) option.
+To speed up L<nytprofhtml> try using the --minimal (-m) or --no-flame options.
 
 =head1 REPORTS
 
@@ -800,15 +832,15 @@ Creates attractive, richly annotated, and fully cross-linked html
 reports (including statistics, source code and color highlighting).
 This is the main report generation tool for NYTProf.
 
-=head2 nytprofcsv
-
-Creates comma delimited profile reports. Old and limited.
-
 =head2 nytprofcg
 
 Translates a profile into a format that can be loaded into KCachegrind
 L<http://kcachegrind.sourceforge.net>
 
+=head2 nytprofcalls
+
+Reads a profile and processes the calls events it contains.
+
 =head2 nytprofmerge
 
 Reads multiple profile data files and writes out a new file containing the merged profile data.
@@ -980,6 +1012,10 @@ Why 'realtime' can appear to go backwards:
 
   http://preview.tinyurl.com/5wawnn
 
+The PostgreSQL pg_test_timing utility documentation has a good summary of timing issues:
+
+  http://www.postgresql.org/docs/9.2/static/pgtesttiming.html
+
 =for comment
 http://preview.tinyurl.com/5wawnn redirects to:
 http://groups.google.com/group/comp.os.linux.development.apps/tree/browse_frm/thread/dc29071f2417f75f/ac44671fdb35f6db?rnum=1&_done=%2Fgroup%2Fcomp.os.linux.development.apps%2Fbrowse_frm%2Fthread%2Fdc29071f2417f75f%2Fc46264dba0863463%3Flnk%3Dst%26rnum%3D1%26
@@ -1091,6 +1127,10 @@ it's very likely to be deprecated in a future release).
 L<Devel::NYTProf::ReadStream> is the module that lets you read a profile data
 file as a stream of chunks of data.
 
+Other tools:
+
+DTrace L<https://speakerdeck.com/mrallen1/perl-dtrace-and-you>
+
 =head1 TROUBLESHOOTING
 
 =head2 "Profile data incomplete, ..." or "File format error: ..."
@@ -1185,7 +1225,7 @@ For more details see L</HISTORY> below.
 =head1 COPYRIGHT AND LICENSE
 
   Copyright (C) 2008 by Adam Kaplan and The New York Times Company.
-  Copyright (C) 2008-2010 by Tim Bunce, Ireland.
+  Copyright (C) 2008-2013 by Tim Bunce, Ireland.
 
 This library is free software; you can redistribute it and/or modify
 it under the same terms as Perl itself, either Perl version 5.8.8 or,
@@ -1,11 +1,11 @@
 use Test::More;
 
 use strict;
-use lib qw(t/lib);
 use Config;
-use NYTProfTest;
+use Data::Dumper;
 
-plan tests => 20;
+use lib qw(t/lib);
+use NYTProfTest;
 
 use Devel::NYTProf::ReadStream qw(for_chunks);
 
@@ -15,7 +15,7 @@ my $pre589 = ($] < 5.008009 or $] eq "5.010000");
 
 # generate an nytprof out file
 my $out = 'nytprof_readstream.out';
-$ENV{NYTPROF} = "file=$out";
+$ENV{NYTPROF} = "calls=2:blocks=1:file=$out";
 unlink $out;
 
 run_perl_command(qq{-d:NYTProf -e "sub A { };" -e "1;" -e "A() $Devel::NYTProf::StrEvalTestPad"});
@@ -35,22 +35,34 @@ for_chunks {
     }
 } filename => $out;
 
+my %option    = map { @$_ } @{$prof{OPTION}};
+cmp_ok scalar keys %option, '>=', 17, 'enough options';
+#diag Dumper(\%option);
+
+my %attribute = map { @$_ } @{$prof{ATTRIBUTE}};
+cmp_ok scalar keys %attribute, '>=', 9, 'enough attribute';
+#diag Dumper(\%attribute);
+
 ok scalar @seqn, 'should have read chunks';
 is_deeply(\@seqn, [0..@seqn-1], "chunk seq");
 
 #use Data::Dumper; warn Dumper \%prof;
 
-is_deeply $prof{VERSION}, [ [ 4, 0 ] ];
+is_deeply $prof{VERSION}, [ [ 5, 0 ] ];
 
 # check for expected tags
 # but not START_DEFLATE as that'll be missing if there's no zlib
 # and not SRC_LINE as old perl's 
-for my $tag (qw(
-        COMMENT ATTRIBUTE DISCOUNT TIME_BLOCK
-        SUB_INFO SUB_CALLERS
-        PID_START PID_END NEW_FID
-)) {
-    is ref $prof{$tag}[0], 'ARRAY', $tag;
+my @expected_tags = qw(
+    COMMENT ATTRIBUTE OPTION DISCOUNT
+    SUB_INFO SUB_CALLERS
+    PID_START PID_END NEW_FID
+    SUB_ENTRY SUB_RETURN
+);
+push @expected_tags, 'TIME_BLOCK' if $option{calls};
+for my $tag (@expected_tags) {
+    is ref $prof{$tag}[0], 'ARRAY', "raw $tag array seen"
+        or diag Dumper $prof{$tag};
 }
 
 SKIP: {
@@ -77,3 +89,10 @@ $prof{SUB_CALLERS}[0][$_] = 0 for (3,4);
 is_deeply $prof{SUB_CALLERS}, [
     [ 1, 3, 1, 0, 0, '0', 0, 'main::A', 'main::RUNTIME' ]
 ];
+
+is_deeply $prof{SUB_ENTRY}, [ [ 1, 3 ] ], 'SUB_ENTRY args';
+
+$prof{SUB_RETURN}[0][$_] = 0 for (1,2);
+is_deeply $prof{SUB_RETURN}, [ [ 1, 0, 0, 'main::A' ] ], 'SUB_RETURN args';
+
+done_testing();
@@ -6,7 +6,7 @@ use Devel::NYTProf::Util qw(
     trace_level
 );
 
-my $us = "&micro;s";
+my $us = "µs";
 
 is(fmt_time(0), "0s");
 
@@ -2,6 +2,7 @@
 
 use strict;
 use Test::More;
+use Test::Differences;
 
 use lib qw(t/lib);
 use NYTProfTest;
@@ -47,7 +48,7 @@ run_test_group( {
 
         my $subs2 = $profile->subs_defined_in_file($fid);
 
-        is_deeply [ keys %$subs2 ], [ keys %$subs1 ],
+        eq_or_diff [ sort keys %$subs2 ], [ sort keys %$subs1 ],
             'keys from subname_subinfo_map and subs_defined_in_file should match';
 
         my @begins = grep { $_->subname =~ /\bBEGIN\b/ } values %$subs2;
@@ -9,6 +9,7 @@ use ExtUtils::testlib;
 use Getopt::Long;
 use Test::More;
 use Data::Dumper;
+use File::Spec;
 use File::Temp qw(tempfile);
 use List::Util qw(shuffle);
 
@@ -34,7 +35,7 @@ my %opts = (
     html         => $ENV{NYTPROF_TEST_HTML},
     mergerdt     => $ENV{NYTPROF_TEST_MERGERDT}, # overkill, but handy
 );
-GetOptions(\%opts, qw/p=s I=s v|verbose d|debug html open profperlopts=s leave=i use_db_sub=i savesrc=i compress=i one abort/)
+GetOptions(\%opts, qw/p=s I=s v|verbose d|debug html open profperlopts=s blocks=i leave=i use_db_sub=i savesrc=i compress=i one abort/)
     or exit 1;
 
 $opts{v}    ||= $opts{d};
@@ -60,10 +61,11 @@ for my $opt (qw(trace)) {
 }
 
 
-my $tests_per_extn = {
-    p   => 1,
-    rdt => ($opts{mergerdt}) ? 2 : 1,
-    x   => 3
+my $text_extn_info = {
+    p     => { order => 10, tests => 1, },
+    rdt   => { order => 20, tests => ($opts{mergerdt}) ? 2 : 1, },
+    x     => { order => 30, tests => 3, },
+    calls => { order => 40, tests => 1, },
 };
 
 chdir('t') if -d 't';
@@ -77,9 +79,10 @@ my $bindir      = (grep {-d} qw(./blib/script ../blib/script))[0] || do {
     warn "Couldn't find blib/script directory, so using $bin";
     $bin;
 };
-my $nytprofcsv  = "$bindir/nytprofcsv";
-my $nytprofhtml = "$bindir/nytprofhtml";
-my $nytprofmerge= "$bindir/nytprofmerge";
+my $nytprofcsv   = File::Spec->catfile($bindir, "nytprofcsv");
+my $nytprofcalls = File::Spec->catfile($bindir, "nytprofcalls");
+my $nytprofhtml  = File::Spec->catfile($bindir, "nytprofhtml");
+my $nytprofmerge = File::Spec->catfile($bindir, "nytprofmerge");
 
 my $path_sep = $Config{path_sep} || ':';
 my $perl5lib = $opts{I} || join($path_sep, @INC);
@@ -94,41 +97,51 @@ if ($opts{one}) {           # for one quick test
     $opts{use_db_sub} = 0;
     $opts{savesrc}    = 1;
     $opts{compress}   = 1;
+    $opts{calls}      = 2;
+    $opts{blocks}     = 1;
 }
 
 # force savesrc off for perl 5.11.2 due to perl bug RT#70804
 $opts{savesrc} = 0 if $] eq "5.011002";
 
+my @test_opt_blocks     = (defined $opts{blocks})     ? ($opts{blocks})     : (1);
 my @test_opt_leave      = (defined $opts{leave})      ? ($opts{leave})      : (0, 1);
 my @test_opt_use_db_sub = (defined $opts{use_db_sub}) ? ($opts{use_db_sub}) : (0, 1);
 my @test_opt_savesrc    = (defined $opts{savesrc})    ? ($opts{savesrc})    : (0, 1);
 my @test_opt_compress   = (defined $opts{compress})   ? ($opts{compress})   : (0, 1);
+my @test_opt_calls      = (defined $opts{calls})      ? ($opts{calls})      : (0, 1, 2);
 
 sub mk_opt_combinations {
     my ($overrides) = @_;
 
     my @opt_combinations;
     my %seen;
+
+    for my $blocks (@test_opt_blocks) {
     for my $leave (@test_opt_leave) {
-        for my $use_db_sub (@test_opt_use_db_sub) {
-            for my $savesrc (@test_opt_savesrc) {
-                for my $compress (@test_opt_compress) {
-                    my $o = {
-                        start      => 'init',
-                        slowops    => 2,
-                        leave      => $leave,
-                        use_db_sub => $use_db_sub,
-                        savesrc    => $savesrc,
-                        compress   => $compress,
-                        ($overrides) ? %$overrides : (),
-                    };
-                    my $key = join "\t", map { "$_=>$o->{$_}" } sort keys %$o;
-                    next if $seen{$key}++;
-                    push @opt_combinations, $o;
-                }
-            }
-        }
-    }
+    for my $use_db_sub (@test_opt_use_db_sub) {
+    for my $savesrc (@test_opt_savesrc) {
+    for my $compress (@test_opt_compress) {
+
+            my $o = {
+                start      => 'init',
+                slowops    => 2,
+                blocks     => $blocks,
+                leave      => $leave,
+                use_db_sub => $use_db_sub,
+                savesrc    => $savesrc,
+                compress   => $compress,
+                # we don't need to test the 'calls' opt with all other combinations
+                # so we fudge it here to be on most, but not all, of the time
+                calls      => (!!$savesrc + !!$compress), # 0|1|2
+                ($overrides) ? %$overrides : (),
+            };
+            my $key = join "\t", map { "$_=>$o->{$_}" } sort keys %$o;
+            next if $seen{$key}++;
+            push @opt_combinations, $o;
+
+    } } } } }
+
     @opt_combinations = shuffle @opt_combinations;
     return \@opt_combinations;
 }
@@ -165,7 +178,7 @@ sub do_foreach_opt_combination {
         # did any tests fail?
         my $failed = (count_of_failed_tests() - $prev_failures) ? 1 : 0;
         # record what env settings may have influenced the failure
-        ++$env_influence{$_}{$env->{$_}}{$failed ? 'fail' : 'pass'}
+        ++$env_influence{$_}{$env->{$_}}{$failed ? 'FAIL' : 'pass'}
             for keys %$env;
         $env_failed{ $ENV{NYTPROF} } = $failed;
     }
@@ -181,15 +194,21 @@ sub report_env_influence {
     my @env_influence;
     for my $envvar (sort keys %env_influence) {
         my $variants = $env_influence{$envvar};
+
         local $Data::Dumper::Indent   = 0;
         local $Data::Dumper::Sortkeys = 1;
         local $Data::Dumper::Terse    = 1;
         local $Data::Dumper::Quotekeys= 0;
         local $Data::Dumper::Pair     = ' ';
         $variants->{$_} = Dumper($variants->{$_}) for keys %$variants;
+
+        # was there at least one failure?
+        next unless grep { /FAIL/ } values %$variants;
+
         my $v = (values %$variants)[0]; # use one as a reference
         # all the same?
         next if keys %$variants == grep { $_ eq $v } values %$variants;
+
         push @env_influence, sprintf "%15s: %s\n", $envvar,
             join ', ', map { "$_ => $variants->{$_}" } sort keys %$variants;
     }
@@ -229,14 +248,17 @@ sub run_test_group {
         croak "Can't determine test group";
     }
 
-    my @tests = grep { -f $_ } map { "$group.$_" } sort keys %$tests_per_extn;
-    unlink <$group.*_new{,p}>; # delete _new and _newp files from previous run
+    my @tests = grep { -f $_ }
+        map { "$group.$_" }
+        sort { $text_extn_info->{$a}{order} <=> $text_extn_info->{$b}{order} }
+        keys %$text_extn_info;
+    unlink <$group.*_new*>; # delete _new* files from previous run
 
     if ($opts{v}) {
         print "tests: @tests\n";
         print "perl: $perl\n";
         print "perl5lib: $perl5lib\n";
-        print "nytprofcvs: $nytprofcsv\n";
+        print "nytprofbin: $bindir\n";
     }
 
     plan skip_all => "No '$group.*' test files and no extra_test_code"
@@ -317,7 +339,7 @@ sub run_test {
 
         if ($opts{mergerdt}) { # run the file through nytprofmerge
             my $merged = "$profile_datafile.merged";
-            my $merge_cmd = "$nytprofmerge -v --out=$merged $test_datafile";
+            my $merge_cmd = "$perl $nytprofmerge -v --out=$merged $test_datafile";
             warn "$merge_cmd\n";
             system($merge_cmd) == 0
                 or die "Error running $merge_cmd\n";
@@ -325,6 +347,14 @@ sub run_test {
             unlink $merged;
         }
     }
+    elsif ($type eq 'calls') {
+        if ($env->{calls}) {
+            verify_calls_report($test, $tag, $test_datafile, $outdir);
+        }
+        else {
+            pass("no calls");
+        }
+    }
     elsif ($type eq 'x') {
         mkdir $outdir or die "mkdir($outdir): $!" unless -d $outdir;
         unlink <$outdir/*>;
@@ -376,7 +406,7 @@ sub run_perl_command {
 }
 
 
-sub profile {
+sub profile { # TODO refactor to use run_perl_command()?
     my ($test, $profile_datafile) = @_;
 
     my @perl = perl_command_words(skip_sitecustomize => 1);
@@ -398,16 +428,23 @@ sub verify_data {
     SKIP: {
         skip 'Expected profile data does not have VMS paths', 1
             if $^O eq 'VMS' and $test =~ m/test60|test14/i;
-        $profile->normalize_variables;
+        $profile->normalize_variables(1); # and options
         dump_profile_to_file($profile, $test.'_new', $test.'_newp');
-        my @got      = slurp_file($test.'_new'); chomp @got;
-        my @expected = slurp_file($test);        chomp @expected;
-        is_deeply(\@got, \@expected, "$test match generated profile data for $tag")
-            ? unlink($test.'_new')
-            : diff_files($test, $test.'_new', $test.'_newp');
+        is_file_content_same($test.'_new', $test, "$test match generated profile data for $tag");
     }
 }
 
+sub is_file_content_same {
+    my ($got_file, $exp_file, $testname) = @_;
+
+    my @got = slurp_file($got_file); chomp @got;
+    my @exp = slurp_file($exp_file); chomp @exp;
+
+    is_deeply(\@got, \@exp, $testname)
+        ? unlink($got_file)
+        : diff_files($exp_file, $got_file, $got_file."_patch");
+}
+
 
 sub dump_data_to_file {
     my ($profile, $file) = @_;
@@ -446,6 +483,15 @@ sub diff_files {
 }
 
 
+sub verify_calls_report {
+    my ($test, $tag, $profile_datafile, $outdir) = @_;
+    my $got_file = "${test}_new";
+    note "generating $got_file";
+    run_command("$perl $nytprofcalls $profile_datafile -stable --calls > $got_file");
+    is_file_content_same($got_file, $test, "$test match generated calls data for $tag");
+}
+
+
 sub verify_csv_report {
     my ($test, $tag, $profile_datafile, $outdir) = @_;
 
@@ -494,8 +540,7 @@ sub verify_csv_report {
     # slow systems, e.g. cpan-testers running in cpu-starved virtual machines.
     # e.g., http://www.nntp.perl.org/group/perl.cpan.testers/2009/06/msg4227689.html
     my $max_time_overrun_percentage = ($automated_testing) ? 400 : 200;
-    # e.g., http://www.nntp.perl.org/group/perl.cpan.testers/2009/06/msg4230206.html
-    my $max_time_underrun_percentage = 90;
+    my $max_time_underrun_percentage = 80;
 
     my @accuracy_errors;
     $index = 0;
@@ -505,7 +550,8 @@ sub verify_csv_report {
 
         next if m/^# Version/;    # Ignore version numbers
 
-        s/^([0-9.]+),([0-9.]+),([0-9.]+),(.*)$/0,$2,0,$4/o;
+        # we allow negative numbers here re RT#85556
+        s/^(-?[0-9.]+),([0-9.]+),([0-9.]+),(.*)$/0,$2,0,$4/o;
         my $t0  = $1;
         my $c0  = $2;
         my $tc0 = $3;
@@ -563,7 +609,7 @@ sub number_of_tests {
     my $total_tests = 0;
     for (@_) {
         next unless m/\.(\w+)$/;
-        my $tests = $tests_per_extn->{$1};
+        my $tests = $text_extn_info->{$1}{tests};
         warn "Unknown test type '$1' for test file '$_'\n" if not defined $tests;
         $total_tests += $tests if $tests;
     }
@@ -0,0 +1,14 @@
+main::bar 1
+main::bar;main::CORE:print 1
+main::foo 1
+main::foo;main::CORE:print 1
+main::foo;main::bar 1
+main::foo;main::bar;main::CORE:print 1
+main::baz 1
+main::baz;main::CORE:print 1
+main::baz;main::bar 1
+main::baz;main::bar;main::CORE:print 1
+main::baz;main::foo 1
+main::baz;main::foo;main::CORE:print 1
+main::baz;main::foo;main::bar 1
+main::baz;main::foo;main::bar;main::CORE:print 1
@@ -0,0 +1,14 @@
+main::bar 2
+main::bar;main::CORE:print 2
+main::foo 1
+main::foo;main::CORE:print 1
+main::foo;main::bar 1
+main::foo;main::bar;main::CORE:print 1
+main::baz 1
+main::baz;main::CORE:print 1
+main::baz;main::bar 3
+main::baz;main::bar;main::CORE:print 3
+main::baz;main::foo 1
+main::baz;main::foo;main::CORE:print 1
+main::baz;main::foo;main::bar 1
+main::baz;main::foo;main::bar;main::CORE:print 1
@@ -0,0 +1,8 @@
+main::bar 1
+main::bar;main::CORE:print 1
+main::baz 1
+main::baz;main::CORE:print 1
+main::baz;main::bar 1
+main::baz;main::bar;main::CORE:print 1
+main::baz;main::foo 1
+main::baz;main::foo;main::CORE:print 1
@@ -0,0 +1,12 @@
+main::foo1 1
+main::foo1;main::CORE:print 1
+main::foo1;main::bar 1
+main::foo1;main::bar;main::CORE:print 1
+main::foo1;main::bar;main::yeppers 1
+main::foo1;main::bar;main::yeppers;main::CORE:print 1
+main::foo2 1
+main::foo2;main::CORE:print 1
+main::foo2;main::bar 1
+main::foo2;main::bar;main::CORE:print 1
+main::foo2;main::bar;main::yeppers 1
+main::foo2;main::bar;main::yeppers;main::CORE:print 1
@@ -0,0 +1,9 @@
+main::foo 1
+main::foo;main::CORE:print 1
+main::foo;main::noop 110
+main::bar 1
+main::bar;main::CORE:print 1
+main::bar;main::noop 100
+main::baz 1
+main::baz;main::CORE:print 1
+main::baz;main::noop 200
@@ -0,0 +1 @@
+main::CORE:print 1
diff --git a/var/tmp/source/TIMB/Devel-NYTProf-5.06/Devel-NYTProf-5.06/t/test08.calls b/var/tmp/source/TIMB/Devel-NYTProf-5.06/Devel-NYTProf-5.06/t/test08.calls
new file mode 100644
index 00000000..e69de29b
@@ -0,0 +1,3 @@
+main::bar 1
+main::foo 2
+main::foo;main::bar 2
@@ -0,0 +1,2 @@
+main::__ANON__[(eval 0)[test10.p:1]:1] 1
+main::__ANON__[(eval 0)[test10.p:1]:1];main::CORE:sleep 1
@@ -0,0 +1 @@
+main::__ANON__[(eval 0)[test11.p:3]:1] 2
diff --git a/var/tmp/source/TIMB/Devel-NYTProf-5.06/Devel-NYTProf-5.06/t/test12.calls b/var/tmp/source/TIMB/Devel-NYTProf-5.06/Devel-NYTProf-5.06/t/test12.calls
new file mode 100644
index 00000000..e69de29b
@@ -0,0 +1,9 @@
+main::foo 1
+main::foo;main::CORE:print 1
+main::bar 1
+main::bar;main::CORE:print 1
+main::baz 1
+main::baz;main::CORE:print 1
+main::baz;main::foo 2
+main::baz;main::foo;main::CORE:print 2
+main::baz;main::x 1
@@ -0,0 +1,6 @@
+main::foo 2
+main::foo;main::CORE:match 3
+main::foo;main::CORE:say 2
+main::bar 2
+main::bar;main::CORE:match 3
+main::bar;main::CORE:print 2
@@ -0,0 +1,6 @@
+main::origin 1
+main::origin;main::other 1
+main::destination 1
+main::destination;main::other 1
+main::foo 1
+main::foo;main::bar 1
@@ -0,0 +1,3 @@
+Test18::longmess 1
+Test18::longmess_jmp 1
+Test18::longmess_real 1
@@ -0,0 +1,2 @@
+main::foo 4
+main::foo;main::CORE:print 4
@@ -0,0 +1,2 @@
+main::CORE:sselect 3
+main::foo 3
@@ -0,0 +1,6 @@
+main::__ANON__[(eval 0)[test22-strevala.p:6]:2] 1
+main::__ANON__[(eval 0)[test22-strevala.p:6]:2];main::CORE:print 1
+main::__ANON__[(eval 0)[test22-strevala.p:9]:2] 2
+main::__ANON__[(eval 0)[test22-strevala.p:9]:2];main::CORE:print 2
+main::__ANON__[(eval 0)[(eval 0)[test22-strevala.p:12]:2]:2] 2
+main::__ANON__[(eval 0)[(eval 0)[test22-strevala.p:12]:2]:2];main::CORE:print 2
@@ -52,6 +52,8 @@ run_test_group( {
     },
 } );
 
+exit 0;
+
 __END__
 my $code = 'sub { print "sub called\n" }';
 eval($code)->();
diff --git a/var/tmp/source/TIMB/Devel-NYTProf-5.06/Devel-NYTProf-5.06/t/test23-strevall.calls b/var/tmp/source/TIMB/Devel-NYTProf-5.06/Devel-NYTProf-5.06/t/test23-strevall.calls
new file mode 100644
index 00000000..e69de29b
@@ -0,0 +1 @@
+main::__ANON__[(eval 0)[test24-strevalc.p:8]:1] 2
@@ -0,0 +1,11 @@
+main::other 1
+main::other;main::CORE:print 1
+main::prefork 1
+main::prefork;main::CORE:print 1
+main::prefork;main::other 1
+main::prefork;main::other;main::CORE:print 1
+main::postfork 1
+main::postfork;main::CORE:print 1
+main::postfork;main::other 1
+main::postfork;main::other;main::CORE:print 1
+main::CORE:wait 1
@@ -0,0 +1 @@
+test40pmc::foo 1
@@ -0,0 +1 @@
+DB::disable_profile 2
@@ -0,0 +1,3 @@
+main::CORE:unlink 1
+main::sub1 1
+DB::disable_profile 1
@@ -47,3 +47,5 @@ DB::finish_profile();
 
 # This can be removed once we have a better test harness
 -f $_ or die "$_ should exist" for ($file_b, $file_c);
+
+# TODO should test for enable/disable within subs
@@ -50,3 +50,5 @@
 0,0,0,
 0,0,0,# This can be removed once we have a better test harness
 0,0,0,-f $_ or die "$_ should exist" for ($file_b, $file_c);
+0,0,0,
+0,0,0,# TODO should test for enable/disable within subs
@@ -0,0 +1,5 @@
+Devel::NYTProf::Test::example_xsub 7
+Devel::NYTProf::Test::example_xsub;main::will_die 1
+main::launch 1
+main::CORE:wait 1
+main::CORE:open 1
@@ -0,0 +1,3 @@
+main::__ANON__[(eval 0)[test61-submerge.p:8]:1] 3
+main::__ANON__[(eval 0)[test61-submerge.p:8]:1];main::foo 3
+main::__ANON__[(eval 0)[test61-submerge.p:8]:1];main::foo;main::CORE:print 3
@@ -0,0 +1,11 @@
+MyTie::TIESCALAR 1
+main::sub4 2
+MyTie::STORE 1
+MyTie::FETCH 1
+Devel::NYTProf::Test::example_xsub 2
+main::sub1 1
+main::CORE:sort 2
+main::CORE:sort;Devel::NYTProf::Test::example_xsub 3
+main::CORE:sort;main::sub2 6
+main::CORE:subst 1
+main::CORE:substcont 3
@@ -0,0 +1,6 @@
+main::D 1
+main::D;main::C 2
+main::D;main::C;main::B 2
+main::D;main::C;main::B;main::CORE:sselect 2
+main::D;main::C;main::B;main::A 2
+main::D;main::C;main::B;main::A;main::CORE:sselect 2
@@ -5,7 +5,7 @@
 # code using a command like
 # make && NYTPROF_TEST=trace=3 perl -Mblib test.pl -leave=1 -use_db_sub=0 t/test70-subexcl.*
 
-my $T = 0.2;
+my $T = $ENV{NYTPROF_TEST_PAUSE_TIME} || 0.2;
 
 sub A {     # inclusive ~= $T, exclusive ~= $T
     select undef, undef, undef, $T;
@@ -0,0 +1,6 @@
+main::recurs 1
+main::recurs;main::CORE:sselect 1
+main::recurs;main::recurs 1
+main::recurs;main::recurs;main::CORE:sselect 1
+main::recurs;main::recurs;main::recurs 1
+main::recurs;main::recurs;main::recurs;main::CORE:sselect 1
@@ -0,0 +1,45 @@
+# Tests dieing on Can't use string ... as a subroutine ref while "strict refs" in use
+# that used to core dump (RT#86638)
+# https://rt.cpan.org/Ticket/Display.html?id=86638
+
+use strict;
+use Test::More;
+
+use lib qw(t/lib);
+use NYTProfTest;
+use Data::Dumper;
+
+use Devel::NYTProf::Run qw(profile_this);
+
+my $src_code = join("", <DATA>);
+
+run_test_group( {
+    extra_options => {
+        start => 'begin',
+        compress => 1,
+        calls => 0,
+        savesrc => 0,
+        stmts => 0,
+        slowops => 0,
+    },
+    extra_test_count => 2,
+    extra_test_code  => sub {
+        my ($profile, $env) = @_;
+
+        $profile = profile_this(
+            src_code => $src_code,
+            out_file => $env->{file},
+            skip_sitecustomize => 1,
+        );
+        isa_ok $profile, 'Devel::NYTProf::Data';
+        # check if data was truncated
+        ok $profile->{attribute}{complete};
+    },
+});
+
+__DATA__
+#!perl
+use strict;
+# Can't use string ("") as a subroutine ref while "strict refs" in use at - line 4.
+eval { $x::z->() };
+die $@ if $@ !~ /^Can't use .* as a subroutine ref/;