/*
* Copyright 2008-2014 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 static net.bull.javamelody.HttpParameters.CONNECTIONS_PART;
import static net.bull.javamelody.HttpParameters.CURRENT_REQUESTS_PART;
import static net.bull.javamelody.HttpParameters.DATABASE_PART;
import static net.bull.javamelody.HttpParameters.DEFAULT_WITH_CURRENT_REQUESTS_PART;
import static net.bull.javamelody.HttpParameters.EXPLAIN_PLAN_PART;
import static net.bull.javamelody.HttpParameters.GRAPH_PARAMETER;
import static net.bull.javamelody.HttpParameters.HEAP_HISTO_PART;
import static net.bull.javamelody.HttpParameters.HEIGHT_PARAMETER;
import static net.bull.javamelody.HttpParameters.HOTSPOTS_PART;
import static net.bull.javamelody.HttpParameters.JNDI_PART;
import static net.bull.javamelody.HttpParameters.JROBINS_PART;
import static net.bull.javamelody.HttpParameters.MBEANS_PART;
import static net.bull.javamelody.HttpParameters.OTHER_JROBINS_PART;
import static net.bull.javamelody.HttpParameters.PART_PARAMETER;
import static net.bull.javamelody.HttpParameters.PATH_PARAMETER;
import static net.bull.javamelody.HttpParameters.PROCESSES_PART;
import static net.bull.javamelody.HttpParameters.REQUEST_PARAMETER;
import static net.bull.javamelody.HttpParameters.SESSIONS_PART;
import static net.bull.javamelody.HttpParameters.SESSION_ID_PARAMETER;
import static net.bull.javamelody.HttpParameters.WIDTH_PARAMETER;
import java.io.IOException;
import java.io.Serializable;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import net.bull.javamelody.SamplingProfiler.SampledMethod;
/**
* Collecteur de données pour une application sur un ou plusieurs serveur(s) distant() : utilisé par serveur de collecte et par IHM Swing.
* @author Emeric Vernat
*/
class RemoteCollector {
private final String application;
private List<URL> urls;
private Collector collector;
private List<JavaInformations> javaInformationsList;
private Map<JavaInformations, List<CounterRequestContext>> currentRequests;
private String cookies;
private boolean aggregationDisabled;
/**
* Constructeur.
* @param application Nom de l'application
* @param urls URLs
*/
RemoteCollector(String application, List<URL> urls) {
super();
assert application != null;
assert urls != null;
this.application = application;
this.urls = urls;
}
String collectData() throws IOException {
return collectDataWithUrls(urls);
}
String collectDataIncludingCurrentRequests() throws IOException {
final List<URL> urlsWithCurrentRequests = new ArrayList<URL>();
for (final URL url : urls) {
urlsWithCurrentRequests.add(new URL(url.toString() + '&' + PART_PARAMETER + '='
+ DEFAULT_WITH_CURRENT_REQUESTS_PART));
}
return collectDataWithUrls(urlsWithCurrentRequests);
}
private String collectDataWithUrls(List<URL> urlsForCollect) throws IOException {
final List<JavaInformations> javaInfosList = new ArrayList<JavaInformations>();
final Map<JavaInformations, List<CounterRequestContext>> counterRequestContextsByJavaInformations = new HashMap<JavaInformations, List<CounterRequestContext>>();
final StringBuilder sb = new StringBuilder();
for (final URL url : urlsForCollect) {
final List<Counter> counters = new ArrayList<Counter>();
final List<Serializable> serialized = collectForUrl(url);
dispatchSerializables(serialized, counters, javaInfosList,
counterRequestContextsByJavaInformations, sb);
if (this.collector == null || aggregationDisabled) {
this.collector = new Collector(application, counters);
} else {
addRequestsAndErrors(counters);
}
}
this.javaInformationsList = javaInfosList;
this.currentRequests = counterRequestContextsByJavaInformations;
final String messageForReport;
if (sb.length() == 0) {
messageForReport = null;
} else {
messageForReport = sb.toString();
}
return messageForReport;
}
private void dispatchSerializables(
List<Serializable> serialized,
List<Counter> counters,
List<JavaInformations> javaInfosList,
Map<JavaInformations, List<CounterRequestContext>> counterRequestContextsByJavaInformations,
StringBuilder sb) {
JavaInformations latestJavaInformations = null;
final List<CounterRequestContext> counterRequestContextsList = new ArrayList<CounterRequestContext>();
for (final Serializable serializable : serialized) {
if (serializable instanceof Counter) {
final Counter counter = (Counter) serializable;
counter.setApplication(application);
counters.add(counter);
} else if (serializable instanceof JavaInformations) {
final JavaInformations newJavaInformations = (JavaInformations) serializable;
latestJavaInformations = newJavaInformations;
javaInfosList.add(newJavaInformations);
} else if (serializable instanceof String) {
sb.append(serializable).append('\n');
} else if (serializable instanceof CounterRequestContext) {
final CounterRequestContext counterRequestContext = (CounterRequestContext) serializable;
counterRequestContextsList.add(counterRequestContext);
}
}
if (!counterRequestContextsList.isEmpty()) {
counterRequestContextsByJavaInformations.put(latestJavaInformations,
counterRequestContextsList);
}
}
String executeActionAndCollectData(Action action, String counterName, String sessionId,
String threadId, String jobId, String cacheId) throws IOException {
assert action != null;
final List<URL> actionUrls = new ArrayList<URL>(urls.size());
for (final URL url : urls) {
final StringBuilder actionUrl = new StringBuilder(url.toString());
actionUrl.append("&action=").append(action);
if (counterName != null) {
actionUrl.append("&counter=").append(counterName);
}
if (sessionId != null) {
actionUrl.append("&sessionId=").append(sessionId);
}
if (threadId != null) {
actionUrl.append("&threadId=").append(threadId);
}
if (jobId != null) {
actionUrl.append("&jobId=").append(jobId);
}
if (cacheId != null) {
actionUrl.append("&cacheId=").append(cacheId);
}
actionUrls.add(new URL(actionUrl.toString()));
}
return collectDataWithUrls(actionUrls);
}
List<SessionInformations> collectSessionInformations(String sessionId) throws IOException {
// sessionId est null si on veut toutes les sessions
if (sessionId == null) {
// récupération à la demande des sessions
final List<SessionInformations> sessionsInformations = new ArrayList<SessionInformations>();
for (final URL url : urls) {
final URL sessionsUrl = new URL(url.toString() + '&' + PART_PARAMETER + '='
+ SESSIONS_PART);
final List<SessionInformations> sessions = collectForUrl(sessionsUrl);
sessionsInformations.addAll(sessions);
}
SessionListener.sortSessions(sessionsInformations);
return sessionsInformations;
}
SessionInformations found = null;
for (final URL url : urls) {
final URL sessionsUrl = new URL(url.toString() + '&' + PART_PARAMETER + '='
+ SESSIONS_PART + '&' + SESSION_ID_PARAMETER + '=' + sessionId);
final SessionInformations session = collectForUrl(sessionsUrl);
if (session != null) {
found = session;
break;
}
}
if (found == null) {
// si found est toujours null, alors la session a été invalidée
return Collections.emptyList();
}
return Collections.singletonList(found);
}
List<SampledMethod> collectHotspots() throws IOException {
// récupération à la demande des hotspots
final Map<SampledMethod, SampledMethod> map = new HashMap<SampledMethod, SampledMethod>();
for (final URL url : urls) {
final URL hotspotsUrl = new URL(url.toString() + '&' + PART_PARAMETER + '='
+ HOTSPOTS_PART);
final List<SampledMethod> hotspots = collectForUrl(hotspotsUrl);
if (urls.size() == 1) {
// s'il n'y a qu'un serveur, inutile d'aller plus loin pour fusionner les données
return hotspots;
}
for (final SampledMethod method : hotspots) {
// SampledMethod implémente hashCode et equals
final SampledMethod previous = map.get(method);
if (previous == null) {
map.put(method, method);
} else {
previous.setCount(previous.getCount() + method.getCount());
}
}
}
final List<SampledMethod> hotspots = new ArrayList<SampledMethod>(map.values());
Collections.sort(hotspots);
return hotspots;
}
HeapHistogram collectHeapHistogram() throws IOException {
// récupération à la demande des HeapHistogram
HeapHistogram heapHistoTotal = null;
for (final URL url : urls) {
final URL heapHistoUrl = new URL(url.toString() + '&' + PART_PARAMETER + '='
+ HEAP_HISTO_PART);
final HeapHistogram heapHisto = collectForUrl(heapHistoUrl);
if (heapHistoTotal == null) {
heapHistoTotal = heapHisto;
} else {
heapHistoTotal.add(heapHisto);
}
}
return heapHistoTotal;
}
DatabaseInformations collectDatabaseInformations(int requestIndex) throws IOException {
final URL url = urls.get(0);
final URL databaseUrl = new URL(url.toString() + '&' + PART_PARAMETER + '=' + DATABASE_PART
+ '&' + REQUEST_PARAMETER + '=' + requestIndex);
return collectForUrl(databaseUrl);
}
@SuppressWarnings("unchecked")
List<List<ConnectionInformations>> collectConnectionInformations() throws IOException {
// récupération à la demande des connections
final List<List<ConnectionInformations>> connectionInformations = new ArrayList<List<ConnectionInformations>>();
for (final URL url : urls) {
final URL connectionsUrl = new URL(url.toString() + '&' + PART_PARAMETER + '='
+ CONNECTIONS_PART);
final Object result = collectForUrl(connectionsUrl);
if (result instanceof List && !((List<?>) result).isEmpty()
&& ((List<?>) result).get(0) instanceof List) {
// pour le serveur de collecte
final List<List<ConnectionInformations>> connections = (List<List<ConnectionInformations>>) result;
connectionInformations.addAll(connections);
} else {
final List<ConnectionInformations> connections = (List<ConnectionInformations>) result;
connectionInformations.add(connections);
}
}
return connectionInformations;
}
@SuppressWarnings("unchecked")
Map<String, List<ProcessInformations>> collectProcessInformations() throws IOException {
// récupération à la demande des processus
final String title = I18N.getString("Processus");
final Map<String, List<ProcessInformations>> processesByTitle = new LinkedHashMap<String, List<ProcessInformations>>();
for (final URL url : urls) {
final URL processUrl = new URL(url.toString() + '&' + PART_PARAMETER + '='
+ PROCESSES_PART);
final Object result = collectForUrl(processUrl);
if (result instanceof Map) {
// pour le serveur de collecte et pour les nodes dans Jenkins
final Map<String, List<ProcessInformations>> processByTitle = (Map<String, List<ProcessInformations>>) result;
for (final Map.Entry<String, List<ProcessInformations>> entry : processByTitle
.entrySet()) {
String node = entry.getKey();
if (!node.startsWith(title)) {
// si serveur de collecte alors il y a déjà un titre, mais pas pour les nodes Jenkins
node = title + " (" + entry.getKey() + ')';
}
final List<ProcessInformations> processList = entry.getValue();
processesByTitle.put(node, processList);
}
} else {
final List<ProcessInformations> processList = (List<ProcessInformations>) result;
processesByTitle.put(title + " (" + getHostAndPort(url) + ')', processList);
}
}
return processesByTitle;
}
List<JndiBinding> collectJndiBindings(String path) throws IOException {
// récupération à la demande des bindings JNDI,
// contrairement aux requêtes en cours ou aux processus, un serveur de l'application suffira
// car l'arbre JNDI est en général identique dans tout l'éventuel cluster
final URL url = urls.get(0);
final URL jndiUrl = new URL(url.toString() + '&' + PART_PARAMETER + '=' + JNDI_PART
+ (path != null ? '&' + PATH_PARAMETER + '=' + path : ""));
return collectForUrl(jndiUrl);
}
@SuppressWarnings("unchecked")
Map<String, List<MBeanNode>> collectMBeans() throws IOException {
// récupération à la demande des MBeans
final String title = I18N.getString("MBeans");
final Map<String, List<MBeanNode>> mbeansByTitle = new LinkedHashMap<String, List<MBeanNode>>();
for (final URL url : urls) {
final URL mbeansUrl = new URL(url.toString() + '&' + PART_PARAMETER + '=' + MBEANS_PART);
final Object result = collectForUrl(mbeansUrl);
if (result instanceof Map) {
// pour le serveur de collecte et les nodes dans Jenkins
final Map<String, List<MBeanNode>> mbeansByNodeName = (Map<String, List<MBeanNode>>) result;
for (final Map.Entry<String, List<MBeanNode>> entry : mbeansByNodeName.entrySet()) {
String node = entry.getKey();
if (!node.startsWith(title)) {
// si serveur de collecte alors il y a déjà un titre, mais pas pour les nodes Jenkins
node = title + " (" + entry.getKey() + ')';
}
final List<MBeanNode> mbeans = entry.getValue();
mbeansByTitle.put(node, mbeans);
}
} else {
final List<MBeanNode> mbeans = (List<MBeanNode>) result;
mbeansByTitle.put(title + " (" + getHostAndPort(url) + ')', mbeans);
}
}
return mbeansByTitle;
}
Map<JavaInformations, List<CounterRequestContext>> collectCurrentRequests() throws IOException {
// récupération à la demande des requêtes en cours
final Map<JavaInformations, List<CounterRequestContext>> requests = new LinkedHashMap<JavaInformations, List<CounterRequestContext>>();
for (final URL url : urls) {
final URL currentRequestsUrl = new URL(url.toString() + '&' + PART_PARAMETER + '='
+ CURRENT_REQUESTS_PART);
final Map<JavaInformations, List<CounterRequestContext>> result = collectForUrl(currentRequestsUrl);
requests.putAll(result);
}
return requests;
}
List<List<ThreadInformations>> getThreadInformationsLists() {
final List<List<ThreadInformations>> result = new ArrayList<List<ThreadInformations>>();
for (final JavaInformations javaInformations : this.javaInformationsList) {
result.add(new ArrayList<ThreadInformations>(javaInformations
.getThreadInformationsList()));
}
return result;
}
Map<String, byte[]> collectJRobins(int width, int height) throws IOException {
final URL url = urls.get(0);
final URL jrobinNamesUrl = new URL(url.toString() + '&' + PART_PARAMETER + '='
+ JROBINS_PART + '&' + WIDTH_PARAMETER + '=' + width + '&' + HEIGHT_PARAMETER + '='
+ height);
return collectForUrl(jrobinNamesUrl);
}
Map<String, byte[]> collectOtherJRobins(int width, int height) throws IOException {
final URL url = urls.get(0);
final URL otherJRobinNamesUrl = new URL(url.toString() + '&' + PART_PARAMETER + '='
+ OTHER_JROBINS_PART + '&' + WIDTH_PARAMETER + '=' + width + '&' + HEIGHT_PARAMETER
+ '=' + height);
return collectForUrl(otherJRobinNamesUrl);
}
byte[] collectJRobin(String graphName, int width, int height) throws IOException {
final URL url = urls.get(0);
final URL jrobinUrl = new URL(url.toString() + '&' + GRAPH_PARAMETER + '=' + graphName
+ '&' + PART_PARAMETER + '=' + JROBINS_PART + '&' + WIDTH_PARAMETER + '=' + width
+ '&' + HEIGHT_PARAMETER + '=' + height);
return collectForUrl(jrobinUrl);
}
String collectSqlRequestExplainPlan(String sqlRequest) throws IOException {
final URL url = urls.get(0);
final URL explainPlanUrl = new URL(url.toString() + '&' + PART_PARAMETER + '='
+ EXPLAIN_PLAN_PART);
final Map<String, String> headers = new HashMap<String, String>();
headers.put(REQUEST_PARAMETER, sqlRequest);
if (cookies != null) {
headers.put("Cookie", cookies);
}
final LabradorRetriever labradorRetriever = new LabradorRetriever(explainPlanUrl, headers);
return labradorRetriever.call();
}
private void addRequestsAndErrors(List<Counter> counters) {
for (final Counter newCounter : counters) {
final Counter counter = collector.getCounterByName(newCounter.getName());
// counter.isDisplayed() peut changer pour spring, ejb ou services selon l'utilisation
counter.setDisplayed(newCounter.isDisplayed());
counter.addRequestsAndErrors(newCounter);
}
}
private <T> T collectForUrl(URL url) throws IOException {
final LabradorRetriever labradorRetriever;
if (cookies != null) {
final Map<String, String> headers = Collections.singletonMap("Cookie", cookies);
labradorRetriever = new LabradorRetriever(url, headers);
} else {
labradorRetriever = new LabradorRetriever(url);
}
return labradorRetriever.call();
}
static String getHostAndPort(URL url) {
if (url.getPort() != -1) {
return url.getHost() + ':' + url.getPort();
}
// port est -1 si c'est le port par défaut (80)
return url.getHost();
}
String getApplication() {
return application;
}
List<URL> getURLs() {
return urls;
}
Collector getCollector() {
return collector;
}
List<JavaInformations> getJavaInformationsList() {
return javaInformationsList;
}
Map<JavaInformations, List<CounterRequestContext>> getCurrentRequests() {
return currentRequests;
}
// cette méthode est utilisée dans l'ihm Swing
void setURLs(List<URL> newURLs) {
assert urls != null;
this.urls = newURLs;
}
// cette méthode est utilisée dans l'ihm Swing
void setCookies(String cookies) {
// cookies peut être null
this.cookies = cookies;
}
// cette méthode est utilisée dans l'ihm Swing
void disableAggregation() {
aggregationDisabled = true;
}
}