サイトマップ

JavaScript Profiler/Tracer --- Java

Profiler.java

Profiler.java
/*
 * Copyright 2015 Shorindo, Inc.
 * 
 * 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 com.shorindo.jstools;
 
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.List;
 
import org.mozilla.javascript.CompilerEnvirons;
import org.mozilla.javascript.IRFactory;
import org.mozilla.javascript.Node;
import org.mozilla.javascript.Parser;
import org.mozilla.javascript.ast.AstNode;
import org.mozilla.javascript.ast.AstRoot;
import org.mozilla.javascript.ast.ExpressionStatement;
import org.mozilla.javascript.ast.FunctionCall;
import org.mozilla.javascript.ast.FunctionNode;
import org.mozilla.javascript.ast.Name;
import org.mozilla.javascript.ast.NodeVisitor;
import org.mozilla.javascript.ast.NumberLiteral;
import org.mozilla.javascript.ast.PropertyGet;
import org.mozilla.javascript.ast.ReturnStatement;
import org.mozilla.javascript.ast.StringLiteral;
 
/**
 * 
 */
public class Profiler extends Instrument {
    private static String OBJECT_NAME = "__jstools__";
    private static String ENTER_NAME = "enter";
    private static String EXIT_NAME = "exit";
        private String source;
 
    public static void main(String args[]) {
        try {
            Profiler profiler = new Profiler(new File("tools"));
            profiler.setIncludes("*.js");
            profiler.instrumentSources(
                    new File("tools"),
                    new File("instrumented"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
 
    public Profiler(File base) {
        super(base);
        fileList = new ArrayList<String>();
    }
 
    public void instrumentSources(File srcDir, File destDir) throws IOException {
        String sourcePath = srcDir.getAbsolutePath();
        List<File> sourceList = visit(srcDir);
        for (File src : sourceList) {
            String path = src.getAbsolutePath().substring(sourcePath.length());
            File dest = new File(destDir, path);
            dest.getParentFile().mkdirs();
            if (matchPattern(src)) {
                log("[inst]" + dest.getAbsolutePath());
                String instrumented = instrument(src);
                Writer writer = new FileWriter(dest);
                writer.write(instrumented);
                writer.close();
 
                String filePath = src.toURI().toString();
                String srcPath = srcDir.toURI().toString();
                filePath = filePath.substring(srcPath.length());
                fileList.add(filePath);
                generateSourceView(srcDir, destDir, filePath);
            } else {
                if (dest.exists() && dest.lastModified() >= src.lastModified())
                    continue;
                log("[copy]" + dest.getAbsolutePath());
                Files.copy(src.toPath(), dest.toPath(), StandardCopyOption.REPLACE_EXISTING);
            }
        }
        generateTools(destDir);
    }
 
    protected void generateSourceView(File srcDir, File destDir, String path) throws IOException {
        BufferedReader reader = new BufferedReader(new FileReader(new File(srcDir, path)));
        File destFile = new File(destDir, ".jstools/sources/" + path + ".html");
        destFile.getParentFile().mkdirs();
        PrintWriter writer = new PrintWriter(new FileWriter(destFile));
//        writer.println("<!doctype html>");
//        writer.println("<html>");
//        writer.println("<head>");
//        writer.println("<style type=\"text/css\">");
//        writer.println("body { font-family:\"monospace\"; }");
//        writer.println("pre.line { margin:0 0 0 0px; min-height:1em; }");
//        writer.println("</style>");
//        writer.println("<script type=\"text/javascript\">");
//        writer.println("window.onload = function() {");
//        writer.println("  var line = document.getElementById(location.hash.replace(/^#/, ''));");
//        writer.println("  if (line) line.style.background = 'yellow';");
//        writer.println("};");
//        writer.println("</script>");
//        writer.println("</head>"); 
//        writer.println("<body>");
        writer.println("<ol>");
        String line;
        int lineNumber = 1;
        while ((line = reader.readLine()) != null) {
            line = line.replaceAll("&", "&amp;")
                    .replaceAll("<", "&lt;")
                    .replaceAll(">", "&gt;")
                    .replaceAll("\"", "&quot;");
            writer.print("<li><pre class=\"line\" id=\"line-" + lineNumber + "\">");
            writer.print("<a name=\"line-" + lineNumber + "\"></a>");
            writer.print(line);
            writer.println("</pre></li>");
            lineNumber++;
        }
        writer.println("</ol>"); 
//        writer.println("</body>"); 
//        writer.println("</html>");
        writer.close();
        reader.close();
    }
 
    public String instrument(File source) throws IOException {
        Reader reader = new FileReader(source);
        int len = 0;
        char buf[] = new char[4096];
        StringBuffer sb = new StringBuffer();
        while ((len = reader.read(buf)) > 0) {
            sb.append(buf, 0, len);
        }
        reader.close();
        this.source = sb.toString();
 
        return _instrument(source, this.source);
    }
 
    private String _instrument(File file, String source) throws IOException {
        int fileId = fileList.size();
        final List<FunctionNode> functionNodeList = new ArrayList<FunctionNode>();
        final List<ReturnStatement> returnList = new ArrayList<ReturnStatement>();
 
        AstNode root = parse(new StringReader(source));
        root.visit(new NodeVisitor() {
            @Override
            public boolean visit(AstNode node) {
                if (node instanceof FunctionNode) {
                    functionNodeList.add((FunctionNode)node);
                } else if (node instanceof ReturnStatement) {
                    returnList.add((ReturnStatement)node);
                }
                return true;
            }
        });
        for (FunctionNode node : functionNodeList) {
            insrumentFunction(fileId, node);
        }
        for (ReturnStatement node : returnList) {
            instrumentReturn(node);
        }
        return preProcess(file, fileId) + root.toSource();
    }
 
    public AstRoot parse(Reader reader) throws IOException {
        CompilerEnvirons env = new CompilerEnvirons();
        env.setRecoverFromErrors(true);
        env.setGenerateDebugInfo(true);
        env.setRecordingComments(true);
        env.setRecordingLocalJsDocComments(true);
 
        StringBuilder sb = new StringBuilder();
        int len = 0;
        char chars[] = new char[4096];
        while ((len = reader.read(chars)) > 0) {
            sb.append(chars, 0, len);
        }
        Parser parser = new Parser(env);
        return parser.parse(sb.toString(), null, 1);
    }
 
    public String preProcess(File file, int fileId) {
        StringBuffer sb = new StringBuffer();
        try {
            InputStream is = getClass().getClassLoader().getResourceAsStream("template/jstools.js");
            InputStreamReader reader = new InputStreamReader(is, "UTF-8");
            char buf[] = new char[2048];
            int len = 0;
            while ((len = reader.read(buf)) > 0) {
                sb.append(new String(buf, 0, len));
            }
            reader.close();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return sb.toString();
    }
 
    protected void insrumentFunction(int fileId, FunctionNode node) {
        Name objName = new Name();
        objName.setIdentifier(OBJECT_NAME);
        Name callName = new Name();
        callName.setIdentifier(ENTER_NAME);
        PropertyGet propertyGet = new PropertyGet();
        propertyGet.setTarget(objName);
        propertyGet.setProperty(callName);
        FunctionCall call = new FunctionCall();
 
        FunctionInfo info = new FunctionInfo();
        info.setFileId(fileId);
        info.setFunctionId(functionList.size() + 1);
        info.setName(resolveName(node));
        info.setRow(node.getLineno());
        info.setCol(getColumn(node));
        functionList.add(info);
 
        call.setTarget(propertyGet);
        NumberLiteral functionId = new NumberLiteral();
        functionId.setValue(String.valueOf(info.getFunctionId()));
        call.addArgument(functionId);
        StringLiteral functionName = new StringLiteral();
        functionName.setQuoteCharacter('\'');
        functionName.setValue(info.getName());
        call.addArgument(functionName);
        ExpressionStatement stmt = new ExpressionStatement();
        stmt.setExpression(call);
 
        AstNode body = node.getBody();
        Node first = body.getFirstChild();
        if (first != null) {
            body.addChildBefore(stmt, first);
        } else {
            body.addChild(stmt);
        }
        Node last = body.getLastChild();
        if (!(last instanceof ReturnStatement)) {
            ReturnStatement returnNode = new ReturnStatement();
            instrumentReturn(returnNode);
            body.addChild(returnNode);
        }
    }
 
    private int getColumn(AstNode node) {
        if (this.source == null || this.source.length() < node.getAbsolutePosition())
            return 0;
        String forward = this.source.substring(0, node.getAbsolutePosition());
        int pos = forward.lastIndexOf('\n');
        if (pos < 0) {
            pos = forward.length() + 1;
        } else {
            pos = forward.length() - pos;
        }
        return pos;
    }
 
    protected void instrumentReturn(ReturnStatement node) {
        Name exitName = new Name();
        exitName.setIdentifier(EXIT_NAME);
        PropertyGet propertyGet = new PropertyGet();
        Name tracerName = new Name();
        tracerName.setIdentifier(OBJECT_NAME);
        propertyGet.setTarget(tracerName);
        propertyGet.setProperty(exitName);
        FunctionCall call = new FunctionCall();
        call.setTarget(propertyGet);
 
        AstNode returnValue = node.getReturnValue();
        if (returnValue != null) {
            node.removeChildren();
            call.addArgument(returnValue);
        }
        node.setReturnValue(call);
    }
 
}

Instrument.java

Instrument.java
/*
 * Copyright 2015 Shorindo, Inc.
 * 
 * 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 com.shorindo.jstools;
 
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
 
import org.mozilla.javascript.ast.Assignment;
import org.mozilla.javascript.ast.AstNode;
import org.mozilla.javascript.ast.AstRoot;
import org.mozilla.javascript.ast.FunctionNode;
import org.mozilla.javascript.ast.ObjectProperty;
import org.mozilla.javascript.ast.PropertyGet;
import org.mozilla.javascript.ast.StringLiteral;
import org.mozilla.javascript.ast.VariableInitializer;
 
public abstract class Instrument {
    private List<Pattern> includeList = new ArrayList<Pattern>();
    private List<Pattern> excludeList = new ArrayList<Pattern>();
    private FileMatcher includesMatcher;
    private FileMatcher excludesMatcher;
    protected File base;
    protected List<String> fileList;
    protected List<FunctionInfo> functionList = new ArrayList<FunctionInfo>();
 
    public abstract String instrument(File source) throws IOException;
 
    public Instrument(File base) {
        this.base = base;
    }
 
    public void generateTools(File dest) throws IOException {
        File dir = new File(dest, ".jstools");
        dir.mkdirs();
        //
        copyFromResource("template/analyze.css", new File(dir, "analyze.css"));
        copyFromResource("template/analyze.html", new File(dir, "analyze.html"));
        copyFromResource("template/analyze.js", new File(dir, "analyze.js"));
        copyFromResource("template/lib/jquery.js", new File(dir, "lib/jquery.js"));
        copyFromResource("template/lib/kickstart.js", new File(dir, "lib/kickstart.js"));
        copyFromResource("template/lib/w2ui.css", new File(dir, "lib/w2ui.css"));
        copyFromResource("template/lib/w2ui.js", new File(dir, "lib/w2ui.js"));
 
        //
        File mapFile = new File(dir, "function_map.js");
        PrintWriter writer = new PrintWriter(new FileWriter(mapFile));
        writer.println("var functionMap = {");
        writer.println("  'files':[");
        for (String fileName : fileList) {
            writer.print("    '");
            writer.print(fileName);
            writer.print("',");
            writer.println();
        }
        writer.println("  ],");
        writer.println("  'functions':[");
        writer.println("    { id:0, fileId:-1, name:'<root>', row:0, col:0 },");
        for (FunctionInfo info : functionList) {
            writer.print("    { id:");
            writer.print(info.getFunctionId());
            writer.print(", fileId:");
            writer.print(info.getFileId());
            writer.print(", name:'");
            writer.print(info.getName().replaceAll("'", "\\\\'"));
            writer.print("', row:");
            writer.print(info.getRow());
            writer.print(", col:");
            writer.print(info.getCol());
            writer.print(" },");
            writer.println("");
        }
        writer.println("  ]");
        writer.println("};\n");
        writer.close();
    }
 
    public void copyFromResource(String resourceName, File dest) throws IOException {
        InputStream is = getClass().getClassLoader().getResourceAsStream(resourceName);
        File parent = dest.getParentFile();
        if (!parent.exists()) {
            parent.mkdirs();
        }
        OutputStream os = new FileOutputStream(dest);
        int len = 0;
        byte[] buff = new byte[4096];
        while ((len = is.read(buff)) > 0) {
            os.write(buff, 0, len);
        }
        os.close();
        is.close();
    }
 
    private List<NamedNode> namedList = new ArrayList<NamedNode>();
    public String resolveName(FunctionNode node) {
        NamedNode namedNode = new NamedNode();
        namedNode.setNode(node);
        String name = node.getName();
        if ("".equals(name)) {
            AstNode parent = node.getParent();
            if (parent instanceof Assignment) {
                AstNode left = ((Assignment)parent).getLeft();
                if (left instanceof PropertyGet) {
                    name = left.toSource();
                    //"this.func = .."のときは、上位の関数の名前+この名前
                    String targetName = ((PropertyGet)left).getTarget().toSource();
                    if ("this".equals(targetName)) {
                        AstNode wrapFunction = parent.getParent();
                        while (wrapFunction != null) {
                            if (wrapFunction instanceof FunctionNode) {
                                name = resolveName((FunctionNode)wrapFunction) + "." + name.replaceAll("^this\\.", "");
                                //インスタンスメソッドはクラスと同じスコープ
                                for (NamedNode target : namedList) {
                                    if (target.getNode() == wrapFunction) {
                                        namedNode.setScope(target.getScope());
                                        break;
                                    }
                                }
                                break;
                            }
                            wrapFunction = wrapFunction.getParent();
                        }
                    }
                } else {
                    name = left.toSource();
                }
            } else if (parent instanceof VariableInitializer) {
                AstNode target = ((VariableInitializer)parent).getTarget();
                name = target.toSource();
            } else if (parent instanceof ObjectProperty) {
                AstNode left = ((ObjectProperty)parent).getLeft();
                if (left instanceof StringLiteral) {
                    name = ((StringLiteral)left).getValue();
                } else {
                    name = left.toSource();
                }
                parent = parent.getParent().getParent();
                while (true) {
                    if (parent instanceof ObjectProperty) {
                        AstNode parentLeft = ((ObjectProperty)parent).getLeft();
                        if (parentLeft instanceof StringLiteral) {
                            name = ((StringLiteral)parentLeft).getValue() + "." + name;
                        } else {
                            name = parentLeft.toSource() + "." + name;
                        }
                        parent = parent.getParent().getParent();
                    } else if (parent instanceof VariableInitializer) {
                        name = ((VariableInitializer)parent).getTarget().toSource() + "." + name;
                        break;
                    } else if (parent instanceof Assignment) {
                        name = ((Assignment)parent).getLeft().toSource() + "." + name;
                        break;
                    } else {
                        break;
                    }
                }
            }
        }
        namedNode.setName(name.replaceAll("\\.prototype", "").replaceAll("^$", "<anonymous>"));
        return namedNode.getName();
    }
 
    protected List<File> visit(File dir) {
        List<File> result = new ArrayList<File>();
        File files[] = dir.listFiles();
        for (File file : files) {
            if (file.isDirectory()) {
                result.addAll(visit(file));
            } else {
                result.add(file);
            }
        }
        return result;
    }
 
    protected boolean matchPattern(File path) {
        if (excludesMatcher != null) {
            if (excludesMatcher.matches(path)) {
                return false;
            }
        }
        if (includesMatcher != null) {
            if (includesMatcher.matches(path)) {
                return true;
            }
        }
        return false;
    }
 
    protected void setIncludes(String pattern) {
        includesMatcher = new FileMatcher(base, pattern);
    }
 
    protected void setExcludes(String pattern) {
        excludesMatcher = new FileMatcher(base, pattern);
    }
 
    protected void usage() {
        System.err.println("java " + this.getClass().getName() + " [-p pattern] source destination");
    }
 
    protected void log(String msg) {
        System.out.println(msg);
    }
 
    public class NamedNode {
        private AstNode scope;
        private String name;
        private AstNode node;
        public NamedNode() {
        }
        public NamedNode(AstNode scope, String name, AstNode node) {
            this.scope = scope;
            this.name = name;
            this.node = node;
        }
        public AstNode getScope() {
            return scope;
        }
        public String getName() {
            return name;
        }
        public AstNode getNode() {
            return node;
        }
        public void setScope(AstNode scope) {
            this.scope = scope;
        }
        public void setName(String name) {
            this.name = name;
        }
        public void setNode(AstNode node) {
            this.node = node;
        }
        public int getDepth() {
            int depth = 0;
            AstNode curr = node.getParent();
            while (curr != null && !(curr instanceof AstRoot)) {
                if (curr instanceof FunctionNode) {
                    depth++;
                }
                curr = curr.getParent();
            }
            return depth;
        }
        public String toString() {
            return name;
        }
    }
}

FunctionInfo.java

FunctionInfo.java
/*
 * Copyright 2015 Shorindo, Inc.
 * 
 * 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 com.shorindo.jstools;
 
/**
 * 
 */
public class FunctionInfo {
    private int fileId;
    private int functionId;
    private int row;
    private int col;
    private String name;
    public int getFileId() {
        return fileId;
    }
    public void setFileId(int fileId) {
        this.fileId = fileId;
    }
    public int getFunctionId() {
        return functionId;
    }
    public void setFunctionId(int functionId) {
        this.functionId = functionId;
    }
    public int getRow() {
        return row;
    }
    public void setRow(int row) {
        this.row = row;
    }
    public int getCol() {
        return col;
    }
    public void setCol(int col) {
        this.col = col;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
 
}

FileMatcher.java

FileMatcher.java
/*
 * Copyright 2017 Shorindo, Inc.
 * 
 * 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 com.shorindo.jstools;
 
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
 
/**
 * 
 */
public class FileMatcher {
    private List<Stack<String>> pathList = new ArrayList<Stack<String>>();
    private File base;
 
    /**
     * 
     */
    public FileMatcher(File base, String patterns) {
        this.base = base;
        if (base == null || !base.exists()) {
            throw new IllegalArgumentException("");
        }
        for (String pattern : patterns.trim().split("[,\\s]+")) {
            Stack<String> pathStack = new Stack<String>();
            String paths[] = pattern.split("/+");
            for (String path : paths) {
                pathStack.push(path);
            }
            pathList.add(pathStack);
        }
    }
 
    @SuppressWarnings("unchecked")
    public boolean matches(File file) {
        for (Stack<String> pathStack : pathList) {
            Stack<String> p = (Stack<String>)pathStack.clone();
            if (matches(file, p)) {
                return true;
            }
        }
        return false;
    }
 
    private boolean matches(File file, Stack<String> patternStack) {
        if (patternStack.size() == 0) {
            if (file.equals(base)) {
                return true;
            } else {
                return false;
            }
        }
        String pattern = patternStack.pop();
        if ("**".equals(pattern)) {
            File parent = file;
            while (parent != null) {
                if (matches(parent, patternStack)) {
                    return true;
                }
                parent = parent.getParentFile();
            }
        } else {
            pattern = "^"
                    + pattern.replaceAll("\\.", "\\\\.")
                        .replaceAll("\\?", "\\\\.")
                        .replaceAll("\\*", ".*?")
                    + "$";
            if (Pattern.matches(pattern, file.getName())) {
                return matches(file.getParentFile(), patternStack);
            }
        }
        patternStack.push(pattern);
        return false;
    }
 
}
 
research/20260419162703.txt · 最終更新: 2026/04/19 16:36 by Kazuyuki Matsuda
特に明示されていない限り、本サイトの内容は次のライセンスに従います:Copyright(C) 2011 Shorindo, Inc. All Rights Reserved
Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki