-
Notifications
You must be signed in to change notification settings - Fork 52
perf: short-circuit observe() when OTEL_ENABLE_EXTRA_METRICS is disabled #1087
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2,36 +2,44 @@ import { isWrappedError } from "../blocks/loader.ts"; | |||||||||||||||||||||||||
| import { ValueType } from "../deps.ts"; | ||||||||||||||||||||||||||
| import { meter, OTEL_ENABLE_EXTRA_METRICS } from "./otel/metrics.ts"; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const operationDuration = meter.createHistogram("block_op_duration", { | ||||||||||||||||||||||||||
| description: "operation duration", | ||||||||||||||||||||||||||
| unit: "ms", | ||||||||||||||||||||||||||
| valueType: ValueType.DOUBLE, | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
| const observeWithMetrics = (() => { | ||||||||||||||||||||||||||
| const operationDuration = meter.createHistogram("block_op_duration", { | ||||||||||||||||||||||||||
| description: "operation duration", | ||||||||||||||||||||||||||
| unit: "ms", | ||||||||||||||||||||||||||
| valueType: ValueType.DOUBLE, | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||
| * Observe function durations based on the provided labels | ||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||
| export const observe = async <T>( | ||||||||||||||||||||||||||
| op: string, | ||||||||||||||||||||||||||
| f: () => Promise<T>, | ||||||||||||||||||||||||||
| ): Promise<T> => { | ||||||||||||||||||||||||||
| const start = performance.now(); | ||||||||||||||||||||||||||
| let isError = "false"; | ||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||
| const result = await f(); | ||||||||||||||||||||||||||
| if (isWrappedError(result)) { | ||||||||||||||||||||||||||
| return async <T>( | ||||||||||||||||||||||||||
| op: string, | ||||||||||||||||||||||||||
| f: () => Promise<T>, | ||||||||||||||||||||||||||
| ): Promise<T> => { | ||||||||||||||||||||||||||
| const start = performance.now(); | ||||||||||||||||||||||||||
| let isError = "false"; | ||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||
| const result = await f(); | ||||||||||||||||||||||||||
| if (isWrappedError(result)) { | ||||||||||||||||||||||||||
| isError = "true"; | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| return result; | ||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||
| isError = "true"; | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| return result; | ||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||
| isError = "true"; | ||||||||||||||||||||||||||
| throw error; | ||||||||||||||||||||||||||
| } finally { | ||||||||||||||||||||||||||
| if (OTEL_ENABLE_EXTRA_METRICS) { | ||||||||||||||||||||||||||
| throw error; | ||||||||||||||||||||||||||
| } finally { | ||||||||||||||||||||||||||
| operationDuration.record(performance.now() - start, { | ||||||||||||||||||||||||||
| "operation.name": op, | ||||||||||||||||||||||||||
| "operation.is_error": isError, | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||
| })(); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||
| * When metrics are disabled, pass through directly to avoid async wrapper | ||||||||||||||||||||||||||
| * overhead (performance.now, try/catch, isWrappedError check). | ||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||
| export const observe: <T>( | ||||||||||||||||||||||||||
| op: string, | ||||||||||||||||||||||||||
| f: () => Promise<T>, | ||||||||||||||||||||||||||
| ) => Promise<T> = OTEL_ENABLE_EXTRA_METRICS | ||||||||||||||||||||||||||
| ? observeWithMetrics | ||||||||||||||||||||||||||
| : (_op, f) => f(); | ||||||||||||||||||||||||||
|
Comment on lines
+40
to
+45
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # First, locate the observability directory and the observe.ts file
fd -t f observe.tsRepository: deco-cx/deco Length of output: 80 🏁 Script executed: # Read the observe.ts file to see full context
cat -n observability/observe.tsRepository: deco-cx/deco Length of output: 1555 🏁 Script executed: # Search for callers of the observe function
rg "observe\(" -A 3 -B 1 --type ts --type jsRepository: deco-cx/deco Length of output: 1210 🏁 Script executed: # Check git history to see previous implementations
git log --oneline -20 -- observability/observe.tsRepository: deco-cx/deco Length of output: 136 🏁 Script executed: # Look for tests of the observe function
fd -t f "*.test\|*.spec" | xargs grep -l "observe"Repository: deco-cx/deco Length of output: 351 🏁 Script executed: # Search for imports of observe function from observe.ts
rg "from ['\"].*observe" --type ts --type js | head -20Repository: deco-cx/deco Length of output: 125 🏁 Script executed: # Search for "observe" being called with a function argument
rg "observe\s*\(" -A 2 --type ts --type js | grep -B 2 -A 2 "observe.*=>|observe.*function"Repository: deco-cx/deco Length of output: 38 🏁 Script executed: # Get the git diff for the perf commit
git show f5997fa --statRepository: deco-cx/deco Length of output: 18468 🏁 Script executed: # Check what the previous implementation looked like
git show f5997faRepository: deco-cx/deco Length of output: 50372 🏁 Script executed: # Find actual calls to observe() from observe.ts module
rg "observe\(" runtime/mod.ts -A 3 -B 1Repository: deco-cx/deco Length of output: 38 🏁 Script executed: # Search for how observe is used across the codebase
rg "observe\(" --type ts -B 2 -A 5 | grep -A 7 -B 3 "observe.*Promise\|observe.*async\|observe.*catch"Repository: deco-cx/deco Length of output: 38 🏁 Script executed: # Look for error handling around observe calls
rg "observe\(" --type ts --type js -A 10 | head -50Repository: deco-cx/deco Length of output: 1888 🏁 Script executed: # Read runtime/mod.ts to see how observe is actually used
cat -n runtime/mod.ts | head -100Repository: deco-cx/deco Length of output: 4211 🏁 Script executed: # Search more carefully for the observe function usage pattern
rg "observe\s*\(" runtime/mod.ts -A 5 -B 2Repository: deco-cx/deco Length of output: 38 🏁 Script executed: # Look for the exact pattern where observe() is called with a function
rg "observe\(['\"]" --type ts --type js -A 3Repository: deco-cx/deco Length of output: 38 🏁 Script executed: # Check if there are any tests for the observe function
find . -name "*.test.ts" -o -name "*.spec.ts" | xargs grep -l "observe" 2>/dev/null || echo "No test files found matching observe"Repository: deco-cx/deco Length of output: 92 🏁 Script executed: # Search for actual invocations of observe() with string and function
rg "observe\s*\(\s*['\"]" --type ts --type js -A 3Repository: deco-cx/deco Length of output: 38 🏁 Script executed: # Look at more of runtime/mod.ts to find where observe is called
cat -n runtime/mod.ts | sed -n '100,276p'Repository: deco-cx/deco Length of output: 5771 🏁 Script executed: # Search for "observe(" as a function call in the entire codebase
rg "observe\s*\(\s*['\"]" --type ts --type jsRepository: deco-cx/deco Length of output: 38 🏁 Script executed: # Check if observe function is used in any render or invoke functions
rg "observe" runtime/features/render.tsx -A 3 -B 2
rg "observe" runtime/features/invoke.ts -A 3 -B 2
rg "observe" runtime/features/meta.ts -A 3 -B 2Repository: deco-cx/deco Length of output: 38 🏁 Script executed: # Search for how monitoring.metrics is used
rg "monitoring\.metrics\|state\.monitoring\.metrics" --type ts --type js -A 3 -B 1Repository: deco-cx/deco Length of output: 38 🏁 Script executed: # Search for uses of the metrics property in monitoring
rg "\.metrics\s*\(" --type ts --type js -A 3 -B 1 | head -50Repository: deco-cx/deco Length of output: 38 🏁 Script executed: # Look for where observe might be called as a function
rg "metrics\s*\(\s*['\"]" --type ts --type js -A 2Repository: deco-cx/deco Length of output: 38 🏁 Script executed: # Look for where the function passed to observe might throw
rg "observe\|metrics" engine/ --type ts -A 5 | head -80Repository: deco-cx/deco Length of output: 38 🏁 Script executed: # Check the resolver to see how observe/metrics is used
rg "monitoring" engine/core/resolver.ts -A 3 -B 3Repository: deco-cx/deco Length of output: 1006 🏁 Script executed: # Look at blocks/loader.ts since observe is imported with isWrappedError from there
rg "isWrappedError" blocks/loader.ts -A 3 -B 3Repository: deco-cx/deco Length of output: 234 🏁 Script executed: # Search for any synchronous error throwing patterns in the codebase
rg "throw\s+new\s+Error|throw\s+\w" blocks/loader.ts -B 2 -A 2 | head -40Repository: deco-cx/deco Length of output: 327 🏁 Script executed: # Check if there are any function blocks that might synchronously throw before returning Promise
rg "observe\(" --type ts -B 5 -A 5 runtime/ | head -100Repository: deco-cx/deco Length of output: 856 🏁 Script executed: # Check if metricsFunc is actually used in resolver.ts
cat -n engine/core/resolver.ts | grep -A 10 -B 5 "metricsFunc"Repository: deco-cx/deco Length of output: 1206 🏁 Script executed: # Search for any patterns where observe might be invoked
rg "observe\s*\(\s*[\"\']|metrics\s*\(\s*[\"\']" --type ts --type jsRepository: deco-cx/deco Length of output: 180 🏁 Script executed: # Look at the complete monitoring type definition
rg "interface Monitoring|type Monitoring" --type ts -A 10Repository: deco-cx/deco Length of output: 566 🏁 Script executed: # Check what the previous version of observe looked like before the perf commit
git show f5997fa^:observability/observe.ts 2>/dev/null || echo "Cannot retrieve previous version"Repository: deco-cx/deco Length of output: 88 🏁 Script executed: # Find where metricsFunc is actually called with arguments
rg "metricsFunc" engine/core/resolver.ts -A 5 -B 2Repository: deco-cx/deco Length of output: 796 🏁 Script executed: # Check the ObserveFunc type definition
rg "type ObserveFunc|interface ObserveFunc" --type ts -A 3Repository: deco-cx/deco Length of output: 240 🏁 Script executed: # Verify that the function passed to observe at line 478 is always async
sed -n '476,490p' engine/core/resolver.tsRepository: deco-cx/deco Length of output: 786 🏁 Script executed: # Check if there are any other callers of metrics function besides the one in resolver
rg "metrics\?" engine/core/resolver.ts -n | grep -v "monitoring"Repository: deco-cx/deco Length of output: 38 🏁 Script executed: # Check if there are any other uses of observe/metrics in the codebase
rg "metricsFunc" engine/ --type ts -A 3 -B 1Repository: deco-cx/deco Length of output: 784 🏁 Script executed: # Verify the complete call signature at line 478
sed -n '475,495p' engine/core/resolver.tsRepository: deco-cx/deco Length of output: 926 🏁 Script executed: # Check if the function signature constraint is enforced
rg "ObserveFunc|<T>\(" observability/observe.ts -B 2 -A 3Repository: deco-cx/deco Length of output: 323 🏁 Script executed: # Do a final check - see if any tests would break with the proposed fix
find . -name "*.test.ts" -o -name "*.spec.ts" | head -20Repository: deco-cx/deco Length of output: 254 🏁 Script executed: # Check if there are any other places where observe might be called besides the one in resolver.ts
rg "observe\(" --type ts --type js | grep -v "observer\|IntersectionObserver"Repository: deco-cx/deco Length of output: 521 Disabled branch can break Promise contract via synchronous throws. At line 45, For stricter Promise contract compliance, consider wrapping the call: Proposed fix) => Promise<T> = OTEL_ENABLE_EXTRA_METRICS
? observeWithMetrics
- : (_op, f) => f();
+ : (_op, f) => Promise.resolve().then(f);📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
observeWithMetricsis still eagerly initialized when metrics are disabled.Line 5 uses an IIFE, so
meter.createHistogram(...)on Line 6 runs at import time even whenOTEL_ENABLE_EXTRA_METRICSis false. That defeats the lazy-init/perf goal in disabled mode.💡 Proposed fix
Also applies to: 34-34
🤖 Prompt for AI Agents