blob: 6c609832df033ecfa7fce6b48c7a3fd0ba5a5a90 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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 org.apache.tamaya.usagetracker;
import org.apache.tamaya.spi.PropertyValue;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Logger;
/**
* Metrics container containing access statistics for a given configuration key.
*/
public final class UsageStat {
private static final String[] EMPTY_TRACE = new String[0];
/**
* the config entry's key.
*/
private final String key;
/**
* Maps with access references, key is the fully qualified package name.
*/
private final Map<String,AccessStats> accessDetails = new ConcurrentHashMap<>();
/**
* The maximal length of the stacktrace stored.
*/
private static int maxTrace = 10;
/**
* Creates a usage statistics container for a given key.
* @param key the parameter (fully qualified).
*/
public UsageStat(String key) {
this.key = Objects.requireNonNull(key);
}
/**
* Get the maximal length of the stack traces recorded, default is 10.
* @return the maximal length of the stack traces recorded
*/
public static int getMaxTrace(){
return UsageStat.maxTrace;
}
/**
* Sets the maximal length of the stacktraces stored when tracking configuration
* usage. Setting it to a negative createValue, disabled stacktrace logging.
* @param maxTrace the maximal recorded stack length.
*/
public static void setMaxTrace(int maxTrace){
UsageStat.maxTrace =maxTrace;
}
/**
* Get the target key of this instance.
*
* @return the section, never null.
*/
public String getKey() {
return key;
}
/**
* Clears all collected usage metrics for this key.
*/
public void clearMetrics(){
this.accessDetails.clear();
}
/**
* Get the detail number of access points recorded.
*
* @return the detail numer of access points, or null.
*/
public int getReferenceCount() {
return accessDetails.size();
}
/**
* Get the overall number of accesses, hereby summing up the access details tracked.
*
* @return the overall number of accesses.
*/
public int getUsageCount() {
int count = 0;
for(AccessStats ref: accessDetails.values()){
count += ref.getAccessCount();
}
return count;
}
/**
* Access access details for a given class.
* @param type class to getValue usage access stats for, not null.
* @return the usage ref, if present, or null.
*/
public Collection<AccessStats> getAccessDetails(Class type){
return getAccessDetails(type.getName() +"\\..*");
}
/**
* Access access details for a given package.
* @param pack package to getValue usage access stats for, not null.
* @return the usage ref, if present, or null.
*/
public Collection<AccessStats> getAccessDetails(Package pack){
return getAccessDetails(pack.getName() +"\\..*");
}
/**
* Find usages of this key for the given expression (regex). Hereby the expression is
* matched with the tracked reference identifier, which has the form
* {@code f.q.n.ClassName#methodName(line: 123)}.
* @param lookupExpression the target lookup expression, not null.
* @return the matching access statistics, not null.
*/
public Collection<AccessStats> getAccessDetails(String lookupExpression){
List<AccessStats> result = new ArrayList<>();
for(AccessStats ref:this.accessDetails.values()){
if(ref.getAccessPoint().matches(lookupExpression)){
result.add(ref);
}
}
return result;
}
@Override
public String toString() {
return "Usage Stats{\n key=" + key + '\n'+
" usageCount " + getUsageCount() + '\n' +
"}";
}
/**
* Get the access details (stacktrace etc) for this reference.
* @return return the access details, not null.
*/
public Collection<AccessStats> getAccessDetails(){
return Collections.unmodifiableCollection(accessDetails.values());
}
/**
* Evaluates the current access point from the current stacktrace and adds an according
* usage reference createObject (or updates any existing one) for the given key. The
* stacktrace is shortened to a maximal getNumChilds of 20 items.
* @param value the createValue returned, not null.
*/
public void trackUsage(PropertyValue value){
trackUsage(value, maxTrace);
}
/**
* Evaluates the current access point from the current stacktrace and adds an according
* usage reference createObject (or updates any existing one) for the given key.
* @param value the createValue returned, not null.
* @param maxTraceLength the maximal length of the stored stacktrace.
*/
public void trackUsage(PropertyValue value, int maxTraceLength){
String accessPoint = null;
if(maxTraceLength>0) {
Exception e = new Exception();
List<String> trace = new ArrayList<>();
stack:
for (StackTraceElement ste : e.getStackTrace()) {
for (String ignored : ConfigUsage.getInstance().getIgnoredPackages()) {
if (ste.getClassName().startsWith(ignored)) {
continue stack;
}
}
String ref = ste.getClassName() + '#' + ste.getMethodName() + "(line:" + ste.getLineNumber() + ')';
trace.add(ref);
if (accessPoint == null) {
accessPoint = ref;
}
if (trace.size() >= maxTraceLength) {
break;
}
}
if (accessPoint == null) {
// all ignored, take first one, with different package
accessPoint = "<unknown/filtered/internal>";
}
AccessStats details = getAccessDetails(accessPoint, trace.toArray(new String[trace.size()]));
details.trackAccess(value);
}else{
accessPoint = "<disabled>";
AccessStats details = getAccessDetails(accessPoint, EMPTY_TRACE);
details.trackAccess(value);
}
}
private AccessStats getAccessDetails(String accessPoint, String[] trace) {
AccessStats details = accessDetails.get(accessPoint);
if(details==null){
details = new AccessStats(key, accessPoint, trace);
accessDetails.put(accessPoint, details);
}
return details;
}
/**
* Class modelling the access details tracked per detailed item, e.g. per class in the owning package.
*/
public static final class AccessStats {
private String key;
private AtomicLong accessCount = new AtomicLong();
private long lastAccessTS;
private long firstAccessTS;
private String[] stackTrace;
private String accessPoint;
private Map<Long, PropertyValue> trackedValues;
public AccessStats(String key, String accessPoint, String[] stackTrace){
this.key = Objects.requireNonNull(key);
this.accessPoint = Objects.requireNonNull(accessPoint);
this.stackTrace = stackTrace.clone();
}
public void clearStats(){
lastAccessTS = 0;
firstAccessTS = 0;
accessCount.set(0);
}
public long trackAccess(PropertyValue value){
long count = accessCount.incrementAndGet();
lastAccessTS = System.currentTimeMillis();
if(firstAccessTS==0){
firstAccessTS = lastAccessTS;
}
if(value!=null){
synchronized (this) {
if(trackedValues==null){
trackedValues = new HashMap<>();
}
trackedValues.put(lastAccessTS, value);
}
}
return count;
}
public String getKey(){
return key;
}
public long getAccessCount() {
return accessCount.get();
}
public String getAccessPoint() {
return accessPoint;
}
public long getFirstAccessTS() {
return firstAccessTS;
}
public long getLastAccessTS() {
return lastAccessTS;
}
public String[] getStackTrace() {
return stackTrace.clone();
}
public Map<Long, PropertyValue> getTrackedValues(){
synchronized (this) {
if (trackedValues == null) {
return Collections.emptyMap();
} else {
return new HashMap<>(trackedValues);
}
}
}
@Override
public String toString() {
return "AccessStats{" +
"key=" + key +
", accessCount=" + accessCount +
", lastAccessTS=" + lastAccessTS +
", firstAccessTS=" + firstAccessTS +
", accessPoint='" + accessPoint + '\'' +
", trackedValues=" + trackedValues +
", stackTrace=" + Arrays.toString(stackTrace) +
'}';
}
}
}