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:
Craig Bryant 2014-04-14 08:00:43 -06:00
parent 87e660eee2
commit ec0148e242
2 changed files with 274 additions and 54 deletions

View File

@ -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);
}
}
}

View File

@ -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);
}
}