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 }