new decorator API: share Scenario metadata to interested types

This commit is contained in:
Jonathan Shook 2021-10-11 17:23:19 -05:00
parent bbeee5cb1a
commit 8c6d0cfe57
7 changed files with 141 additions and 8 deletions

View File

@ -5,7 +5,7 @@ import io.nosqlbench.engine.clients.grafana.GStitcher;
import io.nosqlbench.engine.clients.grafana.GrafanaClient;
import io.nosqlbench.engine.clients.grafana.GrafanaClientConfig;
import io.nosqlbench.engine.clients.grafana.transfer.*;
import io.nosqlbench.nb.api.SystemId;
import io.nosqlbench.nb.api.metadata.SystemId;
import java.nio.file.Path;
import java.time.Instant;

View File

@ -6,7 +6,7 @@ import io.nosqlbench.engine.clients.grafana.transfer.GAnnotation;
import io.nosqlbench.nb.annotations.Service;
import io.nosqlbench.nb.api.NBEnvironment;
import io.nosqlbench.nb.api.OnError;
import io.nosqlbench.nb.api.SystemId;
import io.nosqlbench.nb.api.metadata.SystemId;
import io.nosqlbench.nb.api.annotations.Annotation;
import io.nosqlbench.nb.api.annotations.Annotator;
import io.nosqlbench.nb.api.config.*;

View File

@ -25,6 +25,9 @@ import io.nosqlbench.engine.core.lifecycle.ScenarioController;
import io.nosqlbench.engine.core.lifecycle.ScenarioResult;
import io.nosqlbench.engine.core.annotation.Annotators;
import io.nosqlbench.engine.core.metrics.PolyglotMetricRegistryBindings;
import io.nosqlbench.nb.api.metadata.ScenarioMetadata;
import io.nosqlbench.nb.api.metadata.ScenarioMetadataAware;
import io.nosqlbench.nb.api.metadata.SystemId;
import io.nosqlbench.nb.api.annotations.Layer;
import io.nosqlbench.nb.api.annotations.Annotation;
import org.apache.logging.log4j.LogManager;
@ -46,10 +49,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.stream.Collectors;
@ -63,6 +63,7 @@ public class Scenario implements Callable<ScenarioResult> {
private State state = State.Scheduled;
private volatile ScenarioShutdownHook scenarioShutdownHook;
private Exception error;
private ScenarioMetadata scenarioMetadata;
public enum State {
@ -236,12 +237,23 @@ public class Scenario implements Callable<ScenarioResult> {
metricRegistry,
scriptEnv
);
ScenarioMetadataAware.apply(extensionObject,getScenarioMetadata());
logger.trace("Adding extension object: name=" + extensionDescriptor.getBaseVariableName() +
" class=" + extensionObject.getClass().getSimpleName());
scriptEngine.put(extensionDescriptor.getBaseVariableName(), extensionObject);
}
}
private synchronized ScenarioMetadata getScenarioMetadata() {
if (this.scenarioMetadata==null) {
this.scenarioMetadata = new ScenarioMetadata(
this.startedAtMillis,
this.scenarioName,
SystemId.getNodeId(),
SystemId.getNodeFingerprint()
);
}
return scenarioMetadata;
}
public void runScenario() {

View File

@ -0,0 +1,47 @@
package io.nosqlbench.nb.api.metadata;
import java.util.Map;
/**
* If an object is ScenarioMetadata, then they will be updated with a map of
* scenario metadata. Supported types are:
* <UL>
* <LI>ScriptingPluginInfo</LI>
* </UL>
*/
public class ScenarioMetadata {
private final long startedAt;
private final String sessionName;
private final String systemId;
private final String systemFingerprint;
public ScenarioMetadata(long startedAt, String sessionName, String systemId, String systemFingerprint) {
this.startedAt = startedAt;
this.sessionName = sessionName;
this.systemId = systemId;
this.systemFingerprint = systemFingerprint;
}
public long getStartedAt() {
return startedAt;
}
public String getSessionName() {
return sessionName;
}
public String getSystemId() {
return systemId;
}
public String getSystemFingerprint() {
return systemFingerprint;
}
public Map<String,String> asMap() {
return Map.of("STARTED_AT",String.valueOf(startedAt),
"SESSION_NAME",sessionName,
"SYSTEM_ID",systemId,
"SYSTEM_FINGERPRINT", systemFingerprint);
}
}

View File

@ -0,0 +1,21 @@
package io.nosqlbench.nb.api.metadata;
/**
* Where supported, the following named fields are injected into object which
* implement this interface:
* <UL>
* <LI>SCENARIO_NAME - The full scenario name, used for logging, metrics, etc</LI>
* <LI>STARTED_AT_MILLIS - The millisecond timestamp used to create the scenario name</LI>
* <LI>SYSTEM_ID - A stable identifier based on the available ip addresses</LI></LK>
* <LI>SYSTEM_FINGERPRINT - a stable and pseudonymous identifier based on SYSTEM_ID</LI>
* </UL>
*/
public interface ScenarioMetadataAware {
void setScenarioMetadata(ScenarioMetadata metadata);
static void apply(Object target, ScenarioMetadata metadata) {
if (target instanceof ScenarioMetadataAware) {
((ScenarioMetadataAware)target).setScenarioMetadata(metadata);
}
}
}

View File

@ -1,4 +1,4 @@
package io.nosqlbench.nb.api;
package io.nosqlbench.nb.api.metadata;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
@ -7,10 +7,25 @@ import oshi.hardware.CentralProcessor;
import oshi.hardware.HardwareAbstractionLayer;
import oshi.hardware.NetworkIF;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
public class SystemId {
/**
* Return the address of a node which is likely to be unique enough to identify
* it within a given subnet, after filtering out all local addresses. This is useful
* when you are managing configuration or results for a set of systems which
* share a common IP addressing scheme. This identifier should be stable as long
* as the node's addresses do not change.
*
* If you are needing an identifier for a node but wish to expose any address data,
* you can use the {@link #getNodeFingerprint()} which takes this value and hashes
* it with SHA-1 to produce a hex string.
* @return A address for the node, likely to be unique and stable for its lifetime
*/
public static String getNodeId() {
SystemInfo sysinfo = new SystemInfo();
HardwareAbstractionLayer hal = sysinfo.getHardware();
@ -38,6 +53,28 @@ public class SystemId {
return systemID;
}
/**
* Produce a stable string identifier consisting of hexadecimal characters.
* The internal data used for this value is based on a stable ordering of non-local
* ip addresses available on the system.
* @return A stable node identifier
*/
public static String getNodeFingerprint() {
String addrId = getNodeId();
try {
MessageDigest sha1_digest = MessageDigest.getInstance("SHA-1");
byte[] addrBytes = sha1_digest.digest(addrId.getBytes(StandardCharsets.UTF_8));
String fingerprint = "";
for (int i=0; i < addrBytes.length; i++) {
fingerprint +=
Integer.toString( ( addrBytes[i] & 0xff ) + 0x100, 16).substring( 1 );
}
return fingerprint.toUpperCase(Locale.ROOT);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
public static String getHostSummary() {
SystemInfo sysinfo = new SystemInfo();
HardwareAbstractionLayer hal = sysinfo.getHardware();

View File

@ -1,7 +1,10 @@
package io.nosqlbench.nb.api;
import io.nosqlbench.nb.api.metadata.SystemId;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class SystemIdTest {
@Test
@ -9,4 +12,17 @@ public class SystemIdTest {
String info = SystemId.getHostSummary();
System.out.println(info);
}
@Test
public void testNostId() {
String info = SystemId.getNodeId();
assertThat(info).matches("\\d+\\.\\d+\\.\\d+\\.\\d+");
}
@Test
public void testNodeFingerprint() {
String hash = SystemId.getNodeFingerprint();
assertThat(hash).matches("[A-Z0-9]+");
}
}