Full Code of ko1/allocation_tracer for AI

master f4ac1e8c15f8 cached
15 files
55.2 KB
16.5k tokens
67 symbols
1 requests
Download .txt
Repository: ko1/allocation_tracer
Branch: master
Commit: f4ac1e8c15f8
Files: 15
Total size: 55.2 KB

Directory structure:
gitextract_gpj107i2/

├── .gitignore
├── .travis.yml
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── allocation_tracer.gemspec
├── ext/
│   └── allocation_tracer/
│       ├── allocation_tracer.c
│       └── extconf.rb
├── lib/
│   ├── allocation_tracer/
│   │   ├── trace.rb
│   │   └── version.rb
│   ├── allocation_tracer.rb
│   └── rack/
│       └── allocation_tracer.rb
└── spec/
    ├── allocation_tracer_spec.rb
    └── spec_helper.rb

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
*.gem
*.rbc
.bundle
.config
.yardoc
Gemfile.lock
InstalledFiles
_yardoc
coverage
doc/
lib/bundler/man
pkg
rdoc
spec/reports
test/tmp
test/version_tmp
tmp
*.bundle
*.DS_Store


================================================
FILE: .travis.yml
================================================
language: ruby
rvm:
  - 2.1.0
  - 2.2.0
  - 2.3.0
before_install:
  - gem update bundler


================================================
FILE: Gemfile
================================================
source 'https://rubygems.org'

# Specify your gem's dependencies in allocation_tracer.gemspec
gemspec


================================================
FILE: LICENSE.txt
================================================
Copyright (c) 2014 Koichi Sasada

MIT License

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


================================================
FILE: README.md
================================================
# ObjectSpace::AllocationTracer

This module allows to trace object allocation.

This feature is similar to https://github.com/SamSaffron/memory_profiler
and https://github.com/srawlins/allocation_stats. But this feature
focused on `age' of objects.

This gem was separated from gc_tracer.gem.

[![Build Status](https://travis-ci.org/ko1/allocation_tracer.svg?branch=master)](https://travis-ci.org/ko1/allocation_tracer)

## Installation

Add this line to your application's Gemfile:

    gem 'allocation_tracer'

And then execute:

    $ bundle

Or install it yourself as:

    $ gem install allocation_tracer

## Usage

### Allocation tracing

You can trace allocation and aggregate information. Information includes:

* count - how many objects are created.
* total_age - total age of objects which created here
* max_age - age of longest living object created here
* min_age - age of shortest living object created here

Age of Object can be calculated by this formula: [current GC count] - [birth time GC count]

For example:

```ruby
require 'allocation_tracer'
require 'pp'

pp ObjectSpace::AllocationTracer.trace{
  50_000.times{|i|
    i.to_s
    i.to_s
    i.to_s
  }
}
```

will show

```
{["test.rb", 6]=>[50000, 0, 47440, 0, 1, 0],
 ["test.rb", 7]=>[50000, 4, 47452, 0, 6, 0],
 ["test.rb", 8]=>[50000, 7, 47456, 0, 6, 0]}
```

In this case,
* 50,000 objects are created at `test.rb:6' and
  * 0 old objects created.
  * 47,440 is total age of objects created at this line (average age of object created at this line is 47440/50000 = 0.9488).
  * 0 is minimum age
  * 1 is maximum age.
  * 0 total memory consumption without RVALUE

You can also specify `type' in GC::Tracer.setup_allocation_tracing() to
specify what should be keys to aggregate like that.

```ruby
require 'allocation_tracer'
require 'pp'

ObjectSpace::AllocationTracer.setup(%i{path line type})

result = ObjectSpace::AllocationTracer.trace do
  50_000.times{|i|
    a = [i.to_s]
    b = {i.to_s => nil}
    c = (i.to_s .. i.to_s)
  }
end

pp result
```

and you will get:

```
{["test.rb", 8, :T_STRING]=>[50000, 15, 49165, 0, 16, 0],
 ["test.rb", 8, :T_ARRAY]=>[50000, 12, 49134, 0, 16, 0],
 ["test.rb", 9, :T_STRING]=>[100000, 27, 98263, 0, 16, 0],
 ["test.rb", 9, :T_HASH]=>[50000, 16, 49147, 0, 16, 8998848],
 ["test.rb", 10, :T_STRING]=>[100000, 36, 98322, 0, 16, 0],
 ["test.rb", 10, :T_STRUCT]=>[50000, 16, 49147, 0, 16, 0]}
```

Interestingly, you can not see array creations in a middle of block:

```ruby
require 'allocation_tracer'
require 'pp'

ObjectSpace::AllocationTracer.setup(%i{path line type})

result = ObjectSpace::AllocationTracer.trace do
  50_000.times{|i|
    [i.to_s]
    nil
  }
end

pp result
```

and it prints:

```
{["test.rb", 8, :T_STRING]=>[25015, 5, 16299, 0, 2, 0]}
```

There are only string creation. This is because unused array creation is
ommitted by optimizer.

Simply you can require `allocation_tracer/trace' to start allocation
tracer and output the aggregated information into stdout at the end of
program.

```ruby
require 'allocation_tracer/trace'

# Run your program here
50_000.times{|i|
  i.to_s
  i.to_s
  i.to_s
}
```

and you will see:

```
path    line    count   old_count       total_age       min_age max_age total_memsize
...rubygems/core_ext/kernel_require.rb 55     18       1       23      1       6       358
...lib/allocation_tracer/lib/allocation_tracer/trace.rb       6       2      012      6       6       0
test.rb 0       1       0       0       0       0       0
test.rb 5       50000   4       41492   0       5       0
test.rb 6       50000   3       41490   0       5       0
test.rb 7       50000   7       41497   0       5       0
```

(tab separated columns)

### Total Allocations / Free

Allocation tracer collects the total number of allocations and frees during the
`trace` block.  After the block finishes executing, you can examine the total
number of allocations / frees per object type like this:

```ruby
require 'allocation_tracer'

ObjectSpace::AllocationTracer.trace do
  1000.times {
    ["foo", {}]
  }
end
p allocated: ObjectSpace::AllocationTracer.allocated_count_table
p freed: ObjectSpace::AllocationTracer.freed_count_table
```

The output of the script will look like this:

```
{: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}}
{: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}}
```

### Lifetime table

You can collect lifetime statistics with
ObjectSpace::AllocationTracer.lifetime_table method.

```ruby
require 'pp'
require 'allocation_tracer'

ObjectSpace::AllocationTracer.lifetime_table_setup true
result = ObjectSpace::AllocationTracer.trace do
  100000.times{
    Object.new
    ''
  }
end
pp ObjectSpace::AllocationTracer.lifetime_table
```

will show

```
{:T_OBJECT=>[3434, 96563, 0, 0, 1, 0, 0, 2],
 :T_STRING=>[3435, 96556, 2, 1, 1, 1, 1, 1, 2]}
```

This output means that the age of 3434 T_OBJECT objects are 0, 96563
objects are 1 and 2 objects are 7. Also the age of 3435 T_STRING
objects are 0, 96556 objects are 1 and so on.

Note that these numbers includes living objects and dead objects.  For
dead objects, age means lifetime. For living objects, age means
current age.

## Rack middleware

You can use AllocationTracer via rack middleware.

```ruby
require 'rack'
require 'sinatra'
require 'rack/allocation_tracer'

use Rack::AllocationTracerMiddleware

get '/' do
  'foo'
end
```

When you access to `http://host/allocation_tracer/` then you can see a table of allocation tracer.

You can access the following pages.

* http://host/allocation_tracer/
* http://host/allocation_tracer/allocated_count_table
* http://host/allocation_tracer/freed_count_table_page
* http://host/allocation_tracer/lifetime_table

The following pages are demonstration Rails app on Heroku environment.

* http://protected-journey-7206.herokuapp.com/allocation_tracer/
* http://protected-journey-7206.herokuapp.com/allocation_tracer/allocated_count_table
* http://protected-journey-7206.herokuapp.com/allocation_tracer/freed_count_table_page
* http://protected-journey-7206.herokuapp.com/allocation_tracer/lifetime_table

Source 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.

## Development

Compile the code:

```
$ rake compile
```

Run the tests:

```
$ rake spec
```

## Contributing

1. Fork it ( http://github.com/ko1/allocation_tracer/fork )
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create new Pull Request

## Author

Koichi Sasada from Heroku, Inc.



================================================
FILE: Rakefile
================================================
require "bundler/gem_tasks"
require "rake/extensiontask"
require 'rspec/core/rake_task'

spec = Gem::Specification.load('allocation_tracer.gemspec')

Rake::ExtensionTask.new("allocation_tracer", spec){|ext|
  ext.lib_dir = "lib/allocation_tracer"
}

RSpec::Core::RakeTask.new('spec' => 'compile')

task default: :spec

task :run => 'compile' do
  ruby %q{-I ./lib test.rb}
end


================================================
FILE: allocation_tracer.gemspec
================================================
# coding: utf-8
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'allocation_tracer/version'

Gem::Specification.new do |spec|
  spec.name          = "allocation_tracer"
  spec.version       = ObjectSpace::AllocationTracer::VERSION
  spec.authors       = ["Koichi Sasada"]
  spec.email         = ["ko1@atdot.net"]
  spec.summary       = %q{allocation_tracer gem adds ObjectSpace::AllocationTracer module.}
  spec.description   = %q{allocation_tracer gem adds ObjectSpace::AllocationTracer module.}
  spec.homepage      = "https://github.com/ko1/allocation_tracer"
  spec.license       = "MIT"

  spec.extensions    = %w[ext/allocation_tracer/extconf.rb]
  spec.required_ruby_version = '>= 2.1.0'

  spec.files         = `git ls-files -z`.split("\x0")
  spec.executables   = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
  spec.test_files    = spec.files.grep(%r{^(test|spec|features)/})
  spec.require_paths = ["lib"]

  spec.add_development_dependency "bundler", "~> 1.5"
  spec.add_development_dependency "rake"
  spec.add_development_dependency "rake-compiler"
  spec.add_development_dependency "rspec"
end


================================================
FILE: ext/allocation_tracer/allocation_tracer.c
================================================
/*
 * allocation tracer: adds GC::Tracer::start_allocation_tracing
 *
 * By Koichi Sasada
 * created at Thu Apr 17 03:50:38 2014.
 */

#include "ruby/ruby.h"
#include "ruby/debug.h"
#include <assert.h>

size_t rb_obj_memsize_of(VALUE obj); /* in gc.c */

static VALUE rb_mAllocationTracer;

struct traceobj_arg {
    int running;
    int keys, vals;
    st_table *object_table;     /* obj (VALUE)      -> allocation_info */
    st_table *str_table;        /* cstr             -> refcount */

    st_table *aggregate_table;  /* user defined key -> [count, total_age, max_age, min_age] */
    struct allocation_info *freed_allocation_info;

    /* */
    size_t **lifetime_table;
    size_t allocated_count_table[T_MASK];
    size_t freed_count_table[T_MASK];
};

struct allocation_info {
    struct allocation_info *next;

    /* all of information don't need marking. */
    int living;
    VALUE flags;
    VALUE klass;
    size_t generation;
    size_t memsize;

    /* allocator info */
    const char *path;
    unsigned long line;
};

#define MAX_KEY_DATA 4

#define KEY_PATH    (1<<1)
#define KEY_LINE    (1<<2)
#define KEY_TYPE    (1<<3)
#define KEY_CLASS   (1<<4)

#define MAX_VAL_DATA 6

#define VAL_COUNT     (1<<1)
#define VAL_OLDCOUNT  (1<<2)
#define VAL_TOTAL_AGE (1<<3)
#define VAL_MIN_AGE   (1<<4)
#define VAL_MAX_AGE   (1<<5)
#define VAL_MEMSIZE   (1<<6)

static char *
keep_unique_str(st_table *tbl, const char *str)
{
    st_data_t n;

    if (str && st_lookup(tbl, (st_data_t)str, &n)) {
	char *result;

	st_insert(tbl, (st_data_t)str, n+1);
	st_get_key(tbl, (st_data_t)str, (st_data_t *)&result);

	return result;
    }
    else {
	return NULL;
    }
}

static const char *
make_unique_str(st_table *tbl, const char *str, long len)
{
    if (!str) {
	return NULL;
    }
    else {
	char *result;

	if ((result = keep_unique_str(tbl, str)) == NULL) {
	    result = (char *)ruby_xmalloc(len+1);
	    strncpy(result, str, len);
	    result[len] = 0;
	    st_add_direct(tbl, (st_data_t)result, 1);
	}
	return result;
    }
}

static void
delete_unique_str(st_table *tbl, const char *str)
{
    if (str) {
	st_data_t n;

	if (st_lookup(tbl, (st_data_t)str, &n) == 0) rb_bug("delete_unique_str: unreachable");

	if (n == 1) {
	    st_delete(tbl, (st_data_t *)&str, NULL);
	    ruby_xfree((char *)str);
	}
	else {
	    st_insert(tbl, (st_data_t)str, n-1);
	}
    }
}

struct memcmp_key_data {
    int n;
    st_data_t data[MAX_KEY_DATA];
};

static int
memcmp_hash_compare(st_data_t a, st_data_t b)
{
    struct memcmp_key_data *k1 = (struct memcmp_key_data *)a;
    struct memcmp_key_data *k2 = (struct memcmp_key_data *)b;
    return memcmp(&k1->data[0], &k2->data[0], k1->n * sizeof(st_data_t));
}

static st_index_t
memcmp_hash_hash(st_data_t a)
{
    struct memcmp_key_data *k = (struct memcmp_key_data *)a;
    return rb_memhash(k->data, sizeof(st_data_t) * k->n);
}

static const struct st_hash_type memcmp_hash_type = {
    memcmp_hash_compare, memcmp_hash_hash
};

static struct traceobj_arg *tmp_trace_arg; /* TODO: Do not use global variables */

static struct traceobj_arg *
get_traceobj_arg(void)
{
    if (tmp_trace_arg == 0) {
	tmp_trace_arg = ALLOC_N(struct traceobj_arg, 1);
	MEMZERO(tmp_trace_arg, struct traceobj_arg, 1);
	tmp_trace_arg->running = 0;
	tmp_trace_arg->keys = 0;
	tmp_trace_arg->vals = VAL_COUNT | VAL_OLDCOUNT | VAL_TOTAL_AGE | VAL_MAX_AGE | VAL_MIN_AGE | VAL_MEMSIZE;
	tmp_trace_arg->aggregate_table = st_init_table(&memcmp_hash_type);
	tmp_trace_arg->object_table = st_init_numtable();
	tmp_trace_arg->str_table = st_init_strtable();
	tmp_trace_arg->freed_allocation_info = NULL;
	tmp_trace_arg->lifetime_table = NULL;
    }
    return tmp_trace_arg;
}

static int
free_keys_i(st_data_t key, st_data_t value, void *data)
{
    ruby_xfree((void *)key);
    return ST_CONTINUE;
}

static int
free_values_i(st_data_t key, st_data_t value, void *data)
{
    ruby_xfree((void *)value);
    return ST_CONTINUE;
}

static int
free_key_values_i(st_data_t key, st_data_t value, void *data)
{
    ruby_xfree((void *)key);
    ruby_xfree((void *)value);
    return ST_CONTINUE;
}

static void
delete_lifetime_table(struct traceobj_arg *arg)
{
    int i;
    if (arg->lifetime_table) {
	for (i=0; i<T_MASK; i++) {
	    free(arg->lifetime_table[i]);
	}
	free(arg->lifetime_table);
	arg->lifetime_table = NULL;
    }
}

static void
clear_traceobj_arg(void)
{
    struct traceobj_arg * arg = get_traceobj_arg();

    st_foreach(arg->aggregate_table, free_key_values_i, 0);
    st_clear(arg->aggregate_table);
    st_foreach(arg->object_table, free_values_i, 0);
    st_clear(arg->object_table);
    st_foreach(arg->str_table, free_keys_i, 0);
    st_clear(arg->str_table);
    arg->freed_allocation_info = NULL;
    delete_lifetime_table(arg);
}

static struct allocation_info *
create_allocation_info(void)
{
    return (struct allocation_info *)ruby_xmalloc(sizeof(struct allocation_info));
}

static void
free_allocation_info(struct traceobj_arg *arg, struct allocation_info *info)
{
    delete_unique_str(arg->str_table, info->path);
    ruby_xfree(info);
}

static void
newobj_i(VALUE tpval, void *data)
{
    struct traceobj_arg *arg = (struct traceobj_arg *)data;
    struct allocation_info *info;
    rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval);
    VALUE obj = rb_tracearg_object(tparg);
    VALUE path = rb_tracearg_path(tparg);
    VALUE line = rb_tracearg_lineno(tparg);
    VALUE klass = Qnil;
    switch(BUILTIN_TYPE(obj)) {
        case T_NODE:
        case T_IMEMO:
            break;
        default:
            klass = RBASIC_CLASS(obj);
    }
    const char *path_cstr = RTEST(path) ? make_unique_str(arg->str_table, RSTRING_PTR(path), RSTRING_LEN(path)) : NULL;

    if (st_lookup(arg->object_table, (st_data_t)obj, (st_data_t *)&info)) {
	if (info->living) {
	    /* do nothing. there is possibility to keep living if FREEOBJ events while suppressing tracing */
	}
	/* reuse info */
	delete_unique_str(arg->str_table, info->path);
    }
    else {
	info = create_allocation_info();
    }

    info->next = NULL;
    info->flags = RBASIC(obj)->flags;
    info->living = 1;
    info->memsize = 0;
    info->klass = (RTEST(klass) && !RB_TYPE_P(obj, T_NODE)) ? rb_class_real(klass) : Qnil;
    info->generation = rb_gc_count();

    info->path = path_cstr;
    info->line = NUM2INT(line);

    st_insert(arg->object_table, (st_data_t)obj, (st_data_t)info);

    arg->allocated_count_table[BUILTIN_TYPE(obj)]++;
}

/* file, line, type, klass */
#define MAX_KEY_SIZE 4

static void
aggregate_each_info(struct traceobj_arg *arg, struct allocation_info *info, size_t gc_count)
{
    st_data_t key, val;
    struct memcmp_key_data key_data;
    size_t *val_buff;
    size_t age = (int)(gc_count - info->generation);
    int i = 0;

    if (arg->keys & KEY_PATH) {
	key_data.data[i++] = (st_data_t)info->path;
    }
    if (arg->keys & KEY_LINE) {
	key_data.data[i++] = (st_data_t)info->line;
    }
    if (arg->keys & KEY_TYPE) {
	key_data.data[i++] = (st_data_t)(info->flags & T_MASK);
    }
    if (arg->keys & KEY_CLASS) {
	key_data.data[i++] = info->klass;
    }
    key_data.n = i;
    key = (st_data_t)&key_data;

    if (st_lookup(arg->aggregate_table, key, &val) == 0) {
	struct memcmp_key_data *key_buff = ruby_xmalloc(sizeof(struct memcmp_key_data));
	key_buff->n = key_data.n;

	for (i=0; i<key_data.n; i++) {
	    key_buff->data[i] = key_data.data[i];
	}
	key = (st_data_t)key_buff;

	/* count, old count, total age, max age, min age */
	val_buff = ALLOC_N(size_t, 6);
	val_buff[0] = val_buff[1] = val_buff[2] = 0;
	val_buff[3] = val_buff[4] = age;
	val_buff[5] = 0;

	if (arg->keys & KEY_PATH) keep_unique_str(arg->str_table, info->path);

	st_insert(arg->aggregate_table, (st_data_t)key_buff, (st_data_t)val_buff);
    }
    else {
	val_buff = (size_t *)val;
    }

    val_buff[0] += 1;
#ifdef FL_PROMOTED
    if (info->flags & FL_PROMOTED) val_buff[1] += 1;
#elif defined(FL_PROMOTED0) && defined(FL_PROMOTED1)
    if (info->flags & FL_PROMOTED0 &&
	info->flags & FL_PROMOTED1) val_buff[1] += 1;
#endif
    val_buff[2] += age;
    if (val_buff[3] > age) val_buff[3] = age; /* min */
    if (val_buff[4] < age) val_buff[4] = age; /* max */
    val_buff[5] += info->memsize;
}

static void
aggregate_freed_info(void *data)
{
    size_t gc_count = rb_gc_count();
    struct traceobj_arg *arg = (struct traceobj_arg *)data;
    struct allocation_info *info = arg->freed_allocation_info;

    arg->freed_allocation_info = NULL;

    if (arg->running) {
	while (info) {
	    struct allocation_info *next_info = info->next;
	    aggregate_each_info(arg, info, gc_count);
	    free_allocation_info(arg, info);
	    info = next_info;
	}
    }
}

static void
move_to_freed_list(struct traceobj_arg *arg, VALUE obj, struct allocation_info *info)
{
    if (arg->freed_allocation_info == NULL) {
	rb_postponed_job_register_one(0, aggregate_freed_info, arg);
    }

    info->next = arg->freed_allocation_info;
    arg->freed_allocation_info = info;
    st_delete(arg->object_table, (st_data_t *)&obj, (st_data_t *)&info);
}

static void
add_lifetime_table(size_t **lines, int type, struct allocation_info *info)
{
    size_t age = rb_gc_count() - info->generation;
    size_t *line = lines[type];
    size_t len, i;

    if (line == NULL) {
	len = age + 1;
	line = lines[type] = calloc(1 + len, sizeof(size_t));
	assert(line != NULL);
	line[0] = len;
    }
    else {
	len = line[0];

	if (len < age + 1) {
	    size_t old_len = len;
	    len = age + 1;
	    line = lines[type] = realloc(line, sizeof(size_t) * (1 + len));

	    assert(line != NULL);

	    for (i=old_len; i<len; i++) {
		line[i+1] = 0;
	    }

	    line[0] = len;
	}
    }

    line[1 + age]++;
}

static void
freeobj_i(VALUE tpval, void *data)
{
    struct traceobj_arg *arg = (struct traceobj_arg *)data;
    rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval);
    VALUE obj = rb_tracearg_object(tparg);
    struct allocation_info *info;

    if (st_lookup(arg->object_table, (st_data_t)obj, (st_data_t *)&info)) {

	info->flags = RBASIC(obj)->flags;
	info->memsize = rb_obj_memsize_of(obj);

	move_to_freed_list(arg, obj, info);

	if (arg->lifetime_table) {
	    add_lifetime_table(arg->lifetime_table, BUILTIN_TYPE(obj), info);
	}
    }

    arg->freed_count_table[BUILTIN_TYPE(obj)]++;
}

static void
check_tracer_running(void)
{
    struct traceobj_arg * arg = get_traceobj_arg();

    if (!arg->running) {
	rb_raise(rb_eRuntimeError, "not started yet");
    }
}

static void
enable_newobj_hook(void)
{
    VALUE newobj_hook;

    check_tracer_running();

    if (!rb_ivar_defined(rb_mAllocationTracer, rb_intern("newobj_hook"))) {
	rb_raise(rb_eRuntimeError, "not started.");
    }
    newobj_hook = rb_ivar_get(rb_mAllocationTracer, rb_intern("newobj_hook"));
    if (rb_tracepoint_enabled_p(newobj_hook)) {
	rb_raise(rb_eRuntimeError, "newobj hooks is already enabled.");
    }

    rb_tracepoint_enable(newobj_hook);
}

static void
disable_newobj_hook(void)
{
    VALUE newobj_hook;

    check_tracer_running();

    if ((!rb_ivar_defined(rb_mAllocationTracer, rb_intern("newobj_hook"))) || ((newobj_hook = rb_ivar_get(rb_mAllocationTracer, rb_intern("newobj_hook"))) == Qnil)) {
	rb_raise(rb_eRuntimeError, "not started.");
    }
    if (rb_tracepoint_enabled_p(newobj_hook) == Qfalse) {
	rb_raise(rb_eRuntimeError, "newobj hooks is already disabled.");
    }

    rb_tracepoint_disable(newobj_hook);
}

static void
start_alloc_hooks(VALUE mod)
{
    VALUE newobj_hook, freeobj_hook;
    struct traceobj_arg *arg = get_traceobj_arg();

    if (!rb_ivar_defined(rb_mAllocationTracer, rb_intern("newobj_hook"))) {
	rb_ivar_set(rb_mAllocationTracer, rb_intern("newobj_hook"), newobj_hook = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_NEWOBJ, newobj_i, arg));
	rb_ivar_set(rb_mAllocationTracer, rb_intern("freeobj_hook"), freeobj_hook = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_FREEOBJ, freeobj_i, arg));
    }
    else {
	newobj_hook = rb_ivar_get(rb_mAllocationTracer, rb_intern("newobj_hook"));
	freeobj_hook = rb_ivar_get(rb_mAllocationTracer, rb_intern("freeobj_hook"));
    }

    rb_tracepoint_enable(newobj_hook);
    rb_tracepoint_enable(freeobj_hook);
}

static VALUE
stop_alloc_hooks(VALUE self)
{
    struct traceobj_arg * arg = get_traceobj_arg();
    check_tracer_running();

    {
	VALUE newobj_hook = rb_ivar_get(rb_mAllocationTracer, rb_intern("newobj_hook"));
	VALUE freeobj_hook = rb_ivar_get(rb_mAllocationTracer, rb_intern("freeobj_hook"));
	rb_tracepoint_disable(newobj_hook);
	rb_tracepoint_disable(freeobj_hook);

	clear_traceobj_arg();

	arg->running = 0;
    }

    return Qnil;
}

static VALUE
type_sym(int type)
{
    static VALUE syms[T_MASK] = {0};

    if (syms[0] == 0) {
	int i;
	for (i=0; i<T_MASK; i++) {
	    switch (i) {
#define TYPE_NAME(t) case (t): syms[i] = ID2SYM(rb_intern(#t)); break;
		TYPE_NAME(T_NONE);
		TYPE_NAME(T_OBJECT);
		TYPE_NAME(T_CLASS);
		TYPE_NAME(T_MODULE);
		TYPE_NAME(T_FLOAT);
		TYPE_NAME(T_STRING);
		TYPE_NAME(T_REGEXP);
		TYPE_NAME(T_ARRAY);
		TYPE_NAME(T_HASH);
		TYPE_NAME(T_STRUCT);
		TYPE_NAME(T_BIGNUM);
		TYPE_NAME(T_FILE);
		TYPE_NAME(T_MATCH);
		TYPE_NAME(T_COMPLEX);
		TYPE_NAME(T_RATIONAL);
		TYPE_NAME(T_NIL);
		TYPE_NAME(T_TRUE);
		TYPE_NAME(T_FALSE);
		TYPE_NAME(T_SYMBOL);
		TYPE_NAME(T_FIXNUM);
		TYPE_NAME(T_UNDEF);
#ifdef T_IMEMO /* introduced from Rub 2.3 */
		TYPE_NAME(T_IMEMO);
#endif
		TYPE_NAME(T_NODE);
		TYPE_NAME(T_ICLASS);
		TYPE_NAME(T_ZOMBIE);
		TYPE_NAME(T_DATA);
	      default:
		syms[i] = ID2SYM(rb_intern("unknown"));
		break;
#undef TYPE_NAME
	    }
	}
    }

    return syms[type];
}

struct arg_and_result {
    int update;
    struct traceobj_arg *arg;
    VALUE result;
};

static int
aggregate_result_i(st_data_t key, st_data_t val, void *data)
{
    struct arg_and_result *aar = (struct arg_and_result *)data;
    struct traceobj_arg *arg = aar->arg;
    VALUE result = aar->result;
    size_t *val_buff = (size_t *)val;
    struct memcmp_key_data *key_buff = (struct memcmp_key_data *)key;
    VALUE v, oldv, k = rb_ary_new();
    int i = 0;

    i = 0;
    if (arg->keys & KEY_PATH) {
	const char *path = (const char *)key_buff->data[i++];
	if (path) {
	    rb_ary_push(k, rb_str_new2(path));
	}
	else {
	    rb_ary_push(k, Qnil);
	}
    }
    if (arg->keys & KEY_LINE) {
	rb_ary_push(k, INT2FIX((int)key_buff->data[i++]));
    }
    if (arg->keys & KEY_TYPE) {
	int sym_index = key_buff->data[i++];
	rb_ary_push(k, type_sym(sym_index));
    }
    if (arg->keys & KEY_CLASS) {
	VALUE klass = key_buff->data[i++];
	if (RTEST(klass) && BUILTIN_TYPE(klass) == T_CLASS) {
	    klass = rb_class_real(klass);
	    rb_ary_push(k, klass);
	    /* TODO: actually, it is dangerous code because klass can be sweeped */
	    /*       So that class specifier is hidden feature                   */
	}
	else {
	    rb_ary_push(k, Qnil);
	}
    }

#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MAX(a, b) ((a) > (b) ? (a) : (b))

    if (aar->update && (oldv = rb_hash_aref(result, k)) != Qnil) {
	v = rb_ary_new3(6,
			INT2FIX(val_buff[0] + (size_t)FIX2INT(RARRAY_AREF(oldv, 0))), /* count */
			INT2FIX(val_buff[1] + (size_t)FIX2INT(RARRAY_AREF(oldv, 1))), /* old count */
			INT2FIX(val_buff[2] + (size_t)FIX2INT(RARRAY_AREF(oldv, 2))), /* total_age */
			INT2FIX(MIN(val_buff[3], (size_t)FIX2INT(RARRAY_AREF(oldv, 3)))), /* min age */
			INT2FIX(MAX(val_buff[4], (size_t)FIX2INT(RARRAY_AREF(oldv, 4)))), /* max age */
			INT2FIX(val_buff[5] + (size_t)FIX2INT(RARRAY_AREF(oldv, 5)))); /* memsize_of */
    }
    else {
	v = rb_ary_new3(6,
			INT2FIX(val_buff[0]), INT2FIX(val_buff[1]),
			INT2FIX(val_buff[2]), INT2FIX(val_buff[3]),
			INT2FIX(val_buff[4]), INT2FIX(val_buff[5]));
    }

    rb_hash_aset(result, k, v);

    return ST_CONTINUE;
}

static int
aggregate_live_object_i(st_data_t key, st_data_t val, void *data)
{
    VALUE obj = (VALUE)key;
    struct allocation_info *info = (struct allocation_info *)val;
    size_t gc_count = rb_gc_count();
    struct traceobj_arg *arg = (struct traceobj_arg *)data;

    if (BUILTIN_TYPE(obj) == (info->flags & T_MASK)) {
	VALUE klass = RBASIC_CLASS(obj);
	info->flags = RBASIC(obj)->flags;
	info->klass = (RTEST(klass) && !RB_TYPE_P(obj, T_NODE)) ? rb_class_real(klass) : Qnil;
    }

    aggregate_each_info(arg, info, gc_count);

    return ST_CONTINUE;
}

static int
lifetime_table_for_live_objects_i(st_data_t key, st_data_t val, st_data_t data)
{
    struct allocation_info *info = (struct allocation_info *)val;
    VALUE h = (VALUE)data;
    int type = info->flags & T_MASK;
    VALUE sym = type_sym(type);
    size_t age = rb_gc_count() - info->generation;
    VALUE line;
    size_t count, i;

    if ((line = rb_hash_aref(h, sym)) == Qnil) {
	line = rb_ary_new();
	rb_hash_aset(h, sym, line);
    }

    for (i=RARRAY_LEN(line); i<age+1; i++) {
	rb_ary_push(line, INT2FIX(0));
    }

    count = NUM2SIZET(RARRAY_AREF(line, age));
    RARRAY_ASET(line, age, SIZET2NUM(count + 1));

    return ST_CONTINUE;
}

static VALUE
aggregate_result(struct traceobj_arg *arg)
{
    struct arg_and_result aar;
    aar.result = rb_hash_new();
    aar.arg = arg;

    while (arg->freed_allocation_info) {
	aggregate_freed_info(arg);
    }

    /* collect from recent-freed objects */
    aar.update = 0;
    st_foreach(arg->aggregate_table, aggregate_result_i, (st_data_t)&aar);

    {
	st_table *dead_object_aggregate_table = arg->aggregate_table;

	/* make live object aggregate table */
	arg->aggregate_table = st_init_table(&memcmp_hash_type);
	st_foreach(arg->object_table, aggregate_live_object_i, (st_data_t)arg);

	/* aggregate table -> Ruby hash */
	aar.update = 1;
	st_foreach(arg->aggregate_table, aggregate_result_i, (st_data_t)&aar);

	/* remove live object aggregate table */
	st_foreach(arg->aggregate_table, free_key_values_i, 0);
	st_free_table(arg->aggregate_table);

	arg->aggregate_table = dead_object_aggregate_table;
    }

    /* lifetime table */
    if (arg->lifetime_table) {
	VALUE h = rb_hash_new();
	int i;

	rb_ivar_set(rb_mAllocationTracer, rb_intern("lifetime_table"), h);

	for (i=0; i<T_MASK; i++) {
	    size_t *line = arg->lifetime_table[i];

	    if (line) {
		size_t len = line[0], j;
		VALUE ary = rb_ary_new();
		VALUE sym = type_sym(i);

		rb_hash_aset(h, sym, ary);

		for (j=0; j<len; j++) {
		    rb_ary_push(ary, SIZET2NUM(line[j+1]));
		}
	    }
	}

	st_foreach(arg->object_table, lifetime_table_for_live_objects_i, (st_data_t)h);
    }

    return aar.result;
}

/*
 *
 *  call-seq:
 *     ObjectSpace::AllocationTracer.result  -> hash
 *
 *  Returns the current allocated results
 *
 *  If you need to know the results of allocation tracing
 *  without pausing or stopping tracing you can use this method.
 *
 *  Example:
 *
 *    require 'allocation_tracer'
 *
 *    ObjectSpace::AllocationTracer.trace do
 *      3.times do |i|
 *        a = "#{i}"
 *        puts ObjectSpace::AllocationTracer.result
 *      end
 *    end
 *
 *    # => {["scratch.rb", 5]=>[2, 0, 0, 0, 0, 0]}
 *    # => {["scratch.rb", 5]=>[4, 0, 0, 0, 0, 0], ["scratch.rb", 6]=>[16, 0, 0, 0, 0, 0]}
 *    # => {["scratch.rb", 5]=>[6, 0, 0, 0, 0, 0], ["scratch.rb", 6]=>[38, 0, 0, 0, 0, 0]}
 *
 */
static VALUE
allocation_tracer_result(VALUE self)
{
    VALUE result;
    struct traceobj_arg *arg = get_traceobj_arg();

    disable_newobj_hook();
    result = aggregate_result(arg);
    enable_newobj_hook();
    return result;
}

/*
 *
 *  call-seq:
 *     ObjectSpace::AllocationTracer.clear  -> NilClass
 *
 *  Clears the current allocated results
 *
 *  If you need to clear the results of allocation tracing
 *  without stopping tracing you can use this method.
 *
 */
static VALUE
allocation_tracer_clear(VALUE self)
{
    clear_traceobj_arg();
    return Qnil;
}

/*! Used in allocation_tracer_trace
*   to ensure that a result is returned.
*/
static VALUE
allocation_tracer_trace_i(VALUE self)
{
    rb_yield(Qnil);
    return allocation_tracer_result(self);
}

/*
 *
 *  call-seq:
 *     ObjectSpace::AllocationTracer.trace { |a, b| block } -> array
 *     ObjectSpace::AllocationTracer.start                  -> NilClass
 *
 *  Traces object allocations inside of the block
 *
 *  Objects created inside of the block will be tracked by the tracer.
 *  If the method is called without a block, the tracer will start
 *  and continue until ObjectSpace::AllocationTracer.stop is called.
 *
 *  Output can be customized with ObjectSpace::AllocationTracer.setup.
 *
 *  Example:
 *
 *     pp ObjectSpace::AllocationTracer.trace{
 *       50_000.times{|i|
 *         i.to_s
 *         i.to_s
 *         i.to_s
 *       }
 *     }
 *
 *     # => {["test.rb", 6]=>[50000, 0, 47440, 0, 1, 0],
 *           ["test.rb", 7]=>[50000, 4, 47452, 0, 6, 0],
 *           ["test.rb", 8]=>[50000, 7, 47456, 0, 6, 0]}
 *
 */
static VALUE
allocation_tracer_trace(VALUE self)
{
    struct traceobj_arg * arg = get_traceobj_arg();

    if (arg->running) {
	rb_raise(rb_eRuntimeError, "can't run recursivly");
    }
    else {
	arg->running = 1;
	if (arg->keys == 0) arg->keys = KEY_PATH | KEY_LINE;
	start_alloc_hooks(rb_mAllocationTracer);

	if (rb_block_given_p()) {
	    return rb_ensure(allocation_tracer_trace_i, self, stop_alloc_hooks, Qnil);
	}
    }

    return Qnil;
}

/*
 *
 *  call-seq:
 *     ObjectSpace::AllocationTracer.stop                  -> array
 *
 *  Stops allocation tracing if currently running
 *
 *  When allocation tracing is started via ObjectSpace::AllocationTracer.start
 *  it will continue until this method is called.
 *
 *  Example:
 *    pp ObjectSpace::AllocationTracer.stop
 *
 *    # => {["test.rb", 6]=>[50000, 0, 47440, 0, 1, 0],
 *          ["test.rb", 7]=>[50000, 4, 47452, 0, 6, 0],
 *          ["test.rb", 8]=>[50000, 7, 47456, 0, 6, 0]}
 *
 */
static VALUE
allocation_tracer_stop(VALUE self)
{
    VALUE result;

    disable_newobj_hook();
    result = aggregate_result(get_traceobj_arg());
    stop_alloc_hooks(self);
    return result;
}

/*
 *
 *  call-seq:
 *     ObjectSpace::AllocationTracer.pause                  -> NilClass
 *
 *  Pauses allocation tracing
 *
 *  Use in conjunction with ObjectSpace::AllocationTracer.start and
 *  ObjectSpace::AllocationTracer.stop.
 *
 */
static VALUE
allocation_tracer_pause(VALUE self)
{
    disable_newobj_hook();
    return Qnil;
}

/*
 *
 *  call-seq:
 *     ObjectSpace::AllocationTracer.resume                  -> NilClass
 *
 *  Resumes allocation tracing if previously paused
 *
 *  See ObjectSpace::AllocationTracer.pause to pause allocation tracing.
 *
 */
static VALUE
allocation_tracer_resume(VALUE self)
{
    enable_newobj_hook();
    return Qnil;
}

/*
 *
 *  call-seq:
 *     ObjectSpace::AllocationTracer.setup([symbol])       -> NilClass
 *
 *  Change the format that results will be returned.
 *
 *  Takes an array of symbols containing the order you would like the output
 *  to be returned. Valid options:
 *
 *    - :path
 *    - :line
 *    - :type
 *    - :class
 *
 *  Example:
 *
 *     ObjectSpace::AllocationTracer.setup(%i{path line type})
 *
 *     result = ObjectSpace::AllocationTracer.trace do
 *       50_000.times{|i|
 *         a = [i.to_s]
 *         b = {i.to_s => nil}
 *         c = (i.to_s .. i.to_s)
 *       }
 *     end
 *
 *     pp result
 *
 *     # => {["test.rb", 8, :T_STRING]=>[50000, 15, 49165, 0, 16, 0],
 *           ["test.rb", 8, :T_ARRAY]=>[50000, 12, 49134, 0, 16, 0],
 *           ["test.rb", 9, :T_STRING]=>[100000, 27, 98263, 0, 16, 0],
 *           ["test.rb", 9, :T_HASH]=>[50000, 16, 49147, 0, 16, 8998848],
 *           ["test.rb", 10, :T_STRING]=>[100000, 36, 98322, 0, 16, 0],
 *           ["test.rb", 10, :T_STRUCT]=>[50000, 16, 49147, 0, 16, 0]}
 *
 */
static VALUE
allocation_tracer_setup(int argc, VALUE *argv, VALUE self)
{
    struct traceobj_arg * arg = get_traceobj_arg();

    if (arg->running) {
	rb_raise(rb_eRuntimeError, "can't change configuration during running");
    }
    else {
	if (argc >= 1) {
	    int i;
	    VALUE ary = rb_check_array_type(argv[0]);

	    arg->keys = 0;

	    for (i=0; i<(int)RARRAY_LEN(ary); i++) {
		if (RARRAY_AREF(ary, i) == ID2SYM(rb_intern("path"))) arg->keys |= KEY_PATH;
		else if (RARRAY_AREF(ary, i) == ID2SYM(rb_intern("line"))) arg->keys |= KEY_LINE;
		else if (RARRAY_AREF(ary, i) == ID2SYM(rb_intern("type"))) arg->keys |= KEY_TYPE;
		else if (RARRAY_AREF(ary, i) == ID2SYM(rb_intern("class"))) arg->keys |= KEY_CLASS;
		else {
		    rb_raise(rb_eArgError, "not supported key type");
		}
	    }
	}
    }

    if (argc == 0) {
	arg->keys = KEY_PATH | KEY_LINE;
    }
    return Qnil;
}

/*
 *
 *  call-seq:
 *     ObjectSpace::AllocationTracer.header   -> array
 *
 *  Return headers
 *
 *  Example:
 *
 *     puts ObjectSpace::AllocationTracer.header
 *     => [:path, :line, :count, :old_count, :total_age, :min_age, :max_age, :total_memsize]
 *
 */
VALUE
allocation_tracer_header(VALUE self)
{
    VALUE ary = rb_ary_new();
    struct traceobj_arg * arg = get_traceobj_arg();

    if (arg->keys & KEY_PATH) rb_ary_push(ary, ID2SYM(rb_intern("path")));
    if (arg->keys & KEY_LINE) rb_ary_push(ary, ID2SYM(rb_intern("line")));
    if (arg->keys & KEY_TYPE) rb_ary_push(ary, ID2SYM(rb_intern("type")));
    if (arg->keys & KEY_CLASS) rb_ary_push(ary, ID2SYM(rb_intern("class")));

    if (arg->vals & VAL_COUNT) rb_ary_push(ary, ID2SYM(rb_intern("count")));
    if (arg->vals & VAL_OLDCOUNT) rb_ary_push(ary, ID2SYM(rb_intern("old_count")));
    if (arg->vals & VAL_TOTAL_AGE) rb_ary_push(ary, ID2SYM(rb_intern("total_age")));
    if (arg->vals & VAL_MIN_AGE) rb_ary_push(ary, ID2SYM(rb_intern("min_age")));
    if (arg->vals & VAL_MAX_AGE) rb_ary_push(ary, ID2SYM(rb_intern("max_age")));
    if (arg->vals & VAL_MEMSIZE) rb_ary_push(ary, ID2SYM(rb_intern("total_memsize")));
    return ary;
}

/*
 *
 *  call-seq:
 *     ObjectSpace::AllocationTracer.lifetime_table_setup(true)   -> NilClass
 *
 * Enables tracing for the generation of objects
 *
 * See ObjectSpace::AllocationTracer.lifetime_table for an example.
 */
static VALUE
allocation_tracer_lifetime_table_setup(VALUE self, VALUE set)
{
    struct traceobj_arg * arg = get_traceobj_arg();

    if (arg->running) {
	rb_raise(rb_eRuntimeError, "can't change configuration during running");
    }

    if (RTEST(set)) {
	if (arg->lifetime_table == NULL) {
	    arg->lifetime_table = (size_t **)calloc(T_MASK, sizeof(size_t **));
	}
    }
    else {
	delete_lifetime_table(arg);
    }

    return Qnil;
}

/*
 *
 *  call-seq:
 *     ObjectSpace::AllocationTracer.lifetime_table   -> hash
 *
 * Returns generations for objects
 *
 * Count is for both living (retained) and dead (freed) objects.
 *
 * The key is the type of objects, for example `T_OBJECT` for Ruby objects
 * or `T_STRING` for Ruby strings.
 *
 * The value is an array containing a count of the objects, the index is
 * the generation.
 *
 * Example:
 *
 *     require 'pp'
 *     require 'allocation_tracer'
 *
 *     ObjectSpace::AllocationTracer.lifetime_table_setup true
 *     result = ObjectSpace::AllocationTracer.trace do
 *       100000.times{
 *         Object.new
 *         ''
 *       }
 *     end
 *     pp ObjectSpace::AllocationTracer.lifetime_table
 *     # => {:T_OBJECT=>[3434, 96563, 0, 0, 1, 0, 0, 2],
 *           :T_STRING=>[3435, 96556, 2, 1, 1, 1, 1, 1, 2]}
 */
static VALUE
allocation_tracer_lifetime_table(VALUE self)
{
    VALUE result = rb_ivar_get(rb_mAllocationTracer, rb_intern("lifetime_table"));
    rb_ivar_set(rb_mAllocationTracer, rb_intern("lifetime_table"), Qnil);
    return result;
}


/*
 *
 *  call-seq:
 *     ObjectSpace::AllocationTracer.allocated_count_table   -> hash
 *
 * Returns allocation count totals for Ruby object types
 *
 * Returns a hash showing the number of each type of object that has been allocated.
 *
 * Example:
 *
 *     require 'allocation_tracer'
 *
 *     ObjectSpace::AllocationTracer.trace do
 *       1000.times {
 *         ["foo", {}]
 *       }
 *     end
 *     p allocated: ObjectSpace::AllocationTracer.allocated_count_table
 *     {: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}}
 *
 *     p freed: ObjectSpace::AllocationTracer.freed_count_table
 *     {: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}}
 */
static VALUE
allocation_tracer_allocated_count_table(VALUE self)
{
    struct traceobj_arg * arg = get_traceobj_arg();
    VALUE h = rb_hash_new();
    int i;

    for (i=0; i<T_MASK; i++) {
	rb_hash_aset(h, type_sym(i), SIZET2NUM(arg->allocated_count_table[i]));
    }

    return h;
}

/*
 *
 *  call-seq:
 *     ObjectSpace::AllocationTracer.freed_count_table   -> hash
 *
 * Returns freed count totals for Ruby object types
 *
 * Returns a hash showing the number of each type of object that has been freed.
 *
 * See ObjectSpace::AllocationTracer.allocated_count_table for example usage
 *
 */
static VALUE
allocation_tracer_freed_count_table(VALUE self)
{
    struct traceobj_arg * arg = get_traceobj_arg();
    VALUE h = rb_hash_new();
    int i;

    for (i=0; i<T_MASK; i++) {
	rb_hash_aset(h, type_sym(i), SIZET2NUM(arg->freed_count_table[i]));
    }

    return h;
}

void
Init_allocation_tracer(void)
{
    VALUE rb_mObjSpace = rb_const_get(rb_cObject, rb_intern("ObjectSpace"));
    VALUE mod = rb_mAllocationTracer = rb_define_module_under(rb_mObjSpace, "AllocationTracer");

    /* allocation tracer methods */
    rb_define_module_function(mod, "trace", allocation_tracer_trace, 0);
    rb_define_module_function(mod, "start", allocation_tracer_trace, 0);
    rb_define_module_function(mod, "stop", allocation_tracer_stop, 0);
    rb_define_module_function(mod, "pause", allocation_tracer_pause, 0);
    rb_define_module_function(mod, "resume", allocation_tracer_resume, 0);

    rb_define_module_function(mod, "result", allocation_tracer_result, 0);
    rb_define_module_function(mod, "clear", allocation_tracer_clear, 0);
    rb_define_module_function(mod, "setup", allocation_tracer_setup, -1);
    rb_define_module_function(mod, "header", allocation_tracer_header, 0);

    rb_define_module_function(mod, "lifetime_table_setup", allocation_tracer_lifetime_table_setup, 1);
    rb_define_module_function(mod, "lifetime_table", allocation_tracer_lifetime_table, 0);

    rb_define_module_function(mod, "allocated_count_table", allocation_tracer_allocated_count_table, 0);
    rb_define_module_function(mod, "freed_count_table", allocation_tracer_freed_count_table, 0);
}


================================================
FILE: ext/allocation_tracer/extconf.rb
================================================
require 'mkmf'
create_makefile('allocation_tracer/allocation_tracer')


================================================
FILE: lib/allocation_tracer/trace.rb
================================================
require 'allocation_tracer'

# ObjectSpace::AllocationTracer.setup(%i{path line})
ObjectSpace::AllocationTracer.trace

at_exit{
  results = ObjectSpace::AllocationTracer.stop

  puts ObjectSpace::AllocationTracer.header.join("\t")
  results.sort_by{|k, v| k}.each{|k, v|
    puts (k+v).join("\t")
  }
}




================================================
FILE: lib/allocation_tracer/version.rb
================================================
module ObjectSpace::AllocationTracer
  VERSION = "0.6.3"
end


================================================
FILE: lib/allocation_tracer.rb
================================================
require "allocation_tracer/version"
require "allocation_tracer/allocation_tracer"

module ObjectSpace::AllocationTracer

  def self.output_lifetime_table table
    out = (file = ENV['RUBY_ALLOCATION_TRACER_LIFETIME_OUT']) ? open(File.expand_path(file), 'w') : STDOUT
    max_lines = table.inject(0){|r, (_type, lines)| r < lines.size ? lines.size : r}
    out.puts "type\t" + (0...max_lines).to_a.join("\t")
    table.each{|type, line|
      out.puts "#{type}\t#{line.join("\t")}"
    }
  end

  def self.collect_lifetime_table
    ObjectSpace::AllocationTracer.lifetime_table_setup true

    if block_given?
      begin
        ObjectSpace::AllocationTracer.trace do
          yield
        end
        result = ObjectSpace::AllocationTracer.lifetime_table
        output_lifetime_table(result)
      ensure
        ObjectSpace::AllocationTracer.lifetime_table_setup false
      end
    else
      ObjectSpace::AllocationTracer.trace
    end
  end

  def self.collect_lifetime_table_stop
    ObjectSpace::AllocationTracer.stop
    result = ObjectSpace::AllocationTracer.lifetime_table
    ObjectSpace::AllocationTracer.lifetime_table_setup false
    output_lifetime_table(result)
    result
  end
end


================================================
FILE: lib/rack/allocation_tracer.rb
================================================
#
# Rack middleware
#

require 'allocation_tracer'

module Rack
  module AllocationTracerMiddleware
    def self.new *args
      TotalTracer.new *args
    end

    class Tracer
      def initialize app
        @app = app
        @sort_order = (0..7).to_a
      end

      def allocation_trace_page result, env
        if /\As=(\d+)/ =~ env["QUERY_STRING"]
          top = $1.to_i
          @sort_order.unshift top if @sort_order.delete top
        end

        table = result.map{|(file, line, klass), (count, oldcount, total_age, min_age, max_age, memsize)|
          ["#{Rack::Utils.escape_html(file)}:#{'%04d' % line}",
            Rack::Utils.escape_html(klass ? klass.name : '<internal>'),
            count, oldcount, total_age / Float(count), min_age, max_age, memsize]
        }

        begin
          table = table.sort_by{|vs|
            ary = @sort_order.map{|i| Numeric === vs[i] ? -vs[i] : vs[i]}
          }
        rescue
          ts = []
          table.each{|*cols|
            cols.each.with_index{|c, i|
              h = (ts[i] ||= Hash.new(0))
              h[c.class] += 1
            }
          }
          return "<pre>Sorting error\n" + Rack::Utils.escape_html(h.inspect) + "</pre>"
        end

        headers = %w(path class count old_count average_age min_age max_age memsize).map.with_index{|e, i|
          "<th><a href='./?s=#{i}'>#{e}</a></th>"
        }.join("\n")
        header = "<tr>#{headers}</tr>"
        body = table.map{|cols|
          "<tr>" + cols.map{|c| "<td>#{c}</td>"}.join("\n") + "</tr>"
        }.join("\n")
        "<table>#{header}#{body}</table>"
      end

      def count_table_page count_table
        text = count_table.map{|k, v| "%-10s\t%8d" % [k, v]}.join("\n")
        "<pre>#{text}</pre>"
      end

      def allocated_count_table_page
        count_table_page ObjectSpace::AllocationTracer.allocated_count_table
      end

      def freed_count_table_page
        count_table_page ObjectSpace::AllocationTracer.freed_count_table
      end

      def lifetime_table_page
        table = []
        max_age = 0
        ObjectSpace::AllocationTracer.lifetime_table.each{|type, ages|
          max_age = [max_age, ages.size - 1].max
          table << [type, *ages]
        }
        headers = ['type', *(0..max_age)].map{|e| "<th>#{e}</th>"}.join("\n")
        body =  table.map{|cols|
          "<tr>" + cols.map{|c| "<td>#{c}</td>"}.join("\n") + "</tr>"
        }.join("\n")
        "<table border='1'><tr>#{headers}</tr>\n#{body}</table>"
      end

      def call env
        if /\A\/allocation_tracer(?:\/|$)/ =~ env["PATH_INFO"]
          result = ObjectSpace::AllocationTracer.result
          ObjectSpace::AllocationTracer.pause

          begin
            html = case env["PATH_INFO"]
                   when /lifetime_table/
                     lifetime_table_page
                   when /allocated_count_table/
                     allocated_count_table_page
                   when /freed_count_table/
                     freed_count_table_page
                   else
                     allocation_trace_page result, env
                   end
            #
            [200, {"Content-Type" => "text/html"}, [html]]
          ensure
            ObjectSpace::AllocationTracer.resume
          end
        else
          @app.call env
        end
      end
    end

    class TotalTracer < Tracer
      def initialize *args
        super
        ObjectSpace::AllocationTracer.setup %i(path line class)
        ObjectSpace::AllocationTracer.lifetime_table_setup true
        ObjectSpace::AllocationTracer.start
      end
    end
  end
end


================================================
FILE: spec/allocation_tracer_spec.rb
================================================
require 'spec_helper'
require 'tmpdir'
require 'fileutils'

describe ObjectSpace::AllocationTracer do
  describe 'ObjectSpace::AllocationTracer.trace' do
    it 'should includes allocation information' do
      line = __LINE__ + 2
      result = ObjectSpace::AllocationTracer.trace do
        Object.new
      end

      expect(result.length).to be >= 1
      expect(result[[__FILE__, line]]).to eq [1, 0, 0, 0, 0, 0]
    end

    it 'should run twice' do
      line = __LINE__ + 2
      result = ObjectSpace::AllocationTracer.trace do
        Object.new
      end
      #GC.start
      # p result
      expect(result.length).to be >= 1
      expect(result[[__FILE__, line]]).to eq [1, 0, 0, 0, 0, 0]
    end

    it 'should analyze many objects' do
      line = __LINE__ + 3
      result = ObjectSpace::AllocationTracer.trace do
        50_000.times{|i|
          i.to_s
          i.to_s
          i.to_s
        }
      end

      GC.start
      #pp result

      expect(result[[__FILE__, line + 0]][0]).to be >= 50_000
      expect(result[[__FILE__, line + 1]][0]).to be >= 50_000
      expect(result[[__FILE__, line + 2]][0]).to be >= 50_000
    end

    it 'should count old objects' do
      a = nil
      line = __LINE__ + 2
      result = ObjectSpace::AllocationTracer.trace do
        a = 'x' # it will be old object
        32.times{GC.start}
      end

      expect(result.length).to be >= 1
      _, old_count, * = *result[[__FILE__, line]]
      expect(old_count).to be == 1
    end

    it 'should acquire allocated memsize' do
      line = __LINE__ + 2
      result = ObjectSpace::AllocationTracer.trace do
        _ = 'x' * 1234 # danger
        GC.start
      end

      expect(result.length).to be >= 1
      size = result[[__FILE__, line]][-1]
      expect(size).to be > 1234 if size > 0
    end

    it 'can be paused and resumed' do
      line = __LINE__ + 2
      result = ObjectSpace::AllocationTracer.trace do
        Object.new
        ObjectSpace::AllocationTracer.pause
        Object.new # ignore tracing
        ObjectSpace::AllocationTracer.resume
        Object.new
      end

      expect(result.length).to be 2
      expect(result[[__FILE__, line    ]]).to eq [1, 0, 0, 0, 0, 0]
      expect(result[[__FILE__, line + 4]]).to eq [1, 0, 0, 0, 0, 0]
    end

    it 'can be get middle result' do
      middle_result = nil
      line = __LINE__ + 2
      result = ObjectSpace::AllocationTracer.trace do
        Object.new
        middle_result = ObjectSpace::AllocationTracer.result
        Object.new
      end

      expect(result.length).to be 2
      expect(result[[__FILE__, line    ]]).to eq [1, 0, 0, 0, 0, 0]
      expect(result[[__FILE__, line + 2]]).to eq [1, 0, 0, 0, 0, 0]

      expect(middle_result.length).to be 1
      expect(middle_result[[__FILE__, line    ]]).to eq [1, 0, 0, 0, 0, 0]
    end

    describe 'stop when not started yet' do
      it 'should raise RuntimeError' do
        expect do
          ObjectSpace::AllocationTracer.stop
        end.to raise_error(RuntimeError)
      end
    end

    describe 'pause when not started yet' do
      it 'should raise RuntimeError' do
        expect do
          ObjectSpace::AllocationTracer.pause
        end.to raise_error(RuntimeError)
      end
    end

    describe 'resume when not started yet' do
      it 'should raise RuntimeError' do
        expect do
          ObjectSpace::AllocationTracer.resume
        end.to raise_error(RuntimeError)
      end
    end

    describe 'when starting recursively' do
      it 'should raise RuntimeError' do
        expect do
          ObjectSpace::AllocationTracer.trace{
            ObjectSpace::AllocationTracer.trace{}
          }
        end.to raise_error(RuntimeError)
      end
    end

    describe 'with different setup' do
      it 'should work with type' do
        line = __LINE__ + 3
        ObjectSpace::AllocationTracer.setup(%i(path line type))
        result = ObjectSpace::AllocationTracer.trace do
          _a = [Object.new]
          _b = {Object.new => 'foo'}
        end

        expect(result.length).to be 5
        expect(result[[__FILE__, line, :T_OBJECT]]).to eq [1, 0, 0, 0, 0, 0]
        expect(result[[__FILE__, line, :T_ARRAY]]).to eq [1, 0, 0, 0, 0, 0]
        # expect(result[[__FILE__, line + 1, :T_HASH]]).to eq [1, 0, 0, 0, 0]
        expect(result[[__FILE__, line + 1, :T_OBJECT]]).to eq [1, 0, 0, 0, 0, 0]
        expect(result[[__FILE__, line + 1, :T_STRING]]).to eq [1, 0, 0, 0, 0, 0]
      end

      it 'should work with class' do
        line = __LINE__ + 3
        ObjectSpace::AllocationTracer.setup(%i(path line class))
        result = ObjectSpace::AllocationTracer.trace do
          _a = [Object.new]
          _b = {Object.new => 'foo'}
        end

        expect(result.length).to be 5
        expect(result[[__FILE__, line, Object]]).to eq [1, 0, 0, 0, 0, 0]
        expect(result[[__FILE__, line, Array]]).to eq [1, 0, 0, 0, 0, 0]
        # expect(result[[__FILE__, line + 1, Hash]]).to eq [1, 0, 0, 0, 0, 0]
        expect(result[[__FILE__, line + 1, Object]]).to eq [1, 0, 0, 0, 0, 0]
        expect(result[[__FILE__, line + 1, String]]).to eq [1, 0, 0, 0, 0, 0]
      end

      it 'should have correct headers' do
        ObjectSpace::AllocationTracer.setup(%i(path line))
        expect(ObjectSpace::AllocationTracer.header).to eq [:path, :line, :count, :old_count, :total_age, :min_age, :max_age, :total_memsize]
        ObjectSpace::AllocationTracer.setup(%i(path line class))
        expect(ObjectSpace::AllocationTracer.header).to eq [:path, :line, :class, :count, :old_count, :total_age, :min_age, :max_age, :total_memsize]
        ObjectSpace::AllocationTracer.setup(%i(path line type class))
        expect(ObjectSpace::AllocationTracer.header).to eq [:path, :line, :type, :class, :count, :old_count, :total_age, :min_age, :max_age, :total_memsize]
      end

      it 'should set default setup' do
        ObjectSpace::AllocationTracer.setup()
        expect(ObjectSpace::AllocationTracer.header).to eq [:path, :line, :count, :old_count, :total_age, :min_age, :max_age, :total_memsize]
      end
    end
  end

  describe 'collect lifetime_table' do
    before do
      ObjectSpace::AllocationTracer.lifetime_table_setup true
    end

    after do
      ObjectSpace::AllocationTracer.lifetime_table_setup false
    end

    it 'should make lifetime table' do
      ObjectSpace::AllocationTracer.trace do
        100000.times{
          Object.new
          ''
        }
      end
      table = ObjectSpace::AllocationTracer.lifetime_table

      expect(table[:T_OBJECT].inject(&:+)).to be >= 10_000
      expect(table[:T_STRING].inject(&:+)).to be >= 10_000
      expect(table[:T_NONE]).to be nil
    end

    it 'should return nil when ObjectSpace::AllocationTracer.lifetime_table_setup is false' do
      ObjectSpace::AllocationTracer.lifetime_table_setup false

      ObjectSpace::AllocationTracer.trace do
        100000.times{
          Object.new
          ''
        }
      end

      table = ObjectSpace::AllocationTracer.lifetime_table

      expect(table).to be nil
    end

    it 'should return nil getting it twice' do
      ObjectSpace::AllocationTracer.trace do
        100000.times{
          Object.new
          ''
        }
      end

      table = ObjectSpace::AllocationTracer.lifetime_table
      table = ObjectSpace::AllocationTracer.lifetime_table

      expect(table).to be nil
    end
  end

  describe 'ObjectSpace::AllocationTracer.collect_lifetime_table' do
    it 'should collect lifetime table' do
      table = ObjectSpace::AllocationTracer.collect_lifetime_table do
        100000.times{
          Object.new
          ''
        }
      end

      expect(table[:T_OBJECT].inject(&:+)).to be >= 10_000
      expect(table[:T_STRING].inject(&:+)).to be >= 10_000
      expect(table[:T_NONE]).to be nil
    end
  end

  describe 'ObjectSpace::AllocationTracer.allocated_count_table' do
    it 'should return a Hash object' do
      h = ObjectSpace::AllocationTracer.allocated_count_table
      expect(h[:T_NONE]).to be 0
    end
  end

  describe 'ObjectSpace::AllocationTracer.freed_count_table' do
    it 'should return a Hash object' do
      h = ObjectSpace::AllocationTracer.freed_count_table
      expect(h[:T_NONE]).to be 0
    end
  end
end


================================================
FILE: spec/spec_helper.rb
================================================
require 'rubygems'
require 'bundler/setup'
require 'allocation_tracer'

RSpec.configure do |config|
  config.mock_framework = :rspec
end
Download .txt
gitextract_gpj107i2/

├── .gitignore
├── .travis.yml
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── allocation_tracer.gemspec
├── ext/
│   └── allocation_tracer/
│       ├── allocation_tracer.c
│       └── extconf.rb
├── lib/
│   ├── allocation_tracer/
│   │   ├── trace.rb
│   │   └── version.rb
│   ├── allocation_tracer.rb
│   └── rack/
│       └── allocation_tracer.rb
└── spec/
    ├── allocation_tracer_spec.rb
    └── spec_helper.rb
Download .txt
SYMBOL INDEX (67 symbols across 4 files)

FILE: ext/allocation_tracer/allocation_tracer.c
  type traceobj_arg (line 16) | struct traceobj_arg {
  type allocation_info (line 31) | struct allocation_info {
  function delete_unique_str (line 99) | static void
  type memcmp_key_data (line 117) | struct memcmp_key_data {
  function memcmp_hash_compare (line 122) | static int
  function st_index_t (line 130) | static st_index_t
  type st_hash_type (line 137) | struct st_hash_type
  type traceobj_arg (line 141) | struct traceobj_arg
  type traceobj_arg (line 143) | struct traceobj_arg
  function free_keys_i (line 161) | static int
  function free_values_i (line 168) | static int
  function free_key_values_i (line 175) | static int
  function delete_lifetime_table (line 183) | static void
  function clear_traceobj_arg (line 196) | static void
  type allocation_info (line 211) | struct allocation_info
  type allocation_info (line 214) | struct allocation_info
  type allocation_info (line 214) | struct allocation_info
  function free_allocation_info (line 217) | static void
  function newobj_i (line 224) | static void
  function aggregate_each_info (line 272) | static void
  function aggregate_freed_info (line 332) | static void
  function move_to_freed_list (line 351) | static void
  function add_lifetime_table (line 363) | static void
  function freeobj_i (line 397) | static void
  function check_tracer_running (line 420) | static void
  function enable_newobj_hook (line 430) | static void
  function disable_newobj_hook (line 448) | static void
  function start_alloc_hooks (line 465) | static void
  function VALUE (line 484) | static VALUE
  function VALUE (line 504) | static VALUE
  type arg_and_result (line 553) | struct arg_and_result {
  function aggregate_result_i (line 559) | static int
  function aggregate_live_object_i (line 624) | static int
  function lifetime_table_for_live_objects_i (line 643) | static int
  function VALUE (line 669) | static VALUE
  function VALUE (line 757) | static VALUE
  function VALUE (line 780) | static VALUE
  function VALUE (line 790) | static VALUE
  function VALUE (line 826) | static VALUE
  function VALUE (line 865) | static VALUE
  function VALUE (line 887) | static VALUE
  function VALUE (line 904) | static VALUE
  function VALUE (line 948) | static VALUE
  function VALUE (line 994) | VALUE
  function VALUE (line 1023) | static VALUE
  function VALUE (line 1075) | static VALUE
  function VALUE (line 1108) | static VALUE
  function VALUE (line 1134) | static VALUE
  function Init_allocation_tracer (line 1148) | void

FILE: lib/allocation_tracer.rb
  type ObjectSpace::AllocationTracer (line 4) | module ObjectSpace::AllocationTracer
    function output_lifetime_table (line 6) | def self.output_lifetime_table table
    function collect_lifetime_table (line 15) | def self.collect_lifetime_table
    function collect_lifetime_table_stop (line 33) | def self.collect_lifetime_table_stop

FILE: lib/allocation_tracer/version.rb
  type ObjectSpace::AllocationTracer (line 1) | module ObjectSpace::AllocationTracer

FILE: lib/rack/allocation_tracer.rb
  type Rack (line 7) | module Rack
    type AllocationTracerMiddleware (line 8) | module AllocationTracerMiddleware
      function new (line 9) | def self.new *args
      class Tracer (line 13) | class Tracer
        method initialize (line 14) | def initialize app
        method allocation_trace_page (line 19) | def allocation_trace_page result, env
        method count_table_page (line 56) | def count_table_page count_table
        method allocated_count_table_page (line 61) | def allocated_count_table_page
        method freed_count_table_page (line 65) | def freed_count_table_page
        method lifetime_table_page (line 69) | def lifetime_table_page
        method call (line 83) | def call env
      class TotalTracer (line 110) | class TotalTracer < Tracer
        method initialize (line 111) | def initialize *args
Condensed preview — 15 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (61K chars).
[
  {
    "path": ".gitignore",
    "chars": 174,
    "preview": "*.gem\n*.rbc\n.bundle\n.config\n.yardoc\nGemfile.lock\nInstalledFiles\n_yardoc\ncoverage\ndoc/\nlib/bundler/man\npkg\nrdoc\nspec/repo"
  },
  {
    "path": ".travis.yml",
    "chars": 96,
    "preview": "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",
    "chars": 102,
    "preview": "source 'https://rubygems.org'\n\n# Specify your gem's dependencies in allocation_tracer.gemspec\ngemspec\n"
  },
  {
    "path": "LICENSE.txt",
    "chars": 1070,
    "preview": "Copyright (c) 2014 Koichi Sasada\n\nMIT License\n\nPermission is hereby granted, free of charge, to any person obtaining\na c"
  },
  {
    "path": "README.md",
    "chars": 7364,
    "preview": "# ObjectSpace::AllocationTracer\n\nThis module allows to trace object allocation.\n\nThis feature is similar to https://gith"
  },
  {
    "path": "Rakefile",
    "chars": 377,
    "preview": "require \"bundler/gem_tasks\"\nrequire \"rake/extensiontask\"\nrequire 'rspec/core/rake_task'\n\nspec = Gem::Specification.load("
  },
  {
    "path": "allocation_tracer.gemspec",
    "chars": 1174,
    "preview": "# coding: utf-8\nlib = File.expand_path('../lib', __FILE__)\n$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)\nrequi"
  },
  {
    "path": "ext/allocation_tracer/allocation_tracer.c",
    "chars": 32223,
    "preview": "/*\r\n * allocation tracer: adds GC::Tracer::start_allocation_tracing\r\n *\r\n * By Koichi Sasada\r\n * created at Thu Apr 17 0"
  },
  {
    "path": "ext/allocation_tracer/extconf.rb",
    "chars": 72,
    "preview": "require 'mkmf'\r\ncreate_makefile('allocation_tracer/allocation_tracer')\r\n"
  },
  {
    "path": "lib/allocation_tracer/trace.rb",
    "chars": 320,
    "preview": "require 'allocation_tracer'\r\n\r\n# ObjectSpace::AllocationTracer.setup(%i{path line})\r\nObjectSpace::AllocationTracer.trace"
  },
  {
    "path": "lib/allocation_tracer/version.rb",
    "chars": 61,
    "preview": "module ObjectSpace::AllocationTracer\n  VERSION = \"0.6.3\"\nend\n"
  },
  {
    "path": "lib/allocation_tracer.rb",
    "chars": 1202,
    "preview": "require \"allocation_tracer/version\"\nrequire \"allocation_tracer/allocation_tracer\"\n\nmodule ObjectSpace::AllocationTracer\n"
  },
  {
    "path": "lib/rack/allocation_tracer.rb",
    "chars": 3625,
    "preview": "#\n# Rack middleware\n#\n\nrequire 'allocation_tracer'\n\nmodule Rack\n  module AllocationTracerMiddleware\n    def self.new *ar"
  },
  {
    "path": "spec/allocation_tracer_spec.rb",
    "chars": 8561,
    "preview": "require 'spec_helper'\r\nrequire 'tmpdir'\r\nrequire 'fileutils'\r\n\r\ndescribe ObjectSpace::AllocationTracer do\r\n  describe 'O"
  },
  {
    "path": "spec/spec_helper.rb",
    "chars": 142,
    "preview": "require 'rubygems'\r\nrequire 'bundler/setup'\r\nrequire 'allocation_tracer'\r\n\r\nRSpec.configure do |config|\r\n  config.mock_f"
  }
]

About this extraction

This page contains the full source code of the ko1/allocation_tracer GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 15 files (55.2 KB), approximately 16.5k tokens, and a symbol index with 67 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!