package diskCacheV111.cells; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Joiner; import com.google.common.base.Strings; import com.google.common.base.Throwables; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.security.auth.Subject; import java.io.PrintWriter; import java.io.Serializable; import java.net.InetAddress; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import diskCacheV111.util.HTMLBuilder; import diskCacheV111.util.TransferInfo; import diskCacheV111.util.UserInfo; import diskCacheV111.vehicles.IoDoorInfo; import diskCacheV111.vehicles.IoJobInfo; import dmg.cells.nucleus.CellAdapter; import dmg.cells.nucleus.CellMessage; import dmg.cells.nucleus.CellNucleus; import dmg.cells.nucleus.NoRouteToCellException; import dmg.cells.services.login.LoginBrokerInfo; import dmg.cells.services.login.LoginBrokerSubscriber; import dmg.cells.services.login.LoginManagerChildrenInfo; import org.dcache.cells.CellStub; import org.dcache.util.Args; import org.dcache.util.NetworkUtils; import org.dcache.util.TransferCollector; import org.dcache.util.TransferCollector.Transfer; import static java.util.concurrent.TimeUnit.SECONDS; import static org.dcache.util.ByteUnit.BYTES; public class TransferObserverV1 extends CellAdapter implements Runnable { private static final Logger _log = LoggerFactory.getLogger(TransferObserverV1.class); private final CellNucleus _nucleus; private final CellStub _cellStub; private final Args _args; private TransferCollector _collector; private Thread _workerThread; private LoginBrokerSubscriber _loginBrokerSource; private long _update = 120000L; private long _timeUsed; private long _processCounter; private final Map<String, TableEntry> _tableHash = new HashMap<>(); private static final String[] __className = { "cell", // 0 "domain", // 1 "sequence", // 2 "protocol", // 3 "uid", // 4 "gid", // 5 "vomsGroup", // 6 "process", // 7 "pnfs", // 8 "pool", // 9 "host", // 10 "status", // 11 "waiting", // 12 "state", // 13 "submitted", // 14 "time", // 15 "transferred", // 16 "speed", // 17 "running" // 18 }; private static final String[] __listHeader = { "Cell", // 0 "Domain", // 1 "Seq", // 2 "Prot", // 3 "UID", // 4 "GID", // 5 "VOMS Group", // 6 "Proc", // 7 "PnfsId", // 8 "Pool", // 9 "Host", // 10 "State", // 11 "Waiting", // 12 "JobState", // 13 "Submitted", // 14 "Time", // 15 "Trans. (KB)", // 16 "Speed (KB/s)", // 17 "Running" // 18 }; private static class TableEntry { private final String _tableName; private final int[] _fields; private final String _title; private long _olderThan; private boolean _ifNotYetStarted; private boolean _ifMoverMissing; private TableEntry(String tableName, int[] fields, String title) { _tableName = tableName; _fields = fields; _title = title; } private int[] getFields() { return _fields; } private String getName() { return _tableName; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(_tableName).append(" = "); if (_fields.length > 0) { for (int i = 0; i < (_fields.length - 1); i++) { sb.append(_fields[i]).append(","); } sb.append(_fields[_fields.length - 1]); } if (_title != null) { sb.append(" \"").append(_title).append("\""); } return sb.toString(); } private boolean ifNotYetStarted() { return _ifNotYetStarted; } private boolean ifMoverMissing() { return _ifMoverMissing; } private long getOlderThan() { return _olderThan; } } /** * <p>Used to produce JSON representation, but also provides methods * for conversion into .txt and .html table entries.</p> */ private static class TransferBean extends TransferInfo { private static final long serialVersionUID = 3132576086376551676L; private final long now; public TransferBean() { now = System.currentTimeMillis(); } public TransferBean(Transfer transfer, long now) { this.now = now; cellName = transfer.door().getCellName(); domainName = transfer.door().getDomainName(); serialId = transfer.session().getSerialId(); setProtocol(transfer.door().getProtocolFamily(), transfer.door().getProtocolVersion()); Subject subject = transfer.session().getSubject(); if (subject == null) { setUserInfo(new UserInfo()); } else { setSubject(subject); } process = transfer.door().getProcess(); pnfsId = Objects.toString(transfer.session().getPnfsId(), ""); pool = Objects.toString(transfer.session().getPool(), ""); replyHost = Objects.toString(transfer.session().getReplyHost(), ""); sessionStatus = Objects.toString(transfer.session().getStatus(), ""); waitingSince = transfer.session().getWaitingSince(); IoJobInfo moverInfo = transfer.mover(); if (moverInfo == null) { moverStatus = null; } else { moverId = moverInfo.getJobId(); moverStatus = moverInfo.getStatus(); moverSubmit = moverInfo.getSubmitTime(); if (moverInfo.getStartTime() > 0L) { transferTime = moverInfo.getTransferTime(); bytesTransferred = moverInfo.getBytesTransferred(); moverStart = moverInfo.getStartTime(); } } } void toHtmlRow(List<String> out) { out.add(cellName); out.add(domainName); out.add(String.valueOf(serialId)); out.add(protocol); out.add(userInfo.getUid()); out.add(userInfo.getGid()); out.add(userInfo.getPrimaryVOMSGroup()); out.add(process); out.add(pnfsId); out.add(pool); out.add(replyHost); out.add(sessionStatus == null ? "" : sessionStatus.replace(" ", " ")); out.add(timeWaiting(now, true)); if (moverStatus != null) { out.add(moverStatus); out.add(timeElapsedSinceSubmitted(now, true)); addMoverInfo(out, true); } } void toAsciiLine(StringBuilder builder) { List<String> args = new ArrayList<>(); args.add(cellName); args.add(domainName); args.add(String.valueOf(serialId)); args.add(protocol); args.add(userInfo.getUid()); args.add(process); args.add(pnfsId); args.add(pool); args.add(replyHost); args.add(sessionStatus); args.add(timeWaiting(now, false)); if (moverStatus == null) { args.add("No-mover()-Found"); } else { args.add(moverStatus); addMoverInfo(args, false); } Joiner.on(" ").appendTo(builder, args); builder.append('\n'); } String timeWaiting(boolean display) { return timeWaiting(now, true); } private void addMoverInfo(List<String> list, boolean display) { if (moverStart != null) { list.add(getTimeString(transferTime, display)); list.add(String.valueOf(bytesTransferred)); if (display) { list.add(transferTime > 0 ? String.valueOf((1000 * bytesTransferred)/(1024 * transferTime)) : "-"); } else { list.add(String.valueOf(transferTime > 0 ? ((double) bytesTransferred / (double) transferTime) : 0)); } list.add(timeRunning(now, display)); } } } public TransferObserverV1(String name, String args) { super(name, TransferObserverV1.class.getName(), args); _nucleus = getNucleus(); _cellStub = new CellStub(this, null, 30, SECONDS); _args = getArgs(); } @Override protected void starting() throws Exception { if (_args.argc() < 0) { throw new IllegalArgumentException("Usage : ... "); } _loginBrokerSource = new LoginBrokerSubscriber(); addCellEventListener(_loginBrokerSource); addCommandListener(_loginBrokerSource); _loginBrokerSource.setCellEndpoint(this); _loginBrokerSource.setTopic(_args.getOpt("loginBroker")); _collector = new TransferCollector(_cellStub, _loginBrokerSource.doors()); String updateString = _args.getOpt("update"); try { if (updateString != null) { _update = Long.parseLong(updateString) * 1000L; } } catch (NumberFormatException e) { _log.warn("Illegal value for -update: " + updateString); } useInterpreter(true); } @Override protected void started() { _workerThread = _nucleus.newThread(this, "worker"); _workerThread.start(); _loginBrokerSource.afterStart(); } @Override public void stopped() { if (_workerThread != null) { _workerThread.interrupt(); } _loginBrokerSource.beforeStop(); } public static final String hh_table_help = ""; public String ac_table_help(Args args) { StringBuilder sb = new StringBuilder(); int i = 0; for (String header : __listHeader) { sb.append(i++).append(" ").append(header).append("\n"); } return sb.toString(); } public static final String hh_table_define = "<tableName> <n>[,<m>[,...]] [<tableHeader>]"; public synchronized String ac_table_define_$_2_3(Args args) throws NumberFormatException { String tableName = args.argv(0); String header = args.argc() > 2 ? args.argv(2) : null; String[] list = args.argv(1).split(","); int[] array = new int[list.length]; for (int i = 0; i < list.length; i++) { array[i] = Integer.parseInt(list[i]); } _tableHash.put(tableName, new TableEntry(tableName, array, header)); return ""; } public static final String hh_table_undefine = "<tableName>"; public synchronized String ac_table_undefine_$_1(Args args) { String tableName = args.argv(0); _tableHash.remove(tableName); _nucleus.getDomainContext().remove(tableName + ".html"); return ""; } public static final String hh_table_ls = "[<tableName>]"; public synchronized String ac_table_ls_$_0_1(Args args) { StringBuilder sb = new StringBuilder(); if (args.argc() == 0) { for (TableEntry entry : _tableHash.values()) { sb.append(entry.toString()).append("\n"); } } else { String tableName = args.argv(0); TableEntry entry = _tableHash.get(tableName); if (entry == null) { throw new NoSuchElementException("Not found : " + tableName); } sb.append(entry.toString()).append("\n"); } return sb.toString(); } public static final String hh_set_update = "<updateTime/sec>"; public String ac_set_update_$_1(Args args) { long update = Long.parseLong(args.argv(0)) * 1000L; if (update < 10000L) { throw new IllegalArgumentException( "Update time must exceed 10 seconds"); } synchronized (this) { _update = update; notifyAll(); } return "Update time set to " + args.argv(0) + " seconds"; } @Override public void getInfo(PrintWriter pw) { pw.println(" Update Time : " + (_update / 1000L) + " seconds"); pw.println(" Counter : " + _processCounter); pw.println(" Last Time Used : " + _timeUsed + " milliseconds"); } @Override public void messageArrived(CellMessage envelope) { Serializable message = envelope.getMessageObject(); if (message instanceof LoginBrokerInfo) { _loginBrokerSource.messageArrived((LoginBrokerInfo) message); } else if (message instanceof NoRouteToCellException) { _loginBrokerSource.messageArrived((NoRouteToCellException) message); } } @Override public void run() { try { while (!Thread.interrupted()) { try { _processCounter++; long start = System.currentTimeMillis(); collectDataSequentially(); _timeUsed = System.currentTimeMillis() - start; } catch (RuntimeException ee) { _log.error(ee.toString(), ee); } synchronized (this) { wait(_update); } } } catch (InterruptedException e) { _log.info("Data collector interrupted"); } } public String ac_go(Args args) { synchronized (this) { notifyAll(); } return "Update started."; } private void collectDataSequentially() throws InterruptedException { try { Collection<LoginBrokerInfo> loginBrokerInfos = _collector.getLoginBrokerInfo(); Collection<LoginManagerChildrenInfo> loginManagerInfos = _collector.collectLoginManagerInfo( TransferCollector.getLoginManagers(loginBrokerInfos)).get(); Collection<IoDoorInfo> doorInfos = _collector.collectDoorInfo( TransferCollector.getDoors(loginManagerInfos)).get(); Collection<IoJobInfo> movers = _collector.collectMovers( TransferCollector.getPools(doorInfos)).get(); List<Transfer> transfers = TransferCollector.getTransfers(doorInfos, movers); transfers.sort(new TransferCollector.ByDoorAndSequence()); Map<String, Object> domainContext = _nucleus.getDomainContext(); domainContext.put("doors.html", createDoorPage(loginBrokerInfos)); domainContext.put("transfers.list", transfers); long now = System.currentTimeMillis(); List<TransferBean> beans = transfers .stream() .map((t) -> new TransferBean(t, now)) .collect(Collectors.toList()); domainContext.put("transfers.html", createHtmlTable(beans)); domainContext.put("transfers.txt", createAsciiTable(beans)); domainContext.put("transfers.json", new ObjectMapper().writerWithDefaultPrettyPrinter() .writeValueAsString(beans)); synchronized (this) { for (TableEntry entry : _tableHash.values()) { domainContext.put(entry.getName() + ".html", createDynamicTable(transfers, entry.getFields())); } } } catch (ExecutionException e) { Throwables.throwIfUnchecked(e.getCause()); throw new RuntimeException(e.getCause()); } catch (JsonProcessingException e) { throw new RuntimeException(e); } } // // the html stuff. // private String createDoorPage(Collection<LoginBrokerInfo> doors) { HTMLBuilder page = new HTMLBuilder(_nucleus.getDomainContext()); page.addHeader("/styles/doors.css", "Doors"); page.beginTable("sortable", "cell", "Cell", "domain", "Domain", "protocol", "Protocol", "version", "Version", "host", "Host", "port", "Port", "load", "Load"); for (LoginBrokerInfo door : doors) { InetAddress address = door.getAddresses().stream().max( Comparator.comparing(NetworkUtils.InetAddressScope::of)).get(); page.beginRow(null, "odd"); page.td("cell", door.getCellName()); page.td("domain", door.getDomainName()); page.td("protocol", door.getProtocolFamily()); page.td("version", door.getProtocolVersion()); page.td("host", address.getHostName()); page.td("port", door.getPort()); page.td("load", (int) (door.getLoad() * 100.0)); page.endRow(); } page.endTable(); return page.toString(); } private String createDynamicTable(List<Transfer> transfers, int[] fields) { HTMLBuilder page = new HTMLBuilder(_nucleus.getDomainContext()); page.addHeader("/styles/transfers.css", "Active Transfers"); page.beginTable("sortable"); page.beginTHead(); for (int field : fields) { page.th(__className[field], __listHeader[field]); } page.endTHead(); long now = System.currentTimeMillis(); for (Transfer transfer : transfers) { List<String> values = new ArrayList<>(); new TransferBean(transfer, now).toHtmlRow(values); for (int field : fields) { if (field >= values.size()) { page.td(__className[field], ""); } else { page.td(__className[field], values.get(field)); } } } page.endTable(); page.addFooter(getClass().getName()); return page.toString(); } public static final String hh_ls_iolist = ""; public synchronized String ac_ls_iolist(Args args) { return Objects.toString(_nucleus.getDomainContext().get("transfers.txt"), ""); } private String createAsciiTable(List<TransferBean> transfers) { StringBuilder sb = new StringBuilder(); transfers.stream().forEach((t) -> t.toAsciiLine(sb)); return sb.toString(); } private void createHtmlTableRow(HTMLBuilder page, TransferBean transfer) { page.beginRow(null, "odd"); page.td("door", transfer.getCellName()); page.td("domain", transfer.getDomainName()); page.td("sequence", transfer.getSerialId()); page.td("protocol", transfer.getProtocol().replaceAll("[<]unknown[>]", "?")); page.td("uid", transfer.getUserInfo().getUid()); page.td("gid", transfer.getUserInfo().getGid()); page.td("vomsGroup", transfer.getUserInfo().getPrimaryVOMSGroup()); String tmp = transfer.getProcess(); tmp = tmp.contains("known") ? "?" : tmp; page.td("process", tmp); String poolName = Strings.emptyToNull(transfer.getPool()); if (poolName == null || poolName.equals("<unknown>")) { poolName = "N.N."; } page.td("pnfs", transfer.getPnfsId()); page.td("pool", poolName); page.td("host", transfer.getReplyHost()); String status = transfer.getSessionStatus(); page.td("status", status != null ? status.replace(" ", " ") : ""); page.td("waiting", transfer.timeWaiting(true)); if (transfer.getMoverStatus() == null) { if (poolName.equals("N.N.")) { page.td(3, "staging", "Staging"); } else { page.td(3, "missing", "No Mover found"); } } else { page.td("state", transfer.getMoverStatus()); if (transfer.getMoverStart() != null) { page.td("transferred", BYTES.toKiB(transfer.getBytesTransferred())); } else { page.td("transferred", "-"); } page.td("speed", transfer.getTransferRate()); } page.endRow(); } private String createHtmlTable(List<TransferBean> transfers) { HTMLBuilder page = new HTMLBuilder(_nucleus.getDomainContext()); page.addHeader("/styles/transfers.css", "Active Transfers"); page.beginTable("sortable", "door", "Door", "domain", "Domain", "sequence", "Seq", "protocol", "Prot", "uid", "UID", "gid", "GID", "vomsGroup", "VOMS Group", "process", "Proc", "pnfs", "PnfsId", "pool", "Pool", "host", "Host", "status", "Status", "waiting", "Waiting", "state", "S", "transferred", "Trans. (KB)", "speed", "Speed (KB/s)"); transfers.stream().forEach((t) -> createHtmlTableRow(page, t)); page.endTable(); page.addFooter(getClass().getName()); return page.toString(); } }