package fi.vtt.simantics.procore.internal; import java.io.File; import java.io.IOException; import java.lang.management.ManagementFactory; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import java.util.TreeSet; import java.util.UUID; import org.eclipse.core.runtime.Platform; import org.osgi.framework.Bundle; import org.simantics.databoard.binding.mutable.Variant; import org.simantics.databoard.parser.repository.DataValueRepository; import org.simantics.db.DirectStatements; import org.simantics.db.Resource; import org.simantics.db.Session; import org.simantics.db.Statement; import org.simantics.db.WriteGraph; import org.simantics.db.common.request.WriteRequest; import org.simantics.db.common.request.WriteResultRequest; import org.simantics.db.common.utils.Logger; import org.simantics.db.common.utils.NameUtils; import org.simantics.db.exception.DatabaseException; import org.simantics.db.exception.ServiceException; import org.simantics.db.exception.ValidationException; import org.simantics.db.impl.Activator; import org.simantics.db.impl.ClusterI; import org.simantics.db.impl.ResourceImpl; import org.simantics.db.impl.graph.ReadGraphImpl; import org.simantics.db.impl.graph.WriteLogger; import org.simantics.db.impl.query.QueryProcessor; import org.simantics.db.impl.query.QuerySupport; import org.simantics.db.procore.cluster.ClusterBig; import org.simantics.db.procore.cluster.ClusterImpl; import org.simantics.db.procore.cluster.ClusterSmall; import org.simantics.db.service.DebugSupport; import org.simantics.db.service.QueryControl; import org.simantics.db.service.XSupport; import org.simantics.layer0.Layer0; import org.simantics.scl.runtime.function.Function2; import org.simantics.scl.runtime.function.Function3; import org.simantics.scl.runtime.function.FunctionImpl2; import org.simantics.scl.runtime.function.FunctionImpl3; import org.simantics.utils.Development; import org.simantics.utils.FileUtils; import gnu.trove.map.hash.TIntIntHashMap; import gnu.trove.procedure.TIntIntProcedure; import gnu.trove.procedure.TIntShortProcedure; import gnu.trove.set.hash.TIntHashSet; public class DebugSupportImpl implements DebugSupport { final private Map> getCommands = new HashMap>(); final private Map> listCommands = new HashMap>(); final private Map> execCommands = new HashMap>(); final private Map> printCommands = new HashMap>(); private static SessionImplSocket getSession(WriteGraph graph) { return (SessionImplSocket)graph.getSession(); } DebugSupportImpl() { getCommands.put("listeners", new FunctionImpl2() { @Override public Object apply(WriteGraph graph, String args) { try { return getSession(graph).queryProvider2.getListenerReport(); } catch (IOException e) { Logger.defaultLogError(e); return e.getMessage(); } } }); listCommands.put("counters", new FunctionImpl3() { @Override public String apply(WriteGraph graph, File file, String args) { try { return ReadGraphImpl.listCounters(file); } catch (IOException e) { Logger.defaultLogError(e); return e.getMessage(); } } }); listCommands.put("queries", new FunctionImpl3() { @Override public String apply(WriteGraph graph, File file, String args) { try { return getSession(graph).queryProvider2.reportQueries(file); } catch (IOException e) { Logger.defaultLogError(e); return e.getMessage(); } } }); listCommands.put("queryActivity", new FunctionImpl3() { @Override public String apply(WriteGraph graph, File file, String args) { try { return getSession(graph).queryProvider2.reportQueryActivity(file); } catch (IOException e) { Logger.defaultLogError(e); return e.getMessage(); } } }); listCommands.put("listeners", new FunctionImpl3() { @Override public String apply(WriteGraph graph, File file, String args) { try { return getSession(graph).queryProvider2.reportListeners(file); } catch (IOException e) { Logger.defaultLogError(e); return e.getMessage(); } } }); listCommands.put("clusters", new FunctionImpl3() { @Override public String apply(WriteGraph graph, File file, String args) { return reportClusters(getSession(graph), file); } }); listCommands.put("cluster", new FunctionImpl3() { @Override public String apply(WriteGraph graph, File file, String args) { return reportCluster(graph, file, args); } }); listCommands.put("virtuals", new FunctionImpl3() { @Override public String apply(WriteGraph graph, File file, String args) { return reportVirtuals(getSession(graph), file); } }); listCommands.put("heap", new FunctionImpl3() { @Override public String apply(WriteGraph graph, File file, String args) { try { file.delete(); Object bean = getBean(); if (bean == null) return "Could not retrieve bean."; Method m = bean.getClass().getMethod("dumpHeap", String.class, boolean.class); if (args.length() > 0) { m.invoke(bean, file.getParent() + "/" + args, true); } else { m.invoke(bean, file.getAbsolutePath(), true); } } catch (Throwable t) { Logger.defaultLogError(t); return "Unexpected exception " + t; } return "Wrote " + file.getAbsolutePath(); } private Object getBean() { Class beanClass = getBeanClass(); if (beanClass == null) return null; try { Object bean = ManagementFactory.newPlatformMXBeanProxy( ManagementFactory.getPlatformMBeanServer(), "com.sun.management:type=HotSpotDiagnostic", beanClass); return bean; } catch (IOException e) { return null; } } private Class getBeanClass() { try { Class clazz = Class.forName("com.sun.management.HotSpotDiagnosticMXBean"); return clazz; } catch (ClassNotFoundException e) { return null; } } }); execCommands.put("WriteLogger.read", new FunctionImpl2() { @Override public String apply(WriteGraph graph, String args) { graph.getSession().async(new WriteRequest() { @Override public void perform(WriteGraph graph) throws DatabaseException { try { WriteLogger.read(graph); } catch (Exception e) { e.printStackTrace(); } } }); return "Started to read the write log."; } }); execCommands.put("ReadGraph.resetCounters", new FunctionImpl2() { @Override public String apply(WriteGraph graph, String args) { graph.getSession().async(new WriteRequest() { @Override public void perform(WriteGraph graph) throws DatabaseException { try { ReadGraphImpl.resetCounters(); } catch (Exception e) { e.printStackTrace(); } } }); return "Started to read the write log."; } }); execCommands.put("QueryControl.flush", new FunctionImpl2() { @Override public String apply(WriteGraph graph, String args) { QueryControl qc = graph.getService(QueryControl.class); qc.flush(graph); return "Flushed queries."; } }); execCommands.put("DebugSupport.validateClusters", new FunctionImpl2() { @Override public String apply(WriteGraph graph, String args) { return validateClusters(graph); } }); execCommands.put("DebugSupport.writeForMs", new FunctionImpl2() { @Override public String apply(WriteGraph graph, String args) { Integer amount = Integer.parseInt(args); return writeForMs(graph, amount); } }); printCommands.put("nextId", new FunctionImpl2() { @Override public String apply(WriteGraph graph, String args) { try { SessionImplSocket session = getSession(graph); ClusterImpl cluster = session.clusterTable.getNewResourceCluster(session.clusterTranslator, session.graphSession, false); long cid = cluster.getClusterId(); int rid = cluster.getNumberOfResources(session.clusterTranslator); return cid + "#" + rid; } catch (Throwable t) { return UUID.randomUUID().toString(); } } }); printCommands.put("usedMemory", new FunctionImpl2() { @Override public String apply(WriteGraph graph, String args) { try { Runtime runtime = Runtime.getRuntime(); return "" + (runtime.totalMemory() - runtime.freeMemory()); } catch (Throwable t) { return UUID.randomUUID().toString(); } } }); } private String reportClusters(SessionImplSocket session, File file) { try { StringBuilder b = new StringBuilder(); long totalApproxSize = 0; int loaded = 0; for(ClusterI cluster : session.clusterTable.getClusters()) { b.append("[" + cluster.getClusterKey() + "]: "); if(cluster instanceof ClusterSmall) b.append("ClusterSmall[" + cluster.getClusterId() + "]"); if(cluster instanceof ClusterBig) b.append("ClusterBig[" + cluster.getClusterId() + "]"); if(cluster instanceof ClusterWriteOnly) b.append("ClusterWriteOnly[" + cluster.getClusterId() + "]"); if(cluster.isLoaded()) { long approx = cluster.getUsedSpace(); b.append(" approx size = " + approx + " bytes.\n"); totalApproxSize += approx; loaded++; } else { b.append(" is not loaded.\n"); } } b.append("#Total approx size is " + totalApproxSize + " bytes.\n"); b.append("#Amount of loaded clusters is " + loaded + ".\n"); FileUtils.writeFile(file, b.toString().getBytes()); } catch (IOException e) { e.printStackTrace(); } catch (DatabaseException e) { e.printStackTrace(); } return "OK"; } private String reportCluster(final WriteGraph graph, File file, String args) { try { final StringBuilder b = new StringBuilder(); final SessionImplSocket session = (SessionImplSocket)graph.getSession(); long clusterId = Long.parseLong(args); b.append("cluster id: " + clusterId); b.append("\n"); b.append("internal resources: "); b.append("\n"); ClusterI cluster = session.clusterTable.getClusterByClusterId(clusterId); for(int i=1;i<=cluster.getNumberOfResources(session.clusterTranslator);i++) { Resource r = session.getResource(i, clusterId); String def = NameUtils.getSafeName(graph, r); b.append( i+": " + def); b.append("\n"); } if(cluster instanceof ClusterSmall) { ClusterSmall clusterSmall = (ClusterSmall)cluster; b.append("foreign resources: "); b.append("\n"); final TIntIntHashMap clusterHistogram = new TIntIntHashMap(); clusterSmall.foreignTable.getResourceHashMap().forEachEntry(new TIntShortProcedure() { @Override public boolean execute(int index, short pos) { try { Resource r = session.getResource(index); String def = NameUtils.getSafeName(graph, r, true); int cluster = index >>> 12; int key = index & 0xfff; int exist = clusterHistogram.get(cluster); clusterHistogram.put(cluster, exist+1); b.append( cluster + "$" + key +": " + def); b.append("\n"); } catch (ValidationException e) { e.printStackTrace(); } catch (ServiceException e) { e.printStackTrace(); } return true; } }); b.append("foreign histogram: "); b.append("\n"); clusterHistogram.forEachEntry(new TIntIntProcedure() { @Override public boolean execute(int cluster, int count) { b.append( cluster +": " + count); b.append("\n"); return true; } }); } FileUtils.writeFile(file, b.toString().getBytes()); } catch (IOException e) { e.printStackTrace(); } catch (DatabaseException e) { e.printStackTrace(); } return "OK"; } private String reportVirtuals(SessionImplSocket session, File file) { session.virtualGraphServerSupport.report(file); return "OK"; } public Object query(Session session, final String command) { try { return session.sync(new WriteResultRequest() { @Override public Object perform(WriteGraph graph) throws DatabaseException { return query(graph, command); } }); } catch (DatabaseException e) { Logger.defaultLogError(e); return null; } } @Override public Object query(WriteGraph graph, String command) { Bundle bundle = Platform.getBundle(Activator.BUNDLE_ID); File base = Utils.getBaseFile(bundle); command = command.trim(); try { if("help".equals(command)) { return "Welcome to the Simantics session debugger.

" + "This shell allows you to make following queries into the running Simantics database session:
" + "
  • Get commands, which return debug objects. Type 'help get' to obtain more information.
  • " + "
  • List commands, which create debug listings into files. Type 'help list' to obtain more information.
  • " + "
  • Print commands, which output information about session state. Type 'help print' to obtain more information.
  • " + "
  • Set commands, which modify session state variables. Type 'help set' to obtain more information.
  • " + "
  • Exec commands, which perform certain actions. Type 'help exec' to obtain more information.
" ; } else if ("help get".equals(command)) { StringBuilder b = new StringBuilder(); b.append("The following get commands are available.
    "); for(String key : getCommands.keySet()) b.append("
  • " + key + "
  • "); b.append("
"); return b.toString(); } else if ("help list".equals(command)) { StringBuilder b = new StringBuilder(); b.append("The following list commands are available.
    "); for(String key : listCommands.keySet()) b.append("
  • " + key + "
  • "); b.append("
"); return b.toString(); } else if ("help exec".equals(command)) { StringBuilder b = new StringBuilder(); b.append("The following exec commands are available.
    "); for(String key : execCommands.keySet()) b.append("
  • " + key + "
  • "); b.append("
"); return b.toString(); } else if ("help print".equals(command)) { StringBuilder b = new StringBuilder(); b.append("The following print commands are available.
    "); for(String key : printCommands.keySet()) b.append("
  • " + key + "
  • "); b.append("
"); return b.toString(); } else if ("help set".equals(command)) { StringBuilder b = new StringBuilder(); b.append("The following set commands are available.
    "); for(Map.Entry e : Development.getProperties().entrySet()) { b.append("
  • " + e.getKey() + " - " + e.getValue().getBinding().type() + "
  • "); } b.append("
"); return b.toString(); } else if(command.startsWith("get")) { String remainder = command.substring(3).trim(); for(Map.Entry> e : getCommands.entrySet()) { String key = e.getKey(); if(remainder.startsWith(key)) { String args = remainder.substring(key.length()).trim(); return e.getValue().apply(graph, args); } } } else if(command.startsWith("list")) { String remainder = command.substring(4).trim(); for(Map.Entry> e : listCommands.entrySet()) { String key = e.getKey(); if(remainder.startsWith(key)) { String args = remainder.substring(key.length()).trim(); File file = new File(base, key + ".list"); base.mkdirs(); e.getValue().apply(graph, file, args); return "Wrote " + file.getAbsolutePath(); } } } else if(command.startsWith("set")) { String remainder = command.substring(3).trim(); String[] keyAndValue = remainder.split("="); if(keyAndValue.length == 2) { Variant exist = Development.getProperties().get(keyAndValue[0]); if(exist != null) { Development.setProperty(keyAndValue[0], exist.getBinding().parseValue(keyAndValue[1], new DataValueRepository()), exist.getBinding()); return "Property " + keyAndValue[0] + " was set to '" + keyAndValue[1] + "'"; } else { return query(graph, "help set"); } } else { return query(graph, "help set"); } } else if(command.startsWith("print")) { String remainder = command.substring(5).trim(); for(Map.Entry> e : printCommands.entrySet()) { String key = e.getKey(); if(remainder.startsWith(key)) { String args = remainder.substring(key.length()).trim(); return e.getValue().apply(graph, args); } } } else if(command.startsWith("exec")) { String remainder = command.substring(4).trim(); for(Map.Entry> e : execCommands.entrySet()) { String key = e.getKey(); if(remainder.startsWith(key)) { String args = remainder.substring(key.length()).trim(); return e.getValue().apply(graph, args); } } } return "Unknown command '" + command + "'"; } catch (Throwable t) { t.printStackTrace(); return t.getMessage(); } } private void writeResouce(WriteGraph graph) throws DatabaseException { Layer0 l0 = Layer0.getInstance(graph); Resource r = graph.newResource(); graph.claim(r, l0.InstanceOf, l0.Entity); XSupport xs = (XSupport)graph.getService(XSupport.class); xs.flushCluster(r); } private void wait(int amount) { long start = System.nanoTime(); long limit = amount * 1000000L; while ((System.nanoTime() - start) < limit) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } String writeForMs(WriteGraph graph, int amount) { try { writeResouce(graph); wait(amount); writeResouce(graph); } catch (DatabaseException e) { e.printStackTrace(); } return "Slept for " + amount + " ms in write transaction."; } String validateClusters(WriteGraph graph) { ReadGraphImpl impl = (ReadGraphImpl)graph; QueryProcessor processor = impl.processor; QuerySupport qs = graph.getService(QuerySupport.class); TIntHashSet done = new TIntHashSet(); TreeSet fringe = new TreeSet(); ResourceImpl root = (ResourceImpl)graph.getRootLibrary(); done.add(root.id); fringe.add(root.id); while(!fringe.isEmpty()) { int r = fringe.first(); fringe.remove(r); DirectStatements ds = qs.getStatements(impl, r, processor, true); for(Statement stm : ds) { ResourceImpl p = (ResourceImpl)stm.getPredicate(); ResourceImpl o = (ResourceImpl)stm.getObject(); if(done.add(p.id)) fringe.add(p.id); if(done.add(o.id)) fringe.add(o.id); } qs.getValue(impl, r); } return "Validated " + done.size() + " resources."; } }