Skip to content

Commit

Permalink
Work around a concurrent modification issue of KeyValues
Browse files Browse the repository at this point in the history
  • Loading branch information
jonatan-ivanov committed Dec 10, 2024
1 parent f58d4bc commit bc48089
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Copyright 2024 VMware, Inc.
*
* 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
*
* https://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.micrometer.benchmark.core;

import io.micrometer.common.KeyValue;
import io.micrometer.common.KeyValues;
import io.micrometer.observation.Observation;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

import java.util.concurrent.TimeUnit;

@State(Scope.Benchmark)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public class ObservationKeyValuesBenchmark {

private static final KeyValues KEY_VALUES = KeyValues.of("key1", "value1", "key2", "value2", "key3", "value3",
"key4", "value4", "key5", "value5");

private static final KeyValue KEY_VALUE = KeyValue.of("testKey", "testValue");

@Benchmark
public void noopBaseline() {
}

@Benchmark
public Observation.Context contextBaseline() {
return new TestContext().addLowCardinalityKeyValues(KEY_VALUES);
}

@Benchmark
public Observation.Context putKeyValue() {
return new TestContext().addLowCardinalityKeyValues(KEY_VALUES).addLowCardinalityKeyValue(KEY_VALUE);
}

@Benchmark
public KeyValues readKeyValues() {
return new TestContext().addLowCardinalityKeyValues(KEY_VALUES).getLowCardinalityKeyValues();
}

public static void main(String[] args) throws RunnerException {
Options options = new OptionsBuilder().include(ObservationKeyValuesBenchmark.class.getSimpleName())
.warmupIterations(5)
.measurementIterations(10)
.mode(Mode.SampleTime)
.forks(1)
.build();

new Runner(options).run();
}

static class TestContext extends Observation.Context {

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright 2024 VMware, Inc.
*
* 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
*
* https://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.micrometer.concurrencytests;

import io.micrometer.common.KeyValue;
import io.micrometer.observation.Observation;
import org.openjdk.jcstress.annotations.Actor;
import org.openjdk.jcstress.annotations.JCStressTest;
import org.openjdk.jcstress.annotations.Outcome;
import org.openjdk.jcstress.annotations.State;
import org.openjdk.jcstress.infra.results.Z_Result;

import java.util.UUID;

import static org.openjdk.jcstress.annotations.Expect.ACCEPTABLE;
import static org.openjdk.jcstress.annotations.Expect.FORBIDDEN;

public class ObservationContextConcurrencyTest {

@JCStressTest
@State
@Outcome(id = "true", expect = ACCEPTABLE)
@Outcome(expect = FORBIDDEN)
public static class ConsistentKeyValues {

private final Observation.Context context = new TestContext();

@Actor
public void read(Z_Result r) {
try {
context.getHighCardinalityKeyValues();
r.r1 = true;
}
catch (Exception e) {
r.r1 = false;
}
}

@Actor
public void write(Z_Result r) {
try {
String uuid = UUID.randomUUID().toString();
context.addHighCardinalityKeyValue(KeyValue.of(uuid, uuid));
r.r1 = true;
}
catch (Exception e) {
r.r1 = false;
}
}

}

static class TestContext extends Observation.Context {

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,12 @@ else if (keyValues instanceof KeyValues) {
}
else if (keyValues instanceof Collection) {
Collection<? extends KeyValue> keyValuesCollection = (Collection<? extends KeyValue>) keyValues;
return toKeyValues(keyValuesCollection.toArray(EMPTY_KEY_VALUE_ARRAY));
try {
return toKeyValues(keyValuesCollection.toArray(EMPTY_KEY_VALUE_ARRAY));
}
catch (ArrayIndexOutOfBoundsException exception) {
return of(keyValues);
}
}
else {
return toKeyValues(StreamSupport.stream(keyValues.spliterator(), false).toArray(KeyValue[]::new));
Expand Down

0 comments on commit bc48089

Please sign in to comment.