/**
* Copyright (c) Codice Foundation
* <p>
* This is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser
* General Public License as published by the Free Software Foundation, either version 3 of the
* License, or any later version.
* <p>
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details. A copy of the GNU Lesser General Public License
* is distributed along with this program and can be found at
* <http://www.gnu.org/licenses/lgpl.html>.
*/
package ddf.metrics.reporting.internal.rest;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TimeZone;
import java.util.TreeMap;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.UriInfo;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.StringUtils;
import org.codice.ddf.configuration.AbsolutePathResolver;
import org.codice.ddf.configuration.SystemBaseUrl;
import org.codice.ddf.configuration.SystemInfo;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;
import org.json.simple.JSONValue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ddf.metrics.reporting.internal.MetricsEndpointException;
import ddf.metrics.reporting.internal.MetricsGraphException;
import ddf.metrics.reporting.internal.MetricsRetriever;
import ddf.metrics.reporting.internal.rrd4j.RrdMetricsRetriever;
/**
* This class provides an endpoint for a client to access the historical metrics data collected by DDF.
* <p>
* This endpoint provides a URL to retrieve the list of metrics collected by DDF, including their
* associated URLs to access pre-defined time ranges of each metric's historical data, e.g., for the
* past 15 minutes, 1 hour, 4 hours, 12 hours, 24 hours, 3 days, 1 week, 1 month, and 1 year. Each
* of these hyperlinks will return a byte array containing a PNG graph of the metric's historical
* data for the given time range.
*/
@Path("/")
public class MetricsEndpoint {
public static final String DEFAULT_METRICS_DIR = new AbsolutePathResolver("data" + File.separator + "metrics" + File.separator).getPath();
static final Map<String, Long> TIME_RANGES = new HashMap<String, Long>();
private static final Logger LOGGER = LoggerFactory.getLogger(MetricsEndpoint.class);
private static final String METRICS_SERVICE_BASE_URL = "/internal/metrics";
private static final String RRD_FILE_EXTENSION = ".rrd";
private static final String JSON_MIME_TYPE = "application/json";
private static final String PNG_MIME_TYPE = "image/png";
private static final String START_DATE_QUERY = "?startDate=";
private static final String END_DATE_QUERY = "&endDate=";
private static final String DATE_OFFSET_QUERY = "?dateOffset=";
private static final int MILLISECONDS_PER_SECOND = 1000;
private static final long FIFTEEN_MINUTES_IN_SECONDS = 15 * 60;
private static final long ONE_HOUR_IN_SECONDS = 4 * FIFTEEN_MINUTES_IN_SECONDS;
private static final long ONE_DAY_IN_SECONDS = 24 * ONE_HOUR_IN_SECONDS;
private static final long ONE_WEEK_IN_SECONDS = 7 * ONE_DAY_IN_SECONDS;
private static final long ONE_MONTH_IN_SECONDS = 30 * ONE_DAY_IN_SECONDS;
private static final long THREE_MONTHS_IN_SECONDS = 90 * ONE_DAY_IN_SECONDS;
private static final long SIX_MONTHS_IN_SECONDS = 180 * ONE_DAY_IN_SECONDS;
private static final long ONE_YEAR_IN_SECONDS = 365 * ONE_DAY_IN_SECONDS;
private static final String PNG_FORMAT = "png";
private static final String CSV_FORMAT = "csv";
static {
TIME_RANGES.put("15m", FIFTEEN_MINUTES_IN_SECONDS);
TIME_RANGES.put("1h", ONE_HOUR_IN_SECONDS);
TIME_RANGES.put("1d", ONE_DAY_IN_SECONDS);
TIME_RANGES.put("1w", ONE_WEEK_IN_SECONDS);
TIME_RANGES.put("1M", ONE_MONTH_IN_SECONDS);
TIME_RANGES.put("3M", THREE_MONTHS_IN_SECONDS);
TIME_RANGES.put("6M", SIX_MONTHS_IN_SECONDS);
TIME_RANGES.put("1y", ONE_YEAR_IN_SECONDS);
}
private final SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
private String metricsDir = DEFAULT_METRICS_DIR;
private MetricsRetriever metricsRetriever = new RrdMetricsRetriever();
private double metricsMaxThreshold;
/**
* Retrieve data for the specified metric over the given time range. The URL to access this
* method is of the form http://<host>:<port>/<metricName>.<outputFormat> So the desired metric
* filename is specified in the URL, e.g., catalogQueryCount.png, where the filename extension
* defines the desired output format returned for the metric's data. Currently supported formats
* are png, csv, xls, ppt, xml, and json.
* <p>
* Note that the time range can be specified as either a start and end date (in RFC3339 format,
* i.e., YYYY-MM-DD'T'hh:mm:ssZ), or as an offset in seconds from the current time. These 2 time
* range mechanisms cannot be combined, e.g., you cannot specify an end date and an offset to be
* applied from that end date.
* <p>
* By default, the metric's name will be used for the y-axis label on the PNG graph, and the
* metric name and time range will be used for the graph's title. Both of these can be
* optionally specified with the yAxisLabel and title parameters. These 2 parameters do not
* apply for the other formats.
*
* @param metricName Name of the metric being graphed, e.g., queryCount
* @param outputFormat output format of the metric, e.g. csv
* @param startDate Specifies the start of the time range of the search on the metric's data (RFC-3339
* - Date and Time format, i.e. YYYY-MM-DDTHH:mm:ssZ). Cannot be used with dateOffset
* parameter.
* @param endDate Specifies the end of the time range of the search on the metric's data (RFC-3339 -
* Date and Time format, i.e. YYYY-MM-DDTHH:mm:ssZ). Cannot be used with dateOffset
* parameter.
* @param dateOffset Specifies an offset, backwards from the current time, to search on the modified
* time field for entries. Defined in seconds. Cannot be used with startDate and
* endDate parameters.
* @param yAxisLabel (optional) the label to apply to the graph's y-axis
* @param title (optional) the title to be applied to the graph
* @param uriInfo
* @return Response containing the metric's data in the specified outputFormat
* @throws MetricsEndpointException
*/
@GET
@Path("/{metricName}.{outputFormat}")
public Response getMetricsData(@PathParam("metricName") String metricName,
@PathParam("outputFormat") String outputFormat,
@QueryParam("startDate") String startDate, @QueryParam("endDate") String endDate,
@QueryParam("dateOffset") String dateOffset,
@QueryParam("yAxisLabel") String yAxisLabel, @QueryParam("title") String title,
@Context UriInfo uriInfo) throws MetricsEndpointException {
LOGGER.trace("ENTERING: getMetricsData - metricName = {}, outputFormat = {}",
metricName, outputFormat);
LOGGER.trace("request url: {}", uriInfo.getRequestUri());
LOGGER.trace("startDate = {}, endDate = {}", startDate, endDate);
LOGGER.trace("dateOffset = {}", dateOffset);
Response response = null;
// Client must specify *either* startDate and/or endDate *OR* dateOffset
if (!StringUtils.isBlank(dateOffset) && (!StringUtils.isBlank(startDate)
|| !StringUtils.isBlank(endDate))) {
throw new MetricsEndpointException(
"Cannot specify dateOffset and startDate or endDate, must specify either dateOffset only or startDate and/or endDate",
Response.Status.BAD_REQUEST);
}
long endTime;
if (!StringUtils.isBlank(endDate)) {
endTime = parseDate(endDate);
LOGGER.trace("Parsed endTime = {}", endTime);
} else {
// Default end time for metrics graphing to now (in seconds)
Calendar now = getCalendar();
endTime = now.getTimeInMillis() / MILLISECONDS_PER_SECOND;
LOGGER.trace("Defaulted endTime to {}", endTime);
// Set endDate to new calculated endTime (so that endDate is displayed properly
// in graph's title)
endDate = dateFormatter.format(now.getTime());
}
long startTime;
if (!StringUtils.isBlank(startDate)) {
startTime = parseDate(startDate);
LOGGER.trace("Parsed startTime = {}", startTime);
} else if (!StringUtils.isBlank(dateOffset)) {
startTime = endTime - Long.parseLong(dateOffset);
LOGGER.trace("Offset-computed startTime = {}", startTime);
// Set startDate to new calculated startTime (so that startDate is displayed properly
// in graph's title)
Calendar cal = getCalendar();
cal.setTimeInMillis(startTime * MILLISECONDS_PER_SECOND);
startDate = dateFormatter.format(cal.getTime());
} else {
// Default start time for metrics graphing to end time last 24 hours (in seconds)
startTime = endTime - ONE_DAY_IN_SECONDS;
LOGGER.trace("Defaulted startTime to {}", startTime);
// Set startDate to new calculated startTime (so that startDate is displayed properly
// in graph's title)
Calendar cal = getCalendar();
cal.setTimeInMillis(startTime * MILLISECONDS_PER_SECOND);
startDate = dateFormatter.format(cal.getTime());
}
LOGGER.trace("startDate = {}, endDate = {}", startDate, endDate);
if (StringUtils.isBlank(yAxisLabel)) {
yAxisLabel = RrdMetricsRetriever.convertCamelCase(metricName);
}
if (StringUtils.isBlank(title)) {
title = RrdMetricsRetriever.convertCamelCase(metricName) + " for " + startDate + " to "
+ endDate;
}
// Convert metric filename to rrd filename (because RRD file required by MetricRetriever to
// generate graph)
String rrdFilename = metricsDir + metricName + RRD_FILE_EXTENSION;
if (outputFormat.equalsIgnoreCase(PNG_FORMAT)) {
LOGGER.trace("Retrieving PNG-formatted data for metric {}", metricName);
try {
byte[] metricsGraphBytes = metricsRetriever.createGraph(metricName,
rrdFilename,
startTime,
endTime,
yAxisLabel,
title);
ByteArrayInputStream bis = new ByteArrayInputStream(metricsGraphBytes);
response = Response.ok(bis, PNG_MIME_TYPE)
.build();
} catch (IOException | MetricsGraphException e) {
LOGGER.info("Could not create graph for metric {}", metricName);
throw new MetricsEndpointException(
"Cannot create metrics graph for specified metric.",
Response.Status.BAD_REQUEST);
}
} else if (outputFormat.equalsIgnoreCase("csv")) {
try {
String csv = metricsRetriever.createCsvData(rrdFilename, startTime, endTime);
ResponseBuilder responseBuilder = Response.ok(csv);
responseBuilder.type("text/csv");
response = responseBuilder.build();
} catch (IOException | MetricsGraphException e) {
LOGGER.info("Could not create CSV data for metric {}", metricName);
throw new MetricsEndpointException(
"Cannot create CSV data for specified metric.",
Response.Status.BAD_REQUEST);
}
} else if (outputFormat.equalsIgnoreCase("xls")) {
LOGGER.trace("Retrieving XLS-formatted data for metric {}", metricName);
try {
OutputStream os = metricsRetriever.createXlsData(metricName,
rrdFilename,
startTime,
endTime);
InputStream is =
new ByteArrayInputStream(((ByteArrayOutputStream) os).toByteArray());
ResponseBuilder responseBuilder = Response.ok(is);
responseBuilder.type("application/vnd.ms-excel");
response = responseBuilder.build();
} catch (IOException | MetricsGraphException e) {
LOGGER.info("Could not create XLS data for metric {}", metricName);
throw new MetricsEndpointException(
"Cannot create XLS data for specified metric.",
Response.Status.BAD_REQUEST);
}
} else if (outputFormat.equalsIgnoreCase("ppt")) {
LOGGER.trace("Retrieving PPT-formatted data for metric {}", metricName);
try {
OutputStream os = metricsRetriever.createPptData(metricName,
rrdFilename,
startTime,
endTime);
InputStream is =
new ByteArrayInputStream(((ByteArrayOutputStream) os).toByteArray());
ResponseBuilder responseBuilder = Response.ok(is);
responseBuilder.type("application/vnd.ms-powerpoint");
response = responseBuilder.build();
} catch (IOException | MetricsGraphException e) {
LOGGER.info("Could not create PPT data for metric {}", metricName);
throw new MetricsEndpointException(
"Cannot create PPT data for metric for specified metric.",
Response.Status.BAD_REQUEST);
}
} else if (outputFormat.equalsIgnoreCase("xml")) {
LOGGER.trace("Retrieving XML-formatted data for metric {}", metricName);
try {
String xmlData = metricsRetriever.createXmlData(metricName,
rrdFilename,
startTime,
endTime);
ResponseBuilder responseBuilder = Response.ok(xmlData);
responseBuilder.type("text/xml");
response = responseBuilder.build();
} catch (IOException | MetricsGraphException e) {
LOGGER.info("Could not create XML data for metric {}", metricName);
throw new MetricsEndpointException(
"Cannot create XML data for specified metric.",
Response.Status.BAD_REQUEST);
}
} else if (outputFormat.equalsIgnoreCase("json")) {
LOGGER.trace("Retrieving JSON-formatted data for metric {}", metricName);
try {
String jsonData = metricsRetriever.createJsonData(metricName,
rrdFilename,
startTime,
endTime);
ResponseBuilder responseBuilder = Response.ok(jsonData);
responseBuilder.type("application/json");
response = responseBuilder.build();
} catch (IOException | MetricsGraphException e) {
LOGGER.info("Could not create JSON data for metric {}", metricName);
throw new MetricsEndpointException(
"Cannot create JSON data for specified metric.",
Response.Status.BAD_REQUEST);
}
}
LOGGER.trace("EXITING: getMetricsData");
return response;
}
/**
* Get list of available metrics and the associated URLs to their historical data.
*
* @param uriInfo
* @return JSON-formatted response where each metric has a list of URLs (and the display text
* for them), where each URL links to a graph of the metric's data for a specific time
* range from current time (e.g., for last 4 hours).
*/
@GET
@Path("/")
@Produces({JSON_MIME_TYPE})
public Response getMetricsList(@Context UriInfo uriInfo) {
Response response = null;
List<String> metricNames = getMetricsNames();
Map<String, Map<String, Map<String, String>>> metrics =
new LinkedHashMap<String, Map<String, Map<String, String>>>();
for (String metricName : metricNames) {
generateMetricsUrls(metrics, metricName, uriInfo);
}
String jsonText = JSONValue.toJSONString(metrics);
LOGGER.trace(jsonText);
response = Response.ok(jsonText)
.build();
return response;
}
/**
* Retrieve data for the all metrics over the given time range. The URL to access this method is
* of the form http://<host>:<port>/report.<outputFormat> The filename extension defines the
* desired output format returned for the report's data. Currently supported formats are xls and
* ppt.
* <p>
* The XLS-formatted report will be one spreadsheet (workbook) with a worksheet per metric. The
* PPT-formatted report will be one PowerPoint slide deck with a slide per metric. Each slide
* will contain the metric's PNG graph.
* <p>
* If a summary interval is requested, the XSL report will instead contain a single table, with
* the summarized values for each interval and metric. Cannot be used with PPT format.
* <p>
* Note that the time range can be specified as either a start and end date (in RFC3339 format,
* i.e., YYYY-MM-DD'T'hh:mm:ssZ), or as an offset in seconds from the current time. These 2 time
* range mechanisms cannot be combined, e.g., you cannot specify an end date and an offset to be
* applied from that end date.
* <p>
* By default, the metric's name will be used for the y-axis label, and the metric name and time
* range will be used for the graph's title for the report in PPT format.
*
* @param startDate Specifies the start of the time range of the search on the metric's data (RFC-3339
* - Date and Time format, i.e. YYYY-MM-DDTHH:mm:ssZ). Cannot be used with dateOffset
* parameter.
* @param endDate Specifies the end of the time range of the search on the metric's data (RFC-3339 -
* Date and Time format, i.e. YYYY-MM-DDTHH:mm:ssZ). Cannot be used with dateOffset
* parameter.
* @param dateOffset Specifies an offset, backwards from the current time, to search on the modified
* time field for entries. Defined in seconds. Cannot be used with startDate or
* endDate parameters.
* @param summaryInterval One of {@link ddf.metrics.reporting.internal.rrd4j.RrdMetricsRetriever.SUMMARY_INTERVALS}
* @param uriInfo
* @return Response containing the report as a stream in either XLS or PPT format
* @throws MetricsEndpointException
*/
@GET
@Path("/report.{outputFormat}")
public Response getMetricsReport(@PathParam("outputFormat") String outputFormat,
@QueryParam("startDate") String startDate, @QueryParam("endDate") String endDate,
@QueryParam("dateOffset") String dateOffset,
@QueryParam("summaryInterval") String summaryInterval, @Context UriInfo uriInfo)
throws MetricsEndpointException {
LOGGER.debug("ENTERING: getMetricsReport - outputFormat = {}", outputFormat);
LOGGER.debug("request url: {}", uriInfo.getRequestUri());
LOGGER.debug("startDate = {}, endDate = {}", startDate, endDate);
LOGGER.debug("dateOffset = {}", dateOffset);
Response response = null;
// Client must specify *either* startDate and/or endDate *OR* dateOffset
if (!StringUtils.isBlank(dateOffset) && (!StringUtils.isBlank(startDate)
|| !StringUtils.isBlank(endDate))) {
throw new MetricsEndpointException(
"Cannot specify dateOffset and startDate or endDate, must specify either dateOffset only or startDate and/or endDate",
Response.Status.BAD_REQUEST);
}
long endTime;
if (!StringUtils.isBlank(endDate)) {
endTime = parseDate(endDate);
LOGGER.debug("Parsed endTime = {}", endTime);
} else {
// Default end time for metrics graphing to now (in seconds)
Calendar now = getCalendar();
endTime = now.getTimeInMillis() / MILLISECONDS_PER_SECOND;
LOGGER.debug("Defaulted endTime to {}", endTime);
// Set endDate to new calculated endTime (so that endDate is displayed properly
// in graph's title)
endDate = dateFormatter.format(now.getTime());
}
long startTime;
if (!StringUtils.isBlank(startDate)) {
startTime = parseDate(startDate);
LOGGER.debug("Parsed startTime = {}", startTime);
} else if (!StringUtils.isBlank(dateOffset)) {
startTime = endTime - Long.parseLong(dateOffset);
LOGGER.debug("Offset-computed startTime = {}", startTime);
// Set startDate to new calculated startTime (so that startDate is displayed properly
// in graph's title)
Calendar cal = getCalendar();
cal.setTimeInMillis(startTime * MILLISECONDS_PER_SECOND);
startDate = dateFormatter.format(cal.getTime());
} else {
// Default start time for metrics graphing to end time last 24 hours (in seconds)
startTime = endTime - ONE_DAY_IN_SECONDS;
LOGGER.debug("Defaulted startTime to {}", startTime);
// Set startDate to new calculated startTime (so that startDate is displayed properly
// in graph's title)
Calendar cal = getCalendar();
cal.setTimeInMillis(startTime * MILLISECONDS_PER_SECOND);
startDate = dateFormatter.format(cal.getTime());
}
LOGGER.debug("startDate = {}, endDate = {}", startDate, endDate);
List<String> metricNames = getMetricsNames();
// Generated name for metrics file (<DDF Sitename>_<Startdate>_<EndDate>.outputFormat)
String dispositionString =
"attachment; filename=" + SystemInfo.getSiteName() + "_" + startDate.substring(0,
10) + "_" + endDate.substring(0, 10) + "." + outputFormat;
try {
if (outputFormat.equalsIgnoreCase("xls")) {
OutputStream os = metricsRetriever.createXlsReport(metricNames,
metricsDir,
startTime,
endTime,
summaryInterval);
InputStream is =
new ByteArrayInputStream(((ByteArrayOutputStream) os).toByteArray());
ResponseBuilder responseBuilder = Response.ok(is);
responseBuilder.type("application/vnd.ms-excel");
responseBuilder.header("Content-Disposition", dispositionString);
response = responseBuilder.build();
} else if (outputFormat.equalsIgnoreCase("ppt")) {
if (StringUtils.isNotEmpty(summaryInterval)) {
throw new MetricsEndpointException("Summary interval not allowed for ppt format",
Response.Status.BAD_REQUEST);
}
OutputStream os = metricsRetriever.createPptReport(metricNames,
metricsDir,
startTime,
endTime);
InputStream is =
new ByteArrayInputStream(((ByteArrayOutputStream) os).toByteArray());
ResponseBuilder responseBuilder = Response.ok(is);
responseBuilder.type("application/vnd.ms-powerpoint");
responseBuilder.header("Content-Disposition", dispositionString);
response = responseBuilder.build();
}
} catch (IOException | MetricsGraphException e) {
LOGGER.debug("Could not create {} report", outputFormat, e);
throw new MetricsEndpointException("Could not create report in specified output format.",
Response.Status.BAD_REQUEST);
}
LOGGER.debug("EXITING: getMetricsReport");
return response;
}
private Calendar getCalendar() {
return Calendar.getInstance(TimeZone.getTimeZone("UTC"));
}
/**
* Parse date in ISO8601 format into seconds since Unix epoch.
*
* @param date
* @return
*/
protected long parseDate(String date) {
DateTimeFormatter dateFormatter = ISODateTimeFormat.dateTimeNoMillis();
Date formattedDate = dateFormatter.parseDateTime(date)
.toDate();
return formattedDate.getTime() / 1000;
}
/**
* Generates the URLs for each time range, e.g., 15m, 1h, etc. for the specified metric.
* <p>
* The metric's URL info will be put in the {@code metrics} Maps passed in to this method. The
* structure of these nested Maps are:
* {@code Map1<String1, Map2<String2, Map3<String3,String4>>>} (Numbers added to end of Map and
* String types so that each position could be referred to) where:
* <ul>
* <li>String1 = metric name, e.g., "catalogQueries"</li>
* <li>Map2 = mapping of time range to its list of hyperlinks</li>
* <li>String2 = time range, e.g., "15m"</li>
* <li>Map3 = hyperlink for each format type (e.g., PNG) for each time range for each metric</li>
* <li>String3 = display text for hyperlink, e.g., PNG</li>
* <li>String4 = hyperlink for metric data in specific format, e.g.,
* http://host:port/services/internal/metrics/catalogQueries.png?dateOffset=900</li>
* </ul>
*
* @param metrics nested Maps that will be populated with the metric's URL info
* @param metricsName name of the metric to generate URLs for
* @param uriInfo used to extract the base URL and append the metric's path to it
*/
protected void generateMetricsUrls(Map<String, Map<String, Map<String, String>>> metrics,
String metricsName, UriInfo uriInfo) {
// Generate text and hyperlink for single metric for 15 minute, 1 hour, 4 hours,
// 12 hours, 1 day, 3 days, 1 week, 1 month, and 1 year
Calendar cal = getCalendar();
long endTime = cal.getTimeInMillis() / 1000;
LOGGER.trace("Defaulted endTime to {}", endTime);
String[] supportedFormats = new String[] {"png", "csv", "xls"};
// key=time range
// value=list of hyperlinks (and their display text) for each supported format
// Example:
// key="15m"
// value=[("PNG", "http://host:port/.../catalogQueries.png?dateOffset=900),("CSV", ...)]
SortedMap<String, Map<String, String>> metricTimeRangeLinks =
new TreeMap<String, Map<String, String>>(new MetricsTimeRangeComparator());
Iterator timeRangesIter = TIME_RANGES.entrySet()
.iterator();
while (timeRangesIter.hasNext()) {
Map.Entry entry = (Map.Entry) timeRangesIter.next();
String timeRange = (String) entry.getKey();
long timeRangeInSeconds = (Long) entry.getValue();
Map<String, String> metricsUrls = new LinkedHashMap<String, String>();
for (String format : supportedFormats) {
// CXF bug: getAbsolutePath() caches the path that was used the very first time
// to access this REST service. For example, if client used localhost:8181/... to
// access this service and then later a different client uses the IP address,
// 172.18.14.169:8181/... to access this service, getAbsolutePath() will still
// return localhost:8181 (which will not work for someone connecting remotely to
// this service since they are probably not running DDF too).
/*
* START: CXF bug with getAbsolutePath()
*
* String uriAbsolutePath = uriInfo.getAbsolutePath().toASCIIString(); UriBuilder
* uriBuilder = UriBuilder.fromPath(uriAbsolutePath); uriBuilder.path(metricsName +
* "." + format); URI uri = uriBuilder.build(); String baseMetricsUrl =
* uri.toASCIIString(); String metricsUrl = baseMetricsUrl + DATE_OFFSET_QUERY +
* timeRangeInSeconds;
*
* END: CXF bug
*/
String metricsUrl = SystemBaseUrl.getRootContext() + METRICS_SERVICE_BASE_URL + "/"
+ metricsName + "." + format + DATE_OFFSET_QUERY + timeRangeInSeconds;
// key=format
// value=url for format with specified time range in seconds
// Example:
// "PNG", "http://host:port/.../catalogQueries.png?dateOffset=900
metricsUrls.put(format.toUpperCase(), metricsUrl);
}
metricTimeRangeLinks.put(timeRange, metricsUrls);
}
metrics.put(metricsName, metricTimeRangeLinks);
}
/**
* Returns the list of all of the RRD files in the metrics directory.
*
* @return
*/
private String[] getRrdFiles() {
FilenameFilter rrdFilter = new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.endsWith(RRD_FILE_EXTENSION);
}
};
File dir = new File(metricsDir);
String[] rrdFiles = dir.list(rrdFilter);
return rrdFiles;
}
/**
* Returns a list of all of the metrics' names based on the list of RRD files found in the
* metrics directory.
*
* @return
*/
private List<String> getMetricsNames() {
String[] rrdFiles = getRrdFiles();
List<String> metricNames = new ArrayList<String>();
if (rrdFiles != null) {
for (String rrdFile : rrdFiles) {
String metricsName = FilenameUtils.getFullPath(rrdFile) + FilenameUtils.getBaseName(
rrdFile);
metricNames.add(metricsName);
}
}
Collections.sort(metricNames);
LOGGER.trace("Returning {} metrics", metricNames.size());
return metricNames;
}
void setMetricsDir(String metricsDir) {
this.metricsDir = metricsDir;
}
void setMetricsRetriever(MetricsRetriever metricsRetriever) {
this.metricsRetriever = metricsRetriever;
}
public void setMetricsMaxThreshold(double metricsMaxThreshold) {
LOGGER.debug("Creating new RrdMetricsRetriever with metricsMaxThreshold = {}",
metricsMaxThreshold);
this.metricsMaxThreshold = metricsMaxThreshold;
metricsRetriever = new RrdMetricsRetriever(metricsMaxThreshold);
}
/**
* Comparator used to sort metric time ranges by chronological order rather than the default
* lexigraphical order.
*/
static class MetricsTimeRangeComparator implements Comparator, Serializable {
private static final long serialVersionUID = 1L;
public int compare(Object o1, Object o2) {
String timeRange1 = (String) o1;
String timeRange2 = (String) o2;
Long dateOffset1 = TIME_RANGES.get(timeRange1);
Long dateOffset2 = TIME_RANGES.get(timeRange2);
return dateOffset1.compareTo(dateOffset2);
}
}
}