package com.liveramp.hank.ui; import java.io.IOException; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.UUID; import org.apache.commons.lang.StringUtils; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import com.liveramp.hank.coordinator.Domain; import com.liveramp.hank.coordinator.DomainAndVersion; import com.liveramp.hank.coordinator.DomainGroup; import com.liveramp.hank.coordinator.DomainVersion; import com.liveramp.hank.coordinator.Host; import com.liveramp.hank.coordinator.HostState; import com.liveramp.hank.coordinator.Ring; import com.liveramp.hank.coordinator.RingGroup; import com.liveramp.hank.coordinator.UpdateProgress; import com.liveramp.hank.coordinator.UpdateProgressAggregator; import com.liveramp.hank.partition_server.DoublePopulationStatisticsAggregator; import com.liveramp.hank.partition_server.FilesystemStatisticsAggregator; import com.liveramp.hank.partition_server.RuntimeStatisticsAggregator; import com.liveramp.hank.util.FormatUtils; public class UiUtils { private UiUtils() { } private static final int BAR_SIZE = 100; private static DateTimeFormatter dateFormat = DateTimeFormat.forPattern("d MMM yyyy HH:mm:ss"); public static String hostStateToClass(HostState state) throws IOException { switch (state) { case SERVING: return "host-serving"; case UPDATING: return "host-updating"; case IDLE: return "host-idle"; case OFFLINE: return "host-offline"; default: throw new RuntimeException("Unknown host state."); } } public static String formatHostListTooltip(Ring ring, Set<Host> hosts) throws IOException { return formatHostListTooltip(ring.getRingGroup().getName() + " ring " + ring.getRingNumber(), hosts); } public static String formatHostListTooltip(RingGroup ringGroup, Set<Host> hosts) throws IOException { return formatHostListTooltip(ringGroup.getName(), hosts); } private static String formatHostListTooltip(String title, Set<Host> hosts) throws IOException { if (hosts.size() == 0) { return "-"; } else { TreeSet<Host> sortedHosts = new TreeSet<Host>(hosts); StringBuilder content = new StringBuilder(); for (Host host : sortedHosts) { content.append("<div class='" + hostStateToClass(host.getState()) + "'>"); content.append(host.getAddress().toString()); content.append("</div>"); } return htmlTooltip(Integer.toString(hosts.size()), title, content.toString()); } } private static void addBar(StringBuilder content, String label, Double value, Double maximum, String unit) { if (value == null) { value = 0.0; } if (maximum == null) { maximum = 0.0; } long size = 0; if (maximum != 0) { size = Math.round(BAR_SIZE * (Math.log(1 + value) / Math.log(1 + maximum))); } if (size < 1) { size = 1; } content.append("<tr><td>"); content.append(label); content.append("</td><td>"); content.append("<div class='tooltipBar' style='width: " + size + "px;'></div>"); content.append("</td><td>"); content.append(DoublePopulationStatisticsAggregator.formatDouble(value)); content.append(unit); content.append("</td></tr>"); } private static String htmlTooltip(String text, String title, String content) { String uniqueId = "tooltipContent_" + UUID.randomUUID().toString().replaceAll("-", "_"); return "<div id=\"" + uniqueId + "\" style=\"display: none;\">" + content + "</div>" + "<div style=\"cursor: help;\" onmouseover=\"tooltip.show('" + uniqueId + "', '" + title + "');\" onmouseout=\"tooltip.hide();\">" + text + "</div>"; } public static String formatDomainGroupDomainVersionsTable(DomainGroup domainGroup, String cssClass, boolean linkToDomains) throws IOException { StringBuilder content = new StringBuilder(); content.append("<table class='" + cssClass + "'><tr><th>Domain</th><th>Version</th><th>Closed On</th></tr>"); for (DomainAndVersion version : domainGroup.getDomainVersionsSorted()) { content.append("<tr><td class='centered'>"); if (linkToDomains) { content.append("<a href='/domain.jsp?n=" + URLEnc.encode(version.getDomain().getName()) + "'>"); content.append(version.getDomain().getName()); content.append("</a>"); } else { content.append(version.getDomain().getName()); } content.append("</td><td class='centered'>"); content.append(version.getVersionNumber()); content.append("</td><td class='centered'>"); content.append(formatDomainVersionClosedAt(version.getDomain().getVersion(version.getVersionNumber()))); content.append("</td></tr>"); } content.append("</table>"); return content.toString(); } public static String formatDomainGroupInfoTooltip(DomainGroup domainGroup, String text) throws IOException { String title = domainGroup.getName(); String content = formatDomainGroupDomainVersionsTable(domainGroup, "domain-group-info", false); return htmlTooltip(text, title, content); } public static String formatDomainVersionClosedAt(DomainVersion domainVersion) throws IOException { if (domainVersion == null) { return "?"; } Long closedAt = domainVersion.getClosedAt(); if (closedAt == null) { return "-"; } else { return dateFormat.print(closedAt); } } public static String formatPopulationStatistics(String title, DoublePopulationStatisticsAggregator populationStatistics) { if (populationStatistics == null) { return "-"; } else { double[] deciles = populationStatistics.computeDeciles(); StringBuilder tooltipContent = new StringBuilder(); tooltipContent.append("<table>"); addBar(tooltipContent, "min", populationStatistics.getMinimum(), populationStatistics.getMaximum(), "ms"); for (int i = 0; i < 9; ++i) { addBar(tooltipContent, ((i + 1) * 10) + "%", deciles[i], populationStatistics.getMaximum(), "ms"); } addBar(tooltipContent, "max", populationStatistics.getMaximum(), populationStatistics.getMaximum(), "ms"); tooltipContent.append("</table>"); return htmlTooltip(populationStatistics.format(), title, tooltipContent.toString()); } } public static String formatFilesystemStatistics(FilesystemStatisticsAggregator filesystemStatistics) { return FormatUtils.formatDouble(filesystemStatistics.getUsedPercentage()) + "% used, " + FormatUtils.formatNumBytes(filesystemStatistics.getUsedSpace()) + "/" + FormatUtils.formatNumBytes(filesystemStatistics.getTotalSpace()) ; } public static String formatCacheHits(RuntimeStatisticsAggregator runtimeStatisticsAggregator) { double l1 = runtimeStatisticsAggregator.getL1CacheHitRate(); double l2 = runtimeStatisticsAggregator.getL2CacheHitRate(); if (l1 == 0 && l2 == 0) { return "-"; } else { String l1Str = "-"; String l2Str = "-"; if (l1 != 0) { l1Str = FormatUtils.formatDouble(l1 * 100.0) + "%"; } if (l2 != 0) { l2Str = FormatUtils.formatDouble(l2 * 100.0) + "%"; } return l1Str + " / " + l2Str; } } public static String formatUpdateProgress(UpdateProgressAggregator updateProgressAggregator) { return formatUpdateProgress(updateProgressAggregator, -1); } public static String formatUpdateProgress(UpdateProgressAggregator updateProgressAggregator, long eta) { if (updateProgressAggregator == null) { return ""; } StringBuilder content = new StringBuilder(); StringBuilder tooltip = new StringBuilder(); UpdateProgress updateProgress = updateProgressAggregator.computeUpdateProgress(); content.append(FormatUtils.formatDouble(updateProgress.getUpdateProgress() * 100) + "% up-to-date" + " (" + updateProgress.getNumPartitionsUpToDate() + "/" + updateProgress.getNumPartitions() + ")"); if (eta >= 0) { content.append(" ETA: " + FormatUtils.formatSecondsDuration(eta)); } content.append("<div class=\'progress-bar\'><div class=\'progress-bar-filler\' style=\'width: " + Math.round(updateProgress.getUpdateProgress() * 100) + "%\'></div></div>"); // Build tooltip tooltip.append("<table>"); for (Map.Entry<Domain, UpdateProgress> entry : updateProgressAggregator.sortedEntrySet()) { UpdateProgress domainUpdateProgress = entry.getValue(); tooltip.append("<tr><td class='centered'>"); tooltip.append(entry.getKey().getName()); tooltip.append("</td><td class='centered'>"); tooltip.append("<div class=\'progress-bar\'><div class=\'progress-bar-filler\' style=\'width: " + Math.round(domainUpdateProgress.getUpdateProgress() * 100) + "%\'></div></div>"); tooltip.append("</td><td class='centered'>"); tooltip.append(FormatUtils.formatDouble(domainUpdateProgress.getUpdateProgress() * 100) + "%"); tooltip.append("</td></tr>"); } tooltip.append("</table>"); return htmlTooltip(content.toString(), "Update Progress", tooltip.toString()); } public static String join(List<String> input, String separator) { return StringUtils.join(input, separator); } }