WeBWorK Problems

time to the microsecond

time to the microsecond

by Alex Jordan -
Number of replies: 7
We can use time() to get the current time in seconds.

I'd like to get the current time in microseconds. In Time::HiRes, there is gettimeofday() which can do this.

Where is the appropriate place for me to do something like:

use Time::HiRes qw/gettimeofday/;

so that in a PG problem, I could use gettimeofday()?
In reply to Alex Jordan

Re: time to the microsecond

by Michael Gage -
It can be tricky to include CPAN modules inside the Safe.pm compartment and possibly has some security risks particularly if they are not pure perl but involve some C code as well.

A first attempt might be to try to make a .pm module such as PGcore.pm which gets processed initially outside the Safe compartment. A second idea: Time::HiRes is already being used in WeBWorK2. You might be able to create a specialized hires_time() function in webwork2 and then share it explicitly with the Safe compartment in PG.pm. Be careful with this one.

Take care,

Mike

In reply to Michael Gage

Re: time to the microsecond

by Alex Jordan -
So the following works. I put this in PGcore.pm:

use Time::HiRes qw/gettimeofday/;

and then later:

sub time_hires {
Time::HiRes::gettimeofday();
}

and then in a problem, I can use:

PGcore::time_hires()

But if I wanted to do this in a way that I can commit and open a pull request for, I suspect this is wrong. I suspect I should not use PGCore.pm for this, is that right? Also, should I strive to make it so that I can use "time_hires()" instead of "PGcore::time_hires()"?
In reply to Alex Jordan

Re: time to the microsecond

by Michael Gage -
Cool. It's not always that easy (see the hoops jumped through to get the localization maketext() to work).

As for presenting it. It depends on how often and where you see it being used. If it's going to be used regularly you can leave it in PGcore. You can also put a front end on it by putting time_hires() in PGbasicmacros.pl redirected to PGcore. You'll notice that many functions that used to be defined in PGbasicmacros.pl (or PG.pl) are now defined there but redirected to PGcore to get the heavy work done.

What's the use case for time_hires()?


In reply to Michael Gage

Re: time to the microsecond

by Alex Jordan -
This is my current idea for getting unique id's on GeoGebra applet-containing divs. In the parserGeoGebra I'm working on, you could make

$a = GeoGebra()->with(params that define the applet);
$b = GeoGebra()->with(params that define the applet);

And when each is created, these are made:
$a->{label}
$b->{label}

And used as part of the HTML div id. (And also, if there is a hidden answer blank, used in the name/id for the input field.)

The issue that this attempts to solve is that if you had something like the library browser load 20 separate problems that all use GeoGebra, current methods for naming the applet and its div will usually result in the same id like "applet" and there would be clashing as the js tries to do things to the content in the div. Even within the same problem file, this is a subtlety that could be off the radar of problem writers, so the $a and $b above might use the same div id.

If there could always be some way to count how many PG problems have been loaded on a page so far, that could be leveraged to get unique div ids. But that would mean always accounting for whatever is rendering the multiple problems (Library Browser, Set Details page, PreTeXt, etc, ....) and I think it's too much to work out.

So instead, if $a->{label} relies on the current microsecond count, that's enough. With:

TEXT((PGcore::time_hires())[1], $BR, (PGcore::time_hires())[1]);

my server gives:
802693
802707

so 14 μs difference, even when executed immediately adjacent. With two GeoGebra Math Objects, they will be created with even more temporal separation.

I guess more generally, time_hires() could be a way to generate "more random" values without having to use srand. You could have problems with "stable random" numbers from rand, and "unstable random" numbers from time_hires.

Separate question. This is really based on Time::HiRes's gettimeofday() function. Should I make the function that PG uses mirror that name?
In reply to Alex Jordan

Re: time to the microsecond

by Michael Gage -
If there could always be some way to count how many PG problems have been loaded on a page so far, that could be leveraged to get unique div ids. But that would mean always accounting for whatever is rendering the multiple problems (Library Browser, Set Details page, PreTeXt, etc, ....) and I think it's too much to work out.

Without having actually tried this I don't think it would be that hard to supply a unique id.

My initial idea was to put a counter in WeBWorK.pm (or even Apache::WeBWorK.pm) that is initialized when a new child process is created.
That would create an id unique to each child process. However it's possible that a problem won't always be processed by the same child -- so that approach is probably too risky.

Tying the counter to the database might work however. Simply have a table that records each instance of a geogebra app being created for a course and use the index of the row to make it unique.
That might be a little more work initially but it is more straightforward than trying to use time() to create unique ids. I've grown wary of using one thing (time()) to accomplish an unrelated task (unique IDs) if the goal is long term stability even though it is a very tempting fix for a quick proof-of-concept.

On further reflection I think this unique id problem might already have been solved, or at least a great deal of progress made toward solving it, in code that already resides in the WW repos.

If you grep through the code base for "uniq" you'll find a lot of other places
where a unique ID is being determined. In particular for the definitions of alias() in PGalias.pm and also PGresource.pm (and also in Chromatic.pm which does use time() ) UUID::Tiny is the CPAN module which we have already included in WW. You might be able to extend alias() directly to handle geogebra applets as it already handles images, html files and so forth.
See what you think.

By the way the random() function in WeBWorK does not use rand or srand to calculate it's pseudorandom string. It uses the simplest algorithm we could find in our number theory book for generating a pseudorandom string. That means that (as long as you use the same seed for the problem) random() is machine independent and you can transfer problems from one machine to another. We originally used time() and rand() to generate random numbers but that caused issues later with debugging on different machines (my Mac and Arnie's PC :-) )

Another option: https://metacpan.org/pod/Data::Uniqid which might have features that UUID::Tiny does not have. I'm sure that there are more cpan modules addressing this issue.




In reply to Michael Gage

Re: time to the microsecond

by Alex Jordan -
Hi Mike,

I agree that it is not a good idea to build in hacks that use some tool for some purpose other than its intended purpose. For now I am sticking with using the gettimeofday function from Time::HiRes, but I'm using it in a modular way so that a better method to produce a unique id can be swapped in. I'll need to do that before I submit the library in a PR, since I don't want to mess with PGcore.pl for a hack like this.

You mentioned solving the unique id problem for images. This reminds me of something I have wanted to bring to your attention for a while now, but keep getting distracted from. See my next post.

Alex
In reply to Alex Jordan

Re: time to the microsecond

by Michael Gage -
Two comments about this.

1. This method of determining a UUID is not time persistent. If the same problem and geogebra applet are run again the UUID will change. I don't know whether this would cause problems. It's something I tried to avoid in labeling images, including on the fly graphics. In the case that these items are cached you could end up with multiple copies that differ only in the label. For graphs this was not always a good idea.

2. I think you should extend alias() to refer to the geogebra applet. Alias() is designed to reduce the load on the problem writer from remembering exactly where the auxiliary file is located, but it also records the auxiliary file path so that eventually we can automatically include all auxiliary files associated with the pg file. (In this case it might be a link to a geogebra applet in the cloud.)

I've been trying to reproduce what I see in the ActiveCalculus example. So far I haven't been able to get the same effect from tests of different graphs in HTML pages. Can you send me a sample of the PTX that produces that page?