Files
2023-02-14 02:15:48 +00:00

324 lines
9.6 KiB
C++

/*
* Copyright 2017 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 <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "demangle.h"
#include "frameName.h"
#include "profiler.h"
#include "vmStructs.h"
static inline bool isDigit(char c) {
return c >= '0' && c <= '9';
}
Matcher::Matcher(const char* pattern) {
if (pattern[0] == '*') {
_type = MATCH_ENDS_WITH;
_pattern = strdup(pattern + 1);
} else {
_type = MATCH_EQUALS;
_pattern = strdup(pattern);
}
_len = strlen(_pattern);
if (_len > 0 && _pattern[_len - 1] == '*') {
_type = _type == MATCH_EQUALS ? MATCH_STARTS_WITH : MATCH_CONTAINS;
_pattern[--_len] = 0;
}
}
Matcher::~Matcher() {
free(_pattern);
}
Matcher::Matcher(const Matcher& m) {
_type = m._type;
_pattern = strdup(m._pattern);
_len = m._len;
}
Matcher& Matcher::operator=(const Matcher& m) {
free(_pattern);
_type = m._type;
_pattern = strdup(m._pattern);
_len = m._len;
return *this;
}
bool Matcher::matches(const char* s) {
switch (_type) {
case MATCH_EQUALS:
return strcmp(s, _pattern) == 0;
case MATCH_CONTAINS:
return strstr(s, _pattern) != NULL;
case MATCH_STARTS_WITH:
return strncmp(s, _pattern, _len) == 0;
case MATCH_ENDS_WITH:
int slen = strlen(s);
return slen >= _len && strcmp(s + slen - _len, _pattern) == 0;
}
return false;
}
JMethodCache FrameName::_cache;
FrameName::FrameName(Arguments& args, int style, int epoch, Mutex& thread_names_lock, ThreadMap& thread_names) :
_class_names(),
_include(),
_exclude(),
_str(),
_style(style),
_cache_epoch((unsigned char)epoch),
_cache_max_age(args._mcache),
_thread_names_lock(thread_names_lock),
_thread_names(thread_names)
{
// Require printf to use standard C format regardless of system locale
_saved_locale = uselocale(newlocale(LC_NUMERIC_MASK, "C", (locale_t)0));
buildFilter(_include, args._buf, args._include);
buildFilter(_exclude, args._buf, args._exclude);
Profiler::instance()->classMap()->collect(_class_names);
}
FrameName::~FrameName() {
if (_cache_max_age == 0) {
_cache.clear();
} else {
// Remove stale methods from the cache, leave the fresh ones for the next profiling session
for (JMethodCache::iterator it = _cache.begin(); it != _cache.end(); ) {
if (_cache_epoch - (unsigned char)it->second[0] >= _cache_max_age) {
_cache.erase(it++);
} else {
++it;
}
}
}
freelocale(uselocale(_saved_locale));
}
void FrameName::buildFilter(std::vector<Matcher>& vector, const char* base, int offset) {
while (offset != 0) {
vector.push_back(base + offset);
offset = ((int*)(base + offset))[-1];
}
}
const char* FrameName::decodeNativeSymbol(const char* name) {
const char* lib_name = (_style & STYLE_LIB_NAMES) ? Profiler::instance()->getLibraryName(name) : NULL;
if (name[0] == '_' && name[1] == 'Z') {
char* demangled = Demangle::demangle(name);
if (demangled != NULL) {
if (lib_name != NULL) {
_str.assign(lib_name).append("`").append(demangled);
} else {
_str.assign(demangled);
}
free(demangled);
return _str.c_str();
}
}
if (lib_name != NULL) {
return _str.assign(lib_name).append("`").append(name).c_str();
} else {
return name;
}
}
const char* FrameName::typeSuffix(FrameTypeId type) {
if (_style & STYLE_ANNOTATE) {
switch (type) {
case FRAME_INTERPRETED: return "_[0]";
case FRAME_JIT_COMPILED: return "_[j]";
case FRAME_INLINED: return "_[i]";
case FRAME_C1_COMPILED: return "_[1]";
default: return NULL;
}
}
return NULL;
}
void FrameName::javaMethodName(jmethodID method) {
jclass method_class;
char* class_name = NULL;
char* method_name = NULL;
char* method_sig = NULL;
jvmtiEnv* jvmti = VM::jvmti();
jvmtiError err;
if ((err = jvmti->GetMethodName(method, &method_name, &method_sig, NULL)) == 0 &&
(err = jvmti->GetMethodDeclaringClass(method, &method_class)) == 0 &&
(err = jvmti->GetClassSignature(method_class, &class_name, NULL)) == 0) {
// Trim 'L' and ';' off the class descriptor like 'Ljava/lang/Object;'
javaClassName(class_name + 1, strlen(class_name) - 2, _style);
_str.append(".").append(method_name);
if (_style & STYLE_SIGNATURES) {
if (_style & STYLE_NO_SEMICOLON) {
for (char* s = method_sig; *s; s++) {
if (*s == ';') *s = '|';
}
}
_str.append(method_sig);
}
} else {
char buf[32];
snprintf(buf, sizeof(buf), "[jvmtiError %d]", err);
_str.assign(buf);
}
jvmti->Deallocate((unsigned char*)class_name);
jvmti->Deallocate((unsigned char*)method_sig);
jvmti->Deallocate((unsigned char*)method_name);
}
void FrameName::javaClassName(const char* symbol, size_t length, int style) {
int array_dimension = 0;
while (*symbol == '[') {
array_dimension++;
symbol++;
}
if (array_dimension == 0) {
_str.assign(symbol, length);
} else {
switch (*symbol) {
case 'B': _str.assign("byte"); break;
case 'C': _str.assign("char"); break;
case 'I': _str.assign("int"); break;
case 'J': _str.assign("long"); break;
case 'S': _str.assign("short"); break;
case 'Z': _str.assign("boolean"); break;
case 'F': _str.assign("float"); break;
case 'D': _str.assign("double"); break;
default: _str.assign(symbol + 1, length - array_dimension - 2);
}
do {
_str += "[]";
} while (--array_dimension > 0);
}
if (style & STYLE_SIMPLE) {
size_t start = 0;
size_t size = _str.size();
for (size_t i = 0; i < size; i++) {
if (_str[i] == '/' && !isDigit(_str[i + 1])) start = i + 1;
}
_str.erase(0, start);
}
if (style & STYLE_DOTTED) {
size_t size = _str.size();
for (size_t i = 0; i < size; i++) {
if (_str[i] == '/' && !isDigit(_str[i + 1])) _str[i] = '.';
}
}
}
const char* FrameName::name(ASGCT_CallFrame& frame, bool for_matching) {
if (frame.method_id == NULL) {
return "[unknown]";
}
switch (frame.bci) {
case BCI_NATIVE_FRAME:
return decodeNativeSymbol((const char*)frame.method_id);
case BCI_ALLOC:
case BCI_ALLOC_OUTSIDE_TLAB:
case BCI_LOCK:
case BCI_PARK: {
const char* symbol = _class_names[(uintptr_t)frame.method_id];
javaClassName(symbol, strlen(symbol), _style | STYLE_DOTTED);
if (!for_matching && !(_style & STYLE_DOTTED)) {
_str += frame.bci == BCI_ALLOC_OUTSIDE_TLAB ? "_[k]" : "_[i]";
}
return _str.c_str();
}
case BCI_THREAD_ID: {
int tid = (int)(uintptr_t)frame.method_id;
MutexLocker ml(_thread_names_lock);
ThreadMap::iterator it = _thread_names.find(tid);
if (for_matching) {
return it != _thread_names.end() ? it->second.c_str() : "";
}
char buf[32];
snprintf(buf, sizeof(buf), "tid=%d]", tid);
if (it != _thread_names.end()) {
return _str.assign("[").append(it->second).append(" ").append(buf).c_str();
} else {
return _str.assign("[").append(buf).c_str();
}
}
case BCI_ERROR:
return _str.assign("[").append((const char*)frame.method_id).append("]").c_str();
default: {
const char* type_suffix = typeSuffix(FrameType::decode(frame.bci));
JMethodCache::iterator it = _cache.lower_bound(frame.method_id);
if (it != _cache.end() && it->first == frame.method_id) {
it->second[0] = _cache_epoch;
if (type_suffix != NULL) {
return _str.assign(it->second, 1, std::string::npos).append(type_suffix).c_str();
}
return it->second.c_str() + 1;
}
javaMethodName(frame.method_id);
_cache.insert(it, JMethodCache::value_type(frame.method_id, std::string(1, _cache_epoch) + _str));
if (type_suffix != NULL) {
_str += type_suffix;
}
return _str.c_str();
}
}
}
bool FrameName::include(const char* frame_name) {
for (int i = 0; i < _include.size(); i++) {
if (_include[i].matches(frame_name)) {
return true;
}
}
return false;
}
bool FrameName::exclude(const char* frame_name) {
for (int i = 0; i < _exclude.size(); i++) {
if (_exclude[i].matches(frame_name)) {
return true;
}
}
return false;
}