mirror of https://github.com/jenkinsci/jenkins.git
This commit is contained in:
commit
70c1c98da4
|
@ -2186,12 +2186,14 @@ public class Functions {
|
||||||
/**
|
/**
|
||||||
* Generate a series of {@code <script>} tags to include {@code script.js}
|
* Generate a series of {@code <script>} tags to include {@code script.js}
|
||||||
* from {@link ConsoleAnnotatorFactory}s and {@link ConsoleAnnotationDescriptor}s.
|
* from {@link ConsoleAnnotatorFactory}s and {@link ConsoleAnnotationDescriptor}s.
|
||||||
|
*
|
||||||
|
* @see hudson.console.ConsoleAnnotatorFactory.RootAction
|
||||||
*/
|
*/
|
||||||
public static String generateConsoleAnnotationScriptAndStylesheet() {
|
public static String generateConsoleAnnotationScriptAndStylesheet() {
|
||||||
String cp = Stapler.getCurrentRequest2().getContextPath() + Jenkins.RESOURCE_PATH;
|
String cp = Stapler.getCurrentRequest2().getContextPath() + Jenkins.RESOURCE_PATH;
|
||||||
StringBuilder buf = new StringBuilder();
|
StringBuilder buf = new StringBuilder();
|
||||||
for (ConsoleAnnotatorFactory f : ConsoleAnnotatorFactory.all()) {
|
for (ConsoleAnnotatorFactory f : ConsoleAnnotatorFactory.all()) {
|
||||||
String path = cp + "/extensionList/" + ConsoleAnnotatorFactory.class.getName() + "/" + f.getClass().getName();
|
String path = cp + "/" + ConsoleAnnotatorFactory.class.getName() + "/" + f.getClass().getName();
|
||||||
if (f.hasScript())
|
if (f.hasScript())
|
||||||
buf.append("<script src='").append(path).append("/script.js'></script>");
|
buf.append("<script src='").append(path).append("/script.js'></script>");
|
||||||
if (f.hasStylesheet())
|
if (f.hasStylesheet())
|
||||||
|
|
|
@ -27,6 +27,7 @@ package hudson.console;
|
||||||
import hudson.Extension;
|
import hudson.Extension;
|
||||||
import hudson.ExtensionList;
|
import hudson.ExtensionList;
|
||||||
import hudson.ExtensionPoint;
|
import hudson.ExtensionPoint;
|
||||||
|
import hudson.model.InvisibleAction;
|
||||||
import hudson.model.Run;
|
import hudson.model.Run;
|
||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -35,6 +36,8 @@ import java.lang.reflect.Type;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import org.jvnet.tiger_types.Types;
|
import org.jvnet.tiger_types.Types;
|
||||||
|
import org.kohsuke.accmod.Restricted;
|
||||||
|
import org.kohsuke.accmod.restrictions.NoExternalUse;
|
||||||
import org.kohsuke.stapler.StaplerRequest2;
|
import org.kohsuke.stapler.StaplerRequest2;
|
||||||
import org.kohsuke.stapler.StaplerResponse2;
|
import org.kohsuke.stapler.StaplerResponse2;
|
||||||
import org.kohsuke.stapler.WebMethod;
|
import org.kohsuke.stapler.WebMethod;
|
||||||
|
@ -129,4 +132,24 @@ public abstract class ConsoleAnnotatorFactory<T> implements ExtensionPoint {
|
||||||
public static ExtensionList<ConsoleAnnotatorFactory> all() {
|
public static ExtensionList<ConsoleAnnotatorFactory> all() {
|
||||||
return ExtensionList.lookup(ConsoleAnnotatorFactory.class);
|
return ExtensionList.lookup(ConsoleAnnotatorFactory.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This action makes {@link hudson.console.ConsoleAnnotatorFactory} instances accessible via HTTP.
|
||||||
|
*
|
||||||
|
* @see hudson.Functions#generateConsoleAnnotationScriptAndStylesheet
|
||||||
|
* @see ConsoleAnnotatorFactory#hasStylesheet()
|
||||||
|
* @see ConsoleAnnotatorFactory#hasScript()
|
||||||
|
*/
|
||||||
|
@Restricted(NoExternalUse.class)
|
||||||
|
@Extension
|
||||||
|
public static class RootAction extends InvisibleAction implements hudson.model.RootAction {
|
||||||
|
@Override
|
||||||
|
public String getUrlName() {
|
||||||
|
return ConsoleAnnotatorFactory.class.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConsoleAnnotatorFactory<?> getDynamic(String className) {
|
||||||
|
return all().getDynamic(className);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
* The MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025, CloudBees, Inc.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package jenkins.diagnosis;
|
||||||
|
|
||||||
|
import hudson.Extension;
|
||||||
|
import hudson.ExtensionList;
|
||||||
|
import hudson.diagnosis.MemoryUsageMonitor;
|
||||||
|
import hudson.model.InvisibleAction;
|
||||||
|
import hudson.model.RootAction;
|
||||||
|
import jenkins.model.Jenkins;
|
||||||
|
import org.kohsuke.accmod.Restricted;
|
||||||
|
import org.kohsuke.accmod.restrictions.NoExternalUse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expose {@link hudson.diagnosis.MemoryUsageMonitor#heap} at the {@code /hudson.diagnosis.MemoryUsageMonitor/heap} URL.
|
||||||
|
*
|
||||||
|
* @since TODO
|
||||||
|
*/
|
||||||
|
@Extension
|
||||||
|
@Restricted(NoExternalUse.class)
|
||||||
|
public class MemoryUsageMonitorAction extends InvisibleAction implements RootAction {
|
||||||
|
@Override
|
||||||
|
public String getUrlName() {
|
||||||
|
return MemoryUsageMonitorAction.class.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MemoryUsageMonitor.MemoryGroup getHeap() {
|
||||||
|
Jenkins.get().checkAnyPermission(Jenkins.SYSTEM_READ, Jenkins.MANAGE);
|
||||||
|
return ExtensionList.lookupSingleton(MemoryUsageMonitor.class).heap;
|
||||||
|
}
|
||||||
|
}
|
|
@ -2825,11 +2825,13 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to bind {@link ExtensionList}s to URLs.
|
* Formerly used to bind {@link ExtensionList}s to URLs.
|
||||||
|
* <p>
|
||||||
|
* Currently handled by {@link jenkins.telemetry.impl.HttpExtensionList.ExtensionListRootAction}.
|
||||||
|
* </p>
|
||||||
*
|
*
|
||||||
* @since 1.349
|
* @since 1.349
|
||||||
*/
|
*/
|
||||||
@StaplerDispatchable
|
|
||||||
public ExtensionList getExtensionList(String extensionType) throws ClassNotFoundException {
|
public ExtensionList getExtensionList(String extensionType) throws ClassNotFoundException {
|
||||||
return getExtensionList(pluginManager.uberClassLoader.loadClass(extensionType));
|
return getExtensionList(pluginManager.uberClassLoader.loadClass(extensionType));
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,124 @@
|
||||||
|
/*
|
||||||
|
* The MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025, CloudBees, Inc.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package jenkins.telemetry.impl;
|
||||||
|
|
||||||
|
import static java.util.logging.Level.FINE;
|
||||||
|
|
||||||
|
import edu.umd.cs.findbugs.annotations.NonNull;
|
||||||
|
import hudson.Extension;
|
||||||
|
import hudson.ExtensionList;
|
||||||
|
import hudson.model.InvisibleAction;
|
||||||
|
import hudson.model.RootAction;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
import jenkins.model.Jenkins;
|
||||||
|
import jenkins.telemetry.Telemetry;
|
||||||
|
import net.sf.json.JSONObject;
|
||||||
|
import org.kohsuke.accmod.Restricted;
|
||||||
|
import org.kohsuke.accmod.restrictions.NoExternalUse;
|
||||||
|
import org.kohsuke.stapler.Stapler;
|
||||||
|
import org.kohsuke.stapler.StaplerRequest2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collect information about which URLs in {@code /extensionList/} are being accessed.
|
||||||
|
*
|
||||||
|
* @since TODO
|
||||||
|
*/
|
||||||
|
@Extension
|
||||||
|
@Restricted(NoExternalUse.class)
|
||||||
|
public class HttpExtensionList extends Telemetry {
|
||||||
|
private final Map<String, Integer> calls = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String getDisplayName() {
|
||||||
|
return "Extension List access via HTTP";
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public LocalDate getStart() {
|
||||||
|
return LocalDate.of(2025, 4, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public LocalDate getEnd() {
|
||||||
|
return LocalDate.of(2025, 7, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized JSONObject createContent() {
|
||||||
|
JSONObject info = new JSONObject();
|
||||||
|
info.put("components", buildComponentInformation());
|
||||||
|
|
||||||
|
Map<String, Integer> currentRequests = new TreeMap<>(calls);
|
||||||
|
calls.clear();
|
||||||
|
|
||||||
|
JSONObject payload = new JSONObject();
|
||||||
|
payload.putAll(currentRequests);
|
||||||
|
|
||||||
|
info.put("dispatches", payload);
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void record(String path) {
|
||||||
|
String[] parts = path.split("/");
|
||||||
|
if (parts.length > 1) {
|
||||||
|
// Record just extension point + implementation class
|
||||||
|
path = parts[0] + '/' + parts[1];
|
||||||
|
}
|
||||||
|
calls.compute(path, (p, v) -> v == null ? 1 : v + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Extension
|
||||||
|
@Restricted(NoExternalUse.class)
|
||||||
|
public static class ExtensionListRootAction extends InvisibleAction implements RootAction {
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(ExtensionListRootAction.class.getName());
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUrlName() {
|
||||||
|
return "extensionList";
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExtensionList getDynamic(String extensionType) throws ClassNotFoundException {
|
||||||
|
StaplerRequest2 req = Stapler.getCurrentRequest2();
|
||||||
|
if (req != null && extensionType != null) {
|
||||||
|
try {
|
||||||
|
final HttpExtensionList telemetry = ExtensionList.lookupSingleton(HttpExtensionList.class);
|
||||||
|
if (telemetry.isActivePeriod()) {
|
||||||
|
telemetry.record(extensionType + req.getRestOfPath());
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
LOGGER.log(FINE, "Failed to record telemetry for " + HttpExtensionList.class.getName(), ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Jenkins.get().getExtensionList(extensionType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?jelly escape-by-default='true'?>
|
||||||
|
<j:jelly xmlns:j="jelly:core">
|
||||||
|
<p>
|
||||||
|
${%blurb}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
${%blurb2}
|
||||||
|
</p>
|
||||||
|
</j:jelly>
|
|
@ -0,0 +1,4 @@
|
||||||
|
blurb = This trial records uses of an HTTP endpoint granting direct access to extensions, the building blocks of Jenkins's extensibility through plugins. \
|
||||||
|
It is expected that there are few legitimate uses of this endpoint, and this trial informs plans for its eventual removal.
|
||||||
|
blurb2 = Additionally, this trial collects the list of installed plugins, their version, and the version of Jenkins. \
|
||||||
|
This data will be used to understand the environments that Jenkins is running in.
|
|
@ -10,7 +10,7 @@ graphHost.style.aspectRatio = `${imageWidth} / ${imageHeight}`;
|
||||||
timespanSelect.addEventListener("change", () => {
|
timespanSelect.addEventListener("change", () => {
|
||||||
const rootURL = document.head.dataset.rooturl;
|
const rootURL = document.head.dataset.rooturl;
|
||||||
const type = timespanSelect.value;
|
const type = timespanSelect.value;
|
||||||
graphHost.innerHTML = `<img src="${rootURL}/extensionList/hudson.diagnosis.MemoryUsageMonitor/0/heap/graph?type=${type}&width=${imageWidth}&height=${imageHeight}" srcset="${rootURL}/extensionList/hudson.diagnosis.MemoryUsageMonitor/0/heap/graph?type=${type}&width=${imageWidth}&height=${imageHeight}&scale=2 2x" loading="lazy" style="width: 100%" alt="Memory usage graph"/>`;
|
graphHost.innerHTML = `<img src="${rootURL}/jenkins.diagnosis.MemoryUsageMonitorAction/heap/graph?type=${type}&width=${imageWidth}&height=${imageHeight}" srcset="${rootURL}/jenkins.diagnosis.MemoryUsageMonitorAction/heap/graph?type=${type}&width=${imageWidth}&height=${imageHeight}&scale=2 2x" loading="lazy" style="width: 100%" alt="Memory usage graph"/>`;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Dispatch a change event to insert a graph on page load
|
// Dispatch a change event to insert a graph on page load
|
||||||
|
|
Loading…
Reference in New Issue