Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,9 @@ build/
*.dll
*.dylib
hs_err*.log

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I use IntelliJ rather than Eclipse, and there are a different set of local user-specific project files that should be ignored by Git

# Intellij project files
*.iml
*.ipr
*.iws
.idea
1 change: 1 addition & 0 deletions build_mac.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I'm missing some part in the build process as to why this file isn't cleaned up in this script, but the result of not deleting it is that it leaves a file that Git then detects as a change, leaving my working state dirty even if I made no changes.

STATUS=$?
if [ $STATUS -eq 0 ]; then
echo "MacOS Build Successful"
Expand Down
215 changes: 215 additions & 0 deletions jni/com_eclipsesource_v8_V8Impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@
#include <iostream>
#include <v8-debug.h>
#include <v8.h>
#include <v8-profiler.h>
#include <map>
#include <cstdlib>
#include <fstream>
#include <sstream>
#include "com_eclipsesource_v8_V8Impl.h"
#pragma comment(lib, "Ws2_32.lib")
#pragma comment(lib, "WINMM.lib")
Expand Down Expand Up @@ -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 {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There may be a third party library I could use for this, but I was trying to keep things simple as it's not a complicated class. Also, if you did know of a library, it would need to support writing the serialized json on the fly (compared to just taking in an object and spitting out a string)

public:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want, I can split out this class into separate header/cpp files. I left it currently in place as this prevents changes you would potentially have to make to the build process (as the g++ command would now have new files)

void startObject(string name = "");
void endObject(bool isDoneAdding = false);
void startArray(string name = "");
void endArray(bool isDoneAdding = false);
template<class T> void addValue(T value, bool quoteValue, bool isDoneAdding = false);
template<class T> void addProperty(string name, T value, bool quoteValue, bool isDoneAdding = false);
string getJson();
private:
stringstream json;
template<class T> void addQuotedValue(T value);
template<class T> 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<class T> void JsonSerializer::addQuotedValue(T value) {
json << "\"" << value << "\"";
}

template<class T> void JsonSerializer::addValueImpl(T value, bool quoteValue, bool isDoneAdding) {
if(quoteValue) {
addQuotedValue(value);
} else {
json << value;
}
doneAdding(isDoneAdding);
}

template<class T> 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<class T> 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<String> 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<String> title = createV8String(env, isolate, profileTitle);
Local<String> 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<String> title = createV8String(env, isolate, profileTitle);
Local<String> path = createV8String(env, isolate, filePath);
String::Utf8Value strPath(path);
const HeapSnapshot* heapSnapshot = isolate->GetHeapProfiler()->TakeHeapSnapshot(title);
serializeHeapSnapshotToFile(heapSnapshot, *strPath);
const_cast<v8::HeapSnapshot*>(heapSnapshot)->Delete();
}
24 changes: 24 additions & 0 deletions jni/com_eclipsesource_v8_V8Impl.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

50 changes: 50 additions & 0 deletions src/main/java/com/eclipsesource/v8/V8.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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()) {
Expand Down Expand Up @@ -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);
Expand Down