Skip to content

Instantly share code, notes, and snippets.

@ghenzler
Created December 11, 2018 20:01
Show Gist options
  • Save ghenzler/d1bcf70f44864843580894a6286c903b to your computer and use it in GitHub Desktop.
Save ghenzler/d1bcf70f44864843580894a6286c903b to your computer and use it in GitHub Desktop.
//-------------------------------------
// *** Max Nodes for Session Save
//-------------------------------------
// Breaklet Config
//-------------------------------------
def breakForDurationInSec = 60
def basePath = "/content/nodetest"
def nodesToSavePerCommitMultiplier = 1000
def primaryType = "nt:unstructured"
def sessionUser = 'admin'
def sessionPassword = 'admin'
//-------------------------------------
// Breaklet
//-------------------------------------
new Breaklet(this, breakForDurationInSec, sessionUser, sessionPassword).run { breaklet, currentIt, itSession ->
breaklet.registerResourceChangeListener(basePath)
def nodesToCreateInThisIteration = nodesToSavePerCommitMultiplier * (currentIt + 40)
for(int nodeNum=1; nodeNum<= nodesToCreateInThisIteration; nodeNum++) {
def nodeName = "node" + StringUtils.leftPad(String.valueOf(currentIt), 14, '0') + '-' + StringUtils.leftPad(String.valueOf(nodeNum), 4, '0')
def path = basePath + "/" + nodeName
def node = JcrUtils.getOrCreateByPath(path, true, primaryType, primaryType, itSession, false)
node.setProperty("randomNumber", ThreadLocalRandom.current().nextInt(0, 100))
}
breaklet.logOpFinished("Create nodes", "("+nodesToCreateInThisIteration+" nodes in total)")
itSession.save()
breaklet.logOpFinished("Save nodes")
breaklet.waitForResourceChangedEvents(nodesToCreateInThisIteration, 300, "Wait for save events")
session.removeItem(basePath)
breaklet.logOpFinished("Delete node base path")
session.save()
breaklet.logOpFinished("Save deletion")
breaklet.waitForResourceChangedEvents(1, 300, "Wait for single delete event")
}
// DO NOT CHANGE BEYOND THIS POINT - master is https://gist.github.com/ghenzler/58ce57794ba9b5a60f2a2e60160be747 here
// --------------------------------------------------------------------
// Breaklet Base Utilities (to be copied at bottom of other breaklets)
// --------------------------------------------------------------------
import org.apache.jackrabbit.commons.JcrUtils
import org.apache.commons.lang3.StringUtils
import java.util.concurrent.ThreadLocalRandom
import javax.jcr.query.*
import org.apache.sling.jcr.api.*
import org.apache.sling.api.resource.observation.*
import java.util.Properties
import java.io.ByteArrayInputStream
import java.nio.charset.StandardCharsets
import org.osgi.service.event.EventConstants
import org.osgi.service.event.EventHandler
import org.osgi.service.event.Event
import org.apache.commons.io.IOUtils
import org.apache.sling.hc.util.FormattingResultLog
class BreakletBrokeException extends RuntimeException {
String opName
String timeSpent
BreakletBrokeException(opName, timeSpent) {
this.opName = opName
this.timeSpent = timeSpent
}
}
class BreakletPanicException extends RuntimeException { }
class Breaklet {
int iterations
int breakForDurationInSec
String user
String password
long lastTimestamp = System.currentTimeMillis()
def script
long currentIteration
String currentItLogMarker
Map<String,List> timings = new LinkedHashMap<String,List>()
Browser browser
def resourceChangeListener = null
def resourceChangeListenerReg = null
def resourceChangeListenerPath = null
def resourceChangeEventHandler = null
def resourceChangeEventHandlerReg = null
def resourceChangeEventHandlerPath = null
Breaklet(def script, int breakForDurationInSec, String user=null, String password=null, int iterations=Integer.MAX_VALUE) {
this.script = script
this.breakForDurationInSec = breakForDurationInSec
this.user = user
this.password = password
this.iterations = iterations
this.browser = new Browser(script, breakForDurationInSec, user, password)
}
def logOpFinished(String opName, String opDescription="", def timeSpentMillis=null, def neverBreak=false) {
script.logMsg(StringUtils.leftPad(currentItLogMarker, 7)+" " + StringUtils.rightPad(getTimeSpent(opName, timeSpentMillis as Long, neverBreak) + ":", 8) + " "+opName + " "+opDescription)
currentItLogMarker = ""
}
def getTimeSpent(String opName, Long timeSpentMillis=null, def neverBreak=false) {
long delta = timeSpentMillis ?: System.currentTimeMillis()-lastTimestamp
def timingSeriesForOp = timings.get(opName)
if(timingSeriesForOp==null) { timings.put(opName, (timingSeriesForOp = new ArrayList<Long>())) }
while(timingSeriesForOp.size() < currentIteration-1) { timingSeriesForOp.add(timingSeriesForOp.size(), '') }
timingSeriesForOp.add((int)currentIteration-1, delta)
String timeSpent = FormattingResultLog.msHumanReadable(delta)
if((delta/1000) > breakForDurationInSec && !neverBreak) {
throw new BreakletBrokeException(opName, timeSpent)
}
lastTimestamp = System.currentTimeMillis()
return timeSpent
}
def run(toRun) {
File currentWorkingDir = new File(System.getProperty('user.dir'))
File panicFile = new File(currentWorkingDir, 'panic')
try {
(1..iterations).each { itNo ->
currentItLogMarker = itNo+":"
def sessionForIteration = null
try {
if(StringUtils.isNotBlank(user)) {
sessionForIteration = getSessionFor(user, password)
}
currentIteration = itNo
def sessionForIt = sessionForIteration ?: script.session
toRun(this, currentIteration, sessionForIt)
if(panicFile.exists()) {
panicFile.renameTo(new File(currentWorkingDir, 'no-panic'))
throw new BreakletPanicException()
}
} finally {
if(sessionForIteration!=null && sessionForIteration.isLive()) {
sessionForIteration.logout()
}
}
}
script.logMsg("\n\nBreaklet survived - "+iterations+" iterations finished without exceeding the threshold\n")
} catch(BreakletBrokeException e) {
script.logMsg("\n\nBreaklet broke at iteration "+currentIteration+": Operation '"+e.opName+"' "+e.timeSpent + " >= "+breakForDurationInSec + "sec\n")
} catch(BreakletPanicException e) {
script.logMsg("\nBreaklet broke due to panic after iteration "+currentIteration+"\n")
} catch(Exception e) {
script.logMsg("\n\nBreaklet broke at iteration "+currentIteration+" with unexpected exception: "+e)
script.log.info("Exception:"+e, e)
} finally {
String result = ""
timings.keySet().each { key ->
result += ""+key+","+ StringUtils.join(timings.get(key),',') + "\n"
}
new File(currentWorkingDir, 'breaklet-result-'+new Date().getTime()+'.csv').text = result
if(resourceChangeListenerReg) {
resourceChangeListenerReg.unregister()
}
if(resourceChangeEventHandlerReg) {
resourceChangeEventHandlerReg.unregister()
}
}
}
def runInBrowser(String puppeteerScript) {
return browser.run(puppeteerScript)
}
def getSessionFor(sessionUser, sessionPassword) {
def repository = script.session.getRepository()
def session = repository.login(new SimpleCredentials(sessionUser, sessionPassword.toCharArray()));
return session
}
def registerResourceChangeListener(paths) {
resourceChangeListenerPath = paths
resourceChangeListener = new ResourceChangeListener() {
int changesReceived = 0
int callsReceived = 0
def allChanges = []
public void onChange(List<ResourceChange> changes) {
changesReceived += changes.size()
callsReceived++
allChanges.addAll(changes)
}
public void reset() {
changesReceived = 0
callsReceived = 0
allChanges = []
}
}
resourceChangeListenerReg = script.bundleContext.registerService("org.apache.sling.api.resource.observation.ResourceChangeListener", resourceChangeListener, new Hashtable([(ResourceChangeListener.PATHS): paths]))
return resourceChangeListener
}
def registerResourceChangeEventHandler(path) {
resourceChangeEventHandlerPath = path
resourceChangeEventHandler = new EventHandler() {
int eventsReceived = 0
def allEvents = []
public void handleEvent(final Event event) {
eventsReceived++
allEvents.addAll(event)
}
public void reset() {
eventsReceived = 0
allEvents = []
}
}
resourceChangeEventHandlerReg = script.bundleContext.registerService("org.osgi.service.event.EventHandler", resourceChangeEventHandler,
new Hashtable([
(EventConstants.EVENT_TOPIC): "org/apache/sling/api/resource/Resource/*",
(EventConstants.EVENT_FILTER): "("+SlingConstants.PROPERTY_PATH+"="+path+"*)",
]))
return resourceChangeEventHandler
}
def waitForResourceChangedEvents(long minimumExpectedChanges, long maxWaitInSec=300, String opName=null) {
boolean timeout = false
int waitCounter = 0
while((resourceChangeListener && resourceChangeListener.changesReceived < minimumExpectedChanges)
|| (resourceChangeEventHandler && resourceChangeEventHandler.eventsReceived < minimumExpectedChanges)
) {
if((lastTimestamp + maxWaitInSec*1000) < System.currentTimeMillis()) {
timeout = true
break
}
Thread.sleep(50)
}
String resourceChangeListenerResult = resourceChangeListener ? " changesReceived="+resourceChangeListener.changesReceived+" (in "+resourceChangeListener.callsReceived +" calls) " : ""
String resourceChangeEventHandlerResult = resourceChangeEventHandler ? " eventsReceived="+resourceChangeEventHandler.eventsReceived : ""
opName = opName ?: "Wait for resource changed events ("+(resourceChangeListenerPath?:resourceChangeEventHandlerPath)+")"
logOpFinished(opName, (timeout?'Timed out after '+maxWaitInSec+'sec ':'') + resourceChangeListenerResult + resourceChangeEventHandlerResult)
resourceChangeListener?.reset()
resourceChangeEventHandler?.reset()
}
def performRequest(String requestPath, String reqUser=user, String reqPassword=password) {
def con = new URL(script.getBaseUrl()+requestPath).openConnection();
String encoded = Base64.getEncoder().encodeToString((reqUser+":"+reqPassword).getBytes(StandardCharsets.UTF_8));
con.setRequestProperty("Authorization", "Basic "+encoded);
def result = IOUtils.toString(con.getInputStream());
return result
}
}
class Browser {
// config
def script
String user
String password
int breakForDurationInSec
// state
File browserDir
String testJsTemplate
Properties resultProps
Browser(def script, int breakForDurationInSec, String user, String password) {
this.script = script
this.breakForDurationInSec = breakForDurationInSec
this.user = user
this.password = password
}
def init() {
browserDir = new File(System.getProperty('user.dir')+'/puppeteer')
if(!browserDir.exists()) {
script.logMsg("init "+ browserDir)
browserDir.mkdir()
new File(browserDir, 'package.json').text = """
{
"name": "puppeteer-breaklet",
"version": "1.0.0",
"scripts": {
"test": "node test.js"
},
"dependencies": {
"puppeteer": "1.7.0"
}
}
""".stripIndent()
def result = script.exec("npm install", browserDir)
script.logMsg("npm install result: "+ result)
}
testJsTemplate = '''
const puppeteer = require('puppeteer');
let baseUrl = process.argv[2]
let user = process.argv[3]
let password = process.argv[4]
let startTime = process.argv[5]
let defaultTimeout = 120*1000
let screenshotCreated = false
let loginPage = "/libs/granite/core/content/login.html"
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function getCrxdeXPath(nodeName) {
return "//*[@id='repository']//a[span/text()='"+nodeName+"']"
}
async function waitForXPath(page, xpath) {
await page.waitForXPath(xpath, { timeout: defaultTimeout})
let nodeElements = await page.$x(xpath)
return nodeElements[0]
}
async function waitForCrxdeNodeWithName(page, nodeName) {
let nodeElXPath = getCrxdeXPath(nodeName)
return await waitForXPath(page, nodeElXPath)
}
async function waitForAndClick(page, selector, retry=true) {
await page.waitForSelector(selector, { timeout: defaultTimeout, visible: true})
if (await page.$(selector) === null) {
throw new Error("Could not find " + selector + " even after waiting for " + defaultTimeout);
}
try {
await page.click(selector)
} catch(err) {
if(retry) {
console.log("Error " +err+" while clicking on selector "+selector+", retrying...")
await sleep(200)
waitForAndClick(page, selector, false);
} else {
throw err
}
}
}
async function clickOnIconOfCrxdeNode(page, nodeHandle) {
await page.evaluate(node => node.previousSibling.previousSibling.click(), nodeHandle);
}
async function createScreenshot(page, name=null) {
if(page) {
await page.screenshot({path: 'run-'+startMillis+(name ? '-'+name : '')+'.png'});
screenshotCreated = true
}
}
async function run() {
var resultProps = {};
const browser = await puppeteer.launch({headless: true, dumpio: true, args: ['--window-size=1440,900', "--no-sandbox"]});
let page = null
try {
//const context = await browser.createIncognitoBrowserContext();
page = await browser.newPage(); // context or browser
await page.setViewport({width: 1440, height: 900});
await page.goto(baseUrl + loginPage);
await page.$eval('#username', (el, pUsername) => el.value = pUsername, user);
await page.$eval('#password', (el, pPassword) => el.value = pPassword, password);
await sleep(500);
await page.$eval('#login', form => form.submit());
await page.waitForNavigation({ waitUntil: 'networkidle0'})
if(new RegExp('.*?'+loginPage+'.*').test(await page.url())) {
throw new Error("Could not login!");
} else {
console.log("Logged in user "+user)
}
startMillis = new Date().getTime()
console.log("Starting user script at start milliseconds =", startMillis)
%SCRIPT%
} catch(err) {
console.log("Error during execution: ", err)
} finally {
if(!screenshotCreated) {
await createScreenshot(page)
}
browser.close();
let delta = new Date().getTime()-startMillis
console.log("----------------")
for (let key in resultProps) {
console.log(key+"="+resultProps[key])
}
console.log("browserDurationInMillis="+delta)
}
}
run();
'''.stripIndent()
}
def run(String scriptToRun) {
init()
new File(browserDir, 'test.js').text = testJsTemplate.replace('%SCRIPT%', scriptToRun)
def baseUrl = script.getBaseUrl()
def startTime = System.currentTimeMillis()
def cmd = "npm test $baseUrl $user $password $startTime"
def scriptResult = script.exec(cmd, browserDir, breakForDurationInSec)
new File(browserDir, 'run-'+new Date().getTime()+".log").text = scriptResult
if(scriptResult.contains("Error during execution:")) {
throw new IllegalStateException("Browser execution failed")
}
def propsString = StringUtils.substringAfter(scriptResult, "----------------\n")
resultProps = new Properties()
resultProps.load(new ByteArrayInputStream(propsString.getBytes(StandardCharsets.UTF_8)))
long browserDurationInMillis = resultProps.containsKey('breakForDurationInSec') ? Long.parseLong(resultProps.getProperty('breakForDurationInSec')) : (System.currentTimeMillis()-startTime)
return browserDurationInMillis
}
}
def logMsg(String msg) {
log.info(msg)
println(msg)
}
def getBaseUrl() {
def aemPort = System.getProperty('sun.java.command').replaceFirst(".*?-p ([0-9]+) .*", "\$1")
return "http://localhost:"+aemPort
}
import org.apache.commons.exec.CommandLine
import org.apache.commons.exec.DefaultExecutor
import org.apache.commons.exec.PumpStreamHandler
import org.apache.commons.exec.ExecuteWatchdog
def exec(String command, File dir, int breakForDurationInSec=600) {
CommandLine cmdLine = CommandLine.parse(command);
DefaultExecutor executor = new DefaultExecutor();
executor.setWorkingDirectory(dir)
ExecuteWatchdog watchdog = new ExecuteWatchdog(breakForDurationInSec * 1000);
executor.setWatchdog(watchdog);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
PumpStreamHandler streamHandler = new PumpStreamHandler(outputStream);
executor.setStreamHandler(streamHandler);
executor.setExitValues([0,1,143] as int[])
int exitValue = executor.execute(cmdLine);
String scriptOutput = outputStream.toString()
if(exitValue == 1) {
logMsg("Script '"+command+"' returned exit code 1, output:\n "+ scriptOutput)
}
return scriptOutput
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment