Skip to content

Commit

Permalink
Add ThreadsCount type
Browse files Browse the repository at this point in the history
  • Loading branch information
wendigo committed Mar 29, 2023
1 parent 72628e3 commit 7668ea8
Show file tree
Hide file tree
Showing 6 changed files with 448 additions and 0 deletions.
12 changes: 12 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,18 @@
<optional>true</optional>
</dependency>

<dependency>
<groupId>com.github.oshi</groupId>
<artifactId>oshi-core</artifactId>
<version>6.4.1</version>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>

<!-- for testing -->
<dependency>
<groupId>org.testng</groupId>
Expand Down
38 changes: 38 additions & 0 deletions src/main/java/io/airlift/units/PowerOfTwo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* 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 io.airlift.units;

import javax.validation.Constraint;
import javax.validation.Payload;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Target({METHOD, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = PowerOfTwoValidator.class)
public @interface PowerOfTwo
{
String message() default "is not a power of two";

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};
}
32 changes: 32 additions & 0 deletions src/main/java/io/airlift/units/PowerOfTwoValidator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* 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 io.airlift.units;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class PowerOfTwoValidator
implements ConstraintValidator<PowerOfTwo, Integer>
{
@Override
public void initialize(PowerOfTwo powerOfTwo)
{
}

@Override
public boolean isValid(Integer value, ConstraintValidatorContext context)
{
return value == null || Integer.bitCount(value) == 1;
}
}
183 changes: 183 additions & 0 deletions src/main/java/io/airlift/units/ThreadsCount.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
/*
* 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 io.airlift.units;

import oshi.SystemInfo;

import java.util.function.Supplier;

import static io.airlift.units.Preconditions.checkArgument;
import static java.lang.Math.min;
import static java.lang.Math.round;
import static java.lang.String.format;

public class ThreadsCount
implements Comparable<ThreadsCount>
{
private static final String PER_CORE_SUFFIX = "C";
private static final Supplier<Integer> AVAILABLE_PROCESSORS = MachineInfo::getAvailablePhysicalProcessorCount;
private final int threadsCount;

ThreadsCount(int threadsCount)
{
checkArgument(threadsCount >= 0, "Threads count cannot be smaller than 0");
this.threadsCount = threadsCount;
}

public int getThreadsCount()
{
return threadsCount;
}

public ThreadsCount nextPowerOf2()
{
return new ThreadsCount(nextPowerOfTwo(getThreadsCount()));
}

public static ThreadsCount exactValueOf(int value)
{
return new ThreadsCount(value);
}

public static ThreadsCount valueOf(String value)
{
if (value.endsWith(PER_CORE_SUFFIX)) {
float parsedMultiplier = parseFloat(value.substring(0, value.lastIndexOf(PER_CORE_SUFFIX)));
return new ThreadsCount(round(parsedMultiplier * AVAILABLE_PROCESSORS.get()));
}

return new ThreadsCount(parseInteger(value));
}

public static ThreadsCount boundedValueOf(String value, String minValue, String maxValue)
{
ThreadsCount parsed = ThreadsCount.valueOf(value);
ThreadsCount min = ThreadsCount.valueOf(minValue);
ThreadsCount max = ThreadsCount.valueOf(maxValue);

if (parsed.compareTo(min) < 0) {
return min;
}

if (parsed.compareTo(max) > 0) {
return max;
}
return parsed;
}

private static float parseFloat(String value)
{
try {
return Float.parseFloat(value);
}
catch (NumberFormatException e) {
throw new IllegalArgumentException(format("Cannot parse value '%s' as float", value), e);
}
}

private static int parseInteger(String value)
{
try {
return Integer.parseInt(value);
}
catch (NumberFormatException e) {
throw new IllegalArgumentException(format("Cannot parse value '%s' as integer", value), e);
}
}

static int nextPowerOfTwo(int x)
{
if (x == 0) {
return 1;
}

--x;
x |= x >> 1;
x |= x >> 2;
x |= x >> 4;
x |= x >> 8;
return (x | x >> 16) + 1;
}

@Override
public int compareTo(ThreadsCount o)
{
return Integer.compare(threadsCount, o.threadsCount);
}

@Override
public boolean equals(Object o)
{
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}

ThreadsCount that = (ThreadsCount) o;
return threadsCount == that.threadsCount;
}

@Override
public int hashCode()
{
return threadsCount;
}

@Override
public String toString()
{
if (threadsCount == 1) {
return "1 thread";
}
return format("%d threads", threadsCount);
}

public static final class MachineInfo
{
// cache physical processor count, so that it's not queried multiple times during tests
private static volatile int physicalProcessorCount = -1;

private MachineInfo() {}

public static int getAvailablePhysicalProcessorCount()
{
if (physicalProcessorCount != -1) {
return physicalProcessorCount;
}

String osArch = System.getProperty("os.arch");
// logical core count (including container cpu quota if there is any)
int availableProcessorCount = Runtime.getRuntime().availableProcessors();
int totalPhysicalProcessorCount;
if ("amd64".equals(osArch) || "x86_64".equals(osArch)) {
// Oshi can recognize physical processor count (without hyper threading) for x86 platforms.
// However, it doesn't correctly recognize physical processor count for ARM platforms.
totalPhysicalProcessorCount = new SystemInfo()
.getHardware()
.getProcessor()
.getPhysicalProcessorCount();
}
else {
// ARM platforms do not support hyper threading, therefore each logical processor is separate core
totalPhysicalProcessorCount = availableProcessorCount;
}

// cap available processor count to container cpu quota (if there is any).
physicalProcessorCount = min(totalPhysicalProcessorCount, availableProcessorCount);
return physicalProcessorCount;
}
}
}
87 changes: 87 additions & 0 deletions src/test/java/io/airlift/units/TestPowerOfTwoValidator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* 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 io.airlift.units;

import org.testng.annotations.Test;

import static io.airlift.testing.ValidationAssertions.assertFailsValidation;
import static io.airlift.testing.ValidationAssertions.assertValidates;

public class TestPowerOfTwoValidator
{
@Test
public void testValidator()
{
assertValid(1);
assertValid(2);
assertValid(64);
assertInvalid(0);
assertInvalid(3);
assertInvalid(99);
assertInvalid(-1);
assertInvalid(-2);
assertInvalid(-4);
}

@Test
public void testAllowsNullPowerOfTwoAnnotation()
{
assertValidates(new NullPowerOfTwoAnnotation());
}

private static void assertValid(int value)
{
assertValidates(new ConstrainedPowerOfTwo(value));
}

private static void assertInvalid(int value)
{
Object object = new ConstrainedPowerOfTwo(value);
assertFailsValidation(object, "unboxed", "is not a power of two", PowerOfTwo.class);
assertFailsValidation(object, "boxed", "is not a power of two", PowerOfTwo.class);
}

@SuppressWarnings("UnusedDeclaration")
public static class ConstrainedPowerOfTwo
{
private final int value;

public ConstrainedPowerOfTwo(int value)
{
this.value = value;
}

@PowerOfTwo
public int getUnboxed()
{
return value;
}

@PowerOfTwo
public Integer getBoxed()
{
return value;
}
}

@SuppressWarnings("UnusedDeclaration")
public static class NullPowerOfTwoAnnotation
{
@PowerOfTwo
public Integer getNull()
{
return null;
}
}
}
Loading

0 comments on commit 7668ea8

Please sign in to comment.