137f7c3a73d290eed1bf4cdff80e029eaa7620801jessewilson@google.com/* 237f7c3a73d290eed1bf4cdff80e029eaa7620801jessewilson@google.com * Copyright (C) 2010 The Android Open Source Project 337f7c3a73d290eed1bf4cdff80e029eaa7620801jessewilson@google.com * 437f7c3a73d290eed1bf4cdff80e029eaa7620801jessewilson@google.com * Licensed under the Apache License, Version 2.0 (the "License"); 537f7c3a73d290eed1bf4cdff80e029eaa7620801jessewilson@google.com * you may not use this file except in compliance with the License. 637f7c3a73d290eed1bf4cdff80e029eaa7620801jessewilson@google.com * You may obtain a copy of the License at 737f7c3a73d290eed1bf4cdff80e029eaa7620801jessewilson@google.com * 837f7c3a73d290eed1bf4cdff80e029eaa7620801jessewilson@google.com * http://www.apache.org/licenses/LICENSE-2.0 937f7c3a73d290eed1bf4cdff80e029eaa7620801jessewilson@google.com * 1037f7c3a73d290eed1bf4cdff80e029eaa7620801jessewilson@google.com * Unless required by applicable law or agreed to in writing, software 1137f7c3a73d290eed1bf4cdff80e029eaa7620801jessewilson@google.com * distributed under the License is distributed on an "AS IS" BASIS, 1237f7c3a73d290eed1bf4cdff80e029eaa7620801jessewilson@google.com * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1337f7c3a73d290eed1bf4cdff80e029eaa7620801jessewilson@google.com * See the License for the specific language governing permissions and 1437f7c3a73d290eed1bf4cdff80e029eaa7620801jessewilson@google.com * limitations under the License. 1537f7c3a73d290eed1bf4cdff80e029eaa7620801jessewilson@google.com */ 1637f7c3a73d290eed1bf4cdff80e029eaa7620801jessewilson@google.com 1737f7c3a73d290eed1bf4cdff80e029eaa7620801jessewilson@google.compackage vogar.monitor; 1837f7c3a73d290eed1bf4cdff80e029eaa7620801jessewilson@google.com 19b9e409160756d5f422da6a56cd2354d0afb27047Neil Fullerimport com.google.gson.JsonElement; 20b9e409160756d5f422da6a56cd2354d0afb27047Neil Fullerimport com.google.gson.JsonObject; 21693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.comimport java.io.BufferedInputStream; 22693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.comimport java.io.IOException; 23693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.comimport java.io.InputStream; 24693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.comimport java.io.InputStreamReader; 25693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.comimport java.net.ConnectException; 26693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.comimport java.net.Socket; 27693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.comimport java.net.SocketException; 28693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.comimport java.nio.charset.Charset; 29b5c5c44d0c0a01c278cdac68ae23646682eb8ef7jessewilson@google.comimport vogar.Log; 3037f7c3a73d290eed1bf4cdff80e029eaa7620801jessewilson@google.comimport vogar.Outcome; 31693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.comimport vogar.Result; 32693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.comimport vogar.util.IoUtils; 3337f7c3a73d290eed1bf4cdff80e029eaa7620801jessewilson@google.com 3437f7c3a73d290eed1bf4cdff80e029eaa7620801jessewilson@google.com/** 35693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com * Connects to a target process to monitor its action using XML over raw 36693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com * sockets. 3737f7c3a73d290eed1bf4cdff80e029eaa7620801jessewilson@google.com */ 38693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.compublic final class HostMonitor { 39693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com private static final Charset UTF8 = Charset.forName("UTF-8"); 4037f7c3a73d290eed1bf4cdff80e029eaa7620801jessewilson@google.com 41b5c5c44d0c0a01c278cdac68ae23646682eb8ef7jessewilson@google.com private Log log; 42693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com private Handler handler; 43693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com private final String marker = "//00xx"; 4437f7c3a73d290eed1bf4cdff80e029eaa7620801jessewilson@google.com 45b5c5c44d0c0a01c278cdac68ae23646682eb8ef7jessewilson@google.com public HostMonitor(Log log, Handler handler) { 46b5c5c44d0c0a01c278cdac68ae23646682eb8ef7jessewilson@google.com this.log = log; 47693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com this.handler = handler; 48693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com } 49693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com 503cc430f91313dab5074cffa6508c0b47cd9f2b50jessewilson@google.com /** 513cc430f91313dab5074cffa6508c0b47cd9f2b50jessewilson@google.com * Returns true if the target process completed normally. 523cc430f91313dab5074cffa6508c0b47cd9f2b50jessewilson@google.com */ 533cc430f91313dab5074cffa6508c0b47cd9f2b50jessewilson@google.com public boolean attach(int port) throws IOException { 54693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com for (int attempt = 0; true; attempt++) { 55693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com Socket socket = null; 56693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com try { 57693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com socket = new Socket("localhost", port); 58693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com InputStream in = new BufferedInputStream(socket.getInputStream()); 59693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com if (checkStream(in)) { 60b5c5c44d0c0a01c278cdac68ae23646682eb8ef7jessewilson@google.com log.verbose("action monitor connected to " + socket.getRemoteSocketAddress()); 613cc430f91313dab5074cffa6508c0b47cd9f2b50jessewilson@google.com return followStream(in); 62693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com } 63693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com } catch (ConnectException ignored) { 64693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com } catch (SocketException ignored) { 65693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com } finally { 66693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com IoUtils.closeQuietly(socket); 67693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com } 68693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com 69b5c5c44d0c0a01c278cdac68ae23646682eb8ef7jessewilson@google.com log.verbose("connection " + attempt + " to localhost:" 70693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com + port + " failed; retrying in 1s"); 71693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com try { 72693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com Thread.sleep(1000); 73693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com } catch (InterruptedException ignored) { 74693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com } 75693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com } 76693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com } 77693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com 78693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com /** 79693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com * Somewhere between the host and client process, broken socket connections 80693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com * are being accepted. Before we try to do any work on such a connection, 81693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com * check it to make sure it's not dead! 82693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com * 83693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com * TODO: file a bug (against adb?) for this 84693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com */ 85693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com private boolean checkStream(InputStream in) throws IOException { 86693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com in.mark(1); 87693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com if (in.read() == -1) { 88693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com return false; 89693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com } else { 90693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com in.reset(); 91693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com return true; 92693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com } 93693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com } 94693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com 953cc430f91313dab5074cffa6508c0b47cd9f2b50jessewilson@google.com public boolean followStream(InputStream in) throws IOException { 963cc430f91313dab5074cffa6508c0b47cd9f2b50jessewilson@google.com return followProcess(new InterleavedReader(marker, new InputStreamReader(in, UTF8))); 97693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com } 98693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com 99693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com /** 100693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com * Our wire format is a mix of strings and the JSON values like the following: 101693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com * 102693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com * {"outcome"="java.util.FormatterMain"} 103693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com * {"result"="SUCCESS"} 104bd8bda78cc5740aeae8de67b52f1d86e208f4864jessewilson@google.com * {"outcome"="java.util.FormatterTest#testBar" runner="vogar.target.junit.JUnitRunner"} 105693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com * {"result"="SUCCESS"} 1063cc430f91313dab5074cffa6508c0b47cd9f2b50jessewilson@google.com * {"completedNormally"=true} 107693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com */ 1083cc430f91313dab5074cffa6508c0b47cd9f2b50jessewilson@google.com private boolean followProcess(InterleavedReader reader) throws IOException { 109693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com String currentOutcome = null; 110693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com StringBuilder output = new StringBuilder(); 1113cc430f91313dab5074cffa6508c0b47cd9f2b50jessewilson@google.com boolean completedNormally = false; 112693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com 113693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com Object o; 114693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com while ((o = reader.read()) != null) { 115693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com if (o instanceof String) { 116693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com String text = (String) o; 117693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com if (currentOutcome != null) { 118693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com output.append(text); 119693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com handler.output(currentOutcome, text); 120693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com } else { 121693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com handler.print(text); 122693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com } 123693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com } else if (o instanceof JsonObject) { 124693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com JsonObject jsonObject = (JsonObject) o; 125693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com if (jsonObject.get("outcome") != null) { 126693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com currentOutcome = jsonObject.get("outcome").getAsString(); 127693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com handler.output(currentOutcome, ""); 128693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com JsonElement runner = jsonObject.get("runner"); 129693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com String runnerClass = runner != null ? runner.getAsString() : null; 1303cc430f91313dab5074cffa6508c0b47cd9f2b50jessewilson@google.com handler.start(currentOutcome, runnerClass); 131693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com } else if (jsonObject.get("result") != null) { 132693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com Result currentResult = Result.valueOf(jsonObject.get("result").getAsString()); 1333cc430f91313dab5074cffa6508c0b47cd9f2b50jessewilson@google.com handler.finish(new Outcome(currentOutcome, currentResult, output.toString())); 134693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com output.delete(0, output.length()); 135693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com currentOutcome = null; 1363cc430f91313dab5074cffa6508c0b47cd9f2b50jessewilson@google.com } else if (jsonObject.get("completedNormally") != null) { 1373cc430f91313dab5074cffa6508c0b47cd9f2b50jessewilson@google.com completedNormally = jsonObject.get("completedNormally").getAsBoolean(); 138693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com } 139693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com } else { 140693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com throw new IllegalStateException("Unexpected object: " + o); 141693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com } 142693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com } 1433cc430f91313dab5074cffa6508c0b47cd9f2b50jessewilson@google.com 1443cc430f91313dab5074cffa6508c0b47cd9f2b50jessewilson@google.com return completedNormally; 145693bab6f249797311a9c4bcd4c9d9c7cfd5ae8d3jessewilson@google.com } 14637f7c3a73d290eed1bf4cdff80e029eaa7620801jessewilson@google.com 14737f7c3a73d290eed1bf4cdff80e029eaa7620801jessewilson@google.com 14837f7c3a73d290eed1bf4cdff80e029eaa7620801jessewilson@google.com /** 14937f7c3a73d290eed1bf4cdff80e029eaa7620801jessewilson@google.com * Handles updates on the outcomes of a target process. 15037f7c3a73d290eed1bf4cdff80e029eaa7620801jessewilson@google.com */ 15137f7c3a73d290eed1bf4cdff80e029eaa7620801jessewilson@google.com public interface Handler { 15237f7c3a73d290eed1bf4cdff80e029eaa7620801jessewilson@google.com 15337f7c3a73d290eed1bf4cdff80e029eaa7620801jessewilson@google.com /** 15437f7c3a73d290eed1bf4cdff80e029eaa7620801jessewilson@google.com * @param runnerClass can be null, indicating nothing is actually being run. This will 15537f7c3a73d290eed1bf4cdff80e029eaa7620801jessewilson@google.com * happen in the event of an impending error. 15637f7c3a73d290eed1bf4cdff80e029eaa7620801jessewilson@google.com */ 1573cc430f91313dab5074cffa6508c0b47cd9f2b50jessewilson@google.com void start(String outcomeName, String runnerClass); 15837f7c3a73d290eed1bf4cdff80e029eaa7620801jessewilson@google.com 15937f7c3a73d290eed1bf4cdff80e029eaa7620801jessewilson@google.com /** 16037f7c3a73d290eed1bf4cdff80e029eaa7620801jessewilson@google.com * Receive a completed outcome. 16137f7c3a73d290eed1bf4cdff80e029eaa7620801jessewilson@google.com */ 1623cc430f91313dab5074cffa6508c0b47cd9f2b50jessewilson@google.com void finish(Outcome outcome); 16337f7c3a73d290eed1bf4cdff80e029eaa7620801jessewilson@google.com 16437f7c3a73d290eed1bf4cdff80e029eaa7620801jessewilson@google.com /** 16537f7c3a73d290eed1bf4cdff80e029eaa7620801jessewilson@google.com * Receive partial output from an action being executed. 16637f7c3a73d290eed1bf4cdff80e029eaa7620801jessewilson@google.com */ 16737f7c3a73d290eed1bf4cdff80e029eaa7620801jessewilson@google.com void output(String outcomeName, String output); 1683c513c52be35990697865d1ad171378565cae1f0jsharpe@google.com 1693c513c52be35990697865d1ad171378565cae1f0jsharpe@google.com /** 1703c513c52be35990697865d1ad171378565cae1f0jsharpe@google.com * Receive a string to print immediately 1713c513c52be35990697865d1ad171378565cae1f0jsharpe@google.com */ 1723c513c52be35990697865d1ad171378565cae1f0jsharpe@google.com void print(String string); 17337f7c3a73d290eed1bf4cdff80e029eaa7620801jessewilson@google.com } 17437f7c3a73d290eed1bf4cdff80e029eaa7620801jessewilson@google.com} 175