Timing a shell script

Given that perl -e ‘use Time::HiRes qw(time); print time’ returns the system time in seconds, but to many significant figures of precision (microseconds I think) [system time is the time in microseconds since December 31, 1969 at 11:59:59 PM GMT], would this be a reasonable way to time a shell script from within an AppleScript anyone? I should comment that I don’t know perl, I just looked it up.


set ProcTime to "perl -e 'use Time::HiRes qw(time); print time'"
set t1 to 0
set t2 to 0
set N to current date
set SysTime0 to N - (do shell script ProcTime) - (time to GMT)
-- should be "Wednesday, December 31, 1969 11:59:59 PM"
repeat 10 times -- get the pumps primed
	do shell script ProcTime
end repeat
repeat 100 times -- do the test for no shell call (the latency in doing a shell script??)
	set t to do shell script ProcTime
	set t1 to t1 + (do shell script ProcTime) - t
end repeat
repeat 100 times
	set t to do shell script ProcTime
	do shell script "ls -AFR ~/Documents/" -- in my case nearly 3 MB of docs in lots of folders
	set t2 to t2 + (do shell script ProcTime) - t
end repeat
{t1 / 100, t2 / 100, (t2 - t1) / 100}
--> {0.030225086212, 0.502110207081, 0.471885120869}

i.e. recursively listing my documents folder takes about half a second

awesome :slight_smile:

Unfortunately I get an error message in line 5:
Can’t make “1166308341.16978” into type number or date.

Strange. I get the same date with or without parentheses around (current date)

Try this instead - it’s one second later:


set N to (current date)
set SysTime0 to N - (do shell script ProcTime) div 1 - (time to GMT)
--> date "Thursday, January 1, 1970 12:00:00 AM"

Also, I’m not thrilled with the result for GetMilliSec as the tested function: I get a negative time. Hmmm.
{0.030434930325, 0.030301787853, -1.33142471313477E-4}

Hi Adam,

I was just trying something like this a while back with python. Unfortunately, even when there is no delay between the time calls, the script returns about one tenth of a second delay. I think if you use this for testing you need to call a script from the same shell script instead of making two calls.


set t to "import time
print time.time()
"
set new_t to (ReplaceText(return, ASCII character 10, t))
set t_py to quoted form of new_t
set x to do shell script "/usr/bin/python -c " & t_py
--delay 5
set y to do shell script "/usr/bin/python -c " & t_py
set d to (y as number) - (x as number)
set c to (round (d * 100) rounding down) / 100
{x, y, d, c}
--
-- search, replace, the text t
on ReplaceText(s, r, t)
	set dtid to AppleScript's text item delimiters
	set AppleScript's text item delimiters to {s}
	set t_list to text items of t
	set AppleScript's text item delimiters to {r}
	set new_t to t_list as string
	set AppleScript's text item delimiters to dtid
	return new_t
end ReplaceText

→ {“1166309414.41”, “1166309414.5”, 0.089999914169, 0.08}

gl,

my result of the perl script is class Unicode text, not date

Another possiblity Stefan - you’re using commas to delineate decimals and perl returns a decimal point.

Adam, I get it with

set SysTime0 to N - ((do shell script ProcTime) as real) - (time to GMT)

strangely “…as number” doesn’t work either, maybe I should test a clean environment without any scripting additions

Edit: the different international format settings is the crucial point. With US settings everything works fine

When I do this:


set t to "python -c 'import time; print time.time(); print time.time()'"
do shell script t

there is no delay. But how would you call a script between the timing calls?

I’ve asked that very question on another board, Kel.

What you need is a shell script that:

python -c 'import time; print time.time(); print time.time() to a variable x instead of print
do the tested shell function [like ls -R somethingbig]
python -c 'import time; print time.time(); print time.time() (as a value) - x

then run that as a do shell script

I don’t know how to do that.

This is a bit of a hotch-potch, but it might be somewhere to start. I’m not sure how accurate it is. According to my timings, the shell script takes about 0.6 to 0.7 seconds longer to run than the interval between the two times in its result. That’s quite a difference. :confused: Perhaps it’s because the result also contains 100 complete listings of my Documents folder!

local r -- I think this will avoid an "Out of memory" error.

set shellString to "ls -AFR ~/Documents/ "

set r to (do shell script "python -c 'import time; print time.time()' ;  for ((n=0; n<100; n++)); do " & shellString & " ; done ; python -c 'import time ; print time.time()'")

-- Get the times from the result and replace their decimal points
-- with the local version so that they can be coerced to number.
set t to first paragraph of r & return & last paragraph of r
set astid to AppleScript's text item delimiters
set AppleScript's text item delimiters to "."
set t to t's text items
set AppleScript's text item delimiters to character 2 of (0.0 as Unicode text)
set t to t as Unicode text
set AppleScript's text item delimiters to astid

((paragraph -1 of t) - (paragraph 1 of t)) / 100
-- 0.03870000124

It may be possible to use the Perl code instead, but this would need to insert a paragraph ending after the first ‘print time’ result.

Edit: In view of Stefan’s point about the decimal points, I’ve added some code that replaces them in the result with the local version before the coercions to number.

Nigel, I still get out of memory

Here is what I was playing with
There will always be some timing differences, depending on what else your system is doing.
And also the tests without delay; to see the result you have to run the script, the script take some time…

set biglist to {}
repeat 10 times
	set a to "`perl -e 'use Time::HiRes qw(time); print time'`"
	set a to (do shell script "echo " & a)
	delay 10
	--do shell script "ls -AFR ~/Documents/"
	set b to "`perl -e 'use Time::HiRes qw(time); print time'`"
	set b to (do shell script "echo " & b)
	set c to a - b
	copy {a, b, c} to end of biglist
end repeat
biglist

Hi Nigel,

I didn’t think making two python calls would return two values!


do shell script "python -c 'import time; print time.time()'; python -c 'import time; print time.time()'"

→ “1166321048.88
1166321048.97”

I wonder where that extra tenth of a second comes from. Anyway, I tried it the variable way after reading a lot on the internet and still lose about a tenth of a second:


set t to "m=$(python -c 'import time; print time.time()'); n=$(python -c 'import time; print time.time()'); echo $m
$n"
set {t1, t2} to paragraphs of (do shell script t)
set {t1, t2} to {t1 as number, t2 as number}
t2 - t1

→ 0.100000143051
Here’s the one with the added osascript delay:


set t to "m=$(python -c 'import time; print time.time()'); osascript -e \"delay 5\"; n=$(python -c 'import time; print time.time()'); echo $m
$n"
set {t1, t2} to paragraphs of (do shell script t)
set {t1, t2} to {t1 as number, t2 as number}
t2 - t1

→ 0.080000162125

At least I learned how to set variable in unix. :slight_smile:

Thanks,

Hi, Mark and Kel.

Thanks for the feedback. Taking Stefan’s earlier point on board, I’ve just edited my script to ensure that people who use comma decimal points are able to use it.

The result of the shell script contains the start time, the massed results of 100 executions of the tested code, and the end time. I’m not surprised that anyone gets “Out of memory” errors! Maybe someone can come up with a way not to return the tested code’s results, while keeping the timings the same…

My Documents folder is very small right now, so I tried doing my entire home directory; This caused the script to throw an “Out of memory” error. However, redirecting the ls output seems to solve the problem (for me, at least).

set shellString to "ls -AFR ~ > /dev/null"

Yep, Bruce’s solution improves it greatly:

set shellScript to "ls -AFR ~/Documents/"

set wholeShellScript to "echo `python -c 'import time; print time.time()'`; " & shellScript & " > /dev/null; echo `python -c 'import time; print time.time()'`"
tell (do shell script wholeShellScript) to return (paragraph 2) - (paragraph 1)

But I guess if we are getting “out of memory” errors, it must be time to ask Adam what the script is for. I mean, you obviously want to use the result of the ls command, and don’t just want to redirect it to /dev/null and find out the time taken. We want this thing to actually work for you! :slight_smile:

Maybe you could redirect the result to a temporary file and then read it using vanilla AppleScript commands.

In this version, the shell script itself returns the running time and nothing else. It turns out that setting a kel variable to the return from the tested code prevents the return from being added to the output. :slight_smile: (I don’t know if this helps with the “Out of memory” error and I’m sure there are better ways “ maybe Bruce’s /dev/null idea.) This adds about 0.002 seconds to the running time returned, which probably isn’t too bad.

Edit: Yes. /dev/null is better than my “set a variable” idea. It hardly affects the timing at all. I’ve modified this script accordingly. Thanks, Bruce!

local r -- I think this will avoid an "Out of memory" error.

set shellString to "ls -AFR ~/Documents/ "

set r to (do shell script "m=$(python -c 'import time; print time.time()') ;  for ((n=0; n<100; n++)); do " & shellString & " > /dev/null ; done ; n=$(python -c 'import time ; print time.time()'); echo \"scale=4 ; ($n-$m)/100\" | bc")

-- Replace the decimal point in the result with the local version.
set astid to AppleScript's text item delimiters
set AppleScript's text item delimiters to "."
set t to r's text items
set AppleScript's text item delimiters to character 2 of (0.0 as Unicode text)
set t to t as Unicode text
set AppleScript's text item delimiters to astid

t as number

Sheesh; I missed all the fun here in the last while.

Querty asked what I was about here, and my “mission”, if I can call it that, was to try to get a reliable estimate of what the latency in a do shell script call actually is.

Cameron Hayne, over at MacOSXhints.com has provided a perl script for timing multiple runs of a shell script. I’ll play with it tomorrow and see how it works. One thing for others who might want to “play” with it - perl scripts MUST have newline endings so copy this to an app where you can make sure that happens. His last post explains usage.

I had in mind something like this: (Using Nigel’s “Lotsa” form)


lotsa(500)

on lotsa(many)
	-- Any other preliminary values here.
	
	-- Dummy loop to absorb a small observed
	-- time handicap in the first repeat.
	repeat many times
	end repeat
	
	-- Test 1.
	set t to GetMilliSec
	repeat many times
		-- First test code or handler call here.
		-- Plain vanilla AppleScript
	end repeat
	set t1 to ((GetMilliSec) - t) / many
	
	-- Test 2.
	set t to GetMilliSec
	repeat many times
		-- Second test code or handler call here.
		-- using a shell script call
	end repeat
	set t2 to ((GetMilliSec) - t) / many
	
	-- Test 3
	-- Insert code to run shell function
	-- tester here, then back out shell
	-- overhead.
	
	-- Timings.
	return {t1, t2, t1 / t2, t2 / t3}
end lotsa

Hi everybody,

I finally figured out how to time AppleScript statements from within a Python script! I used the timing and os modules. I kind of went off on a tangent, but if antone wants to see it:


set t to "import os; import timing; timing.start(); os.popen('osascript -e \"delay 5\"'); timing.finish(); print timing.milli()"
set c to "python -c " & quoted form of t
do shell script c

→ “5847” milliseconds

It’s off by eight tenths, but that’s ok. :slight_smile: I guess it’s something with the compiler.

Now back to reading this thread.

gl,

/dev/null did turn out to be better for my script. But playing with Kel’s Python timer script, which returns integer text values, has also made me realise that there’s no need to use TIDs to adjust the decimal point character. The Python ‘time.time()’ results apparently have a resolution of a hundredth of a second. Using ‘bc’ to multiply their difference by 100 returns a whole-number text that ends with “.00”. AppleScript can easily strip the last three digits from this and coerce the remaining integer representation to a number, regardless of the user’s number preferences.

set shellString to "ls -AFR ~/Documents/ "

set r to (do shell script "m=$(python -c 'import time; print time.time()') ;  for ((n=0; n<100; n++)); do " & shellString & " > /dev/null ; done ; n=$(python -c 'import time ; print time.time()'); echo \"scale = 2 ;($n-$m)*100\" | bc")

-- Strip the ."00" from the whole-number text,
-- divide by 100 to compensate for the multiplication to whole number,
-- and divide by another 100 for the average repeat iteration timing.
set t to (text 1 thru -4 of r) / 10000

Using Kel’s Python timer instead gives this:

set shellString to "ls -AFR ~/Documents/ "

set r to (do shell script "python -c " & quoted form of ("import os; import timing; timing.start(); os.popen(' for ((n=0; n<100; n++)); do " & shellString & " > /dev/null ; done'); timing.finish(); print timing.milli()"))

-- Divide by 1000 to convert milliseconds to seconds
-- and by 100 for the average repeat iteration timing.
set t to r / 100000

This gives a slightly lower result, which I suspect may be more accurate. It also has greater precision. Thanks, Kel!

Hi Nigel,

You’re welcome.

You folks lost me around the /dev/null/ thing.

Anyway, I ended up with this script which returns the results also. Ended up using time.time() because Adam wanted the decimals I think.


set py_text to "import time, os
cmd = 'ls -R ~/'
start = time.time()
names = os.popen(cmd).read()
finish = time.time()
print finish - start
print names"
set py_text to ReplaceText(py_text, return, ASCII character 10)
set c to "python -c " & quoted form of py_text
set py_out to do shell script c
tell paragraphs of py_out
	set {t, file_list} to {item 1, rest}
end tell
--
on ReplaceText(t, s, r)
	set user_tid to AppleScript's text item delimiters
	set AppleScript's text item delimiters to s
	set temp_list to text items of t
	set AppleScript's text item delimiters to r
	set new_t to temp_list as string
	set AppleScript's text item delimiters to user_tid
	return new_t
end ReplaceText

If anyone sees iimprovements, please say so.

Thanks to everybody also.

goodnight (or morning?),