Improved the efficiency of the matching of MetricDefinitionAndTenantIdMatcher instances to SubAlarms that define a subset of the Dimensions. Added another Map instead of doing a search of a List of MetricDefinitionAndTenantIds. Just need to calculate the possible sets of Dimenstions that could be matched.
This commit is contained in:
parent
87e660eee2
commit
ec0148e242
@ -1,40 +1,60 @@
|
||||
package com.hpcloud.mon.domain.model;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.hpcloud.mon.common.model.metric.MetricDefinition;
|
||||
|
||||
/**
|
||||
* 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, List<MetricDefinitionAndTenantId>>> byTenantId = new ConcurrentHashMap<>();
|
||||
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();
|
||||
|
||||
public void add(MetricDefinitionAndTenantId metricDefinitionAndTenantId) {
|
||||
Map<String, List<MetricDefinitionAndTenantId>> byMetricName = byTenantId.get(metricDefinitionAndTenantId.tenantId);
|
||||
Map<String, Map<DimensionSet, Object>> byMetricName = byTenantId.get(metricDefinitionAndTenantId.tenantId);
|
||||
if (byMetricName == null) {
|
||||
byMetricName = new ConcurrentHashMap<>();
|
||||
byTenantId.put(metricDefinitionAndTenantId.tenantId, byMetricName);
|
||||
}
|
||||
List<MetricDefinitionAndTenantId> defsList = byMetricName.get(metricDefinitionAndTenantId.metricDefinition.name);
|
||||
if (defsList == null) {
|
||||
defsList = new LinkedList<>();
|
||||
byMetricName.put(metricDefinitionAndTenantId.metricDefinition.name, defsList);
|
||||
Map<DimensionSet, Object> byDimensionSet = byMetricName.get(metricDefinitionAndTenantId.metricDefinition.name);
|
||||
if (byDimensionSet == null) {
|
||||
byDimensionSet = new ConcurrentHashMap<>();
|
||||
byMetricName.put(metricDefinitionAndTenantId.metricDefinition.name, byDimensionSet);
|
||||
}
|
||||
defsList.add(metricDefinitionAndTenantId);
|
||||
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) {
|
||||
Map<String, List<MetricDefinitionAndTenantId>> byMetricName = byTenantId.get(metricDefinitionAndTenantId.tenantId);
|
||||
if (byMetricName == null) {
|
||||
final Map<String, Map<DimensionSet, Object>> byMetricName = byTenantId.get(metricDefinitionAndTenantId.tenantId);
|
||||
if (byMetricName == null)
|
||||
return false;
|
||||
}
|
||||
List<MetricDefinitionAndTenantId> defsList = byMetricName.get(metricDefinitionAndTenantId.metricDefinition.name);
|
||||
if (defsList == null) {
|
||||
|
||||
final Map<DimensionSet, Object> byDimensionSet = byMetricName.get(metricDefinitionAndTenantId.metricDefinition.name);
|
||||
if (byDimensionSet == null)
|
||||
return false;
|
||||
}
|
||||
final boolean result = defsList.remove(metricDefinitionAndTenantId);
|
||||
|
||||
final DimensionSet dimensionSet = createDimensionSet(metricDefinitionAndTenantId.metricDefinition);
|
||||
final boolean result = byDimensionSet.remove(dimensionSet) != null;
|
||||
if (result) {
|
||||
if (defsList.isEmpty()) {
|
||||
if (byDimensionSet.isEmpty()) {
|
||||
byMetricName.remove(metricDefinitionAndTenantId.metricDefinition.name);
|
||||
if (byMetricName.isEmpty())
|
||||
byTenantId.remove(metricDefinitionAndTenantId.tenantId);
|
||||
@ -45,29 +65,69 @@ public class MetricDefinitionAndTenantIdMatcher {
|
||||
|
||||
public boolean match(final MetricDefinitionAndTenantId toMatch,
|
||||
final List<MetricDefinitionAndTenantId> matches) {
|
||||
Map<String, List<MetricDefinitionAndTenantId>> byMetricName = byTenantId.get(toMatch.tenantId);
|
||||
final Map<String, Map<DimensionSet, Object>> byMetricName = byTenantId.get(toMatch.tenantId);
|
||||
if (byMetricName == null)
|
||||
return false;
|
||||
List<MetricDefinitionAndTenantId> defsList = byMetricName.get(toMatch.metricDefinition.name);
|
||||
if (defsList == null)
|
||||
|
||||
final Map<DimensionSet, Object> byDimensionSet = byMetricName.get(toMatch.metricDefinition.name);
|
||||
if (byDimensionSet == null)
|
||||
return false;
|
||||
final DimensionSet[] possibleDimensionSets = createPossibleDimensionPairs(toMatch.metricDefinition);
|
||||
matches.clear();
|
||||
for (final MetricDefinitionAndTenantId existing : defsList) {
|
||||
if (toMatch.metricDefinition.dimensions.size() >= existing.metricDefinition.dimensions.size()) {
|
||||
boolean isMatch = true;
|
||||
for (final Entry<String, String> entry : existing.metricDefinition.dimensions.entrySet()) {
|
||||
if (!compareStrings(entry.getValue(), toMatch.metricDefinition.dimensions.get(entry.getKey()))) {
|
||||
isMatch = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isMatch)
|
||||
matches.add(existing);
|
||||
}
|
||||
for (final DimensionSet dimensionSet : possibleDimensionSets) {
|
||||
if (byDimensionSet.containsKey(dimensionSet))
|
||||
matches.add(createFromDimensionSet(toMatch, dimensionSet));
|
||||
}
|
||||
return !matches.isEmpty();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
@ -76,12 +136,107 @@ public class MetricDefinitionAndTenantIdMatcher {
|
||||
byTenantId.clear();
|
||||
}
|
||||
|
||||
private boolean compareStrings(final String s1,
|
||||
final String s2) {
|
||||
if (s1 == s2)
|
||||
return true;
|
||||
if (s1 == null)
|
||||
return false;
|
||||
return s1.equals(s2);
|
||||
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 (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 (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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,8 @@ import org.testng.annotations.BeforeMethod;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import com.hpcloud.mon.common.model.metric.MetricDefinition;
|
||||
import com.hpcloud.mon.domain.model.MetricDefinitionAndTenantIdMatcher.DimensionPair;
|
||||
import com.hpcloud.mon.domain.model.MetricDefinitionAndTenantIdMatcher.DimensionSet;
|
||||
|
||||
@Test
|
||||
public class MetricDefinitionAndTenantIdMatcherTest {
|
||||
@ -73,6 +75,23 @@ public class MetricDefinitionAndTenantIdMatcherTest {
|
||||
assertTrue(matcher.isEmpty());
|
||||
final MetricDefinitionAndTenantId toMatch = new MetricDefinitionAndTenantId(metricDef, tenantId);
|
||||
|
||||
final Map<String, String> nullDimensions = new HashMap<>(dimensions);
|
||||
nullDimensions.put(HOST, null);
|
||||
final MetricDefinitionAndTenantId nullMatch = new MetricDefinitionAndTenantId(
|
||||
new MetricDefinition(CPU_METRIC_NAME, nullDimensions), tenantId);
|
||||
matcher.add(nullMatch);
|
||||
assertTrue(matcher.match(nullMatch, matches));
|
||||
assertEqualsNoOrder(matches.toArray(), new MetricDefinitionAndTenantId[] {
|
||||
nullMatch});
|
||||
|
||||
final Map<String, String> noDimensions = new HashMap<>();
|
||||
final MetricDefinitionAndTenantId noMatch = new MetricDefinitionAndTenantId(
|
||||
new MetricDefinition(CPU_METRIC_NAME, noDimensions), tenantId);
|
||||
matcher.add(noMatch);
|
||||
assertTrue(matcher.match(noMatch, matches));
|
||||
assertEqualsNoOrder(matches.toArray(), new MetricDefinitionAndTenantId[] {
|
||||
noMatch});
|
||||
|
||||
final Map<String, String> hostDimensions = new HashMap<>();
|
||||
hostDimensions.put(HOST, dimensions.get(HOST));
|
||||
final MetricDefinitionAndTenantId hostMatch = new MetricDefinitionAndTenantId(
|
||||
@ -87,17 +106,23 @@ public class MetricDefinitionAndTenantIdMatcherTest {
|
||||
|
||||
assertTrue(matcher.match(toMatch, matches));
|
||||
assertEqualsNoOrder(matches.toArray(), new MetricDefinitionAndTenantId[] {
|
||||
hostMatch, groupMatch});
|
||||
noMatch, hostMatch, groupMatch});
|
||||
matches.clear();
|
||||
|
||||
matcher.add(toMatch);
|
||||
assertTrue(matcher.match(toMatch, matches));
|
||||
assertEqualsNoOrder(matches.toArray(), new MetricDefinitionAndTenantId[] {
|
||||
hostMatch, groupMatch, toMatch});
|
||||
noMatch, hostMatch, groupMatch, toMatch});
|
||||
matches.clear();
|
||||
|
||||
matcher.remove(groupMatch);
|
||||
assertTrue(matcher.match(toMatch, matches));
|
||||
assertEqualsNoOrder(matches.toArray(), new MetricDefinitionAndTenantId[] {
|
||||
noMatch, hostMatch, toMatch});
|
||||
matches.clear();
|
||||
|
||||
matcher.remove(noMatch);
|
||||
assertTrue(matcher.match(toMatch, matches));
|
||||
assertEqualsNoOrder(matches.toArray(), new MetricDefinitionAndTenantId[] {
|
||||
hostMatch, toMatch});
|
||||
matches.clear();
|
||||
@ -124,20 +149,60 @@ public class MetricDefinitionAndTenantIdMatcherTest {
|
||||
matcher.remove(hostMatch);
|
||||
|
||||
matcher.remove(loadMetric);
|
||||
assertTrue(matcher.isEmpty());
|
||||
|
||||
// I don't really expect nulls values for the dimensions, but make sure it doesn't throw an exception
|
||||
final Map<String, String> nullDimensions = new HashMap<>(dimensions);
|
||||
nullDimensions.put(HOST, null);
|
||||
final MetricDefinitionAndTenantId nullMatch = new MetricDefinitionAndTenantId(
|
||||
new MetricDefinition(CPU_METRIC_NAME, nullDimensions), tenantId);
|
||||
matcher.add(nullMatch);
|
||||
assertTrue(matcher.match(nullMatch, matches));
|
||||
assertEqualsNoOrder(matches.toArray(), new MetricDefinitionAndTenantId[] {
|
||||
nullMatch});
|
||||
assertFalse(matcher.match(toMatch, matches));
|
||||
|
||||
matcher.remove(nullMatch);
|
||||
assertTrue(matcher.isEmpty());
|
||||
assertFalse(matcher.match(toMatch, matches));
|
||||
}
|
||||
|
||||
public void shouldCreatePossiblePairs() {
|
||||
final Map<String, String> dimensions = new HashMap<>();
|
||||
DimensionSet[] actual = matcher.createPossibleDimensionPairs(new MetricDefinition(CPU_METRIC_NAME, dimensions));
|
||||
DimensionSet[] expected = { new DimensionSet() };
|
||||
assertEqualsNoOrder(actual, expected);
|
||||
|
||||
dimensions.put("1", "a");
|
||||
actual = matcher.createPossibleDimensionPairs(new MetricDefinition(CPU_METRIC_NAME, dimensions));
|
||||
expected = new DimensionSet[] { new DimensionSet(), new DimensionSet(new DimensionPair("1", "a")) };
|
||||
assertEqualsNoOrder(actual, expected);
|
||||
|
||||
dimensions.put("2", "b");
|
||||
actual = matcher.createPossibleDimensionPairs(new MetricDefinition(CPU_METRIC_NAME, dimensions));
|
||||
expected = new DimensionSet[] { new DimensionSet(), new DimensionSet(new DimensionPair("1", "a")),
|
||||
new DimensionSet(new DimensionPair("2", "b")),
|
||||
new DimensionSet(new DimensionPair("1", "a"), new DimensionPair("2", "b")) };
|
||||
assertEqualsNoOrder(actual, expected);
|
||||
|
||||
dimensions.put("3", "c");
|
||||
actual = matcher.createPossibleDimensionPairs(new MetricDefinition(CPU_METRIC_NAME, dimensions));
|
||||
expected = new DimensionSet[] { new DimensionSet(),
|
||||
new DimensionSet(new DimensionPair("1", "a")),
|
||||
new DimensionSet(new DimensionPair("2", "b")),
|
||||
new DimensionSet(new DimensionPair("3", "c")),
|
||||
new DimensionSet(new DimensionPair("1", "a"), new DimensionPair("2", "b")),
|
||||
new DimensionSet(new DimensionPair("1", "a"), new DimensionPair("3", "c")),
|
||||
new DimensionSet(new DimensionPair("2", "b"), new DimensionPair("3", "c")),
|
||||
new DimensionSet(new DimensionPair("1", "a"), new DimensionPair("2", "b"), new DimensionPair("3", "c"))
|
||||
};
|
||||
|
||||
dimensions.put("4", "d");
|
||||
actual = matcher.createPossibleDimensionPairs(new MetricDefinition(CPU_METRIC_NAME, dimensions));
|
||||
expected = new DimensionSet[] { new DimensionSet(),
|
||||
new DimensionSet(new DimensionPair("1", "a")),
|
||||
new DimensionSet(new DimensionPair("2", "b")),
|
||||
new DimensionSet(new DimensionPair("3", "c")),
|
||||
new DimensionSet(new DimensionPair("4", "d")),
|
||||
new DimensionSet(new DimensionPair("1", "a"), new DimensionPair("2", "b")),
|
||||
new DimensionSet(new DimensionPair("1", "a"), new DimensionPair("3", "c")),
|
||||
new DimensionSet(new DimensionPair("1", "a"), new DimensionPair("4", "d")),
|
||||
new DimensionSet(new DimensionPair("2", "b"), new DimensionPair("3", "c")),
|
||||
new DimensionSet(new DimensionPair("2", "b"), new DimensionPair("4", "d")),
|
||||
new DimensionSet(new DimensionPair("3", "c"), new DimensionPair("4", "d")),
|
||||
new DimensionSet(new DimensionPair("1", "a"), new DimensionPair("2", "b"), new DimensionPair("3", "c")),
|
||||
new DimensionSet(new DimensionPair("1", "a"), new DimensionPair("2", "b"), new DimensionPair("4", "d")),
|
||||
new DimensionSet(new DimensionPair("1", "a"), new DimensionPair("3", "c"), new DimensionPair("4", "d")),
|
||||
new DimensionSet(new DimensionPair("2", "b"), new DimensionPair("3", "c"), new DimensionPair("4", "d")),
|
||||
new DimensionSet(new DimensionPair("1", "a"), new DimensionPair("2", "b"), new DimensionPair("3", "c"), new DimensionPair("4", "d"))
|
||||
};
|
||||
assertEqualsNoOrder(actual, expected);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user