package ch.ethz.inf.vs.californium.proxy;
/*******************************************************************************
* Copyright (c) 2012, Institute for Pervasive Computing, ETH Zurich.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the Institute nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* This file is part of the Californium (Cf) CoAP framework.
******************************************************************************/
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import ch.ethz.inf.vs.californium.coap.CoAP.ResponseCode;
import ch.ethz.inf.vs.californium.coap.MediaTypeRegistry;
import ch.ethz.inf.vs.californium.coap.Request;
import ch.ethz.inf.vs.californium.coap.Response;
import ch.ethz.inf.vs.californium.server.resources.CoapExchange;
import ch.ethz.inf.vs.californium.server.resources.ResourceBase;
import com.google.common.cache.CacheStats;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
/**
* Resource that encapsulate the proxy statistics.
*
* @author Francesco Corazza
*
*/
public class StatsResource extends ResourceBase {
private final Table<String, String, StatHelper> statsTable = HashBasedTable.create();
private static String CACHE_LOG_NAME = "_cache_log.log";
/**
* Instantiates a new stats resource.
*
* @param cacheResource
*/
public StatsResource(CacheResource cacheResource) {
super("stats");
getAttributes().setTitle("Keeps track of the requests served by the proxy.");
// add the sub-resource to show stats
add(new CacheStatResource("cache", cacheResource));
add(new ProxyStatResource("proxy"));
}
public void updateStatistics(Request request, boolean cachedResponse) {
URI proxyUri = null;
try {
proxyUri = new URI(request.getOptions().getProxyURI());
} catch (URISyntaxException e) {
LOGGER.warning(String.format("Proxy-uri malformed: %s",
request.getOptions().getProxyURI()));
}
if (proxyUri == null) {
// throw new IllegalArgumentException("proxyUri == null");
return;
}
// manage the address requester
String addressString = proxyUri.getHost();
if (addressString != null) {
// manage the resource requested
String resourceString = proxyUri.getPath();
if (resourceString != null) {
// check if there is already an entry for the row/column
// association
StatHelper statHelper = statsTable.get(addressString, resourceString);
if (statHelper == null) {
// create a new stat if it not present
statHelper = new StatHelper();
// add the new element to the table
statsTable.put(addressString, resourceString, statHelper);
}
// increment the count of the requests
statHelper.increment(cachedResponse);
}
}
}
/**
* Builds a pretty print from the statistics gathered.
*
* @return the statistics string
*/
private String getStatString() {
StringBuilder builder = new StringBuilder();
builder.append(String.format("Served %d addresses and %d resources\n", statsTable.rowKeySet().size(), statsTable.cellSet().size()));
builder.append("_\n");
// iterate over every row (addresses)
for (String address : statsTable.rowKeySet()) {
builder.append(String.format("|- %s\n", address));
builder.append("|\t _\n");
// iterate over every column for a specific address
for (String resource : statsTable.row(address).keySet()) {
builder.append(String.format("|\t |- %s: \n", resource));
// get the statistics
StatHelper statHelper = statsTable.get(address, resource);
builder.append(String.format("|\t |------ total requests: %d\n", statHelper.getTotalCount()));
builder.append(String.format("|\t |------ total cached replies: %d\n", statHelper.getCachedCount()));
// builder.append(String.format("|\t |------ last period (%d sec) requests: %d\n",
// PERIOD_SECONDS, statHelper.getLastPeriodCount()));
// builder.append(String.format("|\t |------ last period (%d sec) avg delay (nanosec): %d\n",
// PERIOD_SECONDS, statHelper.getLastPeriodAvgDelay()));
builder.append("|\t |\n");
}
builder.append("|\t  ̄\n");
builder.append("|\n");
}
builder.append(" ̄\n");
return builder.length() == 0 ? "The proxy has not received any request, yet." : builder.toString();
}
private static final class CacheStatResource extends ResourceBase {
private CacheStats relativeCacheStats;
private final CacheResource cacheResource;
private static final long DEFAULT_LOGGING_DELAY = 5;
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
/**
* Instantiates a new debug resource.
*
* @param resourceIdentifier
* the resource identifier
* @param cacheResource
*/
public CacheStatResource(String resourceIdentifier, CacheResource cacheResource) {
super(resourceIdentifier);
this.cacheResource = cacheResource;
relativeCacheStats = cacheResource.getCacheStats();
}
/**
* Method to get the stats about the cache.
*
* @return
*/
public String getStats() {
StringBuilder stringBuilder = new StringBuilder();
CacheStats cacheStats = cacheResource.getCacheStats().minus(relativeCacheStats);
stringBuilder.append(String.format("Total succesful loaded values: %d %n", cacheStats.loadSuccessCount()));
stringBuilder.append(String.format("Total requests: %d %n", cacheStats.requestCount()));
stringBuilder.append(String.format("Hits ratio: %d/%d - %.3f %n", cacheStats.hitCount(), cacheStats.missCount(), cacheStats.hitRate()));
stringBuilder.append(String.format("Average time spent loading new values (nanoseconds): %.3f %n", cacheStats.averageLoadPenalty()));
stringBuilder.append(String.format("Number of cache evictions: %d %n", cacheStats.evictionCount()));
return stringBuilder.toString();
}
@Override
public void handleDELETE(CoapExchange exchange) {
// reset the cache
relativeCacheStats = cacheResource.getCacheStats().minus(relativeCacheStats);
exchange.respond(ResponseCode.DELETED);
}
@Override
public void handleGET(CoapExchange exchange) {
String payload = "Available commands:\n - GET: show statistics\n - POST write stats to file\n - DELETE: reset statistics\n\n";
payload += getStats();
Response response = new Response(ResponseCode.CONTENT);
response.setPayload(payload);
response.getOptions().setContentFormat(MediaTypeRegistry.TEXT_PLAIN);
exchange.respond(response);
}
@Override
public void handlePOST(CoapExchange exchange) {
// TODO include stopping the writing => make something for the whole
// proxy
// executor.shutdown();
// request.respond(CodeRegistry.RESP_DELETED, "Stopped",
// MediaTypeRegistry.TEXT_PLAIN);
// starting to log the stats on a new file
// create the new file
String logName = System.nanoTime() + CACHE_LOG_NAME;
final File cacheLog = new File(logName);
try {
cacheLog.createNewFile();
// write the header
com.google.common.io.Files.write("hits%, avg. load, #evictions \n", cacheLog, Charset.defaultCharset());
} catch (IOException e) {
}
executor.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
CacheStats cacheStats = cacheResource.getCacheStats().minus(relativeCacheStats);
String csvStats = String.format("%.3f, %.3f, %d %n", cacheStats.hitRate(), cacheStats.averageLoadPenalty(), cacheStats.evictionCount());
try {
com.google.common.io.Files.append(csvStats, cacheLog, Charset.defaultCharset());
} catch (IOException e) {
}
}
}, 0, DEFAULT_LOGGING_DELAY, TimeUnit.SECONDS);
Response response = new Response(ResponseCode.CREATED);
response.setPayload("Creted log: " + logName);
response.getOptions().setContentFormat(MediaTypeRegistry.TEXT_PLAIN);
exchange.respond(response);
}
}
private final class ProxyStatResource extends ResourceBase {
public ProxyStatResource(String resourceIdentifier) {
super(resourceIdentifier);
}
/*
* (non-Javadoc)
* @see
* ch.ethz.inf.vs.californium.endpoint.resources.LocalResource#performDELETE
* (ch.ethz.inf.vs.californium.coap.DELETERequest)
*/
@Override
public void handleDELETE(CoapExchange exchange) {
// reset all the statistics
statsTable.clear();
exchange.respond(ResponseCode.DELETED);
}
/*
* (non-Javadoc)
* @see
* ch.ethz.inf.vs.californium.endpoint.resources.LocalResource#performGET
* (ch.ethz.inf.vs.californium.coap.GETRequest)
*/
@Override
public void handleGET(CoapExchange exchange) {
String payload = "Available commands:\n - GET: show statistics\n - POST write stats to file\n - DELETE: reset statistics\n\n";
payload += getStatString();
Response response = new Response(ResponseCode.CONTENT);
response.setPayload(payload);
response.getOptions().setContentFormat(MediaTypeRegistry.TEXT_PLAIN);
exchange.respond(response);
}
}
/**
* The Class StatisticsHelper.
*
* @author Francesco Corazza
*/
private static class StatHelper {
private int totalCount = 0;
private int cachedCount = 0;
public int getCachedCount() {
return cachedCount;
}
/**
* @return the totalCount
*/
public int getTotalCount() {
return totalCount;
}
public void increment(boolean cachedResponse) {
// add the total request counter
totalCount++;
if (cachedResponse) {
cachedCount++;
}
// add the new request's timestamp to the list
// long currentTimestamp = System.nanoTime();
// lastPeriodTimestamps.add(currentTimestamp);
// clean the list by the old entries
// cleanTimestamps(currentTimestamp);
}
}
}