> e : groups.entrySet()) {
+ //String pfx = "i2p." + e.getKey() + '.';
+ String pfx = "i2p.";
+ for (String s : e.getValue()) {
+ RateStat rs = sm.getRate(s);
+ if (rs == null)
+ continue;
+ String desc = rs.getDescription();
+ if (desc == null)
+ desc = "";
+ long[] pers = rs.getPeriods();
+ final long per = pers[0];
+ final Rate rate = rs.getRate(per);
+ if (rate == null)
+ continue;
+
+ String name = pfx + s + '.' + DataHelper.formatDuration(per);
+ name = name.replace(".", "_");
+ name = name.replace("-", "_");
+ // https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels
+ // Prevent IllegalArgumentExceptions
+ if (name.replaceAll("[a-zA-Z0-9_]", "").length() != 0) {
+ if (_log.shouldWarn())
+ _log.warn("skipping stat with illegal chars: " + name);
+ continue;
+ }
+
+ if (_log.shouldDebug())
+ _log.debug("adding gauge " + name);
+
+ GaugeWithCallback.builder()
+ .name(name)
+ .help(desc)
+ .labelNames("state")
+ .callback(callback -> {
+ callback.call(rate.getAvgOrLifetimeAvg(), "average");
+ })
+ .register();
+ n++;
+ }
+ }
+ i2pCount = n;
+ if (_log.shouldDebug())
+ _log.info(n + " PromManager I2P metrics registered");
+ }
+
+
+ /////// ClientApp methods
+
+ public synchronized void startup() throws Exception {
+ if (_state != STOPPED && _state != INITIALIZED && _state != START_FAILED) {
+ _log.error("Start while state = " + _state);
+ return;
+ }
+ _log.info("PromManager startup");
+ JvmMetrics.builder().register();
+ jvmCount = PrometheusRegistry.defaultRegistry.scrape().size();
+ if (_log.shouldInfo())
+ _log.info(jvmCount + " PromManager JVM metrics registered");
+
+ addStats();
+ changeState(RUNNING);
+ _mgr.register(this);
+ }
+
+ public synchronized void shutdown(String[] args) {
+ _log.warn("PromManager shutdown");
+ if (_state == STOPPED)
+ return;
+ changeState(STOPPING);
+ // clear() supported as of v1.3.2
+ PrometheusRegistry.defaultRegistry.clear();
+ _mgr.unregister(this);
+ changeState(STOPPED);
+ }
+
+ public ClientAppState getState() {
+ return _state;
+ }
+
+ public String getName() {
+ return "prometheus";
+ }
+
+ public String getDisplayName() {
+ return "PromManager Metrics";
+ }
+
+ /////// end ClientApp methods
+
+ private synchronized void changeState(ClientAppState state) {
+ if (state == _state)
+ return;
+ _state = state;
+ _mgr.notify(this, state, null, null);
+ }
+
+ private synchronized void changeState(ClientAppState state, String msg, Exception e) {
+ if (state == _state)
+ return;
+ _state = state;
+ _mgr.notify(this, state, msg, e);
+ }
+}
diff --git a/src/java/net/i2p/prometheus/web/BasicServlet.java b/src/java/net/i2p/prometheus/web/BasicServlet.java
new file mode 100644
index 0000000..55324d3
--- /dev/null
+++ b/src/java/net/i2p/prometheus/web/BasicServlet.java
@@ -0,0 +1,151 @@
+// ========================================================================
+// Copyright 199-2004 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// 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
+// http://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.
+// ========================================================================
+
+package net.i2p.prometheus.web;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Enumeration;
+import java.util.List;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.UnavailableException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import net.i2p.I2PAppContext;
+import net.i2p.data.ByteArray;
+import net.i2p.data.DataHelper;
+import net.i2p.util.ByteCache;
+import net.i2p.util.Log;
+import net.i2p.util.SystemVersion;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * Based on DefaultServlet from Jetty 6.1.26, heavily simplified
+ * and modified to remove all dependencies on Jetty libs.
+ *
+ * Supports HEAD and GET only, for resources from the .war and local files.
+ * Supports files and resource only.
+ * Supports MIME types with local overrides and additions.
+ * Supports Last-Modified.
+ * Supports single request ranges.
+ *
+ * Does not support directories or "welcome files".
+ * Does not support gzip.
+ * Does not support multiple request ranges.
+ * Does not cache.
+ *
+ * POST returns 405.
+ * Directories return 403.
+ * Jar resources are sent with a long cache directive.
+ *
+ * ------------------------------------------------------------
+ *
+ * The default servlet.
+ * This servlet, normally mapped to /, provides the handling for static
+ * content, OPTION and TRACE methods for the context.
+ * The following initParameters are supported, these can be set
+ * on the servlet itself:
+ *
+ *
+ * resourceBase Set to replace the context resource base
+
+ * warBase Path allowed for resource in war
+ *
+ *
+ *
+ *
+ * @author Greg Wilkins (gregw)
+ * @author Nigel Canonizado
+ *
+ * @since Jetty 7
+ */
+class BasicServlet extends HttpServlet
+{
+ protected final I2PAppContext _context;
+ protected final Log _log;
+ protected File _resourceBase;
+ private String _warBase;
+
+ /** same as PeerState.PARTSIZE */
+ private static final int BUFSIZE = 16*1024;
+ private ByteCache _cache = ByteCache.getInstance(16, BUFSIZE);
+
+ private static final int WAR_CACHE_CONTROL_SECS = 24*60*60;
+ private static final int FILE_CACHE_CONTROL_SECS = 24*60*60;
+
+ public BasicServlet() {
+ super();
+ _context = I2PAppContext.getGlobalContext();
+ _log = _context.logManager().getLog(getClass());
+ }
+
+ /* ------------------------------------------------------------ */
+ public void init(ServletConfig cfg) throws ServletException {
+ super.init(cfg);
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void doPost(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException
+ {
+ response.sendError(405);
+ }
+
+ /* ------------------------------------------------------------ */
+ /* (non-Javadoc)
+ * @see javax.servlet.http.HttpServlet#doTrace(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+ */
+ protected void doTrace(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException
+ {
+ response.sendError(405);
+ }
+
+ protected void doOptions(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException
+ {
+ response.sendError(405);
+ }
+
+ protected void doDelete(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException
+ {
+ response.sendError(405);
+ }
+
+ /**
+ * Simple version of URIUtil.addPaths()
+ * @param path may be null
+ */
+ protected static String addPaths(String base, String path) {
+ if (path == null)
+ return base;
+ String rv = (new File(base, path)).toString();
+ if (SystemVersion.isWindows())
+ rv = rv.replace("\\", "/");
+ return rv;
+ }
+}
diff --git a/src/java/net/i2p/prometheus/web/PrometheusServlet.java b/src/java/net/i2p/prometheus/web/PrometheusServlet.java
new file mode 100644
index 0000000..4c638aa
--- /dev/null
+++ b/src/java/net/i2p/prometheus/web/PrometheusServlet.java
@@ -0,0 +1,254 @@
+package net.i2p.prometheus.web;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Properties;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import net.i2p.app.ClientAppManager;
+import net.i2p.app.ClientAppState;
+import net.i2p.data.DataHelper;
+import net.i2p.prometheus.PromManager;
+import net.i2p.util.I2PAppThread;
+import net.i2p.util.PortMapper;
+import net.i2p.util.Translate;
+
+import net.i2p.I2PAppContext;
+
+/**
+ * From socksoutproxy
+ */
+public class PrometheusServlet extends BasicServlet {
+ private String _contextPath;
+ private String _contextName;
+ private volatile PromManager _manager;
+ private volatile boolean _isRunning;
+ private static long _nonce;
+
+ private static final String DEFAULT_NAME = "prometheus";
+ private static final String DOCTYPE = "\n";
+ private static final String FOOTER = "\n\n\n