diff --git a/.gitignore b/.gitignore index 76ea57936..6b988ccac 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,9 @@ build/ *.dll *.dylib hs_err*.log + +# Intellij project files +*.iml +*.ipr +*.iws +.idea diff --git a/build_mac.sh b/build_mac.sh index e168a031d..ff6c87e56 100644 --- a/build_mac.sh +++ b/build_mac.sh @@ -7,6 +7,7 @@ sed s/\$\{arch\}/x86_64/g < pom1.xml > pom.xml mvn -Dos=macosx -Darch=x86_64 clean install cp pom_template.xml pom.xml rm pom1.xml +rm pom_template.xml STATUS=$? if [ $STATUS -eq 0 ]; then echo "MacOS Build Successful" diff --git a/jni/com_eclipsesource_v8_V8Impl.cpp b/jni/com_eclipsesource_v8_V8Impl.cpp index 6908ee75d..d3396fe5f 100644 --- a/jni/com_eclipsesource_v8_V8Impl.cpp +++ b/jni/com_eclipsesource_v8_V8Impl.cpp @@ -12,8 +12,11 @@ #include #include #include +#include #include #include +#include +#include #include "com_eclipsesource_v8_V8Impl.h" #pragma comment(lib, "Ws2_32.lib") #pragma comment(lib, "WINMM.lib") @@ -1469,3 +1472,215 @@ JNIEXPORT jlong JNICALL Java_com_eclipsesource_v8_V8__1getBuildID (JNIEnv *, jobject) { return 2; } + +/***** Serializes objects to JSON on the fly (used by code to serialize profiling information to file) *****/ + +static void findAndReplace(string& source, const string find, const string replace) +{ + string::size_type i = 0; + while((i = source.find(find, i)) != string::npos) { + source.replace(i, find.length(), replace); + i += replace.length(); + } +} + +class JsonSerializer { + public: + void startObject(string name = ""); + void endObject(bool isDoneAdding = false); + void startArray(string name = ""); + void endArray(bool isDoneAdding = false); + template void addValue(T value, bool quoteValue, bool isDoneAdding = false); + template void addProperty(string name, T value, bool quoteValue, bool isDoneAdding = false); + string getJson(); + private: + stringstream json; + template void addQuotedValue(T value); + template void addValueImpl(T value, bool quoteValue, bool isDoneAdding = false); + void addPropertyName(string name); + void doneAdding(bool isDoneAdding); +}; + +void JsonSerializer::startObject(string name) { + addPropertyName(name); + json << "{"; +} + +void JsonSerializer::endObject(bool isDoneAdding) { + json << "}"; + doneAdding(isDoneAdding); +} + +void JsonSerializer::startArray(string name) { + addPropertyName(name); + json << "["; +} + +void JsonSerializer::endArray(bool isDoneAdding) { + json << "]"; + doneAdding(isDoneAdding); +} + +template void JsonSerializer::addQuotedValue(T value) { + json << "\"" << value << "\""; +} + +template void JsonSerializer::addValueImpl(T value, bool quoteValue, bool isDoneAdding) { + if(quoteValue) { + addQuotedValue(value); + } else { + json << value; + } + doneAdding(isDoneAdding); +} + +template void JsonSerializer::addValue(T value, bool quoteValue, bool isDoneAdding) { + addValueImpl(value, quoteValue, isDoneAdding); +} + +template<> void JsonSerializer::addValue(string value, bool quoteValue, bool isDoneAdding) { + findAndReplace(value, "\\", "\\\\"); // backslash needs to be escaped for valid JSON + addValueImpl(value, quoteValue, isDoneAdding); +} + +template void JsonSerializer::addProperty(string name, T value, bool quoteValue, bool isDoneAdding) { + addPropertyName(name); + addValue(value, quoteValue, isDoneAdding); +} + +void JsonSerializer::addPropertyName(string name) { + if(name != "") { + addQuotedValue(name); + json << ":"; + } +} + +void JsonSerializer::doneAdding(bool isDoneAdding) { + if(!isDoneAdding) { + json << ","; + } +} + +string JsonSerializer::getJson() { + return json.str(); +} + +/***** Output stream that writes to file (used by code to serialize heap information to file) *****/ + +class HeapSnapshotFileOutputStream : public v8::OutputStream { + public: + HeapSnapshotFileOutputStream(string fileName) { file.open(fileName.c_str()); } + + virtual void EndOfStream() { file.close(); } + + virtual WriteResult WriteAsciiChunk(char* buffer, int size) { + file.write(buffer, size); + file.flush(); + return kContinue; + } + + private: + ofstream file; +}; + +/***** Writes the profile information to a file in a standard format (*.cpuprofile) *****/ + +static void walkCpuProfileNode(const CpuProfileNode* node, JsonSerializer* serializer) { + String::Utf8Value functionName(node->GetFunctionName()); + String::Utf8Value scriptResourceName(node->GetScriptResourceName()); + serializer->addProperty("functionName", string(*functionName), true); + serializer->addProperty("scriptId", node->GetScriptId(), true); + serializer->addProperty("url", string(*scriptResourceName), true); + serializer->addProperty("lineNumber", node->GetLineNumber(), false); + serializer->addProperty("columnNumber", node->GetColumnNumber(), false); + serializer->addProperty("hitCount", node->GetHitCount(), false); + serializer->addProperty("callUID", node->GetCallUid(), false); + serializer->addProperty("id", node->GetNodeId(), false); + serializer->addProperty("bailoutReason", node->GetBailoutReason(), true); + serializer->startArray("positionTicks"); + serializer->endArray(); + + // recursively walk through the child nodes + serializer->startArray("children"); + int numChildren = node->GetChildrenCount(); + for(int i = 0; i < numChildren; i++) { + serializer->startObject(); + walkCpuProfileNode(node->GetChild(i), serializer); + serializer->endObject(i == (numChildren - 1)); + } + serializer->endArray(true); +} + +static void serializeProfileToFile(CpuProfile* profile, char* filePath) { + ofstream cpuProfileFile; + cpuProfileFile.open(filePath); + + JsonSerializer* serializer = new JsonSerializer(); + serializer->startObject(); + + serializer->startObject("head"); + walkCpuProfileNode(profile->GetTopDownRoot(), serializer); + serializer->endObject(); + + // get times and convert from microseconds to seconds + serializer->addProperty("startTime", profile->GetStartTime() / 1000000.0, false); + serializer->addProperty("endTime", profile->GetEndTime() / 1000000.0, false); + + int sampleCount = profile->GetSamplesCount(); + + serializer->startArray("samples"); + for(int i = 0; i < sampleCount; i++){ + serializer->addValue(profile->GetSample(i)->GetNodeId(), false, i == (sampleCount - 1)); + } + serializer->endArray(); + + serializer->startArray("timestamps"); + for(int i = 0; i < sampleCount; i++){ + serializer->addValue(profile->GetSampleTimestamp(i), false, i == (sampleCount - 1)); + } + serializer->endArray(true); + + serializer-> endObject(true); + cpuProfileFile << serializer->getJson(); + cpuProfileFile.close(); +} + +/***** Writes the heap snapshot information to a file in a standard format (*.heapsnapshot) *****/ + +static void serializeHeapSnapshotToFile(const HeapSnapshot* heapSnapshot, char* filePath) { + HeapSnapshotFileOutputStream* heapSnapshotFile = new HeapSnapshotFileOutputStream(filePath); + heapSnapshot->Serialize(heapSnapshotFile, HeapSnapshot::kJSON); +} + +/***** Starts and stops the profiling session *****/ + +JNIEXPORT void JNICALL Java_com_eclipsesource_v8_V8__1startProfiling +(JNIEnv *env, jobject, jlong v8RuntimePtr, jstring profileTitle) { + Isolate* isolate = SETUP(env, v8RuntimePtr, ); + Local title = createV8String(env, isolate, profileTitle); + isolate->GetCpuProfiler()->StartProfiling(title, true); +} + +JNIEXPORT void JNICALL Java_com_eclipsesource_v8_V8__1stopProfiling +(JNIEnv *env, jobject, jlong v8RuntimePtr, jstring profileTitle, jstring filePath) { + Isolate* isolate = SETUP(env, v8RuntimePtr, ); + Local title = createV8String(env, isolate, profileTitle); + Local path = createV8String(env, isolate, filePath); + String::Utf8Value strPath(path); + CpuProfile* profile = isolate->GetCpuProfiler()->StopProfiling(title); + serializeProfileToFile(profile, *strPath); + profile->Delete(); +} + +/***** Takes a snapshot of the current objects on the heap *****/ + +JNIEXPORT void JNICALL Java_com_eclipsesource_v8_V8__1takeHeapSnapshot +(JNIEnv *env, jobject, jlong v8RuntimePtr, jstring profileTitle, jstring filePath) { + Isolate* isolate = SETUP(env, v8RuntimePtr, ); + Local title = createV8String(env, isolate, profileTitle); + Local path = createV8String(env, isolate, filePath); + String::Utf8Value strPath(path); + const HeapSnapshot* heapSnapshot = isolate->GetHeapProfiler()->TakeHeapSnapshot(title); + serializeHeapSnapshotToFile(heapSnapshot, *strPath); + const_cast(heapSnapshot)->Delete(); +} \ No newline at end of file diff --git a/jni/com_eclipsesource_v8_V8Impl.h b/jni/com_eclipsesource_v8_V8Impl.h index 70b1c8bb9..be696be6c 100644 --- a/jni/com_eclipsesource_v8_V8Impl.h +++ b/jni/com_eclipsesource_v8_V8Impl.h @@ -499,6 +499,30 @@ JNIEXPORT void JNICALL Java_com_eclipsesource_v8_V8__1disableDebugSupport JNIEXPORT void JNICALL Java_com_eclipsesource_v8_V8__1processDebugMessages (JNIEnv *, jobject, jlong); +/* + * Class: com_eclipsesource_v8_V8 + * Method: _startProfiling + * Signature: (JLjava/lang/String;)V + */ +JNIEXPORT void JNICALL Java_com_eclipsesource_v8_V8__1startProfiling + (JNIEnv *, jobject, jlong, jstring); + +/* + * Class: com_eclipsesource_v8_V8 + * Method: _stopProfiling + * Signature: (JLjava/lang/String;Ljava/lang/String;)V + */ +JNIEXPORT void JNICALL Java_com_eclipsesource_v8_V8__1stopProfiling + (JNIEnv *, jobject, jlong, jstring, jstring); + +/* + * Class: com_eclipsesource_v8_V8 + * Method: _takeHeapSnapshot + * Signature: (JLjava/lang/String;Ljava/lang/String;)V + */ +JNIEXPORT void JNICALL Java_com_eclipsesource_v8_V8__1takeHeapSnapshot + (JNIEnv *, jobject, jlong, jstring, jstring); + /* * Class: com_eclipsesource_v8_V8 * Method: _arrayGetDoubles diff --git a/src/main/java/com/eclipsesource/v8/V8.java b/src/main/java/com/eclipsesource/v8/V8.java index 39ab2f7b2..448636536 100644 --- a/src/main/java/com/eclipsesource/v8/V8.java +++ b/src/main/java/com/eclipsesource/v8/V8.java @@ -10,6 +10,7 @@ ******************************************************************************/ package com.eclipsesource.v8; +import java.io.File; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; @@ -673,6 +674,49 @@ public long getBuildID() { return _getBuildID(); } + /** + * Starts a new CPU profiling session with the given title + * + * @param profileTitle the name of the new profiling session + */ + public void startProfiling(String profileTitle) { + _startProfiling(getV8RuntimePtr(), profileTitle); + } + + /** + * Stops the specified CPU profiling session and writes the results + * to a file (*.cpuprofile) that can be loaded and analyzed with + * the Google Chrome developer tools. + * + * @param profileTitle the name of the profiling session to stop + * @param outputDirectory the output directory for the profiling result file + * (file name will be profileTitle.cpuprofile) + */ + public void stopProfiling(String profileTitle, String outputDirectory) { + _stopProfiling(getV8RuntimePtr(), profileTitle, getFilePath(profileTitle, outputDirectory, ".cpuprofile")); + } + + /** + * Takes a snapshot of objects on the heap and writes the results + * to a file (*.heapsnapshot) that can be loaded and analyzed with + * the Google Chrome developer tools. + * + * @param snapshotTitle the name of the new heap snapshot + * @param outputDirectory the output directory for the snapshot result file + * (file name will be snapshotTitle.heapsnapshot) + */ + public void takeHeapSnapshot(String snapshotTitle, String outputDirectory) { + _takeHeapSnapshot(getV8RuntimePtr(), snapshotTitle, getFilePath(snapshotTitle, outputDirectory, ".heapsnapshot")); + } + + String getFilePath(String title, String outputDirectory, String suffix) { + File outputDirPath = new File(outputDirectory.isEmpty() ? "." : outputDirectory); + if(!outputDirPath.exists()){ + throw new Error("File path does not exist: " + outputDirPath.toString()); + } + return new File(outputDirPath, title + suffix).toString(); + } + void checkThread() { locker.checkThread(); if (isReleased()) { @@ -1289,6 +1333,12 @@ protected void terminateExecution(final long v8RuntimePtr) { private native void _processDebugMessages(long v8RuntimePtr); + private native void _startProfiling(long v8RuntimePtr, String profileTitle); + + private native void _stopProfiling(long v8RuntimePtr, String profileTitle, String outputDirectory); + + private native void _takeHeapSnapshot(long v8RuntimePtr, String snapshotTitle, String outputDirectory); + private native double[] _arrayGetDoubles(final long v8RuntimePtr, final long objectHandle, final int index, final int length); private native int[] _arrayGetIntegers(final long v8RuntimePtr, final long objectHandle, final int index, final int length);