/* Copyright (c) 2021-2022 Barcelona Supercomputing Center (BSC) * SPDX-License-Identifier: GPL-3.0-or-later */ #define _POSIX_C_SOURCE 200112L #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "chan.h" #include "emu.h" #include "ovni.h" #include "pcf.h" #include "prv.h" #include "trace.h" #include "utlist.h" /* Obtains the corrected clock of the given event */ static int64_t evclock(struct ovni_stream *stream, struct ovni_ev *ev) { return (int64_t) ovni_ev_get_clock(ev) + stream->clock_offset; } static void print_ev(struct ovni_stream *stream, struct ovni_ev *ev) { int64_t clock = evclock(stream, ev); int64_t delta = clock - stream->lastclock; UNUSED(delta); dbg(">>> %s.%d.%d %c %c %c % 20ld % 15ld ", stream->loom->hostname, stream->proc->pid, stream->thread->tid, ev->header.model, ev->header.category, ev->header.value, clock, delta); int payloadsize = ovni_payload_size(ev); for (int i = 0; i < payloadsize; i++) { dbg("%d ", ev->payload.u8[i]); } dbg("\n"); } static void print_cur_ev(struct ovni_emu *emu) { print_ev(emu->cur_stream, emu->cur_ev); } /* Update the tracking channel if needed */ static void cpu_update_tracking_chan(struct ovni_chan *cpu_chan, struct ovni_ethread *th) { int cpu_enabled = 0; switch (cpu_chan->track) { case CHAN_TRACK_TH_RUNNING: cpu_enabled = th->is_running; break; case CHAN_TRACK_TH_ACTIVE: cpu_enabled = th->is_active; break; default: dbg("ignoring thread %d chan %d with track=%d\n", th->tid, cpu_chan->id, cpu_chan->track); return; } struct ovni_chan *th_chan = &th->chan[cpu_chan->id]; int th_enabled = chan_is_enabled(th_chan); /* Enable the cpu channel if needed */ if (cpu_enabled && !chan_is_enabled(cpu_chan)) chan_enable(cpu_chan, cpu_enabled); /* Copy the state from the thread channel if needed */ if (th_enabled && cpu_enabled) { /* Both enabled: simply follow the same value */ chan_copy(cpu_chan, th_chan); } else if (th_enabled && !cpu_enabled) { /* Only thread enabled: disable CPU */ if (chan_is_enabled(cpu_chan)) chan_disable(cpu_chan); } else if (!th_enabled && cpu_enabled) { /* Only CPU enabled: is this possible? Set to bad */ chan_set(cpu_chan, ST_BAD); err("warning: cpu %s chan %d enabled but tracked thread %d chan disabled\n", cpu_chan->cpu->name, cpu_chan->id, th->tid); } else { /* Both disabled: disable CPU channel if needed */ if (chan_is_enabled(cpu_chan)) chan_disable(cpu_chan); } dbg("cpu %s chan %d enabled=%d state=%d\n", cpu_chan->cpu->name, cpu_chan->id, chan_is_enabled(cpu_chan), chan_get_st(cpu_chan)); } void emu_cpu_update_chan(struct ovni_cpu *cpu, struct ovni_chan *cpu_chan) { int count = 0; struct ovni_ethread *th = NULL; /* Determine the source of tracking */ switch (cpu_chan->track) { case CHAN_TRACK_TH_RUNNING: count = cpu->nrunning_threads; th = cpu->th_running; break; case CHAN_TRACK_TH_ACTIVE: count = cpu->nactive_threads; th = cpu->th_active; break; default: dbg("ignoring %s chan %d with track=%d\n", cpu->name, cpu_chan->id, cpu_chan->track); return; } /* Based on how many threads, determine the state */ if (count == 0) { /* The channel can be already disabled (migration of paused * thread) so only disable it if needed. */ if (chan_is_enabled(cpu_chan)) chan_disable(cpu_chan); } else if (count == 1) { if (th == NULL) die("emu_cpu_update_chan: tracking thread is NULL\n"); /* A unique thread found: copy the state */ dbg("cpu_update_chan: unique thread %d found, updating chan %d\n", th->tid, cpu_chan->id); cpu_update_tracking_chan(cpu_chan, th); } else { /* More than one thread: enable the channel and set it to a * error value */ if (!chan_is_enabled(cpu_chan)) chan_enable(cpu_chan, 1); if (chan_get_st(cpu_chan) != ST_TOO_MANY_TH) chan_set(cpu_chan, ST_TOO_MANY_TH); } } static void propagate_channels(struct ovni_emu *emu) { struct ovni_chan *th_chan = NULL; struct ovni_chan *tmp = NULL; /* Only propagate thread channels to their corresponding CPU */ DL_FOREACH_SAFE(emu->th_chan, th_chan, tmp) { if (th_chan->thread == NULL) die("propagate_channels: channel thread is NULL\n"); struct ovni_ethread *thread = th_chan->thread; /* No CPU in the thread */ if (thread->cpu == NULL) continue; struct ovni_cpu *cpu = thread->cpu; struct ovni_chan *cpu_chan = &cpu->chan[th_chan->id]; dbg("propagate thread %d chan %d in cpu %s\n", thread->tid, th_chan->id, cpu->name); emu_cpu_update_chan(cpu, cpu_chan); } } static void emit_channels(struct ovni_emu *emu) { struct ovni_chan *cpu_chan, *th_chan; dbg("emu enters emit_channels -------------------\n"); /* Emit only the channels that have been updated. We need a list * of updated channels */ DL_FOREACH(emu->th_chan, th_chan) { dbg("emu emits th chan %d\n", th_chan->id); chan_emit(th_chan); } DL_FOREACH(emu->cpu_chan, cpu_chan) { dbg("emu emits cpu chan %d\n", cpu_chan->id); chan_emit(cpu_chan); } /* Reset the list of updated channels */ dbg("emu resets the list of dirty channels -------------------\n"); emu->th_chan = NULL; emu->cpu_chan = NULL; } static void hook_init(struct ovni_emu *emu) { hook_init_ovni(emu); hook_init_nosv(emu); hook_init_tampi(emu); hook_init_openmp(emu); hook_init_nodes(emu); hook_init_kernel(emu); hook_init_nanos6(emu); } static void hook_end(struct ovni_emu *emu) { hook_end_nosv(emu); hook_end_nanos6(emu); } static void hook_pre(struct ovni_emu *emu) { switch (emu->cur_ev->header.model) { case 'O': hook_pre_ovni(emu); break; case 'V': hook_pre_nosv(emu); break; case 'T': hook_pre_tampi(emu); break; case 'M': hook_pre_openmp(emu); break; case 'D': hook_pre_nodes(emu); break; case 'K': hook_pre_kernel(emu); break; case '6': hook_pre_nanos6(emu); break; default: break; } } static void hook_post(struct ovni_emu *emu) { switch (emu->cur_ev->header.model) { default: // dbg("unknown model %c\n", emu->cur_ev->model); break; } } static void set_current(struct ovni_emu *emu, struct ovni_stream *stream) { emu->cur_stream = stream; emu->cur_ev = stream->cur_ev; emu->cur_loom = stream->loom; emu->cur_proc = stream->proc; emu->cur_thread = stream->thread; } /* * heap_node_compare_t - comparison function, returns: * > 0 if a > b * < 0 if a < b * = 0 if a == b * * Invert the comparison function to get a min-heap instead */ static inline int stream_cmp(heap_node_t *a, heap_node_t *b) { struct ovni_stream *sa, *sb; sa = heap_elem(a, struct ovni_stream, hh); sb = heap_elem(b, struct ovni_stream, hh); if (sb->lastclock < sa->lastclock) return -1; else if (sb->lastclock > sa->lastclock) return +1; else return 0; } static double get_time(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (double) ts.tv_sec + (double) ts.tv_nsec * 1.0e-9; } static void print_progress(struct ovni_emu *emu) { double cpu_written = (double) ftell(emu->prv_cpu); double th_written = (double) ftell(emu->prv_thread); double written = cpu_written + th_written; double progress = ((double) emu->global_offset) / ((double) emu->global_size); double time_now = get_time(); double time_elapsed = time_now - emu->start_emulation_time; double time_total = time_elapsed / progress; double time_left = time_total - time_elapsed; double speed_in = (double) emu->nev_processed / time_elapsed; double speed_out = written / time_elapsed; int tmin = (int) (time_left / 60.0); int sec = (int) ((time_left / 60.0 - tmin) * 60); err("%.1f%% done at %.0f Kev/s, out %.1f GB CPU / %.1f GB TH at %.1f MB/s (%d min %d s left)\n", 100.0 * progress, speed_in / 1024.0, cpu_written / (1024.0 * 1024.0 * 1024.0), th_written / (1024.0 * 1024.0 * 1024.0), speed_out / (1024.0 * 1024), tmin, sec); } /* Loads the next event and sets the lastclock in the stream. * Returns -1 if the stream has no more events. */ static int emu_step_stream(struct ovni_emu *emu, struct ovni_stream *stream) { if (ovni_load_next_event(stream) < 0) return -1; stream->lastclock = evclock(stream, stream->cur_ev); heap_insert(&emu->sorted_stream, &stream->hh, &stream_cmp); return 0; } static int next_event(struct ovni_emu *emu) { static int done_first = 0; /* Extract the next stream based on the event clock */ heap_node_t *node = heap_pop_max(&emu->sorted_stream, stream_cmp); /* No more streams */ if (node == NULL) return -1; struct ovni_stream *stream = heap_elem(node, struct ovni_stream, hh); if (stream == NULL) die("next_event: heap_elem returned NULL\n"); set_current(emu, stream); emu->global_offset += ovni_ev_size(stream->cur_ev); // err("stream %d clock at %ld\n", stream->tid, stream->lastclock); /* This can happen if two events are not ordered in the stream, but the * emulator picks other events in the middle. Example: * * Stream A: 10 3 ... * Stream B: 5 12 * * emulator output: * 5 * 10 * 3 -> warning! * 12 * ... * */ if (emu->lastclock > stream->lastclock) { err("warning: backwards jump in time %lu -> %lu for tid %d\n", emu->lastclock, stream->lastclock, stream->tid); if (emu->enable_linter) abort(); } emu->lastclock = stream->lastclock; if (!done_first) { done_first = 1; emu->firstclock = emu->lastclock; } emu->delta_time = emu->lastclock - emu->firstclock; return 0; } static void emu_load_first_events(struct ovni_emu *emu) { /* Prepare the stream heap */ heap_init(&emu->sorted_stream); emu->lastclock = 0; /* Load initial streams and events */ struct ovni_trace *trace = &emu->trace; for (size_t i = 0; i < trace->nstreams; i++) { struct ovni_stream *stream = &trace->stream[i]; emu->global_size += stream->size; if (emu_step_stream(emu, stream) < 0) { err("warning: empty stream for tid %d\n", stream->tid); if (emu->enable_linter) abort(); continue; } } } void emu_run(struct ovni_emu *emu) { emu->nev_processed = 0; emu_load_first_events(emu); emu->start_emulation_time = get_time(); hook_init(emu); emit_channels(emu); /* Then process all events */ for (size_t i = 0; next_event(emu) == 0; i++) { print_cur_ev(emu); hook_pre(emu); propagate_channels(emu); emit_channels(emu); hook_post(emu); if (i >= 100000) { print_progress(emu); i = 0; } emu->nev_processed++; emu_step_stream(emu, emu->cur_stream); } hook_end(emu); print_progress(emu); } struct ovni_ethread * emu_get_thread(struct ovni_eproc *proc, int tid) { for (size_t i = 0; i < proc->nthreads; i++) { struct ovni_ethread *thread = &proc->thread[i]; if (thread->tid == tid) return thread; } return NULL; } static void add_new_cpu(struct ovni_emu *emu, struct ovni_loom *loom, int i, int phyid) { struct ovni_cpu *cpu = &loom->cpu[i]; if (i < 0 || i >= (int) loom->ncpus) die("CPU with index %d in loom %s is out of bounds\n", i, loom->hostname); if (cpu->state != CPU_ST_UNKNOWN) die("new cpu %d in unexpected state in loom %s\n", i, loom->hostname); cpu->state = CPU_ST_READY; cpu->i = i; cpu->phyid = phyid; cpu->gindex = emu->total_ncpus++; cpu->loom = loom; dbg("new cpu %d at phyid=%d\n", cpu->gindex, phyid); } static int proc_load_cpus(struct ovni_emu *emu, struct ovni_loom *loom, struct ovni_eproc *proc, struct ovni_eproc *metadata_proc) { JSON_Object *meta = json_value_get_object(proc->meta); if (meta == NULL) die("json_value_get_object() failed\n"); JSON_Array *cpuarray = json_object_get_array(meta, "cpus"); /* This process doesn't have the cpu list */ if (cpuarray == NULL) return -1; if (metadata_proc) die("duplicated metadata for proc %d and %d in loom %s\n", metadata_proc->pid, proc->pid, loom->hostname); if (loom->ncpus != 0) die("loom %s already has CPUs\n", loom->hostname); loom->ncpus = json_array_get_count(cpuarray); if (loom->ncpus == 0) die("loom %s proc %d has metadata but no CPUs\n", loom->hostname, proc->pid); loom->cpu = calloc(loom->ncpus, sizeof(struct ovni_cpu)); if (loom->cpu == NULL) die("calloc failed: %s\n", strerror(errno)); for (size_t i = 0; i < loom->ncpus; i++) { JSON_Object *cpu = json_array_get_object(cpuarray, i); if (cpu == NULL) die("proc_load_cpus: json_array_get_object() failed\n"); int index = (int) json_object_get_number(cpu, "index"); int phyid = (int) json_object_get_number(cpu, "phyid"); add_new_cpu(emu, loom, index, phyid); } /* If we reach this point, all CPUs are in the ready state */ /* Init the vcpu as well */ struct ovni_cpu *vcpu = &loom->vcpu; if (vcpu->state != CPU_ST_UNKNOWN) die("unexpected virtual CPU state in loom %s\n", loom->hostname); vcpu->state = CPU_ST_READY; vcpu->i = -1; vcpu->phyid = -1; vcpu->gindex = emu->total_ncpus++; vcpu->loom = loom; dbg("new vcpu %d\n", vcpu->gindex); return 0; } /* Obtain CPUs in the metadata files and other data */ static void load_metadata(struct ovni_emu *emu) { struct ovni_trace *trace = &emu->trace; for (size_t i = 0; i < trace->nlooms; i++) { struct ovni_loom *loom = &trace->loom[i]; loom->offset_ncpus = emu->total_ncpus; struct ovni_eproc *metadata_proc = NULL; for (size_t j = 0; j < loom->nprocs; j++) { struct ovni_eproc *proc = &loom->proc[j]; if (proc_load_cpus(emu, loom, proc, metadata_proc) < 0) continue; if (metadata_proc) die("duplicated metadata found in pid %d and %d\n", metadata_proc->pid, proc->pid); metadata_proc = proc; } /* One of the process must have the list of CPUs */ if (metadata_proc == NULL) die("no metadata found in loom %s\n", loom->hostname); if (loom->ncpus == 0) die("no CPUs found in loom %s\n", loom->hostname); } } static int destroy_metadata(struct ovni_emu *emu) { struct ovni_trace *trace = &emu->trace; for (size_t i = 0; i < trace->nlooms; i++) { struct ovni_loom *loom = &trace->loom[i]; for (size_t j = 0; j < loom->nprocs; j++) { struct ovni_eproc *proc = &loom->proc[j]; if (proc->meta == NULL) die("cannot destroy metadata: is NULL\n"); json_value_free(proc->meta); } free(loom->cpu); } return 0; } static void open_prvs(struct ovni_emu *emu, char *tracedir) { char path[PATH_MAX]; sprintf(path, "%s/%s", tracedir, "thread.prv"); emu->prv_thread = fopen(path, "w"); if (emu->prv_thread == NULL) { err("error opening thread PRV file %s: %s\n", path, strerror(errno)); exit(EXIT_FAILURE); } sprintf(path, "%s/%s", tracedir, "cpu.prv"); emu->prv_cpu = fopen(path, "w"); if (emu->prv_cpu == NULL) { err("error opening cpu PRV file %s: %s\n", path, strerror(errno)); exit(EXIT_FAILURE); } prv_header(emu->prv_thread, emu->total_nthreads); prv_header(emu->prv_cpu, emu->total_ncpus); } static void open_pcfs(struct ovni_emu *emu, char *tracedir) { char path[PATH_MAX]; sprintf(path, "%s/%s", tracedir, "thread.pcf"); pcf_open(&emu->pcf[CHAN_TH], path, CHAN_TH); sprintf(path, "%s/%s", tracedir, "cpu.pcf"); pcf_open(&emu->pcf[CHAN_CPU], path, CHAN_CPU); } /* Fix the trace duration at the end */ static void fix_prv_headers(struct ovni_emu *emu) { prv_fix_header(emu->prv_thread, emu->delta_time, emu->total_nthreads); prv_fix_header(emu->prv_cpu, emu->delta_time, emu->total_ncpus); } static void close_prvs(struct ovni_emu *emu) { fclose(emu->prv_thread); fclose(emu->prv_cpu); } static void close_pcfs(struct ovni_emu *emu) { pcf_close(&emu->pcf[CHAN_TH]); pcf_close(&emu->pcf[CHAN_CPU]); } static void usage(void) { err("Usage: ovniemu [-c offsetfile] tracedir\n"); err("\n"); err("Options:\n"); err(" -c offsetfile Use the given offset file to correct\n"); err(" the clocks among nodes. It can be\n"); err(" generated by the ovnisync program\n"); err("\n"); err(" tracedir The output trace dir generated by ovni.\n"); err("\n"); err("The output PRV files are placed in the tracedir directory.\n"); exit(EXIT_FAILURE); } static void parse_args(struct ovni_emu *emu, int argc, char *argv[]) { int opt; while ((opt = getopt(argc, argv, "c:l")) != -1) { switch (opt) { case 'c': emu->clock_offset_file = optarg; break; case 'l': emu->enable_linter = 1; break; default: /* '?' */ usage(); } } if (optind >= argc) { err("missing tracedir\n"); usage(); } emu->tracedir = argv[optind]; } static void set_clock_offsets(struct ovni_emu *emu, const char *host, size_t offset) { size_t matches = 0; for (size_t i = 0; i < emu->trace.nlooms; i++) { struct ovni_loom *loom = &emu->trace.loom[i]; /* Match the hostname exactly */ if (strcmp(loom->hostname, host) != 0) continue; if (loom->clock_offset != 0) die("loom %s already has a clock offset\n", loom->dname); loom->clock_offset = offset; matches++; } if (matches == 0) die("no loom has hostname %s\n", host); } static void load_clock_offsets(struct ovni_emu *emu) { FILE *f = NULL; if (emu->clock_offset_file != NULL) { f = fopen(emu->clock_offset_file, "r"); /* If provided by the user, it must exist */ if (f == NULL) { err("error opening clock offset file %s: %s\n", emu->clock_offset_file, strerror(errno)); exit(EXIT_FAILURE); } } else { char path[PATH_MAX]; if (snprintf(path, PATH_MAX, "%s/clock-offsets.txt", emu->tracedir) >= PATH_MAX) { die("clock offset path too long\n"); } f = fopen(path, "r"); if (f == NULL) { /* May not exist, but is fine */ return; } } /* Ignore header line */ char buf[1024]; if (fgets(buf, 1024, f) == NULL) { err("missing header line in clock offset file"); exit(EXIT_FAILURE); } while (1) { errno = 0; int rank, ret; double offset, mean, std; char host[OVNI_MAX_HOSTNAME]; ret = fscanf(f, "%d %s %lf %lf %lf", &rank, host, &offset, &mean, &std); if (ret == EOF) { if (errno != 0) { perror("fscanf failed"); exit(EXIT_FAILURE); } break; } if (ret != 5) { err("fscanf read %d instead of 5 fields in %s\n", ret, emu->clock_offset_file); exit(EXIT_FAILURE); } set_clock_offsets(emu, host, (int64_t) offset); } /* Then populate the stream offsets */ struct ovni_trace *trace = &emu->trace; for (size_t i = 0; i < trace->nstreams; i++) { struct ovni_stream *stream = &trace->stream[i]; struct ovni_loom *loom = stream->loom; stream->clock_offset = loom->clock_offset; } fclose(f); err("loaded clock offsets ok\n"); } static void write_row_cpu(struct ovni_emu *emu) { char path[PATH_MAX]; sprintf(path, "%s/%s", emu->tracedir, "cpu.row"); FILE *f = fopen(path, "w"); if (f == NULL) { perror("cannot open row file"); exit(EXIT_FAILURE); } fprintf(f, "LEVEL NODE SIZE 1\n"); fprintf(f, "hostname\n"); fprintf(f, "\n"); fprintf(f, "LEVEL THREAD SIZE %ld\n", emu->total_ncpus); for (size_t i = 0; i < emu->total_ncpus; i++) { struct ovni_cpu *cpu = emu->global_cpu[i]; fprintf(f, "%s\n", cpu->name); } fclose(f); } static void write_row_thread(struct ovni_emu *emu) { char path[PATH_MAX]; sprintf(path, "%s/%s", emu->tracedir, "thread.row"); FILE *f = fopen(path, "w"); if (f == NULL) { perror("cannot open row file"); exit(EXIT_FAILURE); } fprintf(f, "LEVEL NODE SIZE 1\n"); fprintf(f, "hostname\n"); fprintf(f, "\n"); fprintf(f, "LEVEL THREAD SIZE %ld\n", emu->total_nthreads); for (size_t i = 0; i < emu->total_nthreads; i++) { struct ovni_ethread *th = emu->global_thread[i]; fprintf(f, "THREAD %d.%d\n", th->proc->appid, th->tid); } fclose(f); } static void init_threads(struct ovni_emu *emu) { emu->total_nthreads = 0; emu->total_nprocs = 0; struct ovni_trace *trace = &emu->trace; /* Count total processes and threads */ for (size_t i = 0; i < trace->nlooms; i++) { struct ovni_loom *loom = &trace->loom[i]; for (size_t j = 0; j < loom->nprocs; j++) { struct ovni_eproc *proc = &loom->proc[j]; emu->total_nprocs++; for (size_t k = 0; k < proc->nthreads; k++) { emu->total_nthreads++; } } } emu->global_thread = calloc(emu->total_nthreads, sizeof(*emu->global_thread)); if (emu->global_thread == NULL) { perror("calloc failed"); exit(EXIT_FAILURE); } int gi = 0; /* Populate global_thread array */ for (size_t i = 0; i < trace->nlooms; i++) { struct ovni_loom *loom = &trace->loom[i]; for (size_t j = 0; j < loom->nprocs; j++) { struct ovni_eproc *proc = &loom->proc[j]; for (size_t k = 0; k < proc->nthreads; k++) { struct ovni_ethread *thread = &proc->thread[k]; emu->global_thread[gi++] = thread; } } } } static void init_cpus(struct ovni_emu *emu) { struct ovni_trace *trace = &emu->trace; emu->global_cpu = calloc(emu->total_ncpus, sizeof(*emu->global_cpu)); if (emu->global_cpu == NULL) { perror("calloc"); exit(EXIT_FAILURE); } for (size_t i = 0; i < trace->nlooms; i++) { struct ovni_loom *loom = &trace->loom[i]; for (size_t j = 0; j < loom->ncpus; j++) { struct ovni_cpu *cpu = &loom->cpu[j]; emu->global_cpu[cpu->gindex] = cpu; if (snprintf(cpu->name, MAX_CPU_NAME, "CPU %ld.%ld", i, j) >= MAX_CPU_NAME) { err("error cpu %ld.%ld name too long\n", i, j); exit(EXIT_FAILURE); } cpu->virtual = 0; } emu->global_cpu[loom->vcpu.gindex] = &loom->vcpu; if (snprintf(loom->vcpu.name, MAX_CPU_NAME, "CPU %ld.*", i) >= MAX_CPU_NAME) { err("error cpu %ld.* name too long\n", i); exit(EXIT_FAILURE); } loom->vcpu.virtual = 1; } } static void create_pcf_cpus(struct ovni_emu *emu) { /* Only needed for the thread PCF */ struct pcf_file *pcf = &emu->pcf[CHAN_TH]; int prvtype = chan_to_prvtype[CHAN_OVNI_CPU]; struct pcf_type *type = pcf_find_type(pcf, prvtype); if (type == NULL) die("cannot find PCF type for CHAN_OVNI_CPU\n"); for (size_t i = 0; i < emu->total_ncpus; i++) { int value = i + 1; char *label = emu->global_cpu[i]->name; pcf_add_value(type, value, label); } } void emu_init(struct ovni_emu *emu, int argc, char *argv[]) { memset(emu, 0, sizeof(*emu)); parse_args(emu, argc, argv); if (ovni_load_trace(&emu->trace, emu->tracedir)) { err("error loading ovni trace\n"); exit(EXIT_FAILURE); } if (ovni_load_streams(&emu->trace)) { err("error loading streams\n"); exit(EXIT_FAILURE); } load_metadata(emu); load_clock_offsets(emu); init_threads(emu); init_cpus(emu); open_prvs(emu, emu->tracedir); open_pcfs(emu, emu->tracedir); create_pcf_cpus(emu); emu->global_size = 0; emu->global_offset = 0; for (size_t i = 0; i < emu->trace.nstreams; i++) emu->global_offset += emu->trace.stream[i].offset; err("loaded %ld cpus and %ld threads\n", emu->total_ncpus, emu->total_nthreads); } static int copy_file(const char *src, const char *dst) { char buffer[1024]; FILE *infile = fopen(src, "r"); if (infile == NULL) { err("fopen(%s) failed: %s\n", src, strerror(errno)); return -1; } FILE *outfile = fopen(dst, "w"); if (outfile == NULL) { err("fopen(%s) failed: %s\n", src, strerror(errno)); return -1; } size_t bytes; while ((bytes = fread(buffer, 1, sizeof(buffer), infile)) > 0) fwrite(buffer, 1, bytes, outfile); fclose(outfile); fclose(infile); return 0; } static int copy_recursive(const char *src, const char *dst) { DIR *dir; int failed = 0; if ((dir = opendir(src)) == NULL) { err("opendir \"%s\" failed: %s\n", src, strerror(errno)); failed = 1; goto bay; } if (mkdir(dst, 0755) != 0) { err("mkdir \"%s\" failed: %s\n", src, strerror(errno)); failed = 1; goto bay; } /* Use the heap, as the recursion may exhaust the stack */ char *newsrc = calloc(1, PATH_MAX); if (newsrc == NULL) die("calloc failed\n"); char *newdst = calloc(1, PATH_MAX); if (newdst == NULL) die("calloc failed\n"); struct dirent *dirent; while ((dirent = readdir(dir)) != NULL) { struct stat st; sprintf(newsrc, "%s/%s", src, dirent->d_name); if (strcmp(dirent->d_name, ".") == 0) continue; if (strcmp(dirent->d_name, "..") == 0) continue; int n = snprintf(newsrc, PATH_MAX, "%s/%s", src, dirent->d_name); if (n >= PATH_MAX) { err("path too long \"%s/%s\"\n", src, dirent->d_name); failed = 1; continue; } int m = snprintf(newdst, PATH_MAX, "%s/%s", dst, dirent->d_name); if (m >= PATH_MAX) { err("path too long \"%s/%s\"\n", dst, dirent->d_name); failed = 1; continue; } if (stat(newsrc, &st) != 0) { err("stat \"%s\" failed: %s\n", newsrc, strerror(errno)); failed = 1; continue; } if (S_ISDIR(st.st_mode)) { if (copy_recursive(newsrc, newdst) != 0) { failed = 1; } } else { if (copy_file(newsrc, newdst) != 0) { failed = 1; } } } closedir(dir); free(newsrc); free(newdst); bay: return -failed; } static void copy_configs(struct ovni_emu *emu) { /* Allow override so we can run the tests without install */ char *src = getenv("OVNI_CONFIG_DIR"); if (src == NULL) src = OVNI_CONFIG_DIR; char dst[PATH_MAX]; if (snprintf(dst, PATH_MAX, "%s/cfg", emu->tracedir) >= PATH_MAX) { err("cannot copy config files: path too long \"%s/cfg\"\n", emu->tracedir); return; } struct stat st; if (stat(dst, &st) == 0) { err("existing cfg directory \"%s\", skipping config copy\n", dst); if (emu->enable_linter) die("cannot continue in linter mode\n"); return; } if (copy_recursive(src, dst) != 0) { err("warning: cannot copy config files: recursive copy failed\n"); if (emu->enable_linter) die("cannot continue in linter mode\n"); } } void emu_post(struct ovni_emu *emu) { /* Write the PCF files */ pcf_write(&emu->pcf[CHAN_TH]); pcf_write(&emu->pcf[CHAN_CPU]); write_row_cpu(emu); write_row_thread(emu); copy_configs(emu); } void emu_destroy(struct ovni_emu *emu) { fix_prv_headers(emu); close_prvs(emu); close_pcfs(emu); destroy_metadata(emu); ovni_free_streams(&emu->trace); ovni_free_trace(&emu->trace); free(emu->global_cpu); free(emu->global_thread); } void edie(struct ovni_emu *emu, const char *fmt, ...) { va_list args; va_start(args, fmt); fprintf(stderr, "fatal: "); vfprintf(stderr, fmt, args); va_end(args); fprintf(stderr, "fatal: while evaluating the event %c%c%c with clock=%ld in thread=%d\n", emu->cur_ev->header.model, emu->cur_ev->header.category, emu->cur_ev->header.value, emu->cur_ev->header.clock, emu->cur_thread->tid); abort(); } void eerr(struct ovni_emu *emu, const char *fmt, ...) { va_list args; va_start(args, fmt); fprintf(stderr, "fatal: "); vfprintf(stderr, fmt, args); va_end(args); fprintf(stderr, "fatal: while evaluating the event %c%c%c with clock=%ld in thread=%d\n", emu->cur_ev->header.model, emu->cur_ev->header.category, emu->cur_ev->header.value, emu->cur_ev->header.clock, emu->cur_thread->tid); }