/*
* Copyright 2016 JBoss Inc
*
* 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 io.apiman.gateway.engine.hawkular;
import io.apiman.common.config.options.HttpConnectorOptions;
import io.apiman.common.net.hawkular.HawkularMetricsClient;
import io.apiman.common.net.hawkular.beans.MetricLongBean;
import io.apiman.common.net.hawkular.beans.MetricType;
import io.apiman.gateway.engine.IComponentRegistry;
import io.apiman.gateway.engine.IMetrics;
import io.apiman.gateway.engine.metrics.RequestMetric;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
/**
* An implementation of the apiman {@link IMetrics} interface that pushes metrics
* data to a Hawkular Metrics server. For more information about Hawkular Metrics,
* see: http://www.hawkular.org/docs/components/metrics/index.html
*
* @author eric.wittmann@gmail.com
*/
public class HawkularMetrics implements IMetrics {
private static final int DEFAULT_QUEUE_SIZE = 10000;
private final HawkularMetricsClient client;
private final BlockingQueue<QueueItem> queue;
/**
* Constructor.
* @param config
*/
@SuppressWarnings("nls")
public HawkularMetrics(Map<String, String> config) {
String endpoint = config.get("hawkular.endpoint");
if (endpoint == null) {
throw new RuntimeException("Missing configuration property: apiman-gateway.metrics.hawkular.endpoint");
}
Map<String, String> httpOptions = new HashMap<>();
httpOptions.put("http.timeouts.read", config.get("http.timeouts.read"));
httpOptions.put("http.timeouts.write", config.get("http.timeouts.write"));
httpOptions.put("http.timeouts.connect", config.get("http.timeouts.connect"));
httpOptions.put("http.followRedirects", config.get("http.followRedirects"));
client = new HawkularMetricsClient(endpoint, new HttpConnectorOptions(httpOptions));
int queueSize = DEFAULT_QUEUE_SIZE;
String queueSizeConfig = config.get("hawkular.queueSize");
if (queueSizeConfig != null) {
queueSize = new Integer(queueSizeConfig);
}
queue = new LinkedBlockingDeque<>(queueSize);
startConsumerThread();
}
/**
* Starts a thread which will serially pull information off the blocking
* queue and submit that information to hawkular metrics.
*/
private void startConsumerThread() {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while (Boolean.TRUE) {
processQueue();
}
}
}, "HawkularMetricsConsumer"); //$NON-NLS-1$
thread.setDaemon(true);
thread.start();
}
/**
* Process the next item in the queue.
*/
protected void processQueue() {
try {
QueueItem item = queue.take();
client.addMultipleCounterDataPoints(item.tenantId, item.data);
} catch (InterruptedException e) {
// TODO better logging of this unlikely error
e.printStackTrace();
return;
}
}
/**
* @see io.apiman.gateway.engine.IMetrics#record(io.apiman.gateway.engine.metrics.RequestMetric)
*/
@Override
public void record(RequestMetric metric) {
// Record data points (potentially) for the following metrics:
// 1) # of total requests (always)
// 2) # of failures (only when a failure)
// 3) # of errors (only when an error)
String tenantId = metric.getApiOrgId();
List<MetricLongBean> data = new ArrayList<>();
// ******************************
// API metrics
// ******************************
{
@SuppressWarnings("nls")
Map<String, String> tags = HawkularMetricsClient.tags(
"planId", metric.getPlanId(),
"clientOrgId", metric.getClientOrgId(),
"clientId", metric.getClientId(),
"clientVersion", metric.getClientVersion());
// # of total requests
MetricLongBean totalRequests = new MetricLongBean();
totalRequests.addDataPoint(metric.getRequestStart(), 1).setTags(tags);
totalRequests.setId("apis." + metric.getApiId() + "." + metric.getApiVersion() + ".Requests.Total"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
totalRequests.setType(MetricType.counter);
data.add(totalRequests);
// # of failures
if (metric.isFailure()) {
MetricLongBean failedRequests = new MetricLongBean();
failedRequests.addDataPoint(metric.getRequestStart(), 1).setTags(tags);
failedRequests.setId("apis." + metric.getApiId() + "." + metric.getApiVersion() + ".Requests.Failed"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
failedRequests.setType(MetricType.counter);
data.add(failedRequests);
}
// # of errors
if (metric.isError()) {
MetricLongBean erroredRequests = new MetricLongBean();
erroredRequests.addDataPoint(metric.getRequestStart(), 1).setTags(tags);
erroredRequests.setId("apis." + metric.getApiId() + "." + metric.getApiVersion() + ".Requests.Errored"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
erroredRequests.setType(MetricType.counter);
data.add(erroredRequests);
}
}
// ******************************
// Client metrics
// ******************************
{
@SuppressWarnings("nls")
Map<String, String> tags = HawkularMetricsClient.tags(
"planId", metric.getPlanId(),
"apiOrgId", metric.getApiOrgId(),
"apiId", metric.getApiId(),
"apiVersion", metric.getApiVersion());
// # of total requests
MetricLongBean totalRequests = new MetricLongBean();
totalRequests.addDataPoint(metric.getRequestStart(), 1).setTags(tags);
totalRequests.setId("clients." + metric.getClientId() + "." + metric.getClientVersion() + ".Requests.Total"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
totalRequests.setType(MetricType.counter);
data.add(totalRequests);
// # of failures
if (metric.isFailure()) {
MetricLongBean failedRequests = new MetricLongBean();
failedRequests.addDataPoint(metric.getRequestStart(), 1).setTags(tags);
failedRequests.setId("clients." + metric.getClientId() + "." + metric.getClientVersion() + ".Requests.Failed"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
failedRequests.setType(MetricType.counter);
data.add(failedRequests);
}
// # of errors
if (metric.isError()) {
MetricLongBean erroredRequests = new MetricLongBean();
erroredRequests.addDataPoint(metric.getRequestStart(), 1).setTags(tags);
erroredRequests.setId("clients." + metric.getClientId() + "." + metric.getClientVersion() + ".Requests.Errored"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
erroredRequests.setType(MetricType.counter);
data.add(erroredRequests);
}
}
try {
queue.put(new QueueItem(tenantId, data));
} catch (InterruptedException e) {
// TODO better logging of this unlikely error
e.printStackTrace();
}
}
/**
* @see io.apiman.gateway.engine.IMetrics#setComponentRegistry(io.apiman.gateway.engine.IComponentRegistry)
*/
@Override
public void setComponentRegistry(IComponentRegistry registry) {
}
private static class QueueItem {
public String tenantId;
public List<MetricLongBean> data;
/**
* Constructor.
*/
public QueueItem(String tenantId, List<MetricLongBean> data) {
this.tenantId = tenantId;
this.data = data;
}
}
}