[
  {
    "path": ".gitignore",
    "content": "*.gem\n*.rbc\n.bundle\n.config\n.yardoc\nGemfile.lock\nInstalledFiles\n_yardoc\ncoverage\ndoc/\nlib/bundler/man\npkg\nrdoc\nspec/reports\ntest/tmp\ntest/version_tmp\ntmp\n*.bundle\n*.DS_Store\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: ruby\r\nrvm:\r\n  - 2.1.0\r\n  - 2.2.0\r\n  - 2.3.0\r\nbefore_install:\r\n  - gem update bundler\r\n"
  },
  {
    "path": "Gemfile",
    "content": "source 'https://rubygems.org'\n\n# Specify your gem's dependencies in allocation_tracer.gemspec\ngemspec\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "Copyright (c) 2014 Koichi Sasada\n\nMIT License\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# ObjectSpace::AllocationTracer\n\nThis module allows to trace object allocation.\n\nThis feature is similar to https://github.com/SamSaffron/memory_profiler\nand https://github.com/srawlins/allocation_stats. But this feature\nfocused on `age' of objects.\n\nThis gem was separated from gc_tracer.gem.\n\n[![Build Status](https://travis-ci.org/ko1/allocation_tracer.svg?branch=master)](https://travis-ci.org/ko1/allocation_tracer)\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n    gem 'allocation_tracer'\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install allocation_tracer\n\n## Usage\n\n### Allocation tracing\n\nYou can trace allocation and aggregate information. Information includes:\n\n* count - how many objects are created.\n* total_age - total age of objects which created here\n* max_age - age of longest living object created here\n* min_age - age of shortest living object created here\n\nAge of Object can be calculated by this formula: [current GC count] - [birth time GC count]\n\nFor example:\n\n```ruby\nrequire 'allocation_tracer'\nrequire 'pp'\n\npp ObjectSpace::AllocationTracer.trace{\n  50_000.times{|i|\n    i.to_s\n    i.to_s\n    i.to_s\n  }\n}\n```\n\nwill show\n\n```\n{[\"test.rb\", 6]=>[50000, 0, 47440, 0, 1, 0],\n [\"test.rb\", 7]=>[50000, 4, 47452, 0, 6, 0],\n [\"test.rb\", 8]=>[50000, 7, 47456, 0, 6, 0]}\n```\n\nIn this case,\n* 50,000 objects are created at `test.rb:6' and\n  * 0 old objects created.\n  * 47,440 is total age of objects created at this line (average age of object created at this line is 47440/50000 = 0.9488).\n  * 0 is minimum age\n  * 1 is maximum age.\n  * 0 total memory consumption without RVALUE\n\nYou can also specify `type' in GC::Tracer.setup_allocation_tracing() to\nspecify what should be keys to aggregate like that.\n\n```ruby\nrequire 'allocation_tracer'\nrequire 'pp'\n\nObjectSpace::AllocationTracer.setup(%i{path line type})\n\nresult = ObjectSpace::AllocationTracer.trace do\n  50_000.times{|i|\n    a = [i.to_s]\n    b = {i.to_s => nil}\n    c = (i.to_s .. i.to_s)\n  }\nend\n\npp result\n```\n\nand you will get:\n\n```\n{[\"test.rb\", 8, :T_STRING]=>[50000, 15, 49165, 0, 16, 0],\n [\"test.rb\", 8, :T_ARRAY]=>[50000, 12, 49134, 0, 16, 0],\n [\"test.rb\", 9, :T_STRING]=>[100000, 27, 98263, 0, 16, 0],\n [\"test.rb\", 9, :T_HASH]=>[50000, 16, 49147, 0, 16, 8998848],\n [\"test.rb\", 10, :T_STRING]=>[100000, 36, 98322, 0, 16, 0],\n [\"test.rb\", 10, :T_STRUCT]=>[50000, 16, 49147, 0, 16, 0]}\n```\n\nInterestingly, you can not see array creations in a middle of block:\n\n```ruby\nrequire 'allocation_tracer'\nrequire 'pp'\n\nObjectSpace::AllocationTracer.setup(%i{path line type})\n\nresult = ObjectSpace::AllocationTracer.trace do\n  50_000.times{|i|\n    [i.to_s]\n    nil\n  }\nend\n\npp result\n```\n\nand it prints:\n\n```\n{[\"test.rb\", 8, :T_STRING]=>[25015, 5, 16299, 0, 2, 0]}\n```\n\nThere are only string creation. This is because unused array creation is\nommitted by optimizer.\n\nSimply you can require `allocation_tracer/trace' to start allocation\ntracer and output the aggregated information into stdout at the end of\nprogram.\n\n```ruby\nrequire 'allocation_tracer/trace'\n\n# Run your program here\n50_000.times{|i|\n  i.to_s\n  i.to_s\n  i.to_s\n}\n```\n\nand you will see:\n\n```\npath    line    count   old_count       total_age       min_age max_age total_memsize\n...rubygems/core_ext/kernel_require.rb 55     18       1       23      1       6       358\n...lib/allocation_tracer/lib/allocation_tracer/trace.rb       6       2      012      6       6       0\ntest.rb 0       1       0       0       0       0       0\ntest.rb 5       50000   4       41492   0       5       0\ntest.rb 6       50000   3       41490   0       5       0\ntest.rb 7       50000   7       41497   0       5       0\n```\n\n(tab separated columns)\n\n### Total Allocations / Free\n\nAllocation tracer collects the total number of allocations and frees during the\n`trace` block.  After the block finishes executing, you can examine the total\nnumber of allocations / frees per object type like this:\n\n```ruby\nrequire 'allocation_tracer'\n\nObjectSpace::AllocationTracer.trace do\n  1000.times {\n    [\"foo\", {}]\n  }\nend\np allocated: ObjectSpace::AllocationTracer.allocated_count_table\np freed: ObjectSpace::AllocationTracer.freed_count_table\n```\n\nThe output of the script will look like this:\n\n```\n{:allocated=>{:T_NONE=>0, :T_OBJECT=>0, :T_CLASS=>0, :T_MODULE=>0, :T_FLOAT=>0, :T_STRING=>1000, :T_REGEXP=>0, :T_ARRAY=>1000, :T_HASH=>1000, :T_STRUCT=>0, :T_BIGNUM=>0, :T_FILE=>0, :T_DATA=>0, :T_MATCH=>0, :T_COMPLEX=>0, :T_RATIONAL=>0, :unknown=>0, :T_NIL=>0, :T_TRUE=>0, :T_FALSE=>0, :T_SYMBOL=>0, :T_FIXNUM=>0, :T_UNDEF=>0, :T_NODE=>0, :T_ICLASS=>0, :T_ZOMBIE=>0}}\n{:freed=>{:T_NONE=>0, :T_OBJECT=>0, :T_CLASS=>0, :T_MODULE=>0, :T_FLOAT=>0, :T_STRING=>1871, :T_REGEXP=>41, :T_ARRAY=>226, :T_HASH=>7, :T_STRUCT=>41, :T_BIGNUM=>0, :T_FILE=>50, :T_DATA=>25, :T_MATCH=>47, :T_COMPLEX=>0, :T_RATIONAL=>0, :unknown=>0, :T_NIL=>0, :T_TRUE=>0, :T_FALSE=>0, :T_SYMBOL=>0, :T_FIXNUM=>0, :T_UNDEF=>0, :T_NODE=>932, :T_ICLASS=>0, :T_ZOMBIE=>0}}\n```\n\n### Lifetime table\n\nYou can collect lifetime statistics with\nObjectSpace::AllocationTracer.lifetime_table method.\n\n```ruby\nrequire 'pp'\nrequire 'allocation_tracer'\n\nObjectSpace::AllocationTracer.lifetime_table_setup true\nresult = ObjectSpace::AllocationTracer.trace do\n  100000.times{\n    Object.new\n    ''\n  }\nend\npp ObjectSpace::AllocationTracer.lifetime_table\n```\n\nwill show\n\n```\n{:T_OBJECT=>[3434, 96563, 0, 0, 1, 0, 0, 2],\n :T_STRING=>[3435, 96556, 2, 1, 1, 1, 1, 1, 2]}\n```\n\nThis output means that the age of 3434 T_OBJECT objects are 0, 96563\nobjects are 1 and 2 objects are 7. Also the age of 3435 T_STRING\nobjects are 0, 96556 objects are 1 and so on.\n\nNote that these numbers includes living objects and dead objects.  For\ndead objects, age means lifetime. For living objects, age means\ncurrent age.\n\n## Rack middleware\n\nYou can use AllocationTracer via rack middleware.\n\n```ruby\nrequire 'rack'\nrequire 'sinatra'\nrequire 'rack/allocation_tracer'\n\nuse Rack::AllocationTracerMiddleware\n\nget '/' do\n  'foo'\nend\n```\n\nWhen you access to `http://host/allocation_tracer/` then you can see a table of allocation tracer.\n\nYou can access the following pages.\n\n* http://host/allocation_tracer/\n* http://host/allocation_tracer/allocated_count_table\n* http://host/allocation_tracer/freed_count_table_page\n* http://host/allocation_tracer/lifetime_table\n\nThe following pages are demonstration Rails app on Heroku environment.\n\n* http://protected-journey-7206.herokuapp.com/allocation_tracer/\n* http://protected-journey-7206.herokuapp.com/allocation_tracer/allocated_count_table\n* http://protected-journey-7206.herokuapp.com/allocation_tracer/freed_count_table_page\n* http://protected-journey-7206.herokuapp.com/allocation_tracer/lifetime_table\n\nSource code of this demo app is https://github.com/ko1/tracer_demo_rails_app. You only need to modify like https://github.com/ko1/tracer_demo_rails_app/blob/master/config.ru to use it on Rails.\n\n## Development\n\nCompile the code:\n\n```\n$ rake compile\n```\n\nRun the tests:\n\n```\n$ rake spec\n```\n\n## Contributing\n\n1. Fork it ( http://github.com/ko1/allocation_tracer/fork )\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Commit your changes (`git commit -am 'Add some feature'`)\n4. Push to the branch (`git push origin my-new-feature`)\n5. Create new Pull Request\n\n## Author\n\nKoichi Sasada from Heroku, Inc.\n\n"
  },
  {
    "path": "Rakefile",
    "content": "require \"bundler/gem_tasks\"\nrequire \"rake/extensiontask\"\nrequire 'rspec/core/rake_task'\n\nspec = Gem::Specification.load('allocation_tracer.gemspec')\n\nRake::ExtensionTask.new(\"allocation_tracer\", spec){|ext|\n  ext.lib_dir = \"lib/allocation_tracer\"\n}\n\nRSpec::Core::RakeTask.new('spec' => 'compile')\n\ntask default: :spec\n\ntask :run => 'compile' do\n  ruby %q{-I ./lib test.rb}\nend\n"
  },
  {
    "path": "allocation_tracer.gemspec",
    "content": "# coding: utf-8\nlib = File.expand_path('../lib', __FILE__)\n$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)\nrequire 'allocation_tracer/version'\n\nGem::Specification.new do |spec|\n  spec.name          = \"allocation_tracer\"\n  spec.version       = ObjectSpace::AllocationTracer::VERSION\n  spec.authors       = [\"Koichi Sasada\"]\n  spec.email         = [\"ko1@atdot.net\"]\n  spec.summary       = %q{allocation_tracer gem adds ObjectSpace::AllocationTracer module.}\n  spec.description   = %q{allocation_tracer gem adds ObjectSpace::AllocationTracer module.}\n  spec.homepage      = \"https://github.com/ko1/allocation_tracer\"\n  spec.license       = \"MIT\"\n\n  spec.extensions    = %w[ext/allocation_tracer/extconf.rb]\n  spec.required_ruby_version = '>= 2.1.0'\n\n  spec.files         = `git ls-files -z`.split(\"\\x0\")\n  spec.executables   = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }\n  spec.test_files    = spec.files.grep(%r{^(test|spec|features)/})\n  spec.require_paths = [\"lib\"]\n\n  spec.add_development_dependency \"bundler\", \"~> 1.5\"\n  spec.add_development_dependency \"rake\"\n  spec.add_development_dependency \"rake-compiler\"\n  spec.add_development_dependency \"rspec\"\nend\n"
  },
  {
    "path": "ext/allocation_tracer/allocation_tracer.c",
    "content": "/*\r\n * allocation tracer: adds GC::Tracer::start_allocation_tracing\r\n *\r\n * By Koichi Sasada\r\n * created at Thu Apr 17 03:50:38 2014.\r\n */\r\n\r\n#include \"ruby/ruby.h\"\r\n#include \"ruby/debug.h\"\r\n#include <assert.h>\r\n\r\nsize_t rb_obj_memsize_of(VALUE obj); /* in gc.c */\r\n\r\nstatic VALUE rb_mAllocationTracer;\r\n\r\nstruct traceobj_arg {\r\n    int running;\r\n    int keys, vals;\r\n    st_table *object_table;     /* obj (VALUE)      -> allocation_info */\r\n    st_table *str_table;        /* cstr             -> refcount */\r\n\r\n    st_table *aggregate_table;  /* user defined key -> [count, total_age, max_age, min_age] */\r\n    struct allocation_info *freed_allocation_info;\r\n\r\n    /* */\r\n    size_t **lifetime_table;\r\n    size_t allocated_count_table[T_MASK];\r\n    size_t freed_count_table[T_MASK];\r\n};\r\n\r\nstruct allocation_info {\r\n    struct allocation_info *next;\r\n\r\n    /* all of information don't need marking. */\r\n    int living;\r\n    VALUE flags;\r\n    VALUE klass;\r\n    size_t generation;\r\n    size_t memsize;\r\n\r\n    /* allocator info */\r\n    const char *path;\r\n    unsigned long line;\r\n};\r\n\r\n#define MAX_KEY_DATA 4\r\n\r\n#define KEY_PATH    (1<<1)\r\n#define KEY_LINE    (1<<2)\r\n#define KEY_TYPE    (1<<3)\r\n#define KEY_CLASS   (1<<4)\r\n\r\n#define MAX_VAL_DATA 6\r\n\r\n#define VAL_COUNT     (1<<1)\r\n#define VAL_OLDCOUNT  (1<<2)\r\n#define VAL_TOTAL_AGE (1<<3)\r\n#define VAL_MIN_AGE   (1<<4)\r\n#define VAL_MAX_AGE   (1<<5)\r\n#define VAL_MEMSIZE   (1<<6)\r\n\r\nstatic char *\r\nkeep_unique_str(st_table *tbl, const char *str)\r\n{\r\n    st_data_t n;\r\n\r\n    if (str && st_lookup(tbl, (st_data_t)str, &n)) {\r\n\tchar *result;\r\n\r\n\tst_insert(tbl, (st_data_t)str, n+1);\r\n\tst_get_key(tbl, (st_data_t)str, (st_data_t *)&result);\r\n\r\n\treturn result;\r\n    }\r\n    else {\r\n\treturn NULL;\r\n    }\r\n}\r\n\r\nstatic const char *\r\nmake_unique_str(st_table *tbl, const char *str, long len)\r\n{\r\n    if (!str) {\r\n\treturn NULL;\r\n    }\r\n    else {\r\n\tchar *result;\r\n\r\n\tif ((result = keep_unique_str(tbl, str)) == NULL) {\r\n\t    result = (char *)ruby_xmalloc(len+1);\r\n\t    strncpy(result, str, len);\r\n\t    result[len] = 0;\r\n\t    st_add_direct(tbl, (st_data_t)result, 1);\r\n\t}\r\n\treturn result;\r\n    }\r\n}\r\n\r\nstatic void\r\ndelete_unique_str(st_table *tbl, const char *str)\r\n{\r\n    if (str) {\r\n\tst_data_t n;\r\n\r\n\tif (st_lookup(tbl, (st_data_t)str, &n) == 0) rb_bug(\"delete_unique_str: unreachable\");\r\n\r\n\tif (n == 1) {\r\n\t    st_delete(tbl, (st_data_t *)&str, NULL);\r\n\t    ruby_xfree((char *)str);\r\n\t}\r\n\telse {\r\n\t    st_insert(tbl, (st_data_t)str, n-1);\r\n\t}\r\n    }\r\n}\r\n\r\nstruct memcmp_key_data {\r\n    int n;\r\n    st_data_t data[MAX_KEY_DATA];\r\n};\r\n\r\nstatic int\r\nmemcmp_hash_compare(st_data_t a, st_data_t b)\r\n{\r\n    struct memcmp_key_data *k1 = (struct memcmp_key_data *)a;\r\n    struct memcmp_key_data *k2 = (struct memcmp_key_data *)b;\r\n    return memcmp(&k1->data[0], &k2->data[0], k1->n * sizeof(st_data_t));\r\n}\r\n\r\nstatic st_index_t\r\nmemcmp_hash_hash(st_data_t a)\r\n{\r\n    struct memcmp_key_data *k = (struct memcmp_key_data *)a;\r\n    return rb_memhash(k->data, sizeof(st_data_t) * k->n);\r\n}\r\n\r\nstatic const struct st_hash_type memcmp_hash_type = {\r\n    memcmp_hash_compare, memcmp_hash_hash\r\n};\r\n\r\nstatic struct traceobj_arg *tmp_trace_arg; /* TODO: Do not use global variables */\r\n\r\nstatic struct traceobj_arg *\r\nget_traceobj_arg(void)\r\n{\r\n    if (tmp_trace_arg == 0) {\r\n\ttmp_trace_arg = ALLOC_N(struct traceobj_arg, 1);\r\n\tMEMZERO(tmp_trace_arg, struct traceobj_arg, 1);\r\n\ttmp_trace_arg->running = 0;\r\n\ttmp_trace_arg->keys = 0;\r\n\ttmp_trace_arg->vals = VAL_COUNT | VAL_OLDCOUNT | VAL_TOTAL_AGE | VAL_MAX_AGE | VAL_MIN_AGE | VAL_MEMSIZE;\r\n\ttmp_trace_arg->aggregate_table = st_init_table(&memcmp_hash_type);\r\n\ttmp_trace_arg->object_table = st_init_numtable();\r\n\ttmp_trace_arg->str_table = st_init_strtable();\r\n\ttmp_trace_arg->freed_allocation_info = NULL;\r\n\ttmp_trace_arg->lifetime_table = NULL;\r\n    }\r\n    return tmp_trace_arg;\r\n}\r\n\r\nstatic int\r\nfree_keys_i(st_data_t key, st_data_t value, void *data)\r\n{\r\n    ruby_xfree((void *)key);\r\n    return ST_CONTINUE;\r\n}\r\n\r\nstatic int\r\nfree_values_i(st_data_t key, st_data_t value, void *data)\r\n{\r\n    ruby_xfree((void *)value);\r\n    return ST_CONTINUE;\r\n}\r\n\r\nstatic int\r\nfree_key_values_i(st_data_t key, st_data_t value, void *data)\r\n{\r\n    ruby_xfree((void *)key);\r\n    ruby_xfree((void *)value);\r\n    return ST_CONTINUE;\r\n}\r\n\r\nstatic void\r\ndelete_lifetime_table(struct traceobj_arg *arg)\r\n{\r\n    int i;\r\n    if (arg->lifetime_table) {\r\n\tfor (i=0; i<T_MASK; i++) {\r\n\t    free(arg->lifetime_table[i]);\r\n\t}\r\n\tfree(arg->lifetime_table);\r\n\targ->lifetime_table = NULL;\r\n    }\r\n}\r\n\r\nstatic void\r\nclear_traceobj_arg(void)\r\n{\r\n    struct traceobj_arg * arg = get_traceobj_arg();\r\n\r\n    st_foreach(arg->aggregate_table, free_key_values_i, 0);\r\n    st_clear(arg->aggregate_table);\r\n    st_foreach(arg->object_table, free_values_i, 0);\r\n    st_clear(arg->object_table);\r\n    st_foreach(arg->str_table, free_keys_i, 0);\r\n    st_clear(arg->str_table);\r\n    arg->freed_allocation_info = NULL;\r\n    delete_lifetime_table(arg);\r\n}\r\n\r\nstatic struct allocation_info *\r\ncreate_allocation_info(void)\r\n{\r\n    return (struct allocation_info *)ruby_xmalloc(sizeof(struct allocation_info));\r\n}\r\n\r\nstatic void\r\nfree_allocation_info(struct traceobj_arg *arg, struct allocation_info *info)\r\n{\r\n    delete_unique_str(arg->str_table, info->path);\r\n    ruby_xfree(info);\r\n}\r\n\r\nstatic void\r\nnewobj_i(VALUE tpval, void *data)\r\n{\r\n    struct traceobj_arg *arg = (struct traceobj_arg *)data;\r\n    struct allocation_info *info;\r\n    rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval);\r\n    VALUE obj = rb_tracearg_object(tparg);\r\n    VALUE path = rb_tracearg_path(tparg);\r\n    VALUE line = rb_tracearg_lineno(tparg);\r\n    VALUE klass = Qnil;\r\n    switch(BUILTIN_TYPE(obj)) {\r\n        case T_NODE:\r\n        case T_IMEMO:\r\n            break;\r\n        default:\r\n            klass = RBASIC_CLASS(obj);\r\n    }\r\n    const char *path_cstr = RTEST(path) ? make_unique_str(arg->str_table, RSTRING_PTR(path), RSTRING_LEN(path)) : NULL;\r\n\r\n    if (st_lookup(arg->object_table, (st_data_t)obj, (st_data_t *)&info)) {\r\n\tif (info->living) {\r\n\t    /* do nothing. there is possibility to keep living if FREEOBJ events while suppressing tracing */\r\n\t}\r\n\t/* reuse info */\r\n\tdelete_unique_str(arg->str_table, info->path);\r\n    }\r\n    else {\r\n\tinfo = create_allocation_info();\r\n    }\r\n\r\n    info->next = NULL;\r\n    info->flags = RBASIC(obj)->flags;\r\n    info->living = 1;\r\n    info->memsize = 0;\r\n    info->klass = (RTEST(klass) && !RB_TYPE_P(obj, T_NODE)) ? rb_class_real(klass) : Qnil;\r\n    info->generation = rb_gc_count();\r\n\r\n    info->path = path_cstr;\r\n    info->line = NUM2INT(line);\r\n\r\n    st_insert(arg->object_table, (st_data_t)obj, (st_data_t)info);\r\n\r\n    arg->allocated_count_table[BUILTIN_TYPE(obj)]++;\r\n}\r\n\r\n/* file, line, type, klass */\r\n#define MAX_KEY_SIZE 4\r\n\r\nstatic void\r\naggregate_each_info(struct traceobj_arg *arg, struct allocation_info *info, size_t gc_count)\r\n{\r\n    st_data_t key, val;\r\n    struct memcmp_key_data key_data;\r\n    size_t *val_buff;\r\n    size_t age = (int)(gc_count - info->generation);\r\n    int i = 0;\r\n\r\n    if (arg->keys & KEY_PATH) {\r\n\tkey_data.data[i++] = (st_data_t)info->path;\r\n    }\r\n    if (arg->keys & KEY_LINE) {\r\n\tkey_data.data[i++] = (st_data_t)info->line;\r\n    }\r\n    if (arg->keys & KEY_TYPE) {\r\n\tkey_data.data[i++] = (st_data_t)(info->flags & T_MASK);\r\n    }\r\n    if (arg->keys & KEY_CLASS) {\r\n\tkey_data.data[i++] = info->klass;\r\n    }\r\n    key_data.n = i;\r\n    key = (st_data_t)&key_data;\r\n\r\n    if (st_lookup(arg->aggregate_table, key, &val) == 0) {\r\n\tstruct memcmp_key_data *key_buff = ruby_xmalloc(sizeof(struct memcmp_key_data));\r\n\tkey_buff->n = key_data.n;\r\n\r\n\tfor (i=0; i<key_data.n; i++) {\r\n\t    key_buff->data[i] = key_data.data[i];\r\n\t}\r\n\tkey = (st_data_t)key_buff;\r\n\r\n\t/* count, old count, total age, max age, min age */\r\n\tval_buff = ALLOC_N(size_t, 6);\r\n\tval_buff[0] = val_buff[1] = val_buff[2] = 0;\r\n\tval_buff[3] = val_buff[4] = age;\r\n\tval_buff[5] = 0;\r\n\r\n\tif (arg->keys & KEY_PATH) keep_unique_str(arg->str_table, info->path);\r\n\r\n\tst_insert(arg->aggregate_table, (st_data_t)key_buff, (st_data_t)val_buff);\r\n    }\r\n    else {\r\n\tval_buff = (size_t *)val;\r\n    }\r\n\r\n    val_buff[0] += 1;\r\n#ifdef FL_PROMOTED\r\n    if (info->flags & FL_PROMOTED) val_buff[1] += 1;\r\n#elif defined(FL_PROMOTED0) && defined(FL_PROMOTED1)\r\n    if (info->flags & FL_PROMOTED0 &&\r\n\tinfo->flags & FL_PROMOTED1) val_buff[1] += 1;\r\n#endif\r\n    val_buff[2] += age;\r\n    if (val_buff[3] > age) val_buff[3] = age; /* min */\r\n    if (val_buff[4] < age) val_buff[4] = age; /* max */\r\n    val_buff[5] += info->memsize;\r\n}\r\n\r\nstatic void\r\naggregate_freed_info(void *data)\r\n{\r\n    size_t gc_count = rb_gc_count();\r\n    struct traceobj_arg *arg = (struct traceobj_arg *)data;\r\n    struct allocation_info *info = arg->freed_allocation_info;\r\n\r\n    arg->freed_allocation_info = NULL;\r\n\r\n    if (arg->running) {\r\n\twhile (info) {\r\n\t    struct allocation_info *next_info = info->next;\r\n\t    aggregate_each_info(arg, info, gc_count);\r\n\t    free_allocation_info(arg, info);\r\n\t    info = next_info;\r\n\t}\r\n    }\r\n}\r\n\r\nstatic void\r\nmove_to_freed_list(struct traceobj_arg *arg, VALUE obj, struct allocation_info *info)\r\n{\r\n    if (arg->freed_allocation_info == NULL) {\r\n\trb_postponed_job_register_one(0, aggregate_freed_info, arg);\r\n    }\r\n\r\n    info->next = arg->freed_allocation_info;\r\n    arg->freed_allocation_info = info;\r\n    st_delete(arg->object_table, (st_data_t *)&obj, (st_data_t *)&info);\r\n}\r\n\r\nstatic void\r\nadd_lifetime_table(size_t **lines, int type, struct allocation_info *info)\r\n{\r\n    size_t age = rb_gc_count() - info->generation;\r\n    size_t *line = lines[type];\r\n    size_t len, i;\r\n\r\n    if (line == NULL) {\r\n\tlen = age + 1;\r\n\tline = lines[type] = calloc(1 + len, sizeof(size_t));\r\n\tassert(line != NULL);\r\n\tline[0] = len;\r\n    }\r\n    else {\r\n\tlen = line[0];\r\n\r\n\tif (len < age + 1) {\r\n\t    size_t old_len = len;\r\n\t    len = age + 1;\r\n\t    line = lines[type] = realloc(line, sizeof(size_t) * (1 + len));\r\n\r\n\t    assert(line != NULL);\r\n\r\n\t    for (i=old_len; i<len; i++) {\r\n\t\tline[i+1] = 0;\r\n\t    }\r\n\r\n\t    line[0] = len;\r\n\t}\r\n    }\r\n\r\n    line[1 + age]++;\r\n}\r\n\r\nstatic void\r\nfreeobj_i(VALUE tpval, void *data)\r\n{\r\n    struct traceobj_arg *arg = (struct traceobj_arg *)data;\r\n    rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval);\r\n    VALUE obj = rb_tracearg_object(tparg);\r\n    struct allocation_info *info;\r\n\r\n    if (st_lookup(arg->object_table, (st_data_t)obj, (st_data_t *)&info)) {\r\n\r\n\tinfo->flags = RBASIC(obj)->flags;\r\n\tinfo->memsize = rb_obj_memsize_of(obj);\r\n\r\n\tmove_to_freed_list(arg, obj, info);\r\n\r\n\tif (arg->lifetime_table) {\r\n\t    add_lifetime_table(arg->lifetime_table, BUILTIN_TYPE(obj), info);\r\n\t}\r\n    }\r\n\r\n    arg->freed_count_table[BUILTIN_TYPE(obj)]++;\r\n}\r\n\r\nstatic void\r\ncheck_tracer_running(void)\r\n{\r\n    struct traceobj_arg * arg = get_traceobj_arg();\r\n\r\n    if (!arg->running) {\r\n\trb_raise(rb_eRuntimeError, \"not started yet\");\r\n    }\r\n}\r\n\r\nstatic void\r\nenable_newobj_hook(void)\r\n{\r\n    VALUE newobj_hook;\r\n\r\n    check_tracer_running();\r\n\r\n    if (!rb_ivar_defined(rb_mAllocationTracer, rb_intern(\"newobj_hook\"))) {\r\n\trb_raise(rb_eRuntimeError, \"not started.\");\r\n    }\r\n    newobj_hook = rb_ivar_get(rb_mAllocationTracer, rb_intern(\"newobj_hook\"));\r\n    if (rb_tracepoint_enabled_p(newobj_hook)) {\r\n\trb_raise(rb_eRuntimeError, \"newobj hooks is already enabled.\");\r\n    }\r\n\r\n    rb_tracepoint_enable(newobj_hook);\r\n}\r\n\r\nstatic void\r\ndisable_newobj_hook(void)\r\n{\r\n    VALUE newobj_hook;\r\n\r\n    check_tracer_running();\r\n\r\n    if ((!rb_ivar_defined(rb_mAllocationTracer, rb_intern(\"newobj_hook\"))) || ((newobj_hook = rb_ivar_get(rb_mAllocationTracer, rb_intern(\"newobj_hook\"))) == Qnil)) {\r\n\trb_raise(rb_eRuntimeError, \"not started.\");\r\n    }\r\n    if (rb_tracepoint_enabled_p(newobj_hook) == Qfalse) {\r\n\trb_raise(rb_eRuntimeError, \"newobj hooks is already disabled.\");\r\n    }\r\n\r\n    rb_tracepoint_disable(newobj_hook);\r\n}\r\n\r\nstatic void\r\nstart_alloc_hooks(VALUE mod)\r\n{\r\n    VALUE newobj_hook, freeobj_hook;\r\n    struct traceobj_arg *arg = get_traceobj_arg();\r\n\r\n    if (!rb_ivar_defined(rb_mAllocationTracer, rb_intern(\"newobj_hook\"))) {\r\n\trb_ivar_set(rb_mAllocationTracer, rb_intern(\"newobj_hook\"), newobj_hook = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_NEWOBJ, newobj_i, arg));\r\n\trb_ivar_set(rb_mAllocationTracer, rb_intern(\"freeobj_hook\"), freeobj_hook = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_FREEOBJ, freeobj_i, arg));\r\n    }\r\n    else {\r\n\tnewobj_hook = rb_ivar_get(rb_mAllocationTracer, rb_intern(\"newobj_hook\"));\r\n\tfreeobj_hook = rb_ivar_get(rb_mAllocationTracer, rb_intern(\"freeobj_hook\"));\r\n    }\r\n\r\n    rb_tracepoint_enable(newobj_hook);\r\n    rb_tracepoint_enable(freeobj_hook);\r\n}\r\n\r\nstatic VALUE\r\nstop_alloc_hooks(VALUE self)\r\n{\r\n    struct traceobj_arg * arg = get_traceobj_arg();\r\n    check_tracer_running();\r\n\r\n    {\r\n\tVALUE newobj_hook = rb_ivar_get(rb_mAllocationTracer, rb_intern(\"newobj_hook\"));\r\n\tVALUE freeobj_hook = rb_ivar_get(rb_mAllocationTracer, rb_intern(\"freeobj_hook\"));\r\n\trb_tracepoint_disable(newobj_hook);\r\n\trb_tracepoint_disable(freeobj_hook);\r\n\r\n\tclear_traceobj_arg();\r\n\r\n\targ->running = 0;\r\n    }\r\n\r\n    return Qnil;\r\n}\r\n\r\nstatic VALUE\r\ntype_sym(int type)\r\n{\r\n    static VALUE syms[T_MASK] = {0};\r\n\r\n    if (syms[0] == 0) {\r\n\tint i;\r\n\tfor (i=0; i<T_MASK; i++) {\r\n\t    switch (i) {\r\n#define TYPE_NAME(t) case (t): syms[i] = ID2SYM(rb_intern(#t)); break;\r\n\t\tTYPE_NAME(T_NONE);\r\n\t\tTYPE_NAME(T_OBJECT);\r\n\t\tTYPE_NAME(T_CLASS);\r\n\t\tTYPE_NAME(T_MODULE);\r\n\t\tTYPE_NAME(T_FLOAT);\r\n\t\tTYPE_NAME(T_STRING);\r\n\t\tTYPE_NAME(T_REGEXP);\r\n\t\tTYPE_NAME(T_ARRAY);\r\n\t\tTYPE_NAME(T_HASH);\r\n\t\tTYPE_NAME(T_STRUCT);\r\n\t\tTYPE_NAME(T_BIGNUM);\r\n\t\tTYPE_NAME(T_FILE);\r\n\t\tTYPE_NAME(T_MATCH);\r\n\t\tTYPE_NAME(T_COMPLEX);\r\n\t\tTYPE_NAME(T_RATIONAL);\r\n\t\tTYPE_NAME(T_NIL);\r\n\t\tTYPE_NAME(T_TRUE);\r\n\t\tTYPE_NAME(T_FALSE);\r\n\t\tTYPE_NAME(T_SYMBOL);\r\n\t\tTYPE_NAME(T_FIXNUM);\r\n\t\tTYPE_NAME(T_UNDEF);\r\n#ifdef T_IMEMO /* introduced from Rub 2.3 */\r\n\t\tTYPE_NAME(T_IMEMO);\r\n#endif\r\n\t\tTYPE_NAME(T_NODE);\r\n\t\tTYPE_NAME(T_ICLASS);\r\n\t\tTYPE_NAME(T_ZOMBIE);\r\n\t\tTYPE_NAME(T_DATA);\r\n\t      default:\r\n\t\tsyms[i] = ID2SYM(rb_intern(\"unknown\"));\r\n\t\tbreak;\r\n#undef TYPE_NAME\r\n\t    }\r\n\t}\r\n    }\r\n\r\n    return syms[type];\r\n}\r\n\r\nstruct arg_and_result {\r\n    int update;\r\n    struct traceobj_arg *arg;\r\n    VALUE result;\r\n};\r\n\r\nstatic int\r\naggregate_result_i(st_data_t key, st_data_t val, void *data)\r\n{\r\n    struct arg_and_result *aar = (struct arg_and_result *)data;\r\n    struct traceobj_arg *arg = aar->arg;\r\n    VALUE result = aar->result;\r\n    size_t *val_buff = (size_t *)val;\r\n    struct memcmp_key_data *key_buff = (struct memcmp_key_data *)key;\r\n    VALUE v, oldv, k = rb_ary_new();\r\n    int i = 0;\r\n\r\n    i = 0;\r\n    if (arg->keys & KEY_PATH) {\r\n\tconst char *path = (const char *)key_buff->data[i++];\r\n\tif (path) {\r\n\t    rb_ary_push(k, rb_str_new2(path));\r\n\t}\r\n\telse {\r\n\t    rb_ary_push(k, Qnil);\r\n\t}\r\n    }\r\n    if (arg->keys & KEY_LINE) {\r\n\trb_ary_push(k, INT2FIX((int)key_buff->data[i++]));\r\n    }\r\n    if (arg->keys & KEY_TYPE) {\r\n\tint sym_index = key_buff->data[i++];\r\n\trb_ary_push(k, type_sym(sym_index));\r\n    }\r\n    if (arg->keys & KEY_CLASS) {\r\n\tVALUE klass = key_buff->data[i++];\r\n\tif (RTEST(klass) && BUILTIN_TYPE(klass) == T_CLASS) {\r\n\t    klass = rb_class_real(klass);\r\n\t    rb_ary_push(k, klass);\r\n\t    /* TODO: actually, it is dangerous code because klass can be sweeped */\r\n\t    /*       So that class specifier is hidden feature                   */\r\n\t}\r\n\telse {\r\n\t    rb_ary_push(k, Qnil);\r\n\t}\r\n    }\r\n\r\n#define MIN(a, b) ((a) < (b) ? (a) : (b))\r\n#define MAX(a, b) ((a) > (b) ? (a) : (b))\r\n\r\n    if (aar->update && (oldv = rb_hash_aref(result, k)) != Qnil) {\r\n\tv = rb_ary_new3(6,\r\n\t\t\tINT2FIX(val_buff[0] + (size_t)FIX2INT(RARRAY_AREF(oldv, 0))), /* count */\r\n\t\t\tINT2FIX(val_buff[1] + (size_t)FIX2INT(RARRAY_AREF(oldv, 1))), /* old count */\r\n\t\t\tINT2FIX(val_buff[2] + (size_t)FIX2INT(RARRAY_AREF(oldv, 2))), /* total_age */\r\n\t\t\tINT2FIX(MIN(val_buff[3], (size_t)FIX2INT(RARRAY_AREF(oldv, 3)))), /* min age */\r\n\t\t\tINT2FIX(MAX(val_buff[4], (size_t)FIX2INT(RARRAY_AREF(oldv, 4)))), /* max age */\r\n\t\t\tINT2FIX(val_buff[5] + (size_t)FIX2INT(RARRAY_AREF(oldv, 5)))); /* memsize_of */\r\n    }\r\n    else {\r\n\tv = rb_ary_new3(6,\r\n\t\t\tINT2FIX(val_buff[0]), INT2FIX(val_buff[1]),\r\n\t\t\tINT2FIX(val_buff[2]), INT2FIX(val_buff[3]),\r\n\t\t\tINT2FIX(val_buff[4]), INT2FIX(val_buff[5]));\r\n    }\r\n\r\n    rb_hash_aset(result, k, v);\r\n\r\n    return ST_CONTINUE;\r\n}\r\n\r\nstatic int\r\naggregate_live_object_i(st_data_t key, st_data_t val, void *data)\r\n{\r\n    VALUE obj = (VALUE)key;\r\n    struct allocation_info *info = (struct allocation_info *)val;\r\n    size_t gc_count = rb_gc_count();\r\n    struct traceobj_arg *arg = (struct traceobj_arg *)data;\r\n\r\n    if (BUILTIN_TYPE(obj) == (info->flags & T_MASK)) {\r\n\tVALUE klass = RBASIC_CLASS(obj);\r\n\tinfo->flags = RBASIC(obj)->flags;\r\n\tinfo->klass = (RTEST(klass) && !RB_TYPE_P(obj, T_NODE)) ? rb_class_real(klass) : Qnil;\r\n    }\r\n\r\n    aggregate_each_info(arg, info, gc_count);\r\n\r\n    return ST_CONTINUE;\r\n}\r\n\r\nstatic int\r\nlifetime_table_for_live_objects_i(st_data_t key, st_data_t val, st_data_t data)\r\n{\r\n    struct allocation_info *info = (struct allocation_info *)val;\r\n    VALUE h = (VALUE)data;\r\n    int type = info->flags & T_MASK;\r\n    VALUE sym = type_sym(type);\r\n    size_t age = rb_gc_count() - info->generation;\r\n    VALUE line;\r\n    size_t count, i;\r\n\r\n    if ((line = rb_hash_aref(h, sym)) == Qnil) {\r\n\tline = rb_ary_new();\r\n\trb_hash_aset(h, sym, line);\r\n    }\r\n\r\n    for (i=RARRAY_LEN(line); i<age+1; i++) {\r\n\trb_ary_push(line, INT2FIX(0));\r\n    }\r\n\r\n    count = NUM2SIZET(RARRAY_AREF(line, age));\r\n    RARRAY_ASET(line, age, SIZET2NUM(count + 1));\r\n\r\n    return ST_CONTINUE;\r\n}\r\n\r\nstatic VALUE\r\naggregate_result(struct traceobj_arg *arg)\r\n{\r\n    struct arg_and_result aar;\r\n    aar.result = rb_hash_new();\r\n    aar.arg = arg;\r\n\r\n    while (arg->freed_allocation_info) {\r\n\taggregate_freed_info(arg);\r\n    }\r\n\r\n    /* collect from recent-freed objects */\r\n    aar.update = 0;\r\n    st_foreach(arg->aggregate_table, aggregate_result_i, (st_data_t)&aar);\r\n\r\n    {\r\n\tst_table *dead_object_aggregate_table = arg->aggregate_table;\r\n\r\n\t/* make live object aggregate table */\r\n\targ->aggregate_table = st_init_table(&memcmp_hash_type);\r\n\tst_foreach(arg->object_table, aggregate_live_object_i, (st_data_t)arg);\r\n\r\n\t/* aggregate table -> Ruby hash */\r\n\taar.update = 1;\r\n\tst_foreach(arg->aggregate_table, aggregate_result_i, (st_data_t)&aar);\r\n\r\n\t/* remove live object aggregate table */\r\n\tst_foreach(arg->aggregate_table, free_key_values_i, 0);\r\n\tst_free_table(arg->aggregate_table);\r\n\r\n\targ->aggregate_table = dead_object_aggregate_table;\r\n    }\r\n\r\n    /* lifetime table */\r\n    if (arg->lifetime_table) {\r\n\tVALUE h = rb_hash_new();\r\n\tint i;\r\n\r\n\trb_ivar_set(rb_mAllocationTracer, rb_intern(\"lifetime_table\"), h);\r\n\r\n\tfor (i=0; i<T_MASK; i++) {\r\n\t    size_t *line = arg->lifetime_table[i];\r\n\r\n\t    if (line) {\r\n\t\tsize_t len = line[0], j;\r\n\t\tVALUE ary = rb_ary_new();\r\n\t\tVALUE sym = type_sym(i);\r\n\r\n\t\trb_hash_aset(h, sym, ary);\r\n\r\n\t\tfor (j=0; j<len; j++) {\r\n\t\t    rb_ary_push(ary, SIZET2NUM(line[j+1]));\r\n\t\t}\r\n\t    }\r\n\t}\r\n\r\n\tst_foreach(arg->object_table, lifetime_table_for_live_objects_i, (st_data_t)h);\r\n    }\r\n\r\n    return aar.result;\r\n}\r\n\r\n/*\r\n *\r\n *  call-seq:\r\n *     ObjectSpace::AllocationTracer.result  -> hash\r\n *\r\n *  Returns the current allocated results\r\n *\r\n *  If you need to know the results of allocation tracing\r\n *  without pausing or stopping tracing you can use this method.\r\n *\r\n *  Example:\r\n *\r\n *    require 'allocation_tracer'\r\n *\r\n *    ObjectSpace::AllocationTracer.trace do\r\n *      3.times do |i|\r\n *        a = \"#{i}\"\r\n *        puts ObjectSpace::AllocationTracer.result\r\n *      end\r\n *    end\r\n *\r\n *    # => {[\"scratch.rb\", 5]=>[2, 0, 0, 0, 0, 0]}\r\n *    # => {[\"scratch.rb\", 5]=>[4, 0, 0, 0, 0, 0], [\"scratch.rb\", 6]=>[16, 0, 0, 0, 0, 0]}\r\n *    # => {[\"scratch.rb\", 5]=>[6, 0, 0, 0, 0, 0], [\"scratch.rb\", 6]=>[38, 0, 0, 0, 0, 0]}\r\n *\r\n */\r\nstatic VALUE\r\nallocation_tracer_result(VALUE self)\r\n{\r\n    VALUE result;\r\n    struct traceobj_arg *arg = get_traceobj_arg();\r\n\r\n    disable_newobj_hook();\r\n    result = aggregate_result(arg);\r\n    enable_newobj_hook();\r\n    return result;\r\n}\r\n\r\n/*\r\n *\r\n *  call-seq:\r\n *     ObjectSpace::AllocationTracer.clear  -> NilClass\r\n *\r\n *  Clears the current allocated results\r\n *\r\n *  If you need to clear the results of allocation tracing\r\n *  without stopping tracing you can use this method.\r\n *\r\n */\r\nstatic VALUE\r\nallocation_tracer_clear(VALUE self)\r\n{\r\n    clear_traceobj_arg();\r\n    return Qnil;\r\n}\r\n\r\n/*! Used in allocation_tracer_trace\r\n*   to ensure that a result is returned.\r\n*/\r\nstatic VALUE\r\nallocation_tracer_trace_i(VALUE self)\r\n{\r\n    rb_yield(Qnil);\r\n    return allocation_tracer_result(self);\r\n}\r\n\r\n/*\r\n *\r\n *  call-seq:\r\n *     ObjectSpace::AllocationTracer.trace { |a, b| block } -> array\r\n *     ObjectSpace::AllocationTracer.start                  -> NilClass\r\n *\r\n *  Traces object allocations inside of the block\r\n *\r\n *  Objects created inside of the block will be tracked by the tracer.\r\n *  If the method is called without a block, the tracer will start\r\n *  and continue until ObjectSpace::AllocationTracer.stop is called.\r\n *\r\n *  Output can be customized with ObjectSpace::AllocationTracer.setup.\r\n *\r\n *  Example:\r\n *\r\n *     pp ObjectSpace::AllocationTracer.trace{\r\n *       50_000.times{|i|\r\n *         i.to_s\r\n *         i.to_s\r\n *         i.to_s\r\n *       }\r\n *     }\r\n *\r\n *     # => {[\"test.rb\", 6]=>[50000, 0, 47440, 0, 1, 0],\r\n *           [\"test.rb\", 7]=>[50000, 4, 47452, 0, 6, 0],\r\n *           [\"test.rb\", 8]=>[50000, 7, 47456, 0, 6, 0]}\r\n *\r\n */\r\nstatic VALUE\r\nallocation_tracer_trace(VALUE self)\r\n{\r\n    struct traceobj_arg * arg = get_traceobj_arg();\r\n\r\n    if (arg->running) {\r\n\trb_raise(rb_eRuntimeError, \"can't run recursivly\");\r\n    }\r\n    else {\r\n\targ->running = 1;\r\n\tif (arg->keys == 0) arg->keys = KEY_PATH | KEY_LINE;\r\n\tstart_alloc_hooks(rb_mAllocationTracer);\r\n\r\n\tif (rb_block_given_p()) {\r\n\t    return rb_ensure(allocation_tracer_trace_i, self, stop_alloc_hooks, Qnil);\r\n\t}\r\n    }\r\n\r\n    return Qnil;\r\n}\r\n\r\n/*\r\n *\r\n *  call-seq:\r\n *     ObjectSpace::AllocationTracer.stop                  -> array\r\n *\r\n *  Stops allocation tracing if currently running\r\n *\r\n *  When allocation tracing is started via ObjectSpace::AllocationTracer.start\r\n *  it will continue until this method is called.\r\n *\r\n *  Example:\r\n *    pp ObjectSpace::AllocationTracer.stop\r\n *\r\n *    # => {[\"test.rb\", 6]=>[50000, 0, 47440, 0, 1, 0],\r\n *          [\"test.rb\", 7]=>[50000, 4, 47452, 0, 6, 0],\r\n *          [\"test.rb\", 8]=>[50000, 7, 47456, 0, 6, 0]}\r\n *\r\n */\r\nstatic VALUE\r\nallocation_tracer_stop(VALUE self)\r\n{\r\n    VALUE result;\r\n\r\n    disable_newobj_hook();\r\n    result = aggregate_result(get_traceobj_arg());\r\n    stop_alloc_hooks(self);\r\n    return result;\r\n}\r\n\r\n/*\r\n *\r\n *  call-seq:\r\n *     ObjectSpace::AllocationTracer.pause                  -> NilClass\r\n *\r\n *  Pauses allocation tracing\r\n *\r\n *  Use in conjunction with ObjectSpace::AllocationTracer.start and\r\n *  ObjectSpace::AllocationTracer.stop.\r\n *\r\n */\r\nstatic VALUE\r\nallocation_tracer_pause(VALUE self)\r\n{\r\n    disable_newobj_hook();\r\n    return Qnil;\r\n}\r\n\r\n/*\r\n *\r\n *  call-seq:\r\n *     ObjectSpace::AllocationTracer.resume                  -> NilClass\r\n *\r\n *  Resumes allocation tracing if previously paused\r\n *\r\n *  See ObjectSpace::AllocationTracer.pause to pause allocation tracing.\r\n *\r\n */\r\nstatic VALUE\r\nallocation_tracer_resume(VALUE self)\r\n{\r\n    enable_newobj_hook();\r\n    return Qnil;\r\n}\r\n\r\n/*\r\n *\r\n *  call-seq:\r\n *     ObjectSpace::AllocationTracer.setup([symbol])       -> NilClass\r\n *\r\n *  Change the format that results will be returned.\r\n *\r\n *  Takes an array of symbols containing the order you would like the output\r\n *  to be returned. Valid options:\r\n *\r\n *    - :path\r\n *    - :line\r\n *    - :type\r\n *    - :class\r\n *\r\n *  Example:\r\n *\r\n *     ObjectSpace::AllocationTracer.setup(%i{path line type})\r\n *\r\n *     result = ObjectSpace::AllocationTracer.trace do\r\n *       50_000.times{|i|\r\n *         a = [i.to_s]\r\n *         b = {i.to_s => nil}\r\n *         c = (i.to_s .. i.to_s)\r\n *       }\r\n *     end\r\n *\r\n *     pp result\r\n *\r\n *     # => {[\"test.rb\", 8, :T_STRING]=>[50000, 15, 49165, 0, 16, 0],\r\n *           [\"test.rb\", 8, :T_ARRAY]=>[50000, 12, 49134, 0, 16, 0],\r\n *           [\"test.rb\", 9, :T_STRING]=>[100000, 27, 98263, 0, 16, 0],\r\n *           [\"test.rb\", 9, :T_HASH]=>[50000, 16, 49147, 0, 16, 8998848],\r\n *           [\"test.rb\", 10, :T_STRING]=>[100000, 36, 98322, 0, 16, 0],\r\n *           [\"test.rb\", 10, :T_STRUCT]=>[50000, 16, 49147, 0, 16, 0]}\r\n *\r\n */\r\nstatic VALUE\r\nallocation_tracer_setup(int argc, VALUE *argv, VALUE self)\r\n{\r\n    struct traceobj_arg * arg = get_traceobj_arg();\r\n\r\n    if (arg->running) {\r\n\trb_raise(rb_eRuntimeError, \"can't change configuration during running\");\r\n    }\r\n    else {\r\n\tif (argc >= 1) {\r\n\t    int i;\r\n\t    VALUE ary = rb_check_array_type(argv[0]);\r\n\r\n\t    arg->keys = 0;\r\n\r\n\t    for (i=0; i<(int)RARRAY_LEN(ary); i++) {\r\n\t\tif (RARRAY_AREF(ary, i) == ID2SYM(rb_intern(\"path\"))) arg->keys |= KEY_PATH;\r\n\t\telse if (RARRAY_AREF(ary, i) == ID2SYM(rb_intern(\"line\"))) arg->keys |= KEY_LINE;\r\n\t\telse if (RARRAY_AREF(ary, i) == ID2SYM(rb_intern(\"type\"))) arg->keys |= KEY_TYPE;\r\n\t\telse if (RARRAY_AREF(ary, i) == ID2SYM(rb_intern(\"class\"))) arg->keys |= KEY_CLASS;\r\n\t\telse {\r\n\t\t    rb_raise(rb_eArgError, \"not supported key type\");\r\n\t\t}\r\n\t    }\r\n\t}\r\n    }\r\n\r\n    if (argc == 0) {\r\n\targ->keys = KEY_PATH | KEY_LINE;\r\n    }\r\n    return Qnil;\r\n}\r\n\r\n/*\r\n *\r\n *  call-seq:\r\n *     ObjectSpace::AllocationTracer.header   -> array\r\n *\r\n *  Return headers\r\n *\r\n *  Example:\r\n *\r\n *     puts ObjectSpace::AllocationTracer.header\r\n *     => [:path, :line, :count, :old_count, :total_age, :min_age, :max_age, :total_memsize]\r\n *\r\n */\r\nVALUE\r\nallocation_tracer_header(VALUE self)\r\n{\r\n    VALUE ary = rb_ary_new();\r\n    struct traceobj_arg * arg = get_traceobj_arg();\r\n\r\n    if (arg->keys & KEY_PATH) rb_ary_push(ary, ID2SYM(rb_intern(\"path\")));\r\n    if (arg->keys & KEY_LINE) rb_ary_push(ary, ID2SYM(rb_intern(\"line\")));\r\n    if (arg->keys & KEY_TYPE) rb_ary_push(ary, ID2SYM(rb_intern(\"type\")));\r\n    if (arg->keys & KEY_CLASS) rb_ary_push(ary, ID2SYM(rb_intern(\"class\")));\r\n\r\n    if (arg->vals & VAL_COUNT) rb_ary_push(ary, ID2SYM(rb_intern(\"count\")));\r\n    if (arg->vals & VAL_OLDCOUNT) rb_ary_push(ary, ID2SYM(rb_intern(\"old_count\")));\r\n    if (arg->vals & VAL_TOTAL_AGE) rb_ary_push(ary, ID2SYM(rb_intern(\"total_age\")));\r\n    if (arg->vals & VAL_MIN_AGE) rb_ary_push(ary, ID2SYM(rb_intern(\"min_age\")));\r\n    if (arg->vals & VAL_MAX_AGE) rb_ary_push(ary, ID2SYM(rb_intern(\"max_age\")));\r\n    if (arg->vals & VAL_MEMSIZE) rb_ary_push(ary, ID2SYM(rb_intern(\"total_memsize\")));\r\n    return ary;\r\n}\r\n\r\n/*\r\n *\r\n *  call-seq:\r\n *     ObjectSpace::AllocationTracer.lifetime_table_setup(true)   -> NilClass\r\n *\r\n * Enables tracing for the generation of objects\r\n *\r\n * See ObjectSpace::AllocationTracer.lifetime_table for an example.\r\n */\r\nstatic VALUE\r\nallocation_tracer_lifetime_table_setup(VALUE self, VALUE set)\r\n{\r\n    struct traceobj_arg * arg = get_traceobj_arg();\r\n\r\n    if (arg->running) {\r\n\trb_raise(rb_eRuntimeError, \"can't change configuration during running\");\r\n    }\r\n\r\n    if (RTEST(set)) {\r\n\tif (arg->lifetime_table == NULL) {\r\n\t    arg->lifetime_table = (size_t **)calloc(T_MASK, sizeof(size_t **));\r\n\t}\r\n    }\r\n    else {\r\n\tdelete_lifetime_table(arg);\r\n    }\r\n\r\n    return Qnil;\r\n}\r\n\r\n/*\r\n *\r\n *  call-seq:\r\n *     ObjectSpace::AllocationTracer.lifetime_table   -> hash\r\n *\r\n * Returns generations for objects\r\n *\r\n * Count is for both living (retained) and dead (freed) objects.\r\n *\r\n * The key is the type of objects, for example `T_OBJECT` for Ruby objects\r\n * or `T_STRING` for Ruby strings.\r\n *\r\n * The value is an array containing a count of the objects, the index is\r\n * the generation.\r\n *\r\n * Example:\r\n *\r\n *     require 'pp'\r\n *     require 'allocation_tracer'\r\n *\r\n *     ObjectSpace::AllocationTracer.lifetime_table_setup true\r\n *     result = ObjectSpace::AllocationTracer.trace do\r\n *       100000.times{\r\n *         Object.new\r\n *         ''\r\n *       }\r\n *     end\r\n *     pp ObjectSpace::AllocationTracer.lifetime_table\r\n *     # => {:T_OBJECT=>[3434, 96563, 0, 0, 1, 0, 0, 2],\r\n *           :T_STRING=>[3435, 96556, 2, 1, 1, 1, 1, 1, 2]}\r\n */\r\nstatic VALUE\r\nallocation_tracer_lifetime_table(VALUE self)\r\n{\r\n    VALUE result = rb_ivar_get(rb_mAllocationTracer, rb_intern(\"lifetime_table\"));\r\n    rb_ivar_set(rb_mAllocationTracer, rb_intern(\"lifetime_table\"), Qnil);\r\n    return result;\r\n}\r\n\r\n\r\n/*\r\n *\r\n *  call-seq:\r\n *     ObjectSpace::AllocationTracer.allocated_count_table   -> hash\r\n *\r\n * Returns allocation count totals for Ruby object types\r\n *\r\n * Returns a hash showing the number of each type of object that has been allocated.\r\n *\r\n * Example:\r\n *\r\n *     require 'allocation_tracer'\r\n *\r\n *     ObjectSpace::AllocationTracer.trace do\r\n *       1000.times {\r\n *         [\"foo\", {}]\r\n *       }\r\n *     end\r\n *     p allocated: ObjectSpace::AllocationTracer.allocated_count_table\r\n *     {:allocated=>{:T_NONE=>0, :T_OBJECT=>0, :T_CLASS=>0, :T_MODULE=>0, :T_FLOAT=>0, :T_STRING=>1000, :T_REGEXP=>0, :T_ARRAY=>1000, :T_HASH=>1000, :T_STRUCT=>0, :T_BIGNUM=>0, :T_FILE=>0, :T_DATA=>0, :T_MATCH=>0, :T_COMPLEX=>0, :T_RATIONAL=>0, :unknown=>0, :T_NIL=>0, :T_TRUE=>0, :T_FALSE=>0, :T_SYMBOL=>0, :T_FIXNUM=>0, :T_UNDEF=>0, :T_NODE=>0, :T_ICLASS=>0, :T_ZOMBIE=>0}}\r\n *\r\n *     p freed: ObjectSpace::AllocationTracer.freed_count_table\r\n *     {:freed=>{:T_NONE=>0, :T_OBJECT=>0, :T_CLASS=>0, :T_MODULE=>0, :T_FLOAT=>0, :T_STRING=>1871, :T_REGEXP=>41, :T_ARRAY=>226, :T_HASH=>7, :T_STRUCT=>41, :T_BIGNUM=>0, :T_FILE=>50, :T_DATA=>25, :T_MATCH=>47, :T_COMPLEX=>0, :T_RATIONAL=>0, :unknown=>0, :T_NIL=>0, :T_TRUE=>0, :T_FALSE=>0, :T_SYMBOL=>0, :T_FIXNUM=>0, :T_UNDEF=>0, :T_NODE=>932, :T_ICLASS=>0, :T_ZOMBIE=>0}}\r\n */\r\nstatic VALUE\r\nallocation_tracer_allocated_count_table(VALUE self)\r\n{\r\n    struct traceobj_arg * arg = get_traceobj_arg();\r\n    VALUE h = rb_hash_new();\r\n    int i;\r\n\r\n    for (i=0; i<T_MASK; i++) {\r\n\trb_hash_aset(h, type_sym(i), SIZET2NUM(arg->allocated_count_table[i]));\r\n    }\r\n\r\n    return h;\r\n}\r\n\r\n/*\r\n *\r\n *  call-seq:\r\n *     ObjectSpace::AllocationTracer.freed_count_table   -> hash\r\n *\r\n * Returns freed count totals for Ruby object types\r\n *\r\n * Returns a hash showing the number of each type of object that has been freed.\r\n *\r\n * See ObjectSpace::AllocationTracer.allocated_count_table for example usage\r\n *\r\n */\r\nstatic VALUE\r\nallocation_tracer_freed_count_table(VALUE self)\r\n{\r\n    struct traceobj_arg * arg = get_traceobj_arg();\r\n    VALUE h = rb_hash_new();\r\n    int i;\r\n\r\n    for (i=0; i<T_MASK; i++) {\r\n\trb_hash_aset(h, type_sym(i), SIZET2NUM(arg->freed_count_table[i]));\r\n    }\r\n\r\n    return h;\r\n}\r\n\r\nvoid\r\nInit_allocation_tracer(void)\r\n{\r\n    VALUE rb_mObjSpace = rb_const_get(rb_cObject, rb_intern(\"ObjectSpace\"));\r\n    VALUE mod = rb_mAllocationTracer = rb_define_module_under(rb_mObjSpace, \"AllocationTracer\");\r\n\r\n    /* allocation tracer methods */\r\n    rb_define_module_function(mod, \"trace\", allocation_tracer_trace, 0);\r\n    rb_define_module_function(mod, \"start\", allocation_tracer_trace, 0);\r\n    rb_define_module_function(mod, \"stop\", allocation_tracer_stop, 0);\r\n    rb_define_module_function(mod, \"pause\", allocation_tracer_pause, 0);\r\n    rb_define_module_function(mod, \"resume\", allocation_tracer_resume, 0);\r\n\r\n    rb_define_module_function(mod, \"result\", allocation_tracer_result, 0);\r\n    rb_define_module_function(mod, \"clear\", allocation_tracer_clear, 0);\r\n    rb_define_module_function(mod, \"setup\", allocation_tracer_setup, -1);\r\n    rb_define_module_function(mod, \"header\", allocation_tracer_header, 0);\r\n\r\n    rb_define_module_function(mod, \"lifetime_table_setup\", allocation_tracer_lifetime_table_setup, 1);\r\n    rb_define_module_function(mod, \"lifetime_table\", allocation_tracer_lifetime_table, 0);\r\n\r\n    rb_define_module_function(mod, \"allocated_count_table\", allocation_tracer_allocated_count_table, 0);\r\n    rb_define_module_function(mod, \"freed_count_table\", allocation_tracer_freed_count_table, 0);\r\n}\r\n"
  },
  {
    "path": "ext/allocation_tracer/extconf.rb",
    "content": "require 'mkmf'\r\ncreate_makefile('allocation_tracer/allocation_tracer')\r\n"
  },
  {
    "path": "lib/allocation_tracer/trace.rb",
    "content": "require 'allocation_tracer'\r\n\r\n# ObjectSpace::AllocationTracer.setup(%i{path line})\r\nObjectSpace::AllocationTracer.trace\r\n\r\nat_exit{\r\n  results = ObjectSpace::AllocationTracer.stop\r\n\r\n  puts ObjectSpace::AllocationTracer.header.join(\"\\t\")\r\n  results.sort_by{|k, v| k}.each{|k, v|\r\n    puts (k+v).join(\"\\t\")\r\n  }\r\n}\r\n\r\n\r\n"
  },
  {
    "path": "lib/allocation_tracer/version.rb",
    "content": "module ObjectSpace::AllocationTracer\n  VERSION = \"0.6.3\"\nend\n"
  },
  {
    "path": "lib/allocation_tracer.rb",
    "content": "require \"allocation_tracer/version\"\nrequire \"allocation_tracer/allocation_tracer\"\n\nmodule ObjectSpace::AllocationTracer\n\n  def self.output_lifetime_table table\n    out = (file = ENV['RUBY_ALLOCATION_TRACER_LIFETIME_OUT']) ? open(File.expand_path(file), 'w') : STDOUT\n    max_lines = table.inject(0){|r, (_type, lines)| r < lines.size ? lines.size : r}\n    out.puts \"type\\t\" + (0...max_lines).to_a.join(\"\\t\")\n    table.each{|type, line|\n      out.puts \"#{type}\\t#{line.join(\"\\t\")}\"\n    }\n  end\n\n  def self.collect_lifetime_table\n    ObjectSpace::AllocationTracer.lifetime_table_setup true\n\n    if block_given?\n      begin\n        ObjectSpace::AllocationTracer.trace do\n          yield\n        end\n        result = ObjectSpace::AllocationTracer.lifetime_table\n        output_lifetime_table(result)\n      ensure\n        ObjectSpace::AllocationTracer.lifetime_table_setup false\n      end\n    else\n      ObjectSpace::AllocationTracer.trace\n    end\n  end\n\n  def self.collect_lifetime_table_stop\n    ObjectSpace::AllocationTracer.stop\n    result = ObjectSpace::AllocationTracer.lifetime_table\n    ObjectSpace::AllocationTracer.lifetime_table_setup false\n    output_lifetime_table(result)\n    result\n  end\nend\n"
  },
  {
    "path": "lib/rack/allocation_tracer.rb",
    "content": "#\n# Rack middleware\n#\n\nrequire 'allocation_tracer'\n\nmodule Rack\n  module AllocationTracerMiddleware\n    def self.new *args\n      TotalTracer.new *args\n    end\n\n    class Tracer\n      def initialize app\n        @app = app\n        @sort_order = (0..7).to_a\n      end\n\n      def allocation_trace_page result, env\n        if /\\As=(\\d+)/ =~ env[\"QUERY_STRING\"]\n          top = $1.to_i\n          @sort_order.unshift top if @sort_order.delete top\n        end\n\n        table = result.map{|(file, line, klass), (count, oldcount, total_age, min_age, max_age, memsize)|\n          [\"#{Rack::Utils.escape_html(file)}:#{'%04d' % line}\",\n            Rack::Utils.escape_html(klass ? klass.name : '<internal>'),\n            count, oldcount, total_age / Float(count), min_age, max_age, memsize]\n        }\n\n        begin\n          table = table.sort_by{|vs|\n            ary = @sort_order.map{|i| Numeric === vs[i] ? -vs[i] : vs[i]}\n          }\n        rescue\n          ts = []\n          table.each{|*cols|\n            cols.each.with_index{|c, i|\n              h = (ts[i] ||= Hash.new(0))\n              h[c.class] += 1\n            }\n          }\n          return \"<pre>Sorting error\\n\" + Rack::Utils.escape_html(h.inspect) + \"</pre>\"\n        end\n\n        headers = %w(path class count old_count average_age min_age max_age memsize).map.with_index{|e, i|\n          \"<th><a href='./?s=#{i}'>#{e}</a></th>\"\n        }.join(\"\\n\")\n        header = \"<tr>#{headers}</tr>\"\n        body = table.map{|cols|\n          \"<tr>\" + cols.map{|c| \"<td>#{c}</td>\"}.join(\"\\n\") + \"</tr>\"\n        }.join(\"\\n\")\n        \"<table>#{header}#{body}</table>\"\n      end\n\n      def count_table_page count_table\n        text = count_table.map{|k, v| \"%-10s\\t%8d\" % [k, v]}.join(\"\\n\")\n        \"<pre>#{text}</pre>\"\n      end\n\n      def allocated_count_table_page\n        count_table_page ObjectSpace::AllocationTracer.allocated_count_table\n      end\n\n      def freed_count_table_page\n        count_table_page ObjectSpace::AllocationTracer.freed_count_table\n      end\n\n      def lifetime_table_page\n        table = []\n        max_age = 0\n        ObjectSpace::AllocationTracer.lifetime_table.each{|type, ages|\n          max_age = [max_age, ages.size - 1].max\n          table << [type, *ages]\n        }\n        headers = ['type', *(0..max_age)].map{|e| \"<th>#{e}</th>\"}.join(\"\\n\")\n        body =  table.map{|cols|\n          \"<tr>\" + cols.map{|c| \"<td>#{c}</td>\"}.join(\"\\n\") + \"</tr>\"\n        }.join(\"\\n\")\n        \"<table border='1'><tr>#{headers}</tr>\\n#{body}</table>\"\n      end\n\n      def call env\n        if /\\A\\/allocation_tracer(?:\\/|$)/ =~ env[\"PATH_INFO\"]\n          result = ObjectSpace::AllocationTracer.result\n          ObjectSpace::AllocationTracer.pause\n\n          begin\n            html = case env[\"PATH_INFO\"]\n                   when /lifetime_table/\n                     lifetime_table_page\n                   when /allocated_count_table/\n                     allocated_count_table_page\n                   when /freed_count_table/\n                     freed_count_table_page\n                   else\n                     allocation_trace_page result, env\n                   end\n            #\n            [200, {\"Content-Type\" => \"text/html\"}, [html]]\n          ensure\n            ObjectSpace::AllocationTracer.resume\n          end\n        else\n          @app.call env\n        end\n      end\n    end\n\n    class TotalTracer < Tracer\n      def initialize *args\n        super\n        ObjectSpace::AllocationTracer.setup %i(path line class)\n        ObjectSpace::AllocationTracer.lifetime_table_setup true\n        ObjectSpace::AllocationTracer.start\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/allocation_tracer_spec.rb",
    "content": "require 'spec_helper'\r\nrequire 'tmpdir'\r\nrequire 'fileutils'\r\n\r\ndescribe ObjectSpace::AllocationTracer do\r\n  describe 'ObjectSpace::AllocationTracer.trace' do\r\n    it 'should includes allocation information' do\r\n      line = __LINE__ + 2\r\n      result = ObjectSpace::AllocationTracer.trace do\r\n        Object.new\r\n      end\r\n\r\n      expect(result.length).to be >= 1\r\n      expect(result[[__FILE__, line]]).to eq [1, 0, 0, 0, 0, 0]\r\n    end\r\n\r\n    it 'should run twice' do\r\n      line = __LINE__ + 2\r\n      result = ObjectSpace::AllocationTracer.trace do\r\n        Object.new\r\n      end\r\n      #GC.start\r\n      # p result\r\n      expect(result.length).to be >= 1\r\n      expect(result[[__FILE__, line]]).to eq [1, 0, 0, 0, 0, 0]\r\n    end\r\n\r\n    it 'should analyze many objects' do\r\n      line = __LINE__ + 3\r\n      result = ObjectSpace::AllocationTracer.trace do\r\n        50_000.times{|i|\r\n          i.to_s\r\n          i.to_s\r\n          i.to_s\r\n        }\r\n      end\r\n\r\n      GC.start\r\n      #pp result\r\n\r\n      expect(result[[__FILE__, line + 0]][0]).to be >= 50_000\r\n      expect(result[[__FILE__, line + 1]][0]).to be >= 50_000\r\n      expect(result[[__FILE__, line + 2]][0]).to be >= 50_000\r\n    end\r\n\r\n    it 'should count old objects' do\r\n      a = nil\r\n      line = __LINE__ + 2\r\n      result = ObjectSpace::AllocationTracer.trace do\r\n        a = 'x' # it will be old object\r\n        32.times{GC.start}\r\n      end\r\n\r\n      expect(result.length).to be >= 1\r\n      _, old_count, * = *result[[__FILE__, line]]\r\n      expect(old_count).to be == 1\r\n    end\r\n\r\n    it 'should acquire allocated memsize' do\r\n      line = __LINE__ + 2\r\n      result = ObjectSpace::AllocationTracer.trace do\r\n        _ = 'x' * 1234 # danger\r\n        GC.start\r\n      end\r\n\r\n      expect(result.length).to be >= 1\r\n      size = result[[__FILE__, line]][-1]\r\n      expect(size).to be > 1234 if size > 0\r\n    end\r\n\r\n    it 'can be paused and resumed' do\r\n      line = __LINE__ + 2\r\n      result = ObjectSpace::AllocationTracer.trace do\r\n        Object.new\r\n        ObjectSpace::AllocationTracer.pause\r\n        Object.new # ignore tracing\r\n        ObjectSpace::AllocationTracer.resume\r\n        Object.new\r\n      end\r\n\r\n      expect(result.length).to be 2\r\n      expect(result[[__FILE__, line    ]]).to eq [1, 0, 0, 0, 0, 0]\r\n      expect(result[[__FILE__, line + 4]]).to eq [1, 0, 0, 0, 0, 0]\r\n    end\r\n\r\n    it 'can be get middle result' do\r\n      middle_result = nil\r\n      line = __LINE__ + 2\r\n      result = ObjectSpace::AllocationTracer.trace do\r\n        Object.new\r\n        middle_result = ObjectSpace::AllocationTracer.result\r\n        Object.new\r\n      end\r\n\r\n      expect(result.length).to be 2\r\n      expect(result[[__FILE__, line    ]]).to eq [1, 0, 0, 0, 0, 0]\r\n      expect(result[[__FILE__, line + 2]]).to eq [1, 0, 0, 0, 0, 0]\r\n\r\n      expect(middle_result.length).to be 1\r\n      expect(middle_result[[__FILE__, line    ]]).to eq [1, 0, 0, 0, 0, 0]\r\n    end\r\n\r\n    describe 'stop when not started yet' do\r\n      it 'should raise RuntimeError' do\r\n        expect do\r\n          ObjectSpace::AllocationTracer.stop\r\n        end.to raise_error(RuntimeError)\r\n      end\r\n    end\r\n\r\n    describe 'pause when not started yet' do\r\n      it 'should raise RuntimeError' do\r\n        expect do\r\n          ObjectSpace::AllocationTracer.pause\r\n        end.to raise_error(RuntimeError)\r\n      end\r\n    end\r\n\r\n    describe 'resume when not started yet' do\r\n      it 'should raise RuntimeError' do\r\n        expect do\r\n          ObjectSpace::AllocationTracer.resume\r\n        end.to raise_error(RuntimeError)\r\n      end\r\n    end\r\n\r\n    describe 'when starting recursively' do\r\n      it 'should raise RuntimeError' do\r\n        expect do\r\n          ObjectSpace::AllocationTracer.trace{\r\n            ObjectSpace::AllocationTracer.trace{}\r\n          }\r\n        end.to raise_error(RuntimeError)\r\n      end\r\n    end\r\n\r\n    describe 'with different setup' do\r\n      it 'should work with type' do\r\n        line = __LINE__ + 3\r\n        ObjectSpace::AllocationTracer.setup(%i(path line type))\r\n        result = ObjectSpace::AllocationTracer.trace do\r\n          _a = [Object.new]\r\n          _b = {Object.new => 'foo'}\r\n        end\r\n\r\n        expect(result.length).to be 5\r\n        expect(result[[__FILE__, line, :T_OBJECT]]).to eq [1, 0, 0, 0, 0, 0]\r\n        expect(result[[__FILE__, line, :T_ARRAY]]).to eq [1, 0, 0, 0, 0, 0]\r\n        # expect(result[[__FILE__, line + 1, :T_HASH]]).to eq [1, 0, 0, 0, 0]\r\n        expect(result[[__FILE__, line + 1, :T_OBJECT]]).to eq [1, 0, 0, 0, 0, 0]\r\n        expect(result[[__FILE__, line + 1, :T_STRING]]).to eq [1, 0, 0, 0, 0, 0]\r\n      end\r\n\r\n      it 'should work with class' do\r\n        line = __LINE__ + 3\r\n        ObjectSpace::AllocationTracer.setup(%i(path line class))\r\n        result = ObjectSpace::AllocationTracer.trace do\r\n          _a = [Object.new]\r\n          _b = {Object.new => 'foo'}\r\n        end\r\n\r\n        expect(result.length).to be 5\r\n        expect(result[[__FILE__, line, Object]]).to eq [1, 0, 0, 0, 0, 0]\r\n        expect(result[[__FILE__, line, Array]]).to eq [1, 0, 0, 0, 0, 0]\r\n        # expect(result[[__FILE__, line + 1, Hash]]).to eq [1, 0, 0, 0, 0, 0]\r\n        expect(result[[__FILE__, line + 1, Object]]).to eq [1, 0, 0, 0, 0, 0]\r\n        expect(result[[__FILE__, line + 1, String]]).to eq [1, 0, 0, 0, 0, 0]\r\n      end\r\n\r\n      it 'should have correct headers' do\r\n        ObjectSpace::AllocationTracer.setup(%i(path line))\r\n        expect(ObjectSpace::AllocationTracer.header).to eq [:path, :line, :count, :old_count, :total_age, :min_age, :max_age, :total_memsize]\r\n        ObjectSpace::AllocationTracer.setup(%i(path line class))\r\n        expect(ObjectSpace::AllocationTracer.header).to eq [:path, :line, :class, :count, :old_count, :total_age, :min_age, :max_age, :total_memsize]\r\n        ObjectSpace::AllocationTracer.setup(%i(path line type class))\r\n        expect(ObjectSpace::AllocationTracer.header).to eq [:path, :line, :type, :class, :count, :old_count, :total_age, :min_age, :max_age, :total_memsize]\r\n      end\r\n\r\n      it 'should set default setup' do\r\n        ObjectSpace::AllocationTracer.setup()\r\n        expect(ObjectSpace::AllocationTracer.header).to eq [:path, :line, :count, :old_count, :total_age, :min_age, :max_age, :total_memsize]\r\n      end\r\n    end\r\n  end\r\n\r\n  describe 'collect lifetime_table' do\r\n    before do\r\n      ObjectSpace::AllocationTracer.lifetime_table_setup true\r\n    end\r\n\r\n    after do\r\n      ObjectSpace::AllocationTracer.lifetime_table_setup false\r\n    end\r\n\r\n    it 'should make lifetime table' do\r\n      ObjectSpace::AllocationTracer.trace do\r\n        100000.times{\r\n          Object.new\r\n          ''\r\n        }\r\n      end\r\n      table = ObjectSpace::AllocationTracer.lifetime_table\r\n\r\n      expect(table[:T_OBJECT].inject(&:+)).to be >= 10_000\r\n      expect(table[:T_STRING].inject(&:+)).to be >= 10_000\r\n      expect(table[:T_NONE]).to be nil\r\n    end\r\n\r\n    it 'should return nil when ObjectSpace::AllocationTracer.lifetime_table_setup is false' do\r\n      ObjectSpace::AllocationTracer.lifetime_table_setup false\r\n\r\n      ObjectSpace::AllocationTracer.trace do\r\n        100000.times{\r\n          Object.new\r\n          ''\r\n        }\r\n      end\r\n\r\n      table = ObjectSpace::AllocationTracer.lifetime_table\r\n\r\n      expect(table).to be nil\r\n    end\r\n\r\n    it 'should return nil getting it twice' do\r\n      ObjectSpace::AllocationTracer.trace do\r\n        100000.times{\r\n          Object.new\r\n          ''\r\n        }\r\n      end\r\n\r\n      table = ObjectSpace::AllocationTracer.lifetime_table\r\n      table = ObjectSpace::AllocationTracer.lifetime_table\r\n\r\n      expect(table).to be nil\r\n    end\r\n  end\r\n\r\n  describe 'ObjectSpace::AllocationTracer.collect_lifetime_table' do\r\n    it 'should collect lifetime table' do\r\n      table = ObjectSpace::AllocationTracer.collect_lifetime_table do\r\n        100000.times{\r\n          Object.new\r\n          ''\r\n        }\r\n      end\r\n\r\n      expect(table[:T_OBJECT].inject(&:+)).to be >= 10_000\r\n      expect(table[:T_STRING].inject(&:+)).to be >= 10_000\r\n      expect(table[:T_NONE]).to be nil\r\n    end\r\n  end\r\n\r\n  describe 'ObjectSpace::AllocationTracer.allocated_count_table' do\r\n    it 'should return a Hash object' do\r\n      h = ObjectSpace::AllocationTracer.allocated_count_table\r\n      expect(h[:T_NONE]).to be 0\r\n    end\r\n  end\r\n\r\n  describe 'ObjectSpace::AllocationTracer.freed_count_table' do\r\n    it 'should return a Hash object' do\r\n      h = ObjectSpace::AllocationTracer.freed_count_table\r\n      expect(h[:T_NONE]).to be 0\r\n    end\r\n  end\r\nend\r\n"
  },
  {
    "path": "spec/spec_helper.rb",
    "content": "require 'rubygems'\r\nrequire 'bundler/setup'\r\nrequire 'allocation_tracer'\r\n\r\nRSpec.configure do |config|\r\n  config.mock_framework = :rspec\r\nend"
  }
]