Skip to content

Commit

Permalink
publish data in Graphite, if Graphite address configured
Browse files Browse the repository at this point in the history
  • Loading branch information
evernat committed May 23, 2017
1 parent 348a976 commit ce94787
Show file tree
Hide file tree
Showing 3 changed files with 196 additions and 43 deletions.
100 changes: 58 additions & 42 deletions javamelody-core/src/main/java/net/bull/javamelody/Collector.java
Expand Up @@ -83,6 +83,7 @@ class Collector { // NOPMD
private Date lastDateOfDeletedObsoleteFiles = new Date();
private boolean stopped;
private final boolean noDatabase = Parameters.isNoDatabase();
private final Graphite graphite = Graphite.getInstance();
/**
* Les versions de l'applications avec pour chacune la date de déploiement.
*/
Expand Down Expand Up @@ -361,23 +362,29 @@ void collectWithoutErrors(List<JavaInformations> javaInformationsList) {

private synchronized long collect(List<JavaInformations> javaInformationsList)
throws IOException {
// si pas d'informations, on ne met pas 0 : on ne met rien
if (!javaInformationsList.isEmpty()) {
collectJavaInformations(javaInformationsList);
collectOtherJavaInformations(javaInformationsList);
collectTomcatInformations(javaInformationsList);
}
long memorySize = 0;
for (final Counter counter : counters) {
// counter.isDisplayed() peut changer pour spring, ejb, guice ou services selon l'utilisation
dayCountersByCounter.get(counter).setDisplayed(counter.isDisplayed());
// collecte pour chaque compteur (hits par minute, temps moyen, % d'erreurs système)
// Rq : il serait possible d'ajouter le débit total en Ko / minute (pour http)
// mais autant monitorer les vrais débits réseaux au niveau de l'OS
if (counter.isDisplayed()) {
// si le compteur n'est pas affiché (par ex ejb), pas de collecte
// et pas de persistance de fichiers jrobin ou du compteur
memorySize += collectCounterData(counter);
try {
// si pas d'informations, on ne met pas 0 : on ne met rien
if (!javaInformationsList.isEmpty()) {
collectJavaInformations(javaInformationsList);
collectOtherJavaInformations(javaInformationsList);
collectTomcatInformations(javaInformationsList);
}
for (final Counter counter : counters) {
// counter.isDisplayed() peut changer pour spring, ejb, guice ou services selon l'utilisation
dayCountersByCounter.get(counter).setDisplayed(counter.isDisplayed());
// collecte pour chaque compteur (hits par minute, temps moyen, % d'erreurs système)
// Rq : il serait possible d'ajouter le débit total en Ko / minute (pour http)
// mais autant monitorer les vrais débits réseaux au niveau de l'OS
if (counter.isDisplayed()) {
// si le compteur n'est pas affiché (par ex ejb), pas de collecte
// et pas de persistance de fichiers jrobin ou du compteur
memorySize += collectCounterData(counter);
}
}
} finally {
if (graphite != null) {
graphite.send();
}
}

Expand Down Expand Up @@ -496,9 +503,9 @@ private void collectOtherJavaInformations(List<JavaInformations> javaInformation
final int gcPercentage = Math
.min((int) ((garbageCollectionTimeMillis - this.gcTimeMillis) * 100
/ periodMillis / availableProcessors), 100);
getOtherJRobin("gc").addValue(gcPercentage);
addJRobinValue(getOtherJRobin("gc"), gcPercentage);
} else {
getOtherJRobin("gc").addValue(0d);
addJRobinValue(getOtherJRobin("gc"), 0d);
}
this.gcTimeMillis = garbageCollectionTimeMillis;
}
Expand All @@ -515,7 +522,7 @@ private void collectOtherJavaInformations(List<JavaInformations> javaInformation
otherJRobinsValues.put("fileDescriptors", (double) unixOpenFileDescriptorCount);
for (final Map.Entry<String, Double> entry : otherJRobinsValues.entrySet()) {
if (entry.getValue() >= 0) {
getOtherJRobin(entry.getKey()).addValue(entry.getValue());
addJRobinValue(getOtherJRobin(entry.getKey()), entry.getValue());
}
}

Expand All @@ -525,16 +532,16 @@ private void collectOtherJavaInformations(List<JavaInformations> javaInformation
// collecte du nombre de transactions base de données par minute
if (this.transactionCount != NOT_A_NUMBER) {
final double periodMinutes = periodMillis / 60000d;
getOtherJRobin("transactionsRate").addValue(
addJRobinValue(getOtherJRobin("transactionsRate"),
(databaseTransactionCount - this.transactionCount) / periodMinutes);
} else {
getOtherJRobin("transactionsRate").addValue(0d);
addJRobinValue(getOtherJRobin("transactionsRate"), 0d);
}
this.transactionCount = databaseTransactionCount;
}

if (freeDiskSpaceInTemp != Long.MAX_VALUE) {
getOtherJRobin("Free_disk_space").addValue(freeDiskSpaceInTemp);
addJRobinValue(getOtherJRobin("Free_disk_space"), freeDiskSpaceInTemp);
}

// on pourrait collecter la valeur 100 dans jrobin pour qu'il fasse la moyenne
Expand Down Expand Up @@ -570,7 +577,7 @@ private void collectJRobinValues(long usedMemory, long processesCpuTimeMillis,
int availableProcessors, int sessionCount, int activeThreadCount,
int activeConnectionCount, int usedConnectionCount) throws IOException {
// collecte de la mémoire java
getCounterJRobin("usedMemory").addValue(usedMemory);
addJRobinValue(getCounterJRobin("usedMemory"), usedMemory);

// collecte du pourcentage d'utilisation cpu
if (processesCpuTimeMillis >= 0) {
Expand All @@ -587,9 +594,9 @@ private void collectJRobinValues(long usedMemory, long processesCpuTimeMillis,
final int cpuPercentage = Math
.min((int) ((processesCpuTimeMillis - this.cpuTimeMillis) * 100
/ periodMillis / availableProcessors), 100);
getCounterJRobin("cpu").addValue(cpuPercentage);
addJRobinValue(getCounterJRobin("cpu"), cpuPercentage);
} else {
getCounterJRobin("cpu").addValue(0d);
addJRobinValue(getCounterJRobin("cpu"), 0d);
}
this.cpuTimeMillis = processesCpuTimeMillis;
}
Expand All @@ -599,37 +606,37 @@ private void collectJRobinValues(long usedMemory, long processesCpuTimeMillis,
if (getCounterByName(Counter.HTTP_COUNTER_NAME) != null) {
// collecte du nombre de sessions http
if (sessionCount >= 0) {
getCounterJRobin("httpSessions").addValue(sessionCount);
addJRobinValue(getCounterJRobin("httpSessions"), sessionCount);
}
// collecte du nombre de threads actifs (requêtes http en cours)
getCounterJRobin("activeThreads").addValue(activeThreadCount);
addJRobinValue(getCounterJRobin("activeThreads"), activeThreadCount);
}
if (!noDatabase) {
// collecte du nombre de connexions jdbc actives et du nombre de connexions jdbc ouvertes
getCounterJRobin("activeConnections").addValue(activeConnectionCount);
getCounterJRobin("usedConnections").addValue(usedConnectionCount);
addJRobinValue(getCounterJRobin("activeConnections"), activeConnectionCount);
addJRobinValue(getCounterJRobin("usedConnections"), usedConnectionCount);
}

// si ce collector est celui des nodes Jenkins, on collecte le nombre de builds en cours
// pour le graphique
if (getCounterByName(Counter.BUILDS_COUNTER_NAME) != null) {
getCounterJRobin("runningBuilds").addValue(JdbcWrapper.getRunningBuildCount());
getCounterJRobin("buildQueueLength").addValue(JdbcWrapper.getBuildQueueLength());
addJRobinValue(getCounterJRobin("runningBuilds"), JdbcWrapper.getRunningBuildCount());
addJRobinValue(getCounterJRobin("buildQueueLength"), JdbcWrapper.getBuildQueueLength());
}
}

private void collectTomcatValues(int tomcatBusyThreads, long bytesReceived, long bytesSent)
throws IOException {
getOtherJRobin("tomcatBusyThreads").addValue(tomcatBusyThreads);
addJRobinValue(getOtherJRobin("tomcatBusyThreads"), tomcatBusyThreads);
if (this.tomcatBytesSent != NOT_A_NUMBER) {
final double periodMinutes = periodMillis / 60000d;
getOtherJRobin("tomcatBytesReceived")
.addValue((bytesReceived - this.tomcatBytesReceived) / periodMinutes);
getOtherJRobin("tomcatBytesSent")
.addValue((bytesSent - this.tomcatBytesSent) / periodMinutes);
addJRobinValue(getOtherJRobin("tomcatBytesReceived"),
(bytesReceived - this.tomcatBytesReceived) / periodMinutes);
addJRobinValue(getOtherJRobin("tomcatBytesSent"),
(bytesSent - this.tomcatBytesSent) / periodMinutes);
} else {
getOtherJRobin("tomcatBytesReceived").addValue(0d);
getOtherJRobin("tomcatBytesSent").addValue(0d);
addJRobinValue(getOtherJRobin("tomcatBytesReceived"), 0d);
addJRobinValue(getOtherJRobin("tomcatBytesSent"), 0d);
}
this.tomcatBytesReceived = bytesReceived;
this.tomcatBytesSent = bytesSent;
Expand All @@ -643,7 +650,14 @@ private void collectSessionsMeanAge(long sessionAgeSum, int sessionCount) throws
} else {
sessionAgeMeanInMinutes = -1;
}
getOtherJRobin("httpSessionsMeanAge").addValue(sessionAgeMeanInMinutes);
addJRobinValue(getOtherJRobin("httpSessionsMeanAge"), sessionAgeMeanInMinutes);
}
}

private void addJRobinValue(JRobin jRobin, double value) throws IOException {
jRobin.addValue(value);
if (graphite != null) {
graphite.addValue(jRobin.getName(), value);
}
}

Expand Down Expand Up @@ -725,11 +739,12 @@ private long collectCounterData(Counter counter) throws IOException {
final long hitsParMinute = hits * 60 * 1000 / periodMillis;

// on remplit le stockage avec les données
hitsJRobin.addValue(hitsParMinute);
addJRobinValue(hitsJRobin, hitsParMinute);
// s'il n'y a pas eu de hits, alors la moyenne vaut -1 : elle n'a pas de sens
if (hits > 0) {
meanTimesJRobin.addValue(lastPeriodGlobalRequest.getMean());
systemErrorsJRobin.addValue(lastPeriodGlobalRequest.getSystemErrorPercentage());
addJRobinValue(meanTimesJRobin, lastPeriodGlobalRequest.getMean());
addJRobinValue(systemErrorsJRobin,
lastPeriodGlobalRequest.getSystemErrorPercentage());

// s'il y a eu des requêtes, on persiste le compteur pour ne pas perdre les stats
// en cas de crash ou d'arrêt brutal (mais normalement ils seront aussi persistés
Expand Down Expand Up @@ -824,6 +839,7 @@ private void collectCounterRequestData(Counter dayCounter, CounterRequest newReq
newRequest.getName());
// plus nécessaire: if (dayCounter.isErrorCounter()) requestJRobin.addValue(lastPeriodRequest.getHits());

// pas addJRobinValue ici, il y en aurait trop pour Graphite
requestJRobin.addValue(lastPeriodRequest.getMean());
}

Expand Down
132 changes: 132 additions & 0 deletions javamelody-core/src/main/java/net/bull/javamelody/Graphite.java
@@ -0,0 +1,132 @@
/*
* Copyright 2008-2017 by Emeric Vernat
*
* This file is part of Java Melody.
*
* 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 net.bull.javamelody;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Locale;

import javax.net.SocketFactory;

/**
* Publish chart data to Graphite, http://graphiteapp.org/.
* @author Emeric Vernat
*/
class Graphite {
private static final int DEFAULT_GRAPHITE_PORT = 2003;
private static final char SEPARATOR = ' ';

private final SocketFactory socketFactory;
private final InetAddress address;
private final int port;
private final String prefix;

private final DecimalFormat decimalFormat = new DecimalFormat("0.00",
DecimalFormatSymbols.getInstance(Locale.US));
private final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
private final Writer bufferWriter;
private long lastTime;
private String lastTimestamp;

Graphite(SocketFactory socketFactory, InetAddress address, int port, Charset charset,
String prefix) {
super();
assert socketFactory != null;
assert address != null;
assert charset != null;
assert prefix != null;
this.socketFactory = socketFactory;
this.address = address;
this.port = port;
this.prefix = prefix;
this.bufferWriter = new OutputStreamWriter(buffer, charset);
}

static Graphite getInstance() {
final String graphiteAddress = Parameters.getParameter(Parameter.GRAPHITE_ADDRESS);
if (graphiteAddress != null) {
final String address;
final int port;
final int index = graphiteAddress.indexOf(':');
if (index != -1 && index < graphiteAddress.length()) {
address = graphiteAddress.substring(0, index);
port = Integer
.parseInt(graphiteAddress.substring(index + 1, graphiteAddress.length()));
} else {
address = graphiteAddress;
port = DEFAULT_GRAPHITE_PORT;
}
try {
return new Graphite(SocketFactory.getDefault(), InetAddress.getByName(address),
port, Charset.forName("UTF-8"), getDefaultPrefix());
} catch (final UnknownHostException e) {
throw new IllegalArgumentException("Invalid host: " + address, e);
}
}
return null;
}

synchronized void addValue(String metric, double value) throws IOException {
final long timeInSeconds = System.currentTimeMillis() / 1000;
if (lastTime != timeInSeconds) {
lastTimestamp = String.valueOf(timeInSeconds);
lastTime = timeInSeconds;
}
bufferWriter.append(prefix).append('.').append(metric).append(' ');
bufferWriter.append(decimalFormat.format(value)).append(' ');
bufferWriter.append(lastTimestamp).append('\n');
}

synchronized void send() throws IOException {
try {
bufferWriter.flush();
final Socket socket = createSocket();
try {
buffer.writeTo(socket.getOutputStream());
} finally {
socket.close();
}
} catch (final ConnectException e) {
throw new IOException("Error connecting to Graphite at " + address + ':' + port, e);
} finally {
// finally to be sure to not keep too much data in buffer
// including when the socket can't connect
buffer.reset();
}
}

private Socket createSocket() throws IOException {
return socketFactory.createSocket(address, port);
}

private static String getDefaultPrefix() {
final String defaultPrefix = "javamelody." + Parameters
.getContextPath(Parameters.getServletContext()).replace("/", "").replace('.', '_')
+ '.' + Parameters.getHostName().replace('.', '_');
return defaultPrefix.replace(SEPARATOR, '_');
}
}
Expand Up @@ -338,7 +338,12 @@ public enum Parameter {
/**
* If Real User Monitoring enabled by injecting Boomerang javascript into html page ("false" by default).
*/
RUM_ENABLED("rum-enabled");
RUM_ENABLED("rum-enabled"),

/**
* Address of the Graphite (http://graphiteapp.org/) server to send metrics to, for example: 11.22.33.44:2003 (null by default).
*/
GRAPHITE_ADDRESS("graphite-address");

private final String code;

Expand Down

0 comments on commit ce94787

Please sign in to comment.