Add support for cpu speed events on Mac. PiperOrigin-RevId: 421140063
diff --git a/src/main/java/com/google/devtools/build/lib/platform/BUILD b/src/main/java/com/google/devtools/build/lib/platform/BUILD index 9f5620b..c8aa482 100644 --- a/src/main/java/com/google/devtools/build/lib/platform/BUILD +++ b/src/main/java/com/google/devtools/build/lib/platform/BUILD
@@ -106,3 +106,19 @@ "//third_party:jsr305", ], ) + +java_library( + name = "system_cpu_speed_module", + srcs = [ + "SystemCPUSpeedEvent.java", + "SystemCPUSpeedModule.java", + ], + deps = [ + "//src/main/java/com/google/devtools/build/lib:runtime", + "//src/main/java/com/google/devtools/build/lib/events", + "//src/main/java/com/google/devtools/build/lib/jni", + "//third_party:flogger", + "//third_party:guava", + "//third_party:jsr305", + ], +)
diff --git a/src/main/java/com/google/devtools/build/lib/platform/SystemCPUSpeedEvent.java b/src/main/java/com/google/devtools/build/lib/platform/SystemCPUSpeedEvent.java new file mode 100644 index 0000000..f0268db --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/platform/SystemCPUSpeedEvent.java
@@ -0,0 +1,46 @@ +// Copyright 2022 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. + +package com.google.devtools.build.lib.platform; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.devtools.build.lib.events.ExtendedEventHandler; + +/** + * This event is fired from {@link + * com.google.devtools.build.lib.platform.SystemCPUSpeedModule#cpuSpeedCallback} to indicate that a + * cpu speed event has occurred. + */ +public class SystemCPUSpeedEvent implements ExtendedEventHandler.Postable { + private final int speed; + + /** + * SystemCPUSpeedEvent Constructor. + * + * @param speed The actual speed (1-100). + */ + public SystemCPUSpeedEvent(int speed) { + checkArgument(speed > 0 && speed <= 100); + this.speed = speed; + } + + public int speed() { + return speed; + } + + public String logString() { + return String.format("SystemCPUSpeedEvent: %d", speed); + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/platform/SystemCPUSpeedModule.java b/src/main/java/com/google/devtools/build/lib/platform/SystemCPUSpeedModule.java new file mode 100644 index 0000000..524ff13 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/platform/SystemCPUSpeedModule.java
@@ -0,0 +1,73 @@ +// Copyright 2022 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. + +package com.google.devtools.build.lib.platform; + +import com.google.common.flogger.GoogleLogger; +import com.google.devtools.build.lib.events.Reporter; +import com.google.devtools.build.lib.jni.JniLoader; +import com.google.devtools.build.lib.runtime.BlazeModule; +import com.google.devtools.build.lib.runtime.CommandEnvironment; +import javax.annotation.Nullable; +import javax.annotation.concurrent.GuardedBy; + +/** Detects cpu speed events. */ +public final class SystemCPUSpeedModule extends BlazeModule { + private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); + + static { + JniLoader.loadJni(); + } + + @GuardedBy("this") + @Nullable + private Reporter reporter; + + private native void registerJNI(); + + private static native int cpuSpeed(); + + public SystemCPUSpeedModule() { + if (JniLoader.isJniAvailable()) { + registerJNI(); + } + } + + @Override + public synchronized void beforeCommand(CommandEnvironment env) { + this.reporter = env.getReporter(); + int startingSpeed = cpuSpeed(); + if (startingSpeed < 100) { + cpuSpeedCallback(startingSpeed); + } + } + + @Override + public synchronized void afterCommand() { + this.reporter = null; + } + + private synchronized void cpuSpeedCallback(int speed) { + if (speed == -1) { + // Speeds of -1 imply an error occurred in our speed gathering code. + // It is expected that lower level code has logged the error, so we are just going to ignore. + return; + } + SystemCPUSpeedEvent event = new SystemCPUSpeedEvent(speed); + if (reporter != null) { + reporter.post(event); + } + logger.atInfo().log(event.logString()); + } +}
diff --git a/src/main/native/BUILD b/src/main/native/BUILD index 1e02c9c..7b5533a 100644 --- a/src/main/native/BUILD +++ b/src/main/native/BUILD
@@ -27,6 +27,7 @@ "darwin/fsevents.cc", "darwin/file_jni.cc", "darwin/sleep_prevention_jni.cc", + "darwin/system_cpu_speed_monitor_jni.cc", "darwin/system_disk_space_monitor_jni.cc", "darwin/system_load_advisory_monitor_jni.cc", "darwin/system_memory_pressure_jni.cc",
diff --git a/src/main/native/darwin/system_cpu_speed_monitor_jni.cc b/src/main/native/darwin/system_cpu_speed_monitor_jni.cc new file mode 100644 index 0000000..2e9dd19 --- /dev/null +++ b/src/main/native/darwin/system_cpu_speed_monitor_jni.cc
@@ -0,0 +1,84 @@ +// Copyright 2022 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. + +#include <CoreFoundation/CoreFoundation.h> +#include <IOKit/pwr_mgt/IOPM.h> +#include <IOKit/pwr_mgt/IOPMLib.h> +#include <mach/mach_error.h> +#include <notify.h> + +#include "src/main/cpp/util/logging.h" +#include "src/main/native/darwin/util.h" +#include "src/main/native/unix_jni.h" + +namespace blaze_jni { + +void portable_start_cpu_speed_monitoring() { + // We install a test notification as well that can be used for testing. + static dispatch_once_t once_token; + dispatch_once(&once_token, ^{ + dispatch_queue_t queue = bazel::darwin::JniDispatchQueue(); + int token; + int status = notify_register_dispatch(kIOPMCPUPowerNotificationKey, &token, + queue, ^(int state) { + int value = portable_cpu_speed(); + cpu_speed_callback(value); + }); + BAZEL_CHECK_EQ(status, NOTIFY_STATUS_OK); + + // This is registered solely so we can test the system from end-to-end. + // Using the Apple notification requires admin access. + status = notify_register_dispatch( + "com.google.bazel.test.cpuspeed", &token, queue, ^(int t) { + uint64_t state; + uint32_t status = notify_get_state(t, &state); + if (status != NOTIFY_STATUS_OK) { + BAZEL_LOG(FATAL) << "notify_get_state failed: " << status; + } + cpu_speed_callback(state); + }); + BAZEL_CHECK_EQ(status, NOTIFY_STATUS_OK); + }); +} + +int portable_cpu_speed() { + CFDictionaryRef cfCpuPowerStatus = NULL; + IOReturn ret = IOPMCopyCPUPowerStatus(&cfCpuPowerStatus); + if (ret == kIOReturnNotFound) { + // This is a common occurrence when starting up so don't bother logging. + return -1; + } else if (ret != kIOReturnSuccess) { + BAZEL_LOG(ERROR) << "IOPMCopyCPUPowerStatus failed: " + << mach_error_string(ret) << "(" << ret << ")"; + return -1; + } + CFNumberRef cfSpeed = static_cast<CFNumberRef>(CFDictionaryGetValue( + cfCpuPowerStatus, CFSTR(kIOPMCPUPowerLimitProcessorSpeedKey))); + + if (!cfSpeed) { + BAZEL_LOG(ERROR) + << "IOPMCopyCPUPowerStatus missing kIOPMCPUPowerLimitProcessorSpeedKey"; + CFRelease(cfCpuPowerStatus); + return -1; + } + int speed; + CFNumberGetValue(cfSpeed, kCFNumberIntType, &speed); + CFRelease(cfCpuPowerStatus); + + BAZEL_LOG(USER) << "cpu speed anomaly: " << speed; + + return speed; +} + +} // namespace blaze_jni
diff --git a/src/main/native/unix_jni.cc b/src/main/native/unix_jni.cc index 09cc59a..6264b99 100644 --- a/src/main/native/unix_jni.cc +++ b/src/main/native/unix_jni.cc
@@ -1498,4 +1498,56 @@ } } +jobject g_cpu_speed_module; + +/* + * Class: Java_com_google_devtools_build_lib_platform_SystemCPUSpeedModule + * Method: registerJNI + * Signature: ()I + */ +extern "C" JNIEXPORT void JNICALL +Java_com_google_devtools_build_lib_platform_SystemCPUSpeedModule_registerJNI( + JNIEnv *env, jobject local_object) { + + if (g_cpu_speed_module != nullptr) { + PostAssertionError( + env, "Singleton SystemCPUSpeedModule already registered"); + return; + } + + JavaVM *java_vm = GetJavaVM(env); + if (java_vm == nullptr) { + PostAssertionError( + env, "Unable to get javaVM registering SystemCPUSpeedModule"); + return; + } + + g_cpu_speed_module = env->NewGlobalRef(local_object); + if (g_cpu_speed_module == nullptr) { + PostAssertionError( + env, "Unable to create global ref for SystemCPUSpeedModule"); + return; + } + portable_start_cpu_speed_monitoring(); +} + +void cpu_speed_callback(int speed) { + if (g_cpu_speed_module != nullptr) { + PerformIntegerValueCallback(g_cpu_speed_module, "cpuSpeedCallback", speed); + } +} + +/* + * Class: Java_com_google_devtools_build_lib_platform_SystemCPUSpeedModule + * Method: cpuSpeed + * Signature: ()I + * + * Returns 1-100 to represent CPU speed. Returns -1 in case of error. + */ +extern "C" JNIEXPORT jint JNICALL +Java_com_google_devtools_build_lib_platform_SystemCPUSpeedModule_cpuSpeed( + JNIEnv *env, jclass) { + return portable_cpu_speed(); +} + } // namespace blaze_jni
diff --git a/src/main/native/unix_jni.h b/src/main/native/unix_jni.h index 8060e2d..3e231fc 100644 --- a/src/main/native/unix_jni.h +++ b/src/main/native/unix_jni.h
@@ -169,6 +169,17 @@ // monitoring when a disk space alert happens. extern void disk_space_callback(DiskSpaceLevel level); +// Starts up any infrastructure needed to do cpu speed monitoring. +// May be called more than once. +void portable_start_cpu_speed_monitoring(); + +// Returns the current CPU speed. Return -1 in case of error. +int portable_cpu_speed(); + +// Declaration for callback function that is called by cpu speed +// monitoring when a cpu speed alert happens. +extern void cpu_speed_callback(int speed); + } // namespace blaze_jni #endif // BAZEL_SRC_MAIN_NATIVE_UNIX_JNI_H__
diff --git a/src/main/native/unix_jni_bsd.cc b/src/main/native/unix_jni_bsd.cc index 9f65854..6dd5068 100644 --- a/src/main/native/unix_jni_bsd.cc +++ b/src/main/native/unix_jni_bsd.cc
@@ -161,4 +161,13 @@ // Currently not implemented. } +void portable_start_cpu_speed_monitoring() { + // Currently not implemented. +} + +int portable_cpu_speed() { + // Currently not implemented. + return -1; +} + } // namespace blaze_jni
diff --git a/src/main/native/unix_jni_linux.cc b/src/main/native/unix_jni_linux.cc index 42221bc..63a204f 100644 --- a/src/main/native/unix_jni_linux.cc +++ b/src/main/native/unix_jni_linux.cc
@@ -136,4 +136,13 @@ // Currently not implemented. } +void portable_start_cpu_speed_monitoring() { + // Currently not implemented. +} + +int portable_cpu_speed() { + // Currently not implemented. + return -1; +} + } // namespace blaze_jni
diff --git a/src/main/native/windows/BUILD b/src/main/native/windows/BUILD index 8588762..934c235 100644 --- a/src/main/native/windows/BUILD +++ b/src/main/native/windows/BUILD
@@ -59,6 +59,7 @@ "jni-util.h", "processes-jni.cc", "sleep_prevention_jni.cc", + "system_cpu_speed_monitor_jni.cc", "system_disk_space_monitor_jni.cc", "system_load_advisory_monitor_jni.cc", "system_memory_pressure_jni.cc",
diff --git a/src/main/native/windows/system_cpu_speed_monitor_jni.cc b/src/main/native/windows/system_cpu_speed_monitor_jni.cc new file mode 100644 index 0000000..f2eb055 --- /dev/null +++ b/src/main/native/windows/system_cpu_speed_monitor_jni.cc
@@ -0,0 +1,46 @@ +// Copyright 2022 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. + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +#include <windows.h> + +#include "src/main/native/jni.h" + +/* + * Class: Java_com_google_devtools_build_lib_platform_SystemCPUSpeedModule + * Method: registerJNI + * Signature: ()V + */ +extern "C" JNIEXPORT void JNICALL +Java_com_google_devtools_build_lib_platform_SystemCPUSpeedModule_registerJNI( + JNIEnv *env, jobject local_object) { + // Currently not implemented. +} + +/* + * Class: Java_com_google_devtools_build_lib_platform_SystemCPUSpeedModule + * Method: cpuSpeed + * Signature: ()I + * + * Returns 1-100 to represent CPU speed. Returns -1 in case of error. + */ +extern "C" JNIEXPORT jint JNICALL +Java_com_google_devtools_build_lib_platform_SystemCPUSpeedModule_cpuSpeed( + JNIEnv *env, jclass) { + // Currently not implemented. + return -1; +}
diff --git a/src/test/java/com/google/devtools/build/lib/platform/BUILD b/src/test/java/com/google/devtools/build/lib/platform/BUILD index d0cb0c7..aad8713 100644 --- a/src/test/java/com/google/devtools/build/lib/platform/BUILD +++ b/src/test/java/com/google/devtools/build/lib/platform/BUILD
@@ -108,3 +108,26 @@ "@bazel_tools//tools/java/runfiles", ], ) + +java_test( + name = "SystemCPUSpeedEventTest", + timeout = "short", + srcs = ["SystemCPUSpeedEventTest.java"], + data = select({ + "//src/conditions:darwin": [ + "//src/test/java/com/google/devtools/build/lib/platform/darwin:notifier", + ], + "//conditions:default": [ + ], + }), + deps = [ + "//src/main/java/com/google/devtools/build/lib:runtime", + "//src/main/java/com/google/devtools/build/lib/platform:system_cpu_speed_module", + "//src/main/java/com/google/devtools/build/lib/util:os", + "//src/test/java/com/google/devtools/build/lib/buildtool/util", + "//third_party:guava", + "//third_party:junit4", + "//third_party:truth", + "@bazel_tools//tools/java/runfiles", + ], +)
diff --git a/src/test/java/com/google/devtools/build/lib/platform/SystemCPUSpeedEventTest.java b/src/test/java/com/google/devtools/build/lib/platform/SystemCPUSpeedEventTest.java new file mode 100644 index 0000000..c51ac8d --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/platform/SystemCPUSpeedEventTest.java
@@ -0,0 +1,78 @@ +// Copyright 2022 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. + +package com.google.devtools.build.lib.platform; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.eventbus.Subscribe; +import com.google.devtools.build.lib.buildtool.util.BuildIntegrationTestCase; +import com.google.devtools.build.lib.runtime.BlazeModule; +import com.google.devtools.build.lib.runtime.BlazeRuntime; +import com.google.devtools.build.lib.runtime.CommandEnvironment; +import com.google.devtools.build.lib.util.OS; +import com.google.devtools.build.runfiles.Runfiles; +import org.junit.Assume; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests {@link SystemCPUSpeedEvent} by sending fake notifications. */ +@RunWith(JUnit4.class) +public final class SystemCPUSpeedEventTest extends BuildIntegrationTestCase { + static class SystemCPUSpeedEventListener extends BlazeModule { + public int cpuSpeedEventCount = 0; + + @Override + public void beforeCommand(CommandEnvironment env) { + env.getEventBus().register(this); + } + + @Subscribe + public void cpuSpeedEvent(SystemCPUSpeedEvent event) { + ++cpuSpeedEventCount; + } + } + + private final SystemCPUSpeedEventListener eventListener = new SystemCPUSpeedEventListener(); + + @Override + protected BlazeRuntime.Builder getRuntimeBuilder() throws Exception { + return super.getRuntimeBuilder() + .addBlazeModule(eventListener) + .addBlazeModule(new SystemCPUSpeedModule()); + } + + @Test + public void testCPUSpeed() throws Exception { + Assume.assumeTrue(OS.getCurrent() == OS.DARWIN); + Runfiles runfiles = Runfiles.create(); + String notifierFilePath = + runfiles.rlocation( + "io_bazel/src/test/java/com/google/devtools/build/lib/platform/darwin/notifier"); + write( + "system_cpuspeed_event/BUILD", + "genrule(", + " name = 'fire_cpuspeed_notifications',", + " outs = ['fire_cpuspeed_notifications.out'],", + " cmd = '" + notifierFilePath + " com.google.bazel.test.cpuspeed 80 > $@ && ' + ", + " '" + notifierFilePath + " com.google.bazel.test.cpuspeed 60 >> $@ && ' + ", + " '" + notifierFilePath + " com.google.bazel.test.cpuspeed 70 >> $@ && ' + ", + " '" + notifierFilePath + " com.google.bazel.test.cpuspeed 40 >> $@ && ' + ", + " '" + notifierFilePath + " com.google.bazel.test.cpuspeed 50 >> $@',", + ")"); + buildTarget("//system_cpuspeed_event:fire_cpuspeed_notifications"); + assertThat(eventListener.cpuSpeedEventCount).isAtLeast(5); + } +}