Skip to content

Commit

Permalink
basic concurrency tests written using K6 and added synchronization ar…
Browse files Browse the repository at this point in the history
…ound AutoIncrement
  • Loading branch information
eviltester committed Apr 14, 2024
1 parent 2d1bd95 commit fc4c788
Show file tree
Hide file tree
Showing 11 changed files with 197 additions and 30 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package uk.co.compendiumdev.challenge.practicemodes.simpleapi;

import uk.co.compendiumdev.thingifier.api.http.HttpApiRequest;
import uk.co.compendiumdev.thingifier.api.http.HttpApiResponse;
import uk.co.compendiumdev.thingifier.apiconfig.ThingifierApiConfig;
import uk.co.compendiumdev.thingifier.application.httpapimessagehooks.HttpApiRequestHook;
import uk.co.compendiumdev.thingifier.core.EntityRelModel;
import uk.co.compendiumdev.thingifier.core.domain.instances.AutoIncrement;
import uk.co.compendiumdev.thingifier.core.domain.instances.ERInstanceData;
import uk.co.compendiumdev.thingifier.core.domain.instances.EntityInstanceCollection;

public class ResetAutoIncrementWhenTooHigh implements HttpApiRequestHook {

private final EntityRelModel erModel;

public ResetAutoIncrementWhenTooHigh(EntityRelModel eRmodel){
this.erModel = eRmodel;
}

@Override
public HttpApiResponse run(HttpApiRequest request, ThingifierApiConfig config) {

ERInstanceData instanceData = erModel.getInstanceData(EntityRelModel.DEFAULT_DATABASE_NAME);
if(instanceData!=null){
EntityInstanceCollection collection = instanceData.
getInstanceCollectionForEntityNamed("item");
AutoIncrement idCounter = collection.getCounters().get("id");
//if(idCounter.getCurrentValue()>2140000000){
if(idCounter.getCurrentValue()>99999){
// reset it
idCounter.incrementToNextAbove(0);
}
if(collection != null && collection.countInstances()<5) {
erModel.populateDatabase(EntityRelModel.DEFAULT_DATABASE_NAME);
}
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ public void configure() {

simpleApiHttpRouting = new ThingifierHttpApiRoutings(simplethings, apiDocDefn);
simpleApiHttpRouting.registerHttpApiRequestHook(new AddMoreItemsIfNecessary(simplethings.getERmodel()));
simpleApiHttpRouting.registerHttpApiRequestHook(new ResetAutoIncrementWhenTooHigh(simplethings.getERmodel()));


new SimpleSparkRouteCreator("/simpleap/items").status(501, List.of("patch", "trace"));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ If you want to explore the tool more then you could try the experiments below, o

## Swagger OpenAPI File

You can download a simple Swagger [OpenAPI File for simulation mode](/practice-modes/simulation/swagger).
You can download a simple Swagger [OpenAPI File for simulation mode](/sim/docs/swaggercd ..).

## Simulation Mode Walkthrough - Insomnia

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -622,7 +622,7 @@ public void ensureAtMostXTodoAvailable(int x){
final EntityInstanceCollection todos = ChallengeMain.getChallenger().getThingifier().getThingInstancesNamed("todo", challenger.getXChallenger());
if(todos.countInstances()>x){
for(int delCount = todos.countInstances()-x; delCount > 0; delCount--) {
todos.deleteInstance(((EntityInstance) (todos.getInstances().toArray()[0])).getInternalId());
todos.deleteInstance((EntityInstance) (todos.getInstances().toArray()[0]));
}
}
}
Expand Down
121 changes: 121 additions & 0 deletions docs/k6/script.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {

scenarios: {
my_scenario1: {
executor: 'constant-arrival-rate',
duration: '60s', // total duration
preAllocatedVUs: 100, // to allocate runtime resources preAll
rate: 100, // number of constant iterations given `timeUnit`
timeUnit: '1s',
},
},

// // A number specifying the number of VUs to run concurrently.
// vus: 100,
// // A string specifying the total duration of the test run.
// duration: '20s',


// Uncomment this section to enable the use of Browser API in your tests.
//
// See https://grafana.com/docs/k6/latest/using-k6-browser/running-browser-tests/ to learn more
// about using Browser API in your test scripts.
//
// scenarios: {
// // The scenario name appears in the result summary, tags, and so on.
// // You can give the scenario any name, as long as each name in the script is unique.
// ui: {
// // Executor is a mandatory parameter for browser-based tests.
// // Shared iterations in this case tells k6 to reuse VUs to execute iterations.
// //
// // See https://grafana.com/docs/k6/latest/using-k6/scenarios/executors/ for other executor types.
// executor: 'shared-iterations',
// options: {
// browser: {
// // This is a mandatory parameter that instructs k6 to launch and
// // connect to a chromium-based browser, and use it to run UI-based
// // tests.
// type: 'chromium',
// },
// },
// },
// }
};

// The function that defines VU logic.
//
// See https://grafana.com/docs/k6/latest/examples/get-started-with-k6/ to learn more
// about authoring k6 scripts.
//
const origin = "http://localhost:4567";

export default function() {

const itemsJson = getItems();

// random item id
const randomIndex = Math.floor(Math.random()*itemsJson.items.length)
const itemId = itemsJson.items[randomIndex].id;
if((Math.random()*100)<5){
// 5% chance we will get an item
const itemJson = getItem(itemId);
}
if((Math.random()*100)<10){
// 10% chance that we will delete something
deleteItem(itemId);
}
if(itemsJson.items.length<80){
createRandomItem();
}
}

function getItems(){
const response = http.get(`${origin}/simpleapi/items`);
check(response, { 'get items status was 200': (r) => r.status == 200 });
if(response.status!=200){
console.log(response.body)
}
return response.json();
}

function getItem(id){
const response = http.get(`${origin}/simpleapi/items/${id}`);
check(response, { 'get item status was 200': (r) => r.status == 200 });
if(response.status!=200){
console.log(response.body)
}
return response.json();
}

function deleteItem(id){
const response = http.del(`${origin}/simpleapi/items/${id}`);
check(response, { 'delete item status was 200': (r) => r.status == 200 });
if(response.status!=200){
console.log(response.body)
}
}

function createRandomItem(){
const types = ["cd", "book", "dvd", "blu-ray"]
const randomType = Math.floor(Math.random()*types.length)
const isbn = ("" + Math.random()).substring(2, 8) + ("" + Math.random()).substring(2, 8) + ("" + Math.random()).substring(2, 3)
const anItem = {
"type": types[randomType],
"isbn13": isbn,
"price" : ((Math.random()*100+10)+"").substring(0,5)
}
//console.log(anItem);
const response = http.post(`${origin}/simpleapi/items`, JSON.stringify(anItem),
{
headers: { 'Content-Type': 'application/json' },
}
)

if(response.status==400){
console.log(response.body)
}
check(response, { 'post item status was 201': (r) => r.status == 201 });
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,24 @@ public String getName(){
return name;
}

@Deprecated // we probably want to use getNextValueAndUpdate
public int getCurrentValue(){
//TODO: have a list of free items, used prior to the nextInt
// e.g. on DELETE, or if we do not create an item, or if we skip items during an increment on PUT
return nextInt;
}

public void update(){
private synchronized void update(){
nextInt = nextInt + incrementBy;
}

public void setNextValue(int integer) {

public synchronized int getNextValueAndUpdate(){
int curr = getCurrentValue();
update();
return curr;
}

public void incrementToNextAbove(Integer integer) {
public synchronized void incrementToNextAbove(Integer integer) {
nextInt = integer;
update();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ public void deleteEntityInstance(final EntityInstance anEntityInstance) {

// we may also have to delete things which are mandatorily related i.e. can't exist on their own
final List<EntityInstance> otherInstancesToDelete =
anInstanceCollection.deleteInstance(anEntityInstance.getInternalId());
anInstanceCollection.deleteInstance(anEntityInstance);

// TODO: Warning recursion with no 'cut off' if any cyclical relationships then this might fail
for(EntityInstance deleteMe : otherInstancesToDelete){
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ public void addAutoIncrementIdsToInstance(Map<String,AutoIncrement> autos) {
for(Field autoIncrementedField : entityDefinition.getFieldsOfType(FieldType.AUTO_INCREMENT)){
AutoIncrement auto = autos.get(autoIncrementedField.getName());
if(!instanceFields.hasAssignedValue(autoIncrementedField.getName())){
instanceFields.putValue(autoIncrementedField.getName(), String.valueOf(auto.getCurrentValue()));
auto.update();
instanceFields.putValue(autoIncrementedField.getName(),
String.valueOf(auto.getNextValueAndUpdate()));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,8 @@ public EntityInstanceCollection addInstance(EntityInstance instance) {
if(counter==null){
counter = createCounterFor(fieldDefn);
}
instance.overrideValue(fieldDefn.getName(),String.valueOf(counter.getCurrentValue()));
counter.update();
instance.overrideValue(fieldDefn.getName(),
String.valueOf(counter.getNextValueAndUpdate()));
}
}else{
if(fieldDefn.getType()==FieldType.AUTO_INCREMENT) {
Expand Down Expand Up @@ -200,7 +200,7 @@ public List<EntityInstance> deleteInstance(String guid) {

if (!instances.containsKey(guid)) {
throw new IndexOutOfBoundsException(
String.format("Could not find a %s with GUID %s",
String.format("unable to delete, could not find a %s with GUID %s",
definition.getName(), guid));
}

Expand All @@ -214,8 +214,10 @@ public List<EntityInstance> deleteInstance(EntityInstance anInstance) {

if (!instances.containsValue(anInstance)) {
throw new IndexOutOfBoundsException(
String.format("Could not find a %s with Internal GUID %s",
definition.getName(), anInstance.getInternalId()));
String.format("Unable to delete, could not find a %s with %s of %s",
definition.getName(),
definition.getPrimaryKeyField().getName(),
anInstance.getPrimaryKeyValue()));
}

instances.remove(anInstance.getInternalId());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ public InstanceFields addAutoIncrementIdsToInstance(AutoIncrement anAuto) {
for(Field aField : idfields){
if(aField.getType()==FieldType.AUTO_INCREMENT){
if(!values.containsKey(aField.getName().toLowerCase())) {
addValue(FieldValue.is(aField, String.valueOf(anAuto.getCurrentValue())));
anAuto.update();
addValue(
FieldValue.is(aField,
String.valueOf(anAuto.getNextValueAndUpdate())));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,38 +19,37 @@ public void canAutoIncrement(){
@Test
public void canUpdateAutoIncrement(){
AutoIncrement auto = new AutoIncrement("afield", 1);

auto.update();
Assertions.assertEquals(2, auto.getCurrentValue());
Assertions.assertEquals(1, auto.getNextValueAndUpdate());
Assertions.assertEquals(2, auto.getNextValueAndUpdate());
}

@Test
public void canUpdateAutoIncrementMultipleTimes(){
AutoIncrement auto = new AutoIncrement("afield", 1);

auto.update();
auto.update();
auto.update();
Assertions.assertEquals(4, auto.getCurrentValue());
Assertions.assertEquals(1, auto.getNextValueAndUpdate());
Assertions.assertEquals(2, auto.getNextValueAndUpdate());
Assertions.assertEquals(3, auto.getNextValueAndUpdate());
Assertions.assertEquals(4, auto.getNextValueAndUpdate());
}

@Test
public void canUpdateAutoIncrementInJumps(){
AutoIncrement auto = new AutoIncrement("afield", 1);
auto.by(10);

auto.update();
Assertions.assertEquals(11, auto.getCurrentValue());
Assertions.assertEquals(1, auto.getNextValueAndUpdate());
Assertions.assertEquals(11, auto.getNextValueAndUpdate());
}

@Test
public void canUpdateAutoIncrementInMultipleJumps(){
AutoIncrement auto = new AutoIncrement("afield", 1);
auto.by(10);
auto.by(5);

auto.update();
auto.update();
auto.update();
Assertions.assertEquals(31, auto.getCurrentValue());
Assertions.assertEquals(1, auto.getNextValueAndUpdate());
Assertions.assertEquals(6, auto.getNextValueAndUpdate());
Assertions.assertEquals(11, auto.getNextValueAndUpdate());
Assertions.assertEquals(16, auto.getNextValueAndUpdate());
}
}

0 comments on commit fc4c788

Please sign in to comment.