<commit_before>/*
 * The MIT License
 *
 * Copyright (c) 2014, 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.metrics.api;

import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.health.HealthCheck;
import com.codahale.metrics.json.HealthCheckModule;
import com.codahale.metrics.json.MetricsModule;
import com.codahale.metrics.jvm.ThreadDump;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.SuppressWarnings;
import hudson.Extension;
import hudson.Util;
import hudson.model.PeriodicWork;
import hudson.model.UnprotectedRootAction;
import hudson.util.HttpResponses;
import hudson.util.IOUtils;
import jenkins.metrics.util.ExponentialLeakyBucket;
import jenkins.metrics.util.NameRewriterMetricRegistry;
import jenkins.model.Jenkins;
import org.apache.commons.lang.StringUtils;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.interceptor.RequirePOST;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.lang.management.ManagementFactory;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

import static com.codahale.metrics.MetricRegistry.name;

/**
 * @author <NAME>
 */
@Extension
public class MetricsRootAction implements UnprotectedRootAction {

    public static final TimeUnit RATE_UNIT = TimeUnit.MINUTES;
    public static final TimeUnit DURATION_UNIT = TimeUnit.SECONDS;
    private final Pseudoservlet currentUser = new CurrentUserPseudoservlet();
    private final ObjectMapper healthCheckMapper = new ObjectMapper().registerModule(new HealthCheckModule());
    private final ObjectMapper metricsMapper =
            new ObjectMapper().registerModule(new MetricsModule(RATE_UNIT, DURATION_UNIT, true));

    private static boolean isAllHealthy(Map<String, HealthCheck.Result> results) {
        for (HealthCheck.Result result : results.values()) {
            if (!result.isHealthy()) {
                return false;
            }
        }
        return true;
    }

    private static ObjectWriter getWriter(ObjectMapper mapper, HttpServletRequest request) {
        final boolean prettyPrint = Boolean.parseBoolean(request.getParameter("pretty"));
        if (prettyPrint) {
            return mapper.writerWithDefaultPrettyPrinter();
        }
        return mapper.writer();
    }

    @SuppressWarnings("unchecked")
    private static void requireCorrectMethod(@NonNull StaplerRequest req) throws IllegalAccessException {
        if (!(req.getMethod().equals("POST")
                || (req.getMethod().equals("OPTIONS") && StringUtils.isNotBlank(req.getHeader("Origin")))
                || (req.getMethod().equals("GET") && getKeyFromAuthorizationHeader(req) != null)
        )) {
            throw new IllegalAccessException("POST is required");
        }
    }

    @CheckForNull
    private static String getKeyFromAuthorizationHeader(@NonNull StaplerRequest req) throws IllegalAccessException {
        for (Object o : Collections.list(req.getHeaders("Authorization"))) {
            if (o instanceof String && ((String) o).startsWith("Jenkins-Metrics-Key ")) {
                return Util.fixEmptyAndTrim(((String) o).substring("Jenkins-Metrics-Key ".length()));
            }
        }
        return null;
    }

    public String getIconFileName() {
        return null;
    }

    public String getDisplayName() {
        return null;
    }

    public String getUrlName() {
        return "metrics";
    }

    public Object getDynamic(final String key) {
        Metrics.checkAccessKey(key);
        return new AccessKeyPseudoservlet(key);
    }

    public Object getCurrentUser() {
        Jenkins.getInstance().checkPermission(Metrics.VIEW);
        return currentUser;
    }

    public HttpResponse doHealthcheck(StaplerRequest req, @QueryParameter("key") String key)
            throws IllegalAccessException {
        requireCorrectMethod(req);
        if (StringUtils.isBlank(key)) {
            key = getKeyFromAuthorizationHeader(req);
        }
        Metrics.checkAccessKeyHealthCheck(key);
        return Metrics.cors(key, new HealthCheckResponse(Metrics.healthCheckRegistry().runHealthChecks()));
    }

    /**
     * Condense the health check into one bit of information
     * for frontend reverse proxies like haproxy.
     *
     * Other health check calls requires authentication, which
     * is not suitable for the haproxy use. But this endpoint
     * only exposes one bit information, it's deemed OK to be exposed
     * unsecurely.
     *
     * return status 200 if everything is OK, 417 (expectation failed) otherwise
     */
    public HttpResponse doHealthcheckOk() {
        SortedMap<String, HealthCheck.Result> checks =  Metrics.healthCheckRegistry().runHealthChecks();
        boolean allOk = true;
        for(Map.Entry<String, HealthCheck.Result> entry: checks.entrySet()){
            HealthCheck.Result result = entry.getValue();
            if(!result.isHealthy()){
                allOk = false;
                break;
            }
        }
        return HttpResponses.status(allOk ? 200 : 417);
    }

    public HttpResponse doMetrics(StaplerRequest req, @QueryParameter("key") String key) throws IllegalAccessException {
        requireCorrectMethod(req);
        if (StringUtils.isBlank(key)) {
            key = getKeyFromAuthorizationHeader(req);
        }
        Metrics.checkAccessKeyMetrics(key);
        return Metrics.cors(key, new MetricsResponse(Metrics.metricRegistry()));
    }

    public HttpResponse doMetricsHistory(StaplerRequest req, @QueryParameter("key") String key)
            throws IllegalAccessException {
        requireCorrectMethod(req);
        if (StringUtils.isBlank(key)) {
            key = getKeyFromAuthorizationHeader(req);
        }
        Metrics.checkAccessKeyMetrics(key);
        return Metrics.cors(key, new MetricsHistoryResponse());
    }

    public HttpResponse doPing(StaplerRequest req, @QueryParameter("key") String key) throws IllegalAccessException {
        requireCorrectMethod(req);
        if (StringUtils.isBlank(key)) {
            key = getKeyFromAuthorizationHeader(req);
        }
        Metrics.checkAccessKeyPing(key);<commit_msg>Based on code review comment from Jesse, changing to 503.<commit_after>/*
 * The MIT License
 *
 * Copyright (c) 2014, 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.metrics.api;

import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.health.HealthCheck;
import com.codahale.metrics.json.HealthCheckModule;
import com.codahale.metrics.json.MetricsModule;
import com.codahale.metrics.jvm.ThreadDump;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.SuppressWarnings;
import hudson.Extension;
import hudson.Util;
import hudson.model.PeriodicWork;
import hudson.model.UnprotectedRootAction;
import hudson.util.HttpResponses;
import hudson.util.IOUtils;
import jenkins.metrics.util.ExponentialLeakyBucket;
import jenkins.metrics.util.NameRewriterMetricRegistry;
import jenkins.model.Jenkins;
import org.apache.commons.lang.StringUtils;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.interceptor.RequirePOST;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.lang.management.ManagementFactory;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

import static com.codahale.metrics.MetricRegistry.name;

/**
 * @author <NAME>
 */
@Extension
public class MetricsRootAction implements UnprotectedRootAction {

    public static final TimeUnit RATE_UNIT = TimeUnit.MINUTES;
    public static final TimeUnit DURATION_UNIT = TimeUnit.SECONDS;
    private final Pseudoservlet currentUser = new CurrentUserPseudoservlet();
    private final ObjectMapper healthCheckMapper = new ObjectMapper().registerModule(new HealthCheckModule());
    private final ObjectMapper metricsMapper =
            new ObjectMapper().registerModule(new MetricsModule(RATE_UNIT, DURATION_UNIT, true));

    private static boolean isAllHealthy(Map<String, HealthCheck.Result> results) {
        for (HealthCheck.Result result : results.values()) {
            if (!result.isHealthy()) {
                return false;
            }
        }
        return true;
    }

    private static ObjectWriter getWriter(ObjectMapper mapper, HttpServletRequest request) {
        final boolean prettyPrint = Boolean.parseBoolean(request.getParameter("pretty"));
        if (prettyPrint) {
            return mapper.writerWithDefaultPrettyPrinter();
        }
        return mapper.writer();
    }

    @SuppressWarnings("unchecked")
    private static void requireCorrectMethod(@NonNull StaplerRequest req) throws IllegalAccessException {
        if (!(req.getMethod().equals("POST")
                || (req.getMethod().equals("OPTIONS") && StringUtils.isNotBlank(req.getHeader("Origin")))
                || (req.getMethod().equals("GET") && getKeyFromAuthorizationHeader(req) != null)
        )) {
            throw new IllegalAccessException("POST is required");
        }
    }

    @CheckForNull
    private static String getKeyFromAuthorizationHeader(@NonNull StaplerRequest req) throws IllegalAccessException {
        for (Object o : Collections.list(req.getHeaders("Authorization"))) {
            if (o instanceof String && ((String) o).startsWith("Jenkins-Metrics-Key ")) {
                return Util.fixEmptyAndTrim(((String) o).substring("Jenkins-Metrics-Key ".length()));
            }
        }
        return null;
    }

    public String getIconFileName() {
        return null;
    }

    public String getDisplayName() {
        return null;
    }

    public String getUrlName() {
        return "metrics";
    }

    public Object getDynamic(final String key) {
        Metrics.checkAccessKey(key);
        return new AccessKeyPseudoservlet(key);
    }

    public Object getCurrentUser() {
        Jenkins.getInstance().checkPermission(Metrics.VIEW);
        return currentUser;
    }

    public HttpResponse doHealthcheck(StaplerRequest req, @QueryParameter("key") String key)
            throws IllegalAccessException {
        requireCorrectMethod(req);
        if (StringUtils.isBlank(key)) {
            key = getKeyFromAuthorizationHeader(req);
        }
        Metrics.checkAccessKeyHealthCheck(key);
        return Metrics.cors(key, new HealthCheckResponse(Metrics.healthCheckRegistry().runHealthChecks()));
    }

    /**
     * Condense the health check into one bit of information
     * for frontend reverse proxies like haproxy.
     *
     * Other health check calls requires authentication, which
     * is not suitable for the haproxy use. But this endpoint
     * only exposes one bit information, it's deemed OK to be exposed
     * unsecurely.
     *
     * return status 200 if everything is OK, 503 (service unavailable) otherwise
     */
    public HttpResponse doHealthcheckOk() {
        SortedMap<String, HealthCheck.Result> checks =  Metrics.healthCheckRegistry().runHealthChecks();
        boolean allOk = true;
        for(Map.Entry<String, HealthCheck.Result> entry: checks.entrySet()){
            HealthCheck.Result result = entry.getValue();
            if(!result.isHealthy()){
                allOk = false;
                break;
            }
        }
        return HttpResponses.status(allOk ? 200 : 503);
    }

    public HttpResponse doMetrics(StaplerRequest req, @QueryParameter("key") String key) throws IllegalAccessException {
        requireCorrectMethod(req);
        if (StringUtils.isBlank(key)) {
            key = getKeyFromAuthorizationHeader(req);
        }
        Metrics.checkAccessKeyMetrics(key);
        return Metrics.cors(key, new MetricsResponse(Metrics.metricRegistry()));
    }

    public HttpResponse doMetricsHistory(StaplerRequest req, @QueryParameter("key") String key)
            throws IllegalAccessException {
        requireCorrectMethod(req);
        if (StringUtils.isBlank(key)) {
            key = getKeyFromAuthorizationHeader(req);
        }
        Metrics.checkAccessKeyMetrics(key);
        return Metrics.cors(key, new MetricsHistoryResponse());
    }

    public HttpResponse doPing(StaplerRequest req, @QueryParameter("key") String key) throws IllegalAccessException {
        requireCorrectMethod(req);
        if (StringUtils.isBlank(key)) {
            key = getKeyFromAuthorizationHeader(req);
        }
        Metrics.checkAccessKeyPing(key);
{'fraction_non_alphanumeric': 0.0905112989270515, 'fraction_numerical': 0.002097944014865432, 'mean_word_length': 3.6331574562621496, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 14}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3016129', 'n_tokens_mistral': 4642, 'n_tokens_neox': 4401, 'n_words': 1170}