All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.gridkit.jvmtool.stacktrace.analytics.flame.AbstractFlameCharter Maven / Gradle / Ivy

There is a newer version: 0.23
Show newest version
/**
 * Copyright 2016 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.stacktrace.analytics.flame;

import java.io.IOException;
import java.io.Writer;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Locale;
import java.util.SortedMap;
import java.util.TreeMap;

import org.gridkit.jvmtool.stacktrace.GenericStackElement;
import org.gridkit.jvmtool.stacktrace.StackFrame;
import org.gridkit.jvmtool.stacktrace.StackFrameList;

public abstract class AbstractFlameCharter {

    private static final GenericStackElement[] ROOT = new GenericStackElement[0];
    private static final Locale SVG_LOCALE;
    static {
        SVG_LOCALE = Locale.ROOT;
    }

    private Node root;

    private FlameColorPicker colorPicker = new DefaultColorPicker();

    public AbstractFlameCharter() {
        root = new Node(ROOT, comparator());
    }

    protected abstract Comparator comparator();

    public void setColorPicker(FlameColorPicker cp) {
        this.colorPicker = cp;
    }

    public void feed(StackFrameList trace) {
        Node node = root;
        ++node.totalCount;
        for(int i = trace.depth(); i > 0; --i) {
            StackFrame f = trace.frameAt(i - 1).withoutSource();
            Node c = node.child(f);
            ++c.totalCount;
            node = c;
        }
        ++node.terminalCount;
    }

    public void renderSVG(String title, int width, Writer writer) throws IOException {
        int topm = 24;
        int bm = 0;
        int frameheight = 16;

        int threashold = (int)(1.5d * root.totalCount / width);
        int maxDepth = calculateMaxDepth(root, threashold);

        int height = maxDepth * frameheight + topm + bm;

        appendHeader(width, height, writer);

        format(writer, "\n", width, height);

        format(writer, "%s\n", width/2, topm, title);

        appendChildNodes(writer, root, 0, width, height - frameheight, frameheight, threashold);

        format(writer, "");
    }

    private int calculateMaxDepth(Node node, int threshold) {
        if (node.totalCount < threshold) {
            return 0;
        }
        else {
            int max = 0;
            for(Node n: node.children.values()) {
                max = Math.max(max, calculateMaxDepth(n, threshold));
            }
            return max + 1;
        }
    }

    private void appendChildNodes(Writer writer, Node node, int xoffs, int width, int height, int frameheight, int threshold) throws IOException {
        int x = xoffs;
//        x += node.terminalCount / 2;
        for(Node child: node.children.values()) {
            if (child.totalCount > threshold) {
                renderNode(writer, child, x, height, width, frameheight);
                appendChildNodes(writer, child, x, width, height - frameheight, frameheight, threshold);
            }
            x += child.totalCount;
        }
        if (node.terminalCount > threshold) {
            renderSmoke(writer, x, node.terminalCount, height, width, frameheight);
        }
    }

    private void renderNode(Writer writer, Node node, int x, int height, int width, int frameheight) throws IOException {
        double rx = (double)(width) * x / root.totalCount;
        double rw = (double)(width) * node.totalCount / root.totalCount;
        double ry = height;
        double rh = frameheight;

        int c = colorPicker.pickColor(node.path);
        int cr = (0xFF) & (c >> 16);
        int cg = (0xFF) & (c >>  8);
        int cb = (0xFF) & (c >>  0);

        // Node box
        format(writer, "\n");
        format(writer, "%s (%d samples, %.2f%%)\n",
                escape(describe(node)), node.totalCount, 100d * node.totalCount / root.totalCount);
        format(writer, "\n",
                rx, ry, rw, rh, cr, cg, cb);
        format(writer, "%s\n",
                rx + 10, ry + frameheight - 3, escape(trimStr(describe(node), (int)(rw - 10) / 7)));
        format(writer, "\n");

//        if (node.terminalCount == node.totalCount) {
//            renderSmoke(writer, x, node.terminalCount, height, width, frameheight);
//        }
    }

    private void renderSmoke(Writer writer, int x, int samples, int height, int width, int frameheight) throws IOException {
        double rx = (double)(width) * x / root.totalCount;
        double rw = (double)(width) * samples / root.totalCount;
        double ry = height;
        double rh = frameheight;

        format(writer, "\n");
        format(writer, "%d samples, %.2f%%", samples, 100d * samples / root.totalCount);
        format(writer, "\n",
                rx, ry + rh / 2, rw, 3f);
        format(writer, "\n");
    }

    private String trimStr(String describe, int len) {
        if (len < 3) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        int n = Math.min(describe.length(), len);
        boolean trimed = describe.length() > len;
        for(int i = 0; i != n; ++i) {
            if (trimed && i > n - 3) {
                sb.append('.');
            }
            else {
                sb.append(describe.charAt(i));
            }
        }
        return sb.toString();
    }

    protected String escape(String text) {
        return text
             .replace((CharSequence)"<", "<")
             .replace((CharSequence)">", ">");
    }

    protected abstract String describe(Node node);

    private void format(Writer writer, String format, Object...  args) throws IOException {
        writer.append(String.format(SVG_LOCALE, format, args));
    }

    protected void appendHeader(int width, int height, Writer writer) throws IOException {
        format(writer, "\n");
        format(writer, "\n"
                , width, height, width, height);
        format(writer, "");
        format(writer, "  \n");
        format(writer, "      \n");
        format(writer, "      \n");
        format(writer, "  \n");
        format(writer, "\n");
        format(writer, "\n");
    }

    static class Node {

        GenericStackElement[] path;
        int totalCount;
        int terminalCount;
        Comparator comparator;
        SortedMap children;

        public Node(GenericStackElement[] path, Comparator comparator) {
            this.path = path;
            this.comparator = comparator;
            this.children = new TreeMap(comparator);
        }

        public Node child(GenericStackElement f) {
            Node c = children.get(f);
            if (c == null) {
                GenericStackElement[] npath = Arrays.copyOf(path, path.length + 1);
                npath[path.length] = f;
                c = new Node(npath, comparator);
                children.put(f, c);
            }
            return c;
        }

        public GenericStackElement element() {
            return path[path.length - 1];
        }

        @Override
        public String toString() {
            return path.length == 0 ? "" : path[path.length - 1].toString();
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy