/** * The MIT License * Copyright © 2010 JmxTrans team * * 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 com.googlecode.jmxtrans.model.output; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.collect.ImmutableList; import com.googlecode.jmxtrans.model.Query; import com.googlecode.jmxtrans.model.Result; import com.googlecode.jmxtrans.model.Server; import com.googlecode.jmxtrans.model.ValidationException; import com.googlecode.jmxtrans.model.naming.KeyUtils; import com.googlecode.jmxtrans.util.OnlyOnceLogger; import lombok.EqualsAndHashCode; import lombok.ToString; import org.apache.commons.pool.impl.GenericKeyedObjectPool; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.concurrent.NotThreadSafe; import javax.inject.Inject; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.InetSocketAddress; import java.net.Socket; import java.util.List; import java.util.Map; import java.util.Map.Entry; import static com.google.common.base.Charsets.UTF_8; import static com.googlecode.jmxtrans.util.NumberUtils.isValidNumber; /** * This low latency and thread safe output writer sends data to a host/port combination * in the Graphite format. * * @see <a href="http://graphite.wikidot.com/getting-your-data-into-graphite">Getting your data into Graphite</a> * * @author jon */ @NotThreadSafe @EqualsAndHashCode(exclude = "pool") @ToString public class GraphiteWriter extends BaseOutputWriter { private static final Logger log = LoggerFactory.getLogger(GraphiteWriter.class); private static final String DEFAULT_ROOT_PREFIX = "servers"; private GenericKeyedObjectPool<InetSocketAddress, Socket> pool; private final String rootPrefix; private final InetSocketAddress address; private final OnlyOnceLogger onlyOnceLogger = new OnlyOnceLogger(log); @JsonCreator public GraphiteWriter( @JsonProperty("typeNames") ImmutableList<String> typeNames, @JsonProperty("booleanAsNumber") boolean booleanAsNumber, @JsonProperty("debug") Boolean debugEnabled, @JsonProperty("rootPrefix") String rootPrefix, @JsonProperty("host") String host, @JsonProperty("port") Integer port, @JsonProperty("settings") Map<String, Object> settings) { super(typeNames, booleanAsNumber, debugEnabled, settings); log.warn("GraphiteWriter is deprecated. Please use GraphiteWriterFactory instead."); this.rootPrefix = firstNonNull( rootPrefix, (String) getSettings().get("rootPrefix"), DEFAULT_ROOT_PREFIX); if (host == null) { host = (String) getSettings().get(HOST); } if (host == null) { throw new NullPointerException("Host cannot be null."); } if (port == null) { port = Settings.getIntegerSetting(getSettings(), PORT, null); } if (port == null) { throw new NullPointerException("Port cannot be null."); } this.address = new InetSocketAddress(host, port); } @Override public void validateSetup(Server server, Query query) throws ValidationException {} @Override public void internalWrite(Server server, Query query, ImmutableList<Result> results) throws Exception { Socket socket = null; PrintWriter writer = null; try { socket = pool.borrowObject(address); writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), UTF_8), true); List<String> typeNames = this.getTypeNames(); for (Result result : results) { log.debug("Query result: {}", result); Map<String, Object> resultValues = result.getValues(); for (Entry<String, Object> values : resultValues.entrySet()) { Object value = values.getValue(); if (isValidNumber(value)) { String line = KeyUtils.getKeyString(server, query, result, values, typeNames, rootPrefix) .replaceAll("[()]", "_") + " " + value.toString() + " " + result.getEpoch() / 1000 + "\n"; log.debug("Graphite Message: {}", line); writer.write(line); } else { onlyOnceLogger.infoOnce("Unable to submit non-numeric value to Graphite: [{}] from result [{}]", value, result); } } } } finally { if (writer != null && writer.checkError()) { log.error("Error writing to Graphite, clearing Graphite socket pool"); pool.invalidateObject(address, socket); } else { pool.returnObject(address, socket); } } } public String getHost() { return address.getHostName(); } public int getPort() { return address.getPort(); } @Inject public void setPool(GenericKeyedObjectPool<InetSocketAddress, Socket> pool) { this.pool = pool; } public static Builder builder() { return new Builder(); } public static final class Builder { private final ImmutableList.Builder<String> typeNames = ImmutableList.builder(); private boolean booleanAsNumber; private Boolean debugEnabled; private String rootPrefix; private String host; private Integer port; private Builder() {} public Builder addTypeNames(List<String> typeNames) { this.typeNames.addAll(typeNames); return this; } public Builder addTypeName(String typeName) { typeNames.add(typeName); return this; } public Builder setBooleanAsNumber(boolean booleanAsNumber) { this.booleanAsNumber = booleanAsNumber; return this; } public Builder setDebugEnabled(boolean debugEnabled) { this.debugEnabled = debugEnabled; return this; } public Builder setRootPrefix(String rootPrefix) { this.rootPrefix = rootPrefix; return this; } public Builder setHost(String host) { this.host = host; return this; } public Builder setPort(int port) { this.port = port; return this; } public GraphiteWriter build() { return new GraphiteWriter( typeNames.build(), booleanAsNumber, debugEnabled, rootPrefix, host, port, null ); } } }