/**
 * @file opgprof.cpp
 * Implement opgprof utility
 *
 * @remark Copyright 2003 OProfile authors
 * @remark Read the file COPYING
 *
 * @author John Levon
 * @author Philippe Elie
 */

#include <iostream>
#include <cstdio>

#include "op_header.h"
#include "profile.h"
#include "op_libiberty.h"
#include "op_fileio.h"
#include "string_filter.h"
#include "profile_container.h"
#include "arrange_profiles.h"
#include "image_errors.h"
#include "opgprof_options.h"
#include "cverb.h"
#include "op_file.h"

using namespace std;

extern profile_classes classes;

namespace {

#define GMON_VERSION 1
#define GMON_TAG_TIME_HIST 0
#define GMON_TAG_CG_ARC 1

struct gmon_hdr {
	char cookie[4];
	u32 version;
	u32 spare[3];
};


void op_write_vma(FILE * fp, op_bfd const & abfd, bfd_vma vma)
{
	// bfd vma write size is a per binary property not a bfd
	// configuration property
	switch (abfd.bfd_arch_bits_per_address()) {
		case 32:
			op_write_u32(fp, vma);
			break;
		case 64:
			op_write_u64(fp, vma);
			break;
		default:
			cerr << "oprofile: unknown vma size for this binary\n";
			exit(EXIT_FAILURE);
	}
}


void get_vma_range(bfd_vma & min, bfd_vma & max,
                   profile_container const & samples)
{
	min = bfd_vma(-1);
	max = 0;

	sample_container::samples_iterator it  = samples.begin();
	sample_container::samples_iterator end = samples.end();
	for (; it != end ; ++it) {
		if (it->second.vma < min)
			min = it->second.vma;
		if (it->second.vma > max)
			max = it->second.vma;
	}

	if (min == bfd_vma(-1))
		min = 0;
	// we must return a range [min, max) not a range [min, max]
	if (max != 0)
		max += 1;
}


/**
 * @param abfd  bfd object
 * @param samples_files  profile container to act on
 * @param gap  a power of 2
 *
 * return true if all sample in samples_files are at least aligned on gap. This
 * function is used to get at runtime the right size of gprof bin size
 * reducing gmon.out on arch with fixed size instruction length
 *
 */
bool aligned_samples(profile_container const & samples, int gap)
{
	sample_container::samples_iterator it  = samples.begin();
	sample_container::samples_iterator end = samples.end();
	for (; it != end ; ++it) {
		if (it->second.vma % gap)
			return false;
	}

	return true;
}


void output_cg(FILE * fp, op_bfd const & abfd, profile_t const & cg_db)
{
	opd_header const & header = cg_db.get_header();
	bfd_vma offset = 0;
	if (header.is_kernel)
		offset = abfd.get_start_offset(0);
	else
		offset = header.anon_start;
 
	profile_t::iterator_pair p_it = cg_db.samples_range();
	for (; p_it.first != p_it.second; ++p_it.first) {
		bfd_vma from = p_it.first.vma() >> 32;
		bfd_vma to = p_it.first.vma() & 0xffffffff;

		op_write_u8(fp, GMON_TAG_CG_ARC);
		op_write_vma(fp, abfd, abfd.offset_to_pc(from + offset));
		op_write_vma(fp, abfd, abfd.offset_to_pc(to + offset));
		u32 count = p_it.first.count();
		if (count != p_it.first.count()) {
			count = (u32)-1;
			cerr << "Warning: capping sample count by "
			     << p_it.first.count() - count << endl;
		}
		op_write_u32(fp, p_it.first.count());
	}
}


void output_gprof(op_bfd const & abfd, profile_container const & samples,
                  profile_t const & cg_db, string const & gmon_filename)
{
	static gmon_hdr hdr = { { 'g', 'm', 'o', 'n' }, GMON_VERSION, {0, 0, 0 } };

	bfd_vma low_pc;
	bfd_vma high_pc;

	/* FIXME worth to try more multiplier ?	*/
	int multiplier = 2;
	if (aligned_samples(samples, 4))
		multiplier = 8;

	cverb << vdebug << "opgrof multiplier: " << multiplier << endl;

	get_vma_range(low_pc, high_pc, samples);

	cverb << vdebug << "low_pc: " << hex << low_pc << " " << "high_pc: "
	      << high_pc << dec << endl;

	// round-down low_pc to ensure bin number is correct in the inner loop
	low_pc = (low_pc / multiplier) * multiplier;
	// round-up high_pc to ensure a correct histsize calculus
	high_pc = ((high_pc + multiplier - 1) / multiplier) * multiplier;

	cverb << vdebug << "low_pc: " << hex << low_pc << " " << "high_pc: "
	      << high_pc << dec << endl;

	size_t histsize = (high_pc - low_pc) / multiplier;

	// FIXME: must we skip the flat profile write if histsize == 0 ?
	// (this can occur with callgraph w/o samples to the binary) but in
	// this case user must gprof --no-flat-profile which is a bit boring
	// and result *seems* weirds.

	FILE * fp = op_open_file(gmon_filename.c_str(), "w");

	op_write_file(fp, &hdr, sizeof(gmon_hdr));
	op_write_u8(fp, GMON_TAG_TIME_HIST);

	op_write_vma(fp, abfd, low_pc);
	op_write_vma(fp, abfd, high_pc);
	/* size of histogram */
	op_write_u32(fp, histsize);
	/* profiling rate */
	op_write_u32(fp, 1);
	op_write_file(fp, "samples\0\0\0\0\0\0\0\0", 15);
	/* abbreviation */
	op_write_u8(fp, '1');

	u16 * hist = (u16*)xcalloc(histsize, sizeof(u16));

	profile_container::symbol_choice choice;
	choice.threshold = options::threshold;
	symbol_collection symbols = samples.select_symbols(choice);

	symbol_collection::const_iterator sit = symbols.begin();
	symbol_collection::const_iterator send = symbols.end();

	for (; sit != send; ++sit) {
		sample_container::samples_iterator it  = samples.begin(*sit);
		sample_container::samples_iterator end = samples.end(*sit);
		for (; it != end ; ++it) {
			u32 pos = (it->second.vma - low_pc) / multiplier;
			count_type count = it->second.counts[0];

			if (pos >= histsize) {
				cerr << "Bogus histogram bin " << pos
				     << ", larger than " << pos << " !\n";
				continue;
			}
	
			if (hist[pos] + count > (u16)-1) {
				hist[pos] = (u16)-1;
				cerr <<	"Warning: capping sample count by "
				     << hist[pos] + count - ((u16)-1) << endl;
			} else {
				hist[pos] += (u16)count;
			}
		}
	}

	op_write_file(fp, hist, histsize * sizeof(u16));

	if (!cg_db.empty())
		output_cg(fp, abfd, cg_db);

	op_close_file(fp);

	free(hist);
}


void
load_samples(op_bfd const & abfd, list<profile_sample_files> const & files,
                  string const & image, profile_container & samples)
{
	list<profile_sample_files>::const_iterator it = files.begin();
	list<profile_sample_files>::const_iterator const end = files.end();

	for (; it != end; ++it) {
		// we can get call graph w/o any samples to the binary
		if (it->sample_filename.empty())
			continue;
			
		cverb << vsfile << "loading flat samples files : "
		      << it->sample_filename << endl;

		profile_t profile;

		profile.add_sample_file(it->sample_filename);
		profile.set_offset(abfd);

		check_mtime(abfd.get_filename(), profile.get_header());

		samples.add(profile, abfd, image, 0);
	}
}


void load_cg(profile_t & cg_db, list<profile_sample_files> const & files)
{
	list<profile_sample_files>::const_iterator it = files.begin();
	list<profile_sample_files>::const_iterator const end = files.end();

	/* the list of non cg files is a super set of the list of cg file
	 * (module always log a samples to non-cg files before logging
	 * call stack) so by using the list of non-cg file we are sure to get
	 * all existing cg files.
	 */
	for (; it != end; ++it) {
		list<string>::const_iterator cit;
		list<string>::const_iterator const cend = it->cg_files.end();
		for (cit = it->cg_files.begin(); cit != cend; ++cit) {
			// FIXME: do we need filtering ?
			/* We can't handle start_offset now but after splitting
			 * data in from/to eip. */
			cverb << vsfile << "loading cg samples file : " 
			      << *cit << endl;
			cg_db.add_sample_file(*cit);
		}
	}
}


int opgprof(options::spec const & spec)
{
	handle_options(spec);

	profile_container samples(false, true, classes.extra_found_images);

	bool ok = image_profile.error == image_ok;
	// FIXME: symbol_filter would be allowed through option
	op_bfd abfd(image_profile.image, string_filter(),
		    classes.extra_found_images, ok);
	if (!ok && image_profile.error == image_ok)
		image_profile.error = image_format_failure;

	if (image_profile.error != image_ok) {
		report_image_error(image_profile, true,
				   classes.extra_found_images);
		exit(EXIT_FAILURE);
	}

	profile_t cg_db;
	
	image_group_set const & groups = image_profile.groups[0];
	image_group_set::const_iterator it;
	for (it = groups.begin(); it != groups.end(); ++it) {
		load_samples(abfd, it->files, image_profile.image, samples);

		load_cg(cg_db, it->files);
	}

	output_gprof(abfd, samples, cg_db, options::gmon_filename);

	return 0;
}


} // anonymous namespace


int main(int argc, char const * argv[])
{
	return run_pp_tool(argc, argv, opgprof);
}