-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 50eed3d
Showing
18 changed files
with
1,082 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
### Intellij ### | ||
.idea | ||
*.iws | ||
/out/ | ||
*.iml | ||
modules.xml | ||
|
||
### Java ### | ||
*.class | ||
*.jar | ||
|
||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml | ||
hs_err_pid* | ||
|
||
|
||
### Gradle ### | ||
.gradle | ||
build/ | ||
|
||
# Ignore Gradle GUI config | ||
gradle-app.setting | ||
|
||
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) | ||
!gradle-wrapper.jar | ||
|
||
# Cache of project | ||
.gradletasknamecache | ||
|
||
# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 | ||
# gradle/wrapper/gradle-wrapper.properties | ||
|
||
|
||
### Project ### | ||
lib/ | ||
franky-proto/generated | ||
|
||
### CMake ### | ||
CMakeCache.txt | ||
CMakeFiles | ||
CMakeScripts | ||
Makefile | ||
cmake_install.cmake | ||
install_manifest.txt | ||
CTestTestfile.cmake |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
cmake_minimum_required(VERSION 3.5) | ||
project(franky) | ||
|
||
set(JAVA_HOME /usr/lib/jvm/java-8-oracle) | ||
set(LIB_NAME libasyncProfiler.so) | ||
|
||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") | ||
|
||
include_directories( | ||
${JAVA_HOME}/include | ||
${JAVA_HOME}/include/linux | ||
${CMAKE_CURRENT_BINARY_DIR}) | ||
|
||
set(SOURCE_FILES | ||
src/profiler.cpp | ||
src/profiler.h | ||
src/vmEntry.cpp | ||
src/vmEntry.h) | ||
|
||
ADD_SUBDIRECTORY(proto) | ||
|
||
|
||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../lib) | ||
|
||
add_library(frankyagent SHARED ${SOURCE_FILES}) | ||
TARGET_LINK_LIBRARIES(frankyagent proto ${PROTOBUF_LIBRARY}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
../franky-proto/src/main/proto/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,307 @@ | ||
/* | ||
* Copyright 2016 Andrei Pangin | ||
* | ||
* 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. | ||
*/ | ||
|
||
#include <stdio.h> | ||
#include <stdlib.h> | ||
#include <cstdint> | ||
#include <string.h> | ||
#include <signal.h> | ||
#include <string> | ||
#include <sys/time.h> | ||
|
||
#include <unistd.h> | ||
#include <sys/types.h> | ||
#include <sys/socket.h> | ||
#include <netinet/in.h> | ||
#include <netdb.h> | ||
|
||
#include "profiler.h" | ||
#include "vmEntry.h" | ||
|
||
|
||
Profiler Profiler::_instance; | ||
|
||
|
||
static void sigprofHandler(int signo, siginfo_t *siginfo, void *ucontext) { | ||
Profiler::_instance.recordSample(ucontext); | ||
} | ||
|
||
|
||
MethodName::MethodName(jmethodID method) { | ||
jclass method_class; | ||
jvmtiEnv *jvmti = VM::jvmti(); | ||
jvmti->GetMethodName(method, &_name, &_sig, NULL); | ||
jvmti->GetMethodDeclaringClass(method, &method_class); | ||
jvmti->GetClassSignature(method_class, &_class_sig, NULL); | ||
|
||
char *s; | ||
for (s = _class_sig; *s; s++) { | ||
if (*s == '/') *s = '.'; | ||
} | ||
s[-1] = 0; | ||
} | ||
|
||
MethodName::~MethodName() { | ||
jvmtiEnv *jvmti = VM::jvmti(); | ||
jvmti->Deallocate((unsigned char *) _name); | ||
jvmti->Deallocate((unsigned char *) _sig); | ||
jvmti->Deallocate((unsigned char *) _class_sig); | ||
} | ||
|
||
|
||
void CallTraceSample::assign(ASGCT_CallTrace *trace) { | ||
_call_count = 1; | ||
_num_frames = trace->num_frames; | ||
for (int i = 0; i < trace->num_frames; i++) { | ||
_frames[i] = trace->frames[i]; | ||
} | ||
} | ||
|
||
|
||
uint64_t Profiler::hashCallTrace(ASGCT_CallTrace *trace) { | ||
const uint64_t M = 0xc6a4a7935bd1e995LL; | ||
const int R = 47; | ||
|
||
uint64_t h = trace->num_frames * M; | ||
|
||
for (int i = 0; i < trace->num_frames; i++) { | ||
uint64_t k = ((uint64_t) trace->frames[i].bci << 32) ^(uint64_t) trace->frames[i].method_id; | ||
k *= M; | ||
k ^= k >> R; | ||
k *= M; | ||
h ^= k; | ||
h *= M; | ||
} | ||
|
||
h ^= h >> R; | ||
h *= M; | ||
h ^= h >> R; | ||
|
||
return h; | ||
} | ||
|
||
void Profiler::storeCallTrace(ASGCT_CallTrace *trace) { | ||
uint64_t hash = hashCallTrace(trace); | ||
int bucket = (int) (hash % MAX_CALLTRACES); | ||
int i = bucket; | ||
|
||
do { | ||
if (_hashes[i] == hash && _traces[i]._call_count > 0) { | ||
_traces[i]._call_count++; | ||
return; | ||
} else if (_hashes[i] == 0) { | ||
break; | ||
} | ||
if (++i == MAX_CALLTRACES) i = 0; | ||
} while (i != bucket); | ||
|
||
_hashes[i] = hash; | ||
_traces[i].assign(trace); | ||
} | ||
|
||
uint64_t Profiler::hashMethod(jmethodID method) { | ||
const uint64_t M = 0xc6a4a7935bd1e995LL; | ||
const int R = 17; | ||
|
||
uint64_t h = (uint64_t) method; | ||
|
||
h ^= h >> R; | ||
h *= M; | ||
h ^= h >> R; | ||
|
||
return h; | ||
} | ||
|
||
void Profiler::storeMethod(jmethodID method) { | ||
uint64_t hash = hashMethod(method); | ||
int bucket = (int) (hash % MAX_CALLTRACES); | ||
int i = bucket; | ||
|
||
do { | ||
if (_methods[i]._method == method) { | ||
_methods[i]._call_count++; | ||
return; | ||
} else if (_methods[i]._method == NULL) { | ||
break; | ||
} | ||
if (++i == MAX_CALLTRACES) i = 0; | ||
} while (i != bucket); | ||
|
||
_methods[i]._call_count = 1; | ||
_methods[i]._method = method; | ||
} | ||
|
||
void Profiler::recordSample(void *ucontext) { | ||
_calls_total++; | ||
|
||
JNIEnv *jni = VM::jni(); | ||
if (jni == NULL) { | ||
_calls_non_java++; | ||
return; | ||
} | ||
|
||
ASGCT_CallFrame frames[MAX_FRAMES]; | ||
ASGCT_CallTrace trace = {jni, MAX_FRAMES, frames}; | ||
AsyncGetCallTrace(&trace, trace.num_frames, ucontext); | ||
|
||
if (trace.num_frames > 0) { | ||
storeCallTrace(&trace); | ||
storeMethod(frames[0].method_id); | ||
} else if (trace.num_frames == -2) { | ||
_calls_gc++; | ||
} else if (trace.num_frames == -9) { | ||
_calls_deopt++; | ||
} else { | ||
_calls_unknown++; | ||
} | ||
} | ||
|
||
void Profiler::setTimer(long sec, long usec) { | ||
bool enabled = sec | usec; | ||
|
||
struct sigaction sa; | ||
sa.sa_handler = enabled ? NULL : SIG_IGN; | ||
sa.sa_sigaction = enabled ? sigprofHandler : NULL; | ||
sa.sa_flags = SA_RESTART | SA_SIGINFO; | ||
sigemptyset(&sa.sa_mask); | ||
sigaction(SIGPROF, &sa, NULL); | ||
|
||
struct itimerval itv = {{sec, usec}, | ||
{sec, usec}}; | ||
setitimer(ITIMER_PROF, &itv, NULL); | ||
} | ||
|
||
void Profiler::start(long interval) { | ||
if (_running) return; | ||
_running = true; | ||
|
||
_calls_total = _calls_non_java = _calls_gc = _calls_deopt = _calls_unknown = 0; | ||
memset(_hashes, 0, sizeof(_hashes)); | ||
memset(_traces, 0, sizeof(_traces)); | ||
memset(_methods, 0, sizeof(_methods)); | ||
|
||
setTimer(interval / 1000, interval * 1000); | ||
} | ||
|
||
void Profiler::stop() { | ||
if (!_running) return; | ||
_running = false; | ||
|
||
setTimer(0, 0); | ||
} | ||
|
||
void Profiler::summary(std::ostream &out) { | ||
float percent = 100.0f / _calls_total; | ||
char buf[256]; | ||
sprintf(buf, | ||
"--- Execution profile ---\n" | ||
"Total: %d\n" | ||
"Non-Java: %d (%.2f%%)\n" | ||
"GC: %d (%.2f%%)\n" | ||
"Deopt: %d (%.2f%%)\n" | ||
"Unknown: %d (%.2f%%)\n" | ||
"\n", | ||
_calls_total, | ||
_calls_non_java, _calls_non_java * percent, | ||
_calls_gc, _calls_gc * percent, | ||
_calls_deopt, _calls_deopt * percent, | ||
_calls_unknown, _calls_unknown * percent | ||
); | ||
out << buf; | ||
} | ||
|
||
void Profiler::dumpTraces(std::ostream &out, int max_traces) { | ||
if (_running) return; | ||
|
||
float percent = 100.0f / _calls_total; | ||
char buf[1024]; | ||
|
||
qsort(_traces, MAX_CALLTRACES, sizeof(CallTraceSample), CallTraceSample::comparator); | ||
if (max_traces > MAX_CALLTRACES) max_traces = MAX_CALLTRACES; | ||
|
||
for (int i = 0; i < max_traces; i++) { | ||
int samples = _traces[i]._call_count; | ||
if (samples == 0) break; | ||
|
||
sprintf(buf, "Samples: %d (%.2f%%)\n", samples, samples * percent); | ||
out << buf; | ||
|
||
for (int j = 0; j < _traces[i]._num_frames; j++) { | ||
ASGCT_CallFrame *frame = &_traces[i]._frames[j]; | ||
if (frame->method_id != NULL) { | ||
MethodName mn(frame->method_id); | ||
sprintf(buf, " [%2d] %s.%s @%d\n", j, mn.holder(), mn.name(), frame->bci); | ||
out << buf; | ||
} | ||
} | ||
out << "\n"; | ||
} | ||
} | ||
|
||
void Profiler::dumpMethods(std::ostream &out) { | ||
if (_running) return; | ||
|
||
float percent = 100.0f / _calls_total; | ||
char buf[1024]; | ||
|
||
qsort(_methods, MAX_CALLTRACES, sizeof(MethodSample), MethodSample::comparator); | ||
|
||
for (int i = 0; i < MAX_CALLTRACES; i++) { | ||
int samples = _methods[i]._call_count; | ||
if (samples == 0) break; | ||
|
||
MethodName mn(_methods[i]._method); | ||
sprintf(buf, "%6d (%.2f%%) %s.%s\n", samples, samples * percent, mn.holder(), mn.name()); | ||
out << buf; | ||
} | ||
} | ||
|
||
void error(const char *msg) { | ||
std::string res = std::string("ERROR") + std::string(msg); | ||
perror(res.c_str()); | ||
exit(0); | ||
} | ||
|
||
|
||
void Profiler::init(int port) { | ||
portno = (uint16_t) port; | ||
sockfd = socket(AF_INET, SOCK_STREAM, 0); | ||
if (sockfd < 0) { | ||
error("opening socket"); | ||
} | ||
server = gethostbyname("localhost"); | ||
if (server == NULL) { | ||
error("unable to connect to localhost"); | ||
} | ||
struct sockaddr_in serv_addr; | ||
bzero((char *) &serv_addr, sizeof(serv_addr)); | ||
serv_addr.sin_family = AF_INET; | ||
bcopy(server->h_addr, (char *) &serv_addr.sin_addr.s_addr, server->h_length); | ||
serv_addr.sin_port = htons(portno); | ||
if (connect(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) { | ||
error("connecting"); | ||
} | ||
|
||
int n; | ||
char buffer[256] = "Hello"; | ||
|
||
n = write(sockfd, buffer, strlen(buffer)); | ||
if (n < 0) | ||
error("ERROR writing to socket"); | ||
close(sockfd); | ||
} | ||
|
||
|
Oops, something went wrong.