001/*
002 * jPOS Project [http://jpos.org]
003 * Copyright (C) 2000-2026 jPOS Software SRL
004 *
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU Affero General Public License as
007 * published by the Free Software Foundation, either version 3 of the
008 * License, or (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013 * GNU Affero General Public License for more details.
014 *
015 * You should have received a copy of the GNU Affero General Public License
016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
017 */
018
019package org.jpos.metrics;
020
021import com.sun.net.httpserver.HttpServer;
022import org.jdom2.Element;
023import org.jpos.core.ConfigurationException;
024import org.jpos.core.annotation.Config;
025import org.jpos.q2.QBeanSupport;
026
027import java.io.IOException;
028import java.io.OutputStream;
029import java.net.InetSocketAddress;
030
031public class PrometheusService extends QBeanSupport {
032    @Config("port")
033    private int port;
034    @Config("path")
035    private String path;
036    @Config("status-path")
037    private String statusPath;
038    private HttpServer server;
039
040    @Override
041    protected void initService() throws ConfigurationException {
042        try {
043            final var registry =  getServer().getPrometheusMeterRegistry();
044            server = HttpServer.create(new InetSocketAddress(port), 0);
045            server.createContext(path, httpExchange -> {
046                String response = registry.scrape();
047                httpExchange.getResponseHeaders().add("Content-Type", "text/plain; version=0.0.4");
048                httpExchange.sendResponseHeaders(200, response.getBytes().length);
049                try (OutputStream os = httpExchange.getResponseBody()) {
050                    os.write(response.getBytes());
051                }
052            });
053            if (statusPath != null) {
054                server.createContext(statusPath, httpExchange -> {
055                    String response = getServer().running() ? "running\n" : "stopping\n";
056                    httpExchange.getResponseHeaders().add("Content-Type", "text/plain");
057                    httpExchange.sendResponseHeaders(200, response.getBytes().length);
058                    try (OutputStream os = httpExchange.getResponseBody()) {
059                        os.write(response.getBytes());
060                    }
061                });
062            }
063            Thread.ofVirtual().start(server::start);
064        } catch (IOException e) {
065            getLog().warn(e);
066            throw new RuntimeException(e);
067        }
068    }
069
070    @Override
071    protected void stopService() {
072        server.stop(2);
073    }
074
075    public static Element createDescriptor (int port, String path, String statusPath) {
076        return new Element("prometheus")
077          .addContent(createProperty ("port", Integer.toString(port)))
078          .addContent (createProperty ("path", path))
079          .addContent (createProperty ("status-path", statusPath));
080
081    }
082    private static Element createProperty (String name, String value) {
083        return new Element ("property")
084          .setAttribute("name", name)
085          .setAttribute("value", value);
086    }
087}