/** * edu.utexas.GeDBIT.mckoi.store.BufferManager 27 Jan 2003 * * Mckoi SQL Database ( http://www.mckoi.com/database ) * Copyright (C) 2000, 2001, 2002 Diehl and Associates, Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * Version 2 as published by the Free Software Foundation. * * 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 Version 2 for more details. * * You should have received a copy of the GNU General Public License * Version 2 along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * Change Log: * 2005.05.24: Added new methods to compute buffer statistics, by Willard * */ package GeDBIT.mckoi.store; import java.io.IOException; import java.io.RandomAccessFile; import java.util.Arrays; import java.util.ArrayList; import java.util.Comparator; /** * A centralized manager that manages one or more AbstractBufferedFile * implementstions. This object manages memory usage of the children buffers and * implements a caching strategy for purging old pages from memory. * * @author Tobias Downer */ @SuppressWarnings("rawtypes") public final class BufferManager { /** * A timer that represents the T value in buffer pages. */ private long current_T; /** * The complete list of PageBuffer objects that have been created by the * children file buffers. */ private ArrayList page_list; /** * A unique id key counter given to AbstractBufferedFile implementations * created for this manager. */ private int unique_id_seq; /** * The paging implementation (either 'Java IO' or 'Java NIO') to use. */ private final String paging_implementation; /** * The maximum number of pages that should be kept in memory before pages * are purged from memory. */ private final int max_pages; /** * The size of each page. */ private final int page_size; /** * The current number of pages that are in memory. */ private int current_page_count; /** * The number of cache hits. */ private long cache_hit_count; /** * The number of cache misses. */ private long cache_miss_count; /** * Added by Willard, 2005.05.24 */ @SuppressWarnings("unused") private PageBuffer page_with_root; /** * Added by Willard, 2005.05.24 */ private ArrayList write_times; /** * Added by Willard, 2005.05.24 */ private ArrayList read_times; /** * Constructs the manager. */ public BufferManager(String paging_implementation, int max_pages, int page_size) { this.paging_implementation = paging_implementation; this.max_pages = max_pages; this.page_size = page_size; current_T = 0; page_list = new ArrayList(); unique_id_seq = 0; cache_hit_count = 0; cache_miss_count = 0; page_with_root = null; write_times = new ArrayList(); read_times = new ArrayList(); } /** * Creates and returns a FileBufferAccess object that is used to access the * RandomAccessFile through this buffer manager. */ public synchronized FileBufferAccessor createBufferedAccessor( RandomAccessFile file, String mode) throws IOException { int id_val = unique_id_seq; ++unique_id_seq; if (paging_implementation.equals("Java IO")) { return new IOBufferedFile(id_val, file, this); } else if (paging_implementation.equals("Java NIO")) { return new NIOBufferedFile(id_val, file, mode, this); } else { throw new RuntimeException( "No not know how to create a buffered accessor for paging " + "implementation: " + paging_implementation); } } /** * Returns the page size to use for the given RandomAccessFile with the * given unique id. */ int getPageSizeFor(int unique_id, RandomAccessFile file) { return page_size; } /** * A callback from an AbstractBufferedFile notifying the manager that a new * page has been created and added to the page cache. */ @SuppressWarnings("unchecked") synchronized void pageCreated(PageBuffer buffer) throws IOException { cacheMiss(); buffer.setT(current_T); ++current_T; ++current_page_count; page_list.add(buffer); // Below is the page purge algorithm. If the maximum number of pages // has been created we sort the page list weighting each page by time // since // last accessed and total number of accesses and clear the bottom 25% // of this list. // Check if we should purge old pages and purge some if we do... if (current_page_count > max_pages) { write_times.add(new Double(System.currentTimeMillis())); // keep root node in cache /* * if (page_with_root == null) { int size = page_list.size(); * PageBuffer page; PageBuffer root_page = (PageBuffer) * page_list.get(0); int root_page_location = 0; for (int i = 1; i < * size; i++) { page = (PageBuffer) page_list.get(i); if * (page.getPageNumber() > root_page.getPageNumber()) { root_page = * page; root_page_location = i; } } //page_with_root = // * (PageBuffer)page_list.remove(root_page_location); page_with_root * = (PageBuffer) page_list.get(root_page_location); * System.out.println("number of pages:" + * page_with_root.getPageNumber()); } */ // Purge 25% of the cache // Sort the pages by the current formula, // ( 1 / page_access_count ) * (current_t - page_t) // See the 'pageEnumValue' value for weighting algorithm // implementation. Object[] pages = page_list.toArray(); Arrays.sort(pages, new PageCacheComparator()); int purge_size = Math.max((int) (pages.length * 0.20f), 2); for (int i = 0; i < purge_size; ++i) { PageBuffer page = (PageBuffer) pages[pages.length - (i + 1)]; synchronized (page) { page.dispose(); } } // System.out.println("Clearing " + purge_size + " pages."); /* * System.out.print("Top pages = "); for (int n = 0; n < 32; ++n) { * System.out.print(pages[n]); System.out.print(", "); } * System.out.println(); System.out.println("Cache hits: " + * cache_hit_count); System.out.println("Cache misses: " + * cache_miss_count);/* System.out.print("Bottom pages = "); for * (int n = pages.length-1; n > 8; n--) { * System.out.print(pages[n]); System.out.print(", "); } */ // Remove all the elements from page_list and set it with the sorted // list (minus the elements we removed). page_list.clear(); for (int i = 0; i < pages.length - purge_size; ++i) { page_list.add(pages[i]); } current_page_count -= purge_size; write_times.add(new Double(System.currentTimeMillis())); } } /** * A callback from an AbstractBufferedFile notifying the manager that a page * has been accessed. */ void pageAccessed(PageBuffer buffer) { synchronized (this) { cacheHit(); buffer.setT(current_T); ++current_T; buffer.incrementAccessCounter(); } } /** * Updates the cache hit statistic. */ final void cacheHit() { synchronized (this) { ++cache_hit_count; // System.out.print(" cache hit: "+cache_hit_count+" // cur_pages_in_memory: "+current_page_count); } } /** * Updates the cache miss statistic. */ final void cacheMiss() { synchronized (this) { ++cache_miss_count; } } /** * The calculation for finding the 'weight' of a page in the cache. A * heavier page is sorted lower and is therefore cleared from the cache * faster. */ private final float pageEnumValue(PageBuffer page) { // We fix the access counter so it can not exceed 10000 accesses. I'm // a little unsure if we should put this constant in the equation but it // ensures that some old but highly accessed page will not stay in the // cache forever. return (1f / Math.min(page.getAccessCounter(), 10000)) * (current_T - page.getT()); } // ---------- Inner classes ---------- /** * A Comparator used to sort cache entries. */ private class PageCacheComparator implements Comparator { public int compare(Object ob1, Object ob2) { float v1 = pageEnumValue((PageBuffer) ob1); float v2 = pageEnumValue((PageBuffer) ob2); if (v1 > v2) { return 1; } else if (v1 < v2) { return -1; } return 0; } } /** * @return Returns the cache_hit_count. */ public long getCache_hit_count() { return cache_hit_count; } /** * @return Returns the cache_miss_count. */ public long getCache_miss_count() { return cache_miss_count; } /** * @param l */ @SuppressWarnings("unchecked") public void addTime(long l) { read_times.add(new Double(l)); } private double total_write_time; private double avg_write_time; private double total_read_time; private double avg_read_time; private double highest_read_time; private double lowest_read_time; private double total_i_o; @SuppressWarnings("unchecked") private void computeStatistics() { int write_times_length = write_times.size(); int read_times_length = read_times.size(); ArrayList put_times = new ArrayList(write_times_length / 2); ArrayList get_times = new ArrayList(read_times_length / 2); // get read times// double start_read = 0; double end_read = 0; for (int i = 0; i < read_times_length; i += 2) { start_read = ((Double) read_times.get(i)).doubleValue(); end_read = ((Double) read_times.get(i + 1)).doubleValue(); get_times.add(new Double(end_read - start_read)); } total_read_time = 0; int get_length = get_times.size(); highest_read_time = 0; lowest_read_time = 1000000; double this_read_time; for (int i = 0; i < get_length; i++) { this_read_time = ((Double) get_times.get(i)).doubleValue(); if (this_read_time > highest_read_time) { highest_read_time = this_read_time; } if (this_read_time < lowest_read_time) { lowest_read_time = this_read_time; } total_read_time += this_read_time; } avg_read_time = total_read_time / get_length; // get write times// if (write_times_length > 0) { double start_write = 0; double end_write = 0; for (int i = 0; i < write_times_length; i += 2) { start_write = ((Double) write_times.get(i)).doubleValue(); end_write = ((Double) write_times.get(i + 1)).doubleValue(); put_times.add(new Double(end_write - start_write)); } total_write_time = 0; int put_length = put_times.size(); for (int i = 0; i < put_length; i++) { total_write_time += ((Double) put_times.get(i)).doubleValue(); } avg_write_time = total_write_time / put_length; total_i_o = total_read_time + total_write_time; } else { total_write_time = 0; avg_write_time = 0; total_i_o = total_read_time; } } /** * Added by Willard, 2005.05.24 */ public void printStatistics() { computeStatistics(); System.out.println("Hits: " + (cache_hit_count)); System.out.println("Misses: " + (cache_miss_count)); System.out.println("Total Write Time:" + (total_write_time / 1000) + " seconds."); System.out.println("Avg Write Time:" + (avg_write_time / 1000) + " seconds."); System.out.println("Total Read Time:" + (total_read_time / 1000) + " seconds."); System.out.println("Avg Read Time:" + (avg_read_time / 1000) + " seconds."); System.out.println("Highest Read Time:" + (highest_read_time / 1000) + " seconds."); System.out.println("Lowest Read Time:" + (lowest_read_time / 1000) + " seconds."); System.out .println("Total I/O Time:" + (total_i_o / 1000) + " seconds."); } }