blob: f573067b5c0ddc4a382e8534ef387ce3dc3942cd [file] [log] [blame]
/* Copyright (C) 2014-2015 The Android Open Source Project
**
** This software is licensed under the terms of the GNU General Public
** License version 2, as published by the Free Software Foundation, and
** may be copied, distributed, and modified under those terms.
**
** 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.
*/
#include "hw/android/goldfish/profile.h"
#include "exec/code-profile.h"
#include "cpu.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define SAMPLE_PERIOD 1000000
#define SAMPLE_BURST 100
#define OUTPUT_THRESHOLD 100
typedef struct MmapData {
target_ulong start;
target_ulong end;
target_ulong offset;
char *name;
struct MmapData *next;
} MmapData;
typedef struct RangeProfile {
uint64_t start;
uint64_t size;
uint64_t count;
struct RangeProfile *next;
} RangeProfile;
typedef struct BranchProfile {
uint64_t from;
uint64_t to;
uint64_t count;
struct BranchProfile *next;
} BranchProfile;
typedef struct BinaryProfile {
char *name;
RangeProfile *range;
BranchProfile *branch;
struct BinaryProfile *next;
} BinaryProfile;
extern unsigned get_current_pid();
static MmapData *mmaps[65536];
static BinaryProfile *head_binary = NULL;
#define BUF_SIZE 4096
static void dump_profile() {
BinaryProfile *curr = head_binary;
int len = strlen(code_profile_dirname);
char filename[BUF_SIZE];
assert(len + 2 < BUF_SIZE);
strcpy(filename, code_profile_dirname);
filename[len] = '/';
filename[len + 1] = 0;
while (curr) {
char *i;
assert(len + strlen(curr->name) + 2 < BUF_SIZE);
strcpy(filename + len + 1, curr->name);
for (i = filename + len + 1; *i; i++) {
if (*i == '/')
*i = '_';
}
*i = '.';
*(i + 1) = 't';
*(i + 2) = 'x';
*(i + 3) = 't';
*(i + 4) = 0;
FILE *f = fopen(filename, "w");
RangeProfile *r = curr->range;
int range_size = 0;
while (r) {
range_size++;
r = r->next;
}
fprintf(f, "%d\n", range_size);
r = curr->range;
while (r) {
fprintf(f, "%llx-%llx:%llu\n", (unsigned long long)r->start,
(unsigned long long)(r->start + r->size),
(unsigned long long)r->count);
r = r->next;
}
fprintf(f, "%d\n", range_size);
r = curr->range;
while (r) {
fprintf(f, "%llx:%llu\n", (unsigned long long)r->start,
(unsigned long long)r->count);
r = r->next;
}
BranchProfile *b = curr->branch;
int branch_size = 0;
while (b) {
branch_size++;
b = b->next;
}
fprintf(f, "%d\n", branch_size);
b = curr->branch;
while (b) {
fprintf(f, "%llx->%llx:%llu\n", (unsigned long long)b->from,
(unsigned long long)b->to, (unsigned long long)b->count);
b = b->next;
}
fclose(f);
curr = curr->next;
}
}
static BinaryProfile *get_binary_profile(const char *name) {
BinaryProfile *curr = head_binary;
while (curr) {
if (!strcmp(curr->name, name)) {
return curr;
}
curr = curr->next;
}
curr = (BinaryProfile *)malloc(sizeof(BinaryProfile));
curr->name = strdup(name);
curr->range = NULL;
curr->branch = NULL;
curr->next = head_binary;
head_binary = curr;
return curr;
}
static void add_branch_count(BinaryProfile *profile, target_ulong from,
target_ulong to) {
BranchProfile *curr = profile->branch;
while (curr) {
if (curr->from == from && curr->to == to) {
curr->count++;
return;
}
curr = curr->next;
}
BranchProfile *b = (BranchProfile *)malloc(sizeof(BranchProfile));
b->from = from;
b->to = to;
b->count = 1;
b->next = profile->branch;
profile->branch = b;
}
static void add_range_count(BinaryProfile *profile, target_ulong start,
target_ulong size) {
RangeProfile *curr = profile->range;
while (curr) {
if (curr->start == start && curr->size == size) {
curr->count++;
return;
}
curr = curr->next;
}
RangeProfile *r = (RangeProfile *)malloc(sizeof(RangeProfile));
r->start = start;
r->size = size;
r->count = 1;
r->next = profile->range;
profile->range = r;
}
static const MmapData *get_mmap_data(target_ulong pc, int pid) {
const MmapData *data = mmaps[pid];
while (data) {
if (pc >= data->start && pc < data->end) {
return data;
}
data = data->next;
}
return NULL;
}
void record_mmap(target_ulong vstart, target_ulong vend, target_ulong offset,
const char *path, unsigned pid) {
MmapData *data = (MmapData *)malloc(sizeof(MmapData));
data->start = vstart;
data->end = vend;
data->offset = offset;
data->name = strdup(path);
data->next = mmaps[pid];
mmaps[pid] = data;
}
void release_mmap(unsigned pid) {
MmapData *curr = mmaps[pid];
while (curr) {
MmapData *next = curr->next;
free(curr->name);
free(curr);
curr = next;
}
mmaps[pid] = 0;
}
void profile_bb_helper(target_ulong pc, uint32_t size) {
static int bb_counter = 0;
static int output_counter = 0;
static int prev_pid;
static const MmapData *prev_mmap;
static target_ulong prev_pc;
static BinaryProfile *curr_profile;
if (code_profile_dirname == NULL)
return;
if (size == 0) {
return;
}
/* pc + size is the start of the next bb, which does not belong to the
current bb. We decrement size by 1 to make sure instructions in the
range of [pc, pc+size] all belong to the current bb. */
size--;
if (bb_counter++ == SAMPLE_PERIOD) {
prev_pid = get_current_pid();
prev_mmap = get_mmap_data(pc, prev_pid);
if (prev_mmap == NULL) {
bb_counter = 0;
return;
}
prev_pc = pc + size;
curr_profile = get_binary_profile(prev_mmap->name);
if (output_counter++ == OUTPUT_THRESHOLD) {
dump_profile();
output_counter = 0;
}
} else if (bb_counter > SAMPLE_PERIOD) {
if (bb_counter > SAMPLE_PERIOD + SAMPLE_BURST ||
get_current_pid() != prev_pid) {
bb_counter = 0;
return;
}
const MmapData *data = get_mmap_data(pc, prev_pid);
if (data != prev_mmap) {
bb_counter = 0;
return;
}
add_range_count(curr_profile, pc + data->offset - data->start, size);
add_branch_count(curr_profile, prev_pc + data->offset - data->start,
pc + data->offset - data->start);
prev_pc = pc + size;
}
}