-
Notifications
You must be signed in to change notification settings - Fork 821
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
feat(plugin-document-load): new plugin for document load for web tracer #433
feat(plugin-document-load): new plugin for document load for web tracer #433
Conversation
/cc @draffensperger |
Codecov Report
@@ Coverage Diff @@
## master #433 +/- ##
==========================================
- Coverage 95.39% 95.26% -0.13%
==========================================
Files 124 125 +1
Lines 6141 6292 +151
Branches 506 516 +10
==========================================
+ Hits 5858 5994 +136
- Misses 283 298 +15
|
packages/opentelemetry-plugin-document-load/src/documentLoad.ts
Outdated
Show resolved
Hide resolved
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.
Very cool!!
I made a bunch of comments, but it's AWESOME to see this coming together.
packages/opentelemetry-plugin-document-load/src/documentLoad.ts
Outdated
Show resolved
Hide resolved
packages/opentelemetry-plugin-document-load/src/documentLoad.ts
Outdated
Show resolved
Hide resolved
packages/opentelemetry-plugin-document-load/src/documentLoad.ts
Outdated
Show resolved
Hide resolved
packages/opentelemetry-plugin-document-load/src/documentLoad.ts
Outdated
Show resolved
Hide resolved
* @param obj | ||
* @param key | ||
*/ | ||
export function hasKey<O>(obj: O, key: keyof any): key is keyof O { |
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.
I commented about this above too, but can we remove this function by doing the typeof value === 'number'
checks above?
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.
it will not work this way with typescript when you use key with forEach
packages/opentelemetry-plugin-document-load/test/documentLoad.test.ts
Outdated
Show resolved
Hide resolved
packages/opentelemetry-plugin-document-load/test/documentLoad.test.ts
Outdated
Show resolved
Hide resolved
packages/opentelemetry-plugin-document-load/test/documentLoad.test.ts
Outdated
Show resolved
Hide resolved
…y-js into plugin-document-load
@draffensperger @mayurkale22 updated code to use events instead of spans now, please have a look again |
addEvent( | ||
name: string, | ||
attributes?: types.Attributes, | ||
startTime?: number |
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.
I think we should use TimeInput
instead of number
here. Basically, type TimeInput = HrTime | number | Date
.
Optional: It would be nice to have this in the separate PR (adding startTime to addEvent
).
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.
This also allows passing in performance.now timestamps easily and more accurately as explained in above comment
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.
Does it make sense to change the order with a default value to startTime?
addEvent(name: string,
startTime:TimeInput = hrTime(),
attributes?: types.Attributes);
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.
I asked previously if this should be on hold until the time for event is added or I should add this here. My understanding was to add this here then and not to hold this PR longer.
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.
Does it make sense to change the order with a default value to startTime?
addEvent(name: string, startTime:TimeInput = hrTime(), attributes?: types.Attributes);
attributes
are already there, so it will break all possible places that this is being used as well as any of new code that is already being developed.- It will break the spec too
https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/api-tracing.md#add-events
An Event is defined by the following properties:
(Required) Name of the event.
(Optional) One or more Attribute.
(Optional) Timestamp for the event.
time is on 3rd place.
- Not sure what would be benefit besides great chance to break something in existing code
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.
My concern is it would be a problem if you give no attributes
but a startTime
to the function. Especially from JavaScript where you don't have the type. WDYT?
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.
Hmm not sure if I understand what you mean, can you give some example and what it may cause and how switching startTime
with attributes
will fix that ?
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.
With current signature, below are the valid scenarios:
span.addEvent('name'); // just a name
span.addEvent('name', {key: 'value'}); // name + attributes
span.addEvent('name', {key: 'value'}, Date.now()); // name + attributes + time
But, what if you have a startTime
but no attributes
?
span.addEvent('name', Date.now()) // invalid params
span.addEvent('name', undefined, Date.now()) // valid but have to pass an undefined
Is it a valid problem or no, what do you think?
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.
ok now it is clear, I can simply add detection if 2nd param is a valid type of TimeInput
when arguments are 2 only and then treat it as time
packages/opentelemetry-tracing/src/export/ConsoleSpanExporter.ts
Outdated
Show resolved
Hide resolved
@@ -21,3 +21,9 @@ export enum LogLevel { | |||
INFO, | |||
DEBUG, | |||
} | |||
|
|||
export interface TimeOriginLegacy { |
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.
Looks good, thanks.
packages/opentelemetry-plugin-document-load/src/documentLoad.ts
Outdated
Show resolved
Hide resolved
packages/opentelemetry-plugin-document-load/src/documentLoad.ts
Outdated
Show resolved
Hide resolved
addEvent( | ||
name: string, | ||
attributes?: types.Attributes, | ||
startTime?: number |
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.
This also allows passing in performance.now timestamps easily and more accurately as explained in above comment
packages/opentelemetry-tracing/src/export/ConsoleSpanExporter.ts
Outdated
Show resolved
Hide resolved
@open-telemetry/node-approvers and @open-telemetry/node-maintainers Please review and approve when you get a chance, thanks :) |
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.
This is great!! I've requested a few much more minor changes, but giving a general LGTM pending those. Excited to see this get in soon.
parent: rootSpan, | ||
} | ||
); | ||
if (fetchSpan) { |
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.
Nice! I really like this fetch span :)
packages/opentelemetry-plugin-document-load/src/documentLoad.ts
Outdated
Show resolved
Hide resolved
const rootSpan = this._startSpan( | ||
AttributeNames.DOCUMENT_LOAD, | ||
PTN.FETCH_START, | ||
entries | ||
); |
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.
I think a component
attribute could be set here. There's no specific spec calling for it, but it is required in each other "span" type.
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.
added inside _startSpan
* This class represents a document load plugin | ||
*/ | ||
export class DocumentLoad extends BasePlugin<unknown> { | ||
readonly component: string = 'document-load'; |
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.
Should this be static?
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.
Or maybe even a module level const
?
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.
I made it the same as for example http
component. But the node plugins creates a new instance immediately with passing the component name which is not the case for browser. Isn't the component
itself already required later the same way as it is with adding component
to each span ? At least I have added the component
to each span now as attribute.
*/ | ||
export class DocumentLoad extends BasePlugin<unknown> { | ||
readonly component: string = 'document-load'; | ||
readonly version: string = '1'; |
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.
What does this version mean? Is it required?
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.
opentelemetry-js/packages/opentelemetry-core/src/trace/instrumentation/BasePlugin.ts
Line 29 in d6f669a
export abstract class BasePlugin<T> implements Plugin<T> { |
*/ | ||
constructor(config: PluginConfig = {}) { | ||
super(); | ||
this._onDocumentLoaded = this._onDocumentLoaded.bind(this); |
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.
Alternatively, to get the intended this
scoping, does TS complain about making the class member function an arrow function? Or would this go against style guidelines?
entries: PerformanceEntries | ||
) { | ||
// span can be undefined when entries are missing the certain performance - the span will not be created | ||
if (typeof span !== 'undefined' && hasKey(entries, performanceName)) { |
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.
nit: 'undefined'
should be made a constant (probably outside this plugin), (e.g. Constants.Undefined
), so that this can be minified better
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.
Would it work to just do span !== undefined
instead?
I would hope that smart minifier should be able to hoist out shared constants
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.
(Though I don't know if they actually do!) I guess to me it feels a little weird to try to do that optimization if it could be done better automatically
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.
You are right, I think !== undefined
would work here. I think typeof
is only needed for global stuff.
For the string constants, uglify won't do any hoisting here, but maybe other bundlers do?
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.
I suspect the Closure compiler can though I have not specifically verified it (see
https://developers.google.com/closure/compiler/docs/api-tutorial3#better)
There is actually a really cool project called Tsickle that can transform TypeScript code into Closure compiler type annotations and then have it feed into the advanced compilation mode for Closure. I would like to eventually see us be able to distribute <script>
tag files for the browser that have been fully minified for basic things like the document load instrumentation. That could help people who aren't interested in custom spans but just want a minimally-invasive way of instrumenting their page loads. Same could go for people who just want page load + out of the box user interaction tracing.
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.
with regards to
Alternatively, to get the intended this scoping, does TS complain about making the class member function an arrow function? Or would this go against style guidelines?
this if fine if you add listener in the same function but here the _onDocumentLoaded
is being added in patch
and removed in unpatch
function - that's why the context needs to be bind before so you remove listener correctly
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.
With regards to micro optimisation
- I think this should be a part of uglifier not the code itself.
And typeof someVariable === 'undefined'
vs someVariable === undefined
.
First example will never raise an error the second will raise a ReferenceError
if such thing isn't declared. There used to be also some extra error with this in IE (hard to track as when debugging in IE it works fine, when debug is closed it might break). To keep consistency I'm choosing the first option always to avoid possible errors as typeof
will never have such issues.
/** | ||
* Performance metrics | ||
*/ | ||
export type PerformanceEntries = { |
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.
nit: Could this use Partial
?
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.
LGTM!
* Prepare release 3.18.0 Signed-off-by: Yuri Shkuro <[email protected]> * fix formatting Signed-off-by: Yuri Shkuro <[email protected]>
Fixes #405
Fixes #438
I have added some performance metrics based on this article
https://developers.google.com/web/fundamentals/performance/navigation-and-resource-timing
I have added
ConsoleExporter
as discussed in [Plugin] - WebTracer - add "document-load" plugin #405 - if you think it is not needed I can remove it - however it was much easier for me to develop new plugin and to have easy way of seeing result in console. if you against it maybe we can consider keeping it until there is an easy way of using zipkin with the Web Tracer, as now I wasn't able to easily have something like this.As it wasn't really decided in which namespace the plugin should be I added this as other plugins
plugin-*
Updated example of using WebTracer