/**
* Licensed to Cloudera, Inc. under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. Cloudera, Inc. licenses this file
* to you 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.cloudera.flume.reporter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.Map.Entry;
import org.apache.commons.lang.StringEscapeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.cloudera.flume.core.Attributes;
import com.cloudera.flume.core.EventImpl;
import com.cloudera.flume.core.Attributes.Type;
import com.google.common.base.Preconditions;
/**
* Reports are just events that have some helpers for quickly outputting data in
* human readable or machine parseable formats.
*
* For now, we use a convention where a attribute starting with "rpt." is a a
* reported field. Later we may include a avro schema or something to include
* type information as well.
*/
public class ReportEvent extends EventImpl {
static final Logger LOG = LoggerFactory.getLogger(ReportEvent.class);
final public static String R_NAME = "name";
final public static String A_COUNT = "count";
final Map<String, Long> longMetrics = new HashMap<String, Long>();
final Map<String, Double> doubleMetrics = new HashMap<String, Double>();
final Map<String, String> stringMetrics = new HashMap<String, String>();
/**
* Set a long-valued metric to supplied value. NOT THREAD SAFE.
*
* @param name
* metric name, null not allowed, if the report previously contained
* a mapping for the name the old value is replaced with the new.
* @param value
* metric value to associate with name
* @throws IllegalArgumentException
* if illegal arguments provided
*/
public void setLongMetric(String name, long value) {
Preconditions.checkArgument(name != null);
longMetrics.put(name, value);
}
/**
* Set a double-valued metric to supplied value. NOT THREAD SAFE.
*
* @param name
* metric name, null not allowed, if the report previously contained
* a mapping for the name the old value is replaced with the new.
* @param value
* metric value to associate with name
* @throws IllegalArgumentException
* if illegal arguments provided
*/
public void setDoubleMetric(String name, double value) {
Preconditions.checkArgument(name != null);
doubleMetrics.put(name, value);
}
/**
* Set a string-valued metric to supplied value. NOT THREAD SAFE.
*
* @param name
* metric name, null not allowed, if the report previously contained
* a mapping for the name the old value is replaced with the new.
* @param value
* metric value to associate with name. Null values will be ignored
* (not included in the report).
* @throws IllegalArgumentException
* if illegal arguments provided
*/
public void setStringMetric(String name, String value) {
Preconditions.checkArgument(name != null);
if (value != null) {
stringMetrics.put(name, value);
} else if (LOG.isDebugEnabled()) {
// TODO we should look for this during testing and address
LOG.debug("Ignoring null string metric " + name);
}
}
/**
* Returns the value of a long-valued metric. NOT THREAD SAFE.
*
* @param name
* metric name, null not allowed
* @throws IllegalArgumentException
* if illegal arguments provided
*/
public Long getLongMetric(String name) {
Preconditions.checkArgument(name != null);
return longMetrics.get(name);
}
/**
* Returns the value of a double-valued metric. NOT THREAD SAFE.
*
* @param name
* metric name, null not allowed
* @throws IllegalArgumentException
* if illegal arguments provided
*/
public Double getDoubleMetric(String name) {
Preconditions.checkArgument(name != null);
return doubleMetrics.get(name);
}
/**
* Returns the value of a string-valued metric. NOT THREAD SAFE.
*
* @param name
* metric name, null not allowed
* @throws IllegalArgumentException
* if illegal arguments provided
*/
public String getStringMetric(String name) {
Preconditions.checkArgument(name != null);
return stringMetrics.get(name);
}
/**
* Returns an unmodifiable map of all double metrics
*/
public Map<String, Double> getAllDoubleMetrics() {
return Collections.unmodifiableMap(doubleMetrics);
}
/**
* Returns an unmodifiable map of all string metrics
*/
public Map<String, String> getAllStringMetrics() {
return Collections.unmodifiableMap(stringMetrics);
}
/**
* Returns an unmodifiable map of all long metrics
*/
public Map<String, Long> getAllLongMetrics() {
return Collections.unmodifiableMap(longMetrics);
}
/**
* Returns the total number of metrics. NOT THREAD SAFE.
*/
public long getNumMetrics() {
return this.doubleMetrics.size() + this.longMetrics.size()
+ this.stringMetrics.size();
}
/**
* Constructs a copy of event e
*/
public ReportEvent(ReportEvent e) {
super(new byte[0]);
Preconditions.checkNotNull(e);
this.merge(e);
}
public ReportEvent(String src) {
super(new byte[0]); // empty body
this.setStringMetric(R_NAME, src);
}
/**
* Construct a ReportEvent given various metrics.
*/
public ReportEvent(Map<String, Long> longMetrics,
Map<String, String> stringMetrics, Map<String, Double> doubleMetrics) {
super(new byte[0]);
this.longMetrics.putAll(longMetrics);
this.stringMetrics.putAll(stringMetrics);
this.doubleMetrics.putAll(doubleMetrics);
}
/**
* Serialises event as JSON string
*/
public void toJson(Writer o) throws IOException {
PrintWriter pw = new PrintWriter(o);
pw.print("{");
// an inconsistency: Some of the "attributes" are not in the attribute hash
// table -- time, priority, host, body.
pw.print("\"host\" : \"" + StringEscapeUtils.escapeJava(getHost()) + "\"");
// get the attributes and output them
for (String attr : getAttrs().keySet()) {
String v = Attributes.toStringStrict(this, attr);
Type t = Attributes.getType(attr);
pw.print(", \"" + StringEscapeUtils.escapeJava(attr) + "\" : ");
if (t != null && t == Type.STRING) {
pw.print("\"" + StringEscapeUtils.escapeJava(v) + "\"");
} else {
pw.print(StringEscapeUtils.escapeJava(v));
}
}
for (Entry<String, String> entry : getAllStringMetrics().entrySet()) {
pw.print(", \"" + StringEscapeUtils.escapeJava(entry.getKey()) + "\" : ");
pw.print("\"" + StringEscapeUtils.escapeJava(entry.getValue()) + "\"");
}
for (Entry<String, Double> entry : getAllDoubleMetrics().entrySet()) {
pw.print(", \"" + StringEscapeUtils.escapeJava(entry.getKey()) + "\" : ");
pw.print(entry.getValue());
}
for (Entry<String, Long> entry : getAllLongMetrics().entrySet()) {
pw.print(", \"" + StringEscapeUtils.escapeJava(entry.getKey()) + "\" : ");
pw.print(entry.getValue());
}
pw.println("}");
}
/**
* Defaults to printing data out as a bunch of html table cells.
*/
public void toHtml(Writer o) throws IOException {
PrintWriter pw = new PrintWriter(o);
pw.print("<table>");
pw.print("<tr>");
pw.print("<th>host</th>");
pw.print("<td>");
pw.print(getHost());
pw.println("</td>");
pw.print("</tr>");
// get the attributes, filter, sort and output them
SortedMap<String, String> reportAttrs = new TreeMap<String, String>();
for (String attr : getAttrs().keySet()) {
reportAttrs.put(attr, Attributes.toString(this, attr));
}
for (Entry<String, Long> e : getAllLongMetrics().entrySet()) {
reportAttrs.put(e.getKey(), e.getValue().toString());
}
for (Entry<String, Double> e : getAllDoubleMetrics().entrySet()) {
reportAttrs.put(e.getKey(), e.getValue().toString());
}
for (Entry<String, String> e : getAllStringMetrics().entrySet()) {
reportAttrs.put(e.getKey(), e.getValue());
}
for (Entry<String, String> a : reportAttrs.entrySet()) {
pw.println("<tr><th>" + a.getKey() + "</th>");
pw.print("<td>");
pw.print("<div class=\"" + a.getKey() + "\">");
pw.print(a.getValue());
pw.print("</div>");
pw.println("</td>");
pw.println("</tr>");
}
pw.print("</table>");
}
/**
* Returns event in string form: host [ priority date ] {attr1:val1}
* {attr2:val2} {metric1:val1} {metric2:val2} body
*/
public String toString() {
StringBuilder metrics = new StringBuilder();
// Iterate over strings, doubles and longs building a { key : value } string
SortedMap<String, String> sortedStrings = new TreeMap<String, String>(this
.getAllStringMetrics());
for (Entry<String, String> e : sortedStrings.entrySet()) {
metrics.append("{ " + e.getKey() + " : ");
String o = e.getValue();
metrics.append(o + " } ");
}
SortedMap<String, Double> sortedDoubles = new TreeMap<String, Double>(this
.getAllDoubleMetrics());
for (Entry<String, Double> e : sortedDoubles.entrySet()) {
metrics.append("{ " + e.getKey() + " : ");
String o = e.getValue().toString();
metrics.append(o + " } ");
}
SortedMap<String, Long> sortedLongs = new TreeMap<String, Long>(this
.getAllLongMetrics());
for (Entry<String, Long> e : sortedLongs.entrySet()) {
metrics.append("{ " + e.getKey() + " : ");
String o = e.getValue().toString();
metrics.append(o + " } ");
}
// This implementation taken from EventImpl
String mbody = StringEscapeUtils.escapeJava(new String(getBody()));
StringBuilder attrs = new StringBuilder();
SortedMap<String, byte[]> sorted = new TreeMap<String, byte[]>(this.fields);
for (Entry<String, byte[]> e : sorted.entrySet()) {
attrs.append("{ " + e.getKey() + " : ");
String o = Attributes.toString(this, e.getKey());
attrs.append(o + " } ");
}
return getHost() + " [" + getPriority().toString() + " "
+ new Date(getTimestamp()) + "] " + attrs.toString()
+ metrics.toString() + mbody;
}
/**
* Serialises event as text to supplied writer.
*/
public void toText(Writer o) throws IOException {
o.write(StringEscapeUtils.escapeJava(this.toString()));
}
/**
* Legacy report takes a arbitrary string and turns in into a report.
*/
static class LegacyReport extends ReportEvent {
static final String ATTR_LEGACY = "report.legacy.html";
static {
Attributes.register(ATTR_LEGACY, Type.STRING);
}
LegacyReport(String s, byte[] data) {
super(s);
set(ATTR_LEGACY, data);
}
public void toHtml(Writer w) {
PrintWriter pw = new PrintWriter(w);
byte[] data = get(ATTR_LEGACY);
pw.print(new String(data));
}
}
/**
* This is a temporary method present while I convert old reports into new
* ones.
*/
public static ReportEvent createLegacyHtmlReport(String name, String data) {
return new LegacyReport(name, data.getBytes());
}
/**
* Does the work for the merge methods by iterating over the set of metrics in
* e and copying them to this event with a prefix.
*/
protected void mergeWithPrefix(String prefix, ReportEvent e) {
for (Entry<String, Double> entry : e.getAllDoubleMetrics().entrySet()) {
String newKey = prefix + entry.getKey();
if (getDoubleMetric(newKey) == null) {
setDoubleMetric(newKey, entry.getValue());
}
}
for (Entry<String, String> entry : e.getAllStringMetrics().entrySet()) {
String newKey = prefix + entry.getKey();
if (getStringMetric(newKey) == null) {
setStringMetric(newKey, entry.getValue());
}
}
for (Entry<String, Long> entry : e.getAllLongMetrics().entrySet()) {
String newKey = prefix + entry.getKey();
if (getLongMetric(newKey) == null) {
setLongMetric(newKey, entry.getValue());
}
}
}
/**
* This method merges in values from other events that are not present in the
* current event.
*/
public void merge(ReportEvent e) {
super.merge(e);
mergeWithPrefix("", e);
}
/**
* This method "hierarchically" merges the attributes of another event
* prefixing each attribute with the specified prefix.
*
* So if our current event has attributes (a,b,c), and the one being merged in
* has attributes (d,e,f), and we have prefix be x, the result event will have
* attributes (a,b,c, x.d, x.e, x.f)
*
* This can be recursive, so if if d was actually an event with (h,i,j) and
* prefix in that call is y, we could end up with (a,b,c,x.d,x.e, x.f, x.y.h,
* x.y.i, x.y.j)
*/
public void hierarchicalMerge(String prefix, ReportEvent e) {
// Copy the attributes
super.hierarchicalMerge(prefix, e);
// Copy the metrics
mergeWithPrefix(prefix + ".", e);
}
/**
* Return escaped String serialisation of this event
*/
public String toText() {
return StringEscapeUtils.escapeJava(this.toString());
}
/**
* Return event as JSON string
*/
public String toJson() {
StringWriter sw = new StringWriter();
try {
toJson(sw);
} catch (IOException e) {
LOG.error("String writer should never throw IOException", e);
return null;
}
return sw.getBuffer().toString();
}
/**
* Returns event as HTML string
*/
public String toHtml() {
StringWriter sw = new StringWriter();
try {
toHtml(sw);
} catch (IOException e) {
LOG.error("String writer should never throw IOException", e);
return null;
}
return sw.getBuffer().toString();
}
}