Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
arvidn committed Dec 7, 2013
1 parent 5dd1903 commit de33ec8
Show file tree
Hide file tree
Showing 8 changed files with 1,074 additions and 4 deletions.
13 changes: 13 additions & 0 deletions Jamfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
lib access_profiler
: # sources
access_profiler.cpp
: # requirements
: # default build
<link>static
: # usage-requirements
<include>.
<include>bfd
;

exe test : test.cpp : <library>access_profiler/<link>static ;

674 changes: 674 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

4 changes: 0 additions & 4 deletions README.md

This file was deleted.

23 changes: 23 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
access_profiler
===============

``access_profiler`` is a heavy-weight class field access
profiler, implemented as C++ library.

to use this profiler, include ``"access_profiler.hpp"``
and make the types you want to instrument derive from
``access_profiler::instrument_type< your-type >`` (i.e. you
need to specify your type as the template argument).

in you ``Jamfile``, add a dependency to the access_profiler
library.

When you terminate your program, the access counters
for your types fields will be printed to "access_profile.out"
in current working directory. This file lists all instrumented
types and the access counters for offsets into those types.

To combine this information with the debug information for
more user-friendly output, use the struct_layout tool and
use the profile as input.

265 changes: 265 additions & 0 deletions access_profiler.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
/*
access_profiler instruments types and collects usage counts for class fields
Copyright (C) 2013 Arvid Norberg
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include <signal.h>
#include <stdlib.h> // for malloc/free
#include <sys/mman.h> // for mprotect
#include <cxxabi.h>

#include <unordered_map>
#include <cstdint>
#include <cinttypes> // for PRId64
#include <typeinfo>
#include <vector>
#include <mutex>

struct type_t
{
// the type this page belongs to
std::uint16_t type_idx;

void* base_ptr;
size_t size;
};

std::mutex pagemutex;

// maps page addresses to the type that page belongs to
std::unordered_map<uintptr_t, type_t> pagemap;

std::mutex typemutex;
std::unordered_map<std::type_info const*, std::uint8_t> typemap;
int next_typeidx = 0;

struct access_t
{
int size;
std::atomic<std::uint64_t>* counter;
};

enum { max_types = 300 };
std::vector<access_t> access;

// when the sigsegv handler unprotects a page and enters
// single step mode, it saves the page that was unprotected
// here, and protects it again in the single step handler
// (i.e. one instruction later)
pthread_key_t last_page = 0;

void segv_handler(int, siginfo_t *info, void* uap)
{
ucontext_t* uc = (ucontext_t*)uap;

uintptr_t page_addr = uintptr_t(info->si_addr);
page_addr &= ~4095;

void* base_ptr;
int type_size;
int type_idx;
{
std::lock_guard<std::mutex> l(pagemutex);
auto i = pagemap.find(page_addr);
if (i == pagemap.end()) return;
type_t& t = i->second;
base_ptr = t.base_ptr;
type_size = t.size;
type_idx = t.type_idx;
}

// the allocator itself apparently touches some memory
// of unrelated pages when freeing
// we must still allow the access, but don't record it
if ((char*)info->si_addr < (char*)base_ptr + type_size)
{
// record the access
int offset = uintptr_t(info->si_addr) - uintptr_t(base_ptr);
++access[type_idx].counter[offset];
}

// unprotect the page
mprotect((void*)page_addr, 4096, PROT_READ | PROT_WRITE);

// save the page address so the single step handler
// know which page to protect again
pthread_setspecific(last_page, (void*)page_addr);

// turn on single step
#ifdef __LP64__
uc->uc_mcontext->__ss.__rflags |= 0x100;
#else
uc->uc_mcontext->__ss.__eflags |= 0x100;
#endif
}

void single_step_handler(int signo, siginfo_t* info, void* uap)
{
ucontext_t* uc = (ucontext_t*)uap;

// turn off single step
#ifdef __LP64__
uc->uc_mcontext->__ss.__rflags &= ~0x100;
#else
uc->uc_mcontext->__ss.__eflags &= ~0x100;
#endif

void* buf = pthread_getspecific(last_page);

// and protect the page again
mprotect(buf, 4096, PROT_NONE);
}

namespace access_profiler
{

namespace detail
{

int type_idx(std::type_info const* ti, int size)
{
std::lock_guard<std::mutex> l(typemutex);
auto i = typemap.find(ti);
if (i != typemap.end()) return i->second;
if (next_typeidx >= max_types) return -1;

access[next_typeidx].counter = new std::atomic<std::uint64_t>[size];
for (int i = 0; i < size; ++i) access[next_typeidx].counter[i] = 0;
access[next_typeidx].size = size;
typemap[ti] = next_typeidx++;
return next_typeidx-1;
}

void* allocate_instrumented_type(int size, int type)
{
if (type == -1) return malloc(size);

// round up to even page
int page_size = (size + 4095) & ~4095;

void* buf = valloc(page_size);
if (buf == nullptr) return buf;

{
std::lock_guard<std::mutex> l(pagemutex);
for (uintptr_t ptr = (uintptr_t)buf; ptr < uintptr_t(buf) + page_size;
ptr += 4096)
{
type_t& t = pagemap[ptr];
t.base_ptr = buf;
t.size = size;
t.type_idx = type;
}
}

mprotect(buf, page_size, PROT_NONE);

return buf;
}

void free_instrumented_type(void* buf, int size, int type)
{
if (type == -1)
{
free(buf);
return;
}

// round up to even page
size = (size + 4095) & ~4095;

mprotect(buf, size, PROT_READ | PROT_WRITE);

{
std::lock_guard<std::mutex> l(pagemutex);
for (uintptr_t ptr = (uintptr_t)buf; ptr < uintptr_t(buf) + size;
ptr += 4096)
{
pagemap.erase(ptr);
}
}

free(buf);
}

} // detail

void init_instrumentation()
{
pthread_key_create(&last_page, NULL);

struct sigaction sa;
sa.sa_sigaction = &segv_handler;
sa.sa_flags = SA_SIGINFO | SA_RESTART;
sigemptyset(&sa.sa_mask);
sigaction(SIGSEGV, &sa, NULL);
sigaction(SIGBUS, &sa, NULL);

sa.sa_sigaction = &single_step_handler;
sigaction(SIGTRAP, &sa, NULL);

access.resize(500);
}

void print_report()
{
FILE* out = fopen("access_profile.out", "w+");
if (out == nullptr)
{
fprintf(stderr, "failed to open \"access_profile.out\" "
"for writing: (%d) %s\n"
, errno, strerror(errno));
return;
}

std::lock_guard<std::mutex> l(typemutex);
for (auto i : typemap)
{
int dummy;
fprintf(out, "\n%s\n", abi::__cxa_demangle(i.first->name(), NULL, NULL, &dummy));

int type_idx = i.second;
access_t& a = access[type_idx];

// don't show fields with less than 1% of max
// hit rate
for (int i = 0; i < a.size; ++i)
{
std::uint64_t cnt = a.counter[i].load();
if (cnt == 0) continue;
fprintf(out, " %4d: %" PRId64 "\n", i, cnt);
}
}

fclose(out);
}

static struct static_init_t
{
static_init_t()
{
init_instrumentation();
}

~static_init_t()
{
print_report();
}

} initializer;

} // access_profiler

53 changes: 53 additions & 0 deletions access_profiler.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
access_profiler instruments types and collects usage counts for class fields
Copyright (C) 2013 Arvid Norberg
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef ACCESS_PROFILER_HPP
#define ACCESS_PROFILER_HPP

#include <typeinfo>

namespace access_profiler
{

namespace detail
{
void* allocate_instrumented_type(int size, int type);
void free_instrumented_type(void* buf, int size, int type);
int type_idx(std::type_info const* ti, int size);
}

template <class T>
struct instrument_type
{
void* operator new(size_t s)
{
return detail::allocate_instrumented_type(s
, detail::type_idx(&typeid(T), sizeof(T)));
}

void operator delete(void* buf)
{
detail::free_instrumented_type(buf, sizeof(T)
, detail::type_idx(&typeid(T), sizeof(T)));
}
};

}

#endif

Empty file added project-root.jam
Empty file.
Loading

0 comments on commit de33ec8

Please sign in to comment.