mirror of
https://github.com/suitenumerique/drive.git
synced 2026-09-23 18:15:15 +02:00
We need to measure backend response times and error codes from 100 up to 100k simultaneous users, on any instance. The scenario simulates a full user session (browse, recents, shared-with-me, folder creation, upload, trash, hard-delete) with randomized think times, using the e2e auth endpoint instead of the IDP, out of scope like the WOPI editors. Everything is property-driven (BASE_URL, USERS, USER_OFFSET...) so the same plan runs unchanged against any instance and across distributed injectors, each with its own USER_OFFSET to keep generated user emails globally unique. Uploads reproduce the frontend flow (item creation, presigned PUT to the object storage, upload-ended) and randomly pick a fixture entry; folder entries mirror the frontend folder upload by materializing the hierarchy before uploading each file into its parent.
74 lines
2.7 KiB
Groovy
74 lines
2.7 KiB
Groovy
// Picks a random entry (file or folder) from the fixtures directory and
|
|
// flattens it into JMeter variables consumed by the two ForEach controllers
|
|
// of the "04 upload entry" transaction:
|
|
// updir_1..N - folder relative paths, parents always before children
|
|
// upfile_1..M - files as "relative dir" + SEP + "absolute path" + SEP + "name"
|
|
// This mirrors the frontend folder upload (useUpload.tsx): materialize the
|
|
// folder hierarchy first, then upload each file into its created parent.
|
|
// This sampler performs no HTTP request and is excluded from the results.
|
|
import java.util.concurrent.ThreadLocalRandom
|
|
import org.apache.jmeter.services.FileServer
|
|
|
|
SampleResult.setIgnore()
|
|
|
|
def fixturesPath = props.getProperty("FIXTURES_DIR", "files")
|
|
def fixtures = new File(fixturesPath)
|
|
if (!fixtures.isAbsolute()) {
|
|
fixtures = new File(FileServer.getFileServer().getBaseDir(), fixturesPath)
|
|
}
|
|
def entries = (fixtures.listFiles() ?: new File[0])
|
|
.findAll { !it.name.startsWith(".") }
|
|
.sort { it.name }
|
|
if (entries.isEmpty()) {
|
|
throw new IllegalStateException("No fixture entries found in " + fixtures.absolutePath)
|
|
}
|
|
|
|
// Clear the plan of the previous iteration.
|
|
["updir_", "upfile_"].each { prefix ->
|
|
def i = 1
|
|
while (vars.get(prefix + i) != null) {
|
|
vars.remove(prefix + i)
|
|
i++
|
|
}
|
|
}
|
|
|
|
// Seed mode (opt-in, used by the read-heavy scenario): on the first
|
|
// iteration of each thread, pick a folder entry deterministically so every
|
|
// user starts the run with a navigable tree to read from. Scenarios that do
|
|
// not define the seed_first_iteration variable keep the random pick.
|
|
def entry
|
|
if ("true" == vars.get("seed_first_iteration") && vars.getIteration() == 1) {
|
|
entry = entries.find { it.isDirectory() } ?: entries[0]
|
|
} else {
|
|
entry = entries[ThreadLocalRandom.current().nextInt(entries.size())]
|
|
}
|
|
// Unit separator: cannot appear in file names, unlike "|" or ",".
|
|
def SEP = "\u001F"
|
|
def dirs = []
|
|
def files = []
|
|
|
|
def collect
|
|
collect = { File node, String rel ->
|
|
if (node.isDirectory()) {
|
|
dirs << rel
|
|
(node.listFiles() ?: new File[0])
|
|
.findAll { !it.name.startsWith(".") }
|
|
.sort { it.name }
|
|
.each { child -> collect(child, rel + "/" + child.name) }
|
|
} else {
|
|
def relDir = rel.substring(0, rel.lastIndexOf("/"))
|
|
files << [relDir, node.absolutePath, node.name].join(SEP)
|
|
}
|
|
}
|
|
|
|
if (entry.isDirectory()) {
|
|
collect(entry, entry.name)
|
|
} else {
|
|
files << ["", entry.absolutePath, entry.name].join(SEP)
|
|
}
|
|
|
|
dirs.eachWithIndex { d, i -> vars.put("updir_" + (i + 1), d) }
|
|
files.eachWithIndex { f, i -> vars.put("upfile_" + (i + 1), f) }
|
|
vars.put("upload_entry", entry.name)
|
|
log.info("upload plan: entry=${entry.name} dirs=${dirs.size()} files=${files.size()}")
|