/* * JBoss, Home of Professional Open Source. * Copyright 2013, Red Hat, Inc., and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package com.hp.mwtests.ts.jts.utils; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.*; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; /** * Useful class for monitoring a thread and producing a stack dump if it stops making forward progress * (after which the thread will no longer be monitored). For a thread to be monitored it should periodically * report its progress via an instance of @see TaskProgress.tick */ public enum TaskMonitor { /** * Obtain a reference to the TaskMonitor singleton */ INSTANCE; private final String outputDirectory = System.getProperty("user.dir"); private final Collection<Job> jobs = new ArrayList<Job>(); private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); private final Runnable progressChecker; private AtomicInteger maxDumps = new AtomicInteger(1); private long minDelay = Long.MAX_VALUE; private ScheduledFuture<?> progressHandle = null; private TaskMonitor() { progressChecker = new Runnable() { public void run() { long timeNow = System.currentTimeMillis(); synchronized (this) { Iterator<Job> i = jobs.iterator(); while (i.hasNext()) { Job job = i.next(); if (job.isFinished()) { try { i.remove(); } catch (Exception e) { System.out.printf("Error removing monitor job %s%n", e.getMessage()); e.printStackTrace(); } } else if (!job.isProgressing(timeNow)) { createThreadDumps(); } } } } }; } public synchronized TaskProgress monitorProgress(String name, String cmdLinePattern, long millisBetweenUpdates) { return monitorProgress(name, cmdLinePattern, new TaskProgress(0), millisBetweenUpdates); } /** * Monitor the progress of a task * * @param name a name for the task used in the filename of any stack dumps * @param cmdLinePattern pattern used to determine which java process should have there stacks dumpted * @param progress an object that a monnitored thread should periodically update * @param millisBetweenUpdates if the job does not make progress within this many milliseconds take stack dumps * @return get the tasks progress back */ public synchronized TaskProgress monitorProgress(String name, String cmdLinePattern, TaskProgress progress, long millisBetweenUpdates) { if (maxDumps.intValue() <= 0) return null; if (progress == null) progress = new TaskProgress(0); jobs.add(new Job(name, cmdLinePattern, progress, millisBetweenUpdates)); if (millisBetweenUpdates < minDelay) { minDelay = millisBetweenUpdates; if (progressHandle != null) progressHandle.cancel(false); // TODO not a fair algorithm since the task may end up running later that desired but it does the job for now progressHandle = scheduler.scheduleAtFixedRate(progressChecker, millisBetweenUpdates, millisBetweenUpdates, TimeUnit.MILLISECONDS); } return progress; } private void terminateScheduler() { progressHandle.cancel(false); jobs.clear(); scheduler.shutdown(); } private void createThreadDumps() { if (maxDumps.getAndDecrement() <= 0) { terminateScheduler(); return; } try { ProcessBuilder builder = new ProcessBuilder("jps", "-mlv"); java.lang.Process process = builder.start(); Scanner scanner = new Scanner(process.getInputStream()); while (scanner.hasNextLine()) { String cmd = scanner.nextLine(); for (Job job : jobs) if (job.matches(cmd)) createThreadDump(job.getName(), cmd); } scanner.close(); process.destroy(); } catch (IOException e) { System.out.printf("ERROR CREATING THREAD DUMPS: %s\n", e.getMessage()); } if (maxDumps.intValue() == 0) terminateScheduler(); } private void createThreadDump(String name, String jps) { int i = jps.indexOf(' '); String pid = i > 0 ? jps.substring(0, i) : null; if (pid == null) return; ProcessBuilder builder = new ProcessBuilder("jstack", pid); System.out.printf("Creating stack dump for job %s pid %s and cmd %s\n", name, pid, jps); try { java.lang.Process process = builder.start(); String dumpFile = outputDirectory + "/testoutput/jstack." + name + '.' + pid; File df = new File(dumpFile); df.getParentFile().mkdirs(); OutputStream os = new FileOutputStream(df); os.write(jps.getBytes()); os.write(System.getProperty("line.separator").getBytes()); byte[] buffer = new byte[1024]; int len = process.getInputStream().read(buffer); while (len != -1) { os.write(buffer, 0, len); len = process.getInputStream().read(buffer); } os.close(); } catch (IOException e) { System.out.printf("ERROR CREATING THREAD DUMP for %s: %s\n", jps, e.getMessage()); } } class Job { private AtomicInteger progress; private long millisBetweenUpdates; private int lastValue; private long lastTimeChecked; private String cmdLinePattern; private String name; Job(String name, String cmdLinePattern, AtomicInteger progress, long millisBetweenUpdates) { this.progress = progress; this.millisBetweenUpdates = millisBetweenUpdates; this.lastValue = progress.intValue(); this.lastTimeChecked = System.currentTimeMillis(); this.cmdLinePattern = cmdLinePattern; this.name = name; } /** * Check that a job is incrementing an atomic integer at least every millisBetweenUpdates milliseconds. * If the integer is less that zero then the task has completed * * @param currentTimeMillis the current time * @return true if the job is making progress */ boolean isProgressing(long currentTimeMillis) { int currentValue = progress.intValue(); if (!isFinished() && lastTimeChecked + millisBetweenUpdates < currentTimeMillis) { if (currentValue == lastValue) return false; lastTimeChecked = currentTimeMillis; lastValue = currentValue; } return true; } boolean isFinished() { return progress.intValue() < 0; } public boolean matches(String cmd) { return cmdLinePattern != null ? cmd.contains(cmdLinePattern) : true; // use Pattern.compile if more flexibility is required } public String getName() { return name; } } }