blob: 1d42662119894a5dc22a1529826e5173f7caab50 [file] [log] [blame]
// Copyright 2020 The Bazel Authors. All rights reserved.
//
// 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.
// POSIX support for Starlark CPU profiler.
#include <arpa/inet.h> // for htonl
#include <errno.h>
#include <fcntl.h>
#include <jni.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/syscall.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
namespace cpu_profiler {
// static native boolean supported();
extern "C" JNIEXPORT jboolean JNICALL
Java_com_google_devtools_build_lib_syntax_CpuProfiler_supported(JNIEnv *env,
jclass clazz) {
return true;
}
static int fd; // the write end of the profile event pipe
pid_t gettid(void) {
#ifdef __linux__
return (pid_t)syscall(SYS_gettid);
#else // darwin
return (pid_t)syscall(SYS_thread_selfid);
#endif
}
// SIGPROF handler.
// Warning: asynchronous! See signal-safety(7) for the programming discipline.
void onsigprof(int sig) {
int old_errno = errno;
if (fd == 0) {
const char *msg = "startTimer called before createPipe\n";
write(2, msg, strlen(msg));
abort();
}
// Send an event containing the int32be-encoded OS thread ID.
pid_t tid = gettid();
uint32_t tid_be = htonl(tid);
int r = write(fd, (void *)&tid_be, sizeof tid_be);
if (r < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
// The Java router thread cannot keep up.
//
// A busy 12-core machine receives 12 * 100Hz = 1200 signals per second,
// and thus writes 4.8KB/s to the pipe. The default pipe buffer
// size on Linux is 64KiB, sufficient to buffer ~14s of data.
// (It is a quarter of that on Mac OS X.)
//
// Rather than block in write(2), causing the JVM to deadlock,
// we print an error and discard the event.
const char *msg =
"Starlark profile event router thread cannot keep up; discarding "
"events\n";
write(2, msg, strlen(msg));
} else {
// We shouldn't use perror in a signal handler.
// Strictly, we shouldn't use strerror either,
// but for all errors returned by write it merely
// returns a constant.
char buf[1024] = "write: ";
strncat(buf, strerror(errno), sizeof buf - strlen(buf) - 1);
strncat(buf, "\n", sizeof buf - strlen(buf) - 1);
write(2, buf, strlen(buf));
abort();
}
}
errno = old_errno;
}
// static native jint gettid();
extern "C" JNIEXPORT jint JNICALL
Java_com_google_devtools_build_lib_syntax_CpuProfiler_gettid(JNIEnv *env,
jclass clazz) {
return gettid();
}
// makeFD: return new FileDescriptor(fd)
//
// This would be easy to do in Java, but for the field being private.
// Java really does everything it can to make system programming hateful.
static jobject makeFD(JNIEnv *env, int fd) {
jclass fdclass = env->FindClass("java/io/FileDescriptor");
if (fdclass == NULL) return NULL; // exception
jmethodID init = env->GetMethodID(fdclass, "<init>", "()V");
if (init == NULL) return NULL; // exception
jobject fdobj = env->NewObject(fdclass, init);
jfieldID fd_field = env->GetFieldID(fdclass, "fd", "I");
if (fd_field == NULL) return NULL; // exception
env->SetIntField(fdobj, fd_field, fd);
return fdobj;
}
// static native FileDescriptor createPipe();
extern "C" JNIEXPORT jobject JNICALL
Java_com_google_devtools_build_lib_syntax_CpuProfiler_createPipe(JNIEnv *env,
jclass clazz) {
// Create a pipe for profile events from the handler to Java.
// The default pipe size is 64KiB on Linux and 16KiB on Mac OS X.
int pipefds[2];
if (pipe(pipefds) < 0) {
perror("pipe");
abort();
}
fd = pipefds[1];
// Make the write end non-blocking so that the signal
// handler can detect overflow (rather than deadlock).
fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
// Return the read end of the event pipe,
// wrapped by a java FileDescriptor.
return makeFD(env, pipefds[0]);
}
// static native boolean startTimer(long period_micros);
extern "C" JNIEXPORT jboolean JNICALL
Java_com_google_devtools_build_lib_syntax_CpuProfiler_startTimer(
JNIEnv *env, jclass clazz, jlong period_micros) {
// Install the signal handler.
// Use sigaction(2) not signal(2) so that we can correctly
// restore the previous handler if necessary.
struct sigaction oldact = {}, act = {};
act.sa_handler = onsigprof;
act.sa_flags = SA_RESTART; // the JVM doesn't expect EINTR
if (sigaction(SIGPROF, &act, &oldact) < 0) {
perror("sigaction");
abort();
}
// Is a handler already in effect?
// Check for 3-arg and 1-arg forms.
typedef void (*sighandler_t)(int); // don't rely on this GNU extension
if ((oldact.sa_flags & SA_SIGINFO) != 0
? (reinterpret_cast<sighandler_t>(oldact.sa_sigaction) != SIG_IGN)
: (oldact.sa_handler != SIG_IGN)) {
// Someone else is profiling this JVM.
// Restore their handler and fail.
(void)sigaction(SIGPROF, &oldact, nullptr);
return false;
}
// Start the CPU interval timer.
struct timeval period = {
.tv_sec = 0,
.tv_usec = static_cast<suseconds_t>(period_micros),
};
struct itimerval timer = {.it_interval = period, .it_value = period};
if (setitimer(ITIMER_PROF, &timer, nullptr) < 0) {
perror("setitimer");
abort();
}
return true;
}
// static native void stopTimer();
extern "C" JNIEXPORT void JNICALL
Java_com_google_devtools_build_lib_syntax_CpuProfiler_stopTimer(JNIEnv *env,
jclass clazz) {
// Disarm the CPU interval timer.
struct itimerval timer = {};
if (setitimer(ITIMER_PROF, &timer, nullptr) < 0) {
perror("setitimer");
abort();
}
// Uninstall signal handler.
signal(SIGPROF, SIG_IGN);
}
} // namespace cpu_profiler