Skip to content

Commit cc52583

Browse files
authored
Merge pull request #2490 from newrelic/disable-sql-regex
Adds a new config option under the transaction_tracer stanza to disable the execution of the call and exec SQL parser regular expressions.
2 parents 81243c7 + 276a072 commit cc52583

File tree

6 files changed

+98
-2
lines changed

6 files changed

+98
-2
lines changed

newrelic-agent/src/main/java/com/newrelic/agent/config/TransactionTracerConfig.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,16 @@ public interface TransactionTracerConfig {
2424
*/
2525
String getRecordSql();
2626

27+
/**
28+
* For large SQL statements, executing the regular expressions that attempt to parse exec and
29+
* call statements can take a significant amount of time. Setting this to true will disable the
30+
* execution of these complex regular expressions.
31+
* Default is false.
32+
*
33+
* @return true if the exec and call regular expression execution is disabled
34+
*/
35+
boolean isExecCallSqlRegexDisabled();
36+
2737
/**
2838
* The set of modules that are allowed to send up obfuscated slow query information when high_security
2939
* mode is enabled. If high_security mode is disabled this setting is ignored.

newrelic-agent/src/main/java/com/newrelic/agent/config/TransactionTracerConfigImpl.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ public final class TransactionTracerConfigImpl extends BaseConfig implements Tra
4646
public static final String TOKEN_LIMIT = "token_limit";
4747
public static final String TOP_N = "top_n";
4848
public static final String TRANSACTION_THRESHOLD = "transaction_threshold";
49+
public static final String EXEC_CALL_SQL_REGEX_DISABLED = "exec_call_sql_regex_disabled";
50+
4951
public static final boolean DEFAULT_COLLECT_TRACES = false;
5052
public static final boolean DEFAULT_ENABLED = true;
5153
public static final boolean DEFAULT_EXPLAIN_ENABLED = true;
@@ -61,6 +63,7 @@ public final class TransactionTracerConfigImpl extends BaseConfig implements Tra
6163
public static final String DEFAULT_TRANSACTION_THRESHOLD = APDEX_F;
6264
public static final int DEFAULT_TOKEN_LIMIT = 3000;
6365
public static final int DEFAULT_TOP_N = 20;
66+
public static final boolean DEFAULT_EXEC_CALL_SQL_REGEX_DISABLED = false;
6467
public static final int APDEX_F_MULTIPLE = 4;
6568
public static final String SYSTEM_PROPERTY_ROOT = "newrelic.config.transaction_tracer.";
6669
public static final String CATEGORY_REQUEST_SYSTEM_PROPERTY_ROOT = "newrelic.config.transaction_tracer.category." + REQUEST_CATEGORY_NAME + ".";
@@ -84,6 +87,7 @@ public final class TransactionTracerConfigImpl extends BaseConfig implements Tra
8487
private final int maxExplainPlans;
8588
private final int maxTokens;
8689
private final int topN;
90+
private final boolean isExecCallSqlRegexDisabled;
8791
protected final String inheritedFromSystemPropertyRoot;
8892

8993
private TransactionTracerConfigImpl(String systemPropertyRoot, String inheritedFromSystemPropertyRoot,
@@ -109,6 +113,7 @@ private TransactionTracerConfigImpl(String systemPropertyRoot, String inheritedF
109113
maxExplainPlans = getIntProperty(MAX_EXPLAIN_PLANS, DEFAULT_MAX_EXPLAIN_PLANS);
110114
maxTokens = getIntProperty(TOKEN_LIMIT, DEFAULT_TOKEN_LIMIT);
111115
topN = getIntProperty(TOP_N, DEFAULT_TOP_N);
116+
isExecCallSqlRegexDisabled = getProperty(EXEC_CALL_SQL_REGEX_DISABLED, DEFAULT_EXEC_CALL_SQL_REGEX_DISABLED);
112117
}
113118

114119
private boolean initEnabled() {
@@ -241,6 +246,11 @@ protected Object getPropertyFromSystemEnvironment(String name, Object defaultVal
241246
return inheritedKey == null ? null : parseValue(SystemPropertyFactory.getSystemPropertyProvider().getEnvironmentVariable(inheritedKey));
242247
}
243248

249+
@Override
250+
public boolean isExecCallSqlRegexDisabled() {
251+
return isExecCallSqlRegexDisabled;
252+
}
253+
244254
@Override
245255
public double getExplainThresholdInMillis() {
246256
return explainThreshold;

newrelic-agent/src/main/java/com/newrelic/agent/database/DefaultDatabaseStatementParser.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ public ParsedDatabaseStatement getParsedDatabaseStatement(
9393
return new ParsedDatabaseStatement(tableName.toLowerCase(), SELECT_OPERATION, true);
9494
}
9595
}
96-
} catch (Exception e) {
96+
} catch (Exception ignored) {
9797
}
9898
}
9999
return parseStatement(statement);

newrelic-agent/src/main/java/com/newrelic/agent/database/DefaultStatementFactory.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,16 @@
88
package com.newrelic.agent.database;
99

1010
import com.newrelic.agent.Agent;
11+
import com.newrelic.agent.config.ConfigService;
12+
import com.newrelic.agent.config.TransactionTracerConfig;
13+
import com.newrelic.agent.service.ServiceFactory;
1114
import com.newrelic.agent.util.Strings;
1215
import org.apache.commons.lang3.StringUtils;
1316

1417
import java.text.MessageFormat;
18+
import java.util.Arrays;
19+
import java.util.HashSet;
20+
import java.util.Set;
1521
import java.util.logging.Level;
1622
import java.util.regex.Matcher;
1723
import java.util.regex.Pattern;
@@ -21,19 +27,24 @@ class DefaultStatementFactory implements StatementFactory {
2127
private final DefaultStatementFactory backupPattern;
2228
protected final String key;
2329
private final boolean generateMetric;
30+
private final boolean isExecCallSqlRegexDisabled;
31+
32+
private final Set<String> DISABLEABLE_REGEX = new HashSet<>(Arrays.asList("call", "exec"));
2433

2534
public DefaultStatementFactory(String key, Pattern pattern, boolean generateMetric) {
2635
this.key = key;
2736
this.pattern = pattern;
2837
this.generateMetric = generateMetric;
2938
this.backupPattern = null;
39+
isExecCallSqlRegexDisabled = getExecCallSqlRegexDisabled();
3040
}
3141

3242
public DefaultStatementFactory(String key, Pattern pattern, boolean generateMetric, Pattern backupPattern) {
3343
this.key = key;
3444
this.pattern = pattern;
3545
this.generateMetric = generateMetric;
3646
this.backupPattern = new DefaultStatementFactory(key, backupPattern, generateMetric);
47+
isExecCallSqlRegexDisabled = getExecCallSqlRegexDisabled();
3748
}
3849

3950
protected boolean isMetricGenerator() {
@@ -42,7 +53,10 @@ protected boolean isMetricGenerator() {
4253

4354
@Override
4455
public ParsedDatabaseStatement parseStatement(String statement) {
45-
// Optimization to prevent running complex regex when we don't need to
56+
// Optimizations to prevent running complex regex when we don't need to
57+
if (isExecCallSqlRegexDisabled && DISABLEABLE_REGEX.contains(getOperation())) {
58+
return null;
59+
}
4660
if (!StringUtils.containsIgnoreCase(statement, key)) {
4761
return null;
4862
}
@@ -86,4 +100,12 @@ ParsedDatabaseStatement createParsedDatabaseStatement(String model) {
86100
public String getOperation() {
87101
return key;
88102
}
103+
104+
private boolean getExecCallSqlRegexDisabled() {
105+
ConfigService configService = ServiceFactory.getConfigService();
106+
TransactionTracerConfig transactionTracerConfig = ServiceFactory.getConfigService()
107+
.getTransactionTracerConfig(configService.getDefaultAgentConfig().getApplicationName());
108+
109+
return transactionTracerConfig.isExecCallSqlRegexDisabled();
110+
}
89111
}

newrelic-agent/src/main/resources/newrelic.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,12 @@ common: &default_settings
225225
# Default is apdex_f.
226226
transaction_threshold: apdex_f
227227

228+
# For large SQL statements, executing the regular expressions that attempt to parse exec and
229+
# call statements can take a significant amount of time. Setting this to true will disable the
230+
# execution of these complex regular expressions.
231+
# Default is false.
232+
exec_call_sql_regex_disabled: false
233+
228234
# When transaction tracer is on, SQL statements can optionally be
229235
# recorded. The recorder has three modes, "off" which sends no
230236
# SQL, "raw" which sends the SQL statement in its original form,

newrelic-agent/src/test/java/com/newrelic/agent/database/DatabaseStatementResponseParserTest.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@
88
package com.newrelic.agent.database;
99

1010
import com.google.common.io.Files;
11+
import com.newrelic.agent.MockConfigService;
1112
import com.newrelic.agent.MockCoreService;
13+
import com.newrelic.agent.MockServiceManager;
1214
import com.newrelic.agent.bridge.datastore.DatastoreVendor;
1315
import com.newrelic.agent.bridge.datastore.UnknownDatabaseVendor;
16+
import com.newrelic.agent.config.AgentConfig;
1417
import com.newrelic.agent.service.ServiceFactory;
1518
import com.newrelic.agent.service.ServiceManager;
1619
import org.junit.AfterClass;
@@ -30,12 +33,23 @@
3033
import static org.junit.Assert.assertNull;
3134
import static org.junit.Assert.assertTrue;
3235

36+
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
37+
import static org.mockito.Mockito.mock;
38+
import static org.mockito.Mockito.when;
39+
3340
public class DatabaseStatementResponseParserTest {
3441
DatabaseStatementParser parser;
42+
private static AgentConfig agentConfig;
3543

3644
@BeforeClass
3745
public static void beforeClass() throws Exception {
3846
MockCoreService.getMockAgentAndBootstrapTheServiceManager();
47+
48+
MockServiceManager sm = new MockServiceManager();
49+
ServiceFactory.setServiceManager(sm);
50+
agentConfig = mock(AgentConfig.class, RETURNS_DEEP_STUBS);
51+
MockConfigService configService = new MockConfigService(agentConfig);
52+
sm.setConfigService(configService);
3953
}
4054

4155
@AfterClass
@@ -49,6 +63,7 @@ public static void afterClass() throws Exception {
4963
@Before
5064
public void before() {
5165
parser = new DefaultDatabaseStatementParser();
66+
when(agentConfig.getTransactionTracerConfig().isExecCallSqlRegexDisabled()).thenReturn(false);
5267
}
5368

5469
@Test
@@ -536,6 +551,39 @@ public void testLargeSqlSlowdown() throws Exception {
536551
assertEquals("jxu7wns.djs_project_test", parsedStatement.getModel());
537552
}
538553

554+
@Test
555+
public void testExecCallRegexDisabled() {
556+
when(agentConfig.getTransactionTracerConfig().isExecCallSqlRegexDisabled()).thenReturn(true);
557+
parser = new DefaultDatabaseStatementParser();
558+
559+
// These should return an unparaseable statement instance
560+
ParsedDatabaseStatement parsedStatement = parseStatement("call dude(?)");
561+
assertEquals("other", parsedStatement.getOperation());
562+
assertNull(parsedStatement.getModel());
563+
564+
parsedStatement = parseStatement("exec dude(?)");
565+
assertEquals("other", parsedStatement.getOperation());
566+
assertNull(parsedStatement.getModel());
567+
568+
// These should parse normally even with the disabled flag on
569+
parsedStatement = parseStatement("Select * from metrics");
570+
assertEquals("select", parsedStatement.getOperation());
571+
assertEquals("metrics", parsedStatement.getModel());
572+
573+
parsedStatement = parseStatement("DROP PROCEDURE IF EXISTS agent_count_all");
574+
assertEquals("drop", parsedStatement.getOperation());
575+
assertEquals("Procedure", parsedStatement.getModel());
576+
577+
parsedStatement = parseStatement("insert into dude(1,2,3)");
578+
assertEquals("insert", parsedStatement.getOperation());
579+
assertEquals("dude", parsedStatement.getModel());
580+
581+
parsedStatement = parseStatement("delete from dude");
582+
assertEquals("delete", parsedStatement.getOperation());
583+
assertEquals("dude", parsedStatement.getModel());
584+
585+
}
586+
539587
private ParsedDatabaseStatement parseStatement(String statement, ResultSetMetaData metaData) {
540588
return parser.getParsedDatabaseStatement(UnknownDatabaseVendor.INSTANCE, statement, metaData);
541589
}

0 commit comments

Comments
 (0)