Closed
Bug 848278
Opened 10 years ago
Closed 10 years ago
[OS.File] Add a way to measure duration of an off main thread operation
Categories
(Toolkit :: OS.File, defect)
Toolkit
OS.File
Tracking
()
RESOLVED
FIXED
mozilla22
People
(Reporter: Yoric, Assigned: yzen)
References
Details
(Keywords: dev-doc-needed)
Attachments
(1 file, 4 obsolete files)
13.74 KB,
patch
|
Details | Diff | Splinter Review |
Whenever attempting to measure the duration of some operation with OS.File, the method that comes to mind is the following: (assuming Task.jsm) let start = Date.now(); yield OS.File.myOperation(...); let stop = Date.now(); let duration = stop - start; (or some Telemetry variant). However, the information given by |duration| is generally not interesting, as it measures not only how long |OS.File.myOperation| has lasted but also how long it took for the main thread to get to handle the message informing that the operation was complete. For many operations, this is much longer than the duration of |OS.File.myOperation|. What we need to do is measure on the OS.File thread the duration of the operation and somehow send this information back to the main thread.
Assignee | ||
Updated•10 years ago
|
Assignee: nobody → yura.zenevich
Assignee | ||
Comment 1•10 years ago
|
||
The main change in the patch is the addition of Scheduler.postWithProbe method that does the same thing as Scheduler.post except for the fact that the first argument is expected to be a histogram name to add a measurement to (I will further comment that as well). This lets anyone to, in case they want to start measuring telemetry on specific osfile off main thread operations, switch from post to postWithProbe. I will add the tests shortly, if you like this approach. Let me know what you think and thanks :)
Attachment #722043 -
Flags: feedback?(dteller)
Reporter | ||
Comment 2•10 years ago
|
||
Comment on attachment 722043 [details] [diff] [review] First pass at adding support for off main thread osfile operations. Review of attachment 722043 [details] [diff] [review]: ----------------------------------------------------------------- Generally, it looks good, but I would not hard-code telemetry. Let me suggest an alternative. Given that all functions of OS.File accept an optional argument |option|, we could use this object to return the duration of the operation. For instance, for any function, if there is an |option| argument and this object has a field |outDuration|, replace this field with the duration of the operation.
Attachment #722043 -
Flags: feedback?(dteller) → feedback+
Assignee | ||
Comment 3•10 years ago
|
||
Hi David, I noticed that, even if the platform specific (actual) implementation of the operation takes options, it's not necessarily passed down from the main thread (hence it's not mentioned in the API docs). Should I leave it as is or should I make sure that for all operations that can make use of options the options are actually propagated ? Thanks.
Flags: needinfo?(dteller)
Assignee | ||
Comment 4•10 years ago
|
||
Since the data is not shared but rather cloned between the controller and the agent, altering the options Object that the worker gets has no effect. In order to update the options' outDuration, the override should happen in the main thread (if we want the user of the method to see the difference). I'm guessing it is still better than hard-coding telemetry, but perhaps you have another suggestion ? Thanks!
Reporter | ||
Comment 5•10 years ago
|
||
I can think of a hack. It might not work, or it might be too subtle for the sake of any reviewer, so don't take for granted that it is the best way to do this. The key idea is that some functions/methods of osfile_async_front.jsm can be labelled as "benchmarkable". For the moment, I would say |OS.File.writeAtomic| and |OS.File.read| are sufficient. 1. Add a boolean field |benchmark| to the messages sent to the worker. 2. Upon reception of a message with |benchmark: true|, osfile_async_worker.js measures the duration and returns |{durationMs: ..., result:...}| instead if just the result (I don't know what we should do in case of exception); 3. Whenever a user calls |OS.File.foo.benchmark| instead of OS.File.foo, the function/method sets |benchmark: true|. Otherwise, |benchmark: false|. Still pretty rough, but it might be nice. What do you think?
Flags: needinfo?(dteller)
Reporter | ||
Comment 6•10 years ago
|
||
Comment on attachment 722043 [details] [diff] [review] First pass at adding support for off main thread osfile operations. Review of attachment 722043 [details] [diff] [review]: ----------------------------------------------------------------- Cancelling f? until we have come up with a good strategy.
Attachment #722043 -
Flags: feedback+
Assignee | ||
Comment 7•10 years ago
|
||
Hi David, Basically this patch is a combination of some of the ideas from comments both yours and mine. I liked the |outDuration| idea so I went ahead and added support for it, it is updated in the main thread after worker sends the message back based on |durationMs| field from the message data. Given that your latest comment implied that there would still be some processing of the worker's message needed, I thought it would be ok to use the |options| and the |outDuration| field for that matter. Note that the |outDuration| will not be updated if the method resulted in exception, this is to be consistent with the way telemetry stopwatch is used, where if the method fails, the measurement is cancelled rather that stopped. Let me know.
Attachment #722043 -
Attachment is obsolete: true
Attachment #723145 -
Flags: feedback?(dteller)
Reporter | ||
Comment 8•10 years ago
|
||
Comment on attachment 723145 [details] [diff] [review] Added support for |outDuration| option on osfile methods. Review of attachment 723145 [details] [diff] [review]: ----------------------------------------------------------------- I like it. ::: toolkit/components/osfile/osfile_async_front.jsm @@ +94,5 @@ > + * > + * @param {JSON} - message returned by the worker. > + * @postArgs {Array} - arguments supplied to PromiseWorker's post method. > + */ > +let updateDuration = function updateDuration(data, postArgs) { Given that this function is used only once – and in a two-liner function – I would suggest inlining it. ::: toolkit/components/osfile/osfile_async_worker.js @@ +42,5 @@ > + options = data.args[data.args.length - 1]; > + } > + // If |outDuration| option was supplied, start measuring the duration > + // of the operation. > + if (options && typeof options.outDuration !== "undefined") { I suspect that you should check whether |typeof options == "object"|.
Attachment #723145 -
Flags: feedback?(dteller) → feedback+
Assignee | ||
Comment 9•10 years ago
|
||
Attachment #723145 -
Attachment is obsolete: true
Attachment #724392 -
Flags: review?(dteller)
Reporter | ||
Comment 10•10 years ago
|
||
Comment on attachment 724392 [details] [diff] [review] Updated to check for options' type and flattened duration check function. Nathan, it would be great to have a second opinion on this.
Attachment #724392 -
Flags: review?(nfroyd)
![]() |
||
Comment 11•10 years ago
|
||
Comment on attachment 724392 [details] [diff] [review] Updated to check for options' type and flattened duration check function. Review of attachment 724392 [details] [diff] [review]: ----------------------------------------------------------------- Communicating through |options| seems a bit sketchy. We freely clone() options to add internal-specific fields. If I'm reading this right, we'll modify the copied options, not the original options that the user passed. So there should be something to transfer |outDuration| back from the cloned options to the user's options, right? Documentation should be added to osfile_async_front.jsm, too. ::: toolkit/components/osfile/osfile_async_front.jsm @@ +102,5 @@ > + } > + let options = methodArgs[methodArgs.length - 1]; > + // Check for options.outDuration. > + if (typeof options !== "object" || > + typeof options.outDuration === "undefined") { Nit: indentation is wrong here. Why not just !("options" in outDuration) ?
Attachment #724392 -
Flags: review?(nfroyd)
![]() |
||
Comment 12•10 years ago
|
||
(In reply to Nathan Froyd (:froydnj) from comment #11) > Communicating through |options| seems a bit sketchy. We freely clone() > options to add internal-specific fields. If I'm reading this right, we'll > modify the copied options, not the original options that the user passed. > So there should be something to transfer |outDuration| back from the cloned > options to the user's options, right? I should clarify: for copying files, we don't modify |options|. For other cases (possibly the more interesting cases), we do.
Assignee | ||
Comment 13•10 years ago
|
||
(In reply to Nathan Froyd (:froydnj) from comment #11) > Comment on attachment 724392 [details] [diff] [review] > Updated to check for options' type and flattened duration check function. > > Review of attachment 724392 [details] [diff] [review]: > ----------------------------------------------------------------- > > Communicating through |options| seems a bit sketchy. We freely clone() > options to add internal-specific fields. If I'm reading this right, we'll > modify the copied options, not the original options that the user passed. > So there should be something to transfer |outDuration| back from the cloned > options to the user's options, right? We only read from options on the worker's side and then optionally return |durationMs| back along with result. Wen only modify the original options in the osfile_async_front.jsm once we get a message back from the worker. > > Documentation should be added to osfile_async_front.jsm, too. > > ::: toolkit/components/osfile/osfile_async_front.jsm > @@ +102,5 @@ > > + } > > + let options = methodArgs[methodArgs.length - 1]; > > + // Check for options.outDuration. > > + if (typeof options !== "object" || > > + typeof options.outDuration === "undefined") { > > Nit: indentation is wrong here. > > Why not just !("options" in outDuration) ? In this case outDuration is in options. We can't really do the other way around, because if options is not an object "in" statement will result in an error.
Reporter | ||
Comment 14•10 years ago
|
||
(In reply to Yura Zenevich [:yzen] from comment #13) > > Communicating through |options| seems a bit sketchy. We freely clone() > > options to add internal-specific fields. If I'm reading this right, we'll > > modify the copied options, not the original options that the user passed. > > So there should be something to transfer |outDuration| back from the cloned > > options to the user's options, right? > > We only read from options on the worker's side and then optionally return > |durationMs| back along with result. Wen only modify the original options in > the osfile_async_front.jsm once we get a message back from the worker. Nathan has a good remark. A few functions call |clone(options)| because they themselves may have side-effects on the options. These functions should copy |outDuration| back in the original argument |options|.
![]() |
||
Comment 15•10 years ago
|
||
(In reply to Yura Zenevich [:yzen] from comment #13) > (In reply to Nathan Froyd (:froydnj) from comment #11) > > ::: toolkit/components/osfile/osfile_async_front.jsm > > @@ +102,5 @@ > > > + } > > > + let options = methodArgs[methodArgs.length - 1]; > > > + // Check for options.outDuration. > > > + if (typeof options !== "object" || > > > + typeof options.outDuration === "undefined") { > > > > Why not just !("options" in outDuration) ? > > In this case outDuration is in options. We can't really do the other way > around, because if options is not an object "in" statement will result in an > error. Sorry, I reversed the bits in my comment! For clarity, I was wondering why you didn't write this as: if (typeof options !== "object" || !("outDuration" in options))
Assignee | ||
Comment 16•10 years ago
|
||
Ya that makes a lot of sense.(In reply to Nathan Froyd (:froydnj) from comment #15) > (In reply to Yura Zenevich [:yzen] from comment #13) > > (In reply to Nathan Froyd (:froydnj) from comment #11) > > > ::: toolkit/components/osfile/osfile_async_front.jsm > > > @@ +102,5 @@ > > > > + } > > > > + let options = methodArgs[methodArgs.length - 1]; > > > > + // Check for options.outDuration. > > > > + if (typeof options !== "object" || > > > > + typeof options.outDuration === "undefined") { > > > > > > Why not just !("options" in outDuration) ? > > > > In this case outDuration is in options. We can't really do the other way > > around, because if options is not an object "in" statement will result in an > > error. > > Sorry, I reversed the bits in my comment! For clarity, I was wondering why > you didn't write this as: > > if (typeof options !== "object" || !("outDuration" in options)) Ya that makes a lot of sense.
Assignee | ||
Comment 17•10 years ago
|
||
So to summarize: I need to update the following: * Handle all cases where user options are cloned * Update a check for "outDuration" in the worker Is there anything else?
Reporter | ||
Comment 18•10 years ago
|
||
(In reply to Yura Zenevich [:yzen] from comment #17) > So to summarize: > > I need to update the following: > > * Handle all cases where user options are cloned Probably add some documentation around function |clone| o ensure that nobody forgets them. > * Update a check for "outDuration" in the worker > Is there anything else? Not that I can think of.
Assignee | ||
Comment 19•10 years ago
|
||
* Updated clone utility to optionally handle references (inspired by worker's transferList option when posting messages - https://developer.mozilla.org/en-US/docs/DOM/Worker) * Updated some of the if statements as per :froydnj * Added more tests.
Attachment #724392 -
Attachment is obsolete: true
Attachment #724392 -
Flags: review?(dteller)
Attachment #724806 -
Flags: review?(dteller)
Reporter | ||
Comment 20•10 years ago
|
||
Comment on attachment 724806 [details] [diff] [review] Patch is now updated to comments. Review of attachment 724806 [details] [diff] [review]: ----------------------------------------------------------------- r=me with a few minor changes. ::: toolkit/components/osfile/_PromiseWorker.jsm @@ +115,5 @@ > * Messages may also contain a field |id| to help > * with debugging. > * > + * If an |outDuration| option was posted to the worker and msg.data > + * contains |ok|, durationMs field will be sent back to the main thread . Nit: "message will also contain a field |durationMs|, holding the duration of the function call in milliseconds." ::: toolkit/components/osfile/osfile_async_front.jsm @@ +75,5 @@ > * changes to an option object. The copy ensures that we do not modify > * a client-provided object by accident. > + * > + * Note: if we want to reference and not copy specific fields, we can > + * provide an optional |keys| argument containing their names. Good idea. Nit: don't use "we". @@ +84,2 @@ > */ > +let clone = function clone(object, keys) { I would suggest |function clone(object, keys = noKeys)|, where |noKeys| has been defined globally as |[]|. Also, |keys| is a little ambiguous – perhaps |refs|? @@ +86,5 @@ > let result = {}; > + // Make a reference between result[key] and object[key]. > + let refer = function refer(result, key, object) { > + Object.defineProperty(result, key, { > + configurable: true, Are you sure about |configurable|? @@ +130,5 @@ > + if (typeof options !== "object" || !("outDuration" in options)) { > + return data.ok; > + } > + // If data.durationMs is not present, return data.ok (there was an > + // exception applying the method). Can that happen? ::: toolkit/components/osfile/tests/mochi/main_test_osfile_async.js @@ +873,5 @@ > + let pathDest = OS.Path.join(OS.Constants.Path.tmpDir, > + "osfile async test read writeAtomic.tmp"); > + let tmpPath = pathDest + ".tmp"; > + let currentDir = yield OS.File.getCurrentDirectory(); > + let pathSource = OS.Path.join(currentDir, EXISTING_FILE); You probably should use |pathSource| instead of |EXISTING_FILE| at line 868.
Attachment #724806 -
Flags: review?(dteller) → review+
Reporter | ||
Comment 21•10 years ago
|
||
Oh, one last thing: |outDuration| is probably ambiguous (bug 850636 will introduce another measurement for duration). Perhaps |outExecutionDuration| would be clearer?
Assignee | ||
Comment 22•10 years ago
|
||
Attachment #724806 -
Attachment is obsolete: true
Assignee | ||
Comment 23•10 years ago
|
||
carrying forward r+ from Yoric.
Assignee | ||
Comment 24•10 years ago
|
||
Here's the try results: https://tbpl.mozilla.org/?tree=Try&rev=99b886f96ba0
Assignee | ||
Updated•10 years ago
|
Keywords: checkin-needed
Comment 25•10 years ago
|
||
https://hg.mozilla.org/integration/mozilla-inbound/rev/3196b8bf8142
Flags: in-testsuite+
Keywords: checkin-needed
Comment 26•10 years ago
|
||
https://hg.mozilla.org/mozilla-central/rev/3196b8bf8142
Status: NEW → RESOLVED
Closed: 10 years ago
Resolution: --- → FIXED
Target Milestone: --- → mozilla22
Reporter | ||
Updated•10 years ago
|
Keywords: dev-doc-needed
You need to log in
before you can comment on or make changes to this bug.
Description
•