package com.jivesoftware.os.amza.ui.region; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.jivesoftware.os.amza.api.partition.PartitionName; import com.jivesoftware.os.amza.api.partition.VersionedPartitionName; import com.jivesoftware.os.amza.api.ring.RingMemberAndHost; import com.jivesoftware.os.amza.service.AmzaRingStoreReader; import com.jivesoftware.os.amza.service.replication.AmzaAquariumProvider; import com.jivesoftware.os.amza.service.ring.AmzaRingReader; import com.jivesoftware.os.amza.service.ring.RingTopology; import com.jivesoftware.os.amza.ui.region.AquariumPluginRegion.AquariumPluginRegionInput; import com.jivesoftware.os.amza.ui.soy.SoyRenderer; import com.jivesoftware.os.aquarium.AquariumStats; import com.jivesoftware.os.aquarium.Liveliness; import com.jivesoftware.os.aquarium.Member; import com.jivesoftware.os.aquarium.State; import com.jivesoftware.os.aquarium.Waterline; import com.jivesoftware.os.lab.guts.LABSparseCircularMetricBuffer; import com.jivesoftware.os.mlogger.core.MetricLogger; import com.jivesoftware.os.mlogger.core.MetricLoggerFactory; import java.awt.Color; import java.text.NumberFormat; import java.util.ArrayList; import java.util.Collections; import java.util.EnumMap; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.LongAdder; /** * */ // soy.page.aquariumPluginRegion public class AquariumPluginRegion implements PageRegion<AquariumPluginRegionInput> { private static final MetricLogger LOG = MetricLoggerFactory.getLogger(); private final NumberFormat numberFormat = NumberFormat.getInstance(); private final String template; private final SoyRenderer renderer; private final AmzaRingStoreReader ringReader; private final AmzaAquariumProvider aquariumProvider; private final Liveliness liveliness; private final AquariumStats aquariumStats; public final EnumMap<State, LABSparseCircularMetricBuffer> currentStateMetricBuffer = new EnumMap<>(State.class); public final EnumMap<State, LABSparseCircularMetricBuffer> desiredStateMetricBuffer = new EnumMap<>(State.class); public AquariumPluginRegion(String template, SoyRenderer renderer, AmzaRingStoreReader ringReader, AmzaAquariumProvider aquariumProvider, Liveliness liveliness, AquariumStats aquariumStats) { this.template = template; this.renderer = renderer; this.ringReader = ringReader; this.aquariumProvider = aquariumProvider; this.liveliness = liveliness; this.aquariumStats = aquariumStats; for (State state : State.values()) { currentStateMetricBuffer.put(state, new LABSparseCircularMetricBuffer(120, 0, 1_000)); desiredStateMetricBuffer.put(state, new LABSparseCircularMetricBuffer(120, 0, 1_000)); } Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> { try { refresh(); } catch (Exception x) { LOG.warn("Refresh aquarium stats failed", x); } }, 0, 500, TimeUnit.MILLISECONDS); } public void refresh() { long timestamp = System.currentTimeMillis(); for (Entry<State, LongAdder> e : aquariumStats.currentState.entrySet()) { currentStateMetricBuffer.get(e.getKey()).set(timestamp, e.getValue()); } for (Entry<State, LongAdder> e : aquariumStats.desiredState.entrySet()) { desiredStateMetricBuffer.get(e.getKey()).set(timestamp, e.getValue()); } } public static class AquariumPluginRegionInput { final String ringName; final String partitionName; final String hexPartitionVersion; public AquariumPluginRegionInput(String ringName, String partitionName, String hexPartitionVersion) { this.ringName = ringName; this.partitionName = partitionName; this.hexPartitionVersion = hexPartitionVersion; } } @Override public String render(AquariumPluginRegionInput input) { Map<String, Object> data = Maps.newHashMap(); try { data.put("ringName", input.ringName); data.put("partitionName", input.partitionName); data.put("partitionVersion", input.hexPartitionVersion); List<Map<String, String>> currentStats = Lists.newArrayList(); currentStats.add(ImmutableMap.of("name", "getMyCurrentWaterline", "value", numberFormat.format(aquariumStats.getMyCurrentWaterline.longValue()))); currentStats.add(ImmutableMap.of("name", "getOthersCurrentWaterline", "value", numberFormat.format(aquariumStats.getOthersCurrentWaterline .longValue()))); currentStats .add(ImmutableMap.of("name", "acknowledgeCurrentOther", "value", numberFormat.format(aquariumStats.acknowledgeCurrentOther.longValue()))); List<Map<String, String>> desiredStats = Lists.newArrayList(); desiredStats.add(ImmutableMap.of("name", "getMyDesiredWaterline", "value", numberFormat.format(aquariumStats.getMyDesiredWaterline.longValue()))); desiredStats.add(ImmutableMap.of("name", "getOthersDesiredWaterline", "value", numberFormat.format(aquariumStats.getOthersDesiredWaterline .longValue()))); desiredStats .add(ImmutableMap.of("name", "acknowledgeDesiredOther", "value", numberFormat.format(aquariumStats.acknowledgeDesiredOther.longValue()))); Map<String, Object> stats = Maps.newHashMap(); stats.put("tapTheGlass", numberFormat.format(aquariumStats.tapTheGlass.longValue())); stats.put("tapTheGlassNotified", numberFormat.format(aquariumStats.tapTheGlassNotified.longValue())); stats.put("captureEndState", numberFormat.format(aquariumStats.captureEndState.longValue())); stats.put("suggestState", numberFormat.format(aquariumStats.suggestState.longValue())); stats.put("getLivelyEndState", numberFormat.format(aquariumStats.getLivelyEndState.longValue())); stats.put("getStateForMember", numberFormat.format(aquariumStats.getStateForMember.longValue())); stats.put("isLivelyStateForMember", numberFormat.format(aquariumStats.isLivelyStateForMember.longValue())); stats.put("isLivelyEndStateForMember", numberFormat.format(aquariumStats.isLivelyEndStateForMember.longValue())); stats.put("feedTheFish", numberFormat.format(aquariumStats.feedTheFish.longValue())); stats.put("acknowledgeOther", numberFormat.format(aquariumStats.acknowledgeOther.longValue())); stats.put("awaitOnline", numberFormat.format(aquariumStats.awaitOnline.longValue())); stats.put("awaitTimedOut", numberFormat.format(aquariumStats.awaitTimedOut.longValue())); stats.put("current", currentStats); stats.put("desired", desiredStats); data.put("stats", stats); List<Map<String, Object>> wavformGroups = Lists.newArrayList(); State[] aquariumStates = State.values(); String[] names = new String[aquariumStates.length * 2]; LABSparseCircularMetricBuffer[] waves = new LABSparseCircularMetricBuffer[aquariumStates.length * 2]; boolean[] fills = new boolean[aquariumStates.length * 2]; for (int i = 0; i < aquariumStates.length; i++) { int j = i * 2; fills[j] = false; fills[j + 1] = false; names[j] = "current-" + aquariumStates[i]; names[j + 1] = "desired-" + aquariumStates[i]; waves[j] = currentStateMetricBuffer.get(aquariumStates[i]); waves[j + 1] = desiredStateMetricBuffer.get(aquariumStates[i]); } wavformGroups.addAll(wavformGroup("states", null, "aquarium-states", stateColors, names, waves, fills)); data.put("wavestats", wavformGroups); long now = System.currentTimeMillis(); List<Map<String, Object>> live = new ArrayList<>(); RingTopology ring = ringReader.getRing(AmzaRingReader.SYSTEM_RING, -1); for (RingMemberAndHost entry : ring.entries) { long aliveUntilTimestamp = liveliness.aliveUntilTimestamp(entry.ringMember.asAquariumMember()); live.add(ImmutableMap.of( "member", entry.ringMember.getMember(), "host", entry.ringHost.toCanonicalString(), "liveliness", (aliveUntilTimestamp > now) ? "alive for " + String.valueOf(aliveUntilTimestamp - now) : "dead for " + String.valueOf( aliveUntilTimestamp - now) )); } data.put("liveliness", live); byte[] ringNameBytes = input.ringName.getBytes(); byte[] partitionNameBytes = input.partitionName.getBytes(); PartitionName partitionName = (ringNameBytes.length > 0 && partitionNameBytes.length > 0) ? new PartitionName(false, ringNameBytes, partitionNameBytes) : null; long partitionVersion = Long.parseLong(input.hexPartitionVersion, 16); VersionedPartitionName versionedPartitionName = partitionName != null ? new VersionedPartitionName(partitionName, partitionVersion) : null; if (versionedPartitionName != null) { List<Map<String, Object>> states = new ArrayList<>(); aquariumProvider.tx(versionedPartitionName, (readCurrent, readDesired, writeCurrent, writeDesired) -> { for (RingMemberAndHost entry : ring.entries) { Member asMember = entry.ringMember.asAquariumMember(); Map<String, Object> state = new HashMap<>(); state.put("partitionName", input.partitionName); state.put("ringName", input.ringName); state.put("partitionVersion", input.hexPartitionVersion); if (readCurrent != null) { Waterline current = readCurrent.get(asMember); if (current != null) { state.put("current", asMap(liveliness, current)); } } if (readDesired != null) { Waterline desired = readDesired.get(asMember); if (desired != null) { state.put("desired", asMap(liveliness, desired)); } } states.add(state); } return true; }); data.put("aquarium", states); } } catch (Exception e) { LOG.error("Unable to retrieve data", e); } return renderer.render(template, data); } private Color[] stateColors = new Color[]{ new Color(0, 0, 128), Color.blue, new Color(128, 0, 0), Color.red, new Color(128, 100, 0), Color.orange, new Color(0, 128, 0), Color.green, new Color(0, 64, 64), // teal new Color(0, 128, 128), // teal new Color(128, 0, 128), Color.magenta, Color.darkGray, Color.gray }; private static Map<String, Object> asMap(Liveliness liveliness, Waterline waterline) throws Exception { State state = waterline.getState(); Map<String, Object> map = new HashMap<>(); map.put("state", state == null ? "null" : state.name()); map.put("member", new String(waterline.getMember().getMember())); map.put("timestamp", String.valueOf(waterline.getTimestamp())); map.put("version", String.valueOf(waterline.getVersion())); map.put("alive", String.valueOf(liveliness.isAlive(waterline.getMember()))); map.put("quorum", waterline.isAtQuorum()); return map; } private List<Map<String, Object>> wavformGroup(String group, String filter, String title, Color[] colors, String[] waveName, LABSparseCircularMetricBuffer[] waveforms, boolean[] fill) { if (filter != null && filter.length() > 0 && !title.contains(filter)) { return Collections.emptyList(); } String total = ""; List<String> ls = new ArrayList<>(); int s = 1; for (double m : waveforms[0].metric()) { ls.add("\"" + s + "\""); s++; } List<Map<String, Object>> ws = new ArrayList<>(); for (int i = 0; i < waveName.length; i++) { List<String> values = Lists.newArrayList(); double[] metric = waveforms[i].metric(); boolean nonZero = false; for (double m : metric) { if (m < 0.0d || m > 0.0d) { nonZero = true; } values.add("\"" + String.valueOf(m) + "\""); } if (!nonZero) { continue; } ws.add(waveform(waveName[i], new Color[]{colors[i]}, 1f, values, fill[i], false)); if (i > 0) { total += ", "; } Color c = colors[i]; int r = c.getRed(); int g = c.getGreen(); int b = c.getBlue(); String colorDiv = "<div style=\"display:inline-block; width:10px; height:10px; background:rgb(" + r + "," + g + "," + b + ");\"></div>"; total += colorDiv + waveName[i] + "=" + numberFormat.format(waveforms[i].total()); } List<Map<String, Object>> listOfwaveformGroups = Lists.newArrayList(); List<Map<String, Object>> ows = new ArrayList<>(); List<String> ols = new ArrayList<>(); List<String> ovalues = Lists.newArrayList(); Color[] ocolors = new Color[waveforms.length]; for (int i = 0; i < waveforms.length; i++) { ovalues.add("\"" + String.valueOf(waveforms[i].total()) + "\""); ols.add("\"" + waveName[i] + "\""); ocolors[i] = colors[i]; } ows.add(waveform(title + "-overview", ocolors, 1f, ovalues, true, false)); Map<String, Object> overViewMap = new HashMap<>(); overViewMap.put("group", group); overViewMap.put("filter", title); overViewMap.put("title", title + "-overview"); overViewMap.put("total", ""); overViewMap.put("height", String.valueOf(150)); overViewMap.put("width", String.valueOf(ls.size() * 10)); overViewMap.put("id", title + "-overview"); overViewMap.put("graphType", "bar"); overViewMap.put("waveform", ImmutableMap.of("labels", ols, "datasets", ows)); listOfwaveformGroups.add(overViewMap); Map<String, Object> map = new HashMap<>(); map.put("group", group); map.put("filter", title); map.put("title", title); map.put("total", total); if (filter != null && filter.length() > 0) { map.put("height", String.valueOf(800)); } else { map.put("height", String.valueOf(300)); } map.put("width", String.valueOf(ls.size() * 10)); map.put("id", title); map.put("graphType", "line"); map.put("waveform", ImmutableMap.of("labels", ls, "datasets", ws)); listOfwaveformGroups.add(map); return listOfwaveformGroups; } public Map<String, Object> waveform(String label, Color[] color, float alpha, List<String> values, boolean fill, boolean stepped) { Map<String, Object> waveform = new HashMap<>(); waveform.put("label", "\"" + label + "\""); Object c = "\"rgba(" + color[0].getRed() + "," + color[0].getGreen() + "," + color[0].getBlue() + "," + String.valueOf(alpha) + ")\""; if (color.length > 1) { List<String> colorStrings = Lists.newArrayList(); for (int i = 0; i < color.length; i++) { colorStrings.add("\"rgba(" + color[i].getRed() + "," + color[i].getGreen() + "," + color[i].getBlue() + "," + String.valueOf(alpha) + ")\""); } c = colorStrings; } waveform.put("fill", fill); waveform.put("steppedLine", stepped); waveform.put("lineTension", "0.1"); waveform.put("backgroundColor", c); waveform.put("borderColor", c); waveform.put("borderCapStyle", "'butt'"); waveform.put("borderDash", "[]"); waveform.put("borderDashOffset", 0.0); waveform.put("borderJoinStyle", "'miter'"); waveform.put("pointBorderColor", c); waveform.put("pointBackgroundColor", "\"#fff\""); waveform.put("pointBorderWidth", 1); waveform.put("pointHoverRadius", 5); waveform.put("pointHoverBackgroundColor", c); waveform.put("pointHoverBorderColor", c); waveform.put("pointHoverBorderWidth", 2); waveform.put("pointRadius", 1); waveform.put("pointHitRadius", 10); waveform.put("spanGaps", false); waveform.put("data", values); return waveform; } @Override public String getTitle() { return "Aquarium"; } }