Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.sap.psr.vulas.monitor.InstrumentationControl Maven / Gradle / Ivy
/**
* This file is part of Eclipse Steady.
*
* 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.
*
* SPDX-License-Identifier: Apache-2.0
*
* Copyright (c) 2018 SAP SE or an SAP affiliate company. All rights reserved.
*/
package com.sap.psr.vulas.monitor;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.sap.psr.vulas.backend.BackendConnectionException;
import com.sap.psr.vulas.backend.BackendConnector;
import com.sap.psr.vulas.core.util.CoreConfiguration;
import com.sap.psr.vulas.java.JarAnalyzer;
import com.sap.psr.vulas.java.JavaId;
import com.sap.psr.vulas.java.JavaPackageId;
import com.sap.psr.vulas.java.WarAnalyzer;
import com.sap.psr.vulas.shared.connectivity.Service;
import com.sap.psr.vulas.shared.json.model.Dependency;
import com.sap.psr.vulas.shared.util.FileUtil;
import com.sap.psr.vulas.shared.util.StringList;
import com.sap.psr.vulas.shared.util.StringUtil;
import com.sap.psr.vulas.shared.util.VulasConfiguration;
/**
* Controls whether or not a given Java class is instrumented.
* To that end, it uses a couple of blacklists and whitelists read from the configuration.
* Moreover, it maintains statistics to report on the overall number of classes and constructs instrumented.
*
* @see DynamicTransformer
* @see JarAnalyzer
* @see WarAnalyzer
*/
public class InstrumentationControl {
// ====================================== STATIC MEMBERS
public static enum InstrumentationMetrics { classesTotal, classesInstrumentedSuccess, classesInstrumentedFailure, classesAlreadyInstrumented };
private static final Log log = LogFactory.getLog(InstrumentationControl.class);
/**
* All instances of the class, used to produce overall instrumentation statistics.
*/
private static Map instances = new HashMap();
// ====================================== INSTANCE MEMBERS
private Object instrumentationContext = null;
private LoaderFilter classloaderWhitelist = null;
private StringList classloaderBlacklist = new StringList();
// ------- Members related to blacklist checks for JARs and DIRs
/** Build from config param instr.blacklist.jars. */
private StringList blacklistedJars = new StringList();
/** Build from config param instr.blacklist.dirs. */
private Set blacklistedDirs = new HashSet();
/** JARs from which classes were loaded, together with the cached result of the blacklist check. */
private Map checkedJars = new ConcurrentHashMap();
private int acceptedJarsCount = 0, blacklistedJarsCount = 0;
// ------- Members related to blacklist checks for classes
/** Build from config param instr.blacklist.classes.jre and instr.blacklist.classes.custom. */
private StringList blacklistedClasses = new StringList();
/** Classes for which {@link #isBlacklistedClass(String)} was called, together with the cached result of the blacklist check. */
private Map checkedClasses = new HashMap();
/** Counters for accepted and blacklisted classes. */
private int acceptedClassesCount = 0, blacklistedClassesCount = 0;
// ------- Members related to instrumentation stats (on class and package level)
private int classesCount = 0, successfulInstrumentationCount = 0, failedInstrumentationCount = 0, alreadyInstrumentedCount = 0;
private Map successfulInstrumentationCountPP = new HashMap();
private Map failedInstrumentationCountPP = new HashMap();
private Map alreadyInstrumentedCountPP = new HashMap();
private Set failedInstrumentations = new HashSet();
/**
* Instances are only created through {@link InstrumentationControl#getInstance()}, in order to maintain
* a set of all instances existing.
*/
private InstrumentationControl(Object _context) {
this.instrumentationContext = _context;
//TODO: It is maybe better to use static members for the blacklists, maybe no need to re-create the same lists over and over again
// Get the configuration
final Configuration cfg = VulasConfiguration.getGlobal().getConfiguration();
// Only instrument classes loader by a certain class loader
if(cfg.getString("instr.whitelist.classloader", null)!=null)
this.classloaderWhitelist = new ClassNameLoaderFilter(cfg.getString("instr.whitelist.classloader", null), cfg.getBoolean("instr.whitelist.classloader.acceptChilds", true));
// Blacklist from configuration (evaluated during transform)
this.blacklistedClasses.addAll(cfg.getStringArray(CoreConfiguration.INSTR_BLACKLIST_CLASSES));
this.blacklistedClasses.addAll(cfg.getStringArray(CoreConfiguration.INSTR_BLACKLIST_JRE_CLASSES));
this.blacklistedClasses.addAll(cfg.getStringArray(CoreConfiguration.INSTR_BLACKLIST_CUSTOM_CLASSES));
//TODO: Duplicate to the class loader filter?
this.classloaderBlacklist.addAll(cfg.getStringArray(CoreConfiguration.INSTR_BLACKLIST_CLASSLOADER));
// JAR and DIR blacklists (evaluated during transform)
this.blacklistedJars.addAll(cfg.getStringArray(CoreConfiguration.INSTR_BLACKLIST_JARS));
this.blacklistedJars.addAll(cfg.getStringArray(CoreConfiguration.INSTR_BLACKLIST_CUSTOM_JARS));
final String[] items = cfg.getStringArray(CoreConfiguration.INSTR_BLACKLIST_DIRS);
for(String item: items) {
try {
this.blacklistedDirs.add(Paths.get(item));
} catch (Exception e) {
InstrumentationControl.log.error("Error when adding [" + item + "] to blacklisted JAR dirs: " + e.getMessage());
}
}
// Depending on the configuration, dependencies with certain scopes can be added to the JAR blacklist
final String[] scopes = cfg.getStringArray(CoreConfiguration.INSTR_BLACKLIST_JAR_SCOPES);
if(scopes!=null && scopes.length>0 && VulasConfiguration.getGlobal().hasServiceUrl(Service.BACKEND)) {
try {
// Read all dependencies and add JARs whose dependency matches the specified scope(s) to the blacklist
final Set deps = BackendConnector.getInstance().getAppDeps(CoreConfiguration.buildGoalContextFromGlobalConfiguration(), CoreConfiguration.getAppContext());
int blacklisted_deps_count = 0;
for(Dependency dep: deps) {
if(dep.getScope()!=null) {
for(String scope: scopes) {
if(scope.equalsIgnoreCase(dep.getScope().toString())) {
this.blacklistedJars.add(dep.getFilename());
blacklisted_deps_count++;
break;
}
}
}
}
InstrumentationControl.log.info("Added [" + blacklisted_deps_count + "] dependencies with scopes [" + StringUtil.join(scopes, ", ") + "] to JAR blacklist");
} catch (ConfigurationException e) {
InstrumentationControl.log.error("Configuration error when adding JARs with blacklisted scopes to JAR blacklist: " + e.getMessage());
} catch (BackendConnectionException e) {
InstrumentationControl.log.error("Connection error when adding JARs with blacklisted scopes to JAR blacklist: " + e.getMessage());
}
}
}
/**
* Updates the statistics depending on whether the instrumentation of the given Java class succeeded or not.
*
* @param _jcid the class that was instrumented successfully or not
* @param _instr_successful null if the class was already instrumented, or true/false if the instrumentation was done in this transformer
*/
public void updateInstrumentationStatistics(JavaId _jcid, Boolean _instr_successful) {
final JavaPackageId pid = _jcid.getJavaPackageId();
this.classesCount++;
// Case 1: Already instrumented
if(_instr_successful==null) {
final Integer count = this.alreadyInstrumentedCountPP.get(pid);
final Integer new_count = new Integer((count==null ? 1 : count.intValue()+1));
this.alreadyInstrumentedCountPP.put(pid, new_count);
this.alreadyInstrumentedCount++;
}
// Case 2: Successful instrumentation
else if(_instr_successful.booleanValue()) {
final Integer count = this.successfulInstrumentationCountPP.get(pid);
final Integer new_count = new Integer((count==null ? 1 : count.intValue()+1));
this.successfulInstrumentationCountPP.put(pid, new_count);
this.successfulInstrumentationCount++;
}
// Case 3: Unsuccessful instrumentation
else {
final Integer count = this.failedInstrumentationCountPP.get(pid);
final Integer new_count = new Integer((count==null ? 1 : count.intValue()+1));
this.failedInstrumentationCountPP.put(pid, new_count);
this.failedInstrumentationCount++;
this.failedInstrumentations.add(_jcid);
}
}
/**
* Logs various statistics, depending on the log level.
* Called by {@link UploadScheduler#run()}.
*/
public synchronized void logStatistics() {
// Log loader hierarchy
/*if(InstrumentationControl.log.isDebugEnabled()) {
InstrumentationControl.log.debug("Class loader hierarchy:");
this.loaderHierarchy.logHierarchy(this.loaderHierarchy.getRoot(), 0);
}*/
if(this.checkedJars.size()>0 || this.checkedClasses.size()>0)
InstrumentationControl.log.info("Instrumentation metrics in context [" + this.instrumentationContext.toString() + "]:");
if(this.checkedClasses.size()>0)
InstrumentationControl.log.info(" Class name filter: [" + this.acceptedClassesCount + " classes] accepted for instrumentation, [" + this.blacklistedClassesCount + " classes] ignored (blacklisted)");
// Accepted and ignored (blacklisted) JARs
if(this.checkedJars.size()>0) {
InstrumentationControl.log.info(" JAR name and directory filter: [" + this.acceptedJarsCount + " JARs] accepted for instrumentation, [" + this.blacklistedJarsCount + " JARs] ignored (blacklisted)");
if(InstrumentationControl.log.isInfoEnabled())
for(String path: this.checkedJars.keySet()) {
if(this.checkedJars.get(path))
InstrumentationControl.log.info(" [IGNOR] [" + path + "]");
else
InstrumentationControl.log.info(" [ACCEP] [" + path + "]");
}
}
// Accepted and ignored (blacklisted) classes
if(this.checkedClasses.size()>0) {
InstrumentationControl.log.info(" Of [" + StringUtil.padLeft(this.classesCount, 5) + "] classes considered for instrumentation after class and JAR filters:");
InstrumentationControl.log.info(" [" + StringUtil.padLeft(this.alreadyInstrumentedCount, 5) + "] classes in [" + StringUtil.padLeft(this.alreadyInstrumentedCountPP.keySet().size(), 3) + "] packages: Instrumentation existed");
if(InstrumentationControl.log.isDebugEnabled())
for(JavaPackageId pid : this.alreadyInstrumentedCountPP.keySet())
InstrumentationControl.log.debug(" | " + this.alreadyInstrumentedCountPP.get(pid).intValue() + " in " + pid.toString());
// Log no. of instrumented classes, and those for which instrumentation failed (per package)
InstrumentationControl.log.info(" [" + StringUtil.padLeft(this.successfulInstrumentationCount, 5) + "] classes in [" + StringUtil.padLeft(this.successfulInstrumentationCountPP.keySet().size(), 3) + "] packages: Instrumentation successful");
// for(JavaPackageId pid : this.successfulInstrumentationCountPP.keySet())
// ConstructTracer.log.info(" | " + this.successfulInstrumentationCountPP.get(pid).intValue() + " in " + pid.toString());
InstrumentationControl.log.info(" [" + StringUtil.padLeft(this.failedInstrumentationCount, 5) + "] classes in [" + StringUtil.padLeft(this.failedInstrumentationCountPP.keySet().size(), 3) + "] packages: Instrumentation failed");
if(InstrumentationControl.log.isDebugEnabled())
for(JavaPackageId pid : this.failedInstrumentationCountPP.keySet())
InstrumentationControl.log.debug(" | " + this.failedInstrumentationCountPP.get(pid).intValue() + " in " + pid.toString());
}
}
/**
* getMetric.
*
* @param _metric a {@link com.sap.psr.vulas.monitor.InstrumentationControl.InstrumentationMetrics} object.
* @return a long.
*/
public long getMetric(InstrumentationMetrics _metric) {
if(InstrumentationMetrics.classesTotal.equals(_metric))
return this.classesCount;
else if(InstrumentationMetrics.classesAlreadyInstrumented.equals(_metric))
return this.alreadyInstrumentedCount;
else if(InstrumentationMetrics.classesInstrumentedSuccess.equals(_metric))
return this.successfulInstrumentationCount;
else if(InstrumentationMetrics.classesInstrumentedFailure.equals(_metric))
return this.failedInstrumentationCount;
else
return -1;
}
/**
* getStatistics.
*
* @return a {@link java.util.Map} object.
*/
public Map getStatistics() {
final Map stats = new HashMap();
stats.put(InstrumentationMetrics.classesTotal, new Long(this.classesCount));
stats.put(InstrumentationMetrics.classesAlreadyInstrumented, new Long(this.alreadyInstrumentedCount));
stats.put(InstrumentationMetrics.classesInstrumentedSuccess, new Long(this.successfulInstrumentationCount));
stats.put(InstrumentationMetrics.classesInstrumentedFailure, new Long(this.failedInstrumentationCount));
return stats;
}
/**
* Returns true if the JAR at the specified path is blacklisted, false otherwise.
* A JAR is blacklisted if the file name matches any of the patterns specified with configuration parameter instr.blacklist.jars,
* or if it resides in a directory (or subdirectory) of any of the paths specified with instr.blacklist.dirs.
*
* @param _url a {@link java.net.URL} object.
* @return a boolean.
*/
public boolean isBlacklistedJar(URL _url) {
// If the URL does not point to a class that has been loaded from a URL, return false
if(_url==null) return true;
// Check whether it has been loaded from a JAR: If not, return false (probably an app class), otherwise check the blacklist
final String jar_path = FileUtil.getJARFilePath(_url.toString());
if(jar_path==null) return false;
// If a JAR URL can be determined, check if we already checked whether it is blacklisted
if(!this.checkedJars.containsKey(jar_path)) {
String path2 = jar_path.toString().replaceAll("C:", "C");
final String jar_file = Paths.get(path2).getFileName().toString();
// Compare path with whitelist
boolean blacklisted_dir = false;
for(Path path: this.blacklistedDirs) {
if(Paths.get(path2).startsWith(path)) {
blacklisted_dir = true;
break;
}
}
// Compare filename with blacklist
boolean blacklisted_jar = this.blacklistedJars.contains(jar_file, StringList.ComparisonMode.PATTERN, StringList.CaseSensitivity.CASE_INSENSITIVE);
// Result
final boolean is_blacklisted = blacklisted_dir || blacklisted_jar;
InstrumentationControl.log.info("JAR [" + jar_path + "] is blacklisted: [" + is_blacklisted + "]");
// Cache
this.checkedJars.put(jar_path, is_blacklisted);
// Stats
if(is_blacklisted) this.blacklistedJarsCount++;
else this.acceptedJarsCount++;
}
return this.checkedJars.get(jar_path);
}
/**
* Returns true if the class with the specified qualified name is blacklisted, false otherwise.
* A qualified name is blacklisted if its package (or any of its parent packages) is specified
* as part of the configuration parameters instr.blacklist.classes.jre or instr.blacklist.classes.custom.
*
* @param _qname a {@link java.lang.String} object.
* @return a boolean.
*/
public boolean isBlacklistedClass(String _qname) {
if(!this.checkedClasses.containsKey(_qname)) {
// Result
final boolean is_blacklisted = this.blacklistedClasses.contains(_qname, StringList.ComparisonMode.STARTSWITH, StringList.CaseSensitivity.CASE_SENSITIVE);
// Cache
this.checkedClasses.put(_qname, is_blacklisted);
// Stats
if(is_blacklisted) this.blacklistedClassesCount++;
else this.acceptedClassesCount++;
}
return this.checkedClasses.get(_qname);
}
/**
* Returns the total number of classes looked at.
*
* @return a int.
*/
public int countClassesTotal() { return this.classesCount; }
/**
* Returns the number of classes that were already instrumented.
*
* @see #updateInstrumentationStatistics(JavaId, Boolean)
* @return a int.
*/
public int countClassesInstrumentedAlready() { return this.alreadyInstrumentedCount; }
/**
* Returns the number of classes that were successfully instrumented.
*
* @see #updateInstrumentationStatistics(JavaId, Boolean)
* @return a int.
*/
public int countClassesInstrumentedSuccess() { return this.successfulInstrumentationCount; }
/**
* Returns the number of classes that could not be instrumented.
*
* @see #updateInstrumentationStatistics(JavaId, Boolean)
* @return a int.
*/
public int countClassesInstrumentedFailure() { return this.failedInstrumentationCount; }
/**
* Returns the classes which could not be instrumented, hence, for which no
* traces or other information could be collected.
*
* @return a {@link java.util.Set} object.
*/
public Set getFailedInstrumentations() { return this.failedInstrumentations; }
// ====================================== STATIC MEMBERS
/**
* getInstance.
*
* @return a {@link com.sap.psr.vulas.monitor.InstrumentationControl} object.
*/
public static synchronized InstrumentationControl getInstance() {
return InstrumentationControl.getInstance(null);
}
/**
* getInstance.
*
* @param _context a {@link java.lang.Object} object.
* @return a {@link com.sap.psr.vulas.monitor.InstrumentationControl} object.
*/
public static synchronized InstrumentationControl getInstance(Object _context) {
InstrumentationControl instance = null;
if(!instances.containsKey(_context))
InstrumentationControl.instances.put(_context, new InstrumentationControl(_context));
instance = instances.get(_context);
return instance;
}
/**
* getOverallStatistics.
*
* @return a {@link java.util.Map} object.
*/
public static synchronized Map getOverallStatistics() {
final Map overall_stats = new HashMap();
for(InstrumentationControl ctrl: instances.values()) {
final Map stats = ctrl.getStatistics();
for(InstrumentationMetrics m: stats.keySet()) {
final String key = m.toString();
if(overall_stats.containsKey(key)) {
final long new_count = overall_stats.get(key).longValue() + stats.get(m).longValue();
overall_stats.put(key, new Long(new_count));
} else {
overall_stats.put(key, stats.get(m));
}
}
}
return overall_stats;
}
/**
* logOverallStatistics.
*/
public static synchronized void logOverallStatistics() {
for(InstrumentationControl ctrl: instances.values()) {
ctrl.logStatistics();
}
}
}