Overhead testing foundations (#3627)

* Skeleton for overhead testing subproject.

* clone into base image instead of submodule

* remove submodules file (even though it was empty)

* set up a junit-based test running project that leverages testcontianers.

* remove builder

* remove builder

* cleanup

* repackage

* update base image url

* leverage an agent object

* add optional jvmargs to the Agent

* cleanup and handle file urls

* allow overwrite

* cleanup

* simplify writing

* simplify writing

* remove dead comment

* pass url
This commit is contained in:
jason plumb 2021-07-27 12:55:37 -07:00 committed by GitHub
parent 8c6adb481a
commit e7a940ae7e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 1064 additions and 0 deletions

View File

@ -0,0 +1,22 @@
plugins {
id("java")
}
repositories {
mavenCentral()
}
dependencies {
testImplementation("org.testcontainers:testcontainers:1.15.3")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.7.2")
testImplementation("org.junit.jupiter:junit-jupiter-params:5.7.2")
testImplementation("com.squareup.okhttp3:okhttp:4.9.1")
testImplementation("org.jooq:joox:1.6.2")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.7.2")
}
tasks {
test {
useJUnitPlatform()
}
}

Binary file not shown.

View File

@ -0,0 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-bin.zip
distributionSha256Sum=2debee19271e1b82c6e41137d78e44e6e841035230a1a169ca47fd3fb09ed87b
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

185
testing-overhead/gradlew vendored Executable file
View File

@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# 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.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

View File

@ -0,0 +1,69 @@
import http from "k6/http";
import { check } from "k6";
import names from "./names.js";
const baseUri = `http://petclinic:9966/petclinic/api`;
export default function() {
const specialtiesUrl = `${baseUri}/specialties`;
const specialtiesResponse = http.get(specialtiesUrl);
const specialties = JSON.parse(specialtiesResponse.body);
// Add a new vet to the list
const newVet = names.randomVet(specialties);
const response = http.post(`${baseUri}/vets`, JSON.stringify(newVet),
{ headers: { 'Content-Type': 'application/json' } });
// we don't guard against dupes, so this could fail on occasion
check(response, { "create vet status 201": (r) => r.status === 201 });
// make sure we can fetch that vet back out
const vetId = JSON.parse(response.body).id;
const vetUrl = `${baseUri}/vets/${vetId}`
const vetResponse = http.get(vetUrl);
check(vetResponse, { "fetch vet status 200": r => r.status === 200 });
// add a new owner
const newOwner = names.randomOwner();
const newOwnerResponse = http.post(`${baseUri}/owners`, JSON.stringify(newOwner),
{ headers: { 'Content-Type': 'application/json' } });
check(newOwnerResponse, { "new owner status 201": r => r.status === 201});
// make sure we can fetch that owner back out
const ownerId = JSON.parse(newOwnerResponse.body).id;
const ownerResponse = http.get(`${baseUri}/owners/${ownerId}`);
check(ownerResponse, { "fetch new owner status 200": r => r.status === 200});
const owner = JSON.parse(ownerResponse.body);
// get the list of all pet types
const petTypes = JSON.parse(http.get(`${baseUri}/pettypes`).body);
const owners = JSON.parse(http.get(`${baseUri}/owners`).body);
// create a 3 new random pets
const pet1 = names.randomPet(petTypes, owner);
const pet2 = names.randomPet(petTypes, owner);
const pet3 = names.randomPet(petTypes, owner);
const petsUrl = `${baseUri}/pets`;
const newPetResponses = http.batch([
["POST", petsUrl, JSON.stringify(pet1), { headers: { 'Content-Type': 'application/json' } } ],
["POST", petsUrl, JSON.stringify(pet2), { headers: { 'Content-Type': 'application/json' } } ],
["POST", petsUrl, JSON.stringify(pet3), { headers: { 'Content-Type': 'application/json' } } ],
]);
check(newPetResponses[0], { "pet status 201": r => r.status === 201});
check(newPetResponses[1], { "pet status 201": r => r.status === 201});
check(newPetResponses[2], { "pet status 201": r => r.status === 201});
const responses = http.batch([
["GET", `${baseUri}/pets/${JSON.parse(newPetResponses[0].body).id}`],
["GET", `${baseUri}/pets/${JSON.parse(newPetResponses[1].body).id}`],
["GET", `${baseUri}/pets/${JSON.parse(newPetResponses[2].body).id}`]
]);
check(responses[0], { "pet exists 200": r => r.status === 200});
check(responses[1], { "pet exists 200": r => r.status === 200});
check(responses[2], { "pet exists 200": r => r.status === 200});
//TODO: Set up a visit or two
//TODO: Fetch out the owner again because their model has been updated.
};

View File

@ -0,0 +1,147 @@
// curl 'https://data.muni.org/api/views/a9a7-y93v/rows.json?accessType=DOWNLOAD' | jq '.data | .[] | .[8]' | shuf | head -200
const petNames = [
"MARIE", "TELLY", "SLATER", "FINNIGAN", "KANU", "BILLY", "JACKIE", "ASUNA", "HUSTLER", "68 WHISKEY", "FISHBONES",
"SCHNITZEL", "DROOPY", "DANIKIS", "BLACK JACK", "BUBBLEZ", "ZORBA", "RAYNIE MAE", "Q", "ARTEMIS", "SPEEDY", "KRAZEE",
"BADGER", "AVERY", "SINA", "CASSI", "DANIELLE", "BRUCE WAYNE", "J.C.", "ZUNI", "KNOX", "D.O.G.", "HERSHEY", "YARROW",
"DAISEY", "GODIVA", "PHINNEAS", "ROMAC", "WILLIS", "LANI", "SQUALL", "LOU", "PEPE LE PEAU", "CHICKADEE", "JIMMY",
"APPLE", "ROXIE", "DEUCE", "GRIFFEN", "VALENTINO", "RASCAL", "DARCIE", "SAMMI", "B.B.", "FINN", "SHILOH", "ROCKY BLUE",
"CHUY", "MOANA", "LILA BLUEBEL", "PEASLEY", "LAIKA", "INDY", "MAXI", "MELISSA", "WALDO V", "SHASHA", "TAITTI",
"KOTA", "LENNY", "JEZABELLE", "GRUMBLE", "GINNY", "ROSIE", "CODY", "NILLA", "BUTCH", "AJAX", "BELLADONA", "CASH",
"LUKKA", "NERO", "SHERMAN", "LOKI", "BELL A DONNA", "MCGYVER", "MAXIMILLIAN", "MOCA", "LADY", "TATIA", "WARD",
"PESKY", "DELILAH", "SYRAH", "FARRAH", "SHOOTER", "MULDER", "GOOBER", "IRA", "HOOCH", "DENEKI 4", "MUSHU", "SHELDON", "TUBBY",
"BITZER", "MARKER", "KRASH", "SPIRIT", "HERA", "LUCY LU", "BLUE", "STORMY", "NAVI", "MARLI", "CHOCHO", "DYLAN",
"TOBY", "BRYCE", "TIGGER", "MARY ANN", "RIKU", "PHOENIX", "MEKA", "CUDDY", "SABRINA", "DAYTON", "DAKODA", "OGUN",
"SOLAR", "RAJ", "DIESEL", "RIPLEY 2", "CARLIE", "ROSEY", "SWISS", "KILLER", "KENLEY", "FEENA", "PIGLET",
"KEYONA", "CHEWIE", "TEINE", "CORWIN", "FLOPPER", "ADELAIDE", "HUNNY", "GEDEON", "BEE", "TALACHULITNA", "PATTON", "KLOEE",
"TRANGO", "IZZI", "PEPPERMINT", "TILLAMOOK", "QI", "KEESTER BOB", "KAINANI", "ELLERA", "RACHEL", "DUKE", "MAZEL", "PRINCE FREDR",
"KATE", "GHANDI", "DREAM", "NINA INDIANA", "JD", "ZIESA", "SAVAGE", "WRANGLER", "MOZZIE", "OSA", "BRODY JOE", "PI", "TOBO JET",
"KHERNAL", "HOPS", "CHEWEY", "KODIAK", "BARTLETT", "MACKENZIE", "RILEY", "GORDY", "SARGENT", "DIVER", "ATTU", "KETEL",
"MIKKI", "FOSBURY", "BESSA", "DESHKA", "WINCHESTER", "ZOEY MARIE", "AUSTIN", "DJANGO", "KINGSTON", "BLAZE", "SABBATH",
"SPRUCE",
];
// curl https://raw.githubusercontent.com/aruljohn/popular-baby-names/master/2017/boy_names_2017.json | jq '.names | .[]' | shuf | head -100 | sed -e "s/$/,/"
const firstNames = [
"Finley", "Jade", "Kylee", "Kensley", "Kira", "Brenna", "Braelyn", "Marley", "Phoenix", "Raegan", "Phoebe", "Kaya",
"Khloe", "Angelique", "Kiana", "Magdalena", "Allison", "Anahi", "Elisabeth", "Juliet", "Miriam", "Elliott",
"Josie", "Brenda", "Hadassah", "Kathryn", "Nadia", "Cataleya", "Lilah", "Yamileth", "Alayna", "Rowan", "Andi",
"Ember", "Gwendolyn", "Jacqueline", "Cecelia", "Kimora", "Lindsey", "Opal", "Lyric", "Dakota", "Jennifer", "Dorothy", "Nia",
"Gabriela", "Mariana", "Haley", "Maliah", "Kelly", "Sloane", "Danica", "Stevie", "Erica", "Jaycee", "Aliya", "Marissa", "Elise",
"Mila", "Oakley", "Adelaide", "Maci", "Dayana", "Aniyah", "Estrella", "Scarlette", "Joy", "Raina", "Sarai", "Luella", "Averi",
"Rebecca", "Eloise", "June", "Hadleigh", "Lena", "Natasha", "Brynn", "Alexis", "Summer", "Clarissa", "Katelyn", "Alexa", "Louise",
"Marianna", "Elsa", "Kehlani", "Tessa", "Katie", "Paisley", "Jasmine", "Brooklyn", "Aubree", "Averie", "Quinn", "Chloe",
"Paula", "Kassidy", "Alessia", "Juliette", "Aydin", "Lucian", "Bronson", "Ryder", "Felix", "Major", "Darius", "Tony", "Junior",
"William", "Johnny", "Patrick", "Raiden", "Nikolas", "Phillip", "Nickolas", "Markus", "Vicente", "Kody", "Rory", "Julian", "Corbin",
"Benjamin", "Dexter", "Emilio", "Jesse", "Romeo", "August", "Gerald", "Gary", "Terrence", "Troy", "Axl", "Ronnie",
"Eugene", "Foster", "Matthias", "Maurice", "Wells", "Kyler", "Leland", "Elian", "Shaun", "Callan", "Samson", "Hector", "Curtis",
"Allan", "Jaxton", "Edgar", "Jonah", "Raul", "Lawrence", "Jude", "Jamison", "Kaysen", "Colten", "Alan", "Tripp", "Aarav",
"Xander", "Landen", "Caden", "Garrett", "Marcelo", "Sean", "Christian", "Reed", "Vaughn", "Kylen", "Ace",
"Kole", "Kristian", "Francis", "Brysen", "Josiah", "Riley", "Brantley", "Colby", "Sonny", "Braydon", "Conrad",
"Michael", "Trent", "Andres", "Billy", "Luka", "Stanley", "Alden", "Victor", "Axton", "Jamari", "Henry", "Jadiel", "Elliott",
"Kristopher", "Kyrie", "Ruben", "Ahmed", "Theodore"
];
// $ curl https://raw.githubusercontent.com/rossgoodwin/american-names/master/surnames.json | jq | shuf | tail -25
const surnames = [
"Coomes", "Kasputis", "Eing", "Budro", "Paszkiewicz", "Reichwald", "Mennona", "Esplin", "Trute", "Endlich",
"Kaman", "Coody", "Urish", "Styes", "Balles", "Semanek", "Tes", "Mediano", "Clave", "Beliard", "Christianson",
"Doy", "Bozman", "Waligura", "Templeman", "Gershenson", "Eckberg", "Harader", "Baurer", "Villao", "Decius",
"Marquardt", "Smaha", "Grzych", "Getto", "Wilberger", "Fleites", "Spoerl", "Oliger", "Gramza", "Prillaman",
"Beinlich", "Marzella", "Bota", "Arguilez", "Piotti", "Karri", "Spiropoulos", "Gambhir", "Franchak"
];
// source https://github.com/baliw/words/blob/master/adjectives.json
const adj = ["Ablaze", "Abrupt", "Accomplished", "Active", "Adored", "Adulated", "Adventurous", "Affectionate", "Amused", "Amusing",
"Animal-like", "Antique", "Appreciated", "Archaic", "Ardent", "Arrogant", "Astonished", "Audacious", "Authoritative",
"Awestruck", "Beaming", "Bewildered", "Bewitching", "Blissful", "Boisterous", "Booming", "Bouncy", "Breathtaking",
"Bright", "Brilliant", "Bubbling", "Calm", "Calming", "Capricious", "Celestial", "Charming", "Cheerful", "Cherished",
"Chiaroscuro", "Chilled", "Comical", "Commanding", "Companionable", "Confident", "Contentment", "Courage", "Crazy",
"Creepy", "Dancing", "Dazzling", "Delicate", "Delightful", "Demented", "Desirable", "Determined", "Devoted", "Dominant",
"Dramatic", "Drawn out", "Dripping", "Dumbstruck", "Ebullient", "Elated", "Elegant", "Enchanted", "Energetic",
"Enthusiastic", "Ethereal", "Exaggerated", "Exalted", "Expectant", "Expressive", "Exuberant", "Faint", "Fantastical",
"Favorable", "Febrile", "Feral", "Feverish", "Fiery", "Floating", "Flying", "Folksy", "Fond", "Forgiven", "Forgiving",
"Freakin' awesome", "Frenetic", "Frenzied", "Friendly", "Amorous", "From a distance", "Frosted", "Funny", "Furry",
"Galloping", "Gaping", "Gentle", "Giddy", "Glacial", "Gladness", "Gleaming", "Gleeful", "Gorgeous", "Graceful",
"Grateful", "Halting", "Happy", "Haunting", "Heavenly", "Hidden", "High-spirited", "Honor", "Hopeful", "Hopping",
"Humble", "Hushed", "Hypnotic", "Illuminated", "Immense", "Imperious", "Impudent", "In charge", "Inflated", "Innocent",
"Inspired", "Intimate", "Intrepid", "Jagged", "Joking", "Joyful", "Jubilant", "Kindly", "Languid", "Larger than life",
"Laughable", "Lickety-split", "Lighthearted", "Limping", "Linear", "Lively", "Lofty", "Love of", "Lovely", "Lulling",
"Luminescent", "Lush", "Luxurious", "Magical", "Maniacal", "Manliness", "March-like", "Masterful", "Merciful", "Merry",
"Mischievous", "Misty", "Modest", "Moonlit", "Mysterious", "Mystical", "Mythological", "Nebulous", "Nostalgic", "Onfire",
"Overstated", "Paganish", "Partying", "Perfunctory", "Perky", "Perplexed", "Persevering", "Pious", "Playful",
"Pleasurable", "Poignant", "Portentous", "Posh", "Powerful", "Pretty", "Prickly", "Prideful", "Princesslike", "Proud",
"Puzzled", "Queenly", "Questing", "Quiet", "Racy", "Ragged", "Regal", "Rejoicing", "Relaxed", "Reminiscent",
"Repentant", "Reserved", "Resolute", "Ridiculous", "Ritualistic", "Robust", "Running", "Sarcastic", "Scampering",
"Scoffing", "Scurrying", "Sensitive", "Serene", "Shaking", "Shining", "Silky", "Silly", "Simple", "Singing", "Skipping",
"Smooth", "Sneaky", "Soaring", "Sophisticated", "Sparkling", "Spell-like", "Spherical", "Spidery", "Splashing",
"Splendid", "Spooky", "Sprinting", "Starlit", "Starry", "Startling", "Successful", "Summery", "Surprised",
"Sympathetic", "Tapping", "Teasing", "Tender", "Thoughtful", "Thrilling", "Tingling", "Tinkling", "Tongue-in-cheek",
"Totemic", "Touching", "Tranquil", "Treasured", "Trembling", "Triumphant", "Twinkling", "Undulating", "Unruly",
"Urgent", "Veiled", "Velvety", "Victorious", "Vigorous", "Virile", "Walking", "Wild", "Witty", "Wondering", "Zealous",
"Zestful"
];
function randItem(list){
return list[rand(list.length)];
}
function rand(max){
return Math.floor(Math.random() * Math.floor(max))
}
function randomVet(specialties) {
const first = randItem(firstNames);
const last = randItem(adj);
const numSpec = rand(specialties.length);
const spec = [...Array(numSpec).keys()].map(n => randItem(specialties))
return {
firstName: first,
lastName: last,
specialties: spec
};
}
function randomOwner(){
const firstName = randItem(firstNames);
const lastName = randItem(surnames);
return {
"firstName": firstName,
"lastName": lastName,
"address": `${rand(10000)} ${randItem(adj)} Ln.`,
"city": "Anytown",
"telephone": "8005551212",
"pets": []
};
}
function randomPet(types, owner) {
const birthDate = "2020/12/31";
const petName = ((rand(100) > 50) ? "" : `${randItem(adj)} `) + randItem(petNames);
const typeId = randItem(types).id;
return {
"birthDate": birthDate,
"id": 0, // one will be chosen for us
"name": petName,
"owner": {
"id": owner.id,
"firstName": null,
"lastName": "",
"address": "",
"city": "",
"telephone": ""
},
"type": {
"id": typeId
},
"visits": []
}
}
export default {
randomVet,
randomOwner,
randomPet
};

View File

@ -0,0 +1 @@
rootProject.name = "opentelemetry-agent-overhead-test"

View File

@ -0,0 +1,74 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
import io.opentelemetry.agents.Agent;
import io.opentelemetry.config.Configs;
import io.opentelemetry.config.TestConfig;
import io.opentelemetry.containers.CollectorContainer;
import io.opentelemetry.containers.K6Container;
import io.opentelemetry.containers.PetClinicRestContainer;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network;
public class OverheadTests {
private static final Network NETWORK = Network.newNetwork();
private static GenericContainer<?> collector;
@BeforeAll
static void setUp() {
collector = CollectorContainer.build(NETWORK);
collector.start();
}
@AfterAll
static void tearDown() {
collector.close();
}
@TestFactory
Stream<DynamicTest> runAllTestConfigurations() {
return Configs.all().map(config ->
dynamicTest(config.getName(), () -> runTestConfig(config))
);
}
void runTestConfig(TestConfig config) {
config.getAgents().forEach(agent -> {
try {
runAppOnce(config, agent);
} catch (Exception e) {
fail("Unhandled exception in " + config.getName(), e);
}
});
}
void runAppOnce(TestConfig config, Agent agent) throws Exception {
GenericContainer<?> petclinic = new PetClinicRestContainer(NETWORK, collector, agent).build();
petclinic.start();
GenericContainer<?> k6 = new K6Container(NETWORK, agent, config).build();
k6.start();
// This is required to get a graceful exit of the VM before testcontainers kills it forcibly.
// Without it, our jfr file will be empty.
petclinic.execInContainer("kill", "1");
while (petclinic.isRunning()) {
TimeUnit.MILLISECONDS.sleep(500);
}
//TODO: Parse and aggregate the test results.
}
}

View File

@ -0,0 +1,66 @@
package io.opentelemetry.agents;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Agent {
final static String OTEL_LATEST = "https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent-all.jar";
public final static Agent NONE = new Agent("none", "no agent at all");
public final static Agent LATEST_RELEASE = new Agent("latest", "latest mainstream release", OTEL_LATEST);
public final static Agent LATEST_SNAPSHOT = new Agent("snapshot", "latest available snapshot version from main");
private final String name;
private final String description;
private final URL url;
private final List<String> additionalJvmArgs;
public Agent(String name, String description) {
this(name, description, null);
}
public Agent(String name, String description, String url) {
this(name, description, url, Collections.emptyList());
}
public Agent(String name, String description, String url, List<String> additionalJvmArgs) {
this.name = name;
this.description = description;
this.url = makeUrl(url);
this.additionalJvmArgs = new ArrayList<>(additionalJvmArgs);
}
public String getName() {
return name;
}
public String getDescription() {
return description;
}
public boolean hasUrl(){
return url != null;
}
public URL getUrl() {
return url;
}
public List<String> getAdditionalJvmArgs() {
return Collections.unmodifiableList(additionalJvmArgs);
}
private static URL makeUrl(String url) {
try {
if(url == null) return null;
return URI.create(url).toURL();
} catch (MalformedURLException e) {
throw new RuntimeException("Error parsing url", e);
}
}
}

View File

@ -0,0 +1,50 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.agents;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.util.Optional;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class AgentResolver {
private final LatestAgentSnapshotResolver snapshotResolver = new LatestAgentSnapshotResolver();
public Optional<Path> resolve(Agent agent) throws Exception {
if(Agent.NONE.equals(agent)){
return Optional.empty();
}
if(Agent.LATEST_SNAPSHOT.equals(agent)){
return snapshotResolver.resolve();
}
if(agent.hasUrl()){
return Optional.of(downloadAgent(agent.getUrl()));
}
throw new IllegalArgumentException("Unknown agent: " + agent);
}
private Path downloadAgent(URL agentUrl) throws Exception {
if(agentUrl.getProtocol().equals("file")){
Path source = Path.of(agentUrl.toURI());
Path result = Paths.get(".", source.getFileName().toString());
Files.copy(source, result, StandardCopyOption.REPLACE_EXISTING);
return result;
}
Request request = new Request.Builder().url(agentUrl).build();
OkHttpClient client = new OkHttpClient();
Response response = client.newCall(request).execute();
byte[] raw = response.body().bytes();
Path path = Paths.get(".", "opentelemetry-javaagent-all.jar");
Files.write(path, raw, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING);
return path;
}
}

View File

@ -0,0 +1,84 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.agents;
import static org.joox.JOOX.$;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Optional;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import org.joox.Match;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
public class LatestAgentSnapshotResolver {
private static final Logger logger = LoggerFactory.getLogger(LatestAgentSnapshotResolver.class);
final static String BASE_URL = "https://oss.sonatype.org/content/repositories/snapshots/io/opentelemetry/javaagent/opentelemetry-javaagent";
final static String LATEST_SNAPSHOT_META = BASE_URL + "/maven-metadata.xml";
Optional<Path> resolve() throws IOException {
String version = fetchLatestSnapshotVersion();
logger.info("Latest snapshot version is {}", version);
String latestFilename = fetchLatestFilename(version);
String url = BASE_URL + "/" + version + "/" + latestFilename;
byte[] jarBytes = fetchBodyBytesFrom(url);
Path path = Paths.get(".", "opentelemetry-javaagent-SNAPSHOT-all.jar");
Files.write(path, jarBytes, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING);
return Optional.of(path);
}
private String fetchLatestFilename(String version) throws IOException {
String url = BASE_URL + "/" + version + "/maven-metadata.xml";
String body = fetchBodyStringFrom(url);
Document document = $(body).document();
Match match = $(document).xpath("/metadata/versioning/snapshotVersions/snapshotVersion");
return match.get().stream()
.filter(elem -> {
String classifier = $(elem).child("classifier").content();
String extension = $(elem).child("extension").content();
return "all".equals(classifier) && "jar".equals(extension);
})
.map(e -> $(e).child("value").content())
.findFirst()
.map(value -> "opentelemetry-javaagent-" + value + "-all.jar")
.orElseThrow();
}
private String fetchLatestSnapshotVersion() throws IOException {
String url = LATEST_SNAPSHOT_META;
String body = fetchBodyStringFrom(url);
Document document = $(body).document();
Match match = $(document).xpath("/metadata/versioning/latest");
return match.get(0).getTextContent();
}
private String fetchBodyStringFrom(String url) throws IOException {
return fetchBodyFrom(url).string();
}
private byte[] fetchBodyBytesFrom(String url) throws IOException {
return fetchBodyFrom(url).bytes();
}
private ResponseBody fetchBodyFrom(String url) throws IOException {
Request request = new Request.Builder().url(url).build();
OkHttpClient client = new OkHttpClient();
Response response = client.newCall(request).execute();
ResponseBody body = response.body();
return body;
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.config;
import io.opentelemetry.agents.Agent;
import java.util.Arrays;
import java.util.stream.Stream;
/**
* Defines all test configurations
*/
public enum Configs {
RELEASE(TestConfig.builder()
.name("release")
.description("compares the latest stable release to no agent")
.withAgents(Agent.NONE, Agent.LATEST_RELEASE)
.build()
),
SNAPSHOT(TestConfig.builder()
.name("snapshot")
.description("compares the latest snapshot to no agent")
.withAgents(Agent.NONE, Agent.LATEST_SNAPSHOT)
.build()
),
SNAPSHOT_REGRESSION(TestConfig.builder()
.name("snapshot-regression")
.description("compares the latest snapshot to the latest stable release")
.withAgents(Agent.LATEST_RELEASE, Agent.LATEST_SNAPSHOT)
.build()
)
;
public final TestConfig config;
public static Stream<TestConfig> all(){
return Arrays.stream(Configs.values()).map(x -> x.config);
}
Configs(TestConfig config) {
this.config = config;
}
}

View File

@ -0,0 +1,108 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.config;
import io.opentelemetry.agents.Agent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* Defines a test config.
*/
public class TestConfig {
private final static int DEFAULT_MAX_REQUEST_RATE = 0; // none
private final static int DEFAULT_CONCURRENT_CONNECTIONS = 5;
private final static int DEFAULT_TOTAL_ITERATIONS = 500;
private final String name;
private final String description;
private final List<Agent> agents;
private final int maxRequestRate;
private final int concurrentConnections;
private final int totalIterations;
public TestConfig(Builder builder) {
this.name = builder.name;
this.description = builder.description;
this.agents = Collections.unmodifiableList(builder.agents);
this.maxRequestRate = builder.maxRequestRate;
this.concurrentConnections = builder.concurrentConnections;
this.totalIterations = builder.totalIterations;
}
public String getName() {
return name;
}
public String getDescription() {
return description;
}
public List<Agent> getAgents() {
return Collections.unmodifiableList(agents);
}
public int getMaxRequestRate() {
return maxRequestRate;
}
public int getConcurrentConnections() {
return concurrentConnections;
}
public int getTotalIterations() {
return totalIterations;
}
static Builder builder() {
return new Builder();
}
static class Builder {
private String name;
private String description;
private List<Agent> agents = new ArrayList<>();
private int maxRequestRate = DEFAULT_MAX_REQUEST_RATE;
private int concurrentConnections = DEFAULT_CONCURRENT_CONNECTIONS;
private int totalIterations = DEFAULT_TOTAL_ITERATIONS;
Builder name(String name) {
this.name = name;
return this;
}
Builder description(String description) {
this.description = description;
return this;
}
Builder withAgents(Agent ...agents) {
this.agents.addAll(Arrays.asList(agents));
return this;
}
Builder maxRequestRate(int maxRequestRate) {
this.maxRequestRate = maxRequestRate;
return this;
}
Builder concurrentConnections(int concurrentConnections) {
this.concurrentConnections = concurrentConnections;
return this;
}
Builder totalIterations(int totalIterations) {
this.totalIterations = totalIterations;
return this;
}
TestConfig build(){
return new TestConfig(this);
}
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.containers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.containers.output.Slf4jLogConsumer;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.utility.DockerImageName;
import org.testcontainers.utility.MountableFile;
public class CollectorContainer {
static final int COLLECTOR_PORT = 4317;
static final int COLLECTOR_HEALTH_CHECK_PORT = 13133;
private static final Logger logger = LoggerFactory.getLogger(CollectorContainer.class);
public static GenericContainer<?> build(Network network) {
return new GenericContainer<>(
DockerImageName.parse("otel/opentelemetry-collector-contrib:latest"))
.withNetwork(network)
.withNetworkAliases("collector")
.withLogConsumer(new Slf4jLogConsumer(logger))
.withExposedPorts(COLLECTOR_PORT, COLLECTOR_HEALTH_CHECK_PORT)
.waitingFor(Wait.forHttp("/health").forPort(COLLECTOR_HEALTH_CHECK_PORT))
.withCopyFileToContainer(
MountableFile.forClasspathResource("collector.yaml"), "/etc/otel.yaml")
.withCommand("--config /etc/otel.yaml");
}
}

View File

@ -0,0 +1,53 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.containers;
import io.opentelemetry.agents.Agent;
import io.opentelemetry.config.TestConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.containers.output.Slf4jLogConsumer;
import org.testcontainers.containers.startupcheck.OneShotStartupCheckStrategy;
import org.testcontainers.utility.DockerImageName;
import org.testcontainers.utility.MountableFile;
import java.time.Duration;
public class K6Container {
private static final Logger logger = LoggerFactory.getLogger(K6Container.class);
private final Network network;
private final Agent agent;
private final TestConfig config;
public K6Container(Network network, Agent agent, TestConfig config) {
this.network = network;
this.agent = agent;
this.config = config;
}
public GenericContainer<?> build(){
String k6OutputFile = "/results/k6_out_" + agent + ".json";
return new GenericContainer<>(
DockerImageName.parse("loadimpact/k6"))
.withNetwork(network)
.withNetworkAliases("k6")
.withLogConsumer(new Slf4jLogConsumer(logger))
.withCopyFileToContainer(
MountableFile.forHostPath("./k6"), "/app")
.withFileSystemBind(".", "/results")
.withCommand(
"run",
"-u", String.valueOf(config.getConcurrentConnections()),
"-i", String.valueOf(config.getTotalIterations()),
"--rps", String.valueOf(config.getMaxRequestRate()),
"--summary-export", k6OutputFile,
"/app/basic.js"
)
.withStartupCheckStrategy(
new OneShotStartupCheckStrategy().withTimeout(Duration.ofMinutes(5))
);
}
}

View File

@ -0,0 +1,85 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.containers;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import io.opentelemetry.agents.Agent;
import io.opentelemetry.agents.AgentResolver;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.containers.output.Slf4jLogConsumer;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.lifecycle.Startable;
import org.testcontainers.utility.DockerImageName;
import org.testcontainers.utility.MountableFile;
public class PetClinicRestContainer {
private static final Logger logger = LoggerFactory.getLogger(PetClinicRestContainer.class);
private static final int PETCLINIC_PORT = 9966;
private final AgentResolver agentResolver = new AgentResolver();
private final Network network;
private final Startable collector;
private final Agent agent;
public PetClinicRestContainer(Network network, Startable collector, Agent agent) {
this.network = network;
this.collector = collector;
this.agent = agent;
}
public GenericContainer<?> build() throws Exception {
Optional<Path> agentJar = agentResolver.resolve(this.agent);
GenericContainer<?> container = new GenericContainer<>(
DockerImageName.parse("ghcr.io/open-telemetry/opentelemetry-java-instrumentation/petclinic-rest-base:latest"))
.withNetwork(network)
.withNetworkAliases("petclinic")
.withLogConsumer(new Slf4jLogConsumer(logger))
.withExposedPorts(PETCLINIC_PORT)
.withFileSystemBind(".", "/results")
.waitingFor(Wait.forHttp("/petclinic/actuator/health").forPort(PETCLINIC_PORT))
.dependsOn(collector)
.withCommand(buildCommandline(agentJar));
agentJar.ifPresent(
agentPath -> container.withCopyFileToContainer(
MountableFile.forHostPath(agentPath),
"/app/" + agentPath.getFileName().toString())
);
return container;
}
@NotNull
private String[] buildCommandline(Optional<Path> agentJar) {
String jfrFile = "petclinic-" + this.agent.getName() + ".jfr";
List<String> result = new ArrayList<>(Arrays.asList(
"java",
"-XX:StartFlightRecording:dumponexit=true,disk=true,settings=profile,name=petclinic,filename=/results/"
+ jfrFile,
"-Dotel.traces.exporter=otlp",
"-Dotel.imr.export.interval=5000",
"-Dotel.exporter.otlp.insecure=true",
"-Dotel.exporter.otlp.endpoint=http://collector:4317",
"-Dotel.resource.attributes=service.name=petclinic-otel-overhead"
));
result.addAll(this.agent.getAdditionalJvmArgs());
agentJar.ifPresent(path -> result.add("-javaagent:/app/" + path.getFileName()));
result.add("-jar");
result.add("/app/spring-petclinic-rest.jar");
return result.toArray(new String[] {});
}
}

View File

@ -0,0 +1,33 @@
extensions:
health_check:
receivers:
otlp:
protocols:
grpc:
processors:
batch:
exporters:
logging/logging_debug:
loglevel: debug
logging/logging_info:
loglevel: info
service:
pipelines:
traces:
receivers: [ otlp ]
processors: [ batch ]
exporters: [ logging/logging_debug ]
metrics:
receivers: [ otlp ]
processors: [ batch ]
exporters: [ logging/logging_info ]
logs:
receivers: [ otlp ]
processors: [ batch ]
exporters: [ logging/logging_debug ]
extensions: [ health_check ]