All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.xceptance.xlt.engine.metrics.graphite.GraphiteMetricsReporter Maven / Gradle / Ivy

Go to download

XLT (Xceptance LoadTest) is an extensive load and performance test tool developed and maintained by Xceptance.

There is a newer version: 8.6.0
Show newest version
/*
 * Copyright (c) 2005-2024 Xceptance Software Technologies GmbH
 *
 * 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 com.xceptance.xlt.engine.metrics.graphite;

import java.net.UnknownHostException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.lang3.CharUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.xceptance.xlt.agent.JvmResourceUsageData;
import com.xceptance.xlt.api.engine.ActionData;
import com.xceptance.xlt.api.engine.CustomData;
import com.xceptance.xlt.api.engine.Data;
import com.xceptance.xlt.api.engine.EventData;
import com.xceptance.xlt.api.engine.PageLoadTimingData;
import com.xceptance.xlt.api.engine.RequestData;
import com.xceptance.xlt.api.engine.Session;
import com.xceptance.xlt.api.engine.TransactionData;
import com.xceptance.xlt.engine.metrics.CounterMetric;
import com.xceptance.xlt.engine.metrics.Metric;
import com.xceptance.xlt.engine.metrics.MetricsReporter;
import com.xceptance.xlt.engine.metrics.RateMetric;
import com.xceptance.xlt.engine.metrics.ValueMetric;

public class GraphiteMetricsReporter implements MetricsReporter
{
    private static final Logger log = LoggerFactory.getLogger(GraphiteMetricsReporter.class);

    private static final int ONE_SEC = 1000;

    private static final int ONE_HOUR = 3600 * ONE_SEC;

    private final int reportingInterval;

    /**
     * The current agent's ID. All characters illegal for Graphite have been sanitized.
     */
    private final String sanitizedAgentId;

    /**
     * All the currently known metrics, keyed by metric name.
     */
    private final Map metricsRegistry = new ConcurrentHashMap();

    public GraphiteMetricsReporter(final int interval, final String host, final int port, String metricsNamePrefix)
        throws UnknownHostException, IllegalArgumentException
    {
        this.reportingInterval = interval * ONE_SEC;

        // sanitize the metric name prefix and add a trailing dot if not present yet
        metricsNamePrefix = sanitizeFullMetricName(metricsNamePrefix);
        if (!metricsNamePrefix.endsWith("."))
        {
            metricsNamePrefix = metricsNamePrefix + ".";
        }

        // get and sanitize the current agent's ID now, we will need it often
        sanitizedAgentId = sanitizeMetricNamePart(Session.getCurrent().getAgentID());

        final PlainTextCarbonClient carbonClient = new PlainTextCarbonClient(host, port);
        final GraphiteReporter reporter = new GraphiteReporter(carbonClient, metricsRegistry, metricsNamePrefix, reportingInterval);
        reporter.start();

        if (log.isInfoEnabled())
        {
            log.info(String.format("Started reporting metrics to Graphite server %s:%d every %d second(s)", host, port, interval));
        }

    }

    public void reportMetrics(final Data data)
    {
        // do the right thing depending on the data type
        if (data instanceof RequestData)
        {
            updateRequestMetrics((RequestData) data);
        }
        else if (data instanceof ActionData)
        {
            updateActionMetrics((ActionData) data);
        }
        else if (data instanceof TransactionData)
        {
            updateTransactionMetrics((TransactionData) data);
        }
        else if (data instanceof PageLoadTimingData)
        {
            updatePageLoadTimingMetrics((PageLoadTimingData) data);
        }
        else if (data instanceof CustomData)
        {
            updateCustomTimerMetrics((CustomData) data);
        }
        else if (data instanceof EventData)
        {
            updateEventMetrics((EventData) data);
        }
        else if (data instanceof JvmResourceUsageData)
        {
            updateJvmMetrics((JvmResourceUsageData) data);
        }
    }

    private void updateTransactionMetrics(final TransactionData transactionData)
    {
        // metrics per transaction name
        final String sanitizedName = sanitizeMetricNamePart(transactionData.getName());
        final String metricPrefix = sanitizedAgentId + ".transactions." + sanitizedName + ".";

        updateValueMetric(metricPrefix + "runtime", (int) transactionData.getRunTime());
        updateCounterMetric(metricPrefix + "errors", transactionData.hasFailed() ? 1 : 0);
        updateRateMetric(metricPrefix + "arrivals_1h", 1, ONE_HOUR, reportingInterval);

        // summary metrics
        final String summaryMetricPrefix = sanitizedAgentId + ".summary.transactions.";

        updateValueMetric(summaryMetricPrefix + "runtime", (int) transactionData.getRunTime());
        updateCounterMetric(summaryMetricPrefix + "count", 1);
        updateCounterMetric(summaryMetricPrefix + "errors", transactionData.hasFailed() ? 1 : 0);
        updateRateMetric(summaryMetricPrefix + "arrivals_1h", 1, ONE_HOUR, reportingInterval);
    }

    private void updateActionMetrics(final ActionData actionData)
    {
        // metrics per action name
        final String sanitizedName = sanitizeMetricNamePart(actionData.getName());
        final String metricPrefix = sanitizedAgentId + ".actions." + sanitizedName + ".";

        updateValueMetric(metricPrefix + "runtime", (int) actionData.getRunTime());
        updateCounterMetric(metricPrefix + "errors", actionData.hasFailed() ? 1 : 0);

        // summary metrics
        final String summaryMetricPrefix = sanitizedAgentId + ".summary.actions.";

        updateValueMetric(summaryMetricPrefix + "runtime", (int) actionData.getRunTime());
        updateCounterMetric(summaryMetricPrefix + "count", 1);
        updateCounterMetric(summaryMetricPrefix + "errors", actionData.hasFailed() ? 1 : 0);
    }

    private void updateRequestMetrics(final RequestData requestData)
    {
        // first get rid of the sub request numbering ("Foo.1.1" -> "Foo")
        final String strippedRequestName = StringUtils.substringBefore(requestData.getName(), ".");

        // metrics per request name
        final String sanitizedName = sanitizeMetricNamePart(strippedRequestName);
        final String metricPrefix = sanitizedAgentId + ".requests." + sanitizedName + ".";

        updateValueMetric(metricPrefix + "runtime", (int) requestData.getRunTime());
        updateCounterMetric(metricPrefix + "errors", requestData.hasFailed() ? 1 : 0);

        // summary metrics
        final String summaryMetricPrefix = sanitizedAgentId + ".summary.requests.";

        updateValueMetric(summaryMetricPrefix + "runtime", (int) requestData.getRunTime());
        updateCounterMetric(summaryMetricPrefix + "count", 1);
        updateCounterMetric(summaryMetricPrefix + "errors", requestData.hasFailed() ? 1 : 0);
        updateRateMetric(summaryMetricPrefix + "bytesSent_1s", requestData.getBytesSent(), ONE_SEC, reportingInterval);
        updateRateMetric(summaryMetricPrefix + "bytesReceived_1s", requestData.getBytesReceived(), ONE_SEC, reportingInterval);
    }

    private void updatePageLoadTimingMetrics(final PageLoadTimingData pageLoadTimingData)
    {
        // metrics per page load timing name
        final String sanitizedName = sanitizeMetricNamePart(pageLoadTimingData.getName());
        final String metricPrefix = sanitizedAgentId + ".pageLoadTimings." + sanitizedName + ".";

        updateValueMetric(metricPrefix + "runtime", (int) pageLoadTimingData.getRunTime());
        updateCounterMetric(metricPrefix + "errors", pageLoadTimingData.hasFailed() ? 1 : 0);

        // summary metrics
        final String summaryMetricPrefix = sanitizedAgentId + ".summary.pageLoadTimings.";

        updateValueMetric(summaryMetricPrefix + "runtime", (int) pageLoadTimingData.getRunTime());
        updateCounterMetric(summaryMetricPrefix + "count", 1);
        updateCounterMetric(summaryMetricPrefix + "errors", pageLoadTimingData.hasFailed() ? 1 : 0);
    }

    private void updateCustomTimerMetrics(final CustomData customData)
    {
        // metrics per custom timer name
        final String sanitizedName = sanitizeMetricNamePart(customData.getName());
        final String metricPrefix = sanitizedAgentId + ".custom." + sanitizedName + ".";

        updateValueMetric(metricPrefix + "runtime", (int) customData.getRunTime());
        updateCounterMetric(metricPrefix + "errors", customData.hasFailed() ? 1 : 0);

        // summary metrics
        final String summaryMetricPrefix = sanitizedAgentId + ".summary.custom.";

        updateValueMetric(summaryMetricPrefix + "runtime", (int) customData.getRunTime());
        updateCounterMetric(summaryMetricPrefix + "count", 1);
        updateCounterMetric(summaryMetricPrefix + "errors", customData.hasFailed() ? 1 : 0);
    }

    private void updateEventMetrics(final EventData eventData)
    {
        // summary metrics
        final String summaryMetricPrefix = sanitizedAgentId + ".summary.events.";

        updateCounterMetric(summaryMetricPrefix + "count", 1);
    }

    private void updateJvmMetrics(final JvmResourceUsageData jvmData)
    {
        // metrics per agent
        final String metricPrefix = sanitizedAgentId + ".agent.";

        updateValueMetric(metricPrefix + "heapUsage", (int) jvmData.getHeapUsage());
        updateValueMetric(metricPrefix + "totalCpuUsage", (int) jvmData.getTotalCpuUsage());
    }

    /**
     * Updates the counter metric with the given name.
     *
     * @param metricName
     *            the name of the metric
     * @param value
     *            the value to add to the counter
     */
    private void updateCounterMetric(final String metricName, final int value)
    {
        Metric metric = metricsRegistry.get(metricName);
        if (metric == null)
        {
            metric = getExistingOrAddNewMetric(metricName, new CounterMetric());
        }

        metric.update(value);
    }

    /**
     * Updates the rate metric with the given name.
     *
     * @param metricName
     *            the name of the metric
     * @param value
     *            the value to add
     * @param rateInterval
     *            the rate interval [ms]
     * @param reportingInterval
     *            the reporting interval [ms]
     */
    private void updateRateMetric(final String metricName, final int value, final int rateInterval, final int reportingInterval)
    {
        Metric metric = metricsRegistry.get(metricName);
        if (metric == null)
        {
            metric = getExistingOrAddNewMetric(metricName, new RateMetric(rateInterval, reportingInterval));
        }

        metric.update(value);
    }

    /**
     * Updates the value metric with the given name.
     *
     * @param metricName
     *            the name of the metric
     * @param value
     *            the value
     */
    private void updateValueMetric(final String metricName, final int value)
    {
        Metric metric = metricsRegistry.get(metricName);
        if (metric == null)
        {
            metric = getExistingOrAddNewMetric(metricName, new ValueMetric());
        }

        metric.update(value);
    }

    /**
     * Gets a metric from the metrics registry, or if it does not exist yet, adds the given metric to the registry and
     * returns that one.
     *
     * @param metricName
     *            the name of the metric
     * @param newMetric
     *            the metric to add in case there is no such metric yet
     */
    private synchronized Metric getExistingOrAddNewMetric(final String metricName, final Metric newMetric)
    {
        Metric metric = metricsRegistry.get(metricName);
        if (metric == null)
        {
            metric = newMetric;
            metricsRegistry.put(metricName, metric);
        }

        return metric;
    }

    /**
     * Sanitizes the given full metric name such that any invalid character is replaced with an underscore. Valid
     * characters are letters, numbers, the underscore, the minus sign, and the dot.
     *
     * @param metricName
     *            the name of the metric
     * @return the sanitized metric name
     */
    private String sanitizeFullMetricName(final String metricName)
    {
        return sanitizeMetricName(metricName, true);
    }

    /**
     * Sanitizes the given metric name part such that any invalid character is replaced with an underscore. Valid
     * characters are letters, numbers, the underscore, and the minus sign.
     *
     * @param metricNamePart
     *            one part of a full metric name
     * @return the sanitized metric name part
     */
    private String sanitizeMetricNamePart(final String metricNamePart)
    {
        return sanitizeMetricName(metricNamePart, false);
    }

    /**
     * Sanitizes the given metric name such that any invalid character is replaced with an underscore. Valid characters
     * are letters, numbers, the underscore, and the minus sign. Whether or not the dot is valid as well can be
     * controlled by the second parameter.
     * 

* Note that Graphite may allow more characters, but not always in all combinations (e.g., aaa[] is OK, while aaa[a] * is not). That's why we are somewhat over-restrictive here. Better safe than sorry. * * @param metricName * the name of the metric * @param dotsAllowed * whether a dot is also a valid character * @return the sanitized metric name */ private String sanitizeMetricName(final String metricName, boolean dotsAllowed) { final char[] chars = metricName.toCharArray(); for (int i = 0; i < chars.length; i++) { final char c = chars[i]; final boolean isValidChar = CharUtils.isAsciiAlphanumeric(c) || c == '_' || c == '-' || (c == '.' && dotsAllowed); if (!isValidChar) { chars[i] = '_'; } } return new String(chars); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy