-
Notifications
You must be signed in to change notification settings - Fork 45
Nocode prototype #2184
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Nocode prototype #2184
Changes from 57 commits
Commits
Show all changes
65 commits
Select commit
Hold shift + click to select a range
512c318
Initial veeeeery messing prototype of the nocode instrumentation concept
johnbley 44bf2ae
Add and fix integral literal tests, misc other FIXME cleanups
johnbley 3af909e
Cleanup initialization logic and immutability around lookup structure
johnbley ff7dcbd
Tests, FIXMEs, some debug printouts removed
johnbley 00fe0ce
Make yml file actually configurable
johnbley cbedaa6
FIXME cleanup
johnbley a4007e9
Factor out interfaces desired by the instrumentation apparatus
johnbley 862f833
Doc a desired test
johnbley 90ed8bb
Make thrown exception capture work, clean up some FIXMEs, doc a few more
johnbley e08ae11
Start cleaning up parsing logic
johnbley 29f5744
Clean up parsing of . separator a bit
johnbley 39d4159
Clean up imports and package names
johnbley 3bbe1f1
Use file i/o directly rather than slurping in a string for yaml parsing
johnbley a641880
Add spanKind support
johnbley c3d44c2
Clean up debug printouts
johnbley e055398
Clean up logging/error handling
johnbley 48c5170
Doc JSPS a bit
johnbley 6eb81e9
Use snakeyaml directly instead of through reflection
johnbley b8c57e3
Fallback to internal spanKind
johnbley 16fc9b1
Build script cleanup
johnbley 757e16e
Use superType instead of exact match to filter class names, just like…
johnbley 00ca67c
Add tests for extractors
johnbley bbbc400
End-to-end integation test, some bootstrap classpath issues with the …
johnbley fc25038
Replace extractor tests with integration ones
johnbley 0a8bd67
Rename instrumentation test
johnbley 5429d18
Inline code to instrument inside the test
johnbley e908851
Organize imports
johnbley bbcab53
Fixed the open-access reflection issue for many cases
johnbley 1ec7221
Merge branch 'signalfx:main' into nocode_prototype
johnbley bd4c6fd
spotlessApply
johnbley f52492c
Merge branch 'signalfx:main' into nocode_prototype
johnbley 8c31160
Update bootstrap/src/main/java/com/splunk/opentelemetry/javaagent/boo…
johnbley 5bf0a94
Update bootstrap/src/main/java/com/splunk/opentelemetry/javaagent/boo…
johnbley fbebfe3
Simplify global rules data structure, naming, and logic per feedback
johnbley a4a4ea7
Naming, docs feedback
johnbley 58d5307
Update instrumentation/nocode/src/main/java/com/splunk/opentelemetry/…
johnbley 52e691b
Update instrumentation/nocode/src/main/java/com/splunk/opentelemetry/…
johnbley 2442690
Update instrumentation/nocode/src/main/java/com/splunk/opentelemetry/…
johnbley d354adb
Update instrumentation/nocode/src/main/java/com/splunk/opentelemetry/…
johnbley 8e29611
Update instrumentation/nocode/src/main/java/com/splunk/opentelemetry/…
johnbley 6e5cce2
Update instrumentation/nocode/src/main/java/com/splunk/opentelemetry/…
johnbley 105f29a
Reduce use of public in test code
johnbley 07ff329
Update instrumentation/nocode/src/main/java/com/splunk/opentelemetry/…
johnbley 36e0258
Update instrumentation/nocode/src/main/java/com/splunk/opentelemetry/…
johnbley 539c71b
Update instrumentation/nocode/src/main/java/com/splunk/opentelemetry/…
johnbley 629e506
Update instrumentation/nocode/src/main/java/com/splunk/opentelemetry/…
johnbley 0dded04
Make PR feedback compile
johnbley 4faae8b
Use more kinds of whitespace in test
johnbley cb8f827
Clean up types, names, local variables for yaml parsing.
johnbley 7641c24
Rename field
johnbley 282fa2f
Comment a bit of danger for any future code editors
johnbley a2b789d
Clean up null checking logic
johnbley 8468dd3
Parameterize some of the jsps tests
johnbley 2cb3ada
Most of spotlessApply
johnbley 2dc2675
Clean up javadoc style/use html rather than markdown for JSPS comment
johnbley 7f8569b
spotlessApply
johnbley 014158e
Parameterize basic jsps behavior tests
johnbley f22f7bb
Feedback nits
johnbley ea9082a
spotlessApply
johnbley 58c71e2
Let muzzle plugin find helper classes
laurit 0a0e20f
Merge pull request #1 from laurit/muzzle
johnbley 43e5449
Logic and naming fixups from PR feedback
johnbley 957fb3b
spotlessApply
johnbley 575b0b8
Add comment about instrumentation order
johnbley 4857123
Non-initialized singleton, better toString
johnbley File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
68 changes: 68 additions & 0 deletions
68
bootstrap/src/main/java/com/splunk/opentelemetry/javaagent/bootstrap/nocode/NocodeRules.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| /* | ||
| * Copyright Splunk Inc. | ||
| * | ||
| * 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.splunk.opentelemetry.javaagent.bootstrap.nocode; | ||
|
|
||
| import java.util.Collections; | ||
| import java.util.HashMap; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.concurrent.ConcurrentHashMap; | ||
|
|
||
| public final class NocodeRules { | ||
|
|
||
| public static final class Rule { | ||
| public final String className; | ||
| public final String methodName; | ||
| public final String spanName; // may be null - use default of "class.method" | ||
| public final String spanKind; // matches the SpanKind enum, null means default to INTERNAL | ||
| public final Map<String, String> attributes; // key name to jsps | ||
|
|
||
| public Rule( | ||
| String className, | ||
| String methodName, | ||
| String spanName, | ||
| String spanKind, | ||
| Map<String, String> attributes) { | ||
| this.className = className; | ||
| this.methodName = methodName; | ||
| this.spanName = spanName; | ||
| this.spanKind = spanKind; | ||
| this.attributes = Collections.unmodifiableMap(new HashMap<>(attributes)); | ||
| } | ||
|
|
||
| public String toString() { | ||
| return className + "." + methodName + ":spanName=" + spanName + ",attrs=" + attributes; | ||
johnbley marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| } | ||
|
|
||
| // Using className.methodName as the key | ||
| private static final ConcurrentHashMap<String, Rule> Name2Rule = new ConcurrentHashMap<>(); | ||
johnbley marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| public static void setGlobalRules(List<Rule> rules) { | ||
| for (Rule r : rules) { | ||
| Name2Rule.put(r.className + "." + r.methodName, r); | ||
| } | ||
| } | ||
|
|
||
| public static Iterable<Rule> getGlobalRules() { | ||
| return Name2Rule.values(); | ||
| } | ||
|
|
||
| public static Rule find(String className, String methodName) { | ||
| return Name2Rule.get(className + "." + methodName); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| plugins { | ||
| id("splunk.instrumentation-conventions") | ||
| } | ||
|
|
||
| dependencies { | ||
| compileOnly("io.opentelemetry.javaagent:opentelemetry-javaagent-tooling") | ||
| compileOnly("org.snakeyaml:snakeyaml-engine:2.8") | ||
| } | ||
|
|
||
| tasks.withType<Test>().configureEach { | ||
| environment("SPLUNK_OTEL_INSTRUMENTATION_NOCODE_YML_FILE", "./src/test/config/nocode.yml") | ||
| } |
173 changes: 173 additions & 0 deletions
173
...umentation/nocode/src/main/java/com/splunk/opentelemetry/instrumentation/nocode/JSPS.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,173 @@ | ||
| /* | ||
| * Copyright Splunk Inc. | ||
| * | ||
| * 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.splunk.opentelemetry.instrumentation.nocode; | ||
|
|
||
| import java.lang.reflect.Method; | ||
| import java.lang.reflect.Modifier; | ||
| import java.util.logging.Logger; | ||
|
|
||
| /** | ||
| * JSPS stands for Java-like String-Producing Statement. A JSPS is essentially a single call in Java | ||
| * (as though it ends with a semicolon), with some limitations. Its purpose is to allow pieces of | ||
| * nocode instrumentation (attributes, span name) to be derived from the instrumentated context. | ||
| * | ||
| * <p>As some illustrative examples: | ||
| * | ||
| * <pre> | ||
| * this.getHeaders().get("X-Custom-Header").substring(5) | ||
| * param0.getDetails().getCustomerAccount().getAccountType() | ||
| * </pre> | ||
| * | ||
| * <p>The limitations are: | ||
| * | ||
| * <ul> | ||
| * <li>no access to variables other than 'this' and 'paramN' (N indexed at 0) | ||
| * <li>no control flow (if), no local variables, basically nothing other than a single chain of | ||
| * method calls | ||
| * <li>Method calls are limited to either 0 or 1 parameters currently | ||
| * <li>Parameters must be literals and only integral (int/long), string, and boolean literals are | ||
| * currently supported | ||
| * </ul> | ||
| */ | ||
| public final class JSPS { | ||
johnbley marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| private static final Logger logger = Logger.getLogger(JSPS.class.getName()); | ||
| private static final Class[] NoParamTypes = new Class[0]; | ||
|
|
||
| public static String evaluate(String jsps, Object thiz, Object[] params) { | ||
| try { | ||
| return unsafeEvaluate(jsps, thiz, params); | ||
| } catch (Throwable t) { | ||
| logger.warning("Can't evaluate {" + jsps + "}: " + t); | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| // FIXME Might be nice to support escaping quotes in string literals... | ||
| private static String unsafeEvaluate(String jsps, Object thiz, Object[] params) throws Exception { | ||
| jsps = jsps.trim(); | ||
| int nextDot = jsps.indexOf('.'); | ||
| String var = jsps.substring(0, nextDot).trim(); | ||
| Object curObject = null; | ||
| if (var.equals("this")) { | ||
| curObject = thiz; | ||
| } else if (var.startsWith("param")) { | ||
| int varIndex = Integer.parseInt(var.substring("param".length())); | ||
| curObject = params[varIndex]; | ||
| } | ||
| int curIndex = nextDot; | ||
| while (curIndex < jsps.length()) { | ||
| curIndex = jsps.indexOf('.', curIndex); | ||
| while (jsps.charAt(curIndex) == '.' || Character.isWhitespace(jsps.charAt(curIndex))) { | ||
| curIndex++; | ||
| } | ||
| int openParen = jsps.indexOf('(', curIndex); | ||
| String method = jsps.substring(curIndex, openParen).trim(); | ||
| int closeParen = jsps.indexOf(')', openParen); | ||
| String paramString = jsps.substring(openParen + 1, closeParen).trim(); | ||
| if (paramString.isEmpty()) { | ||
| Method m = findMatchingMethod(curObject, method, NoParamTypes); | ||
| curObject = m.invoke(curObject); | ||
| } else { | ||
| if (paramString.startsWith("\"") && paramString.endsWith("\"")) { | ||
| String passed = paramString.substring(1, paramString.length() - 1); | ||
| Method m = findMethodWithPossibleTypes(curObject, method, String.class, Object.class); | ||
| curObject = m.invoke(curObject, passed); | ||
| } else if (paramString.equals("true") || paramString.equals("false")) { | ||
| Method m = | ||
| findMethodWithPossibleTypes( | ||
| curObject, method, boolean.class, Boolean.class, Object.class); | ||
| curObject = m.invoke(curObject, Boolean.parseBoolean(paramString)); | ||
| } else if (paramString.matches("[0-9]+")) { | ||
| try { | ||
| Method m = | ||
| findMethodWithPossibleTypes( | ||
| curObject, method, int.class, Integer.class, Object.class); | ||
| int passed = Integer.parseInt(paramString); | ||
| curObject = m.invoke(curObject, passed); | ||
| } catch (NoSuchMethodException tryLongInstead) { | ||
| Method m = | ||
| findMethodWithPossibleTypes( | ||
| curObject, method, long.class, Long.class, Object.class); | ||
| long passed = Long.parseLong(paramString); | ||
| curObject = m.invoke(curObject, passed); | ||
| } | ||
| } else { | ||
| throw new UnsupportedOperationException( | ||
| "Can't parse \"" + paramString + "\" as literal parameter"); | ||
| } | ||
| } | ||
| curIndex = closeParen + 1; | ||
| } | ||
| return curObject == null ? null : curObject.toString(); | ||
| } | ||
|
|
||
| // This sequence of methods is here because: | ||
| // - we want to try a variety of parameter types to match a literal | ||
| // e.g., someMethod(5) could match someMethod(int) or someMethod(Object) | ||
| // - module rules around reflection make some "legal" things harder and require | ||
| // looking for a public class/interface matching the method to call | ||
| // e.g., a naive reflective call through | ||
| // this.getSomeHashMap().entrySet().size() | ||
| // would fail simply because the HashMap$EntrySet implementation | ||
| // is not public, even though the interface it's being call through is. | ||
johnbley marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| private static Method findMatchingMethod( | ||
| Object curObject, String methodName, Class[] actualParamTypes) throws NoSuchMethodException { | ||
| Method m = findMatchingMethod(methodName, curObject.getClass(), actualParamTypes); | ||
| if (m == null) { | ||
| throw new NoSuchMethodException( | ||
| "Can't find matching method for " + methodName + " on " + curObject.getClass().getName()); | ||
| } | ||
| return m; | ||
| } | ||
|
|
||
| // Returns null for none found | ||
| private static Method findMatchingMethod( | ||
| String methodName, Class clazz, Class[] actualParamTypes) { | ||
| if (clazz == null) { | ||
| return null; | ||
| } | ||
| if (Modifier.isPublic(clazz.getModifiers())) { | ||
| try { | ||
| return clazz.getMethod(methodName, actualParamTypes); | ||
| } catch (NoSuchMethodException nsme) { | ||
| // keep trying | ||
| } | ||
| } | ||
| // not public, try interfaces and supertype | ||
johnbley marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| for (Class iface : clazz.getInterfaces()) { | ||
| Method m = findMatchingMethod(methodName, iface, actualParamTypes); | ||
| if (m != null) { | ||
| return m; | ||
| } | ||
| } | ||
| return findMatchingMethod(methodName, clazz.getSuperclass(), actualParamTypes); | ||
| } | ||
|
|
||
| private static Method findMethodWithPossibleTypes( | ||
| Object curObject, String methodName, Class<?>... paramTypesToTryInOrder) | ||
| throws NoSuchMethodException { | ||
| Class c = curObject.getClass(); | ||
| for (Class<?> paramType : paramTypesToTryInOrder) { | ||
| try { | ||
| return findMatchingMethod(curObject, methodName, new Class[] {paramType}); | ||
| } catch (NoSuchMethodException e) { | ||
| // try next one | ||
| } | ||
| } | ||
| throw new NoSuchMethodException(methodName + " with single parameter matching given type"); | ||
| } | ||
| } | ||
60 changes: 60 additions & 0 deletions
60
.../main/java/com/splunk/opentelemetry/instrumentation/nocode/NocodeAttributesExtractor.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| /* | ||
| * Copyright Splunk Inc. | ||
| * | ||
| * 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.splunk.opentelemetry.instrumentation.nocode; | ||
|
|
||
| import io.opentelemetry.api.common.AttributesBuilder; | ||
| import io.opentelemetry.context.Context; | ||
| import io.opentelemetry.instrumentation.api.incubator.semconv.code.CodeAttributesExtractor; | ||
| import io.opentelemetry.instrumentation.api.incubator.semconv.util.ClassAndMethod; | ||
| import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; | ||
| import java.util.Map; | ||
| import javax.annotation.Nullable; | ||
|
|
||
| public final class NocodeAttributesExtractor | ||
| implements AttributesExtractor<NocodeMethodInvocation, Void> { | ||
| private final AttributesExtractor<ClassAndMethod, Void> codeExtractor; | ||
|
|
||
| public NocodeAttributesExtractor() { | ||
| codeExtractor = CodeAttributesExtractor.create(ClassAndMethod.codeAttributesGetter()); | ||
| } | ||
|
|
||
| @Override | ||
| public void onStart( | ||
| AttributesBuilder attributesBuilder, Context context, NocodeMethodInvocation mi) { | ||
| codeExtractor.onStart(attributesBuilder, context, mi.getClassAndMethod()); | ||
|
|
||
| Map<String, String> attributes = mi.getRuleAttributes(); | ||
| for (String key : attributes.keySet()) { | ||
johnbley marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| String jsps = attributes.get(key); | ||
| String value = JSPS.evaluate(jsps, mi.getThiz(), mi.getParameters()); | ||
| if (value != null) { | ||
| attributesBuilder.put(key, value); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public void onEnd( | ||
| AttributesBuilder attributesBuilder, | ||
| Context context, | ||
| NocodeMethodInvocation nocodeMethodInvocation, | ||
| @Nullable Void unused, | ||
| @Nullable Throwable throwable) { | ||
| codeExtractor.onEnd( | ||
| attributesBuilder, context, nocodeMethodInvocation.getClassAndMethod(), unused, throwable); | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.