-
Bug
-
Resolution: Unresolved
-
Minor
-
None
Due to a job-dsl script that runs every 5 minutes creating thousands of "temporary" class loaders, pipelines builds take longer and longer to complete, though they complete all tasks.
Turning on FINEST logging for CpsFlowExecution showed these types of entries repeated endlessly:
Sep 19, 2024 6:09:30 AM FINEST org.jenkinsci.plugins.workflow.cps.CpsFlowExecution
ignoring class sfdc.factory.AppJobFactory$_addStopAllApps_closure16$_closure99 with loader groovy.lang.GroovyClassLoader$InnerLoader@19b1f70
Sep 19, 2024 6:09:30 AM FINEST org.jenkinsci.plugins.workflow.cps.CpsFlowExecution
ignoring class groovy.tmp.templates.StreamingTemplateScript324068 with loader groovy.lang.GroovyClassLoader$InnerLoader@7164c9ed
Sep 19, 2024 6:09:30 AM FINEST org.jenkinsci.plugins.workflow.cps.CpsFlowExecution
ignoring class groovy.tmp.templates.StreamingTemplateScript229000$_getTemplate_closure1$_closure3 with loader groovy.lang.GroovyClassLoader$InnerLoader@38a504f0
I'm encountering this on release 3867.v535458ce43fd, though it seems that the latest code is going to behave in more or less the same way.
------------------
This script gives an idea of the delay interval added to the end of every pipeline build by just iterating on the classloaders (and printing the count).
import java.lang.ref.WeakReferencedef timeIterationOfGlobalClassValues(ClassLoader loader) {
try {
// Get the ClassInfo class
def classInfoC = Class.forName("org.codehaus.groovy.reflection.ClassInfo")// Access the globalClassValue field
def globalClassValueF = classInfoC.getDeclaredField("globalClassValue")
globalClassValueF.setAccessible(true)
def globalClassValue = globalClassValueF.get(null)// Check if GroovyClassValuePreJava7 is in use
def groovyClassValuePreJava7C = Class.forName("org.codehaus.groovy.reflection.GroovyClassValuePreJava7")
if (!groovyClassValuePreJava7C.isInstance(globalClassValue)){{{}
{}}}// Access the map field
def mapF = groovyClassValuePreJava7C.getDeclaredField("map")
mapF.setAccessible(true)
def map = mapF.get(globalClassValue)// Get map entries
def groovyClassValuePreJava7Map = Class.forName("org.codehaus.groovy.reflection.GroovyClassValuePreJava7\$GroovyClassValuePreJava7Map")
def entries = groovyClassValuePreJava7Map.getMethod("values").invoke(map)
def removeM = groovyClassValuePreJava7Map.getMethod("remove", Object.class)// Get value from entries
def entryC = Class.forName("org.codehaus.groovy.util.AbstractConcurrentMapBase\$Entry")
def getValueM = entryC.getMethod("getValue")// To store classes to remove
def toRemove = []
def ignoredClassLoaderCount = 0 // Counter for ignored class loaders// Start timing the iteration
def startTime = System.nanoTime()try {
// For Groovy 2.4.8+
def classRefF = classInfoC.getDeclaredField("classRef")
classRefF.setAccessible(true)
for (entry in entries){{{}
{}}}} catch (NoSuchFieldException e) {
// For Groovy 2.4.7 and below
def klazzF = classInfoC.getDeclaredField("klazz")
klazzF.setAccessible(true)
for (entry in entries){{{}
{}}}}// Iterate and remove classes not associated with the provided loader
def it = toRemove.iterator()
while (it.hasNext()) {
def clazz = it.next()
try {
def encounteredLoader = clazz.getClassLoader()
if (encounteredLoader != loader){{{}
{}}}} catch (Error e) {
println("Error: ${e}")
e.printStackTrace()
}
}// End timing
def endTime = System.nanoTime()// Calculate the elapsed time in milliseconds
def elapsedTimeInMs = (endTime - startTime) / 1_000_000
def minutes = (elapsedTimeInMs / 60000) as int
def seconds = ((elapsedTimeInMs.longValue() % 60000) as int) / 1000println("Total time for iteration: ${minutes} minute(s) and ${seconds} second(s)")
println("Total ignored class loaders: ${ignoredClassLoaderCount}")
} catch (Exception e) {
println("Error: ${e}")
e.printStackTrace()
}
}// Example usage in Jenkins Script Console
def loader = Jenkins.instance.pluginManager.uberClassLoader
timeIterationOfGlobalClassValues(loader)
[JENKINS-73802] Job not completing for extended time despite completing all tasks due to 100,000s "ignored" class loaders in cleanUpGlobalClassValue
Description |
Original:
Due to a job-dsl script that runs every 5 minutes creating thousands of "temporary" class loaders, pipelines builds take longer and longer to complete, though they complete all tasks.
Turning on FINEST logging for CpsFlowExecution showed these types of entries repeated endlessly: ``` Sep 19, 2024 6:09:30 AM FINEST org.jenkinsci.plugins.workflow.cps.CpsFlowExecution ignoring class sfdc.factory.AppJobFactory$_addStopAllApps_closure16$_closure99 with loader groovy.lang.GroovyClassLoader$InnerLoader@19b1f70 Sep 19, 2024 6:09:30 AM FINEST org.jenkinsci.plugins.workflow.cps.CpsFlowExecution ignoring class groovy.tmp.templates.StreamingTemplateScript324068 with loader groovy.lang.GroovyClassLoader$InnerLoader@7164c9ed Sep 19, 2024 6:09:30 AM FINEST org.jenkinsci.plugins.workflow.cps.CpsFlowExecution ignoring class groovy.tmp.templates.StreamingTemplateScript229000$_getTemplate_closure1$_closure3 with loader groovy.lang.GroovyClassLoader$InnerLoader@38a504f0 ``` I'm encountering this on release 3867.v535458ce43fd, though it seems that the latest code is going to behave in more or less the same This script gives an idea of the delay interval added to the end of every pipeline build. ``` import java.lang.ref.WeakReference def timeIterationOfGlobalClassValues(ClassLoader loader) { try { // Get the ClassInfo class def classInfoC = Class.forName("org.codehaus.groovy.reflection.ClassInfo") // Access the globalClassValue field def globalClassValueF = classInfoC.getDeclaredField("globalClassValue") globalClassValueF.setAccessible(true) def globalClassValue = globalClassValueF.get(null) // Check if GroovyClassValuePreJava7 is in use def groovyClassValuePreJava7C = Class.forName("org.codehaus.groovy.reflection.GroovyClassValuePreJava7") if (!groovyClassValuePreJava7C.isInstance(globalClassValue)) { println("Not using GroovyClassValuePreJava7") return } // Access the map field def mapF = groovyClassValuePreJava7C.getDeclaredField("map") mapF.setAccessible(true) def map = mapF.get(globalClassValue) // Get map entries def groovyClassValuePreJava7Map = Class.forName("org.codehaus.groovy.reflection.GroovyClassValuePreJava7\$GroovyClassValuePreJava7Map") def entries = groovyClassValuePreJava7Map.getMethod("values").invoke(map) def removeM = groovyClassValuePreJava7Map.getMethod("remove", Object.class) // Get value from entries def entryC = Class.forName("org.codehaus.groovy.util.AbstractConcurrentMapBase\$Entry") def getValueM = entryC.getMethod("getValue") // To store classes to remove def toRemove = [] def ignoredClassLoaderCount = 0 // Counter for ignored class loaders // Start timing the iteration def startTime = System.nanoTime() try { // For Groovy 2.4.8+ def classRefF = classInfoC.getDeclaredField("classRef") classRefF.setAccessible(true) for (entry in entries) { def value = getValueM.invoke(entry) def clazz = ((WeakReference<Class<?>>) classRefF.get(value)).get() if (clazz != null) toRemove << clazz } } catch (NoSuchFieldException e) { // For Groovy 2.4.7 and below def klazzF = classInfoC.getDeclaredField("klazz") klazzF.setAccessible(true) for (entry in entries) { def value = getValueM.invoke(entry) def clazz = klazzF.get(value) if (clazz != null) toRemove << clazz } } // Iterate and remove classes not associated with the provided loader def it = toRemove.iterator() while (it.hasNext()) { def clazz = it.next() try { def encounteredLoader = clazz.getClassLoader() if (encounteredLoader != loader) { it.remove() // Remove class if the loader does not match ignoredClassLoaderCount++ // Increment counter } } catch (Error e) { println("Error: ${e}") e.printStackTrace() } } // End timing def endTime = System.nanoTime() // Calculate the elapsed time in milliseconds def elapsedTimeInMs = (endTime - startTime) / 1_000_000 def minutes = (elapsedTimeInMs / 60000) as int def seconds = ((elapsedTimeInMs.longValue() % 60000) as int) / 1000 println("Total time for iteration: ${minutes} minute(s) and ${seconds} second(s)") println("Total ignored class loaders: ${ignoredClassLoaderCount}") } catch (Exception e) { println("Error: ${e}") e.printStackTrace() } } // Example usage in Jenkins Script Console def loader = Jenkins.instance.pluginManager.uberClassLoader timeIterationOfGlobalClassValues(loader) ``` |
New:
Due to a job-dsl script that runs every 5 minutes creating thousands of "temporary" class loaders, pipelines builds take longer and longer to complete, though they complete all tasks.
Turning on FINEST logging for CpsFlowExecution showed these types of entries repeated endlessly: {{Sep 19, 2024 6:09:30 AM FINEST org.jenkinsci.plugins.workflow.cps.CpsFlowExecution}} {{ignoring class sfdc.factory.AppJobFactory$_addStopAllApps_closure16$_closure99 with loader groovy.lang.GroovyClassLoader$InnerLoader@19b1f70}} {{Sep 19, 2024 6:09:30 AM FINEST org.jenkinsci.plugins.workflow.cps.CpsFlowExecution}} {{ignoring class groovy.tmp.templates.StreamingTemplateScript324068 with loader groovy.lang.GroovyClassLoader$InnerLoader@7164c9ed}} {{Sep 19, 2024 6:09:30 AM FINEST org.jenkinsci.plugins.workflow.cps.CpsFlowExecution}} {{ignoring class groovy.tmp.templates.StreamingTemplateScript229000$_getTemplate_closure1$_closure3 with loader groovy.lang.GroovyClassLoader$InnerLoader@38a504f0}} I'm encountering this on release 3867.v535458ce43fd, though it seems that the latest code is going to behave in more or less the same This script gives an idea of the delay interval added to the end of every pipeline build. {{{}import java.lang.ref.WeakReference{}}}{{{}def timeIterationOfGlobalClassValues(ClassLoader loader) {{}}} {{try {}} {{// Get the ClassInfo class}} {{{}def classInfoC = Class.forName("org.codehaus.groovy.reflection.ClassInfo"){}}}{{{}// Access the globalClassValue field{}}} {{def globalClassValueF = classInfoC.getDeclaredField("globalClassValue")}} {{globalClassValueF.setAccessible(true)}} {{{}def globalClassValue = globalClassValueF.get(null){}}}{{{}// Check if GroovyClassValuePreJava7 is in use{}}} {{def groovyClassValuePreJava7C = Class.forName("org.codehaus.groovy.reflection.GroovyClassValuePreJava7")}} {{{}if (!groovyClassValuePreJava7C.isInstance(globalClassValue)){}}}{{{}{ println("Not using GroovyClassValuePreJava7") return }{}}}{{{}// Access the map field{}}} {{def mapF = groovyClassValuePreJava7C.getDeclaredField("map")}} {{mapF.setAccessible(true)}} {{{}def map = mapF.get(globalClassValue){}}}{{{}// Get map entries{}}} {{def groovyClassValuePreJava7Map = Class.forName("org.codehaus.groovy.reflection.GroovyClassValuePreJava7\$GroovyClassValuePreJava7Map")}} {{def entries = groovyClassValuePreJava7Map.getMethod("values").invoke(map)}} {{{}def removeM = groovyClassValuePreJava7Map.getMethod("remove", Object.class){}}}{{{}// Get value from entries{}}} {{def entryC = Class.forName("org.codehaus.groovy.util.AbstractConcurrentMapBase\$Entry")}} {{{}def getValueM = entryC.getMethod("getValue"){}}}{{{}// To store classes to remove{}}} {{def toRemove = []}} {{{}def ignoredClassLoaderCount = 0 // Counter for ignored class loaders{}}}{{{}// Start timing the iteration{}}} {{{}def startTime = System.nanoTime(){}}}{{{}try {{}}} {{// For Groovy 2.4.8+}} {{def classRefF = classInfoC.getDeclaredField("classRef")}} {{classRefF.setAccessible(true)}} {{{}for (entry in entries){}}}{{{}{ def value = getValueM.invoke(entry) def clazz = ((WeakReference<Class<?>>) classRefF.get(value)).get() if (clazz != null) toRemove << clazz }{}}}{{{}} catch (NoSuchFieldException e) {{}}} {{// For Groovy 2.4.7 and below}} {{def klazzF = classInfoC.getDeclaredField("klazz")}} {{klazzF.setAccessible(true)}} {{{}for (entry in entries){}}}{{{}{ def value = getValueM.invoke(entry) def clazz = klazzF.get(value) if (clazz != null) toRemove << clazz }{}}}{{{}}{}}}{{{}// Iterate and remove classes not associated with the provided loader{}}} {{def it = toRemove.iterator()}} {{while (it.hasNext()) {}} {{def clazz = it.next()}} {{try {}} {{def encounteredLoader = clazz.getClassLoader()}} {{{}if (encounteredLoader != loader){}}}{{{}{ it.remove() // Remove class if the loader does not match ignoredClassLoaderCount++ // Increment counter }{}}}{{{}} catch (Error e) {{}}} {{println("Error: ${e}")}} {{e.printStackTrace()}} {{}}} {{{}}{}}}{{{}// End timing{}}} {{{}def endTime = System.nanoTime(){}}}{{{}// Calculate the elapsed time in milliseconds{}}} {{def elapsedTimeInMs = (endTime - startTime) / 1_000_000}} {{def minutes = (elapsedTimeInMs / 60000) as int}} {{{}def seconds = ((elapsedTimeInMs.longValue() % 60000) as int) / 1000{}}}{{{}println("Total time for iteration: ${minutes} minute(s) and ${seconds} second(s)"){}}} {{println("Total ignored class loaders: ${ignoredClassLoaderCount}")}} {{} catch (Exception e) {}} {{println("Error: ${e}")}} {{e.printStackTrace()}} {{}}} {{{}}{}}}{{{}// Example usage in Jenkins Script Console{}}} {{def loader = Jenkins.instance.pluginManager.uberClassLoader}} {{timeIterationOfGlobalClassValues(loader)}} |
Description |
Original:
Due to a job-dsl script that runs every 5 minutes creating thousands of "temporary" class loaders, pipelines builds take longer and longer to complete, though they complete all tasks.
Turning on FINEST logging for CpsFlowExecution showed these types of entries repeated endlessly: {{Sep 19, 2024 6:09:30 AM FINEST org.jenkinsci.plugins.workflow.cps.CpsFlowExecution}} {{ignoring class sfdc.factory.AppJobFactory$_addStopAllApps_closure16$_closure99 with loader groovy.lang.GroovyClassLoader$InnerLoader@19b1f70}} {{Sep 19, 2024 6:09:30 AM FINEST org.jenkinsci.plugins.workflow.cps.CpsFlowExecution}} {{ignoring class groovy.tmp.templates.StreamingTemplateScript324068 with loader groovy.lang.GroovyClassLoader$InnerLoader@7164c9ed}} {{Sep 19, 2024 6:09:30 AM FINEST org.jenkinsci.plugins.workflow.cps.CpsFlowExecution}} {{ignoring class groovy.tmp.templates.StreamingTemplateScript229000$_getTemplate_closure1$_closure3 with loader groovy.lang.GroovyClassLoader$InnerLoader@38a504f0}} I'm encountering this on release 3867.v535458ce43fd, though it seems that the latest code is going to behave in more or less the same This script gives an idea of the delay interval added to the end of every pipeline build. {{{}import java.lang.ref.WeakReference{}}}{{{}def timeIterationOfGlobalClassValues(ClassLoader loader) {{}}} {{try {}} {{// Get the ClassInfo class}} {{{}def classInfoC = Class.forName("org.codehaus.groovy.reflection.ClassInfo"){}}}{{{}// Access the globalClassValue field{}}} {{def globalClassValueF = classInfoC.getDeclaredField("globalClassValue")}} {{globalClassValueF.setAccessible(true)}} {{{}def globalClassValue = globalClassValueF.get(null){}}}{{{}// Check if GroovyClassValuePreJava7 is in use{}}} {{def groovyClassValuePreJava7C = Class.forName("org.codehaus.groovy.reflection.GroovyClassValuePreJava7")}} {{{}if (!groovyClassValuePreJava7C.isInstance(globalClassValue)){}}}{{{}{ println("Not using GroovyClassValuePreJava7") return }{}}}{{{}// Access the map field{}}} {{def mapF = groovyClassValuePreJava7C.getDeclaredField("map")}} {{mapF.setAccessible(true)}} {{{}def map = mapF.get(globalClassValue){}}}{{{}// Get map entries{}}} {{def groovyClassValuePreJava7Map = Class.forName("org.codehaus.groovy.reflection.GroovyClassValuePreJava7\$GroovyClassValuePreJava7Map")}} {{def entries = groovyClassValuePreJava7Map.getMethod("values").invoke(map)}} {{{}def removeM = groovyClassValuePreJava7Map.getMethod("remove", Object.class){}}}{{{}// Get value from entries{}}} {{def entryC = Class.forName("org.codehaus.groovy.util.AbstractConcurrentMapBase\$Entry")}} {{{}def getValueM = entryC.getMethod("getValue"){}}}{{{}// To store classes to remove{}}} {{def toRemove = []}} {{{}def ignoredClassLoaderCount = 0 // Counter for ignored class loaders{}}}{{{}// Start timing the iteration{}}} {{{}def startTime = System.nanoTime(){}}}{{{}try {{}}} {{// For Groovy 2.4.8+}} {{def classRefF = classInfoC.getDeclaredField("classRef")}} {{classRefF.setAccessible(true)}} {{{}for (entry in entries){}}}{{{}{ def value = getValueM.invoke(entry) def clazz = ((WeakReference<Class<?>>) classRefF.get(value)).get() if (clazz != null) toRemove << clazz }{}}}{{{}} catch (NoSuchFieldException e) {{}}} {{// For Groovy 2.4.7 and below}} {{def klazzF = classInfoC.getDeclaredField("klazz")}} {{klazzF.setAccessible(true)}} {{{}for (entry in entries){}}}{{{}{ def value = getValueM.invoke(entry) def clazz = klazzF.get(value) if (clazz != null) toRemove << clazz }{}}}{{{}}{}}}{{{}// Iterate and remove classes not associated with the provided loader{}}} {{def it = toRemove.iterator()}} {{while (it.hasNext()) {}} {{def clazz = it.next()}} {{try {}} {{def encounteredLoader = clazz.getClassLoader()}} {{{}if (encounteredLoader != loader){}}}{{{}{ it.remove() // Remove class if the loader does not match ignoredClassLoaderCount++ // Increment counter }{}}}{{{}} catch (Error e) {{}}} {{println("Error: ${e}")}} {{e.printStackTrace()}} {{}}} {{{}}{}}}{{{}// End timing{}}} {{{}def endTime = System.nanoTime(){}}}{{{}// Calculate the elapsed time in milliseconds{}}} {{def elapsedTimeInMs = (endTime - startTime) / 1_000_000}} {{def minutes = (elapsedTimeInMs / 60000) as int}} {{{}def seconds = ((elapsedTimeInMs.longValue() % 60000) as int) / 1000{}}}{{{}println("Total time for iteration: ${minutes} minute(s) and ${seconds} second(s)"){}}} {{println("Total ignored class loaders: ${ignoredClassLoaderCount}")}} {{} catch (Exception e) {}} {{println("Error: ${e}")}} {{e.printStackTrace()}} {{}}} {{{}}{}}}{{{}// Example usage in Jenkins Script Console{}}} {{def loader = Jenkins.instance.pluginManager.uberClassLoader}} {{timeIterationOfGlobalClassValues(loader)}} |
New:
Due to a job-dsl script that runs every 5 minutes creating thousands of "temporary" class loaders, pipelines builds take longer and longer to complete, though they complete all tasks.
Turning on FINEST logging for CpsFlowExecution showed these types of entries repeated endlessly: {{Sep 19, 2024 6:09:30 AM FINEST org.jenkinsci.plugins.workflow.cps.CpsFlowExecution}} {{ignoring class sfdc.factory.AppJobFactory$_addStopAllApps_closure16$_closure99 with loader groovy.lang.GroovyClassLoader$InnerLoader@19b1f70}} {{Sep 19, 2024 6:09:30 AM FINEST org.jenkinsci.plugins.workflow.cps.CpsFlowExecution}} {{ignoring class groovy.tmp.templates.StreamingTemplateScript324068 with loader groovy.lang.GroovyClassLoader$InnerLoader@7164c9ed}} {{Sep 19, 2024 6:09:30 AM FINEST org.jenkinsci.plugins.workflow.cps.CpsFlowExecution}} {{ignoring class groovy.tmp.templates.StreamingTemplateScript229000$_getTemplate_closure1$_closure3 with loader groovy.lang.GroovyClassLoader$InnerLoader@38a504f0}} I'm encountering this on release 3867.v535458ce43fd, though it seems that the latest code is going to behave in more or less the same way. ------------------ This script gives an idea of the delay interval added to the end of every pipeline build by just iterating on the classloaders (and printing the count). {{{}import java.lang.ref.WeakReference{}}}{{{}def timeIterationOfGlobalClassValues(ClassLoader loader) {{}}} {{try {}} {{// Get the ClassInfo class}} {{{}def classInfoC = Class.forName("org.codehaus.groovy.reflection.ClassInfo"){}}}{{{}// Access the globalClassValue field{}}} {{def globalClassValueF = classInfoC.getDeclaredField("globalClassValue")}} {{globalClassValueF.setAccessible(true)}} {{{}def globalClassValue = globalClassValueF.get(null){}}}{{{}// Check if GroovyClassValuePreJava7 is in use{}}} {{def groovyClassValuePreJava7C = Class.forName("org.codehaus.groovy.reflection.GroovyClassValuePreJava7")}} {{{}if (!groovyClassValuePreJava7C.isInstance(globalClassValue)){}}}{\{{} { println("Not using GroovyClassValuePreJava7") return } {}}}{{{}// Access the map field{}}} {{def mapF = groovyClassValuePreJava7C.getDeclaredField("map")}} {{mapF.setAccessible(true)}} {{{}def map = mapF.get(globalClassValue){}}}{{{}// Get map entries{}}} {{def groovyClassValuePreJava7Map = Class.forName("org.codehaus.groovy.reflection.GroovyClassValuePreJava7\$GroovyClassValuePreJava7Map")}} {{def entries = groovyClassValuePreJava7Map.getMethod("values").invoke(map)}} {{{}def removeM = groovyClassValuePreJava7Map.getMethod("remove", Object.class){}}}{{{}// Get value from entries{}}} {{def entryC = Class.forName("org.codehaus.groovy.util.AbstractConcurrentMapBase\$Entry")}} {{{}def getValueM = entryC.getMethod("getValue"){}}}{{{}// To store classes to remove{}}} {{def toRemove = []}} {{{}def ignoredClassLoaderCount = 0 // Counter for ignored class loaders{}}}{{{}// Start timing the iteration{}}} {{{}def startTime = System.nanoTime(){}}}{{{}try {{}}} {{// For Groovy 2.4.8+}} {{def classRefF = classInfoC.getDeclaredField("classRef")}} {{classRefF.setAccessible(true)}} {{{}for (entry in entries){}}}{\{{} { def value = getValueM.invoke(entry) def clazz = ((WeakReference<Class<?>>) classRefF.get(value)).get() if (clazz != null) toRemove << clazz } {}}}{{{}} catch (NoSuchFieldException e) {{}}} {{// For Groovy 2.4.7 and below}} {{def klazzF = classInfoC.getDeclaredField("klazz")}} {{klazzF.setAccessible(true)}} {{{}for (entry in entries){}}}{\{{} { def value = getValueM.invoke(entry) def clazz = klazzF.get(value) if (clazz != null) toRemove << clazz } {}}}{{{}}{}}}{{{}// Iterate and remove classes not associated with the provided loader{}}} {{def it = toRemove.iterator()}} {{while (it.hasNext()) {}} {{def clazz = it.next()}} {{try {}} {{def encounteredLoader = clazz.getClassLoader()}} {{{}if (encounteredLoader != loader){}}}{\{{} { it.remove() // Remove class if the loader does not match ignoredClassLoaderCount++ // Increment counter } {}}}{{{}} catch (Error e) {{}}} {{println("Error: ${e}")}} {{e.printStackTrace()}} {{}}} {{{}}{}}}{{{}// End timing{}}} {{{}def endTime = System.nanoTime(){}}}{{{}// Calculate the elapsed time in milliseconds{}}} {{def elapsedTimeInMs = (endTime - startTime) / 1_000_000}} {{def minutes = (elapsedTimeInMs / 60000) as int}} {{{}def seconds = ((elapsedTimeInMs.longValue() % 60000) as int) / 1000{}}}{{{}println("Total time for iteration: ${minutes} minute(s) and ${seconds} second(s)"){}}} {{println("Total ignored class loaders: ${ignoredClassLoaderCount}")}} {{} catch (Exception e) {}} {{println("Error: ${e}")}} {{e.printStackTrace()}} {{}}} {{{}}{}}}{{{}// Example usage in Jenkins Script Console{}}} {{def loader = Jenkins.instance.pluginManager.uberClassLoader}} {{timeIterationOfGlobalClassValues(loader)}} |