Skip to content
Closed
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
38 changes: 16 additions & 22 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -38,22 +38,31 @@ RUN apt-get update && apt-get -y -q install \
curl \
g++ \
git \
libcurl4-openssl-dev \
libssl-dev \
libtool \
make \
openjdk-11-jdk-headless \
python \
unzip \
zlib1g-dev

# openssl
# This openssl (compiled with -fPIC) is used to statically link into the agent
# shared library.
ENV PKG_CONFIG_PATH=/usr/local/ssl/lib/pkgconfig
RUN mkdir /tmp/openssl && cd /tmp/openssl && \
curl -sL https://github.com/openssl/openssl/archive/OpenSSL_1_1_1f.tar.gz | \
tar xzv --strip=1 && \
./config no-shared -fPIC --openssldir=/usr/local/ssl --prefix=/usr/local/ssl && \
make && make install_sw && \
cd ~ && rm -rf /tmp/openssl

# curl
RUN git clone --depth=1 -b curl-7_55_1 https://github.com/curl/curl.git /tmp/curl && \
RUN git clone --depth=1 -b curl-7_66_0 https://github.com/curl/curl.git /tmp/curl && \
cd /tmp/curl && \
./buildconf && \
./configure --disable-ldap --disable-shared --without-libssh2 \
--without-librtmp --without-libidn --enable-static \
--with-pic --with-ssl && \
--with-pic --with-ssl=/usr/local/ssl/ && \
make -j && make install && \
cd ~ && rm -rf /tmp/curl

Expand All @@ -72,34 +81,19 @@ RUN mkdir /tmp/glog && cd /tmp/glog && \
make -j && make install && \
cd ~ && rm -rf /tmp/glog

# openssl
# This openssl (compiled with -fPIC) is used to statically link into the agent
# shared library. We still install libssl-dev above since getting libcurl to
# link with the custom version is tricky, see
# http://askubuntu.com/questions/475670/how-to-build-curl-with-the-latest-openssl.
# Also, intentionally not using -j as OpenSSL < 1.1.0 not quite supporting the
# parallel builds, see
# https://github.com/openssl/openssl/issues/5762#issuecomment-376622684.
RUN mkdir /tmp/openssl && cd /tmp/openssl && \
curl -sL https://www.openssl.org/source/openssl-1.0.2o.tar.gz | \
tar xzv --strip=1 && \
./config no-shared -fPIC --openssldir=/usr/local/ssl && \
make && make install_sw && \
cd ~ && rm -rf /tmp/openssl

# gRPC & protobuf
# Use the protobuf version from gRPC for everything to avoid conflicting
# versions to be linked in. Disable OpenSSL embedding: when it's on, the build
# process of gRPC puts the OpenSSL static object files into the gRPC archive
# which causes link errors later when the agent is linked with the static
# OpenSSL library itself.
RUN git clone --depth=1 --recursive -b v1.25.0 https://github.com/grpc/grpc.git /tmp/grpc && \
RUN git clone --depth=1 --recursive -b v1.28.1 https://github.com/grpc/grpc.git /tmp/grpc && \
cd /tmp/grpc && \
cd third_party/protobuf && \
./autogen.sh && \
./configure --with-pic CXXFLAGS="$(pkg-config --cflags protobuf)" LIBS="$(pkg-config --libs protobuf)" LDFLAGS="-Wl,--no-as-needed" && \
make -j && make install && ldconfig && \
cd ../.. && \
CPPFLAGS="-I /usr/local/ssl/include" LDFLAGS="-Wl,--no-as-needed" make -j CONFIG=opt EMBED_OPENSSL=false V=1 HAS_SYSTEM_OPENSSL_NPN=0 && \
CPPFLAGS="-I /usr/local/ssl/include" LDFLAGS="-Wl,--no-as-needed" make CONFIG=opt EMBED_OPENSSL=false V=1 HAS_SYSTEM_OPENSSL_NPN=0 install && \
CPPFLAGS="-I /usr/local/ssl/include" LDFLAGS="-L /usr/local/ssl/lib/ -Wl,--no-as-needed" make -j CONFIG=opt EMBED_OPENSSL=false V=1 HAS_SYSTEM_OPENSSL_NPN=0 && \
CPPFLAGS="-I /usr/local/ssl/include" LDFLAGS="-L /usr/local/ssl/lib/ -Wl,--no-as-needed" make CONFIG=opt EMBED_OPENSSL=false V=1 HAS_SYSTEM_OPENSSL_NPN=0 install && \
rm -rf /tmp/grpc
15 changes: 15 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,21 @@ GRPC_LIBS= \
$(LIB_ROOT_PATH)/lib/libgrpc.a \
$(LIB_ROOT_PATH)/lib/libgpr.a \

# Detect if running on Alpine and modify various flags
ifeq ("$(wildcard /etc/alpine-release)","/etc/alpine-release")
# musl only supports global dynamic tls model, and is documented as
# async-signal-safe. See
# https://wiki.musl-libc.org/design-concepts.html#Thread-local-storage
# This selects global-dynamic TLS model in
# third_party/javaprofiler/accessors.h
CFLAGS += -DALPINE

LIBS1 += /usr/lib/libexecinfo.a

# libgcc is not installed by default on Alpine.
LDFLAGS += -static-libgcc
endif

all: \
$(TARGET_AGENT) \
$(TARGET_NOTICES) \
Expand Down
24 changes: 18 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Stackdriver Profiler Java Agent
# Google Cloud Profiler Profiler Java Agent

This repository contains source code for the [Stackdriver
Profiler](https://cloud.google.com/profiler/) Java agent.
This repository contains source code for the
[Google Cloud Profiler Profiler](https://cloud.google.com/profiler/) Java agent.

## Installation

Expand All @@ -12,9 +12,9 @@ wget -q -O- https://storage.googleapis.com/cloud-profiler/java/latest/profiler_j
| sudo tar xzv -C /opt/cprof
```

See the [Stackdriver Profiler Java profiling
doc](https://cloud.google.com/profiler/docs/profiling-java) for detailed and
most up-to-date guide on installing and using the agent.
See the
[Google Cloud Profiler Profiler Java profiling doc](https://cloud.google.com/profiler/docs/profiling-java)
for detailed and most up-to-date guide on installing and using the agent.

## Build from Source

Expand All @@ -27,3 +27,15 @@ commands below.
$ cd cloud-profiler-java
$ ./build.sh
```

## To build for Alpine.

**Per thread timers are not available on Alpine.** Since SIGEV_THREAD_ID is not
supported by `timer_create` on Alpine, per thread timers are not implemented and
the flag `-cprof_cpu_use_per_thread_timers` is ignored on this platform.

```shell
$ git clone https://github.com/GoogleCloudPlatform/cloud-profiler-java.git
$ cd cloud-profiler-java
$ ./build.sh -a
```
22 changes: 17 additions & 5 deletions build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,14 @@ set -o nounset
# Command line arguments: [-d]
# -d: specify the temporary directory for the build.

while getopts ":d:" opt; do
ALPINE_BUILD="0"
DOCKERFILE="Dockerfile"
FILE_SUFFIX=""
while getopts ":ad:" opt; do
case $opt in
a)
ALPINE_BUILD="1"
;;
d)
BUILD_TEMP_DIR=$OPTARG
;;
Expand Down Expand Up @@ -57,8 +63,14 @@ trap "{ echo 'FAILED: see ${LOG_FILE} for details' ; exit 1; }" ERR

mkdir -p "${BUILD_TEMP_DIR}"

PrintMessage "Building the builder Docker container..."
docker build -t cprof-agent-builder . >> "${LOG_FILE}" 2>&1
if [[ "${ALPINE_BUILD}" = "1" ]]; then
PrintMessage "Building the builder Alpine Docker container..."
DOCKERFILE="Dockerfile.alpine"
FILE_SUFFIX="_alpine"
else
PrintMessage "Building the builder Docker container..."
fi
docker build -f "${DOCKERFILE}" -t cprof-agent-builder . >> "${LOG_FILE}" 2>&1

PrintMessage "Packaging the agent code..."
mkdir -p "${BUILD_TEMP_DIR}"/build
Expand All @@ -72,12 +84,12 @@ docker run -ti -v "${BUILD_TEMP_DIR}/build":/root/build \
>> "${LOG_FILE}" 2>&1

PrintMessage "Packaging the agent binaries..."
tar zcf "${BUILD_TEMP_DIR}"/profiler_java_agent.tar.gz \
tar zcf "${BUILD_TEMP_DIR}"/profiler_java_agent${FILE_SUFFIX}.tar.gz \
-C "${BUILD_TEMP_DIR}"/build/.out \
NOTICES profiler_java_agent.so \
>> "${LOG_FILE}" 2>&1

PrintMessage "Agent built and stored locally in: ${BUILD_TEMP_DIR}/profiler_java_agent.tar.gz"
PrintMessage "Agent built and stored locally in: ${BUILD_TEMP_DIR}/profiler_java_agent${FILE_SUFFIX}.tar.gz"

trap - EXIT
PrintMessage "SUCCESS"
3 changes: 1 addition & 2 deletions src/NOTICES
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Google Stackdriver Profiler
Google Cloud Profiler
Copyright Google Inc.
Use of this software is governed by the Google Cloud Platform Terms of Service
found at https://cloud.google.com/terms/
Expand Down Expand Up @@ -470,4 +470,3 @@ OpenSSL License
* copied and put under another distribution licence
* [including the GNU Public Licence.]
*/

2 changes: 1 addition & 1 deletion src/cloud_env.cc
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ std::string GceMetadataRequest(HTTPRequest* req, const std::string& path) {
}

const char* Getenv(const std::string& var) {
#if __GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 17)
#if __GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 17) || ALPINE == 1
return secure_getenv(var.c_str());
#else
return __secure_getenv(var.c_str());
Expand Down
24 changes: 16 additions & 8 deletions src/entry.cc
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@

DEFINE_bool(cprof_cpu_use_per_thread_timers, false,
"when true, use per-thread CLOCK_THREAD_CPUTIME_ID timers; "
"only profiles Java threads, non-Java threads will be missed");
"only profiles Java threads, non-Java threads will be missed. "
"This flag is ignored on Alpine.");
DEFINE_bool(cprof_force_debug_non_safepoints, true,
"when true, force DebugNonSafepoints flag by subscribing to the"
"code generation events. This improves the accuracy of profiles,"
Expand Down Expand Up @@ -267,7 +268,7 @@ jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved) {

ParseArguments(options); // Initializes logger -- do not log before this call

LOG(INFO) << "Stackdriver Profiler Java agent version: "
LOG(INFO) << "Google Cloud Profiler Java agent version: "
<< CLOUD_PROFILER_AGENT_VERSION;
LOG(INFO) << "Profiler agent loaded";
google::javaprofiler::AttributeTable::Init();
Expand All @@ -292,7 +293,16 @@ jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved) {
// The process exit will free the memory. See comments to the variable on why.
// Initialize before registering the JVMTI callbacks to avoid the unlikely
// race of getting thread events before the thread table is born.
#ifdef ALPINE
// musl does not support SIGEV_THREAD_ID. Disable per thread timers.
if (FLAGS_cprof_cpu_use_per_thread_timers) {
LOG(WARNING) << "Per thread timers not available in Alpine. "
<< "Ignoring '-cprof_cpu_use_per_thread_timers' flag.";
}
threads = new ThreadTable(false);
#else
threads = new ThreadTable(FLAGS_cprof_cpu_use_per_thread_timers);
#endif

if (!RegisterJvmti(jvmti)) {
LOG(ERROR) << "Failed to enable JVMTI events. Continuing...";
Expand All @@ -304,21 +314,19 @@ jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved) {

google::javaprofiler::Asgct::SetAsgct(
google::javaprofiler::Accessors::GetJvmFunction<
google::javaprofiler::ASGCTType>("AsyncGetCallTrace"));
google::javaprofiler::ASGCTType>("AsyncGetCallTrace"));

worker = new Worker(jvmti, threads);
return 0;
}

void JNICALL Agent_OnUnload(JavaVM *vm) {
IMPLICITLY_USE(vm);
}
void JNICALL Agent_OnUnload(JavaVM *vm) { IMPLICITLY_USE(vm); }

} // namespace profiler
} // namespace cloud

AGENTEXPORT jint JNICALL
Agent_OnLoad(JavaVM *vm, char *options, void *reserved) {
AGENTEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options,
void *reserved) {
return cloud::profiler::Agent_OnLoad(vm, options, reserved);
}

Expand Down
5 changes: 5 additions & 0 deletions src/threads.cc
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ namespace {
const timer_t kInvalidTimer = reinterpret_cast<timer_t>(-1LL);

timer_t CreateTimer(pid_t tid) {
#ifdef ALPINE
// Per thread timers are not available on Alpine.
return kInvalidTimer;
#else
struct sigevent sevp = {};
sevp.sigev_notify = SIGEV_THREAD_ID;
sevp._sigev_un._tid = tid;
Expand All @@ -38,6 +42,7 @@ timer_t CreateTimer(pid_t tid) {
return kInvalidTimer;
}
return timer;
#endif
}

bool SetTimer(timer_t timer, int64_t period_usec) {
Expand Down
22 changes: 13 additions & 9 deletions third_party/javaprofiler/accessors.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,15 +80,19 @@ class Accessors {
}

private:
// This is dangerous. TLS accesses are by default not async safe, as
// they can call malloc for lazy initialization. The initial-exec
// TLS mode avoids this potential allocation, with the limitation
// that there is a fixed amount of space to hold all TLS variables
// referenced in the module. This should be OK for the cloud
// profiler agent, which is relatively small. We do provide a way
// to override the TLS model for compilation environments where the
// TLS access is async-safe.
#ifdef JAVAPROFILER_GLOBAL_DYNAMIC_TLS
// This is subtle and potentially dangerous, read this carefully.
//
// In glibc, TLS access is not signal-async-safe, as they can call malloc for
// lazy initialization. The initial-exec TLS mode avoids this potential
// allocation, with the limitation that there is a fixed amount of space to
// hold all TLS variables referenced in the module. This should be OK for the
// cloud profiler agent, which is relatively small.
//
// In environments where the TLS access is async-signal-safe, the
// global-dynamic TLS model can be used. For example, it is async-signal-safe
// in musl / Alpine, see
// https://wiki.musl-libc.org/design-concepts.html#Thread-local-storage.
#if defined(JAVAPROFILER_GLOBAL_DYNAMIC_TLS) || defined(ALPINE)
static __thread JNIEnv *env_ __attribute__((tls_model("global-dynamic")));
static __thread int64 attr_ __attribute__((tls_model("global-dynamic")));
static __thread Tags *tags_ __attribute__((tls_model("global-dynamic")));
Expand Down
16 changes: 9 additions & 7 deletions third_party/javaprofiler/async_ref_counted_string.cc
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,12 @@ namespace google {
namespace javaprofiler {
namespace {

using StringRefCountTable = std::unordered_map<string, std::atomic<int32_t>>;
using StringRefCountTable =
std::unordered_map<std::string, std::atomic<int32_t>>;
using StringRefCount = StringRefCountTable::value_type;

// Maps string to its reference count.
std::unordered_map<string, std::atomic<int32_t>> *string_table = nullptr;
std::unordered_map<std::string, std::atomic<int32_t>> *string_table = nullptr;
// All accesses to string_table should be protected by string_table_mutex.
std::mutex string_table_mutex;

Expand All @@ -47,7 +48,7 @@ std::mutex string_table_mutex;
// Resolves the StringRefCount of a given string, increments the reference count
// and returns the pointer of StringRefCount. It returns nullptr if the internal
// string table is not initialized.
StringRefCount *AcquireByString(const string &str) {
StringRefCount *AcquireByString(const std::string &str) {
std::lock_guard<std::mutex> lock(string_table_mutex);
if (string_table == nullptr) {
return nullptr;
Expand Down Expand Up @@ -114,7 +115,7 @@ void Release(StringRefCount *str_ref_cnt) {

} // namespace

AsyncRefCountedString::AsyncRefCountedString(const string &str)
AsyncRefCountedString::AsyncRefCountedString(const std::string &str)
: AsyncRefCountedString() {
Release(ptr_.exchange(AcquireByString(str)));
}
Expand All @@ -128,7 +129,8 @@ AsyncRefCountedString::~AsyncRefCountedString() {
Release(ptr_.exchange(nullptr));
}

AsyncRefCountedString &AsyncRefCountedString::operator=(const string &str) {
AsyncRefCountedString &AsyncRefCountedString::operator=(
const std::string &str) {
Release(ptr_.exchange(AcquireByString(str)));
return *this;
}
Expand Down Expand Up @@ -160,7 +162,7 @@ void AsyncRefCountedString::AsyncSafeReset() {
assert(AsyncSafeRelease(ptr_.exchange(nullptr)));
}

const string *AsyncRefCountedString::Get() const {
const std::string *AsyncRefCountedString::Get() const {
StringRefCount *str_ref_cnt = ptr_.load();
if (str_ref_cnt == nullptr) {
return nullptr;
Expand All @@ -172,7 +174,7 @@ const string *AsyncRefCountedString::Get() const {
bool AsyncRefCountedString::Init() {
std::lock_guard<std::mutex> lock(string_table_mutex);
if (string_table == nullptr) {
string_table = new std::unordered_map<string, std::atomic<int32_t>>();
string_table = new std::unordered_map<std::string, std::atomic<int32_t>>();
return true;
}
return false;
Expand Down
Loading