001    package tynamo_watchdog;
002    
003    import java.io.IOException;
004    import java.util.ArrayList;
005    import java.util.Arrays;
006    import java.util.Date;
007    import java.util.List;
008    import java.util.Properties;
009    
010    import javax.mail.Message;
011    import javax.mail.MessagingException;
012    import javax.mail.Session;
013    import javax.mail.Transport;
014    import javax.mail.internet.InternetAddress;
015    import javax.mail.internet.MimeMessage;
016    
017    public class Watchdog {
018            public static final String SMTP_HOST = "smtp.host";
019            public static final String SMTP_PORT = "smtp.port";
020            public static final String SEND_EMAIL = "watchdog.sendemail";
021            public static final String EMAIL_PATH = "watchdog.emailpath";
022            public static final String COMMAND = "watchdog.command";
023            public static final String KEEPALIVE_INTERVAL = "watchdog.keepalive";
024            public static final String FINALALARM_DELAY = "watchdog.alarmdelay";
025    
026            public static final String STOP_MESSAGE = Watchdog.class.getSimpleName();
027    
028            private String emailRecipient;
029            private String smtpHost;
030            private Integer smtpPort;
031            private String appName;
032            private String hostname;
033    
034            private long lastOk;
035            private long keepAliveInterval = 5000L;
036            private long finalAlarmDelay = 60000L;
037            private boolean warningSent;
038    
039            public Watchdog(String appName, String emailRecipient, String smtpHost, String smtpPort, Long keepAliveInterval, Long finalAlarmDelay) {
040                    this.appName = appName;
041                    this.emailRecipient = emailRecipient;
042                    this.smtpHost = smtpHost;
043                    if (keepAliveInterval != null) this.keepAliveInterval = keepAliveInterval;
044                    if (finalAlarmDelay != null) this.finalAlarmDelay = finalAlarmDelay;
045                    // FIXME catch numberFormatException
046                    this.smtpPort = smtpPort == null ? null : Integer.valueOf(smtpPort);
047                    hostname = System.getenv("HOSTNAME");
048                    if (hostname == null) hostname = "localhost.localdomain";
049                    lastOk = System.currentTimeMillis();
050            }
051    
052            public static void main(String[] args) throws Exception {
053                    // With no arguments, print out the help and exit
054                    List<String> arguments = new ArrayList<String>(Arrays.asList(args));
055    
056                    if (args.length <= 0 || arguments.contains("--help")) {
057                            System.out.println("Tynamo watchdog. This application is designed to run as a child process ");
058                            return;
059                    }
060    
061                    String appName = args.length > 0 ? args[0] : "dev/exploded";
062                    sleep(5000);
063                    String value = System.getProperty(KEEPALIVE_INTERVAL);
064                    Long keepAliveInterval = null;
065                    try {
066                            keepAliveInterval = Long.valueOf(value);
067                    } catch (NumberFormatException e) {
068                    }
069                    value = System.getProperty(FINALALARM_DELAY);
070                    Long finalAlarmDelay = null;
071                    try {
072                            finalAlarmDelay = Long.valueOf(value);
073                    } catch (NumberFormatException e) {
074                    }
075    
076                    Watchdog watchdog = new Watchdog(appName, System.getProperty(SEND_EMAIL), System.getProperty(SMTP_HOST), System.getProperty(SMTP_PORT),
077                                    keepAliveInterval, finalAlarmDelay);
078                    watchdog.go();
079            }
080    
081            public void go() {
082                    try {
083                            while (lastOk + finalAlarmDelay > System.currentTimeMillis())
084                                    makeRounds();
085                    } catch (IOException e) {
086                            System.err.println("Parent process stopped at " + (new Date()));
087                    }
088                    // Exited either because of exception thrown or because exceeded finalAlarmDelay
089                    // BY default, send the application lost email. makeRounds() will System.exit immediately if STOP_MESSAGE is
090                    // received
091                    sendApplicationLostEmail();
092            }
093    
094            private static void sleep(long millis) {
095                    try {
096                            Thread.sleep(millis);
097                    } catch (InterruptedException e) {
098                    }
099            }
100    
101            /**
102             * makeRounds() will make system exit immediately if STOP_MESSAGE is received
103             * 
104             * @throws IOException
105             */
106            public void makeRounds() throws IOException {
107                    int available = 0;
108                    byte[] bytes = new byte[STOP_MESSAGE.getBytes().length];
109    
110                    while ((available = System.in.available()) > 0) {
111                            if (available >= STOP_MESSAGE.getBytes().length) System.exit(0);
112                            // skip() didn't seem to work for standard input
113                            System.in.read(bytes, 0, available);
114                            lastOk = System.currentTimeMillis();
115                            warningSent = false;
116                            // Normally, read at half the rate of the writes
117                            sleep(keepAliveInterval * 2);
118                    }
119                    // Send the first warning
120                    if (!warningSent) sendRunningSlowEmail();
121                    warningSent = true;
122                    sleep(keepAliveInterval);
123            }
124    
125            void sendRunningSlowEmail() {
126                    String subject = "Application " + appName + " is running slow!";
127                    StringBuilder sb = new StringBuilder();
128                    sb.append("Master application '");
129                    sb.append(appName);
130                    sb.append("' at ");
131                    sb.append(hostname);
132                    sb.append(" has missed sending some alive signals. The last OK was received at ");
133                    sb.append(new Date(lastOk));
134                    sb.append(". \n");
135                    sb.append("This may indicate the application has dead-locked, been unexpectedly terminated or is running out of resources. \n");
136                    sb.append("Action taken: email sent to '");
137                    sb.append(emailRecipient);
138                    sb.append("', still monitoring\n");
139                    try {
140                            sendEmail(subject, sb);
141                    } catch (MessagingException e) {
142                            System.err.println("Couldn't send warning email because of: " + e.getMessage());
143                    }
144            }
145    
146            void sendApplicationLostEmail() {
147                    String subject = "Application " + appName + " has failed!";
148                    StringBuilder sb = new StringBuilder();
149                    sb.append("Master application '");
150                    sb.append(appName);
151                    sb.append("' at ");
152                    sb.append(hostname);
153                    sb.append(" was lost at ");
154                    sb.append(new Date());
155                    sb.append("\n");
156                    sb.append("Action taken: email sent to '");
157                    sb.append(emailRecipient);
158                    sb.append("'\n");
159    
160                    try {
161                            sendEmail(subject, sb);
162                    } catch (MessagingException e) {
163                            // TODO Auto-generated catch block
164                            e.printStackTrace();
165                    }
166            }
167    
168            boolean sendEmail(String subject, StringBuilder content) throws MessagingException {
169                    if (emailRecipient == null || emailRecipient.isEmpty()) return false;
170                    System.out.println("Sending email to: " + emailRecipient + " " + System.getProperty(SMTP_PORT));
171                    boolean debug = false;
172    
173                    // Set the host smtp address
174                    Properties props = new Properties();
175                    props.put("mail.smtp.host", smtpHost);
176                    props.put("mail.smtp.port", String.valueOf(smtpPort));
177                    props.put("mail.smtp.debug", "true");
178    
179                    // create some properties and get the default Session
180                    Session session = Session.getDefaultInstance(props, null);
181                    session.setDebug(debug);
182    
183                    // create a message
184                    Message msg = new MimeMessage(session);
185    
186                    // set the from and to addresses
187                    InternetAddress addressFrom = new InternetAddress("watchdog@" + hostname);
188                    msg.setFrom(addressFrom);
189    
190                    InternetAddress[] addressTo = new InternetAddress[1];
191                    addressTo[0] = new InternetAddress(emailRecipient);
192                    msg.setRecipients(Message.RecipientType.TO, addressTo);
193    
194                    msg.setSubject(subject);
195                    msg.setText(content.toString());
196                    Transport.send(msg);
197                    return true;
198            }
199    }