Java tutorial
/** * The MIT License * Copyright (c) 2016 Estonian Information System Authority (RIA), Population Register Centre (VRK) * * 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 ee.ria.xroad.opmonitordaemon; import java.util.SortedMap; import java.util.regex.Pattern; import com.codahale.metrics.Counter; import com.codahale.metrics.Gauge; import com.codahale.metrics.Histogram; import com.codahale.metrics.MetricRegistry; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringEscapeUtils; import org.apache.commons.lang3.StringUtils; import ee.ria.xroad.common.identifier.ServiceId; /** * Helper utilities for preparing and processing health data metrics. */ @Slf4j final class HealthDataMetricsUtil { // The template of the names of the metrics that are registered each time // a new service ID and request status pair is encountered, for storing // the last request timestamp. We use the response out timestamp of the // respective operational data record. private static final String LAST_REQUEST_TIMESTAMP_TEMPLATE = "last%sRequestTimestamp(%s)"; // The template for the names of the sliding window counters that are // registered each time a new service ID and request status pair is // encountered, for storing the request count. private static final String REQUEST_COUNT_TEMPLATE = "%sRequestCount(%s)"; // The template for the names of the sliding window histograms that are // registered each time a new service ID is encountered (successful // requests only), for storing the SOAP sizes of requests. private static final String REQUEST_SOAP_SIZE_TEMPLATE = "requestSoapSize(%s)"; // The template for the names of the sliding window histograms that are // registered each time a new service ID is encountered (successful // requests only), for storing the SOAP sizes of responses. private static final String RESPONSE_SOAP_SIZE_TEMPLATE = "responseSoapSize(%s)"; // The template for the names of the sliding window histograms that are // registered each time a new service ID is encountered (successful // requests only), for storing the duration of requests. private static final String REQUEST_DURATION_TEMPLATE = "requestDuration(%s)"; private HealthDataMetricsUtil() { } /** * @param record an operational data record * @return the service ID the record describes or null if the relevant * fields do not have a value. */ static ServiceId getServiceId(OperationalDataRecord record) { try { return ServiceId.create(record.getServiceXRoadInstance(), record.getServiceMemberClass(), record.getServiceMemberCode(), record.getServiceSubsystemCode(), record.getServiceCode(), record.getServiceVersion()); } catch (Exception e) { return null; } } /** * Escape the minimum set of reserved characters in XML so our metrics * can be forwarded over JMX. Plus, because of the syntax of Zabbix * parameters, escape the dot, the space, the comma, the backslash and * square brackets manually. * Note that the character set of the XRoad identifiers has not been * limited strictly. However, to be able to convert the identifier string * with forward slashes as separators, we escape the forward slash inside * identifier elements. * For consistency, we use HTML escape sequences for all the replacements. * If the subsystem code is missing, the generated short string contains an * empty character between forward slash separators where the subsystem code * would generally be. * @param serviceId the service ID as obtained using getServiceIdInRecord() * @return the escaped short form of the service ID suitable for using * inside a JMX parameter name. */ static String escapeServiceId(ServiceId serviceId) { StringBuilder sb = new StringBuilder(); sb.append(escapeString(serviceId.getXRoadInstance())); sb.append('/'); sb.append(escapeString(serviceId.getMemberClass())); sb.append('/'); sb.append(escapeString(serviceId.getMemberCode())); sb.append('/'); if (!StringUtils.isEmpty(serviceId.getSubsystemCode())) { sb.append(escapeString(serviceId.getSubsystemCode())); } sb.append('/'); sb.append(escapeString(serviceId.getServiceCode())); if (!StringUtils.isEmpty(serviceId.getServiceVersion())) { sb.append('/'); sb.append(escapeString(serviceId.getServiceVersion())); } return sb.toString(); } private static String escapeString(String string) { return StringEscapeUtils.escapeXml11(string) // For Zabbix: escape the dots that are part of the service // ID so Zabbix won't split the value when processing the // key of the item such as: // jmx[com.example:Type=Hello,all.fruits.apple.weight] .replaceAll("\\.", ".") // For Zabbix: escape the backslash, the space and the comma: .replaceAll("\\\\", "\").replaceAll(" ", " ").replaceAll(",", ",") // For Zabbix: escape the square brackets that are used as // the containers of the JMX parameter name and attribute // such as: // jmx[<parameter name [with square brackets], attribute>]. .replaceAll("\\[", "[").replaceAll("]", "]") // Replace forward slashes to be able to convert the identifier // string back into a service identifier object .replaceAll("/", "/"); } /** * @param metricName an metric name where the service ID part has been * escaped using escapeServiceId(). * @return the quoted metric name suitable for passing to a regular * expression matcher method */ static String formatMetricMatchRegexp(String metricName) { return String.format("^%s$", Pattern.quote(metricName)); } /** * @param serviceId the service ID as obtained using getServiceIdInRecord() * @param parameterKeyTemplate template string of the JMX parameter name * @param requestSucceeded set to true for a successfully mediated request * @return the key of the parameter suitable for forwarding over JMX to * Zabbix. */ private static String formatParameterAndStatusKey(ServiceId serviceId, String parameterKeyTemplate, boolean requestSucceeded, boolean uppercase) { return String.format(parameterKeyTemplate, requestSucceeded ? (uppercase ? "Successful" : "successful") : (uppercase ? "Unsuccessful" : "unsuccessful"), escapeServiceId(serviceId)); } static String getRequestCounterName(ServiceId serviceId, boolean success) { return formatParameterAndStatusKey(serviceId, REQUEST_COUNT_TEMPLATE, success, false); } static String getLastRequestTimestampGaugeName(ServiceId serviceId, boolean success) { return formatParameterAndStatusKey(serviceId, LAST_REQUEST_TIMESTAMP_TEMPLATE, success, true); } static String getRequestDurationName(ServiceId serviceId) { return formatParameterKey(serviceId, REQUEST_DURATION_TEMPLATE); } static String getRequestSoapSizeName(ServiceId serviceId) { return formatParameterKey(serviceId, REQUEST_SOAP_SIZE_TEMPLATE); } static String getResponseSoapSizeName(ServiceId serviceId) { return formatParameterKey(serviceId, RESPONSE_SOAP_SIZE_TEMPLATE); } /** * @param serviceId the service ID as obtained using getServiceIdInRecord() * @param parameterKeyTemplate template string of the JMX parameter name * @return the key of the parameter suitable for forwarding over JMX to * Zabbix. */ static String formatParameterKey(ServiceId serviceId, String parameterKeyTemplate) { return String.format(parameterKeyTemplate, escapeServiceId(serviceId)); } /** * @param registry the metric registry where the gauge should be looked up * @param expectedGaugeName the gauge name to find * @return the found gauge or null if it does not exist */ static Gauge findGauge(MetricRegistry registry, String expectedGaugeName) { SortedMap<String, Gauge> gauges = registry.getGauges( (name, metric) -> name.matches(HealthDataMetricsUtil.formatMetricMatchRegexp(expectedGaugeName))); if (gauges.size() > 1) { // Should not happen because we use a strict regexp. log.warn("Multiple gauges matched the name " + expectedGaugeName); } return gauges.isEmpty() ? null : gauges.values().iterator().next(); } /** * @param registry the metric registry where the counter should be looked up * @param expectedCounterName the counter name to find * @return the found counter or null if it does not exist */ static Counter findCounter(MetricRegistry registry, String expectedCounterName) { SortedMap<String, Counter> counters = registry.getCounters( (name, metric) -> name.matches(HealthDataMetricsUtil.formatMetricMatchRegexp(expectedCounterName))); if (counters.size() > 1) { // Should not happen because we use a strict regexp. log.warn("Multiple counters matched the name " + expectedCounterName); } return counters.isEmpty() ? null : counters.values().iterator().next(); } /** * @param registry the metric registry where the histogram should be * looked up * @param expectedHistogramName the counter name to find * @return the found histogram or null if it does not exist */ static Histogram findHistogram(MetricRegistry registry, String expectedHistogramName) { SortedMap<String, Histogram> histograms = registry.getHistograms((name, metric) -> name .matches(HealthDataMetricsUtil.formatMetricMatchRegexp(expectedHistogramName))); if (histograms.size() > 1) { // Should not happen because we use a strict regexp. log.warn("Multiple histograms matched the name " + expectedHistogramName); } return histograms.isEmpty() ? null : histograms.values().iterator().next(); } }