code: /**
   * Copyright 2018 Alexey Ragozin
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package org.gridkit.jvmtool.hflame;
  
  import java.lang.Thread.State;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.LinkedHashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.TreeMap;
  
  import org.gridkit.jvmtool.codec.stacktrace.ThreadSnapshotEvent;
  import org.gridkit.jvmtool.stacktrace.StackFrameList;
  import org.gridkit.jvmtool.stacktrace.analytics.Calculators;
  import org.gridkit.jvmtool.stacktrace.analytics.WeigthCalculator;
  
  public class JsonFlameDataSet {
  
      private final Map<String, Integer> frameSet = new LinkedHashMap<String, Integer>();
      private final Map<String, ThreadDump> threads = new TreeMap<String, ThreadDump>();
      private final List<String> pallete = new ArrayList<String>();
      private FrameColorChooser colorer;
      private FrameFormater formater;
      private WeigthCalculator calc;
  
      public JsonFlameDataSet() {
          this.colorer = new DefaultFrameColorChooser();
          this.formater = new DefaultFrameFormater();
          this.calc = Calculators.SAMPLES;
          intern(State.RUNNABLE);
          intern(State.BLOCKED);
          intern(State.WAITING);
          intern(State.TIMED_WAITING);
      }
  
      public void setWeightCalculator(WeigthCalculator calc) {
          this.calc = calc;
      }
  
      private int intern(String frame) {
          if (frameSet.containsKey(frame)) {
              return frameSet.get(frame);
          }
          else {
              int n = frameSet.size();
              frameSet.put(frame, n);
              pallete.add(toColorLiteral(colorer.getFrameColor(frame)));
              return n;
          }
      }
  
      private int intern(State state) {
          switch (state) {
          case BLOCKED:
              return intern("(BLOCKED)");
          case NEW:
              return intern("(NEW)");
          case RUNNABLE:
              return intern("(RUNNABLE)");
          case TERMINATED:
              return intern("(TERMINATED)");
          case TIMED_WAITING:
              return intern("(TIMED_WAITING)");
          case WAITING:
              return intern("(WAITING)");
          default:
              return intern("(???)");
          }
      }
  
      private String toColorLiteral(int color) {
          if (color < 0) {
              return "null";
          }
          else {
              String hex = Integer.toHexString(color + 0x1000000);
              hex = hex.substring(hex.length() - 6);
              return "\"#" + hex + "\"";
          }
      }
  
      public void feed(Iterable<ThreadSnapshotEvent> events) {
          for(ThreadSnapshotEvent e: events) {
              if (e.stackTrace() != null && e.stackTrace().depth() > 0) {
                  String threadName = String.valueOf(e.threadName());
  
                  ThreadDump dump = thread(threadName);
  
                  int[] trace = intern(e);
                  dump.count(trace, calc.getWeigth(e));
              }
          }
      }
  
      private int[] intern(ThreadSnapshotEvent e) {
          StackFrameList trace = e.stackTrace();
          int[] r = new int[trace.depth() + 1];
          for(int i = 0; i != trace.depth(); ++i) {
              int fp = trace.depth() - 1 - i;
              r[i] = intern(formater.toString(trace.frameAt(fp)));
          }
  
          if (e.threadState() == null) {
  //			r = Arrays.copyOf(r, r.length - 1);
              r[r.length - 1] = intern("(???)");
          }
          else {
              r[r.length - 1] = intern(e.threadState());
          }
  
          return r;
      }
  
      ThreadDump thread(String name) {
          ThreadDump dump = threads.get(name);
          if (dump == null) {
              dump = new ThreadDump(name);
              threads.put(name, dump);
          }
          return dump;
      }
  
      public void exportJson(StringBuilder sb) {
          sb.append("{");
          sb.append("frames: ");
          exportFrames(sb);
          sb.append(", ");
          sb.append("frameColors: ");
          exportFrameColors(sb);
          sb.append(", ");
          sb.append("threads: ");
          exportThreads(sb);
          sb.append("}");
      }
  
      private void exportFrames(StringBuilder sb) {
          sb.append("[");
          for(String frame: frameSet.keySet()) {
              sb.append('"').append(frame).append("\", ");
          }
          sb.setLength(sb.length() - 2);
          sb.append("]");
      }
  
      private void exportFrameColors(StringBuilder sb) {
          sb.append("[");
          for(String col: pallete) {
              sb.append(col).append(", ");
          }
          sb.setLength(sb.length() - 2);
          sb.append("]");
      }
  
      private void exportThreads(StringBuilder sb) {
          sb.append("[");
          for(ThreadDump td: threads.values()) {
              sb.append("{ name: \"").append(escape(td.threadName)).append("\", traces: [");
              for(TraceWeight tw: td.traces) {
                  sb.append("{ trace: ")
                  .append(Arrays.toString(tw.trace))
                  .append(", samples: ")
                  .append(tw.samples)
                  .append("}, ");
              }
              sb.setLength(sb.length() - 2);
              sb.append("]}, ");
          }
          sb.setLength(sb.length() - 2);
          sb.append("]");
      }
  
      private String escape(String threadName) {
          if (threadName.indexOf('\\') >= 0) {
              threadName = threadName.replace("\\", "\\\\");
          }
          if (threadName.indexOf('"') >= 0) {
              threadName = threadName.replace("\"", "\\\"");
          }
          return threadName;
      }
  
      static class ThreadDump {
  
          public final String threadName;
          public final List<TraceWeight> traces;
  
          public ThreadDump(String name) {
              this.threadName = name;
              this.traces = new ArrayList<TraceWeight>();
          }
  
          public void count(int[] trace, long weight) {
              for(TraceWeight t: traces) {
                  if (Arrays.equals(t.trace, trace)) {
                      t.samples += weight;
                      return;
                  }
              }
              TraceWeight tw = new TraceWeight(trace, weight);
              traces.add(tw);
          }
      }
  
      static class TraceWeight {
  
          public final int[] trace;
          public long samples;
  
          public TraceWeight(int[] trace, long samples) {
              this.trace = trace;
              this.samples = samples;
          }
      }
  }
  