Callchain and inlining support

This commit is contained in:
Charlie Curtsinger 2014-07-30 14:12:46 -04:00
parent 184306ff4d
commit bba7864925
11 changed files with 242 additions and 88 deletions

View File

@ -6,6 +6,9 @@ TEST_ARGS =
include $(ROOT)/common.mk
CFLAGS += -O3
CXXFLAGS += -O3
bench:: debug/bin/producer_consumer
$(ROOT)/release/bin/causal --- debug/bin/producer_consumer $(ARGS)

View File

@ -4,10 +4,12 @@
#include <pthread.h>
#include <cassert>
#include <queue>
#include <causal.h>
enum {
Items = 100000,
Items = 1000000,
QueueSize = 10,
ProducerCount = 5,
ConsumerCount = 3
@ -15,8 +17,7 @@ enum {
int produced = 0;
int consumed = 0;
int queue_size;
int queue[QueueSize];
std::queue<int> queue;
pthread_mutex_t queue_lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t producer_condvar = PTHREAD_COND_INITIALIZER;
@ -26,11 +27,10 @@ pthread_cond_t main_condvar = PTHREAD_COND_INITIALIZER;
void* producer(void* arg) {
for(size_t n = 0; n < Items / ProducerCount; n++) {
pthread_mutex_lock(&queue_lock);
while(queue_size == QueueSize) {
while(queue.size() == QueueSize) {
pthread_cond_wait(&producer_condvar, &queue_lock);
}
queue[queue_size] = 123;
queue_size++;
queue.push(123);
produced++;
pthread_mutex_unlock(&queue_lock);
pthread_cond_signal(&consumer_condvar);
@ -44,16 +44,16 @@ void* producer(void* arg) {
void* consumer(void* arg) {
while(true) {
pthread_mutex_lock(&queue_lock);
while(queue_size == 0) {
while(queue.size() == 0) {
pthread_cond_wait(&consumer_condvar, &queue_lock);
}
queue_size--;
assert(queue[queue_size] == 123);
queue[queue_size] = 321;
int front = queue.front();
queue.pop();
assert(front == 123);
consumed++;
pthread_mutex_unlock(&queue_lock);
pthread_cond_signal(&producer_condvar);
//CAUSAL_PROGRESS;
CAUSAL_PROGRESS;
}
}

View File

@ -9,8 +9,8 @@ CFLAGS =
include $(ROOT)/common.mk
debug:: $(ROOT)/debug/lib/libcausal_support.a
debug/lib/libcausal.so:: $(ROOT)/debug/lib/libcausal_support.a
release:: $(ROOT)/release/lib/libcausal_support.a
release/lib/libcausal.so:: $(ROOT)/release/lib/libcausal_support.a
CXXFLAGS += --std=c++11 -fPIC

View File

@ -3,8 +3,8 @@
#include <cstdlib>
#include <iostream>
#include <set>
#include <string>
#include <vector>
#include <boost/program_options.hpp>
namespace causal {
@ -13,11 +13,10 @@ namespace causal {
boost::program_options::options_description desc("Causal options");
desc.add_options()
("help,h", "show this help message")
("exclude-main,e", "do not profile the main executable")
("include,i",
boost::program_options::value<std::vector<std::string>>()
->default_value(std::vector<std::string>(), ""),
"profile libraries with matching names")
"evaluate optimizations to files in the specified file/directory")
("fixed-line,l",
boost::program_options::value<std::string>()->default_value(""),
"profile with a fixed source line as the optimization candidate (<file>:<line>)")

View File

@ -27,10 +27,10 @@ enum {
class profiler {
public:
void include_file(const std::string& filename, uintptr_t load_address);
void register_counter(Counter* c);
void startup(const std::string& output_filename,
const std::vector<std::string>& source_progress_names,
std::vector<std::string> scope,
const std::string& fixed_line_name,
int fixed_speedup);
void shutdown();
@ -70,6 +70,9 @@ private:
/// Process all available samples and insert delays.
void process_samples(thread_state::ref& state);
/// Find the source line that contains a given sample, walking the callchain if necessary
std::shared_ptr<causal_support::line> find_containing_line(perf_event::record& sample);
/// Just insert delays
void add_delays(thread_state::ref& state);

View File

@ -56,45 +56,13 @@ int wrapped_main(int argc, char** argv, char** env) {
return 1;
}
// Get the specified file patterns
vector<string> file_patterns = args["include"].as<vector<string>>();
// If the main executable should NOT be excluded, add the program name to the file patterns set
if(!args.count("exclude-main")) {
file_patterns.push_back(argv[causal_argc + 1]);
}
// Walk through all the loaded executable images
for(const auto& file : causal_support::get_loaded_files()) {
const string& filename = file.first;
uintptr_t load_address = file.second;
// Exclude libcausal
if(filename.find("libcausal") == string::npos) {
// Check if the loaded file matches any of the specified patterns
bool matched = false;
for(const string& pat : file_patterns) {
if(filename.find(pat) != string::npos) {
matched = true;
break;
}
}
if(matched) {
INFO << "Processing file " << filename;
profiler::get_instance().include_file(filename, load_address);
} else {
WARNING << "Omitting file " << filename;
}
}
}
// Collect all the real function pointers for interposed functions
real::init();
// Start the profiler
profiler::get_instance().startup(args["output"].as<string>(),
args["progress"].as<vector<string>>(),
args["include"].as<vector<string>>(),
args["fixed-line"].as<string>(),
args["fixed-speedup"].as<int>());

View File

@ -2,6 +2,7 @@
#include <asm/unistd.h>
#include <execinfo.h>
#include <limits.h>
#include <poll.h>
#include <pthread.h>
@ -32,14 +33,9 @@ using namespace std;
void on_error(int, siginfo_t*, void*);
void samples_ready(int, siginfo_t*, void*);
void profiler::include_file(const string& filename, uintptr_t load_address) {
PREFER(_map.process_file(filename, load_address))
<< "Failed to locate debug version of " << filename;
}
void profiler::register_counter(Counter* c) {
_out->add_counter(c);
}
};
/**
* Set up the profiling environment and start the main profiler thread
@ -47,6 +43,7 @@ void profiler::register_counter(Counter* c) {
*/
void profiler::startup(const string& output_filename,
const vector<string>& source_progress_names,
vector<string> scope,
const string& fixed_line_name,
int fixed_speedup) {
@ -65,6 +62,20 @@ void profiler::startup(const string& output_filename,
real::sigaction(SIGSEGV, &sa, nullptr);
real::sigaction(SIGABRT, &sa, nullptr);
// If the file scope is empty, add the current working directory
if(scope.size() == 0) {
char cwd[PATH_MAX];
getcwd(cwd, PATH_MAX);
scope.push_back(string(cwd));
}
// Build the address -> source map
_map.build(scope);
for(const auto& f : _map.files()) {
INFO << "Including source file " << f.first;
}
// If a non-empty fixed line was provided, attempt to locate it
if(fixed_line_name != "") {
_fixed_line = _map.find_line(fixed_line_name);
@ -256,6 +267,27 @@ void profiler::end_sampling() {
state->sampler.close();
}
shared_ptr<line> profiler::find_containing_line(perf_event::record& sample) {
if(!sample.is_sample())
return shared_ptr<line>();
// Check if the sample occurred in known code
shared_ptr<line> l = _map.find_line(sample.get_ip());
if(l)
return l;
// Walk the callchain
for(uint64_t pc : sample.get_callchain()) {
l = _map.find_line(pc);
if(l) {
return l;
}
}
// No hits. Return null
return shared_ptr<line>();
}
void profiler::process_samples(thread_state::ref& state) {
// Stop sampling
state->sampler.stop();
@ -263,7 +295,7 @@ void profiler::process_samples(thread_state::ref& state) {
for(perf_event::record r : state->sampler) {
if(r.is_sample()) {
// Find the line that contains this sample
shared_ptr<line> l = _map.find_line(r.get_ip());
shared_ptr<line> l = find_containing_line(r);
// Load the selected line
line* current_line = _selected_line.load();

View File

@ -8,6 +8,12 @@
#include <memory>
#include <string>
#include <utility>
#include <vector>
namespace dwarf {
class die;
class line_table;
}
namespace causal_support {
class file;
@ -120,11 +126,8 @@ namespace causal_support {
inline const std::map<std::string, std::shared_ptr<file>>& files() const { return _files; }
inline const std::map<interval, std::shared_ptr<line>>& ranges() const { return _ranges; }
/**
* Locate a debug version of the specified file and add its line number information
* to the memory map.
*/
bool process_file(const std::string& name, uintptr_t load_address = 0);
/// Build a map from addresses to source lines with the provided source file scope
void build(const std::vector<std::string>& scope);
std::shared_ptr<line> find_line(const std::string& name);
std::shared_ptr<line> find_line(uintptr_t addr);
@ -141,15 +144,22 @@ namespace causal_support {
}
}
void add_range(std::string filename, size_t line_no, interval range);
/// Find a debug version of provided file and add all of its in-scope lines to the map
bool process_file(const std::string& name, uintptr_t load_address,
const std::vector<std::string>& scope);
/// Add entries for all inlined calls
void process_inlines(const dwarf::die& d,
const dwarf::line_table& table,
const std::vector<std::string>& scope,
uintptr_t load_address);
std::map<std::string, std::shared_ptr<file>> _files;
std::map<interval, std::shared_ptr<line>> _ranges;
};
/**
* Build a map of all loaded executables and libraries in the current process
*/
std::map<std::string, uintptr_t> get_loaded_files();
static std::ostream& operator<<(std::ostream& os, const interval& i) {
os << std::hex << "0x" << i.get_base() << "-0x" << i.get_limit() << std::dec;
return os;

View File

@ -29,6 +29,7 @@
using boost::is_any_of;
using boost::split;
using boost::filesystem::absolute;
using boost::filesystem::canonical;
using boost::filesystem::exists;
using boost::filesystem::path;
@ -182,7 +183,7 @@ namespace causal_support {
return f;
}
map<string, uintptr_t> get_loaded_files() {
map<string, uintptr_t> result;
@ -202,8 +203,135 @@ namespace causal_support {
return result;
}
void memory_map::build(const vector<string>& scope) {
for(const auto& f : get_loaded_files()) {
INFO << "Processing " << f.first;
if(!process_file(f.first, f.second, scope)) {
INFO << " Couldn't locate debug version of " << f.first;
}
}
}
bool in_scope(string file, const vector<string>& scope) {
for(const string& s : scope) {
if(path(file).normalize().string().find(s) == 0) {
return true;
}
}
return false;
}
dwarf::value find_attribute(const dwarf::die& d, dwarf::DW_AT attr) {
if(!d.valid())
return dwarf::value();
if(d.has(attr))
return d[attr];
if(d.has(dwarf::DW_AT::abstract_origin)) {
const dwarf::die child = d.resolve(dwarf::DW_AT::abstract_origin).as_reference();
dwarf::value v = find_attribute(child, attr);
if(v.valid())
return v;
}
if(d.has(dwarf::DW_AT::specification)) {
const dwarf::die child = d.resolve(dwarf::DW_AT::specification).as_reference();
dwarf::value v = find_attribute(child, attr);
if(v.valid())
return v;
}
return dwarf::value();
}
void memory_map::add_range(std::string filename, size_t line_no, interval range) {
shared_ptr<file> f = get_file(filename);
shared_ptr<line> l = f->get_line(line_no);
auto iter = _ranges.find(range);
if(iter != _ranges.end() && iter->second != l) {
WARNING << "Overlapping entries for lines "
<< f->get_name() << ":" << l->get_line() << " and "
<< iter->second->get_file()->get_name() << ":" << iter->second->get_line();
}
bool memory_map::process_file(const string& name, uintptr_t load_address) {
// Add the entry
_ranges.emplace(range, l);
}
void memory_map::process_inlines(const dwarf::die& d,
const dwarf::line_table& table,
const vector<string>& scope,
uintptr_t load_address) {
if(!d.valid())
return;
if(d.tag == dwarf::DW_TAG::inlined_subroutine) {
string name;
dwarf::value name_val = find_attribute(d, dwarf::DW_AT::name);
if(name_val.valid()) {
name = name_val.as_string();
}
string decl_file;
dwarf::value decl_file_val = find_attribute(d, dwarf::DW_AT::decl_file);
if(decl_file_val.valid()) {
decl_file = table.get_file(decl_file_val.as_uconstant())->path;
}
size_t decl_line = 0;
dwarf::value decl_line_val = find_attribute(d, dwarf::DW_AT::decl_line);
if(decl_line_val.valid())
decl_line = decl_line_val.as_uconstant();
string call_file;
if(d.has(dwarf::DW_AT::call_file)) {
call_file = table.get_file(d[dwarf::DW_AT::call_file].as_uconstant())->path;
}
size_t call_line = 0;
if(d.has(dwarf::DW_AT::call_line)) {
call_line = d[dwarf::DW_AT::call_line].as_uconstant();
}
// If the call location is in scope but the function is not, add an entry
if(decl_file.size() > 0 && call_file.size() > 0) {
if(!in_scope(decl_file, scope) && in_scope(call_file, scope)) {
// Does this inline have separate ranges?
dwarf::value ranges_val = find_attribute(d, dwarf::DW_AT::ranges);
if(ranges_val.valid()) {
// Add each range
for(auto r : ranges_val.as_rangelist()) {
add_range(call_file,
call_line,
interval(r.low, r.high) + load_address);
}
} else {
// Must just be one range. Add it
dwarf::value low_pc_val = find_attribute(d, dwarf::DW_AT::low_pc);
dwarf::value high_pc_val = find_attribute(d, dwarf::DW_AT::high_pc);
if(low_pc_val.valid() && high_pc_val.valid()) {
add_range(call_file,
call_line,
interval(low_pc_val.as_address(),
high_pc_val.as_address()) + load_address);
}
}
}
}
}
for(const auto& child : d) {
process_inlines(child, table, scope, load_address);
}
}
bool memory_map::process_file(const string& name, uintptr_t load_address,
const vector<string>& scope) {
elf::elf f = locate_debug_executable(name);
// If a debug version of the file could not be located, return false
@ -222,22 +350,10 @@ namespace causal_support {
// Walk through the line instructions in the DWARF line table
for(auto& line_info : unit.get_line_table()) {
// Insert an entry if this isn't the first line command in the sequence
if(prev_address != 0) {
// Get or create the file handle for this entry
shared_ptr<file> f = get_file(prev_filename);
// Get or create the line handle for this entry
shared_ptr<line> l = f->get_line(prev_line);
// Make the memory range that holds this line
interval range = interval(prev_address, line_info.address) + load_address;
auto iter = _ranges.find(range);
if(iter != _ranges.end() && iter->second != l) {
WARNING << "Overlapping entries for lines " << f->get_name() << ":" << l->get_line()
<< " and " << iter->second->get_file()->get_name() << ":" << iter->second->get_line();
}
// Add the entry
_ranges.emplace(range, l);
if(prev_address != 0 && in_scope(prev_filename, scope)) {
add_range(prev_filename,
prev_line,
interval(prev_address, line_info.address) + load_address);
}
if(line_info.end_sequence) {
@ -248,6 +364,8 @@ namespace causal_support {
prev_address = line_info.address;
}
}
process_inlines(unit.root(), unit.get_line_table(), scope, load_address);
}
return true;

View File

@ -9,5 +9,9 @@ include $(ROOT)/common.mk
CXXFLAGS += --std=c++11 -fPIC
debug:: $(ROOT)/lib/support/debug/lib/libcausal_support.a
release:: $(ROOT)/lib/support/release/lib/libcausal_support.a
test: debug
./inspect $(ROOT)/tests/kmeans/kmeans
./debug/bin/inspect $(ROOT)/benchmarks/producer_consumer/debug/bin/producer_consumer

View File

@ -1,10 +1,15 @@
#include <limits.h>
#include <unistd.h>
#include <iostream>
#include <vector>
#include "support.h"
#include "log.h"
using std::cerr;
using std::string;
using std::vector;
int main(int argc, char** argv) {
if(argc != 2) {
@ -13,15 +18,27 @@ int main(int argc, char** argv) {
}
causal_support::memory_map m;
REQUIRE(m.process_file(argv[1])) << "Couldn't find a debug version of " << argv[1];
vector<string> scope;
// If the file scope is empty, add the current working directory
char cwd[PATH_MAX];
getcwd(cwd, PATH_MAX);
scope.push_back(string(cwd));
m.build(scope);
size_t file_count = 0;
size_t line_count = 0;
for(const auto& f_info : m.files()) {
const string& filename = f_info.first;
file_count++;
for(const auto& l_info : f_info.second->lines()) {
line_count++;
size_t line_number = l_info.first;
INFO << l_info.second;
}
}
INFO << "Found " << line_count << " lines in " << file_count << " files.";
return 0;
}