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