double ExmapTest::get_pid_size_from_ps(pid_t pid) { list<string> lines; list<string> captures; ok(read_proc_output("ps -e -o pid,vsz", lines), "can read proc output"); Regexp re; stringstream sstr; sstr << "\\s*" << pid << "\\s"; re.compile(sstr.str()); re.grep(lines); is((int) lines.size(), 1, "found line for pid"); re.compile("\\s*(\\d+)\\s+(\\d+)$"); re.match_capture(lines.front(), captures); is((int) captures.size(), 2, "Found my captures"); int size = atoi(captures.back().c_str()); return size * 1024.0; }
bool ExmapTest::run() { SysInfoPtr sysinfo(new LinuxSysInfo); Snapshot snap(sysinfo); is(snap.num_procs(), 0, "zero procs before load"); ok(snap.load(), "can load snapshot"); ok(snap.num_procs() > 0, "some procs after load"); list<ProcessPtr> allprocs = snap.procs(); ok(!allprocs.empty(), "can get a list of procs"); list<ProcessPtr>::iterator proc_it; list<ProcessPtr> procs; bool failed_to_get_sizes = false; SizesPtr sizes; for (proc_it = allprocs.begin(); proc_it != allprocs.end(); ++proc_it) { string cmdline = (*proc_it)->cmdline(); if (cmdline== SA_EXE) { procs.push_back(*proc_it); } sizes = (*proc_it)->sizes(); if (!sizes) { failed_to_get_sizes = true; } } ok(!failed_to_get_sizes, "can get sizes for every proc"); is((int) procs.size(), NUM_INSTANCES, "can find all our sharedarray procs"); ProcessPtr proc = procs.front(); sizes = proc->sizes(); ok(sizes->val(Sizes::VM) > NUM_ARRAYS * ARRAY_SIZE, "image is big enough"); double ps_size = get_pid_size_from_ps(proc->pid()); is_approx_rel(sizes->val(Sizes::VM), ps_size, 0.001, "exmap info matches ps output"); ok(sizes->val(Sizes::RESIDENT) > 0, "nonzero resident size"); ok(sizes->val(Sizes::EFFECTIVE_RESIDENT) > 0, "nonzero eff resident size"); ok(sizes->val(Sizes::EFFECTIVE_RESIDENT) < sizes->val(Sizes::RESIDENT), "effective is smaller than eff resident"); ok(sizes->val(Sizes::MAPPED) > 0, "nonzero mapped size"); ok(sizes->val(Sizes::EFFECTIVE_MAPPED) > 0, "nonzero eff mapped size"); ok(sizes->val(Sizes::EFFECTIVE_MAPPED) < sizes->val(Sizes::MAPPED), "effective is smaller than eff mapped"); list<FilePtr> files; list<FilePtr>::iterator file_it; list<FilePtr> all_files = snap.files(); ok(all_files.size() > 0, "can find some files"); Regexp re; re.compile(SA_LIB + "$"); failed_to_get_sizes = false; for (file_it = all_files.begin(); file_it != all_files.end(); ++file_it) { string name = (*file_it)->name(); if (re.matches(name)) { files.push_back(*file_it); } sizes = (*file_it)->sizes(); if (!sizes) { failed_to_get_sizes = true; } } ok(!failed_to_get_sizes, "can get sizes for every file"); is((int) files.size(), 1, "shared lib only listed once"); FilePtr shlib_file = files.front(); ok(shlib_file->is_elf(), "shared lib recognised as elf file"); list<ProcessPtr> procs_per_file = shlib_file->procs(); is((int) procs_per_file.size(), NUM_INSTANCES, "right number of procs mapping the file"); for (proc_it = procs_per_file.begin(); proc_it != procs_per_file.end(); ++proc_it) { ok(proc->cmdline() == SA_EXE, "each proc has correct cmdline"); } for (proc_it = procs.begin(); proc_it != procs.end(); ++proc_it) { sizes = proc->sizes(shlib_file); long arrays_size = NUM_ARRAYS * ARRAY_SIZE; float delta = std::abs(arrays_size - (long) sizes->val(Sizes::VM)); delta /= arrays_size; ok(delta < 0.01, "Shared lib has correct size in each proc"); } Elf::SectionPtr text = shlib_file->elf()->section(".text"); ok(text, "can find text section"); ok(text->size() > 0, "text section has nonzero size"); sizes = procs.front()->sizes(shlib_file, text->mem_range()); /// This appears to be compiler and system dependent... /* is_approx_rel(sizes->val(Sizes::RESIDENT), 2.0 * text->size(), 0.001, "lib text is resident and mapped twice (!)"); */ Elf::SectionPtr bss = shlib_file->elf()->section(".bss"); ok(bss, "can find bss section"); ok(bss->size() > 0, "bss section has nonzero size"); SizesPtr bss_sizes = procs.front()->sizes(shlib_file, bss->mem_range()); ok(bss_sizes, "can get sizes for bss section"); Elf::SectionPtr data = shlib_file->elf()->section(".data"); ok(data, "can find data section"); ok(data->size() > 0, "data section has nonzero size"); SizesPtr data_sizes = procs.front()->sizes(shlib_file, data->mem_range()); ok(data_sizes, "can get sizes for data section"); is(data->size(), bss->size(), "data and bss sections have same size"); is_approx(data_sizes->val(Sizes::MAPPED), bss_sizes->val(Sizes::MAPPED), Elf::page_size(), "data and bss mapped within page of each other"); is_approx(data_sizes->val(Sizes::RESIDENT), bss_sizes->val(Sizes::RESIDENT), Elf::page_size(), "data and bss resident within page of each other"); // Now get the all-proc sizes from the file sizes = shlib_file->sizes(bss->mem_range()); // The totals should be a multiple of the individual is(sizes->val(Sizes::RESIDENT), bss_sizes->val(Sizes::RESIDENT) * NUM_INSTANCES, "Total resident for all procs the same as multiplying up one proc"); is(sizes->val(Sizes::MAPPED), bss_sizes->val(Sizes::MAPPED) * NUM_INSTANCES, "Total mapped for all procs the same as multiplying up one proc"); double bss_resident_arrays_size = 0; double data_resident_arrays_size = 0; double bss_writable_arrays_size = 0; double data_writable_arrays_size = 0; for (int i = 0; i < NUM_ARRAYS; ++i) { if (ARRAY_INFO[i].initialised) { data_resident_arrays_size += ARRAY_SIZE * ARRAY_INFO[i].resident_percent / 100; data_writable_arrays_size += ARRAY_SIZE * ARRAY_INFO[i].writable_percent / 100; } else { bss_resident_arrays_size += ARRAY_SIZE * ARRAY_INFO[i].resident_percent / 100; bss_writable_arrays_size += ARRAY_SIZE * ARRAY_INFO[i].writable_percent / 100; } } for (proc_it = procs.begin(); proc_it != procs.end(); ++proc_it) { sizes = proc->sizes(shlib_file, data->mem_range()); is_approx(sizes->val(Sizes::RESIDENT), data_resident_arrays_size, Elf::page_size(), "resident size for data in proc correct with a page"); is_approx(sizes->val(Sizes::WRITABLE), data_writable_arrays_size, Elf::page_size(), "writable size for data in proc correct with a page"); sizes = proc->sizes(shlib_file, bss->mem_range()); is_approx_rel(sizes->val(Sizes::RESIDENT), bss_resident_arrays_size, 0.001, "correct resident size for bss in proc"); is_approx_rel(sizes->val(Sizes::WRITABLE), bss_writable_arrays_size, 0.001, "correct writable size for bss in proc"); } for (int i = 0; i < NUM_ARRAYS; ++i) { const struct array_info *info = ARRAY_INFO + i; string symname = info->name; Elf::SymbolPtr sym = shlib_file->elf()->symbol(symname); ok(sym, "can find symbol " + symname); is(sym->size(), ARRAY_SIZE, symname + " has correct size"); sizes = proc->sizes(shlib_file, sym->range()); is_approx_rel((int) sizes->val(Sizes::RESIDENT), ARRAY_SIZE * info->resident_percent / 100, 0.001, symname + " has correct resident size"); is_approx_rel(sizes->val(Sizes::WRITABLE), ARRAY_SIZE * info->writable_percent / 100.0, 0.001, symname + " has correct writable size"); // Uninitialised space which is only read appears to be shared // amongst every proc in the system (a 'zero page'?). This is // good from a low-memusage point of view, but it means that // it is shared among nearly all running procs to varying // degrees. So we can't really account for it. if (symname == "uninit_readme" || symname == "uninit_readhalf") { continue; } int expected_eff_resident = ARRAY_SIZE * info->resident_percent / 100; if (info->shared) { expected_eff_resident /= NUM_INSTANCES; } // approximate match since the percentage (fixed pt) // arithmetic could put us off by a factor of 1/100 (and does...:-) is_approx((int) sizes->val(Sizes::EFFECTIVE_RESIDENT), expected_eff_resident, 1 + (expected_eff_resident / 100), symname + " has correct effective size"); } // Non-elf maps procs.clear(); for (proc_it = allprocs.begin(); proc_it != allprocs.end(); ++proc_it) { string cmdline = (*proc_it)->cmdline(); if (cmdline == MI_EXE) { procs.push_back(*proc_it); } } is((int) procs.size(), NUM_INSTANCES, "can find all our mapit procs"); proc = procs.front(); files.clear(); re.compile("/" + MI_DAT + "$"); for (file_it = all_files.begin(); file_it != all_files.end(); ++file_it) { string name = (*file_it)->name(); if (re.matches(name)) { files.push_back(*file_it); } } is((int) files.size(), 1, MI_DAT + " file only listed once"); FilePtr mapped_file = files.front(); ok(!mapped_file->is_elf(), MI_DAT + " isn't an elf file"); off_t fsize = 0; double mi_data_size = 0; ok(file_size(MI_DAT, fsize), "can get file size"); mi_data_size = fsize; ok(mi_data_size > 0, "file has nonzero size"); for (proc_it = procs.begin(); proc_it != procs.end(); ++proc_it) { sizes = (*proc_it)->sizes(mapped_file); is_approx_rel(sizes->val(Sizes::VM), mi_data_size, 0.001, "correct data file size"); is_approx_rel(sizes->val(Sizes::RESIDENT), mi_data_size, 0.001, "whole data file is resident"); is_approx_rel(sizes->val(Sizes::EFFECTIVE_RESIDENT), mi_data_size / NUM_INSTANCES, 0.001, "data file is shared between instances correctly"); } return true; }