1   package eu.fbk.knowledgestore.runtime;
2   
3   import java.io.File;
4   import java.io.IOException;
5   import java.io.InputStream;
6   import java.io.PrintWriter;
7   import java.lang.management.GarbageCollectorMXBean;
8   import java.lang.management.ManagementFactory;
9   import java.lang.management.MemoryPoolMXBean;
10  import java.lang.management.MemoryUsage;
11  import java.lang.management.ThreadInfo;
12  import java.lang.management.ThreadMXBean;
13  import java.net.URL;
14  import java.util.List;
15  import java.util.Properties;
16  import java.util.ServiceConfigurationError;
17  import java.util.Set;
18  import java.util.concurrent.atomic.AtomicReference;
19  import java.util.concurrent.locks.Lock;
20  import java.util.concurrent.locks.ReentrantLock;
21  
22  import javax.annotation.Nullable;
23  
24  import sun.misc.Signal;
25  import sun.misc.SignalHandler;
26  
27  import com.google.common.base.Charsets;
28  import com.google.common.base.Preconditions;
29  import com.google.common.base.Strings;
30  import com.google.common.base.Throwables;
31  import com.google.common.io.Resources;
32  
33  import org.apache.commons.cli.CommandLine;
34  import org.apache.commons.cli.GnuParser;
35  import org.apache.commons.cli.HelpFormatter;
36  import org.apache.commons.cli.Options;
37  import org.apache.commons.cli.ParseException;
38  import org.openrdf.model.Resource;
39  import org.openrdf.model.Statement;
40  import org.openrdf.model.URI;
41  import org.openrdf.model.Value;
42  import org.openrdf.model.impl.URIImpl;
43  import org.openrdf.rio.RDFFormat;
44  import org.slf4j.Logger;
45  import org.slf4j.LoggerFactory;
46  import org.slf4j.bridge.SLF4JBridgeHandler;
47  
48  import ch.qos.logback.classic.LoggerContext;
49  import ch.qos.logback.classic.joran.JoranConfigurator;
50  import ch.qos.logback.core.joran.spi.JoranException;
51  import ch.qos.logback.core.util.StatusPrinter;
52  
53  import eu.fbk.knowledgestore.data.Data;
54  import eu.fbk.knowledgestore.internal.Util;
55  import eu.fbk.knowledgestore.internal.rdf.RDFUtil;
56  
57  
58  
59  
60  
61  
62  
63  
64  
65  
66  
67  
68  
69  
70  
71  
72  
73  
74  
75  
76  
77  
78  
79  
80  
81  
82  
83  
84  
85  
86  
87  
88  
89  
90  
91  
92  
93  
94  
95  
96  
97  
98  
99  
100 
101 
102 
103 
104 
105 
106 
107 
108 
109 
110 
111 
112 
113 
114 
115 
116 
117 
118 
119 
120 
121 
122 
123 
124 
125 
126 
127 
128 
129 
130 
131 
132 
133 
134 
135 
136 
137 
138 
139 
140 
141 
142 public final class Launcher {
143 
144     private static final Logger LOGGER = LoggerFactory.getLogger(Launcher.class);
145 
146     private static final int EX_OK = 0; 
147 
148     private static final int EX_USAGE = 64; 
149 
150     private static final int EX_CONFIG = 78; 
151 
152     private static final int EX_IOERR = 74; 
153 
154     private static final int EX_UNAVAILABLE = 69; 
155 
156     private static final String SIGNAL_SHUTDOWN = "INT";
157 
158     private static final String SIGNAL_RELOAD = "USR2";
159 
160     private static final String SIGNAL_STATUS = "STATUS"; 
161 
162     private static final String PROGRAM_EXECUTABLE = retrieveProperty("launcher.executable",
163             String.class, "ks");
164 
165     @Nullable
166     private static final String PROGRAM_DESCRIPTION = retrieveProperty("launcher.description",
167             String.class, null);
168 
169     private static final String PROGRAM_DISCLAIMER = retrieveResource(Launcher.class.getName()
170             .replace('.', '/') + ".disclaimer");
171 
172     private static final String PROGRAM_VERSION = retrieveVersion();
173 
174     private static final String DEFAULT_CONFIG = retrieveProperty("launcher.config", String.class,
175             "config.xml");
176 
177     private static final String DEFAULT_THREAD_NAME = "worker-%02d";
178 
179     private static final int DEFAULT_THREAD_COUNT = 32;
180 
181     private static final String DEFAULT_LOG_CONFIG = retrieveProperty("launcher.logging",
182             String.class, "logback.xml");
183 
184     private static final String PROPERTY_LOG_CONFIG = "logConfig";
185 
186     private static final String PROPERTY_THREAD_COUNT = "threadCount";
187 
188     private static final String PROPERTY_THREAD_NAME = "threadName";
189 
190     private static final String PROPERTY_COMPONENT = "component";
191 
192     private static final URI LAUNCHER_URI = new URIImpl("obj:launcher");
193 
194     private static final int WIDTH = 80;
195 
196     private static Component component;
197 
198     
199 
200 
201 
202 
203 
204     public static void main(final String... args) {
205 
206         
207         final Options options = new Options();
208         options.addOption("c", "config", true, "use service configuration file / classpath "
209                 + "resource (default '" + DEFAULT_CONFIG + "')");
210         options.addOption("v", "version", false,
211                 "display version and copyright information, then exit");
212         options.addOption("h", "help", false, "display usage information, then exit");
213 
214         
215         int status = EX_OK;
216 
217         try {
218             
219             final CommandLine cmd = new GnuParser().parse(options, args);
220             if (cmd.hasOption("v")) {
221                 
222                 System.out.println(String.format(
223                         "%s (FBK KnowledgeStore) %s\njava %s bit (%s) %s\n%s", PROGRAM_EXECUTABLE,
224                         PROGRAM_VERSION, System.getProperty("sun.arch.data.model"),
225                         System.getProperty("java.vendor"), System.getProperty("java.version"),
226                         PROGRAM_DISCLAIMER));
227 
228             } else if (cmd.hasOption("h")) {
229                 
230                 status = EX_USAGE;
231 
232             } else {
233                 
234                 final String configLocation = cmd.getOptionValue('c', DEFAULT_CONFIG);
235 
236                 
237                 if (cmd.getArgList().contains("__start")) {
238                     start(configLocation); 
239                 } else if (cmd.getArgList().contains("__stop")) {
240                     stop(); 
241                 } else {
242                     run(configLocation); 
243                 }
244             }
245 
246         } catch (final ParseException ex) {
247             
248             System.err.println("SYNTAX ERROR: " + ex.getMessage());
249             status = EX_USAGE;
250 
251         } catch (final ServiceConfigurationError ex) {
252             
253             System.err.println("INVALID CONFIGURATION: " + ex.getMessage());
254             Throwables.getRootCause(ex).printStackTrace();
255             status = EX_CONFIG;
256 
257         } catch (final Throwable ex) {
258             
259             System.err.print("EXECUTION FAILED: ");
260             ex.printStackTrace();
261             status = ex instanceof IOException ? EX_IOERR : EX_UNAVAILABLE;
262         }
263 
264         
265         if (status == EX_USAGE) {
266             final PrintWriter out = new PrintWriter(System.out);
267             final HelpFormatter formatter = new HelpFormatter();
268             formatter.printUsage(out, WIDTH, PROGRAM_EXECUTABLE, options);
269             if (PROGRAM_DESCRIPTION != null) {
270                 formatter.printWrapped(out, WIDTH, "\n" + PROGRAM_DESCRIPTION.trim());
271             }
272             out.println("\nOptions");
273             formatter.printOptions(out, WIDTH, options, 2, 2);
274             out.flush();
275         }
276 
277         
278         if (status != EX_OK) {
279             System.err.println("[exit status: " + status + "]");
280         } else {
281             System.out.println("[exit status: " + status + "]");
282         }
283 
284         
285         System.out.flush();
286         System.err.flush();
287 
288         
289         System.exit(status);
290     }
291 
292     private static void run(final String configLocation) throws Throwable {
293 
294         Preconditions.checkNotNull(configLocation);
295 
296         final AtomicReference<String> pendingSignal = new AtomicReference<String>(null);
297         final Thread mainThread = Thread.currentThread();
298         final Lock lock = new ReentrantLock();
299 
300         
301         final Thread shutdownHandler = new Thread("shutdown") {
302 
303             @Override
304             public void run() {
305                 pendingSignal.set(SIGNAL_SHUTDOWN);
306                 mainThread.interrupt(); 
307                 lock.lock();
308                 lock.unlock();
309             }
310 
311         };
312 
313         
314         final AtomicReference<Object> oldHandlerHolder = new AtomicReference<Object>(null);
315         Object reloadHandler = null;
316         try {
317             new Signal(SIGNAL_RELOAD); 
318             reloadHandler = new SignalHandler() {
319 
320                 @Override
321                 public void handle(final Signal signal) {
322                     final String name = signal.getName();
323                     try {
324                         pendingSignal.compareAndSet(null, name);
325                         mainThread.interrupt();
326 
327                     } finally {
328                         final Object oldHandler = oldHandlerHolder.get();
329                         if (oldHandler != null) {
330                             ((SignalHandler) oldHandler).handle(signal);
331                         }
332                     }
333                 }
334 
335             };
336         } catch (final Throwable ex) {
337             
338         }
339 
340         start(configLocation);
341         lock.lock();
342 
343         try {
344             
345             java.lang.Runtime.getRuntime().addShutdownHook(shutdownHandler);
346             if (reloadHandler != null) {
347                 try {
348                     oldHandlerHolder.set(Signal.handle(new Signal(SIGNAL_RELOAD),
349                             (SignalHandler) reloadHandler));
350                 } catch (final Throwable ex) {
351                     reloadHandler = null;
352                 }
353             }
354 
355             
356             if (LOGGER.isInfoEnabled()) {
357                 final StringBuilder builder = new StringBuilder("Issue ");
358                 builder.append("q\\n/SIG").append(SIGNAL_SHUTDOWN).append(" to end, ");
359                 final String sig = reloadHandler == null ? "" : "/SIG" + SIGNAL_RELOAD;
360                 builder.append("r\\n").append(sig).append(" to reload, ");
361                 builder.append("i\\n").append(" to show info");
362                 LOGGER.info(builder.toString());
363             }
364 
365             
366             while (true) {
367                 try {
368                     while (pendingSignal.get() == null && System.in.available() > 0) {
369                         final char ch = (char) System.in.read();
370                         if (ch == 'q' || ch == 'Q') {
371                             pendingSignal.set(SIGNAL_SHUTDOWN);
372                         } else if (ch == 'r' || ch == 'R') {
373                             pendingSignal.set(SIGNAL_RELOAD);
374                         } else if (ch == 'i' || ch == 'I') {
375                             pendingSignal.set(SIGNAL_STATUS);
376                         }
377                     }
378                 } catch (final IOException ex) {
379                     
380                 }
381 
382                 try {
383                     if (pendingSignal.get() == null) {
384                         Thread.sleep(1000);
385                     }
386                 } catch (final InterruptedException ex) {
387                     
388                 }
389 
390                 final String signal = pendingSignal.getAndSet(null);
391 
392                 if (SIGNAL_SHUTDOWN.equals(signal)) {
393                     break;
394                 } else if (SIGNAL_RELOAD.equals(signal)) {
395                     stop();
396                     start(configLocation);
397                 } else if (SIGNAL_STATUS.equals(signal)) {
398                     LOGGER.info(status(true));
399                 }
400             }
401 
402         } finally {
403             try {
404                 
405                 stop();
406 
407             } finally {
408                 
409                 if (reloadHandler != null) {
410                     final SignalHandler oldHandler = (SignalHandler) oldHandlerHolder.get();
411                     Signal.handle(new Signal(SIGNAL_RELOAD), oldHandler);
412                 }
413 
414                 try {
415                     java.lang.Runtime.getRuntime().removeShutdownHook(shutdownHandler);
416                 } catch (final Throwable ex) {
417                     
418                 }
419 
420                 try {
421                     
422                     ((LoggerContext) LoggerFactory.getILoggerFactory()).stop();
423                 } catch (final Throwable ex) {
424                     
425                 }
426 
427                 lock.unlock();
428             }
429         }
430     }
431 
432     private static void start(final String configLocation) throws Throwable {
433 
434         Preconditions.checkNotNull(configLocation);
435 
436         
437         if (component != null) {
438             return;
439         }
440 
441         
442         final List<Statement> config;
443         final InputStream stream = retrieveURL(configLocation).openStream();
444         final RDFFormat format = RDFFormat.forFileName(configLocation);
445         config = RDFUtil.readRDF(stream, format, Data.getNamespaceMap(), null, false).toList();
446         stream.close();
447 
448         
449         String threadName = DEFAULT_THREAD_NAME;
450         int threadCount = DEFAULT_THREAD_COUNT;
451         String logConfig = DEFAULT_LOG_CONFIG;
452         URI componentURI = null;
453         for (final Statement statement : config) {
454             final Resource s = statement.getSubject();
455             final URI p = statement.getPredicate();
456             final Value o = statement.getObject();
457             if (s.equals(LAUNCHER_URI)) {
458                 if (p.getLocalName().equals(PROPERTY_THREAD_NAME)) {
459                     threadName = Data.convert(o, String.class);
460                 } else if (p.getLocalName().equals(PROPERTY_THREAD_COUNT)) {
461                     threadCount = Data.convert(o, Integer.class);
462                 } else if (p.getLocalName().equals(PROPERTY_LOG_CONFIG)) {
463                     logConfig = Data.convert(o, String.class);
464                 } else if (p.getLocalName().equals(PROPERTY_COMPONENT)) {
465                     componentURI = (URI) o;
466                 }
467             }
468         }
469 
470         
471         Data.setExecutor(Util.newScheduler(threadCount, threadName, true));
472 
473         
474         final LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
475         try {
476             final JoranConfigurator configurator = new JoranConfigurator();
477             configurator.setContext(context);
478             context.reset();
479             configurator.doConfigure(retrieveURL(logConfig));
480         } catch (final JoranException je) {
481             StatusPrinter.printInCaseOfErrorsOrWarnings(context);
482         }
483         SLF4JBridgeHandler.removeHandlersForRootLogger();
484         SLF4JBridgeHandler.install();
485 
486         
487         String vendor = System.getProperty("java.vendor");
488         final int index = vendor.indexOf(' ');
489         vendor = index < 0 ? vendor : vendor.substring(0, index);
490         final String header = String.format("%s %s / java %s (%s) %s / %s", PROGRAM_EXECUTABLE,
491                 PROGRAM_VERSION, System.getProperty("sun.arch.data.model"), vendor,
492                 System.getProperty("java.version"), System.getProperty("os.name")).toLowerCase();
493         final String line = Strings.repeat("-", header.length());
494         LOGGER.info(line);
495         LOGGER.info(header);
496         LOGGER.info(line);
497         LOGGER.info("Using: {}", configLocation);
498         LOGGER.info("Using: {}", logConfig);
499         LOGGER.info("Using: {} threads", threadCount);
500 
501         
502         final Component newComponent;
503         try {
504             newComponent = Factory.instantiate(config, componentURI, Component.class);
505         } catch (final Throwable ex) {
506             throw new ServiceConfigurationError("Configuration failed: " + ex.getMessage(), ex);
507         }
508 
509         
510         newComponent.init();
511         LOGGER.info("Service started");
512         component = newComponent;
513     }
514 
515     private static void stop() {
516 
517         LOGGER.info("Stopping service ...");
518 
519         if (component == null) {
520             return;
521         }
522 
523         try {
524             component.close();
525             LOGGER.info("Service stopped");
526         } catch (final Throwable ex) {
527             LOGGER.error("Close failed: " + ex.getMessage(), ex);
528         }
529 
530         component = null;
531     }
532 
533     private static String status(final boolean verbose) {
534 
535         final StringBuilder builder = new StringBuilder();
536 
537         
538         builder.append(component != null ? "running" : "not running");
539 
540         
541         final long uptime = ManagementFactory.getRuntimeMXBean().getUptime();
542         final long days = uptime / (24 * 60 * 60 * 1000);
543         final long hours = uptime / (60 * 60 * 1000) - days * 24;
544         final long minutes = uptime / (60 * 1000) - (days * 24 + hours) * 60;
545         long gctime = 0;
546         for (final GarbageCollectorMXBean bean : ManagementFactory.getGarbageCollectorMXBeans()) {
547             gctime += bean.getCollectionTime(); 
548         }
549         builder.append(", ").append(days == 0 ? "" : days + "d")
550                 .append(hours == 0 ? "" : hours + "h").append(minutes).append("m uptime (")
551                 .append(gctime * 100 / uptime).append("% gc)");
552 
553         
554         final MemoryUsage heap = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage();
555         final MemoryUsage nonHeap = ManagementFactory.getMemoryMXBean().getNonHeapMemoryUsage();
556         final long used = heap.getUsed() + nonHeap.getUsed();
557         final long committed = heap.getCommitted() + nonHeap.getCommitted();
558         final long mb = 1024 * 1024;
559         long max = 0;
560         for (final MemoryPoolMXBean bean : ManagementFactory.getMemoryPoolMXBeans()) {
561             max += bean.getPeakUsage().getUsed(); 
562         }
563         builder.append("; ").append(used / mb).append("/").append(committed / mb).append("/")
564                 .append(max / mb).append(" MB mem used/committed/max");
565 
566         
567         final int numThreads = ManagementFactory.getThreadMXBean().getThreadCount();
568         final int daemonThreads = ManagementFactory.getThreadMXBean().getDaemonThreadCount();
569         final int maxThreads = ManagementFactory.getThreadMXBean().getPeakThreadCount();
570         final long startedThreads = ManagementFactory.getThreadMXBean()
571                 .getTotalStartedThreadCount();
572         builder.append("; ").append(daemonThreads).append("/").append(numThreads - daemonThreads)
573                 .append("/").append(maxThreads).append("/").append(startedThreads)
574                 .append(" threads daemon/non-daemon/max/started");
575 
576         
577         final long[] deadlocked = ManagementFactory.getThreadMXBean().findDeadlockedThreads();
578 
579         
580         if (verbose || deadlocked != null) {
581             int maxState = 10; 
582             int maxName = 0;
583             final Set<Thread> threads = Thread.getAllStackTraces().keySet();
584             final ThreadInfo[] infos = ManagementFactory.getThreadMXBean().dumpAllThreads(false,
585                     false);
586             for (final ThreadInfo info : infos) {
587                 maxState = Math.max(maxState, info.getThreadState().toString().length());
588                 maxName = Math.max(maxName, info.getThreadName().length());
589             }
590             for (final ThreadInfo info : infos) {
591                 String state = info.getThreadState().toString().toLowerCase();
592                 if (deadlocked != null) {
593                     for (final long id : deadlocked) {
594                         if (info.getThreadId() == id) {
595                             state = "deadlocked";
596                         }
597                     }
598                 }
599                 boolean daemon = false;
600                 boolean interrupted = false;
601                 for (final Thread thread : threads) {
602                     if (thread.getName().equals(info.getThreadName())) {
603                         daemon = thread.isDaemon();
604                         interrupted = thread.isInterrupted();
605                         break;
606                     }
607                 }
608                 StackTraceElement element = null;
609                 final StackTraceElement[] trace = info.getStackTrace();
610                 if (trace != null && trace.length > 0) {
611                     element = trace[0];
612                     for (int i = 0; i < trace.length; ++i) {
613                         if (trace[i].getClassName().startsWith("eu.fbk")) {
614                             element = trace[i];
615                             break;
616                         }
617                     }
618                 }
619                 builder.append(String.format("\n  %-11s  %-" + maxState + "s  %-" + maxName
620                         + "s  ", (daemon ? "" : "non-") + "daemon" + (interrupted ? "*" : ""),
621                         state, info.getThreadName()));
622                 builder.append(element == null ? "" : element.toString());
623             }
624         }
625 
626         
627         return builder.toString();
628     }
629 
630     private static String retrieveVersion() {
631 
632         final String name = "META-INF/maven/eu.fbk.knowledgestore/ks-runtime/pom.properties";
633         final URL url = Launcher.class.getClassLoader().getResource(name);
634         if (url == null) {
635             return "devel";
636         }
637 
638         try {
639             final InputStream stream = url.openStream();
640             try {
641                 final Properties properties = new Properties();
642                 properties.load(stream);
643                 return properties.getProperty("version").trim();
644             } finally {
645                 stream.close();
646             }
647 
648         } catch (final IOException ex) {
649             return "unknown version";
650         }
651     }
652 
653     private static URL retrieveURL(final String name) {
654         try {
655             URL url = Launcher.class.getClassLoader().getResource(name);
656             if (url == null) {
657                 final File file = new File(name);
658                 if (file.exists() && !file.isDirectory()) {
659                     url = file.toURI().toURL();
660                 }
661             }
662             return Preconditions.checkNotNull(url);
663         } catch (final Throwable ex) {
664             throw new IllegalArgumentException("Invalid path: " + name, ex);
665         }
666     }
667 
668     private static String retrieveResource(final String name) {
669         try {
670             return Resources.toString(retrieveURL(name), Charsets.UTF_8);
671         } catch (final IOException ex) {
672             throw new Error("Cannot load " + name + ": " + ex.getMessage(), ex);
673         }
674     }
675 
676     @Nullable
677     private static <T> T retrieveProperty(final String property, final Class<T> type,
678             final T defaultValue) {
679         final String value = System.getProperty(property);
680         if (value != null) {
681             try {
682                 return Data.convert(value, type);
683             } catch (final Throwable ex) {
684                 LOGGER.warn("Could not retrieve property '" + property + "'", ex);
685             }
686         }
687         return defaultValue;
688     }
689 
690     private Launcher() {
691     }
692 
693 }