Skip to content

Commit 9ee8a44

Browse files
committed
(PE-38408) Remove expensive Regexes from puppet profiler Java impl
We have triggered a nasty performance regression in the JVM[1] with some of our Regex usage in the MetricsPuppetProfiler (particularly the PuppetDB Patterns). Regardless of the performance regression, our Regex usage is unnecessary and unneedlessly expensive (I believe it was taking up ~3% of samples from JFR in an unrelated support escalation). This patch moves from iterating over all metrics and doing regex matches to pull out only those that match the desired pattern when metrics are requested, to caching references to desired metrics in maps as they are recorded. The keys of these maps should be the same as the match group from the previous patterns.
1 parent 0475b27 commit 9ee8a44

File tree

1 file changed

+99
-26
lines changed

1 file changed

+99
-26
lines changed

src/java/com/puppetlabs/puppetserver/MetricsPuppetProfiler.java

Lines changed: 99 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import org.apache.commons.lang.StringUtils;
66

77
import java.util.ArrayList;
8+
import java.util.Arrays;
89
import java.util.HashMap;
910
import java.util.List;
1011
import java.util.Map;
@@ -21,17 +22,21 @@ public class MetricsPuppetProfiler implements PuppetProfiler {
2122
private final MetricRegistry registry;
2223
private final Set<String> metric_ids;
2324

24-
private static final Pattern FUNCTION_PATTERN = Pattern.compile(".*\\.functions\\.([\\w\\d_]+)$");
25-
private static final Pattern RESOURCE_PATTERN = Pattern.compile(".*\\.compiler\\.evaluate_resource\\.([\\w\\d_]+\\[([\\w\\d_]+::)*[\\w\\d_]+\\])$");
26-
private static final Pattern CATALOG_PATTERN = Pattern.compile(".*\\.compiler\\.(static_compile_postprocessing|static_compile|compile|find_node)$");
27-
private static final Pattern INLINING_PATTERN = Pattern.compile(".*\\.compiler\\.static_compile_inlining\\.(.*)$");
28-
private static final Pattern PUPPETDB_PATTERN = Pattern.compile(".*\\.puppetdb\\.(resource\\.search|facts\\.encode|command\\.submit\\.replace facts|catalog\\.munge|command\\.submit\\.replace catalog|report\\.convert_to_wire_format_hash|command\\.submit\\.store report|query)$");
29-
25+
private final Map<String, Timer> function_timers;
26+
private final Map<String, Timer> resource_timers;
27+
private final Map<String, Timer> catalog_timers;
28+
private final Map<String, Timer> inlining_timers;
29+
private final Map<String, Timer> puppetdb_timers;
3030

3131
public MetricsPuppetProfiler(String hostname, MetricRegistry registry) {
3232
this.hostname = hostname;
3333
this.registry = registry;
3434
this.metric_ids = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
35+
this.function_timers = new ConcurrentHashMap<String, Timer>();
36+
this.resource_timers = new ConcurrentHashMap<String, Timer>();
37+
this.catalog_timers = new ConcurrentHashMap<String, Timer>();
38+
this.inlining_timers = new ConcurrentHashMap<String, Timer>();
39+
this.puppetdb_timers = new ConcurrentHashMap<String, Timer>();
3540
}
3641

3742
@Override
@@ -43,9 +48,12 @@ public Object start(String message, String[] metric_id) {
4348
public void finish(Object context, String message, String[] metric_id) {
4449
if (shouldTime(metric_id)) {
4550
Long elapsed = System.currentTimeMillis() - (Long)context;
46-
for (Timer t : getTimers(metric_id)) {
51+
Map<String, Timer> metricsByID = getOrCreateTimersByIDs(metric_id);
52+
for (Timer t : metricsByID.values()) {
4753
t.update(elapsed, TimeUnit.MILLISECONDS);
4854
}
55+
56+
updateMetricsTrackers(metric_id, metricsByID);
4957
}
5058
}
5159

@@ -54,29 +62,105 @@ public Set<String> getAllMetricIds() {
5462
}
5563

5664
public Map<String, Timer> getFunctionTimers() {
57-
return getTimers(FUNCTION_PATTERN);
65+
return this.function_timers;
5866
}
5967

6068
public Map<String, Timer> getResourceTimers() {
61-
return getTimers(RESOURCE_PATTERN);
69+
return this.resource_timers;
6270
}
6371

6472
public Map<String, Timer> getCatalogTimers() {
65-
return getTimers(CATALOG_PATTERN);
73+
return this.catalog_timers;
6674
}
6775

6876
public Map<String, Timer> getInliningTimers() {
69-
return getTimers(INLINING_PATTERN);
77+
return this.inlining_timers;
7078
}
7179

7280
public Map<String, Timer> getPuppetDBTimers() {
73-
return getTimers(PUPPETDB_PATTERN);
81+
return this.puppetdb_timers;
7482
}
7583

7684
@Override
7785
public void shutdown() {
7886
}
7987

88+
private List<String> sliceOfArrayToList(String[] idSegments, int lengthOfID) {
89+
// Callers expect a mutable List returned, but Arrays.asList() returns a
90+
// fix length array, which is why we have to create a List and then add to it.
91+
List<String> idList = new ArrayList<String>();
92+
idList.addAll(Arrays.asList(Arrays.copyOf(idSegments, lengthOfID)));
93+
94+
return idList;
95+
}
96+
97+
private String safeGet(String[] collection, int i) {
98+
try {
99+
return collection[i];
100+
} catch (IndexOutOfBoundsException _ex) {
101+
return "";
102+
}
103+
}
104+
105+
private void updateMetricsTrackers(String[] metricId, Map<String, Timer> metricsByID) {
106+
String firstElement = safeGet(metricId, 0);
107+
String secondElement = safeGet(metricId, 1);
108+
109+
if ("functions".equals(firstElement)) {
110+
Timer metric = metricsByID.get(getMetricName(sliceOfArrayToList(metricId, 2)));
111+
this.function_timers.put(secondElement, metric);
112+
113+
} else if ("compiler".equals(firstElement)) {
114+
String thirdElemet = safeGet(metricId, 2);
115+
116+
if ("evaluate_resource".equals(secondElement)) {
117+
Timer metric = metricsByID.get(getMetricName(sliceOfArrayToList(metricId, 3)));
118+
this.resource_timers.put(thirdElemet, metric);
119+
120+
} else if ("static_compile_inlining".equals(secondElement)) {
121+
Timer metric = metricsByID.get(getMetricName(sliceOfArrayToList(metricId, 3)));
122+
this.inlining_timers.put(thirdElemet, metric);
123+
124+
} else {
125+
Timer metric = metricsByID.get(getMetricName(sliceOfArrayToList(metricId, 2)));
126+
this.catalog_timers.put(secondElement, metric);
127+
}
128+
129+
} else if ("puppetdb".equals(firstElement)) {
130+
if ("query".equals(secondElement)) {
131+
Timer metric = metricsByID.get(getMetricName(sliceOfArrayToList(metricId, 2)));
132+
this.puppetdb_timers.put(secondElement, metric);
133+
134+
} else {
135+
String thirdElemet = safeGet(metricId, 2);
136+
137+
if (
138+
("resource".equals(secondElement) && "search".equals(thirdElemet)) ||
139+
("facts".equals(secondElement) && "encode".equals(thirdElemet)) ||
140+
("catalog".equals(secondElement) && "munge".equals(thirdElemet)) ||
141+
("report".equals(secondElement) && "convert_to_wire_format_hash".equals(thirdElemet))
142+
) {
143+
String key = String.join(".", secondElement, thirdElemet);
144+
Timer metric = metricsByID.get(getMetricName(sliceOfArrayToList(metricId, 3)));
145+
this.puppetdb_timers.put(key, metric);
146+
147+
} else if ("command".equals(secondElement) && "submit".equals(thirdElemet)) {
148+
String fourthElement = safeGet(metricId, 3);
149+
150+
if (
151+
"store report".equals(fourthElement) ||
152+
"replace facts".equals(fourthElement) ||
153+
"replace catalog".equals(fourthElement)
154+
) {
155+
String key = String.join(".", secondElement, thirdElemet, fourthElement);
156+
Timer metric = metricsByID.get(getMetricName(sliceOfArrayToList(metricId, 4)));
157+
this.puppetdb_timers.put(key, metric);
158+
}
159+
}
160+
}
161+
}
162+
}
163+
80164
private boolean shouldTime(String[] metric_id) {
81165
if (metric_id == null) {
82166
return false;
@@ -90,8 +174,8 @@ private boolean shouldTime(String[] metric_id) {
90174
return true;
91175
}
92176

93-
private List<Timer> getTimers(String[] metric_id) {
94-
List<Timer> timers = new ArrayList<Timer>();
177+
private Map<String, Timer> getOrCreateTimersByIDs(String[] metric_id) {
178+
Map<String, Timer> timers = new HashMap<String, Timer>();
95179
// If this is turns out to be a performance hit, we could cache these in a
96180
// map or something.
97181
for (int i = 0; i < metric_id.length; i++) {
@@ -101,7 +185,7 @@ private List<Timer> getTimers(String[] metric_id) {
101185
}
102186
String metric_name = getMetricName(current_id);
103187
registerMetricName(metric_name);
104-
timers.add(registry.timer(metric_name));
188+
timers.put(metric_name, registry.timer(metric_name));
105189
}
106190
return timers;
107191
}
@@ -114,15 +198,4 @@ private String getMetricName(List<String> metric_id) {
114198
private void registerMetricName(String metric_name) {
115199
this.metric_ids.add(metric_name);
116200
}
117-
118-
private Map<String, Timer> getTimers(Pattern pattern) {
119-
Map<String, Timer> rv = new HashMap<>();
120-
for (String metric_id : this.metric_ids) {
121-
Matcher matcher = pattern.matcher(metric_id);
122-
if (matcher.matches()) {
123-
rv.put(matcher.group(1), registry.timer(metric_id));
124-
}
125-
}
126-
return rv;
127-
}
128201
}

0 commit comments

Comments
 (0)