-
Notifications
You must be signed in to change notification settings - Fork 36
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Jesse Yates
committed
Sep 7, 2020
0 parents
commit 5ccf7cf
Showing
9 changed files
with
519 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
# Event Generators | ||
|
||
Generate realistic device and pricing events, modeled on real-world events. | ||
|
||
## Running | ||
|
||
Show the help | ||
``` | ||
java -jar target/event-generators-1.0-SNAPSHOT-jar-with-dependencies.jar -h | ||
java -jar target/event-generators-1.0-SNAPSHOT-jar-with-dependencies.jar --help | ||
``` | ||
|
||
Run the device event generator | ||
|
||
``` | ||
java -jar target/event-generators-1.0-SNAPSHOT-jar-with-dependencies.jar \ | ||
events --target http://host:1234/my/path | ||
```` | ||
|
||
Run the pricing generator | ||
|
||
``` | ||
java -jar target/event-generators-1.0-SNAPSHOT-jar-with-dependencies.jar \ | ||
pricing --target http://host:1234/my/path | ||
```` | ||
|
||
Event generators can also be run with an optional `--seed` flag, that takes a long, to set the | ||
random seed used for generating events, enabling a reproducable stream of events. | ||
|
||
See `--help` for more information. | ||
|
||
## Building the generators | ||
|
||
``` | ||
mvn clean package | ||
``` | ||
|
||
Creates a `-jar-with-dependencies.jar` in the `target/` directory, which can then be run. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
<parent> | ||
<artifactId>dist-grid</artifactId> | ||
<groupId>com.jesseyates.manning</groupId> | ||
<version>1.0-SNAPSHOT</version> | ||
</parent> | ||
<modelVersion>4.0.0</modelVersion> | ||
<artifactId>event-generators</artifactId> | ||
|
||
<properties> | ||
<jcommander.version>1.78</jcommander.version> | ||
<okhttp.version>4.5.0</okhttp.version> | ||
<jackson.version>2.7.2</jackson.version> | ||
<commons-lang3.version>3.10</commons-lang3.version> | ||
</properties> | ||
|
||
|
||
<build> | ||
<plugins> | ||
<plugin> | ||
<groupId>org.apache.maven.plugins</groupId> | ||
<artifactId>maven-compiler-plugin</artifactId> | ||
<configuration> | ||
<source>1.8</source> | ||
<target>1.8</target> | ||
</configuration> | ||
</plugin> | ||
<plugin> | ||
<artifactId>maven-assembly-plugin</artifactId> | ||
<version>3.2.0</version> | ||
<executions> | ||
<execution> | ||
<phase>package</phase> | ||
<goals> | ||
<goal>single</goal> | ||
</goals> | ||
<configuration> | ||
<descriptorRefs> | ||
<descriptorRef>jar-with-dependencies</descriptorRef> | ||
</descriptorRefs> | ||
<archive> | ||
<manifest> | ||
<mainClass>com.jesseyates.manning.Generator</mainClass> | ||
</manifest> | ||
</archive> | ||
</configuration> | ||
</execution> | ||
</executions> | ||
</plugin> | ||
<plugin> | ||
<groupId>org.apache.maven.plugins</groupId> | ||
<artifactId>maven-compiler-plugin</artifactId> | ||
<configuration> | ||
<source>1.8</source> | ||
<target>1.8</target> | ||
</configuration> | ||
</plugin> | ||
</plugins> | ||
</build> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>com.beust</groupId> | ||
<artifactId>jcommander</artifactId> | ||
<version>${jcommander.version}</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.squareup.okhttp3</groupId> | ||
<artifactId>okhttp</artifactId> | ||
<version>${okhttp.version}</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.fasterxml.jackson.core</groupId> | ||
<artifactId>jackson-core</artifactId> | ||
<version>${jackson.version}</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.fasterxml.jackson.core</groupId> | ||
<artifactId>jackson-databind</artifactId> | ||
<version>${jackson.version}</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.apache.commons</groupId> | ||
<artifactId>commons-lang3</artifactId> | ||
<version>${commons-lang3.version}</version> | ||
</dependency> | ||
|
||
<dependency> | ||
<groupId>junit</groupId> | ||
<artifactId>junit</artifactId> | ||
<version>4.12</version> | ||
<scope>test</scope> | ||
</dependency> | ||
</dependencies> | ||
</project> |
95 changes: 95 additions & 0 deletions
95
event-generators/src/main/java/com/jesseyates/manning/BaseGenerator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
package com.jesseyates.manning; | ||
|
||
import com.beust.jcommander.Parameter; | ||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import okhttp3.MediaType; | ||
import okhttp3.OkHttpClient; | ||
import okhttp3.Request; | ||
import okhttp3.RequestBody; | ||
import okhttp3.Response; | ||
|
||
import java.io.IOException; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.Optional; | ||
import java.util.Random; | ||
import java.util.concurrent.TimeUnit; | ||
|
||
public abstract class BaseGenerator { | ||
|
||
@Parameter(names = {"-t", "--target"}, description = "Destination to send the events", | ||
required = true) | ||
private String path; | ||
|
||
@Parameter(names = {"--debug"}, description = "Enable debugging of requests and responses") | ||
private boolean debug; | ||
|
||
@Parameter(names = {"-w", "--wait-interval-ms"}, | ||
description = "Milliseconds to wait between making successive requests") | ||
protected long waitMillis = 500; | ||
|
||
protected final Random r = new Random(319009871); | ||
protected List<Integer> regions = new ArrayList<>(); | ||
|
||
{ | ||
for (int i = 0; i < 1000; i++) { | ||
regions.add(r.nextInt(10000)); | ||
} | ||
} | ||
|
||
public static final MediaType JSON | ||
= MediaType.get("application/json; charset=utf-8"); | ||
|
||
protected static ObjectMapper MAPPER = new ObjectMapper(); | ||
|
||
protected OkHttpClient client = new OkHttpClient.Builder() | ||
.connectTimeout(5, TimeUnit.SECONDS) | ||
.writeTimeout(5, TimeUnit.SECONDS) | ||
.readTimeout(5, TimeUnit.SECONDS) | ||
.callTimeout(10, TimeUnit.SECONDS) | ||
.build(); | ||
|
||
|
||
public void run() throws Exception { | ||
while (true) { | ||
nextEvent(); | ||
Thread.sleep(waitMillis); | ||
} | ||
} | ||
|
||
protected abstract void nextEvent() throws Exception; | ||
|
||
protected String post(Object body) throws Exception { | ||
return post(body, Optional.empty()); | ||
} | ||
|
||
protected String post(Object body, Optional<String> suffix) throws Exception { | ||
return post(MAPPER.writeValueAsString(body), suffix); | ||
} | ||
|
||
protected String post(String json, Optional<String> suffix) throws IOException { | ||
RequestBody body = RequestBody.create(json, JSON); | ||
String url = suffix.map(s -> path + "/" + s).orElse(path); | ||
Request request = new Request.Builder() | ||
.url(url) | ||
.post(body) | ||
.build(); | ||
if (debug) { | ||
System.out.println("Sending: " + request + " : " + json); | ||
} | ||
try (Response response = client.newCall(request).execute()) { | ||
if (debug) { | ||
System.out.println("Got response: " + response); | ||
} | ||
return response.body().string(); | ||
} | ||
} | ||
|
||
protected int intInRange(int lower, int upper) { | ||
return Ranges.intInRange(r, lower, upper); | ||
} | ||
|
||
protected float floatInRange(Float lower, Float upper) { | ||
return Ranges.floatInRange(r, lower, upper); | ||
} | ||
} |
129 changes: 129 additions & 0 deletions
129
event-generators/src/main/java/com/jesseyates/manning/EventGenerator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
package com.jesseyates.manning; | ||
|
||
import com.beust.jcommander.Parameter; | ||
import com.beust.jcommander.Parameters; | ||
import org.apache.commons.lang3.tuple.ImmutablePair; | ||
import org.apache.commons.lang3.tuple.Pair; | ||
|
||
import java.io.IOException; | ||
import java.util.ArrayList; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Optional; | ||
import java.util.Random; | ||
import java.util.UUID; | ||
import java.util.stream.Collectors; | ||
|
||
|
||
@Parameters(commandDescription = "Run the device event generator") | ||
public class EventGenerator extends BaseGenerator { | ||
|
||
@Parameter(names = {"-n", "--num-devices"}, description = "Number of unique devices") | ||
protected int deviceCount = 1_000; | ||
|
||
@Parameter(names = {"-e", "--max-events-per-request"}, | ||
description = "Maximum number of events per request") | ||
protected int maxEvents = 100; | ||
|
||
@Parameter(names = {"-b", "--bad-events"}, description = "Corrupt the events") | ||
private boolean badEvents; | ||
@Parameter(names = {"-m", "--missing-region"}, | ||
description = "Events are missing the region identifier") | ||
private boolean missingRegion; | ||
|
||
private static UUID randomUUID(Random r) { | ||
// copy the UUID logic, but use our own random | ||
byte[] data = new byte[16]; | ||
r.nextBytes(data); | ||
data[6] &= 0x0f; /* clear version */ | ||
data[6] |= 0x40; /* set to version 4 */ | ||
data[8] &= 0x3f; /* clear variant */ | ||
data[8] |= 0x80; /* set to IETF variant */ | ||
long msb = 0; | ||
long lsb = 0; | ||
for (int i = 0; i < 8; i++) | ||
msb = (msb << 8) | (data[i] & 0xff); | ||
for (int i = 8; i < 16; i++) | ||
lsb = (lsb << 8) | (data[i] & 0xff); | ||
return new UUID(msb, lsb); | ||
} | ||
|
||
private final Map<UUID, Integer> deviceToRegion = new HashMap<>(); | ||
|
||
// setup devices and regions | ||
{ | ||
for (int i = 0; i < deviceCount; i++) { | ||
int region = regions.get(r.nextInt(regions.size())); | ||
deviceToRegion.put(randomUUID(r), region); | ||
} | ||
} | ||
|
||
private final Map<String, Pair<Object, Object>> events = new HashMap<>(); | ||
|
||
// setup event field values | ||
{ | ||
events.put("charging", new ImmutablePair<>(-1000, 1000)); | ||
events.put("charging_source", new ImmutablePair<>("solar", "utility")); | ||
events.put("current_capacity", new ImmutablePair<>(0, 13_000)); | ||
// other fields like a real device would send | ||
events.put("moduleL_temp", new ImmutablePair<>(-5, 225)); | ||
events.put("moduleR_temp", new ImmutablePair<>(-5, 225)); | ||
events.put("processor1_temp", new ImmutablePair<>(-5, 225)); | ||
events.put("processor2_temp", new ImmutablePair<>(-5, 225)); | ||
events.put("processor3_temp", new ImmutablePair<>(-5, 225)); | ||
events.put("processor4_temp", new ImmutablePair<>(-5, 225)); | ||
events.put("inverter_state", new ImmutablePair<>(0, 15)); | ||
events.put("SoC_regulator", new ImmutablePair<>(26.0f, 29.6f)); | ||
} | ||
|
||
@Override | ||
public void nextEvent() throws Exception { | ||
UUID[] ids = deviceToRegion.keySet().toArray(new UUID[0]); | ||
UUID id = ids[r.nextInt(ids.length - 1)]; | ||
|
||
int count = Math.max(1, r.nextInt(maxEvents)); | ||
List<Object> events = new ArrayList<>(count); | ||
for (int i = 0; i < count; i++) { | ||
Object event = getEvent(id); | ||
if (badEvents && r.nextBoolean()) { | ||
event = "garbage{\"/}" + MAPPER.writeValueAsString(event); | ||
} | ||
events.add(event); | ||
} | ||
|
||
// then we convert them to be one event per line | ||
String message = events.stream().map(event -> { | ||
try { | ||
return MAPPER.writeValueAsString(event); | ||
} catch (IOException e) { | ||
throw new RuntimeException(e); | ||
} | ||
}).collect(Collectors.joining("\n")); | ||
|
||
post(message, Optional.of(id.toString())); | ||
} | ||
|
||
private Map<String, Object> getEvent(UUID id) { | ||
Map<String, Object> event = new HashMap<>(); | ||
int region = deviceToRegion.get(id); | ||
event.put("device_id", id.toString()); | ||
if (missingRegion && r.nextBoolean()) { | ||
event.put("region", region); | ||
} | ||
for (Map.Entry<String, Pair<Object, Object>> entry : events.entrySet()) { | ||
Pair<Object, Object> range = entry.getValue(); | ||
Object value = null; | ||
if (range.getKey() instanceof Integer) { | ||
value = intInRange((Integer) range.getKey(), (Integer) range.getValue()); | ||
} else if (range.getKey() instanceof Float) { | ||
value = floatInRange((Float)range.getKey(), (Float) range.getValue()); | ||
} else if (range.getKey() instanceof String) { | ||
value = r.nextBoolean() ? range.getKey() : range.getValue(); | ||
} | ||
event.put(entry.getKey(), value); | ||
} | ||
|
||
return event; | ||
} | ||
} |
Oops, something went wrong.