/* This file is part of VoltDB. * Copyright (C) 2008-2010 VoltDB L.L.C. * * VoltDB is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * VoltDB 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 for more details. * * You should have received a copy of the GNU General Public License * along with VoltDB. If not, see <http://www.gnu.org/licenses/>. */ package org.voltdb.utils; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.lang.ref.WeakReference; import java.net.ServerSocket; import java.net.Socket; import java.net.UnknownHostException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.HashSet; /** * <p>Provide a robust, flexible and totally self-contained way for pieces of the * system to dump state (initially to disk).</p> * * <p>Note that Dumpable classes need to be aware of thread-safeness of their * dumps. The system allows the Dumpable implementer to differentiate between * unsafe reads and safe reads. It's also just fine to do both.</p> * */ @Deprecated public class DumpManager extends Thread { /** * Interface for things that want to respond to * systemwide dump requests. */ public static interface Dumpable { /** * Tell a Dumpable to initiate a dump. This is asking it to call * Dumpable.putDump(..) at some point soon, maybe more than once. * * @param timestamp A timestamp from when the dump was requested that * globally identifies the dump. */ public void goDumpYourself(long timestamp); } /** * Since all the goDumpYourself calls might take a big fraction of a second, * this class allows them to run in parallel in the background. */ static class DumperThread extends Thread { Dumpable m_dumper; long m_timestamp; DumperThread(Dumpable dumper, long timestamp) { m_dumper = dumper; m_timestamp = timestamp; } @Override public void run() { //System.err.printf("About to dump instance of type: %s\n", m_dumper.getClass().getName()); m_dumper.goDumpYourself(m_timestamp); } } static final int DUMPMANAGER_PORT = 21217; /** * Set of active or recently active dumper threads. A list must be kept to keep * them from garbage collecting. Dead threads are cleaned up on subsequent calls * to dump. */ static HashSet<DumperThread> m_dumperThreads = new HashSet<DumperThread>(); /** * Time of the previous call to requestGlobalDump(). * Used to eliminate duplicate dumps; */ static long m_previousDump = 0; /** * Map of registered dumpers by id */ static HashMap<String, WeakReference<Dumpable>> m_dumpers = new HashMap<String, WeakReference<Dumpable>>(); static DumpManager m_dumpManager = new DumpManager(); /** Method to start the listener thread for DumpManager */ public synchronized static void init() { if (m_dumpManager.isAlive() == false) try { m_dumpManager.start(); } catch (Exception e) { // nothing to do here } } // prevent this from being instantiated elsewheres private DumpManager() {} /** * Notify the DumpManager that the thing passed in wants to be part of * the dump process. * * @param id The string id of the dumping object (globally unique) * @param dumper The object that implements the Dumpable interface that * will be involved in all dumps. */ public synchronized static void register(String id, Dumpable dumper) { if ((id.indexOf(":") >= 0) || (id.indexOf(":") >= 0)) throw new RuntimeException("DumpManager Error: Dumper ids cannot contain colons or slashes (" + id + ")."); // use a weak reference to make cleanup easier m_dumpers.put(id, new WeakReference<Dumpable>(dumper)); } /** * Ask the DumpManager to get all registered Dumpables to dump state * to text files as best as they are able. * @param timestamp A timestamp to uniquely identify this dump. */ public synchronized static void requestGlobalDump(long timestamp) { // make sure timestamp is unique if (timestamp == m_previousDump) return; m_previousDump = timestamp; // actually request all the dumps for (WeakReference<Dumpable> dumpRef : m_dumpers.values()) { Dumpable dumper = dumpRef.get(); if (dumper != null) { DumperThread dt = new DumperThread(dumper, timestamp); dt.start(); m_dumperThreads.add(dt); } } // remove dead dumpers HashSet<DumperThread> deadDumpers = new HashSet<DumperThread>(); for (DumperThread dt : m_dumperThreads) if (dt.isAlive() == false) deadDumpers.add(dt); for (DumperThread dt : deadDumpers) m_dumperThreads.remove(dt); } /** * Respond to a dump request by returning content to the DumpManager. * * @param id The string id of the dumping object. * @param timestamp The timestamp sent with the dump request (unique per dump). * @param wasThreadsafeDump Should this dump be considered a unique slice, or was it * collected in an unsafe way. * @param dumpvalue The contents to be written to the file. */ // public synchronized static void putDump(String id, long timestamp, boolean wasThreadsafeDump, VoltThreadContext dumpvalue) { // File outpath = getPathForDumper(id, timestamp, wasThreadsafeDump); // ObjectOutputStream oos; // try { // oos = new ObjectOutputStream(new FileOutputStream(outpath)); // oos.writeObject(dumpvalue); // oos.close(); // } catch (IOException e) { // e.printStackTrace(); // } // // } /** * Given info about the dump, return the path to the file where information should be put. * * @param id The string id of the dumping object. * @param timestamp The timestamp sent with the dump request (unique per dump). * @param wasThreadsafeDump Should this dump be considered a unique slice, or was it * collected in an unsafe way. * @return A File instance representing the place to put content. */ private static File getPathForDumper(String id, long timestamp, boolean wasThreadsafeDump) { // make a dateformat that doesn't contain colons or slashes for platform-friendliness SimpleDateFormat df = new SimpleDateFormat("yy.MM.dd-HH.mm.ss-SSS'ms'"); // these will likely be the same thing... but who knows String now = df.format(new Date()); String dumptime = df.format(new Date(timestamp)); String filename = id + "-" + now + (wasThreadsafeDump ? "-safe" : "-unsafe") + ".voltdump"; // assume slashes are path separators String foldername = "dumps/dump-" + dumptime; boolean success = new File(foldername).mkdirs(); assert(success); // assume slashes are path separators return new File(foldername + "/" + filename); } @Override public void run() { ServerSocket server = null; try { server = new ServerSocket(DUMPMANAGER_PORT); } catch (IOException e) { e.printStackTrace(); } while (true) { try { Socket sock = server.accept(); DataInputStream dis = new DataInputStream(sock.getInputStream()); long timestamp = dis.readLong(); sock.getOutputStream().write('a'); sock.getOutputStream().flush(); sock.close(); requestGlobalDump(timestamp); } catch (IOException e) { e.printStackTrace(); } } } /** * Request a dump on a particular hostname. * @param hostname */ public static boolean requestDump(String hostname, long timestamp) { try { // connect to server Socket sock = new Socket(hostname, DUMPMANAGER_PORT); // write the timestamp to the socket DataOutputStream dos = new DataOutputStream(sock.getOutputStream()); dos.writeLong(timestamp); dos.flush(); // wait for pretty much any response int data = sock.getInputStream().read(); // close the socket and check for sanity sock.close(); return data == 'a'; } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return false; } /** * Call a dump on localhost by default, but with other parameters optionally */ public static void main(String[] args) { String hostnames[] = new String[] { "localhost" }; // parse an optional arg in kinda a hacky way ArrayList<String> hostnameList = new ArrayList<String>(); if (args.length > 0) { for (String arg : args) { String hostname = arg.trim(); if ((hostname.length() == 0) || hostname.startsWith("${")) continue; hostnameList.add(hostname); } } if (hostnameList.size() > 0) hostnames = hostnameList.toArray(new String[0]); long now = System.currentTimeMillis(); for (String hostname : hostnames) { System.out.printf("Trying to connect to %s\n", hostname); System.out.flush(); if (requestDump(hostname, now)) System.out.printf("Asyncronous dump in progress.\n"); else System.out.printf("Dump Failed. See stack trace.\n"); System.out.flush(); } } }