C++程序  |  433行  |  10.26 KB

/**
 * @file opd_proc.c
 * Management of processes
 *
 * @remark Copyright 2002 OProfile authors
 * @remark Read the file COPYING
 *
 * @author John Levon
 * @author Philippe Elie
 */

#include "op_hw_config.h"
#include "opd_proc.h"
#include "opd_image.h"
#include "opd_mapping.h"
#include "opd_sample_files.h"
#include "opd_kernel.h"
#include "opd_24_stats.h"
#include "opd_printf.h"
#include "oprofiled.h"

#include "op_interface.h"
#include "op_libiberty.h"

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* size of process hash table */
#define OPD_MAX_PROC_HASH 1024

extern int cpu_number;

/* hash of process lists */
static struct list_head opd_procs[OPD_MAX_PROC_HASH];

/* statistics purpose */
static int nr_procs;


void opd_init_procs(void)
{
	int i;
	for (i = 0; i < OPD_MAX_PROC_HASH; i++)
		list_init(&opd_procs[i]);
}


int opd_get_nr_procs(void)
{
	return nr_procs;
}


/**
 * proc_hash - hash pid value
 * @param tid  pid value to hash
 *
 */
inline static uint proc_hash(pid_t tid)
{
	/* FIXME: hash tgid too! */
	return ((tid >> 4) ^ (tid)) % OPD_MAX_PROC_HASH;
}


struct opd_proc * opd_new_proc(pid_t tid, pid_t tgid)
{
	struct opd_proc * proc;

	nr_procs++;
	proc = xmalloc(sizeof(struct opd_proc));
	list_init(&proc->maps);
	proc->name = NULL;
	proc->tid = tid;
	proc->tgid = tgid;
	proc->dead = 0;
	proc->accessed = 0;
	list_add(&proc->next, &opd_procs[proc_hash(tid)]);
	return proc;
}


struct opd_proc * opd_get_proc(pid_t tid, pid_t tgid)
{
	struct opd_proc * proc;
	uint hash = proc_hash(tid);
	struct list_head * pos, *pos2;

	opd_24_stats[OPD_PROC_QUEUE_ACCESS]++;
	list_for_each_safe(pos, pos2, &opd_procs[hash]) {
		opd_24_stats[OPD_PROC_QUEUE_DEPTH]++;
		proc = list_entry(pos, struct opd_proc, next);
		if (tid == proc->tid && tgid == proc->tgid) {
			/* LRU to head */
			list_del(&proc->next);
			list_add(&proc->next, &opd_procs[hash]);
			return proc;
		}
	}

	return NULL;
}


/**
 * verb_show_sample - print the sample out to the log
 * @param offset  the offset value
 * @param map  map to print
 */
inline static void
verb_show_sample(unsigned long offset, struct opd_map * map)
{
	verbprintf(vsamples, "DO_PUT_SAMPLE : calc offset 0x%.8lx, "
		"map start 0x%.8lx, end 0x%.8lx, offset 0x%.8lx, name \"%s\"\n",
		offset, map->start, map->end, map->offset, 
		map->image->name);
}


void opd_put_image_sample(struct opd_image * image, unsigned long offset,
                          u32 counter)
{
	struct opd_24_sfile * sfile;
	int err;

	if (image->ignored)
		return;

	if (!image->sfiles[cpu_number]) {
		image->sfiles[cpu_number] =
			xcalloc(OP_MAX_COUNTERS, sizeof(struct op_24_sfile *));
	}
	sfile = image->sfiles[cpu_number][counter];

	if (!sfile || !odb_open_count(&sfile->sample_file)) {
		if (opd_open_24_sample_file(image, counter, cpu_number)) {
			/* opd_open_24_sample_file output an error message */
			opd_24_stats[OPD_LOST_SAMPLEFILE]++;
			return;
		}
		sfile = image->sfiles[cpu_number][counter];
	}

	err = odb_update_node(&sfile->sample_file, offset);
	if (err) {
		fprintf(stderr, "%s\n", strerror(err));
		abort();
	}

	opd_24_sfile_lru(sfile);
}


/**
 * opd_lookup_maps - lookup a proc mappings for a sample
 * @param proc proc to lookup
 * @param sample sample to lookup
 *
 * iterate through the proc maps searching the mapping which owns sample
 * if sucessful sample count will be updated and we return non-zero
 */
static int opd_lookup_maps(struct opd_proc * proc,
			struct op_sample const * sample)
{
	struct list_head * pos;

	proc->accessed = 1;

	opd_24_stats[OPD_MAP_ARRAY_ACCESS]++;
	list_for_each(pos, &proc->maps) {
		struct opd_map * map = list_entry(pos, struct opd_map, next);
		if (opd_is_in_map(map, sample->eip)) {
			unsigned long offset = opd_map_offset(map, sample->eip);
			if (map->image != NULL) {
				verb_show_sample(offset, map);
				opd_put_image_sample(map->image, offset, sample->counter);
			}
			opd_24_stats[OPD_PROCESS]++;
			return 1;
		}
		opd_24_stats[OPD_MAP_ARRAY_DEPTH]++;
	}

	return 0;
}


void opd_put_sample(struct op_sample const * sample)
{
	struct opd_proc * proc;
	int in_kernel_eip = opd_eip_is_kernel(sample->eip);

	opd_24_stats[OPD_SAMPLES]++;

	verbprintf(vsamples, "DO_PUT_SAMPLE: c%d, EIP 0x%.8lx, tgid %.6d pid %.6d\n",
		sample->counter, sample->eip, sample->tgid, sample->pid);

	if (!separate_kernel && in_kernel_eip) {
		opd_handle_kernel_sample(sample->eip, sample->counter);
		return;
	}

	if (!(proc = opd_get_proc(sample->pid, sample->tgid))) {
		if (in_kernel_eip || no_vmlinux) {
			/* idle task get a 0 pid and is hidden we can never get
			 * a proc so on we fall back to put sample in vmlinux
			 * or module samples files. Here we will catch also
			 * sample for newly created kernel thread, currently 
			 * we can handle properly only kenel thread created
			 * at daemon startup time */
			opd_handle_kernel_sample(sample->eip, sample->counter);
		} else {
			verbprintf(vmisc, "No proc info for tgid %.6d pid %.6d.\n",
                                   sample->tgid, sample->pid);
			opd_24_stats[OPD_LOST_PROCESS]++;
		}
		return;
	}

	if (opd_lookup_maps(proc, sample))
		return;

	if (in_kernel_eip) {
		opd_add_kernel_map(proc, sample->eip);
		if (opd_lookup_maps(proc, sample))
			return;
	}

	/* couldn't locate it */
	verbprintf(vsamples, "Couldn't find map for pid %.6d, EIP 0x%.8lx.\n",
		   sample->pid, sample->eip);
	opd_24_stats[OPD_LOST_MAP_PROCESS]++;
}


void opd_handle_fork(struct op_note const * note)
{
	struct opd_proc * old;
	struct opd_proc * proc;
	struct list_head * pos;

	verbprintf(vmisc, "DO_FORK: from %d, %d to %ld, %ld\n", note->pid, note->tgid,
	           note->addr, note->len);

	old = opd_get_proc(note->pid, note->tgid);

	/* we can quite easily get a fork() after the execve() because the
	 * notifications are racy. In particular, the fork notification is
	 * done on parent return (so we know the pid), but this will often be
	 * after the execve is done by the child.
	 *
	 * So we only create a new setup if it doesn't exist already, allowing
	 * both the clone() and the execve() cases to work.
	 */
	if (opd_get_proc(note->addr, note->len))
		return;

	/* eip/len is actually tid/tgid of new process */
	proc = opd_new_proc(note->addr, note->len);

	if (!old)
		return;

	/* copy the maps */
	list_for_each(pos, &old->maps) {
		struct opd_map * map = list_entry(pos, struct opd_map, next);
		if (!separate_thread) {
			opd_add_mapping(proc, map->image, map->start,
			                map->offset, map->end);
		} else {
			/* when separating thread we can't create blindly a new
			 * image e.g. pid re-use, multiple mapping with the
			 * same mapping name etc. */
			struct opd_image * image = 
				opd_get_image(map->image->name, old->name,
				map->image->kernel, note->addr, note->len);
			opd_add_mapping(proc, image, map->start, map->offset,
			                map->end);
		}
	}
}


void opd_handle_exec(pid_t tid, pid_t tgid)
{
	struct opd_proc * proc;

	verbprintf(vmisc, "DO_EXEC: pid %u %u\n", tid, tgid);

	/* There is a race for samples received between fork/exec sequence.
	 * These samples belong to the old mapping but we can not say if
	 * samples has been received before the exec or after. This explains
	 * the message "Couldn't find map for ..." in verbose mode.
	 *
	 * Unhappily, it is difficult to get an estimation of these misplaced
	 * samples, the error message can count only out of mapping samples but
	 * not samples between the race and inside the mapping of the exec'ed
	 * process :/.
	 *
	 * Trying to save old mapping is not correct due the above reason. The
	 * only manner to handle this is to flush the module samples hash table
	 * after each fork which is unacceptable for performance reasons */
	proc = opd_get_proc(tid, tgid);
	if (proc) {
		opd_kill_maps(proc);
		/* proc->name will be set when the next mapping occurs */
		free((char *)proc->name);
		proc->name = NULL;
	} else {
		opd_new_proc(tid, tgid);
	}
}


void opd_handle_exit(struct op_note const * note)
{
	struct opd_proc * proc;

	verbprintf(vmisc, "DO_EXIT: process %d\n", note->pid);

	proc = opd_get_proc(note->pid, note->tgid);
	if (proc) {
		proc->dead = 1;
		proc->accessed = 1;
	} else {
		verbprintf(vmisc, "unknown proc %u just exited.\n", note->pid);
	}
}


typedef void (*opd_proc_cb)(struct opd_proc *);

/**
 * @param proc_cb callback to apply onto each existing proc struct
 *
 * the callback receive a struct opd_proc * (not a const struct) and is
 * allowed to freeze the proc struct itself.
 */
static void opd_for_each_proc(opd_proc_cb proc_cb)
{
	struct list_head * pos;
	struct list_head * pos2;
	int i;

	for (i = 0; i < OPD_MAX_PROC_HASH; ++i) {
		list_for_each_safe(pos, pos2, &opd_procs[i]) {
			struct opd_proc * proc =
				list_entry(pos, struct opd_proc, next);
			proc_cb(proc);
		}
	}
}


/**
 * opd_delete_proc - delete a process
 * @param proc  process to delete
 *
 * Remove the process proc from the process list and free
 * the associated structures.
 */
static void opd_delete_proc(struct opd_proc * proc)
{
	--nr_procs;
	list_del(&proc->next);
	opd_kill_maps(proc);
	if (proc->name)
		free((char *)proc->name);
	free(proc);
}


void opd_proc_cleanup(void)
{
	opd_for_each_proc(opd_delete_proc);
}


/**
 * opd_age_proc - age a struct opd_proc
 * @param  proc proc to age
 *
 * age dead proc in such way if a proc doesn't receive any samples
 * between two age_proc the opd_proc struct is deleted
 */
static void opd_age_proc(struct opd_proc * proc)
{
	// delay death whilst its still being accessed
	if (proc->dead) {
		proc->dead += proc->accessed;
		proc->accessed = 0;
		if (--proc->dead == 0)
			opd_delete_proc(proc);
	}
}


void opd_age_procs(void)
{
	opd_for_each_proc(opd_age_proc);
}


/**
 * opd_remove_kernel_mapping - remove all kernel mapping for an opd_proc
 * @param proc  proc where mappings must be updated.
 *
 * invalidate (by removing them) all kernel mapping. This function do nothing
 * when separate_kernel == 0 because we don't add mapping for kernel
 * sample in proc struct.
 */
static void opd_remove_kernel_mapping(struct opd_proc * proc)
{
	struct list_head * pos, * pos2;

	list_for_each_safe(pos, pos2, &proc->maps) {
		struct opd_map * map = list_entry(pos, struct opd_map, next);
		if (opd_eip_is_kernel(map->start + map->offset)) {
			list_del(pos);
			opd_delete_image(map->image);
			free(map);
		}
	}
}


void opd_clear_kernel_mapping(void)
{
	opd_for_each_proc(opd_remove_kernel_mapping);
}