|
|
|
- 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("&", "&")
.replaceAll("<", "<")
.replaceAll(">", ">")
.replaceAll("\"", """);
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
/*
* 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
/*
* 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
/*
* 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;
}
}
|
|
|