/* Copyright 2013 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.arbeitspferde.groningen.utility; import com.google.common.io.Files; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * This class provides primitives for managing the system's processes. */ public class Process { // TODO(drk): rewrite class implementation with help of JNI to support Windows and Linux. private static final Logger log = Logger.getLogger(Process.class.getCanonicalName()); private static final Pattern STAT_FILE_FORMAT = Pattern.compile( "^([0-9-]+)\\s([^\\s]+)\\s[^\\s]\\s([0-9-]+)\\s([0-9-]+)\\s([0-9-]+)\\s([0-9-]+\\s){16}" + "([0-9]+)(\\s[0-9-]+){16}"); /** * This class encapsulates process information. */ public static class ProcessInfo { private final Integer processId; private final Integer processGroupId; private final long startTime; private final String commandLine; public ProcessInfo(int processId, int processGroupId, long startTime, String commandLine) { this.processId = processId; this.processGroupId = processGroupId; this.startTime = startTime; this.commandLine = commandLine; } public Integer getProcessId() { return this.processId; } public Integer getProcessGroupId() { return this.processGroupId; } /** * Returns the creation time of the process in seconds since the start of the epoch. * * @return a long that represents process creation time. */ public long getStartTime() { return this.startTime; } public String getCommandLine() { return this.commandLine; } } /** * Is the process with identifier {@code processId} still alive? * * This method assumes that the process with identifier {@code processId} was alive not too long * ago, and hence assumes no chance of pid-wrapping-around. * * @param processId Process identifier to check. * @return {@code true} if process is alive. */ public static final boolean isAlive(int processId) { java.lang.Process process = null; String[] command = {"kill", "-0", Integer.toString(processId)}; try { process = Runtime.getRuntime().exec(command); } catch (IOException e) { log.log(Level.WARNING, "Error executing shell command " + Arrays.toString(command) + e); return false; } try { process.waitFor(); } catch (InterruptedException e) { return false; } return (process.exitValue() == 0 ? true : false); } /** * @param the identifier of this process or 0 if it cannot be determined. */ public static final int myProcessId() { int processId = 0; try { processId = Integer.parseInt(new File("/proc/self").getCanonicalFile().getName()); } catch (NumberFormatException e) { log.log(Level.WARNING, "Cannot parse PID: " + e); } catch (IOException e) { log.log(Level.WARNING, "PID file cannot be processed: " + e); } return processId; } /** * @param processId the PID of the target process. * @return the {@class ProcessInfo} instance or {@code null} if process information can not be * obtained. */ public static final ProcessInfo getProcessInfo(int processId) { File statStream = null; ProcessInfo info = null; FileReader statReader = null; BufferedReader statDataReader = null; try { statStream = new File("/proc/" + Integer.toString(processId) + "/stat"); statReader = new FileReader(statStream); statDataReader = new BufferedReader(statReader); } catch (FileNotFoundException e) { log.log(Level.WARNING, "PID file doesn't exist: " + "/proc/" + Integer.toString(processId) + "/stat"); return info; } try { String line = statDataReader.readLine(); Matcher matcher = STAT_FILE_FORMAT.matcher(line); if (matcher.find()) { String commandLine = Files.toString(new File("/proc/" + Integer.toString(processId) + "/cmdline"), StandardCharsets.UTF_8); // TODO(drk): read the start time from the stat file. info = new ProcessInfo(processId, Integer.parseInt(matcher.group(4)), statStream.lastModified() / 1000, commandLine); } } catch (IOException e) { log.log(Level.WARNING, "Error reading the stream: " + e); } finally { try { statReader.close(); statDataReader.close(); } catch (IOException e) { log.log(Level.WARNING, "Cannot close the stream"); } } return info; } /** * @return the group identifier of this process. */ public static final int myProcessGroupId() { ProcessInfo info = getProcessInfo(myProcessId()); return info.getProcessGroupId(); } /** * @param processGroupId the process group identifier of the target process group. * @return a list of process identifiers that have same group process group identifier. */ public static final List<Integer> getProcessIdsOf(int processGroupId) { File[] procDir = (new File("/proc")).listFiles(); List<Integer> processIds = new ArrayList(); for (File processDir : procDir) { int processId; try { processId = Integer.parseInt(processDir.getName()); } catch (NumberFormatException e) { continue; } ProcessInfo info = getProcessInfo(processId); if (info != null && info.getProcessGroupId() == processGroupId) { processIds.add(processId); } } return processIds; } public static final void restartProcessGroup(int processGroupId) { // TODO(drk): implement this. log.log(Level.INFO, "Restart group of processes " + processGroupId); } public static final void restartProcess(int processId) { // TODO(drk): implement this. log.log(Level.INFO, "Restart process " + processId); } }