Skip to content
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

Merged
merged 41 commits into from
Oct 23, 2019

Conversation

obecny
Copy link
Member

@obecny obecny commented Oct 15, 2019

Fixes #405
Fixes #438

  1. I have added some performance metrics based on this article
    https://developers.google.com/web/fundamentals/performance/navigation-and-resource-timing

  2. 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.

  3. As it wasn't really decided in which namespace the plugin should be I added this as other plugins plugin-*

  4. Updated example of using WebTracer

@mayurkale22
Copy link
Member

/cc @draffensperger

@codecov-io
Copy link

codecov-io commented Oct 15, 2019

Codecov Report

Merging #433 into master will decrease coverage by 0.12%.
The diff coverage is 86.95%.

@@            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
Impacted Files Coverage Δ
packages/opentelemetry-core/src/common/types.ts 100% <ø> (ø) ⬆️
packages/opentelemetry-web/test/WebTracer.test.ts 0% <0%> (ø) ⬆️
packages/opentelemetry-web/src/WebTracer.ts 0% <0%> (ø) ⬆️
packages/opentelemetry-tracing/src/Span.ts 100% <100%> (ø) ⬆️
...ry-tracing/test/export/ConsoleSpanExporter.test.ts 100% <100%> (ø)
packages/opentelemetry-core/src/common/time.ts 94.91% <100%> (-1.7%) ⬇️
packages/opentelemetry-tracing/test/Span.test.ts 100% <100%> (ø) ⬆️
...ckages/opentelemetry-core/test/common/time.test.ts 100% <100%> (ø) ⬆️
...elemetry-tracing/src/export/ConsoleSpanExporter.ts 85.71% <80%> (+48.21%) ⬆️
... and 7 more

Copy link
Contributor

@draffensperger draffensperger left a 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.

* @param obj
* @param key
*/
export function hasKey<O>(obj: O, key: keyof any): key is keyof O {
Copy link
Contributor

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?

Copy link
Member Author

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-web/src/WebTracer.ts Show resolved Hide resolved
@obecny
Copy link
Member Author

obecny commented Oct 21, 2019

@draffensperger @mayurkale22 updated code to use events instead of spans now, please have a look again

addEvent(
name: string,
attributes?: types.Attributes,
startTime?: number
Copy link
Member

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).

Copy link
Contributor

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

Copy link
Member

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);

Copy link
Member Author

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.

Copy link
Member Author

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);
  1. 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.
  2. 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.

  1. Not sure what would be benefit besides great chance to break something in existing code

Copy link
Member

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?

Copy link
Member Author

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 ?

Copy link
Member

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?

Copy link
Member Author

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

@@ -21,3 +21,9 @@ export enum LogLevel {
INFO,
DEBUG,
}

export interface TimeOriginLegacy {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, thanks.

addEvent(
name: string,
attributes?: types.Attributes,
startTime?: number
Copy link
Contributor

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-types/src/trace/span.ts Outdated Show resolved Hide resolved
packages/opentelemetry-web/src/WebTracer.ts Outdated Show resolved Hide resolved
packages/opentelemetry-web/src/WebTracer.ts Outdated Show resolved Hide resolved
@mayurkale22
Copy link
Member

@open-telemetry/node-approvers and @open-telemetry/node-maintainers Please review and approve when you get a chance, thanks :)

Copy link
Contributor

@draffensperger draffensperger left a 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) {
Copy link
Contributor

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-tracing/src/Span.ts Outdated Show resolved Hide resolved
packages/opentelemetry-tracing/src/Span.ts Outdated Show resolved Hide resolved
packages/opentelemetry-tracing/src/Span.ts Outdated Show resolved Hide resolved
packages/opentelemetry-types/src/trace/span.ts Outdated Show resolved Hide resolved
packages/opentelemetry-web/src/WebTracer.ts Outdated Show resolved Hide resolved
Comment on lines +77 to +81
const rootSpan = this._startSpan(
AttributeNames.DOCUMENT_LOAD,
PTN.FETCH_START,
entries
);
Copy link
Member

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.

https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-semantic-conventions.md#semantic-conventions

Copy link
Member Author

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';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be static?

Copy link
Contributor

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?

Copy link
Member Author

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';
Copy link
Member

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?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

export abstract class BasePlugin<T> implements Plugin<T> {

*/
constructor(config: PluginConfig = {}) {
super();
this._onDocumentLoaded = this._onDocumentLoaded.bind(this);
Copy link
Member

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)) {
Copy link
Member

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

Copy link
Contributor

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

Copy link
Contributor

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

Copy link
Member

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?

Copy link
Contributor

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.

Copy link
Member Author

@obecny obecny Oct 23, 2019

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

Copy link
Member Author

@obecny obecny Oct 23, 2019

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 = {
Copy link
Member

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?

Copy link
Member

@markwolff markwolff left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@mayurkale22 mayurkale22 merged commit b21ed97 into open-telemetry:master Oct 23, 2019
@obecny obecny deleted the plugin-document-load branch July 8, 2020 12:11
lukaswelinder pushed a commit to agile-pm/opentelemetry-js that referenced this pull request Jul 24, 2020
* Prepare release 3.18.0

Signed-off-by: Yuri Shkuro <ys@uber.com>

* fix formatting

Signed-off-by: Yuri Shkuro <ys@uber.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Allow specifying a timestamp when creating Events [Plugin] - WebTracer - add "document-load" plugin
6 participants