2006-04-12 01:59:08 +02:00
|
|
|
/**
|
|
|
|
* =========================================================================
|
|
|
|
* File : trace.cpp
|
|
|
|
* Project : 0 A.D.
|
|
|
|
* Description : allows recording and 'playing back' a sequence of
|
|
|
|
* : I/Os - useful for benchmarking and archive builder.
|
|
|
|
*
|
|
|
|
* @author Jan.Wassenberg@stud.uni-karlsruhe.de
|
|
|
|
* =========================================================================
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Copyright (c) 2006 Jan Wassenberg
|
|
|
|
*
|
|
|
|
* Redistribution and/or modification are also permitted under the
|
|
|
|
* terms of the GNU General Public License as published by the
|
|
|
|
* Free Software Foundation (version 2 or later, at your option).
|
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
2006-03-07 06:44:34 +01:00
|
|
|
#include "precompiled.h"
|
|
|
|
|
|
|
|
#include "lib/allocators.h"
|
|
|
|
#include "lib/timer.h"
|
|
|
|
#include "file_internal.h"
|
|
|
|
|
|
|
|
static uintptr_t trace_initialized; // set via CAS
|
|
|
|
static Pool trace_pool;
|
|
|
|
|
|
|
|
// call at before using trace_pool. no-op if called more than once.
|
|
|
|
static inline void trace_init()
|
|
|
|
{
|
|
|
|
if(CAS(&trace_initialized, 0, 1))
|
|
|
|
(void)pool_create(&trace_pool, 4*MiB, sizeof(TraceEntry));
|
|
|
|
}
|
|
|
|
|
|
|
|
void trace_shutdown()
|
|
|
|
{
|
|
|
|
if(CAS(&trace_initialized, 1, 2))
|
|
|
|
(void)pool_destroy(&trace_pool);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// enabled by default. by the time we can decide whether a trace needs to
|
|
|
|
// be generated (see should_rebuild_main_archive), file accesses will
|
|
|
|
// already have occurred; hence default enabled and disable if not needed.
|
|
|
|
static bool trace_enabled = true;
|
|
|
|
static bool trace_force_enabled = false; // see below
|
|
|
|
|
|
|
|
// note: explicitly enabling trace means the user wants one to be
|
|
|
|
// generated even if an up-to-date version exists.
|
|
|
|
// (mechanism: ignore any attempts to disable)
|
|
|
|
void trace_enable(bool want_enabled)
|
|
|
|
{
|
|
|
|
trace_enabled = want_enabled;
|
|
|
|
|
|
|
|
if(want_enabled)
|
|
|
|
trace_force_enabled = true;
|
|
|
|
if(trace_force_enabled)
|
|
|
|
trace_enabled = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void trace_add(TraceOp op, const char* P_fn, size_t size, uint flags = 0, double timestamp = 0.0)
|
|
|
|
{
|
|
|
|
trace_init();
|
|
|
|
if(!trace_enabled)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if(timestamp == 0.0)
|
|
|
|
timestamp = get_time();
|
|
|
|
|
|
|
|
TraceEntry* t = (TraceEntry*)pool_alloc(&trace_pool, 0);
|
|
|
|
if(!t)
|
|
|
|
return;
|
|
|
|
t->timestamp = timestamp;
|
|
|
|
t->atom_fn = file_make_unique_fn_copy(P_fn);
|
|
|
|
t->size = size;
|
|
|
|
t->op = op;
|
|
|
|
t->flags = flags;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2006-03-25 08:08:29 +01:00
|
|
|
void trace_notify_io(const char* P_fn, size_t size, uint flags)
|
2006-03-07 06:44:34 +01:00
|
|
|
{
|
2006-03-25 08:08:29 +01:00
|
|
|
trace_add(TO_IO, P_fn, size, flags);
|
2006-03-07 06:44:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void trace_notify_free(const char* P_fn, size_t size)
|
|
|
|
{
|
|
|
|
trace_add(TO_FREE, P_fn, size);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void trace_get(Trace* t)
|
|
|
|
{
|
|
|
|
t->ents = (const TraceEntry*)trace_pool.da.base;
|
|
|
|
t->num_ents = (uint)(trace_pool.da.pos / sizeof(TraceEntry));
|
|
|
|
}
|
|
|
|
|
|
|
|
void trace_clear()
|
|
|
|
{
|
|
|
|
pool_free_all(&trace_pool);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
LibError trace_write_to_file(const char* trace_filename)
|
|
|
|
{
|
|
|
|
if(!trace_enabled)
|
|
|
|
return INFO_SKIPPED;
|
|
|
|
|
|
|
|
char N_fn[PATH_MAX];
|
|
|
|
RETURN_ERR(file_make_full_native_path(trace_filename, N_fn));
|
2006-04-03 23:28:10 +02:00
|
|
|
// append at end of file, otherwise we'd only have the most
|
|
|
|
// recently stored trace. vfs_optimizer correctly deals with
|
|
|
|
// several trace runs per file.
|
|
|
|
FILE* f = fopen(N_fn, "at");
|
2006-03-07 06:44:34 +01:00
|
|
|
if(!f)
|
|
|
|
WARN_RETURN(ERR_FILE_ACCESS);
|
|
|
|
|
|
|
|
Trace t;
|
|
|
|
trace_get(&t);
|
|
|
|
const TraceEntry* ent = t.ents;
|
|
|
|
for(size_t i = 0; i < t.num_ents; i++, ent++)
|
|
|
|
{
|
|
|
|
char opcode = '?';
|
|
|
|
switch(ent->op)
|
|
|
|
{
|
2006-03-25 08:08:29 +01:00
|
|
|
case TO_IO: opcode = 'L'; break;
|
2006-03-07 06:44:34 +01:00
|
|
|
case TO_FREE: opcode = 'F'; break;
|
|
|
|
default: debug_warn("invalid TraceOp");
|
|
|
|
}
|
|
|
|
|
2006-03-25 08:08:29 +01:00
|
|
|
debug_assert(ent->op == TO_IO || ent->op == TO_FREE);
|
2006-03-07 06:44:34 +01:00
|
|
|
fprintf(f, "%#010f: %c \"%s\" %d %04x\n", ent->timestamp, opcode, ent->atom_fn, ent->size, ent->flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
(void)fclose(f);
|
|
|
|
return ERR_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
LibError trace_read_from_file(const char* trace_filename, Trace* t)
|
|
|
|
{
|
|
|
|
trace_clear();
|
|
|
|
|
|
|
|
char N_fn[PATH_MAX];
|
|
|
|
RETURN_ERR(file_make_full_native_path(trace_filename, N_fn));
|
|
|
|
FILE* f = fopen(N_fn, "rt");
|
|
|
|
if(!f)
|
|
|
|
WARN_RETURN(ERR_FILE_NOT_FOUND);
|
|
|
|
|
|
|
|
// we use trace_add, which is the same mechanism called by trace_notify*;
|
|
|
|
// therefore, tracing needs to be enabled.
|
|
|
|
trace_enabled = true;
|
|
|
|
|
|
|
|
// parse lines and stuff them in trace_pool
|
|
|
|
// (as if they had been trace_add-ed; replaces any existing data)
|
|
|
|
// .. bake PATH_MAX limit into string.
|
|
|
|
char fmt[30];
|
|
|
|
snprintf(fmt, ARRAY_SIZE(fmt), "%%lf: %%c \"%%%d[^\"]\" %%d %%04x\n", PATH_MAX);
|
|
|
|
for(;;)
|
|
|
|
{
|
|
|
|
double timestamp; char opcode; char P_path[PATH_MAX]; size_t size; uint flags;
|
|
|
|
int ret = fscanf(f, fmt, ×tamp, &opcode, P_path, &size, &flags);
|
|
|
|
if(ret == EOF)
|
|
|
|
break;
|
|
|
|
debug_assert(ret == 5);
|
|
|
|
|
2006-03-25 08:08:29 +01:00
|
|
|
TraceOp op = TO_IO; // default in case file is garbled
|
2006-03-07 06:44:34 +01:00
|
|
|
switch(opcode)
|
|
|
|
{
|
2006-03-25 08:08:29 +01:00
|
|
|
case 'L': op = TO_IO; break;
|
2006-03-07 06:44:34 +01:00
|
|
|
case 'F': op = TO_FREE; break;
|
|
|
|
default: debug_warn("invalid TraceOp");
|
|
|
|
}
|
|
|
|
|
|
|
|
trace_add(op, P_path, size, flags, timestamp);
|
|
|
|
}
|
|
|
|
|
|
|
|
fclose(f);
|
|
|
|
|
|
|
|
trace_get(t);
|
|
|
|
|
|
|
|
// all previous trace entries were hereby lost (overwritten),
|
|
|
|
// so there's no sense in continuing.
|
|
|
|
trace_enabled = false;
|
|
|
|
|
|
|
|
return ERR_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2006-03-24 22:56:00 +01:00
|
|
|
void trace_gen_random(size_t num_entries)
|
|
|
|
{
|
|
|
|
trace_clear();
|
|
|
|
|
|
|
|
for(size_t i = 0; i < num_entries; i++)
|
|
|
|
{
|
|
|
|
// generate random names until we get a valid file;
|
|
|
|
// remember its name and size.
|
|
|
|
const char* atom_fn;
|
|
|
|
off_t size;
|
|
|
|
for(;;)
|
|
|
|
{
|
|
|
|
atom_fn = file_get_random_name();
|
|
|
|
// use instead of vfs_stat to avoid warnings, since some of
|
|
|
|
// atom_fn will actually be directory names.
|
|
|
|
if(vfs_exists(atom_fn))
|
|
|
|
{
|
|
|
|
struct stat s;
|
|
|
|
LibError ret = vfs_stat(atom_fn, &s);
|
|
|
|
// ought to apply due to vfs_exists above.
|
|
|
|
debug_assert(ret == ERR_OK && S_ISREG(s.st_mode));
|
|
|
|
|
|
|
|
size = s.st_size;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2006-03-25 08:08:29 +01:00
|
|
|
trace_add(TO_IO, atom_fn, size);
|
2006-03-24 22:56:00 +01:00
|
|
|
trace_add(TO_FREE, atom_fn, size);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2006-03-07 06:44:34 +01:00
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
// simulate carrying out the entry's TraceOp to determine
|
|
|
|
// whether this IO would be satisfied by the file_buf cache.
|
|
|
|
bool trace_entry_causes_io(const TraceEntry* ent)
|
|
|
|
{
|
2006-04-03 23:28:10 +02:00
|
|
|
uint fb_flags = FB_NO_STATS;
|
2006-03-24 22:56:00 +01:00
|
|
|
if(ent->flags & FILE_LONG_LIVED)
|
2006-04-03 23:28:10 +02:00
|
|
|
fb_flags |= FB_LONG_LIVED;
|
2006-03-24 22:56:00 +01:00
|
|
|
|
2006-03-07 06:44:34 +01:00
|
|
|
FileIOBuf buf;
|
|
|
|
size_t size = ent->size;
|
|
|
|
const char* atom_fn = ent->atom_fn;
|
2006-04-03 23:28:10 +02:00
|
|
|
uint file_flags = ent->flags;
|
2006-03-07 06:44:34 +01:00
|
|
|
switch(ent->op)
|
|
|
|
{
|
2006-03-25 08:08:29 +01:00
|
|
|
case TO_IO:
|
2006-03-07 06:44:34 +01:00
|
|
|
{
|
2006-03-25 08:08:29 +01:00
|
|
|
// we're not interested in writes
|
2006-04-03 23:28:10 +02:00
|
|
|
if(file_flags & FILE_WRITE)
|
2006-03-25 08:08:29 +01:00
|
|
|
return false;
|
2006-04-03 23:28:10 +02:00
|
|
|
buf = file_cache_retrieve(atom_fn, &size, fb_flags);
|
2006-03-07 06:44:34 +01:00
|
|
|
// would not be in cache: add to list of real IOs
|
|
|
|
if(!buf)
|
|
|
|
{
|
2006-04-03 23:28:10 +02:00
|
|
|
buf = file_buf_alloc(size, atom_fn, fb_flags);
|
|
|
|
(void)file_cache_add(buf, size, atom_fn, file_flags);
|
2006-03-07 06:44:34 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case TO_FREE:
|
2006-04-03 23:28:10 +02:00
|
|
|
buf = file_cache_retrieve(atom_fn, &size, fb_flags|FB_NO_ACCOUNTING);
|
|
|
|
(void)file_buf_free(buf, fb_flags);
|
2006-03-07 06:44:34 +01:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
debug_warn("unknown TraceOp");
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// carry out all operations specified in the trace.
|
|
|
|
// if flags&TRF_SYNC_TO_TIMESTAMP, waits until timestamp for each event is
|
|
|
|
// reached; otherwise, they are run as fast as possible.
|
|
|
|
LibError trace_run(const char* trace_filename, uint flags)
|
|
|
|
{
|
|
|
|
Trace t;
|
|
|
|
RETURN_ERR(trace_read_from_file(trace_filename, &t));
|
|
|
|
|
|
|
|
// prevent the actions we carry out below from generating
|
|
|
|
// trace_add-s.
|
|
|
|
trace_enabled = false;
|
|
|
|
|
|
|
|
const double start_time = get_time();
|
|
|
|
const double first_timestamp = t.ents[0].timestamp;
|
|
|
|
|
|
|
|
const TraceEntry* ent = t.ents;
|
|
|
|
for(uint i = 0; i < t.num_ents; i++, ent++)
|
|
|
|
{
|
|
|
|
// wait until time for next entry if caller requested this
|
|
|
|
if(flags & TRF_SYNC_TO_TIMESTAMP)
|
|
|
|
{
|
|
|
|
while(get_time()-start_time < ent->timestamp-first_timestamp)
|
|
|
|
{
|
|
|
|
// busy-wait (don't sleep - can skew results)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// carry out this entry's operation
|
|
|
|
FileIOBuf buf; size_t size;
|
|
|
|
switch(ent->op)
|
|
|
|
{
|
2006-03-25 08:08:29 +01:00
|
|
|
case TO_IO:
|
|
|
|
// do not 'run' writes - we'd destroy the existing data.
|
|
|
|
if(ent->flags & FILE_WRITE)
|
|
|
|
continue;
|
2006-03-07 06:44:34 +01:00
|
|
|
(void)vfs_load(ent->atom_fn, buf, size, ent->flags);
|
|
|
|
break;
|
|
|
|
case TO_FREE:
|
2006-04-03 23:28:10 +02:00
|
|
|
buf = file_cache_retrieve(ent->atom_fn, &size, FB_NO_STATS|FB_NO_ACCOUNTING);
|
2006-03-07 06:44:34 +01:00
|
|
|
(void)file_buf_free(buf);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
debug_warn("unknown TraceOp");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
trace_clear();
|
|
|
|
|
|
|
|
return ERR_OK;
|
|
|
|
}
|
|
|
|
|