stackprof 0.2.12 → 0.2.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.gitignore +0 -1
- data/.travis.yml +22 -8
- data/CHANGELOG.md +14 -2
- data/Dockerfile +21 -0
- data/Gemfile.lock +2 -2
- data/README.md +8 -0
- data/bin/stackprof +14 -4
- data/ext/stackprof/extconf.rb +9 -0
- data/ext/stackprof/stackprof.c +696 -0
- data/lib/stackprof.rb +4 -0
- data/lib/stackprof/report.rb +226 -1
- data/stackprof.gemspec +1 -1
- data/test/test_stackprof.rb +27 -0
- metadata +4 -4
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 | 
            -
             | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 2 | 
            +
            SHA256:
         | 
| 3 | 
            +
              metadata.gz: 81201dbe84f93b79242ce9987c763b7edaa54b4f66a7005ff83d22a9b4e25cb4
         | 
| 4 | 
            +
              data.tar.gz: c80ad82b003cb989c83bc5882f4bcdeb6f6db697729315faf6d4507b7f3888fb
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: f4fec270902e0b6f21e9d37b8317a2c3614cf4647ad29956b52561bc6c86d7625c4ee3362388c1323c1b74fd2fbb6c99242e75cf0314e20f5be97491e9888122
         | 
| 7 | 
            +
              data.tar.gz: 01a18137afc847cba43882fbc7e22483e350490e655e9536f0dc1cb5275a49c222c4613b9a4f39cb0d3265a6e834560fc87cac4fae77324634bc2f68cde0c03a
         | 
    
        data/.gitignore
    CHANGED
    
    
    
        data/.travis.yml
    CHANGED
    
    | @@ -1,8 +1,22 @@ | |
| 1 | 
            -
            sudo:  | 
| 2 | 
            -
             | 
| 3 | 
            -
             | 
| 4 | 
            -
              -  | 
| 5 | 
            -
             | 
| 6 | 
            -
             | 
| 7 | 
            -
             | 
| 8 | 
            -
             | 
| 1 | 
            +
            sudo: required
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            services:
         | 
| 4 | 
            +
              - docker
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            language: general
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            env:
         | 
| 9 | 
            +
              matrix:
         | 
| 10 | 
            +
                - RVM_RUBY_VERSION=2.1
         | 
| 11 | 
            +
                - RVM_RUBY_VERSION=2.2
         | 
| 12 | 
            +
                - RVM_RUBY_VERSION=2.3
         | 
| 13 | 
            +
                - RVM_RUBY_VERSION=2.4
         | 
| 14 | 
            +
                - RVM_RUBY_VERSION=2.5
         | 
| 15 | 
            +
                - RVM_RUBY_VERSION=2.6
         | 
| 16 | 
            +
                - RVM_RUBY_VERSION=ruby-head
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            before_install:
         | 
| 19 | 
            +
            - sudo docker build -t stackprof-$RVM_RUBY_VERSION --build-arg=RVM_RUBY_VERSION=$RVM_RUBY_VERSION .
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            script:
         | 
| 22 | 
            +
            - sudo docker run --name stackprof-$RVM_RUBY_VERSION stackprof-$RVM_RUBY_VERSION
         | 
    
        data/CHANGELOG.md
    CHANGED
    
    | @@ -1,3 +1,15 @@ | |
| 1 | 
            -
            # 0.2. | 
| 1 | 
            +
            # 0.2.13
         | 
| 2 2 |  | 
| 3 | 
            -
            *  | 
| 3 | 
            +
            * Remove /ext from .gitignore
         | 
| 4 | 
            +
            * update gemfile
         | 
| 5 | 
            +
            * Add ruby 2.5 to CI targets
         | 
| 6 | 
            +
            * comment some of the inner workings
         | 
| 7 | 
            +
            * feature: add --json format
         | 
| 8 | 
            +
            * Add test coverage around the string branch in result writing
         | 
| 9 | 
            +
            * Flip conditional to use duck typing
         | 
| 10 | 
            +
            * Allow Pathname objects for Stackprof :out
         | 
| 11 | 
            +
            * Fix a compilation error and a compilation warning
         | 
| 12 | 
            +
            * Add `--alphabetical-flamegraph` for population-based instead of timeline
         | 
| 13 | 
            +
            * Add `--d3-flamegraph` to output html using d3-flame-graph
         | 
| 14 | 
            +
            * Avoid JSON::NestingError when processing deep stacks
         | 
| 15 | 
            +
            * Use docker for CI
         | 
    
        data/Dockerfile
    ADDED
    
    | @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            FROM ubuntu:16.04
         | 
| 2 | 
            +
            ARG DEBIAN_FRONTEND=noninteractive
         | 
| 3 | 
            +
            RUN apt-get update -q && \
         | 
| 4 | 
            +
                apt-get install -qy \
         | 
| 5 | 
            +
                  curl ca-certificates gnupg2 dirmngr build-essential \
         | 
| 6 | 
            +
                  gawk git autoconf automake pkg-config \
         | 
| 7 | 
            +
                  bison libffi-dev libgdbm-dev libncurses5-dev libsqlite3-dev libtool \
         | 
| 8 | 
            +
                  libyaml-dev sqlite3 zlib1g-dev libgmp-dev libreadline-dev libssl-dev \
         | 
| 9 | 
            +
                  ruby --no-install-recommends && \
         | 
| 10 | 
            +
                apt-get clean
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            RUN gpg2 --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
         | 
| 13 | 
            +
            RUN curl -sSL https://get.rvm.io | bash -s
         | 
| 14 | 
            +
            ARG RVM_RUBY_VERSION=ruby-head
         | 
| 15 | 
            +
            RUN /bin/bash -l -c "echo $RVM_RUBY_VERSION"
         | 
| 16 | 
            +
            RUN /bin/bash -l -c ". /etc/profile.d/rvm.sh && rvm install $RVM_RUBY_VERSION --binary || rvm install $RVM_RUBY_VERSION"
         | 
| 17 | 
            +
            ADD . /stackprof/
         | 
| 18 | 
            +
            WORKDIR /stackprof/
         | 
| 19 | 
            +
            RUN /bin/bash -l -c ". /etc/profile.d/rvm.sh && gem install bundler:1.16.0"
         | 
| 20 | 
            +
            RUN /bin/bash -l -c ". /etc/profile.d/rvm.sh && bundle install"
         | 
| 21 | 
            +
            CMD /bin/bash -l -c ". /etc/profile.d/rvm.sh && bundle exec rake"
         | 
    
        data/Gemfile.lock
    CHANGED
    
    
    
        data/README.md
    CHANGED
    
    | @@ -97,6 +97,14 @@ The `--flamegraph-viewer` command will output the exact shell command you need t | |
| 97 97 |  | 
| 98 98 | 
             
            
         | 
| 99 99 |  | 
| 100 | 
            +
            Alternatively, you can generate a flamegraph that uses [d3-flame-graph](https://github.com/spiermar/d3-flame-graph):
         | 
| 101 | 
            +
             | 
| 102 | 
            +
            ```
         | 
| 103 | 
            +
            $ stackprof --d3-flamegraph tmp/stackprof-cpu-myapp.dump > flamegraph.html
         | 
| 104 | 
            +
            ```
         | 
| 105 | 
            +
             | 
| 106 | 
            +
            And just open the result by your browser.
         | 
| 107 | 
            +
             | 
| 100 108 | 
             
            ## Sampling
         | 
| 101 109 |  | 
| 102 110 | 
             
            four sampling modes are supported:
         | 
    
        data/bin/stackprof
    CHANGED
    
    | @@ -8,6 +8,7 @@ parser = OptionParser.new(ARGV) do |o| | |
| 8 8 | 
             
              o.banner = "Usage: stackprof [file.dump]+ [--text|--method=NAME|--callgrind|--graphviz]"
         | 
| 9 9 |  | 
| 10 10 | 
             
              o.on('--text', 'Text summary per method (default)'){ options[:format] = :text }
         | 
| 11 | 
            +
              o.on('--json', 'JSON output (use with web viewers)'){ options[:format] = :json }
         | 
| 11 12 | 
             
              o.on('--files', 'List of files'){ |f| options[:format] = :files }
         | 
| 12 13 | 
             
              o.on('--limit [num]', Integer, 'Limit --text, --files, or --graphviz output to N entries'){ |n| options[:limit] = n }
         | 
| 13 14 | 
             
              o.on('--sort-total', "Sort --text or --files output on total samples\n\n"){ options[:sort] = true }
         | 
| @@ -18,11 +19,14 @@ parser = OptionParser.new(ARGV) do |o| | |
| 18 19 | 
             
              o.on('--graphviz', "Graphviz output (use with dot)"){ options[:format] = :graphviz }
         | 
| 19 20 | 
             
              o.on('--node-fraction [frac]', OptionParser::DecimalNumeric, 'Drop nodes representing less than [frac] fraction of samples'){ |n| options[:node_fraction] = n }
         | 
| 20 21 | 
             
              o.on('--stackcollapse', 'stackcollapse.pl compatible output (use with stackprof-flamegraph.pl)'){ options[:format] = :stackcollapse }
         | 
| 21 | 
            -
              o.on('--flamegraph', "timeline-flamegraph output (js)"){ options[:format] = : | 
| 22 | 
            -
              o.on('--flamegraph | 
| 22 | 
            +
              o.on('--timeline-flamegraph', "timeline-flamegraph output (js)"){ options[:format] = :timeline_flamegraph }
         | 
| 23 | 
            +
              o.on('--alphabetical-flamegraph', "alphabetical-flamegraph output (js)"){ options[:format] = :alphabetical_flamegraph }
         | 
| 24 | 
            +
              o.on('--flamegraph', "alias to --timeline-flamegraph"){ options[:format] = :timeline_flamegraph }
         | 
| 25 | 
            +
              o.on('--flamegraph-viewer [f.js]', String, "open html viewer for flamegraph output"){ |file|
         | 
| 23 26 | 
             
                puts("open file://#{File.expand_path('../../lib/stackprof/flamegraph/viewer.html', __FILE__)}?data=#{File.expand_path(file)}")
         | 
| 24 27 | 
             
                exit
         | 
| 25 28 | 
             
              }
         | 
| 29 | 
            +
              o.on('--d3-flamegraph', "flamegraph output (html using d3-flame-graph)\n\n"){ options[:format] = :d3_flamegraph }
         | 
| 26 30 | 
             
              o.on('--select-files []', String, 'Show results of matching files'){ |path| (options[:select_files] ||= []) << File.expand_path(path) }
         | 
| 27 31 | 
             
              o.on('--reject-files []', String, 'Exclude results of matching files'){ |path| (options[:reject_files] ||= []) << File.expand_path(path) }
         | 
| 28 32 | 
             
              o.on('--select-names []', Regexp, 'Show results of matching method names'){ |regexp| (options[:select_names] ||= []) << regexp }
         | 
| @@ -62,6 +66,8 @@ options.delete(:limit) if options[:limit] == 0 | |
| 62 66 | 
             
            case options[:format]
         | 
| 63 67 | 
             
            when :text
         | 
| 64 68 | 
             
              report.print_text(options[:sort], options[:limit], options[:select_files], options[:reject_files], options[:select_names], options[:reject_names])
         | 
| 69 | 
            +
            when :json
         | 
| 70 | 
            +
              report.print_json
         | 
| 65 71 | 
             
            when :debug
         | 
| 66 72 | 
             
              report.print_debug
         | 
| 67 73 | 
             
            when :dump
         | 
| @@ -72,8 +78,12 @@ when :graphviz | |
| 72 78 | 
             
              report.print_graphviz(options)
         | 
| 73 79 | 
             
            when :stackcollapse
         | 
| 74 80 | 
             
              report.print_stackcollapse
         | 
| 75 | 
            -
            when : | 
| 76 | 
            -
              report. | 
| 81 | 
            +
            when :timeline_flamegraph
         | 
| 82 | 
            +
              report.print_timeline_flamegraph
         | 
| 83 | 
            +
            when :alphabetical_flamegraph
         | 
| 84 | 
            +
              report.print_alphabetical_flamegraph
         | 
| 85 | 
            +
            when :d3_flamegraph
         | 
| 86 | 
            +
              report.print_d3_flamegraph
         | 
| 77 87 | 
             
            when :method
         | 
| 78 88 | 
             
              options[:walk] ? report.walk_method(options[:filter]) : report.print_method(options[:filter])
         | 
| 79 89 | 
             
            when :file
         | 
| @@ -0,0 +1,9 @@ | |
| 1 | 
            +
            require 'mkmf'
         | 
| 2 | 
            +
            if have_func('rb_postponed_job_register_one') &&
         | 
| 3 | 
            +
               have_func('rb_profile_frames') &&
         | 
| 4 | 
            +
               have_func('rb_tracepoint_new') &&
         | 
| 5 | 
            +
               have_const('RUBY_INTERNAL_EVENT_NEWOBJ')
         | 
| 6 | 
            +
              create_makefile('stackprof/stackprof')
         | 
| 7 | 
            +
            else
         | 
| 8 | 
            +
              fail 'missing API: are you using ruby 2.1+?'
         | 
| 9 | 
            +
            end
         | 
| @@ -0,0 +1,696 @@ | |
| 1 | 
            +
            /**********************************************************************
         | 
| 2 | 
            +
             | 
| 3 | 
            +
              stackprof.c - Sampling call-stack frame profiler for MRI.
         | 
| 4 | 
            +
             | 
| 5 | 
            +
              vim: noexpandtab shiftwidth=4 tabstop=8 softtabstop=4
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            **********************************************************************/
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            #include <ruby/ruby.h>
         | 
| 10 | 
            +
            #include <ruby/debug.h>
         | 
| 11 | 
            +
            #include <ruby/st.h>
         | 
| 12 | 
            +
            #include <ruby/io.h>
         | 
| 13 | 
            +
            #include <ruby/intern.h>
         | 
| 14 | 
            +
            #include <signal.h>
         | 
| 15 | 
            +
            #include <sys/time.h>
         | 
| 16 | 
            +
            #include <pthread.h>
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            #define BUF_SIZE 2048
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            typedef struct {
         | 
| 21 | 
            +
                size_t total_samples;
         | 
| 22 | 
            +
                size_t caller_samples;
         | 
| 23 | 
            +
                size_t seen_at_sample_number;
         | 
| 24 | 
            +
                st_table *edges;
         | 
| 25 | 
            +
                st_table *lines;
         | 
| 26 | 
            +
            } frame_data_t;
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            static struct {
         | 
| 29 | 
            +
                int running;
         | 
| 30 | 
            +
                int raw;
         | 
| 31 | 
            +
                int aggregate;
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                VALUE mode;
         | 
| 34 | 
            +
                VALUE interval;
         | 
| 35 | 
            +
                VALUE out;
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                VALUE *raw_samples;
         | 
| 38 | 
            +
                size_t raw_samples_len;
         | 
| 39 | 
            +
                size_t raw_samples_capa;
         | 
| 40 | 
            +
                size_t raw_sample_index;
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                struct timeval last_sample_at;
         | 
| 43 | 
            +
                int *raw_timestamp_deltas;
         | 
| 44 | 
            +
                size_t raw_timestamp_deltas_len;
         | 
| 45 | 
            +
                size_t raw_timestamp_deltas_capa;
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                size_t overall_signals;
         | 
| 48 | 
            +
                size_t overall_samples;
         | 
| 49 | 
            +
                size_t during_gc;
         | 
| 50 | 
            +
                size_t unrecorded_gc_samples;
         | 
| 51 | 
            +
                st_table *frames;
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                VALUE fake_gc_frame;
         | 
| 54 | 
            +
                VALUE fake_gc_frame_name;
         | 
| 55 | 
            +
                VALUE empty_string;
         | 
| 56 | 
            +
                VALUE frames_buffer[BUF_SIZE];
         | 
| 57 | 
            +
                int lines_buffer[BUF_SIZE];
         | 
| 58 | 
            +
            } _stackprof;
         | 
| 59 | 
            +
             | 
| 60 | 
            +
            static VALUE sym_object, sym_wall, sym_cpu, sym_custom, sym_name, sym_file, sym_line;
         | 
| 61 | 
            +
            static VALUE sym_samples, sym_total_samples, sym_missed_samples, sym_edges, sym_lines;
         | 
| 62 | 
            +
            static VALUE sym_version, sym_mode, sym_interval, sym_raw, sym_frames, sym_out, sym_aggregate, sym_raw_timestamp_deltas;
         | 
| 63 | 
            +
            static VALUE sym_gc_samples, objtracer;
         | 
| 64 | 
            +
            static VALUE gc_hook;
         | 
| 65 | 
            +
            static VALUE rb_mStackProf;
         | 
| 66 | 
            +
             | 
| 67 | 
            +
            static void stackprof_newobj_handler(VALUE, void*);
         | 
| 68 | 
            +
            static void stackprof_signal_handler(int sig, siginfo_t* sinfo, void* ucontext);
         | 
| 69 | 
            +
             | 
| 70 | 
            +
            static VALUE
         | 
| 71 | 
            +
            stackprof_start(int argc, VALUE *argv, VALUE self)
         | 
| 72 | 
            +
            {
         | 
| 73 | 
            +
                struct sigaction sa;
         | 
| 74 | 
            +
                struct itimerval timer;
         | 
| 75 | 
            +
                VALUE opts = Qnil, mode = Qnil, interval = Qnil, out = Qfalse;
         | 
| 76 | 
            +
                int raw = 0, aggregate = 1;
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                if (_stackprof.running)
         | 
| 79 | 
            +
            	return Qfalse;
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                rb_scan_args(argc, argv, "0:", &opts);
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                if (RTEST(opts)) {
         | 
| 84 | 
            +
            	mode = rb_hash_aref(opts, sym_mode);
         | 
| 85 | 
            +
            	interval = rb_hash_aref(opts, sym_interval);
         | 
| 86 | 
            +
            	out = rb_hash_aref(opts, sym_out);
         | 
| 87 | 
            +
             | 
| 88 | 
            +
            	if (RTEST(rb_hash_aref(opts, sym_raw)))
         | 
| 89 | 
            +
            	    raw = 1;
         | 
| 90 | 
            +
            	if (rb_hash_lookup2(opts, sym_aggregate, Qundef) == Qfalse)
         | 
| 91 | 
            +
            	    aggregate = 0;
         | 
| 92 | 
            +
                }
         | 
| 93 | 
            +
                if (!RTEST(mode)) mode = sym_wall;
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                if (!_stackprof.frames) {
         | 
| 96 | 
            +
            	_stackprof.frames = st_init_numtable();
         | 
| 97 | 
            +
            	_stackprof.overall_signals = 0;
         | 
| 98 | 
            +
            	_stackprof.overall_samples = 0;
         | 
| 99 | 
            +
            	_stackprof.during_gc = 0;
         | 
| 100 | 
            +
                }
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                if (mode == sym_object) {
         | 
| 103 | 
            +
            	if (!RTEST(interval)) interval = INT2FIX(1);
         | 
| 104 | 
            +
             | 
| 105 | 
            +
            	objtracer = rb_tracepoint_new(Qnil, RUBY_INTERNAL_EVENT_NEWOBJ, stackprof_newobj_handler, 0);
         | 
| 106 | 
            +
            	rb_tracepoint_enable(objtracer);
         | 
| 107 | 
            +
                } else if (mode == sym_wall || mode == sym_cpu) {
         | 
| 108 | 
            +
            	if (!RTEST(interval)) interval = INT2FIX(1000);
         | 
| 109 | 
            +
             | 
| 110 | 
            +
            	sa.sa_sigaction = stackprof_signal_handler;
         | 
| 111 | 
            +
            	sa.sa_flags = SA_RESTART | SA_SIGINFO;
         | 
| 112 | 
            +
            	sigemptyset(&sa.sa_mask);
         | 
| 113 | 
            +
            	sigaction(mode == sym_wall ? SIGALRM : SIGPROF, &sa, NULL);
         | 
| 114 | 
            +
             | 
| 115 | 
            +
            	timer.it_interval.tv_sec = 0;
         | 
| 116 | 
            +
            	timer.it_interval.tv_usec = NUM2LONG(interval);
         | 
| 117 | 
            +
            	timer.it_value = timer.it_interval;
         | 
| 118 | 
            +
            	setitimer(mode == sym_wall ? ITIMER_REAL : ITIMER_PROF, &timer, 0);
         | 
| 119 | 
            +
                } else if (mode == sym_custom) {
         | 
| 120 | 
            +
            	/* sampled manually */
         | 
| 121 | 
            +
            	interval = Qnil;
         | 
| 122 | 
            +
                } else {
         | 
| 123 | 
            +
            	rb_raise(rb_eArgError, "unknown profiler mode");
         | 
| 124 | 
            +
                }
         | 
| 125 | 
            +
             | 
| 126 | 
            +
                _stackprof.running = 1;
         | 
| 127 | 
            +
                _stackprof.raw = raw;
         | 
| 128 | 
            +
                _stackprof.aggregate = aggregate;
         | 
| 129 | 
            +
                _stackprof.mode = mode;
         | 
| 130 | 
            +
                _stackprof.interval = interval;
         | 
| 131 | 
            +
                _stackprof.out = out;
         | 
| 132 | 
            +
             | 
| 133 | 
            +
                if (raw) {
         | 
| 134 | 
            +
            	gettimeofday(&_stackprof.last_sample_at, NULL);
         | 
| 135 | 
            +
                }
         | 
| 136 | 
            +
             | 
| 137 | 
            +
                return Qtrue;
         | 
| 138 | 
            +
            }
         | 
| 139 | 
            +
             | 
| 140 | 
            +
            static VALUE
         | 
| 141 | 
            +
            stackprof_stop(VALUE self)
         | 
| 142 | 
            +
            {
         | 
| 143 | 
            +
                struct sigaction sa;
         | 
| 144 | 
            +
                struct itimerval timer;
         | 
| 145 | 
            +
             | 
| 146 | 
            +
                if (!_stackprof.running)
         | 
| 147 | 
            +
            	return Qfalse;
         | 
| 148 | 
            +
                _stackprof.running = 0;
         | 
| 149 | 
            +
             | 
| 150 | 
            +
                if (_stackprof.mode == sym_object) {
         | 
| 151 | 
            +
            	rb_tracepoint_disable(objtracer);
         | 
| 152 | 
            +
                } else if (_stackprof.mode == sym_wall || _stackprof.mode == sym_cpu) {
         | 
| 153 | 
            +
            	memset(&timer, 0, sizeof(timer));
         | 
| 154 | 
            +
            	setitimer(_stackprof.mode == sym_wall ? ITIMER_REAL : ITIMER_PROF, &timer, 0);
         | 
| 155 | 
            +
             | 
| 156 | 
            +
            	sa.sa_handler = SIG_IGN;
         | 
| 157 | 
            +
            	sa.sa_flags = SA_RESTART;
         | 
| 158 | 
            +
            	sigemptyset(&sa.sa_mask);
         | 
| 159 | 
            +
            	sigaction(_stackprof.mode == sym_wall ? SIGALRM : SIGPROF, &sa, NULL);
         | 
| 160 | 
            +
                } else if (_stackprof.mode == sym_custom) {
         | 
| 161 | 
            +
            	/* sampled manually */
         | 
| 162 | 
            +
                } else {
         | 
| 163 | 
            +
            	rb_raise(rb_eArgError, "unknown profiler mode");
         | 
| 164 | 
            +
                }
         | 
| 165 | 
            +
             | 
| 166 | 
            +
                return Qtrue;
         | 
| 167 | 
            +
            }
         | 
| 168 | 
            +
             | 
| 169 | 
            +
            static int
         | 
| 170 | 
            +
            frame_edges_i(st_data_t key, st_data_t val, st_data_t arg)
         | 
| 171 | 
            +
            {
         | 
| 172 | 
            +
                VALUE edges = (VALUE)arg;
         | 
| 173 | 
            +
             | 
| 174 | 
            +
                intptr_t weight = (intptr_t)val;
         | 
| 175 | 
            +
                rb_hash_aset(edges, rb_obj_id((VALUE)key), INT2FIX(weight));
         | 
| 176 | 
            +
                return ST_CONTINUE;
         | 
| 177 | 
            +
            }
         | 
| 178 | 
            +
             | 
| 179 | 
            +
            static int
         | 
| 180 | 
            +
            frame_lines_i(st_data_t key, st_data_t val, st_data_t arg)
         | 
| 181 | 
            +
            {
         | 
| 182 | 
            +
                VALUE lines = (VALUE)arg;
         | 
| 183 | 
            +
             | 
| 184 | 
            +
                size_t weight = (size_t)val;
         | 
| 185 | 
            +
                size_t total = weight & (~(size_t)0 << (8*SIZEOF_SIZE_T/2));
         | 
| 186 | 
            +
                weight -= total;
         | 
| 187 | 
            +
                total = total >> (8*SIZEOF_SIZE_T/2);
         | 
| 188 | 
            +
                rb_hash_aset(lines, INT2FIX(key), rb_ary_new3(2, ULONG2NUM(total), ULONG2NUM(weight)));
         | 
| 189 | 
            +
                return ST_CONTINUE;
         | 
| 190 | 
            +
            }
         | 
| 191 | 
            +
             | 
| 192 | 
            +
            static int
         | 
| 193 | 
            +
            frame_i(st_data_t key, st_data_t val, st_data_t arg)
         | 
| 194 | 
            +
            {
         | 
| 195 | 
            +
                VALUE frame = (VALUE)key;
         | 
| 196 | 
            +
                frame_data_t *frame_data = (frame_data_t *)val;
         | 
| 197 | 
            +
                VALUE results = (VALUE)arg;
         | 
| 198 | 
            +
                VALUE details = rb_hash_new();
         | 
| 199 | 
            +
                VALUE name, file, edges, lines;
         | 
| 200 | 
            +
                VALUE line;
         | 
| 201 | 
            +
             | 
| 202 | 
            +
                rb_hash_aset(results, rb_obj_id(frame), details);
         | 
| 203 | 
            +
             | 
| 204 | 
            +
                if (frame == _stackprof.fake_gc_frame) {
         | 
| 205 | 
            +
            	name = _stackprof.fake_gc_frame_name;
         | 
| 206 | 
            +
            	file = _stackprof.empty_string;
         | 
| 207 | 
            +
            	line = INT2FIX(0);
         | 
| 208 | 
            +
                } else {
         | 
| 209 | 
            +
            	name = rb_profile_frame_full_label(frame);
         | 
| 210 | 
            +
             | 
| 211 | 
            +
            	file = rb_profile_frame_absolute_path(frame);
         | 
| 212 | 
            +
            	if (NIL_P(file))
         | 
| 213 | 
            +
            	    file = rb_profile_frame_path(frame);
         | 
| 214 | 
            +
            	line = rb_profile_frame_first_lineno(frame);
         | 
| 215 | 
            +
                }
         | 
| 216 | 
            +
             | 
| 217 | 
            +
                rb_hash_aset(details, sym_name, name);
         | 
| 218 | 
            +
                rb_hash_aset(details, sym_file, file);
         | 
| 219 | 
            +
                if (line != INT2FIX(0)) {
         | 
| 220 | 
            +
            	rb_hash_aset(details, sym_line, line);
         | 
| 221 | 
            +
                }
         | 
| 222 | 
            +
             | 
| 223 | 
            +
                rb_hash_aset(details, sym_total_samples, SIZET2NUM(frame_data->total_samples));
         | 
| 224 | 
            +
                rb_hash_aset(details, sym_samples, SIZET2NUM(frame_data->caller_samples));
         | 
| 225 | 
            +
             | 
| 226 | 
            +
                if (frame_data->edges) {
         | 
| 227 | 
            +
                    edges = rb_hash_new();
         | 
| 228 | 
            +
                    rb_hash_aset(details, sym_edges, edges);
         | 
| 229 | 
            +
                    st_foreach(frame_data->edges, frame_edges_i, (st_data_t)edges);
         | 
| 230 | 
            +
                    st_free_table(frame_data->edges);
         | 
| 231 | 
            +
                    frame_data->edges = NULL;
         | 
| 232 | 
            +
                }
         | 
| 233 | 
            +
             | 
| 234 | 
            +
                if (frame_data->lines) {
         | 
| 235 | 
            +
            	lines = rb_hash_new();
         | 
| 236 | 
            +
            	rb_hash_aset(details, sym_lines, lines);
         | 
| 237 | 
            +
            	st_foreach(frame_data->lines, frame_lines_i, (st_data_t)lines);
         | 
| 238 | 
            +
            	st_free_table(frame_data->lines);
         | 
| 239 | 
            +
            	frame_data->lines = NULL;
         | 
| 240 | 
            +
                }
         | 
| 241 | 
            +
             | 
| 242 | 
            +
                xfree(frame_data);
         | 
| 243 | 
            +
                return ST_DELETE;
         | 
| 244 | 
            +
            }
         | 
| 245 | 
            +
             | 
| 246 | 
            +
            static VALUE
         | 
| 247 | 
            +
            stackprof_results(int argc, VALUE *argv, VALUE self)
         | 
| 248 | 
            +
            {
         | 
| 249 | 
            +
                VALUE results, frames;
         | 
| 250 | 
            +
             | 
| 251 | 
            +
                if (!_stackprof.frames || _stackprof.running)
         | 
| 252 | 
            +
            	return Qnil;
         | 
| 253 | 
            +
             | 
| 254 | 
            +
                results = rb_hash_new();
         | 
| 255 | 
            +
                rb_hash_aset(results, sym_version, DBL2NUM(1.2));
         | 
| 256 | 
            +
                rb_hash_aset(results, sym_mode, _stackprof.mode);
         | 
| 257 | 
            +
                rb_hash_aset(results, sym_interval, _stackprof.interval);
         | 
| 258 | 
            +
                rb_hash_aset(results, sym_samples, SIZET2NUM(_stackprof.overall_samples));
         | 
| 259 | 
            +
                rb_hash_aset(results, sym_gc_samples, SIZET2NUM(_stackprof.during_gc));
         | 
| 260 | 
            +
                rb_hash_aset(results, sym_missed_samples, SIZET2NUM(_stackprof.overall_signals - _stackprof.overall_samples));
         | 
| 261 | 
            +
             | 
| 262 | 
            +
                frames = rb_hash_new();
         | 
| 263 | 
            +
                rb_hash_aset(results, sym_frames, frames);
         | 
| 264 | 
            +
                st_foreach(_stackprof.frames, frame_i, (st_data_t)frames);
         | 
| 265 | 
            +
             | 
| 266 | 
            +
                st_free_table(_stackprof.frames);
         | 
| 267 | 
            +
                _stackprof.frames = NULL;
         | 
| 268 | 
            +
             | 
| 269 | 
            +
                if (_stackprof.raw && _stackprof.raw_samples_len) {
         | 
| 270 | 
            +
            	size_t len, n, o;
         | 
| 271 | 
            +
            	VALUE raw_timestamp_deltas;
         | 
| 272 | 
            +
            	VALUE raw_samples = rb_ary_new_capa(_stackprof.raw_samples_len);
         | 
| 273 | 
            +
             | 
| 274 | 
            +
            	for (n = 0; n < _stackprof.raw_samples_len; n++) {
         | 
| 275 | 
            +
            	    len = (size_t)_stackprof.raw_samples[n];
         | 
| 276 | 
            +
            	    rb_ary_push(raw_samples, SIZET2NUM(len));
         | 
| 277 | 
            +
             | 
| 278 | 
            +
            	    for (o = 0, n++; o < len; n++, o++)
         | 
| 279 | 
            +
            		rb_ary_push(raw_samples, rb_obj_id(_stackprof.raw_samples[n]));
         | 
| 280 | 
            +
            	    rb_ary_push(raw_samples, SIZET2NUM((size_t)_stackprof.raw_samples[n]));
         | 
| 281 | 
            +
            	}
         | 
| 282 | 
            +
             | 
| 283 | 
            +
            	free(_stackprof.raw_samples);
         | 
| 284 | 
            +
            	_stackprof.raw_samples = NULL;
         | 
| 285 | 
            +
            	_stackprof.raw_samples_len = 0;
         | 
| 286 | 
            +
            	_stackprof.raw_samples_capa = 0;
         | 
| 287 | 
            +
            	_stackprof.raw_sample_index = 0;
         | 
| 288 | 
            +
             | 
| 289 | 
            +
            	rb_hash_aset(results, sym_raw, raw_samples);
         | 
| 290 | 
            +
             | 
| 291 | 
            +
            	raw_timestamp_deltas = rb_ary_new_capa(_stackprof.raw_timestamp_deltas_len);
         | 
| 292 | 
            +
             | 
| 293 | 
            +
            	for (n = 0; n < _stackprof.raw_timestamp_deltas_len; n++) {
         | 
| 294 | 
            +
            	    rb_ary_push(raw_timestamp_deltas, INT2FIX(_stackprof.raw_timestamp_deltas[n]));
         | 
| 295 | 
            +
            	}
         | 
| 296 | 
            +
             | 
| 297 | 
            +
            	free(_stackprof.raw_timestamp_deltas);
         | 
| 298 | 
            +
            	_stackprof.raw_timestamp_deltas = NULL;
         | 
| 299 | 
            +
            	_stackprof.raw_timestamp_deltas_len = 0;
         | 
| 300 | 
            +
            	_stackprof.raw_timestamp_deltas_capa = 0;
         | 
| 301 | 
            +
             | 
| 302 | 
            +
            	rb_hash_aset(results, sym_raw_timestamp_deltas, raw_timestamp_deltas);
         | 
| 303 | 
            +
             | 
| 304 | 
            +
            	_stackprof.raw = 0;
         | 
| 305 | 
            +
                }
         | 
| 306 | 
            +
             | 
| 307 | 
            +
                if (argc == 1)
         | 
| 308 | 
            +
            	_stackprof.out = argv[0];
         | 
| 309 | 
            +
             | 
| 310 | 
            +
                if (RTEST(_stackprof.out)) {
         | 
| 311 | 
            +
            	VALUE file;
         | 
| 312 | 
            +
            	if (rb_respond_to(_stackprof.out, rb_intern("to_io"))) {
         | 
| 313 | 
            +
            	    file = rb_io_check_io(_stackprof.out);
         | 
| 314 | 
            +
            	} else {
         | 
| 315 | 
            +
            	    file = rb_file_open_str(_stackprof.out, "w");
         | 
| 316 | 
            +
            	}
         | 
| 317 | 
            +
             | 
| 318 | 
            +
            	rb_marshal_dump(results, file);
         | 
| 319 | 
            +
            	rb_io_flush(file);
         | 
| 320 | 
            +
            	_stackprof.out = Qnil;
         | 
| 321 | 
            +
            	return file;
         | 
| 322 | 
            +
                } else {
         | 
| 323 | 
            +
            	return results;
         | 
| 324 | 
            +
                }
         | 
| 325 | 
            +
            }
         | 
| 326 | 
            +
             | 
| 327 | 
            +
            static VALUE
         | 
| 328 | 
            +
            stackprof_run(int argc, VALUE *argv, VALUE self)
         | 
| 329 | 
            +
            {
         | 
| 330 | 
            +
                rb_need_block();
         | 
| 331 | 
            +
                stackprof_start(argc, argv, self);
         | 
| 332 | 
            +
                rb_ensure(rb_yield, Qundef, stackprof_stop, self);
         | 
| 333 | 
            +
                return stackprof_results(0, 0, self);
         | 
| 334 | 
            +
            }
         | 
| 335 | 
            +
             | 
| 336 | 
            +
            static VALUE
         | 
| 337 | 
            +
            stackprof_running_p(VALUE self)
         | 
| 338 | 
            +
            {
         | 
| 339 | 
            +
                return _stackprof.running ? Qtrue : Qfalse;
         | 
| 340 | 
            +
            }
         | 
| 341 | 
            +
             | 
| 342 | 
            +
            static inline frame_data_t *
         | 
| 343 | 
            +
            sample_for(VALUE frame)
         | 
| 344 | 
            +
            {
         | 
| 345 | 
            +
                st_data_t key = (st_data_t)frame, val = 0;
         | 
| 346 | 
            +
                frame_data_t *frame_data;
         | 
| 347 | 
            +
             | 
| 348 | 
            +
                if (st_lookup(_stackprof.frames, key, &val)) {
         | 
| 349 | 
            +
                    frame_data = (frame_data_t *)val;
         | 
| 350 | 
            +
                } else {
         | 
| 351 | 
            +
                    frame_data = ALLOC_N(frame_data_t, 1);
         | 
| 352 | 
            +
                    MEMZERO(frame_data, frame_data_t, 1);
         | 
| 353 | 
            +
                    val = (st_data_t)frame_data;
         | 
| 354 | 
            +
                    st_insert(_stackprof.frames, key, val);
         | 
| 355 | 
            +
                }
         | 
| 356 | 
            +
             | 
| 357 | 
            +
                return frame_data;
         | 
| 358 | 
            +
            }
         | 
| 359 | 
            +
             | 
| 360 | 
            +
            static int
         | 
| 361 | 
            +
            numtable_increment_callback(st_data_t *key, st_data_t *value, st_data_t arg, int existing)
         | 
| 362 | 
            +
            {
         | 
| 363 | 
            +
                size_t *weight = (size_t *)value;
         | 
| 364 | 
            +
                size_t increment = (size_t)arg;
         | 
| 365 | 
            +
             | 
| 366 | 
            +
                if (existing)
         | 
| 367 | 
            +
            	(*weight) += increment;
         | 
| 368 | 
            +
                else
         | 
| 369 | 
            +
            	*weight = increment;
         | 
| 370 | 
            +
             | 
| 371 | 
            +
                return ST_CONTINUE;
         | 
| 372 | 
            +
            }
         | 
| 373 | 
            +
             | 
| 374 | 
            +
            void
         | 
| 375 | 
            +
            st_numtable_increment(st_table *table, st_data_t key, size_t increment)
         | 
| 376 | 
            +
            {
         | 
| 377 | 
            +
                st_update(table, key, numtable_increment_callback, (st_data_t)increment);
         | 
| 378 | 
            +
            }
         | 
| 379 | 
            +
             | 
| 380 | 
            +
            void
         | 
| 381 | 
            +
            stackprof_record_sample_for_stack(int num, int timestamp_delta)
         | 
| 382 | 
            +
            {
         | 
| 383 | 
            +
                int i, n;
         | 
| 384 | 
            +
                VALUE prev_frame = Qnil;
         | 
| 385 | 
            +
             | 
| 386 | 
            +
                _stackprof.overall_samples++;
         | 
| 387 | 
            +
             | 
| 388 | 
            +
                if (_stackprof.raw) {
         | 
| 389 | 
            +
            	int found = 0;
         | 
| 390 | 
            +
             | 
| 391 | 
            +
            	/* If there's no sample buffer allocated, then allocate one.  The buffer
         | 
| 392 | 
            +
            	 * format is the number of frames (num), then the list of frames (from
         | 
| 393 | 
            +
            	 * `_stackprof.raw_samples`), followed by the number of times this
         | 
| 394 | 
            +
            	 * particular stack has been seen in a row.  Each "new" stack is added
         | 
| 395 | 
            +
            	 * to the end of the buffer, but if the previous stack is the same as
         | 
| 396 | 
            +
            	 * the current stack, the counter will be incremented. */
         | 
| 397 | 
            +
            	if (!_stackprof.raw_samples) {
         | 
| 398 | 
            +
            	    _stackprof.raw_samples_capa = num * 100;
         | 
| 399 | 
            +
            	    _stackprof.raw_samples = malloc(sizeof(VALUE) * _stackprof.raw_samples_capa);
         | 
| 400 | 
            +
            	}
         | 
| 401 | 
            +
             | 
| 402 | 
            +
            	/* If we can't fit all the samples in the buffer, double the buffer size. */
         | 
| 403 | 
            +
            	while (_stackprof.raw_samples_capa <= _stackprof.raw_samples_len + (num + 2)) {
         | 
| 404 | 
            +
            	    _stackprof.raw_samples_capa *= 2;
         | 
| 405 | 
            +
            	    _stackprof.raw_samples = realloc(_stackprof.raw_samples, sizeof(VALUE) * _stackprof.raw_samples_capa);
         | 
| 406 | 
            +
            	}
         | 
| 407 | 
            +
             | 
| 408 | 
            +
            	/* If we've seen this stack before in the last sample, then increment the "seen" count. */
         | 
| 409 | 
            +
            	if (_stackprof.raw_samples_len > 0 && _stackprof.raw_samples[_stackprof.raw_sample_index] == (VALUE)num) {
         | 
| 410 | 
            +
            	    /* The number of samples could have been the same, but the stack
         | 
| 411 | 
            +
            	     * might be different, so we need to check the stack here.  Stacks
         | 
| 412 | 
            +
            	     * in the raw buffer are stored in the opposite direction of stacks
         | 
| 413 | 
            +
            	     * in the frames buffer that came from Ruby. */
         | 
| 414 | 
            +
            	    for (i = num-1, n = 0; i >= 0; i--, n++) {
         | 
| 415 | 
            +
            		VALUE frame = _stackprof.frames_buffer[i];
         | 
| 416 | 
            +
            		if (_stackprof.raw_samples[_stackprof.raw_sample_index + 1 + n] != frame)
         | 
| 417 | 
            +
            		    break;
         | 
| 418 | 
            +
            	    }
         | 
| 419 | 
            +
            	    if (i == -1) {
         | 
| 420 | 
            +
            		_stackprof.raw_samples[_stackprof.raw_samples_len-1] += 1;
         | 
| 421 | 
            +
            		found = 1;
         | 
| 422 | 
            +
            	    }
         | 
| 423 | 
            +
            	}
         | 
| 424 | 
            +
             | 
| 425 | 
            +
            	/* If we haven't seen the stack, then add it to the buffer along with
         | 
| 426 | 
            +
            	 * the length of the stack and a 1 for the "seen" count */
         | 
| 427 | 
            +
            	if (!found) {
         | 
| 428 | 
            +
            	    /* Bump the `raw_sample_index` up so that the next iteration can
         | 
| 429 | 
            +
            	     * find the previously recorded stack size. */
         | 
| 430 | 
            +
            	    _stackprof.raw_sample_index = _stackprof.raw_samples_len;
         | 
| 431 | 
            +
            	    _stackprof.raw_samples[_stackprof.raw_samples_len++] = (VALUE)num;
         | 
| 432 | 
            +
            	    for (i = num-1; i >= 0; i--) {
         | 
| 433 | 
            +
            		VALUE frame = _stackprof.frames_buffer[i];
         | 
| 434 | 
            +
            		_stackprof.raw_samples[_stackprof.raw_samples_len++] = frame;
         | 
| 435 | 
            +
            	    }
         | 
| 436 | 
            +
            	    _stackprof.raw_samples[_stackprof.raw_samples_len++] = (VALUE)1;
         | 
| 437 | 
            +
            	}
         | 
| 438 | 
            +
             | 
| 439 | 
            +
            	/* If there's no timestamp delta buffer, allocate one */
         | 
| 440 | 
            +
            	if (!_stackprof.raw_timestamp_deltas) {
         | 
| 441 | 
            +
            	    _stackprof.raw_timestamp_deltas_capa = 100;
         | 
| 442 | 
            +
            	    _stackprof.raw_timestamp_deltas = malloc(sizeof(int) * _stackprof.raw_timestamp_deltas_capa);
         | 
| 443 | 
            +
            	    _stackprof.raw_timestamp_deltas_len = 0;
         | 
| 444 | 
            +
            	}
         | 
| 445 | 
            +
             | 
| 446 | 
            +
            	/* Double the buffer size if it's too small */
         | 
| 447 | 
            +
            	while (_stackprof.raw_timestamp_deltas_capa <= _stackprof.raw_timestamp_deltas_len + 1) {
         | 
| 448 | 
            +
            	    _stackprof.raw_timestamp_deltas_capa *= 2;
         | 
| 449 | 
            +
            	    _stackprof.raw_timestamp_deltas = realloc(_stackprof.raw_timestamp_deltas, sizeof(int) * _stackprof.raw_timestamp_deltas_capa);
         | 
| 450 | 
            +
            	}
         | 
| 451 | 
            +
             | 
| 452 | 
            +
            	/* Store the time delta (which is the amount of time between samples) */
         | 
| 453 | 
            +
            	_stackprof.raw_timestamp_deltas[_stackprof.raw_timestamp_deltas_len++] = timestamp_delta;
         | 
| 454 | 
            +
                }
         | 
| 455 | 
            +
             | 
| 456 | 
            +
                for (i = 0; i < num; i++) {
         | 
| 457 | 
            +
            	int line = _stackprof.lines_buffer[i];
         | 
| 458 | 
            +
            	VALUE frame = _stackprof.frames_buffer[i];
         | 
| 459 | 
            +
            	frame_data_t *frame_data = sample_for(frame);
         | 
| 460 | 
            +
             | 
| 461 | 
            +
            	if (frame_data->seen_at_sample_number != _stackprof.overall_samples) {
         | 
| 462 | 
            +
            	    frame_data->total_samples++;
         | 
| 463 | 
            +
            	}
         | 
| 464 | 
            +
            	frame_data->seen_at_sample_number = _stackprof.overall_samples;
         | 
| 465 | 
            +
             | 
| 466 | 
            +
            	if (i == 0) {
         | 
| 467 | 
            +
            	    frame_data->caller_samples++;
         | 
| 468 | 
            +
            	} else if (_stackprof.aggregate) {
         | 
| 469 | 
            +
            	    if (!frame_data->edges)
         | 
| 470 | 
            +
            		frame_data->edges = st_init_numtable();
         | 
| 471 | 
            +
            	    st_numtable_increment(frame_data->edges, (st_data_t)prev_frame, 1);
         | 
| 472 | 
            +
            	}
         | 
| 473 | 
            +
             | 
| 474 | 
            +
            	if (_stackprof.aggregate && line > 0) {
         | 
| 475 | 
            +
            	    size_t half = (size_t)1<<(8*SIZEOF_SIZE_T/2);
         | 
| 476 | 
            +
            	    size_t increment = i == 0 ? half + 1 : half;
         | 
| 477 | 
            +
            	    if (!frame_data->lines)
         | 
| 478 | 
            +
            		frame_data->lines = st_init_numtable();
         | 
| 479 | 
            +
            	    st_numtable_increment(frame_data->lines, (st_data_t)line, increment);
         | 
| 480 | 
            +
            	}
         | 
| 481 | 
            +
             | 
| 482 | 
            +
            	prev_frame = frame;
         | 
| 483 | 
            +
                }
         | 
| 484 | 
            +
             | 
| 485 | 
            +
                if (_stackprof.raw) {
         | 
| 486 | 
            +
            	gettimeofday(&_stackprof.last_sample_at, NULL);
         | 
| 487 | 
            +
                }
         | 
| 488 | 
            +
            }
         | 
| 489 | 
            +
             | 
| 490 | 
            +
            void
         | 
| 491 | 
            +
            stackprof_record_sample()
         | 
| 492 | 
            +
            {
         | 
| 493 | 
            +
                int timestamp_delta = 0;
         | 
| 494 | 
            +
                int num;
         | 
| 495 | 
            +
                if (_stackprof.raw) {
         | 
| 496 | 
            +
            	struct timeval t;
         | 
| 497 | 
            +
            	struct timeval diff;
         | 
| 498 | 
            +
            	gettimeofday(&t, NULL);
         | 
| 499 | 
            +
            	timersub(&t, &_stackprof.last_sample_at, &diff);
         | 
| 500 | 
            +
            	timestamp_delta = (1000 * diff.tv_sec) + diff.tv_usec;
         | 
| 501 | 
            +
                }
         | 
| 502 | 
            +
                num = rb_profile_frames(0, sizeof(_stackprof.frames_buffer) / sizeof(VALUE), _stackprof.frames_buffer, _stackprof.lines_buffer);
         | 
| 503 | 
            +
                stackprof_record_sample_for_stack(num, timestamp_delta);
         | 
| 504 | 
            +
            }
         | 
| 505 | 
            +
             | 
| 506 | 
            +
            void
         | 
| 507 | 
            +
            stackprof_record_gc_samples()
         | 
| 508 | 
            +
            {
         | 
| 509 | 
            +
                int delta_to_first_unrecorded_gc_sample = 0;
         | 
| 510 | 
            +
                int i;
         | 
| 511 | 
            +
                if (_stackprof.raw) {
         | 
| 512 | 
            +
            	struct timeval t;
         | 
| 513 | 
            +
            	struct timeval diff;
         | 
| 514 | 
            +
            	gettimeofday(&t, NULL);
         | 
| 515 | 
            +
            	timersub(&t, &_stackprof.last_sample_at, &diff);
         | 
| 516 | 
            +
             | 
| 517 | 
            +
            	// We don't know when the GC samples were actually marked, so let's
         | 
| 518 | 
            +
            	// assume that they were marked at a perfectly regular interval.
         | 
| 519 | 
            +
            	delta_to_first_unrecorded_gc_sample = (1000 * diff.tv_sec + diff.tv_usec) - (_stackprof.unrecorded_gc_samples - 1) * NUM2LONG(_stackprof.interval);
         | 
| 520 | 
            +
            	if (delta_to_first_unrecorded_gc_sample < 0) {
         | 
| 521 | 
            +
            	    delta_to_first_unrecorded_gc_sample = 0;
         | 
| 522 | 
            +
            	}
         | 
| 523 | 
            +
                }
         | 
| 524 | 
            +
             | 
| 525 | 
            +
                _stackprof.frames_buffer[0] = _stackprof.fake_gc_frame;
         | 
| 526 | 
            +
                _stackprof.lines_buffer[0] = 0;
         | 
| 527 | 
            +
             | 
| 528 | 
            +
                for (i = 0; i < _stackprof.unrecorded_gc_samples; i++) {
         | 
| 529 | 
            +
            	int timestamp_delta = i == 0 ? delta_to_first_unrecorded_gc_sample : NUM2LONG(_stackprof.interval);
         | 
| 530 | 
            +
            	stackprof_record_sample_for_stack(1, timestamp_delta);
         | 
| 531 | 
            +
                }
         | 
| 532 | 
            +
                _stackprof.during_gc += _stackprof.unrecorded_gc_samples;
         | 
| 533 | 
            +
                _stackprof.unrecorded_gc_samples = 0;
         | 
| 534 | 
            +
            }
         | 
| 535 | 
            +
             | 
| 536 | 
            +
            static void
         | 
| 537 | 
            +
            stackprof_gc_job_handler(void *data)
         | 
| 538 | 
            +
            {
         | 
| 539 | 
            +
                static int in_signal_handler = 0;
         | 
| 540 | 
            +
                if (in_signal_handler) return;
         | 
| 541 | 
            +
                if (!_stackprof.running) return;
         | 
| 542 | 
            +
             | 
| 543 | 
            +
                in_signal_handler++;
         | 
| 544 | 
            +
                stackprof_record_gc_samples();
         | 
| 545 | 
            +
                in_signal_handler--;
         | 
| 546 | 
            +
            }
         | 
| 547 | 
            +
             | 
| 548 | 
            +
            static void
         | 
| 549 | 
            +
            stackprof_job_handler(void *data)
         | 
| 550 | 
            +
            {
         | 
| 551 | 
            +
                static int in_signal_handler = 0;
         | 
| 552 | 
            +
                if (in_signal_handler) return;
         | 
| 553 | 
            +
                if (!_stackprof.running) return;
         | 
| 554 | 
            +
             | 
| 555 | 
            +
                in_signal_handler++;
         | 
| 556 | 
            +
                stackprof_record_sample();
         | 
| 557 | 
            +
                in_signal_handler--;
         | 
| 558 | 
            +
            }
         | 
| 559 | 
            +
             | 
| 560 | 
            +
            static void
         | 
| 561 | 
            +
            stackprof_signal_handler(int sig, siginfo_t *sinfo, void *ucontext)
         | 
| 562 | 
            +
            {
         | 
| 563 | 
            +
                _stackprof.overall_signals++;
         | 
| 564 | 
            +
                if (rb_during_gc()) {
         | 
| 565 | 
            +
            	_stackprof.unrecorded_gc_samples++;
         | 
| 566 | 
            +
            	rb_postponed_job_register_one(0, stackprof_gc_job_handler, (void*)0);
         | 
| 567 | 
            +
                } else {
         | 
| 568 | 
            +
            	rb_postponed_job_register_one(0, stackprof_job_handler, (void*)0);
         | 
| 569 | 
            +
                }
         | 
| 570 | 
            +
            }
         | 
| 571 | 
            +
             | 
| 572 | 
            +
            static void
         | 
| 573 | 
            +
            stackprof_newobj_handler(VALUE tpval, void *data)
         | 
| 574 | 
            +
            {
         | 
| 575 | 
            +
                _stackprof.overall_signals++;
         | 
| 576 | 
            +
                if (RTEST(_stackprof.interval) && _stackprof.overall_signals % NUM2LONG(_stackprof.interval))
         | 
| 577 | 
            +
            	return;
         | 
| 578 | 
            +
                stackprof_job_handler(0);
         | 
| 579 | 
            +
            }
         | 
| 580 | 
            +
             | 
| 581 | 
            +
            static VALUE
         | 
| 582 | 
            +
            stackprof_sample(VALUE self)
         | 
| 583 | 
            +
            {
         | 
| 584 | 
            +
                if (!_stackprof.running)
         | 
| 585 | 
            +
            	return Qfalse;
         | 
| 586 | 
            +
             | 
| 587 | 
            +
                _stackprof.overall_signals++;
         | 
| 588 | 
            +
                stackprof_job_handler(0);
         | 
| 589 | 
            +
                return Qtrue;
         | 
| 590 | 
            +
            }
         | 
| 591 | 
            +
             | 
| 592 | 
            +
            static int
         | 
| 593 | 
            +
            frame_mark_i(st_data_t key, st_data_t val, st_data_t arg)
         | 
| 594 | 
            +
            {
         | 
| 595 | 
            +
                VALUE frame = (VALUE)key;
         | 
| 596 | 
            +
                rb_gc_mark(frame);
         | 
| 597 | 
            +
                return ST_CONTINUE;
         | 
| 598 | 
            +
            }
         | 
| 599 | 
            +
             | 
| 600 | 
            +
            static void
         | 
| 601 | 
            +
            stackprof_gc_mark(void *data)
         | 
| 602 | 
            +
            {
         | 
| 603 | 
            +
                if (RTEST(_stackprof.out))
         | 
| 604 | 
            +
            	rb_gc_mark(_stackprof.out);
         | 
| 605 | 
            +
             | 
| 606 | 
            +
                if (_stackprof.frames)
         | 
| 607 | 
            +
            	st_foreach(_stackprof.frames, frame_mark_i, 0);
         | 
| 608 | 
            +
            }
         | 
| 609 | 
            +
             | 
| 610 | 
            +
            static void
         | 
| 611 | 
            +
            stackprof_atfork_prepare(void)
         | 
| 612 | 
            +
            {
         | 
| 613 | 
            +
                struct itimerval timer;
         | 
| 614 | 
            +
                if (_stackprof.running) {
         | 
| 615 | 
            +
            	if (_stackprof.mode == sym_wall || _stackprof.mode == sym_cpu) {
         | 
| 616 | 
            +
            	    memset(&timer, 0, sizeof(timer));
         | 
| 617 | 
            +
            	    setitimer(_stackprof.mode == sym_wall ? ITIMER_REAL : ITIMER_PROF, &timer, 0);
         | 
| 618 | 
            +
            	}
         | 
| 619 | 
            +
                }
         | 
| 620 | 
            +
            }
         | 
| 621 | 
            +
             | 
| 622 | 
            +
            static void
         | 
| 623 | 
            +
            stackprof_atfork_parent(void)
         | 
| 624 | 
            +
            {
         | 
| 625 | 
            +
                struct itimerval timer;
         | 
| 626 | 
            +
                if (_stackprof.running) {
         | 
| 627 | 
            +
            	if (_stackprof.mode == sym_wall || _stackprof.mode == sym_cpu) {
         | 
| 628 | 
            +
            	    timer.it_interval.tv_sec = 0;
         | 
| 629 | 
            +
            	    timer.it_interval.tv_usec = NUM2LONG(_stackprof.interval);
         | 
| 630 | 
            +
            	    timer.it_value = timer.it_interval;
         | 
| 631 | 
            +
            	    setitimer(_stackprof.mode == sym_wall ? ITIMER_REAL : ITIMER_PROF, &timer, 0);
         | 
| 632 | 
            +
            	}
         | 
| 633 | 
            +
                }
         | 
| 634 | 
            +
            }
         | 
| 635 | 
            +
             | 
| 636 | 
            +
            static void
         | 
| 637 | 
            +
            stackprof_atfork_child(void)
         | 
| 638 | 
            +
            {
         | 
| 639 | 
            +
                stackprof_stop(rb_mStackProf);
         | 
| 640 | 
            +
            }
         | 
| 641 | 
            +
             | 
| 642 | 
            +
            void
         | 
| 643 | 
            +
            Init_stackprof(void)
         | 
| 644 | 
            +
            {
         | 
| 645 | 
            +
            #define S(name) sym_##name = ID2SYM(rb_intern(#name));
         | 
| 646 | 
            +
                S(object);
         | 
| 647 | 
            +
                S(custom);
         | 
| 648 | 
            +
                S(wall);
         | 
| 649 | 
            +
                S(cpu);
         | 
| 650 | 
            +
                S(name);
         | 
| 651 | 
            +
                S(file);
         | 
| 652 | 
            +
                S(line);
         | 
| 653 | 
            +
                S(total_samples);
         | 
| 654 | 
            +
                S(gc_samples);
         | 
| 655 | 
            +
                S(missed_samples);
         | 
| 656 | 
            +
                S(samples);
         | 
| 657 | 
            +
                S(edges);
         | 
| 658 | 
            +
                S(lines);
         | 
| 659 | 
            +
                S(version);
         | 
| 660 | 
            +
                S(mode);
         | 
| 661 | 
            +
                S(interval);
         | 
| 662 | 
            +
                S(raw);
         | 
| 663 | 
            +
                S(raw_timestamp_deltas);
         | 
| 664 | 
            +
                S(out);
         | 
| 665 | 
            +
                S(frames);
         | 
| 666 | 
            +
                S(aggregate);
         | 
| 667 | 
            +
            #undef S
         | 
| 668 | 
            +
             | 
| 669 | 
            +
                gc_hook = Data_Wrap_Struct(rb_cObject, stackprof_gc_mark, NULL, &_stackprof);
         | 
| 670 | 
            +
                rb_global_variable(&gc_hook);
         | 
| 671 | 
            +
             | 
| 672 | 
            +
                _stackprof.raw_samples = NULL;
         | 
| 673 | 
            +
                _stackprof.raw_samples_len = 0;
         | 
| 674 | 
            +
                _stackprof.raw_samples_capa = 0;
         | 
| 675 | 
            +
                _stackprof.raw_sample_index = 0;
         | 
| 676 | 
            +
             | 
| 677 | 
            +
                _stackprof.raw_timestamp_deltas = NULL;
         | 
| 678 | 
            +
                _stackprof.raw_timestamp_deltas_len = 0;
         | 
| 679 | 
            +
                _stackprof.raw_timestamp_deltas_capa = 0;
         | 
| 680 | 
            +
             | 
| 681 | 
            +
                _stackprof.fake_gc_frame = INT2FIX(0x9C);
         | 
| 682 | 
            +
                _stackprof.empty_string = rb_str_new_cstr("");
         | 
| 683 | 
            +
                _stackprof.fake_gc_frame_name = rb_str_new_cstr("(garbage collection)");
         | 
| 684 | 
            +
                rb_global_variable(&_stackprof.fake_gc_frame_name);
         | 
| 685 | 
            +
                rb_global_variable(&_stackprof.empty_string);
         | 
| 686 | 
            +
             | 
| 687 | 
            +
                rb_mStackProf = rb_define_module("StackProf");
         | 
| 688 | 
            +
                rb_define_singleton_method(rb_mStackProf, "running?", stackprof_running_p, 0);
         | 
| 689 | 
            +
                rb_define_singleton_method(rb_mStackProf, "run", stackprof_run, -1);
         | 
| 690 | 
            +
                rb_define_singleton_method(rb_mStackProf, "start", stackprof_start, -1);
         | 
| 691 | 
            +
                rb_define_singleton_method(rb_mStackProf, "stop", stackprof_stop, 0);
         | 
| 692 | 
            +
                rb_define_singleton_method(rb_mStackProf, "results", stackprof_results, -1);
         | 
| 693 | 
            +
                rb_define_singleton_method(rb_mStackProf, "sample", stackprof_sample, 0);
         | 
| 694 | 
            +
             | 
| 695 | 
            +
                pthread_atfork(stackprof_atfork_prepare, stackprof_atfork_parent, stackprof_atfork_child);
         | 
| 696 | 
            +
            }
         | 
    
        data/lib/stackprof.rb
    CHANGED
    
    
    
        data/lib/stackprof/report.rb
    CHANGED
    
    | @@ -68,6 +68,11 @@ module StackProf | |
| 68 68 | 
             
                  f.puts Marshal.dump(@data.reject{|k,v| k == :files })
         | 
| 69 69 | 
             
                end
         | 
| 70 70 |  | 
| 71 | 
            +
                def print_json(f=STDOUT)
         | 
| 72 | 
            +
                  require "json"
         | 
| 73 | 
            +
                  f.puts JSON.generate(@data, max_nesting: false)
         | 
| 74 | 
            +
                end
         | 
| 75 | 
            +
             | 
| 71 76 | 
             
                def print_stackcollapse
         | 
| 72 77 | 
             
                  raise "profile does not include raw samples (add `raw: true` to collecting StackProf.run)" unless raw = data[:raw]
         | 
| 73 78 |  | 
| @@ -80,7 +85,15 @@ module StackProf | |
| 80 85 | 
             
                  end
         | 
| 81 86 | 
             
                end
         | 
| 82 87 |  | 
| 83 | 
            -
                def  | 
| 88 | 
            +
                def print_timeline_flamegraph(f=STDOUT, skip_common=true)
         | 
| 89 | 
            +
                  print_flamegraph(f, skip_common, false)
         | 
| 90 | 
            +
                end
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                def print_alphabetical_flamegraph(f=STDOUT, skip_common=true)
         | 
| 93 | 
            +
                  print_flamegraph(f, skip_common, true)
         | 
| 94 | 
            +
                end
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                def print_flamegraph(f, skip_common, alphabetical=false)
         | 
| 84 97 | 
             
                  raise "profile does not include raw samples (add `raw: true` to collecting StackProf.run)" unless raw = data[:raw]
         | 
| 85 98 |  | 
| 86 99 | 
             
                  stacks = []
         | 
| @@ -93,6 +106,8 @@ module StackProf | |
| 93 106 | 
             
                    max_x += stack.last
         | 
| 94 107 | 
             
                  end
         | 
| 95 108 |  | 
| 109 | 
            +
                  stacks.sort! if alphabetical
         | 
| 110 | 
            +
             | 
| 96 111 | 
             
                  f.puts 'flamegraph(['
         | 
| 97 112 | 
             
                  max_y.times do |y|
         | 
| 98 113 | 
             
                    row_prev = nil
         | 
| @@ -148,6 +163,216 @@ module StackProf | |
| 148 163 | 
             
                  f.puts %{{"x":#{x},"y":#{y},"width":#{weight},"frame_id":#{addr},"frame":#{frame[:name].dump},"file":#{frame[:file].dump}}}
         | 
| 149 164 | 
             
                end
         | 
| 150 165 |  | 
| 166 | 
            +
                def convert_to_d3_flame_graph_format(name, stacks, depth)
         | 
| 167 | 
            +
                  weight = 0
         | 
| 168 | 
            +
                  children = []
         | 
| 169 | 
            +
                  stacks.chunk do |stack|
         | 
| 170 | 
            +
                    if depth == stack.length - 1
         | 
| 171 | 
            +
                      :leaf
         | 
| 172 | 
            +
                    else
         | 
| 173 | 
            +
                      stack[depth]
         | 
| 174 | 
            +
                    end
         | 
| 175 | 
            +
                  end.each do |val, child_stacks|
         | 
| 176 | 
            +
                    if val == :leaf
         | 
| 177 | 
            +
                      child_stacks.each do |stack|
         | 
| 178 | 
            +
                        weight += stack.last
         | 
| 179 | 
            +
                      end
         | 
| 180 | 
            +
                    else
         | 
| 181 | 
            +
                      frame = frames[val]
         | 
| 182 | 
            +
                      child_name = "#{ frame[:name] } : #{ frame[:file] }"
         | 
| 183 | 
            +
                      child_data = convert_to_d3_flame_graph_format(child_name, child_stacks, depth + 1)
         | 
| 184 | 
            +
                      weight += child_data["value"]
         | 
| 185 | 
            +
                      children << child_data
         | 
| 186 | 
            +
                    end
         | 
| 187 | 
            +
                  end
         | 
| 188 | 
            +
             | 
| 189 | 
            +
                  {
         | 
| 190 | 
            +
                    "name" => name,
         | 
| 191 | 
            +
                    "value" => weight,
         | 
| 192 | 
            +
                    "children" => children,
         | 
| 193 | 
            +
                  }
         | 
| 194 | 
            +
                end
         | 
| 195 | 
            +
             | 
| 196 | 
            +
                def print_d3_flamegraph(f=STDOUT, skip_common=true)
         | 
| 197 | 
            +
                  raise "profile does not include raw samples (add `raw: true` to collecting StackProf.run)" unless raw = data[:raw]
         | 
| 198 | 
            +
             | 
| 199 | 
            +
                  stacks = []
         | 
| 200 | 
            +
                  max_x = 0
         | 
| 201 | 
            +
                  max_y = 0
         | 
| 202 | 
            +
                  while len = raw.shift
         | 
| 203 | 
            +
                    max_y = len if len > max_y
         | 
| 204 | 
            +
                    stack = raw.slice!(0, len+1)
         | 
| 205 | 
            +
                    stacks << stack
         | 
| 206 | 
            +
                    max_x += stack.last
         | 
| 207 | 
            +
                  end
         | 
| 208 | 
            +
             | 
| 209 | 
            +
                  # d3-flame-grpah supports only alphabetical flamegraph
         | 
| 210 | 
            +
                  stacks.sort!
         | 
| 211 | 
            +
             | 
| 212 | 
            +
                  require "json"
         | 
| 213 | 
            +
                  json = JSON.generate(convert_to_d3_flame_graph_format("<root>", stacks, 0), max_nesting: false)
         | 
| 214 | 
            +
             | 
| 215 | 
            +
                  # This html code is almost copied from d3-flame-graph sample code.
         | 
| 216 | 
            +
                  # (Apache License 2.0)
         | 
| 217 | 
            +
                  # https://github.com/spiermar/d3-flame-graph/blob/gh-pages/index.html
         | 
| 218 | 
            +
             | 
| 219 | 
            +
                  f.print <<-END
         | 
| 220 | 
            +
            <!DOCTYPE html>
         | 
| 221 | 
            +
            <html lang="en">
         | 
| 222 | 
            +
              <head>
         | 
| 223 | 
            +
                <meta charset="utf-8">
         | 
| 224 | 
            +
                <meta http-equiv="X-UA-Compatible" content="IE=edge">
         | 
| 225 | 
            +
                <meta name="viewport" content="width=device-width, initial-scale=1">
         | 
| 226 | 
            +
             | 
| 227 | 
            +
                <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
         | 
| 228 | 
            +
                <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/gh/spiermar/d3-flame-graph@2.0.3/dist/d3-flamegraph.css">
         | 
| 229 | 
            +
             | 
| 230 | 
            +
                <style>
         | 
| 231 | 
            +
             | 
| 232 | 
            +
                /* Space out content a bit */
         | 
| 233 | 
            +
                body {
         | 
| 234 | 
            +
                  padding-top: 20px;
         | 
| 235 | 
            +
                  padding-bottom: 20px;
         | 
| 236 | 
            +
                }
         | 
| 237 | 
            +
             | 
| 238 | 
            +
                /* Custom page header */
         | 
| 239 | 
            +
                .header {
         | 
| 240 | 
            +
                  padding-bottom: 20px;
         | 
| 241 | 
            +
                  padding-right: 15px;
         | 
| 242 | 
            +
                  padding-left: 15px;
         | 
| 243 | 
            +
                  border-bottom: 1px solid #e5e5e5;
         | 
| 244 | 
            +
                }
         | 
| 245 | 
            +
             | 
| 246 | 
            +
                /* Make the masthead heading the same height as the navigation */
         | 
| 247 | 
            +
                .header h3 {
         | 
| 248 | 
            +
                  margin-top: 0;
         | 
| 249 | 
            +
                  margin-bottom: 0;
         | 
| 250 | 
            +
                  line-height: 40px;
         | 
| 251 | 
            +
                }
         | 
| 252 | 
            +
             | 
| 253 | 
            +
                /* Customize container */
         | 
| 254 | 
            +
                .container {
         | 
| 255 | 
            +
                  max-width: 990px;
         | 
| 256 | 
            +
                }
         | 
| 257 | 
            +
             | 
| 258 | 
            +
                address {
         | 
| 259 | 
            +
                  text-align: right;
         | 
| 260 | 
            +
                }
         | 
| 261 | 
            +
                </style>
         | 
| 262 | 
            +
             | 
| 263 | 
            +
                <title>stackprof (mode: #{ data[:mode] })</title>
         | 
| 264 | 
            +
             | 
| 265 | 
            +
                <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
         | 
| 266 | 
            +
                <!--[if lt IE 9]>
         | 
| 267 | 
            +
                  <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
         | 
| 268 | 
            +
                  <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
         | 
| 269 | 
            +
                <![endif]-->
         | 
| 270 | 
            +
              </head>
         | 
| 271 | 
            +
              <body>
         | 
| 272 | 
            +
                <div class="container">
         | 
| 273 | 
            +
                  <div class="header clearfix">
         | 
| 274 | 
            +
                    <nav>
         | 
| 275 | 
            +
                      <div class="pull-right">
         | 
| 276 | 
            +
                        <form class="form-inline" id="form">
         | 
| 277 | 
            +
                          <a class="btn" href="javascript: resetZoom();">Reset zoom</a>
         | 
| 278 | 
            +
                          <a class="btn" href="javascript: clear();">Clear</a>
         | 
| 279 | 
            +
                          <div class="form-group">
         | 
| 280 | 
            +
                            <input type="text" class="form-control" id="term">
         | 
| 281 | 
            +
                          </div>
         | 
| 282 | 
            +
                          <a class="btn btn-primary" href="javascript: search();">Search</a>
         | 
| 283 | 
            +
                        </form>
         | 
| 284 | 
            +
                      </div>
         | 
| 285 | 
            +
                    </nav>
         | 
| 286 | 
            +
                    <h3 class="text-muted">stackprof (mode: #{ data[:mode] })</h3>
         | 
| 287 | 
            +
                  </div>
         | 
| 288 | 
            +
                  <div id="chart">
         | 
| 289 | 
            +
                  </div>
         | 
| 290 | 
            +
                  <address>
         | 
| 291 | 
            +
                    powered by <a href="https://github.com/spiermar/d3-flame-graph">d3-flame-graph</a>
         | 
| 292 | 
            +
                  </address>
         | 
| 293 | 
            +
                  <hr>
         | 
| 294 | 
            +
                  <div id="details">
         | 
| 295 | 
            +
                  </div>
         | 
| 296 | 
            +
                </div>
         | 
| 297 | 
            +
             | 
| 298 | 
            +
                <!-- D3.js -->
         | 
| 299 | 
            +
                <script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script>
         | 
| 300 | 
            +
             | 
| 301 | 
            +
                <!-- d3-tip -->
         | 
| 302 | 
            +
                <script type="text/javascript" src=https://cdnjs.cloudflare.com/ajax/libs/d3-tip/0.9.1/d3-tip.min.js></script>
         | 
| 303 | 
            +
             | 
| 304 | 
            +
                <!-- d3-flamegraph -->
         | 
| 305 | 
            +
                <script type="text/javascript" src="https://cdn.jsdelivr.net/gh/spiermar/d3-flame-graph@2.0.3/dist/d3-flamegraph.min.js"></script>
         | 
| 306 | 
            +
             | 
| 307 | 
            +
                <script type="text/javascript">
         | 
| 308 | 
            +
                var flameGraph = d3.flamegraph()
         | 
| 309 | 
            +
                  .width(960)
         | 
| 310 | 
            +
                  .cellHeight(18)
         | 
| 311 | 
            +
                  .transitionDuration(750)
         | 
| 312 | 
            +
                  .minFrameSize(5)
         | 
| 313 | 
            +
                  .transitionEase(d3.easeCubic)
         | 
| 314 | 
            +
                  .sort(true)
         | 
| 315 | 
            +
                  //Example to sort in reverse order
         | 
| 316 | 
            +
                  //.sort(function(a,b){ return d3.descending(a.name, b.name);})
         | 
| 317 | 
            +
                  .title("")
         | 
| 318 | 
            +
                  .onClick(onClick)
         | 
| 319 | 
            +
                  .differential(false)
         | 
| 320 | 
            +
                  .selfValue(false);
         | 
| 321 | 
            +
             | 
| 322 | 
            +
             | 
| 323 | 
            +
                // Example on how to use custom tooltips using d3-tip.
         | 
| 324 | 
            +
                // var tip = d3.tip()
         | 
| 325 | 
            +
                //   .direction("s")
         | 
| 326 | 
            +
                //   .offset([8, 0])
         | 
| 327 | 
            +
                //   .attr('class', 'd3-flame-graph-tip')
         | 
| 328 | 
            +
                //   .html(function(d) { return "name: " + d.data.name + ", value: " + d.data.value; });
         | 
| 329 | 
            +
             | 
| 330 | 
            +
                // flameGraph.tooltip(tip);
         | 
| 331 | 
            +
             | 
| 332 | 
            +
                var details = document.getElementById("details");
         | 
| 333 | 
            +
                flameGraph.setDetailsElement(details);
         | 
| 334 | 
            +
             | 
| 335 | 
            +
                // Example on how to use custom labels
         | 
| 336 | 
            +
                // var label = function(d) {
         | 
| 337 | 
            +
                //  return "name: " + d.name + ", value: " + d.value;
         | 
| 338 | 
            +
                // }
         | 
| 339 | 
            +
                // flameGraph.label(label);
         | 
| 340 | 
            +
             | 
| 341 | 
            +
                // Example of how to set fixed chart height
         | 
| 342 | 
            +
                // flameGraph.height(540);
         | 
| 343 | 
            +
             | 
| 344 | 
            +
                d3.select("#chart")
         | 
| 345 | 
            +
                    .datum(#{ json })
         | 
| 346 | 
            +
                    .call(flameGraph);
         | 
| 347 | 
            +
             | 
| 348 | 
            +
                document.getElementById("form").addEventListener("submit", function(event){
         | 
| 349 | 
            +
                  event.preventDefault();
         | 
| 350 | 
            +
                  search();
         | 
| 351 | 
            +
                });
         | 
| 352 | 
            +
             | 
| 353 | 
            +
                function search() {
         | 
| 354 | 
            +
                  var term = document.getElementById("term").value;
         | 
| 355 | 
            +
                  flameGraph.search(term);
         | 
| 356 | 
            +
                }
         | 
| 357 | 
            +
             | 
| 358 | 
            +
                function clear() {
         | 
| 359 | 
            +
                  document.getElementById('term').value = '';
         | 
| 360 | 
            +
                  flameGraph.clear();
         | 
| 361 | 
            +
                }
         | 
| 362 | 
            +
             | 
| 363 | 
            +
                function resetZoom() {
         | 
| 364 | 
            +
                  flameGraph.resetZoom();
         | 
| 365 | 
            +
                }
         | 
| 366 | 
            +
             | 
| 367 | 
            +
                function onClick(d) {
         | 
| 368 | 
            +
                  console.info("Clicked on " + d.data.name);
         | 
| 369 | 
            +
                }
         | 
| 370 | 
            +
                </script>
         | 
| 371 | 
            +
              </body>
         | 
| 372 | 
            +
            </html>
         | 
| 373 | 
            +
                  END
         | 
| 374 | 
            +
                end
         | 
| 375 | 
            +
             | 
| 151 376 | 
             
                def print_graphviz(options = {}, f = STDOUT)
         | 
| 152 377 | 
             
                  if filter = options[:filter]
         | 
| 153 378 | 
             
                    mark_stack = []
         | 
    
        data/stackprof.gemspec
    CHANGED
    
    
    
        data/test/test_stackprof.rb
    CHANGED
    
    | @@ -2,6 +2,7 @@ $:.unshift File.expand_path('../../lib', __FILE__) | |
| 2 2 | 
             
            require 'stackprof'
         | 
| 3 3 | 
             
            require 'minitest/autorun'
         | 
| 4 4 | 
             
            require 'tempfile'
         | 
| 5 | 
            +
            require 'pathname'
         | 
| 5 6 |  | 
| 6 7 | 
             
            class StackProfTest < MiniTest::Test
         | 
| 7 8 | 
             
              def test_info
         | 
| @@ -167,6 +168,32 @@ class StackProfTest < MiniTest::Test | |
| 167 168 | 
             
                refute_empty profile[:frames]
         | 
| 168 169 | 
             
              end
         | 
| 169 170 |  | 
| 171 | 
            +
              def test_out_to_path_string
         | 
| 172 | 
            +
                tmpfile = Tempfile.new('stackprof-out')
         | 
| 173 | 
            +
                ret = StackProf.run(mode: :custom, out: tmpfile.path) do
         | 
| 174 | 
            +
                  StackProf.sample
         | 
| 175 | 
            +
                end
         | 
| 176 | 
            +
             | 
| 177 | 
            +
                refute_equal tmpfile, ret
         | 
| 178 | 
            +
                assert_equal tmpfile.path, ret.path
         | 
| 179 | 
            +
                tmpfile.rewind
         | 
| 180 | 
            +
                profile = Marshal.load(tmpfile.read)
         | 
| 181 | 
            +
                refute_empty profile[:frames]
         | 
| 182 | 
            +
              end
         | 
| 183 | 
            +
             | 
| 184 | 
            +
              def test_pathname_out
         | 
| 185 | 
            +
                tmpfile  = Tempfile.new('stackprof-out')
         | 
| 186 | 
            +
                pathname = Pathname.new(tmpfile.path)
         | 
| 187 | 
            +
                ret = StackProf.run(mode: :custom, out: pathname) do
         | 
| 188 | 
            +
                  StackProf.sample
         | 
| 189 | 
            +
                end
         | 
| 190 | 
            +
             | 
| 191 | 
            +
                assert_equal tmpfile.path, ret.path
         | 
| 192 | 
            +
                tmpfile.rewind
         | 
| 193 | 
            +
                profile = Marshal.load(tmpfile.read)
         | 
| 194 | 
            +
                refute_empty profile[:frames]
         | 
| 195 | 
            +
              end
         | 
| 196 | 
            +
             | 
| 170 197 | 
             
              def math
         | 
| 171 198 | 
             
                250_000.times do
         | 
| 172 199 | 
             
                  2 ** 10
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: stackprof
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0.2. | 
| 4 | 
            +
              version: 0.2.13
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Aman Gupta
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date:  | 
| 11 | 
            +
            date: 2019-10-01 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: rake-compiler
         | 
| @@ -66,6 +66,7 @@ files: | |
| 66 66 | 
             
            - ".gitignore"
         | 
| 67 67 | 
             
            - ".travis.yml"
         | 
| 68 68 | 
             
            - CHANGELOG.md
         | 
| 69 | 
            +
            - Dockerfile
         | 
| 69 70 | 
             
            - Gemfile
         | 
| 70 71 | 
             
            - Gemfile.lock
         | 
| 71 72 | 
             
            - LICENSE
         | 
| @@ -109,8 +110,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 109 110 | 
             
                - !ruby/object:Gem::Version
         | 
| 110 111 | 
             
                  version: '0'
         | 
| 111 112 | 
             
            requirements: []
         | 
| 112 | 
            -
             | 
| 113 | 
            -
            rubygems_version: 2.4.6
         | 
| 113 | 
            +
            rubygems_version: 3.1.0.pre1
         | 
| 114 114 | 
             
            signing_key: 
         | 
| 115 115 | 
             
            specification_version: 4
         | 
| 116 116 | 
             
            summary: sampling callstack-profiler for ruby 2.1+
         |