M7350/qcom-opensource/kernel/kernel-tests/memory_prof/memory_prof.c
2024-09-09 08:57:42 +00:00

1451 lines
35 KiB
C

/*
* Copyright (c) 2013-2015, The Linux Foundation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of The Linux Foundation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <unistd.h>
#include <math.h>
#include <libgen.h> /* for basename */
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/queue.h>
#include <getopt.h>
#include <linux/msm_ion.h>
#include "memory_prof.h"
#include "memory_prof_module.h"
#include "memory_prof_util.h"
#include "alloc_profiles.h"
static unsigned int sleepiness;
static int verbosity;
static void sleepy()
{
usleep(sleepiness);
}
static void print_n_slow(char *st)
{
puts(st);
sleepy();
}
static void hr()
{
puts("---------------");
}
/**
* @ionfd [out] ion fd. On success, user must close.
* @alloc_data [in/out] alloc data. On success, user must free.
* returns 0 on success, 1 otherwise
*/
int alloc_me_up_some_ion(int ionfd,
struct ion_allocation_data *alloc_data)
{
int rc = 0;
rc = ioctl(ionfd, ION_IOC_ALLOC, alloc_data);
if (rc)
perror("couldn't do ion alloc");
return rc;
}
/**
* mmaps an Ion buffer to *buf.
*
* @ionfd - the ion fd
* @handle - the handle to the Ion buffer to map
* @len - the length of the buffer
* @buf - [output] the userspace buffer where the ion buffer will be
* mapped
* @map_fd - [output] the fd corresponding to the ION_IOC_MAP call
* will be saved in *map_fd. *You should close it* when you're done
* with it (and have munmap'd) *buf.
*
* Returns 0 on success, 1 otherwise
*/
static int mmap_my_ion_buffer(int ionfd, ion_user_handle_t handle, size_t len,
char **buf, int *map_fd)
{
struct ion_fd_data fd_data;
int rc;
fd_data.handle = handle;
rc = ioctl(ionfd, ION_IOC_MAP, &fd_data);
if (rc) {
perror("couldn't do ION_IOC_MAP");
return 1;
}
*map_fd = fd_data.fd;
*buf = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED,
*map_fd, 0);
return 0;
}
/**
* Allocates an ion buffer and mmaps it into *buf. See
* alloc_me_up_some_ion and mmap_my_ion_buffer for an explanation of
* the parameters here.
*/
static int alloc_and_map_some_ion(int ionfd,
struct ion_allocation_data *alloc_data,
char **buf, int *map_fd)
{
int rc;
rc = alloc_me_up_some_ion(ionfd, alloc_data);
if (rc)
return rc;
rc = mmap_my_ion_buffer(ionfd, alloc_data->handle, alloc_data->len,
buf, map_fd);
if (rc) {
rc |= ioctl(ionfd, ION_IOC_FREE, &alloc_data->handle);
}
return rc;
}
int do_basic_ion_sanity_test(int ionfd, ion_user_handle_t handle,
unsigned long size)
{
int rc;
unsigned long i, squelched = 0;
struct ion_fd_data fd_data;
struct ion_flush_data flush_data;
struct ion_custom_data custom_data;
uint8_t *buf;
bool integrity_good = true;
int error_vals[256];
memset(error_vals, 0, sizeof(error_vals));
fd_data.handle = handle;
rc = ioctl(ionfd, ION_IOC_MAP, &fd_data);
if (rc) {
perror("couldn't do ION_IOC_MAP");
goto err_out;
}
buf = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED,
fd_data.fd, 0);
if (buf == MAP_FAILED) {
perror("couldn't do mmap");
rc = 1;
goto err_close_fd_data;
}
for (i = 0; i < size; ++i)
if (buf[i]) {
printf("Buffer wasn't zero'd at offset %ld! got: %x\n",
i, buf[i]);
rc = 1;
goto err_munmap;
}
memset(buf, 0xA5, size);
flush_data.handle = handle;
flush_data.vaddr = buf;
flush_data.length = size;
custom_data.cmd = ION_IOC_CLEAN_INV_CACHES;
custom_data.arg = (unsigned long) &flush_data;
if (ioctl(ionfd, ION_IOC_CUSTOM, &custom_data)) {
perror("Couldn't flush caches");
rc = 1;
goto err_munmap;
} else {
puts("flushed caches");
}
for (i = 0; i < size; ++i) {
if (buf[i] != 0xA5) {
error_vals[buf[i]]++;
if (!integrity_good) {
squelched++;
if (verbosity < 2)
continue;
}
printf(" Data integrity error at offset 0x%lx from 0x%p (%x != 0xa5))!!!!\n",
i, buf, buf[i]);
integrity_good = false;
rc = 1;
}
}
if (squelched)
printf(" (squelched %ld additional integrity error%s)\n",
squelched, squelched == 1 ? "" : "s");
if (integrity_good)
printf(" Buffer integrity check succeeded\n");
else if (verbosity)
for (i = 0; i < 255; ++i)
if (error_vals[i])
printf("Saw %d instead of 0xA5 %d times\n",
(int) i, error_vals[i]);
err_munmap:
if (munmap(buf, size)) {
rc = 1;
perror("couldn't do munmap");
}
err_close_fd_data:
close(fd_data.fd);
err_out:
return rc;
}
static int basic_ion_sanity_test(struct ion_allocation_data alloc_data)
{
int ionfd, rc = 0;
unsigned long size = alloc_data.len;
ionfd = open(ION_DEV, O_RDONLY);
if (ionfd < 0) {
perror("couldn't open " ION_DEV);
rc = ionfd;
goto out;
}
rc = alloc_me_up_some_ion(ionfd, &alloc_data);
if (rc)
goto err0;
rc |= do_basic_ion_sanity_test(ionfd, alloc_data.handle, size);
err1:
rc |= ioctl(ionfd, ION_IOC_FREE, &alloc_data.handle);
err0:
close(ionfd);
out:
return rc;
}
static int basic_sanity_tests(unsigned long size)
{
int lrc, rc = 0;
struct ion_allocation_data system_alloc_data = {
.align = SZ_4K,
.len = size,
.heap_id_mask = ION_HEAP(ION_SYSTEM_HEAP_ID),
.flags = 0,
};
struct ion_allocation_data system_contig_alloc_data = {
.align = SZ_4K,
.len = size,
.heap_id_mask = ION_HEAP(ION_SYSTEM_CONTIG_HEAP_ID),
.flags = 0,
};
puts("testing system without caching...");
lrc = basic_ion_sanity_test(system_alloc_data);
puts(lrc ? "FAILED!" : "PASSED");
hr();
sleepy();
rc |= lrc;
puts("testing system with caching...");
system_alloc_data.flags |= ION_FLAG_CACHED;
lrc = basic_ion_sanity_test(system_alloc_data);
puts(lrc ? "FAILED!" : "PASSED");
hr();
sleepy();
rc |= lrc;
puts("testing system contig without caching...");
lrc = basic_ion_sanity_test(system_contig_alloc_data);
puts(lrc ? "FAILED!" : "PASSED");
hr();
sleepy();
rc |= lrc;
puts("testing system contig with caching...");
system_contig_alloc_data.flags |= ION_FLAG_CACHED;
lrc = basic_ion_sanity_test(system_contig_alloc_data);
puts(lrc ? "FAILED!" : "PASSED");
hr();
sleepy();
rc |= lrc;
if (rc)
puts("BASIC SANITY TESTS FAILED!!!!!!!! WOOOWWWW!!!!!!");
else
puts("All basic sanity tests passed");
return rc;
}
/**
* Free memory in alloc_list
*/
void free_mem_list(char **alloc_list)
{
int i = 0;
for (i = 0; i < MAX_PRE_ALLOC_SIZE; i++) {
if (alloc_list[i] != NULL) {
free(alloc_list[i]);
alloc_list[i] = NULL;
}
}
}
/**
* Allocate sizemb in alloc_list
*/
int alloc_mem_list(char **alloc_list, int sizemb)
{
int i = 0;
int alloc_size = 1*1024*1024 * sizeof(char);
if (sizemb > MAX_PRE_ALLOC_SIZE) {
return -1;
}
// Break allocation into 1 MB pieces to ensure
// we easily find enough virtually contigous memory
for (i = 0; i < sizemb; i++)
{
alloc_list[i] =(char *) malloc(alloc_size);
if (alloc_list[i] == NULL)
{
perror("couldn't allocate 1MB");
free_mem_list(alloc_list);
return -1;
}
// Memory must be accessed for it to be page backed.
// We may want to randomize the data in the future
// to prevent features such as KSM returning memory
// to the system.
memset(alloc_list[i], 1, alloc_size);
}
return 0;
}
/**
* Returns the total time taken for ION_IOC_ALLOC
*
* @heap_id_mask - passed to ION_IOC_ALLOC
* @flags - passed to ION_IOC_ALLOC
* @size - passed to ION_IOC_ALLOC
* @alloc_ms - [output] time taken (in MS) to complete the ION_IOC_ALLOC
* @map_ms - [output] time taken (in MS) to complete the ION_IOC_MAP + mmap
* @memset_ms - [output] time taken (in MS) to memset the Ion buffer
* @free_ms - [output] time taken (in MS) to complete the ION_IOC_FREE
* @do_pre_alloc - whether or not we should do a "pre-allocation"
* @handle - an Ion handle to use for profiling. If 0, a buffer will be
* allocated for you.
* @ionfd - fd to /dev/ion to be used (needed if you're passing in a
* handle). Pass -1 to open /dev/ion and get a new client.
* @do_free - whether or not we should free the handle when we're done with it
*
* Returns 0 on success, 1 on failure.
*/
int do_profile_alloc_for_heap(unsigned int heap_id_mask,
unsigned int flags, unsigned int size,
double *alloc_ms, double *map_ms,
double *memset_ms, double *free_ms,
bool do_pre_alloc, ion_user_handle_t handle,
int ionfd, bool do_free)
{
int rc = 0, rc2;
uint8_t *buf = MAP_FAILED;
struct ion_fd_data fd_data;
struct timeval tv_before, tv_after, tv_result;
char **pre_alloc_list = NULL;
bool need_to_close_ionfd = true;
if (do_pre_alloc) {
char *plist[MAX_PRE_ALLOC_SIZE];
memset(plist, 0, MAX_PRE_ALLOC_SIZE * sizeof(char *));
if (alloc_mem_list(plist, ion_pre_alloc_size) < 0) {
rc = 1;
perror("couldn't create pre-allocated buffer");
goto out;
}
pre_alloc_list = plist;
}
if (alloc_ms)
*alloc_ms = 0;
if (free_ms)
*free_ms = 0;
if (map_ms) *map_ms = 0;
if (memset_ms) *memset_ms = 0;
if (ionfd < 0) {
ionfd = open(ION_DEV, O_RDONLY);
if (ionfd < 0) {
rc = 1;
perror("couldn't open " ION_DEV);
goto free_mem_list;
}
} else {
need_to_close_ionfd = false;
}
if (!handle) {
struct ion_allocation_data alloc_data = {
.align = SZ_4K,
.len = size,
.heap_id_mask = heap_id_mask,
.flags = flags,
};
rc = gettimeofday(&tv_before, NULL);
if (rc) {
perror("couldn't get time of day");
goto close_ion;
}
rc2 = ioctl(ionfd, ION_IOC_ALLOC, &alloc_data);
rc = gettimeofday(&tv_after, NULL);
if (rc2) {
rc = rc2;
goto close_ion;
}
if (rc) {
perror("couldn't get time of day");
goto free;
}
handle = alloc_data.handle;
if (alloc_ms)
*alloc_ms = timeval_ms_diff(tv_after, tv_before);
}
if (map_ms) {
rc = gettimeofday(&tv_before, NULL);
if (rc) {
perror("couldn't get time of day");
goto free;
}
fd_data.handle = handle;
rc = ioctl(ionfd, ION_IOC_MAP, &fd_data);
if (rc) {
perror("couldn't do ION_IOC_MAP");
goto free;
}
buf = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED,
fd_data.fd, 0);
rc2 = gettimeofday(&tv_after, NULL);
if (buf == MAP_FAILED) {
perror("couldn't do mmap");
goto fd_data_close;
}
if (rc2) {
perror("couldn't get time of day");
goto munmap;
}
*map_ms = timeval_ms_diff(tv_after, tv_before);
}
if (memset_ms && buf != MAP_FAILED) {
rc = gettimeofday(&tv_before, NULL);
if (rc) {
perror("couldn't get time of day");
goto munmap;
}
memset(buf, 0xA5, size);
rc2 = gettimeofday(&tv_after, NULL);
if (rc2) {
perror("couldn't get time of day");
goto munmap;
}
*memset_ms = timeval_ms_diff(tv_after, tv_before);
}
munmap:
if (buf != MAP_FAILED)
if (munmap(buf, size))
perror("couldn't do munmap");
fd_data_close:
close(fd_data.fd);
/*
* Okay, things are about to get messy. We're profiling our
* cleanup, but we might fail some stuff and need to
* cleanup... during cleanup.
*/
rc = gettimeofday(&tv_before, NULL);
if (rc)
perror("couldn't get time of day");
free:
if (do_free) {
struct ion_handle_data handle_data;
handle_data.handle = handle;
ioctl(ionfd, ION_IOC_FREE, &handle_data);
rc2 = gettimeofday(&tv_after, NULL);
if (!rc) {
if (rc2) {
perror("couldn't get time of day");
goto close_ion;
}
if (free_ms)
*free_ms = timeval_ms_diff(tv_after, tv_before);
}
}
close_ion:
if (need_to_close_ionfd)
close(ionfd);
free_mem_list:
if (do_pre_alloc)
free_mem_list(pre_alloc_list);
out:
return rc;
}
/**
* Allocates an Ion buffer and profiles it. See
* `do_profile_alloc_for_heap'.
*
* Returns 0 on success, 1 on failure.
*/
int profile_alloc_for_heap(unsigned int heap_id_mask,
unsigned int flags, unsigned int size,
double *alloc_ms, double *map_ms,
double *memset_ms, double *free_ms)
{
return do_profile_alloc_for_heap(
heap_id_mask, flags, size, alloc_ms, map_ms, memset_ms, free_ms,
true, 0, -1, true);
}
static int profile_kernel_alloc(void)
{
int rc, memory_prof_fd;
memory_prof_fd = open(MEMORY_PROF_DEV, 0);
if (memory_prof_fd < 0) {
perror("couldn't open " MEMORY_PROF_DEV);
return 1;
}
rc = ioctl(memory_prof_fd, MEMORY_PROF_IOC_TEST_KERNEL_ALLOCS);
if (rc)
perror("couldn't do MEMORY_PROF_IOC_TEST_KERNEL_ALLOCS");
close(memory_prof_fd);
return rc;
}
void compute_stats(double stats[], int num,
double *average, double *std_dev)
{
int i;
double sum = 0, sum_of_squares = 0;
for (i = 0; i < num; ++i)
sum += stats[i];
*average = sum / num;
for (i = 0; i < num; ++i)
sum_of_squares += pow(stats[i] - *average, 2);
*std_dev = sqrt(sum_of_squares / num);
}
void print_stats_results(const char *name, const char *flags_label,
const char *size_string,
double stats[], int reps)
{
int i;
struct timeval tv;
unsigned long long time_ms;
double average, std_dev;
compute_stats(stats, reps, &average, &std_dev);
gettimeofday(&tv, NULL);
time_ms = TV_TO_MS(tv);
printf("%s %llu %s %s %s average: %.2f std_dev: %.2f reps: %d\n",
ST_PREFIX_DATA_ROW, time_ms,
name, flags_label, size_string, average, std_dev, reps);
}
void print_a_bunch_of_stats_results(const char *name,
const char *flags_label,
const char *size_string,
double alloc_stats[],
double map_stats[],
double memset_stats[],
double free_stats[],
int reps)
{
char sbuf[70];
snprintf(sbuf, 70, "ION_IOC_ALLOC %s", name);
print_stats_results(sbuf, flags_label, size_string, alloc_stats, reps);
if (map_stats) {
snprintf(sbuf, 70, "mmap %s", name);
print_stats_results(sbuf, flags_label, size_string,
map_stats, reps);
}
if (memset_stats) {
snprintf(sbuf, 70, "memset %s", name);
print_stats_results(sbuf, flags_label, size_string,
memset_stats, reps);
}
snprintf(sbuf, 70, "ION_IOC_FREE %s", name);
print_stats_results(sbuf, flags_label, size_string, free_stats, reps);
}
struct string_reader_getline_data {
char **lines;
int nlines;
int linum;
};
static const char *alloc_profile_string_reader_getline(
struct alloc_profile_reader *reader)
{
struct string_reader_getline_data *data = reader->priv;
if (data->linum > data->nlines)
return NULL;
return data->lines[data->linum++ - 1];
}
static struct alloc_profile_entry *get_alloc_profile_from_string(
const char *eval_program)
{
const char *tmp;
char **lines;
struct alloc_profile_entry *alloc_profile;
struct alloc_profile_reader reader;
struct string_reader_getline_data reader_data;
int nlines = 1, i;
for (tmp = eval_program; *tmp != '\0'; ++tmp) {
if (*tmp == ';')
nlines++;
}
MALLOC(char **, lines, sizeof(*lines) * nlines);
for (i = 0; i < nlines; ++i)
MALLOC(char *, lines[i], MAX_ALLOC_PROFILE_LINE_LEN);
i = split_string(eval_program, ';', lines, MAX_ALLOC_PROFILE_LINE_LEN);
if (i != nlines)
errx(1,
"Error parsing --eval program! Expected %d lines, got %d (check for trailing ;)",
nlines, i);
reader.getline = alloc_profile_string_reader_getline;
reader.priv = &reader_data;
reader_data.lines = lines;
reader_data.nlines = nlines;
reader_data.linum = 1;
/**
* get_alloc_profile will run the necessary .ctor and
* .parse callbacks to build up an array of
* alloc_profile_entry objects
*/
alloc_profile = get_alloc_profile(&reader);
if (!alloc_profile)
errx(1, "Couldn't parse --eval program");
for (i = 0; i < nlines; ++i)
free(lines[i]);
free(lines);
return alloc_profile;
}
struct file_reader_getline_data {
char *buf;
FILE *fp;
int linum;
const char const *path;
};
static const char *alloc_profile_file_reader_getline(
struct alloc_profile_reader *reader)
{
char *buf;
struct file_reader_getline_data *data = reader->priv;
for (;;) {
buf = fgets(data->buf, MAX_ALLOC_PROFILE_LINE_LEN, data->fp);
if (!buf) {
if (!feof(data->fp))
err(1, "Error reading line %d from %s",
data->linum, data->path);
return NULL;
}
if (!(buf[0] == '#' || buf[0] == '\n' || buf[0] == '\r'))
break;
}
data->linum++;
/* strip off trailing newline */
data->buf[strlen(data->buf) - 1] = '\0';
return data->buf;
}
static struct alloc_profile_entry *get_alloc_profile_from_file(
const char *alloc_profile_path)
{
struct alloc_profile_entry *alloc_profile;
struct file_reader_getline_data reader_data;
struct alloc_profile_reader reader;
char *path = NULL;
bool using_stdin = alloc_profile_path &&
!strcmp(alloc_profile_path, "-");
int rc = 0;
reader.getline = alloc_profile_file_reader_getline;
reader.priv = &reader_data;
reader_data.linum = 1;
reader_data.path = alloc_profile_path;
if (alloc_profile_path) {
if (!file_exists(alloc_profile_path)) {
ASPRINTF(&path, "%s/%s", ALLOC_PROFILES_PATH_STRING,
alloc_profile_path);
if (!file_exists(path)) {
err(1, "Couldn't read alloc profile: %s",
alloc_profile_path);
}
} else {
ASPRINTF(&path, "%s", alloc_profile_path);
}
} else {
ASPRINTF(&path, "%s/builtin.txt", ALLOC_PROFILES_PATH_STRING);
}
printf("Using allocation profile: %s\n",
using_stdin ? "(STDIN)" : path);
MALLOC(char *, reader_data.buf, MAX_ALLOC_PROFILE_LINE_LEN);
if (using_stdin) {
reader_data.fp = stdin;
} else {
reader_data.fp = fopen(path, "r");
if (!reader_data.fp)
err(1, "Couldn't read %s\n", path);
}
/**
* get_alloc_profile will run the necessary .ctor and
* .parse callbacks to build up an array of
* alloc_profile_entry objects
*/
alloc_profile = get_alloc_profile(&reader);
if (!alloc_profile)
errx(1, "Couldn't parse allocation profile: %s\n", path);
if (!using_stdin)
fclose(reader_data.fp);
free(path);
return alloc_profile;
}
static int run_profile(struct alloc_profile_entry *alloc_profile)
{
struct alloc_profile_entry *entry;
struct alloc_profile_handler *iter;
int rc = 0;
/**
* Run .global_setup callbacks for any handlers being used in
* this allocation profile
*/
for_each_alloc_profile_handler(iter) {
int (*gsetup)(struct alloc_profile_entry entries[]) = NULL;
for_each_alloc_profile_entry(entry, alloc_profile) {
if (entry->handler == iter) {
gsetup = iter->ops->global_setup;
break;
}
}
if (gsetup)
if (gsetup(alloc_profile))
warn("global_setup failed for %s!",
iter->keyword);
}
/* Run .run callbacks */
for_each_alloc_profile_entry(entry, alloc_profile)
rc |= entry->handler->ops->run(entry);
/**
* Run .global_teardown callbacks for any handlers being used
* in this allocation profile
*/
for_each_alloc_profile_handler(iter) {
struct alloc_profile_entry *entry;
void (*gteardown)(void) = NULL;
for_each_alloc_profile_entry(entry, alloc_profile) {
if (entry->handler == iter) {
gteardown = iter->ops->global_teardown;
break;
}
}
if (gteardown)
gteardown();
}
/* Run .dtor callbacks and free allocations made with priv_size */
for_each_alloc_profile_entry(entry, alloc_profile) {
if (entry->handler->ops->dtor)
entry->handler->ops->dtor(entry);
if (entry->handler->priv_size)
free(entry->priv);
}
return rc;
}
static int oom_test(void)
{
int rc, ionfd, cnt = 0;
struct ion_allocation_data alloc_data = {
.len = SZ_8M,
.heap_id_mask = ION_HEAP(ION_SYSTEM_HEAP_ID),
.flags = 0,
};
LIST_HEAD(handle_list, ion_handle_node) head;
struct handle_list *headp;
struct ion_handle_node {
ion_user_handle_t handle;
LIST_ENTRY(ion_handle_node) nodes;
} *np;
LIST_INIT(&head);
ionfd = open(ION_DEV, O_RDONLY);
if (ionfd < 0) {
perror("couldn't open " ION_DEV);
return 1;
}
for (;; cnt++) {
rc = ioctl(ionfd, ION_IOC_ALLOC, &alloc_data);
if (rc) {
/* game over! */
break;
} else {
MALLOC(struct ion_handle_node *,
np, sizeof(struct ion_handle_node));
np->handle = alloc_data.handle;
LIST_INSERT_HEAD(&head, np, nodes);
}
printf("Allocated %d MB so far...\n", cnt * 8);
}
printf("Did %d 8M allocations before dying\n", cnt);
while (head.lh_first != NULL) {
np = head.lh_first;
ioctl(ionfd, ION_IOC_FREE, np->handle);
LIST_REMOVE(head.lh_first, nodes);
free(np);
}
return 0;
}
static int leak_test(void)
{
int ionfd;
struct ion_fd_data fd_data;
struct ion_allocation_data alloc_data = {
.len = SZ_4K,
.heap_id_mask = ION_HEAP(ION_SYSTEM_HEAP_ID),
.flags = 0,
};
puts("About to leak a handle...");
fflush(stdout);
ionfd = open(ION_DEV, O_RDONLY);
if (ionfd < 0) {
perror("couldn't open " ION_DEV);
return 1;
}
alloc_me_up_some_ion(ionfd, &alloc_data);
fd_data.handle = alloc_data.handle;
if (ioctl(ionfd, ION_IOC_MAP, &fd_data))
perror("Couldn't ION_IOC_MAP the buffer");
close(ionfd);
puts("closed ionfd w/o free'ing handle.");
puts("If you have CONFIG_ION_LEAK_CHECK turned on in");
puts("your kernel and have already done:");
puts("echo 1 > /debug/ion/check_leaks_on_destroy");
puts("then you should have seen a warning on your");
puts("console.");
puts("We will now sleep for 5 seconds for you to check");
puts("<debugfs>/ion/check_leaked_fds");
sleep(5);
return 0;
}
/**
* Allocate two ion buffers, mmap them, memset each of them to
* something different, memcpy from one to the other, then check that
* they are equal.
* We time the memcpy operation and return the number of milliseconds
* it took in *time_elapsed_memcpy_ms. When time_elapsed_flush_ms is
* not NULL, we flush the dst buffer and return the number of
* milliseconds it took to do so in *time_elapsed_flush_ms.
*
* @src_alloc_data - source allocation data
* @dst_alloc_data - source allocation data
* @time_elapsed_memcpy_ms - [output] time taken (in MS) to do the memcpy
* @time_elapsed_flush_ms - [output] time taken (in MS) to do the
* cache flush. Pass NULL if you don't want to do the flushing.
*
*/
static int do_ion_memcpy(struct ion_allocation_data src_alloc_data,
struct ion_allocation_data dst_alloc_data,
double *time_elapsed_memcpy_ms,
double *time_elapsed_flush_ms)
{
int ionfd, i, src_fd, dst_fd, fail_index = 0, rc = 0;
char *src, *dst;
struct timeval tv;
size_t len = src_alloc_data.len;
ionfd = open(ION_DEV, O_RDONLY);
if (ionfd < 0) {
perror("couldn't open " ION_DEV);
rc = 1;
goto out;
}
if (alloc_and_map_some_ion(ionfd, &src_alloc_data, &src, &src_fd)) {
perror("Couldn't alloc and map src buffer");
rc = 1;
goto close_ionfd;
}
if (alloc_and_map_some_ion(ionfd, &dst_alloc_data, &dst, &dst_fd)) {
perror("Couldn't alloc and map dst buffer");
rc = 1;
goto free_src;
}
memset(src, 0x5a, len);
memset(dst, 0xa5, len);
mprof_tick(&tv);
memcpy(dst, src, len);
*time_elapsed_memcpy_ms = mprof_tock(&tv);
if (time_elapsed_flush_ms) {
struct ion_flush_data flush_data;
struct ion_custom_data custom_data;
flush_data.handle = dst_alloc_data.handle;
flush_data.vaddr = dst;
flush_data.length = len;
custom_data.cmd = ION_IOC_CLEAN_CACHES;
custom_data.arg = (unsigned long) &flush_data;
mprof_tick(&tv);
if (ioctl(ionfd, ION_IOC_CUSTOM, &custom_data)) {
perror("Couldn't flush caches");
rc = 1;
}
*time_elapsed_flush_ms = mprof_tock(&tv);
}
if (!buffers_are_equal(src, dst, len, &fail_index)) {
printf("WARNING: buffer integrity check failed\n"
"dst[%d]=0x%x, src[%d]=0x%x\n",
fail_index, dst[fail_index],
fail_index, src[fail_index]);
rc = 1;
}
munmap_and_close:
munmap(src, len);
munmap(dst, len);
close(src_fd);
close(dst_fd);
free_dst:
ioctl(ionfd, ION_IOC_FREE, &dst_alloc_data.handle);
free_src:
ioctl(ionfd, ION_IOC_FREE, &src_alloc_data.handle);
close_ionfd:
close(ionfd);
out:
return rc;
}
int profile_ion_cache_ops_for_heap(unsigned int heap_id_mask,
unsigned int flags, unsigned int size,
double *time_elapsed_flush_ms,
bool cache_clean, bool cache_inv)
{
struct ion_allocation_data alloc_data;
int ionfd, data_fd, rc = 0;
char *buffer;
struct timeval tv;
struct ion_flush_data flush_data;
struct ion_custom_data custom_data;
ionfd = open(ION_DEV, O_RDONLY);
if (ionfd < 0) {
perror("couldn't open " ION_DEV);
rc = 1;
goto out;
}
alloc_data.len = size;
alloc_data.align = SZ_4K;
alloc_data.heap_id_mask = heap_id_mask;
alloc_data.flags = flags;
if (alloc_and_map_some_ion(ionfd, &alloc_data, &buffer, &data_fd)) {
perror("Couldn't alloc and map buffer");
rc = 1;
goto close_ionfd;
}
memset(buffer, 0x5a, size);
flush_data.handle = alloc_data.handle;
flush_data.vaddr = buffer;
flush_data.length = size;
if (cache_clean && cache_inv)
custom_data.cmd = ION_IOC_CLEAN_INV_CACHES;
else if (cache_clean)
custom_data.cmd = ION_IOC_CLEAN_CACHES;
else if (cache_inv)
custom_data.cmd = ION_IOC_INV_CACHES;
else {
perror("Not cleaning or invalidating caches");
goto munmap_and_close;
}
custom_data.arg = (unsigned long) &flush_data;
mprof_tick(&tv);
if (ioctl(ionfd, ION_IOC_CUSTOM, &custom_data)) {
perror("Couldn't flush caches");
rc = 1;
}
*time_elapsed_flush_ms = mprof_tock(&tv);
munmap_and_close:
munmap(buffer, size);
close(data_fd);
free_src:
ioctl(ionfd, ION_IOC_FREE, &alloc_data.handle);
close_ionfd:
close(ionfd);
out:
return rc;
}
/**
* Profiles the time it takes to copy between various types of Ion
* buffers. Namely, copies between every permutation of
* cached/uncached buffer (4 permutations total). Also profiles the
* amount of time it takes to flush the destination buffer on a
* cached->cached copy.
*/
static int ion_memcpy_test(void)
{
struct ion_allocation_data src_alloc_data, dst_alloc_data;
struct timeval tv;
double time_elapsed_memcpy_ms = 0,
time_elapsed_flush_ms = 0;
src_alloc_data.len = SZ_4M;
src_alloc_data.align = SZ_4K;
src_alloc_data.heap_id_mask = ION_HEAP(ION_SYSTEM_HEAP_ID);
dst_alloc_data.len = SZ_4M;
dst_alloc_data.align = SZ_4K;
dst_alloc_data.heap_id_mask = ION_HEAP(ION_SYSTEM_HEAP_ID);
src_alloc_data.flags = 0;
dst_alloc_data.flags = 0;
if (do_ion_memcpy(src_alloc_data, dst_alloc_data,
&time_elapsed_memcpy_ms, NULL)) {
printf("Uncached -> Uncached: FAIL\n");
} else {
printf("Uncached -> Uncached: %.3fms\n", time_elapsed_memcpy_ms);
}
src_alloc_data.flags = 0;
dst_alloc_data.flags = ION_FLAG_CACHED;
if (do_ion_memcpy(src_alloc_data, dst_alloc_data,
&time_elapsed_memcpy_ms, NULL)) {
printf("Uncached -> Cached: FAIL\n");
} else {
printf("Uncached -> Cached: %.3fms\n", time_elapsed_memcpy_ms);
}
src_alloc_data.flags = ION_FLAG_CACHED;
dst_alloc_data.flags = 0;
if (do_ion_memcpy(src_alloc_data, dst_alloc_data,
&time_elapsed_memcpy_ms, NULL)) {
printf("Cached -> Uncached: FAIL\n");
} else {
printf("Cached -> Uncached: %.3fms\n", time_elapsed_memcpy_ms);
}
src_alloc_data.flags = ION_FLAG_CACHED;
dst_alloc_data.flags = ION_FLAG_CACHED;
if (do_ion_memcpy(src_alloc_data, dst_alloc_data,
&time_elapsed_memcpy_ms,
&time_elapsed_flush_ms)) {
printf("Cached -> Cached: FAIL\n");
} else {
printf("Cached -> Cached: %.3fms (cache flush took %.3fms)\n",
time_elapsed_memcpy_ms, time_elapsed_flush_ms);
}
return 0;
}
/**
* Memory throughput test. Print some stats.
*/
static int mmap_memcpy_test(void)
{
int rc = 0;
char *chunk, *src, *dst;
struct timeval tv;
double *data_rates;
double sum = 0, sum_of_squares = 0, average, std_dev, dmin, dmax;
int iters = 1000, memcpy_iters = 100;
int i, j;
const size_t chunk_len = SZ_1M;
MALLOC(double *, data_rates, iters * sizeof(double));
chunk = mmap(NULL, chunk_len,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1, 0);
if (chunk == MAP_FAILED) {
perror("Couldn't allocate 1MB buffer with mmap\n");
rc = 1;
goto free_data_rates;
}
/*
* Some systems appear to do this trick where they mprotect
* the first page of a large allocation with
* PROT_NONE. Possibly to protect against someone else
* overflowing into their buffer? We do it here just to
* emulate the "real-world" a little closer.
*/
if (mprotect(chunk, SZ_4K, PROT_NONE)) {
perror("Couldn't mprotect the first page of the 1MB buffer\n");
rc = 1;
goto munmap_chunk;
}
src = chunk + SZ_4K;
dst = chunk + SZ_512K;
memset(src, 0x5a, SZ_64K);
memset(dst, 0xa5, SZ_64K);
for (i = 0; i < iters; ++i) {
float elapsed_ms;
mprof_tick(&tv);
for (j = 0; j < memcpy_iters; ++j)
memcpy(dst, src, SZ_64K);
elapsed_ms = mprof_tock(&tv);
/* units: MB/s */
data_rates[i] = ((SZ_64K * memcpy_iters) / SZ_1M)
/ (elapsed_ms / 1000);
}
dmin = dmax = data_rates[0];
for (i = 0; i < iters; ++i) {
sum += data_rates[i];
dmin = MIN(dmin, data_rates[i]);
dmax = MAX(dmax, data_rates[i]);
}
average = sum / iters;
for (i = 0; i < iters; ++i)
sum_of_squares += pow(data_rates[i] - average, 2);
std_dev = sqrt(sum_of_squares / iters);
printf("average: %.3f MB/s, min: %.3f MB/s, max: %.3f MB/s, std_dev: %.3f MB/s\n",
average, dmin, dmax, std_dev);
munmap_chunk:
munmap(chunk, chunk_len);
free_data_rates:
free(data_rates);
return rc;
}
#define USAGE_STRING \
"Usage: %s [options]\n" \
"\n" \
"Supported options:\n" \
"\n" \
" -h Print this message and exit\n" \
" -a Do the adversarial test (same as -l)\n" \
" -b Do basic sanity tests\n" \
" -z Size (in bytes) of buffer for basic sanity tests\n" \
" (default=65536 (64KB))\n" \
" -e Do Ion heap profiling.\n" \
" -i file Input `alloc profile'\n" \
" (Runs a general default profile if omitted).\n" \
" If - is given, the allocation profile is read\n" \
" from stdin.\n" \
" -k Do kernel alloc profiling (requires kernel module)\n" \
" -l Do leak test (leak an ion handle)\n" \
" -n Do the nominal test (same as -b)\n" \
" -o Do OOM test (alloc from Ion system heap until OOM)\n" \
" -p MS Sleep for MS milliseconds between stuff (for debugging)\n" \
" -r Do the repeatability test\n" \
" -s Do the stress test (same as -e)\n" \
" -t MB Size (in MB) of temp buffer pre-allocated before Ion allocations (default 0 MB)\n" \
" -v Increase verbosity. Pass multiple times to increase verbosity\n" \
" --ion-memcpy-test\n" \
" Does some memcpy's between various types of Ion buffers\n" \
" --mmap-memcpy-test\n" \
" Does some memcpy's between some large buffers allocated\n" \
" by mmap\n" \
" --eval PROGRAM\n" \
" Evaluates PROGRAM as an allocation profile.\n" \
" Multiple commands should be separated by a semi-colon (;).\n"
static void usage(char *progname)
{
printf(USAGE_STRING, progname);
}
static struct option memory_prof_options[] = {
{"ion-memcpy-test", no_argument, 0, 0},
{"mmap-memcpy-test", no_argument, 0, 0},
{"eval", required_argument, 0, 0},
{0, 0, 0, 0}
};
int ion_pre_alloc_size = ION_PRE_ALLOC_SIZE_DEFAULT;
int main(int argc, char *argv[])
{
int rc = 0, i, opt;
unsigned long basic_sanity_size = SZ_64K;
bool do_basic_sanity_tests = false;
bool do_heap_profiling = false;
bool do_kernel_alloc_profiling = false;
bool do_oom_test = false;
bool do_leak_test = false;
bool do_ion_memcpy_test = false;
bool do_mmap_memcpy_test = false;
bool did_something = false;
char *alloc_profile_path = NULL;
int num_reps = 1;
int option_index = 0;
char *eval_program = NULL;
while (-1 != (opt = getopt_long(
argc,
argv,
"abe::hi:klmnop:rs::t:vz:",
memory_prof_options,
&option_index))) {
switch (opt) {
case 0:
if (strcmp("ion-memcpy-test",
memory_prof_options[option_index].name)
== 0) {
do_ion_memcpy_test = true;
break;
}
if (strcmp("mmap-memcpy-test",
memory_prof_options[option_index].name)
== 0) {
do_mmap_memcpy_test = true;
break;
}
if (strcmp("eval",
memory_prof_options[option_index].name)
== 0) {
eval_program = strdup(optarg);
break;
}
printf("Unhandled longopt: %s\n",
memory_prof_options[option_index].name);
break;
case 't':
ion_pre_alloc_size = atoi(optarg);
break;
case 'n':
case 'b':
do_basic_sanity_tests = true;
break;
case 's':
case 'e':
do_heap_profiling = true;
break;
case 'i':
STRDUP(alloc_profile_path, optarg);
break;
case 'k':
do_kernel_alloc_profiling = true;
break;
case 'a':
case 'l':
do_leak_test = true;
break;
case 'o':
do_oom_test = true;
break;
case 'r':
num_reps = NUM_REPS_FOR_REPEATABILITY;
break;
case 'p':
/* ms to us */
sleepiness = atoi(optarg) * 1000;
break;
case 'v':
verbosity++;
break;
case 'z':
basic_sanity_size = atoi(optarg);
break;
case 'h':
default:
usage(basename(argv[0]));
exit(1);
}
}
/* make sure we don't get killed: */
set_oom_score_adj_self(-1000);
set_oom_score_adj_parent(-1000);
if (do_basic_sanity_tests) {
for (i = 0; i < num_reps; ++i)
rc |= basic_sanity_tests(basic_sanity_size);
did_something = true;
}
if (do_heap_profiling) {
struct alloc_profile_entry *alloc_profile
= get_alloc_profile_from_file(alloc_profile_path);
puts("All times are in milliseconds unless otherwise indicated");
printf(ST_PREFIX_PREALLOC_SIZE " pre-alloc size (MB): %d\n",
ion_pre_alloc_size);
for (i = 0; i < num_reps; ++i)
rc |= run_profile(alloc_profile);
did_something = true;
} else if (alloc_profile_path) { /* Execute any other profile */
struct alloc_profile_entry *alloc_profile
= get_alloc_profile_from_file(alloc_profile_path);
rc |= run_profile(alloc_profile);
did_something = true;
}
if (eval_program) {
struct alloc_profile_entry *alloc_profile
= get_alloc_profile_from_string(eval_program);
puts("All times are in milliseconds unless otherwise indicated");
printf(ST_PREFIX_PREALLOC_SIZE " pre-alloc size (MB): %d\n",
ion_pre_alloc_size);
rc |= run_profile(alloc_profile);
did_something = true;
}
if (do_kernel_alloc_profiling) {
for (i = 0; i < num_reps; ++i)
rc |= profile_kernel_alloc();
did_something = true;
}
if (do_oom_test) {
for (i = 0; i < num_reps; ++i)
rc |= oom_test();
did_something = true;
}
if (do_leak_test) {
for (i = 0; i < num_reps; ++i)
rc |= leak_test();
did_something = true;
}
if (do_ion_memcpy_test) {
rc |= ion_memcpy_test();
did_something = true;
}
if (do_mmap_memcpy_test) {
rc |= mmap_memcpy_test();
did_something = true;
}
if (!did_something)
printf("Nothing to do. Try %s -h\n", argv[0]);
free(eval_program);
free(alloc_profile_path);
return rc;
}