308 lines
10 KiB
Java
308 lines
10 KiB
Java
/*
|
|
* Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
|
|
*
|
|
* 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 com.hpcloud.mon.domain.model;
|
|
|
|
import com.hpcloud.mon.common.model.metric.MetricDefinition;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Collections;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
|
|
/**
|
|
* This class is used to find any matching MetricDefinitionAndTenantId instances that match a given
|
|
* MetricDefinitionAndTenantId. This class has no way of handling duplicate
|
|
* MetricDefinitionAndTenantIds so it assume some other handles that issue.
|
|
*
|
|
* The actual MetricDefinitionAndTenantId is not kept in the last Map in order to save heap space.
|
|
* It is expected that possibly millions of metrics may be stored in the Matcher and so by only
|
|
* storing the DiminsionPairs instead of the whole MetricDefinitionAndTenantId, a significant amount
|
|
* of heap space will be saved thus reducing swapping. The MetricDefinitionAndTenantId is recreated
|
|
* when returned but since it will be just sent on and then the reference dropped, the object will
|
|
* be quickly and easily garbage collected. Testing shows that this algorithm is faster than keeping
|
|
* the whole MetricDefinitionAndTenantId in the Map.
|
|
*/
|
|
public class MetricDefinitionAndTenantIdMatcher {
|
|
final Map<String, Map<String, Map<DimensionSet, Object>>> byTenantId = new ConcurrentHashMap<>();
|
|
private final static DimensionSet EMPTY_DIMENSION_SET = new DimensionSet(new DimensionPair[0]);
|
|
private final static Object placeHolder = new Object();
|
|
@SuppressWarnings("unchecked")
|
|
private final static List<MetricDefinitionAndTenantId> EMPTY_LIST = Collections.EMPTY_LIST;
|
|
|
|
public void add(MetricDefinitionAndTenantId metricDefinitionAndTenantId) {
|
|
Map<String, Map<DimensionSet, Object>> byMetricName =
|
|
byTenantId.get(metricDefinitionAndTenantId.tenantId);
|
|
if (byMetricName == null) {
|
|
byMetricName = new ConcurrentHashMap<>();
|
|
byTenantId.put(metricDefinitionAndTenantId.tenantId, byMetricName);
|
|
}
|
|
Map<DimensionSet, Object> byDimensionSet =
|
|
byMetricName.get(metricDefinitionAndTenantId.metricDefinition.name);
|
|
if (byDimensionSet == null) {
|
|
byDimensionSet = new ConcurrentHashMap<>();
|
|
byMetricName.put(metricDefinitionAndTenantId.metricDefinition.name, byDimensionSet);
|
|
}
|
|
final DimensionSet dimensionSet =
|
|
createDimensionSet(metricDefinitionAndTenantId.metricDefinition);
|
|
byDimensionSet.put(dimensionSet, placeHolder);
|
|
}
|
|
|
|
private DimensionSet createDimensionSet(MetricDefinition metricDefinition) {
|
|
return new DimensionSet(createPairs(metricDefinition));
|
|
}
|
|
|
|
public boolean remove(MetricDefinitionAndTenantId metricDefinitionAndTenantId) {
|
|
final Map<String, Map<DimensionSet, Object>> byMetricName =
|
|
byTenantId.get(metricDefinitionAndTenantId.tenantId);
|
|
if (byMetricName == null) {
|
|
return false;
|
|
}
|
|
|
|
final Map<DimensionSet, Object> byDimensionSet =
|
|
byMetricName.get(metricDefinitionAndTenantId.metricDefinition.name);
|
|
if (byDimensionSet == null) {
|
|
return false;
|
|
}
|
|
|
|
final DimensionSet dimensionSet =
|
|
createDimensionSet(metricDefinitionAndTenantId.metricDefinition);
|
|
final boolean result = byDimensionSet.remove(dimensionSet) != null;
|
|
if (result) {
|
|
if (byDimensionSet.isEmpty()) {
|
|
byMetricName.remove(metricDefinitionAndTenantId.metricDefinition.name);
|
|
if (byMetricName.isEmpty()) {
|
|
byTenantId.remove(metricDefinitionAndTenantId.tenantId);
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public List<MetricDefinitionAndTenantId> match(final MetricDefinitionAndTenantId toMatch) {
|
|
final Map<String, Map<DimensionSet, Object>> byMetricName = byTenantId.get(toMatch.tenantId);
|
|
if (byMetricName == null) {
|
|
return EMPTY_LIST;
|
|
}
|
|
|
|
final Map<DimensionSet, Object> byDimensionSet =
|
|
byMetricName.get(toMatch.metricDefinition.name);
|
|
if (byDimensionSet == null) {
|
|
return EMPTY_LIST;
|
|
}
|
|
final DimensionSet[] possibleDimensionSets =
|
|
createPossibleDimensionPairs(toMatch.metricDefinition);
|
|
List<MetricDefinitionAndTenantId> matches = null;
|
|
for (final DimensionSet dimensionSet : possibleDimensionSets) {
|
|
if (byDimensionSet.containsKey(dimensionSet)) {
|
|
if (matches == null) {
|
|
matches = new ArrayList<>();
|
|
}
|
|
matches.add(createFromDimensionSet(toMatch, dimensionSet));
|
|
}
|
|
}
|
|
return matches == null ? EMPTY_LIST : matches;
|
|
}
|
|
|
|
private MetricDefinitionAndTenantId createFromDimensionSet(MetricDefinitionAndTenantId toMatch,
|
|
DimensionSet dimensionSet) {
|
|
final Map<String, String> dimensions = new HashMap<>(dimensionSet.pairs.length);
|
|
for (final DimensionPair pair : dimensionSet.pairs) {
|
|
dimensions.put(pair.key, pair.value);
|
|
}
|
|
return new MetricDefinitionAndTenantId(new MetricDefinition(toMatch.metricDefinition.name,
|
|
dimensions), toMatch.tenantId);
|
|
}
|
|
|
|
protected DimensionSet[] createPossibleDimensionPairs(MetricDefinition metricDefinition) {
|
|
final int dimensionSize =
|
|
metricDefinition.dimensions == null ? 0 : metricDefinition.dimensions.size();
|
|
final int size = (int) Math.pow(2, dimensionSize);
|
|
final DimensionSet[] result = new DimensionSet[size];
|
|
int index = 0;
|
|
result[index++] = EMPTY_DIMENSION_SET;
|
|
if (dimensionSize == 0) {
|
|
return result;
|
|
}
|
|
final DimensionPair[] pairs = createPairs(metricDefinition);
|
|
for (int i = 0; i < pairs.length; i++) {
|
|
index = addMore(pairs, i, EMPTY_DIMENSION_SET, result, index);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private int addMore(DimensionPair[] pairs, int start, DimensionSet dimensionSet,
|
|
DimensionSet[] result, int index) {
|
|
final DimensionPair[] newPairs = new DimensionPair[dimensionSet.pairs.length + 1];
|
|
if (dimensionSet.pairs.length > 0) {
|
|
System.arraycopy(dimensionSet.pairs, 0, newPairs, 0, dimensionSet.pairs.length);
|
|
}
|
|
newPairs[dimensionSet.pairs.length] = pairs[start];
|
|
final DimensionSet thisDimensionSet = new DimensionSet(newPairs);
|
|
result[index++] = thisDimensionSet;
|
|
for (int i = start + 1; i < pairs.length; i++) {
|
|
index = addMore(pairs, i, thisDimensionSet, result, index);
|
|
}
|
|
return index;
|
|
}
|
|
|
|
private DimensionPair[] createPairs(MetricDefinition metricDefinition) {
|
|
final int dimensionSize =
|
|
metricDefinition.dimensions == null ? 0 : metricDefinition.dimensions.size();
|
|
final DimensionPair[] pairs = new DimensionPair[dimensionSize];
|
|
if (dimensionSize > 0) { // metricDefinition.dimensions can be null
|
|
int index = 0;
|
|
for (final Map.Entry<String, String> entry : metricDefinition.dimensions.entrySet()) {
|
|
pairs[index++] = new DimensionPair(entry.getKey(), entry.getValue());
|
|
}
|
|
}
|
|
return pairs;
|
|
}
|
|
|
|
public boolean isEmpty() {
|
|
return byTenantId.isEmpty();
|
|
}
|
|
|
|
public void clear() {
|
|
byTenantId.clear();
|
|
}
|
|
|
|
protected static class DimensionSet {
|
|
final DimensionPair[] pairs;
|
|
|
|
public DimensionSet(DimensionPair... pairs) {
|
|
Arrays.sort(pairs);
|
|
this.pairs = pairs;
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
int result = 1;
|
|
final int prime = 31;
|
|
for (DimensionPair pair : pairs) {
|
|
result = result * prime + pair.hashCode();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj) {
|
|
if (this == obj) {
|
|
return true;
|
|
}
|
|
if (obj == null) {
|
|
return false;
|
|
}
|
|
if (getClass() != obj.getClass()) {
|
|
return false;
|
|
}
|
|
final DimensionSet other = (DimensionSet) obj;
|
|
if (this.pairs.length != other.pairs.length) {
|
|
return false;
|
|
}
|
|
for (int i = 0; i < this.pairs.length; i++) {
|
|
if (!this.pairs[i].equals(other.pairs[i])) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
final StringBuilder builder = new StringBuilder(256);
|
|
builder.append("DimensionSet [");
|
|
boolean first = true;
|
|
for (DimensionPair pair : pairs) {
|
|
if (!first) {
|
|
builder.append(", ");
|
|
}
|
|
builder.append(pair.toString());
|
|
first = false;
|
|
}
|
|
builder.append("]");
|
|
|
|
return builder.toString();
|
|
}
|
|
}
|
|
|
|
protected static class DimensionPair implements Comparable<DimensionPair> {
|
|
private String key;
|
|
private String value;
|
|
|
|
public DimensionPair(String key, String value) {
|
|
this.key = key;
|
|
this.value = value;
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
int result = 1;
|
|
final int prime = 31;
|
|
result = prime * result + key.hashCode();
|
|
result = prime * result + ((value == null) ? 0 : value.hashCode());
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj) {
|
|
if (this == obj) {
|
|
return true;
|
|
}
|
|
if (obj == null) {
|
|
return false;
|
|
}
|
|
if (getClass() != obj.getClass()) {
|
|
return false;
|
|
}
|
|
DimensionPair other = (DimensionPair) obj;
|
|
return compareStrings(key, other.key) && compareStrings(value, other.value);
|
|
}
|
|
|
|
private boolean compareStrings(final String s1, final String s2) {
|
|
if (s1 == s2) {
|
|
return true;
|
|
}
|
|
if (s1 == null) {
|
|
return false;
|
|
}
|
|
return s1.equals(s2);
|
|
}
|
|
|
|
@Override
|
|
public int compareTo(DimensionPair o) {
|
|
int c = this.key.compareTo(o.key);
|
|
if (c != 0) {
|
|
return c;
|
|
}
|
|
// Handle possible null values. A actual value is bigger than a null
|
|
if (this.value == null) {
|
|
return o.value == null ? 0 : 1;
|
|
}
|
|
return this.value.compareTo(o.value);
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return String.format("DimensionPair %s=%s", key, value);
|
|
}
|
|
}
|
|
}
|