The macosxhints Forums

The macosxhints Forums (http://hintsforums.macworld.com/index.php)
-   Applications (http://hintsforums.macworld.com/forumdisplay.php?f=5)
-   -   AppleScript to display Safari's memory usage (http://hintsforums.macworld.com/showthread.php?t=64782)

hayne 12-14-2006 07:01 AM

AppleScript to display Safari's memory usage
 
As many of you will have noticed, Safari often seems to get itself into a state where it starts to take a lot of memory. I'm not yet convinced that this is technically a "memory leak" (where an app has lost track of memory that it allocated) since it might just be that Safari is misguidedly caching too much info about previously viewed pages or whatever.

But it certainly is the case that at times Safari starts to take much more of my precious RAM than I want it to. I don't mind too much if it is taking 100 MB if I've been looking at graphically-rich pages, but I expect it to be a lot less than that most of the time. And if I notice that Safari is taking up much more than 100 MB of real memory (e.g. by looking at Activity Monitor), I will quit and re-launch it in order to conserve RAM.

This post presents an AppleScript that monitors the RAM usage of Safari and displays it (in megabytes) in the title of the frontmost window.
The AppleScript pops up an alert dialog if the Safari memory usage exceeds 150 MB.
There are two versions of this AppleScript:
  • one that you compile (in Script Editor) as an application and run by double-clicking (I'll call this VersionA)
  • one that is embedded in a Perl script and which you run from the command-line in a Terminal window (I'll call this VersionP)
The advantage of the first version is that it doesn't require a visit to the Terminal.
However I noticed that the first version takes up more than 10 MB of RAM all the time it is running.
The second version takes up far less RAM - less than 1 MB of RAM most of the time and 2 or 3 MB of RAM during the short times that the embedded AppleScript runs (every 10 seconds).
Neither version takes significant amounts of CPU - the first version averages about 0.2 % of the CPU on my iBook 1.2 GHz G4. The second version takes too small a percentage of CPU to measure.

Of course you would only use one of these two versions at a time.

Here's the VersionA script:
Code:

-- This AppleScript displays the amount of memory being used by Safari
-- in the title of the frontmost Safari window.
-- It is intended to be saved as an application
-- and left running all of the time.
-- Cameron Hayne (macdev@hayne.net)  December 2006

-- getProcessMB:
-- Returns the number of megabytes used by the specified process.
-- It gets this value by invoking the Unix 'ps' command via a Perl script.
on getProcessMB(procName)
        set perlCode to "
# This script gets the 'RSS' of the process specified as 'procName'
# The 'RSS' (resident set size) is a rough measure of how much RAM
# the process is using. The value is given in megabytes.
open(PS, \"/bin/ps -xc -o command,rss |\");
while (<PS>)
{
    if (/^\\s*" & procName & "\\s+(\\d+)\\s*$/o)
    {
        my $rss = $1;
        my $rssMB = sprintf(\"%.1f\", $rss / 1024);
        print $rssMB;
        last;
    }
}
close(PS);
"
        set numMB to do shell script "perl -e " & quoted form of perlCode
        return numMB as real
end getProcessMB

-- removeLastPart:
-- Returns the string 'str' without the last part starting with 'delim'
on removeLastPart(str, delim)
        set saveDelim to AppleScript's text item delimiters
        set AppleScript's text item delimiters to delim
        if (count str's text items) > 1 then
                set str to str's text 1 thru text item -2
        end if
        set AppleScript's text item delimiters to saveDelim
        return str
end removeLastPart

-- appIsRunning:
-- Returns true if the app 'appName' is running, otherwise it returns false
on appIsRunning(appName)
        set isRunning to false
        tell application "System Events"
                if (count (every process whose name is appName)) > 0 then
                        set isRunning to true
                end if
        end tell
        return isRunning
end appIsRunning

-- showAppSizeInTitle:
-- Displays the amount of memory being used by the app 'appName' in the title
-- of the frontmost window of that app.
-- It pops up an alert dialog if the amount of memory used
-- is greater than 'warnMB' (megabytes).               
on showAppSizeInTitle(appName, warnMB)
        if my appIsRunning(appName) then
                tell application appName
                        set title to name of window 1
                        set delim to " *** "
                        set origTitle to my removeLastPart(title, delim)
                        set numMB to my getProcessMB(appName)
                        set title to origTitle & delim & numMB & " MB"
                        set name of window 1 to title
                       
                        if numMB is greater than warnMB then
                                display alert appName & " is taking more than " & warnMB & " MB"
                        end if
                end tell
        end if
end showAppSizeInTitle

on idle
        showAppSizeInTitle("Safari", 150)
        return 10 -- updates every 10 seconds
end idle

To use the above version, copy & paste the code to Script Editor, then Save as an application bundle (.app). Then double-click to launch the app as with any other application.

Here's the VersionP script:
Code:

#!/usr/bin/perl

# safariSizeInTitle:
# This script displays the amount of memory being used by Safari
# in the title of the frontmost Safari window.
# It will pop up an alert dialog if Safari's memory consumption exceeds $warnMB
# It is intended to be left running all of the time in a Terminal window.
# You can put it in the background if you want.
#
# Cameron Hayne (macdev@hayne.net)  December 2006

use strict;
use warnings;

my $appName = "Safari";
my $warnMB = 150; # memory usage (in megabytes) at which an alert is given
my $period = 10;  # time (in seconds) between updates

# function declarations:
sub runAppleScript($$);
sub getProcessMB($);
sub showAppSizeInTitle($$);

MAIN:
{
    while (1)
    {
        showAppSizeInTitle($appName, $warnMB);
        sleep $period;
    }
}

# runAppleScript: Runs the supplied AppleScript
#                The arguments to this function are:
#                - the text of the AppleScript
#                - the command-line args to be passed to the AppleScript
sub runAppleScript($$)
{
    my ($ascript, $args) = @_;

    my $result = `/usr/bin/osascript - $args<<"    EOT"
    $ascript
    EOT
    `;
    chomp($result);
    return $result;
}

# getProcessMB: returns the number of megabytes used by the specified process
#              returns 'undef' if there is no such process
sub getProcessMB($)
{
    my ($procName) = @_;

    my $rssMB;
    my $psCmd = "/bin/ps -xc -o command,rss";
    open(PS, "$psCmd |") or die "Can't run command \"$psCmd\": $!\n";
    while (<PS>)
    {
        if (/^\s*$procName\s+(\d+)\s*$/o)
        {
            my $rss = $1;
            $rssMB = sprintf("%.1f", $rss / 1024);
            last;
        }
    }
    close(PS);

    return $rssMB;
}

# showAppSizeInTitle:
# Displays the amount of memory being used by the app 'appName' in the title
# of the frontmost window of that app.
# It pops up an alert dialog if the amount of memory used
# is greater than 'warnMB' (megabytes).               
sub showAppSizeInTitle($$)
{
    my ($appName, $warnMB) = @_;

    my $numMB = getProcessMB($appName);
    return unless defined($numMB);

    my $applescript = <<"    EOT";
    -- removeLastPart:
    -- Returns the string 'str' without the last part starting with 'delim'
    on removeLastPart(str, delim)
        set saveDelim to AppleScript's text item delimiters
        set AppleScript's text item delimiters to delim
        if (count str's text items) > 1 then
            set str to str's text 1 thru text item -2
        end if
        set AppleScript's text item delimiters to saveDelim
        return str
    end removeLastPart

    tell application "$appName"
        set title to name of window 1
        set delim to " *** "
        set origTitle to my removeLastPart(title, delim)
        set title to origTitle & delim & $numMB & " MB"
        set name of window 1 to title

        if $numMB is greater than $warnMB then
            display alert "$appName is taking more than $warnMB MB"
        end if
    end tell
    EOT

    runAppleScript($applescript, "");
}

To use this version, copy & paste the code into a plain text file, being sure that the line-endings are set to Unix, make that file executable with 'chmod +x', and then run it by typing the name of the script file on the Terminal command line. See the "running scripts" section of this Unix FAQ for details.

I'm interested in hearing what others think of this script (either version) since I will probably submit this as a "hint" on the main macosxhints site and it is best to get the bugs out ahead of time. Of course any suggestions for improvement (either of functionality or implementation) are welcome.

kainewynd2 12-14-2006 11:18 AM

A simple copy paste of the first code to Applescript doesn't seem to accomplish anything - no errors and no response.

The perl script gives the following output when attempting to run:
2006-12-14 10:16:30.034 osascript[576] CFLog (21): dyld returns 2 when trying to load /Users/mjw1/Library/ScriptingAdditions/24U Appearance OSAX.osax/Contents/MacOS/24U Appearance OSAX
2006-12-14 10:16:30.052 osascript[576] CFLog (21): dyld returns 2 when trying to load /Users/mjw1/Library/ScriptingAdditions/24U Appearance OSAX.osax/Contents/MacOS/24U Appearance OSAX
2006-12-14 10:16:40.296 osascript[579] CFLog (21): dyld returns 2 when trying to load /Users/mjw1/Library/ScriptingAdditions/24U Appearance OSAX.osax/Contents/MacOS/24U Appearance OSAX
2006-12-14 10:16:40.313 osascript[579] CFLog (21): dyld returns 2 when trying to load /Users/mjw1/Library/ScriptingAdditions/24U Appearance OSAX.osax/Contents/MacOS/24U Appearance OSAX
2006-12-14 10:16:50.554 osascript[582] CFLog (21): dyld returns 2 when trying to load /Users/mjw1/Library/ScriptingAdditions/24U Appearance OSAX.osax/Contents/MacOS/24U Appearance OSAX
2006-12-14 10:16:50.569 osascript[582] CFLog (21): dyld returns 2 when trying to load /Users/mjw1/Library/ScriptingAdditions/24U Appearance OSAX.osax/Contents/MacOS/24U Appearance OSAX
2006-12-14 10:17:00.841 osascript[586] CFLog (21): dyld returns 2 when trying to load /Users/mjw1/Library/ScriptingAdditions/24U Appearance OSAX.osax/Contents/MacOS/24U Appearance OSAX
2006-12-14 10:17:00.859 osascript[586] CFLog (21): dyld returns 2 when trying to load /Users/mjw1/Library/ScriptingAdditions/24U Appearance OSAX.osax/Contents/MacOS/24U Appearance OSAX

I canceled the process after these many popped up. Is that by design?

NovaScotian 12-14-2006 11:46 AM

The first script fails on mm with the message: "Result of numeric operation was too large" - I think because of a divide by zero somewhere, possibly.

As I go through the handlers, on mm the first: "getProcessMB" produces a null result. The AppleScript below works for me in its place (I haven't examined the others yet):

to getProcMB(procName) -- as string
try
return ((last word of (do shell script "/bin/ps -xc -o command,rss | grep " & procName)) as integer) / 1024 -- result of a division will be real
on error
display dialog "The targeted process is not running"
end try
end getProcMB

getProcMB("Safari")

bramley 12-14-2006 11:55 AM

Quote:

Originally Posted by kainewynd2 (Post 341858)
A simple copy paste of the first code to Applescript doesn't seem to accomplish anything - no errors and no response.

Hayne should have added that when saving as app to check the "stay open" checkbox in the save dialog. Note you cannot run the script from the editor.
Quote:

Originally Posted by kainewynd2 (Post 341858)
I canceled the process after these many popped up. Is that by design?

The message means that you have got the 24UAppearance Scripting Addition on your machine and it there is a problem with loading it i.e. the message is not relevant to the script - it is a general problem with Applescript. The scripting addition is either missing or corrupt. If you need it I'd re-install it.

I've not had it crash on me - yet. Will run it for a while.

NovaScotian 12-14-2006 12:09 PM

With respect to appIsRunning, I would have done this:

on isRunning(appName)
tell application "System Events" to if exists process appName then return true
return false
end isRunning

isRunning("Safari")

NovaScotian 12-14-2006 12:36 PM

My final version (which runs on my machine)

Code:

on showAppSizeInTitle(appName, warnMB)
        if my isRunning(appName) then
                set MB to my getProcMB(appName)
                tell application appName
                        set dlm to " *** "
                        set title to name of window 1
                        set Orig to my removeLastPart(title, dlm)
                        set name of window 1 to Orig & dlm & MB
                        if MB > warnMB then display alert appName & " is taking more than " & warnMB & " MB"
                end tell
        end if
end showAppSizeInTitle

on isRunning(appName)
        tell application "System Events" to if exists process appName then return true
        return false
end isRunning

to getProcMB(procName) -- as string
        try
                return ((last word of (do shell script "/bin/ps -xc -o command,rss | grep " & procName)) as integer) / 1024 -- result of a division will be real
        end try
end getProcMB

on removeLastPart(str, delim)
        set tid to AppleScript's text item delimiters
        set AppleScript's text item delimiters to delim
        if (count str's text items) > 1 then set str to str's text item 1
        set AppleScript's text item delimiters to tid
        return str
end removeLastPart

--on idle
showAppSizeInTitle("Safari", 50)
--return 60
-- end idle


bramley 12-14-2006 12:58 PM

And mine is ...
Code:

-- This AppleScript displays the amount of memory being used by Safari
-- in the title of the frontmost Safari window.
-- It is intended to be saved as an application
-- and left running all of the time.
-- Cameron Hayne (macdev@hayne.net)  December 2006

-- getProcessMB:
-- Returns the number of megabytes used by the specified process.
-- It gets this value by invoking the Unix 'ps' command via a Perl script.
on getProcessMB(procName)
        set perlCode to "
# This script gets the 'RSS' of the process specified as 'procName'
# The 'RSS' (resident set size) is a rough measure of how much RAM
# the process is using. The value is given in megabytes.
open(PS, \"/bin/ps -xc -o command,rss |\");
while (<PS>)
{
    if (/^\\s*" & procName & "\\s+(\\d+)\\s*$/o)
    {
        my $rss = $1;
        my $rssMB = sprintf(\"%.1f\", $rss / 1024);
        print $rssMB;
        last;
    }
}
close(PS);
"
        set numMB to do shell script "perl -e " & quoted form of perlCode
        return numMB as real
end getProcessMB

-- removeLastPart:
-- Returns the string 'str' without the last part starting with 'delim'
on removeLastPart(str, delim)
        set saveDelim to AppleScript's text item delimiters
        set AppleScript's text item delimiters to delim
        if (count str's text items) > 1 then
                set str to str's text 1 thru text item -2
        end if
        set AppleScript's text item delimiters to saveDelim
        return str
end removeLastPart

-- appIsRunning:
-- Returns true if the app 'appName' is running, otherwise it returns false
on appIsRunning(appName)
        tell application "System Events" to return (exists process appName)
end appIsRunning

-- showAppSizeInTitle:
-- Displays the amount of memory being used by the app 'appName' in the title
-- of the frontmost window of that app.
-- It pops up an alert dialog if the amount of memory used
-- is greater than 'warnMB' (megabytes).               
on showAppSizeInTitle(appName, warnMB)
        if my appIsRunning(appName) then
                tell application appName
                        if (count of windows) > 0 then
                                set title to name of window 1
                                set delim to " *** "
                                set origTitle to my removeLastPart(title, delim)
                                set numMB to my getProcessMB(appName)
                                set title to origTitle & delim & numMB & " MB"
                                set name of window 1 to title
                               
                                if numMB is greater than warnMB then
                                        display alert appName & " is taking more than " & warnMB & " MB"
                                end if
                        end if
                end tell
        end if
end showAppSizeInTitle

on idle
        my showAppSizeInTitle("Safari", 150)
        return 10 -- updates every 10 seconds
end idle

I've used Hayne's original code cos it works here - and I'm still reading the man page for 'ps'!

Note script crashes if you accidentally close all windows - so I've added in a 'count the windows first' check above.

Interesting that when started here (without a homepage) Safari uses 48.6 MB, and takes up more memory thereafter. Resetting the cache drops this down. So Safari is storing at least some component of the cache in RAM - so is Apple doing this for performance reasons or because it's a bug?

One could have the script auto-quit the app and then re-launch it. Or clear the cache - there is a keyboard shortcut that could be called from Applescript - although this means clearing the whole cache, and may not be ideal.

NovaScotian 12-14-2006 01:39 PM

You can avoid the crash by substituting this for my isRunning:

Code:

on isRunningHasWindow(appName)
        tell application "System Events" to if exists process appName then
                tell application appName
                        try
                                get name of window 1
                                return true
                        on error
                                return false
                        end try
                end tell
        end if
        return false
end isRunningHasWindow


hayne 12-14-2006 02:48 PM

Quote:

Originally Posted by NovaScotian (Post 341865)
The first script fails on mm with the message: "Result of numeric operation was too large" - I think because of a divide by zero somewhere, possibly.

Hmm - I'd be very interested if you could track this down more.
I (obviously) don't see this in my tests. I don't see anywhere that I could be dividing by zero.

Quote:

As I go through the handlers, on mm the first: "getProcessMB" produces a null result.
It would do that if the process ("Safari") was not found in the results of the 'ps' command - e.g. if it was not running.

Maybe you could try the following stand-alone Perl script to see if you get similar problems.
Sample usage:
getProcessRss Safari

Code:

#!/usr/bin/perl

# getProcessRss:
# This script gets the 'RSS' of the process specified as a command-line arg.
# The 'RSS' (resident set size) is a rough measure of how much RAM
# the process is using.
# Example of use:  getProcessRss Safari
#
# Cameron Hayne (macdev@hayne.net)  December 2006

use warnings;
use strict;

die "Usage: getProcessRss: name_of_process\n" unless scalar(@ARGV) == 1;
my $procName = $ARGV[0];

my $psCmd = "/bin/ps -xc -o command,rss";

open(PS, "$psCmd |") or die "Can't run command \"$psCmd\": $!\n";
while (<PS>)
{
    if (/^\s*$procName\s+(\d+)\s*$/o)
    {
        my $rss = $1;
        my $rssMB = sprintf("%.1f", $rss / 1024);
        print "$rssMB MB\n";
        last;
    }
}
close(PS);

Quote:

The AppleScript below works for me in its place (I haven't examined the others yet):

to getProcMB(procName) -- as string
try
return ((last word of (do shell script "/bin/ps -xc -o command,rss | grep " & procName)) as integer) / 1024 -- result of a division will be real
on error
display dialog "The targeted process is not running"
end try
end getProcMB

getProcMB("Safari")
The problem with your version is that it will screw up if there happens to be more than one app running whose name includes the specified 'procName'.
There is usually only one app running whose name includes the string "Safari" so it will usually work in this case.
But it will fail if you tried it with "iTunes" since there is also an "iTunes Helper" app running.

hayne 12-14-2006 02:53 PM

Quote:

Originally Posted by bramley (Post 341869)
Hayne should have added that when saving as app to check the "stay open" checkbox in the save dialog. Note you cannot run the script from the editor.

Thanks - I edited my post above to add these notes.

hayne 12-14-2006 03:10 PM

Here's a revised version of the "VersionA" script (the AppleScript application) that integrates the improvements of NovaScotian and bramley:
Code:

-- This AppleScript displays the amount of memory being used by Safari
-- in the title of the frontmost Safari window.
-- It is intended to be saved as an application
-- and left running all of the time.
-- Cameron Hayne (macdev@hayne.net)  December 2006

-- getProcessMB:
-- Returns the number of megabytes used by the specified process.
-- It gets this value by invoking the Unix 'ps' command via a Perl script.
on getProcessMB(procName)
    set perlCode to "
# This script gets the 'RSS' of the process specified as 'procName'
# The 'RSS' (resident set size) is a rough measure of how much RAM
# the process is using. The value is given in megabytes.
open(PS, \"/bin/ps -xc -o command,rss |\");
while (<PS>)
{
    if (/^\\s*" & procName & "\\s+(\\d+)\\s*$/o)
    {
        my $rss = $1;
        my $rssMB = sprintf(\"%.1f\", $rss / 1024);
        print $rssMB;
        last;
    }
}
close(PS);
"
    set numMB to do shell script "perl -e " & quoted form of perlCode
    return numMB as real
end getProcessMB

-- removeLastPart:
-- Returns the string 'str' without the last part starting with 'delim'
on removeLastPart(str, delim)
    set saveDelim to AppleScript's text item delimiters
    set AppleScript's text item delimiters to delim
    if (count str's text items) > 1 then
        set str to str's text 1 thru text item -2
    end if
    set AppleScript's text item delimiters to saveDelim
    return str
end removeLastPart

-- appIsRunning:
-- Returns true if the app 'appName' is running, otherwise returns false
on appIsRunning(appName)
    set isRunning to false
    tell application "System Events"
        if exists process appName then
            set isRunning to true
        end if
    end tell
    return isRunning
end appIsRunning

-- showAppSizeInTitle:
-- Displays the amount of memory being used by the app 'appName' in the title
-- of the frontmost window of that app.
-- It pops up an alert dialog if the amount of memory used
-- is greater than 'warnMB' (megabytes).               
on showAppSizeInTitle(appName, warnMB)
    if my appIsRunning(appName) then
        tell application appName
            set numMB to my getProcessMB(appName)
            if (count of windows) > 0 then
                set title to name of window 1
                set delim to " *** "
                set origTitle to my removeLastPart(title, delim)
                set title to origTitle & delim & numMB & " MB"
                set name of window 1 to title
            end if
            if numMB is greater than warnMB then
                display alert appName & " is taking more than " & warnMB & " MB"
            end if
        end tell
    end if
end showAppSizeInTitle

on idle
    showAppSizeInTitle("Safari", 150)
    return 10 -- updates every 10 seconds
end idle

I only put the title-changing inside the if-statement that checks for windows existing since I want it to pop up the alert even if there are no windows.
But I haven't tested that this alert works if there are no windows - I'm just assuming it does.

By the way, I kept the 'appIsRunning' subroutine in the more verbose form since it is easier to generalize it starting from that form. E.g. if I wanted later to add another condition on the app.

Another note: I kept the 'removeLastPart' subroutine using:
set str to str's text 1 thru text item -2
instead of the simpler:
set str to str's text 1
since that way it is more robust in the (hopefully unusual) case where the delimiter (" *** ") happens to occur in the original title.

I'm not sure why, but I now am finding that this AppleScript application is taking about 5 MB of RAM whereas before it was taking more than 10 MB. I'm not sure what caused this difference.

By the way, if I change the script to use the following in place of the 'on idle' handler, I find that it takes about twice as much CPU. It still is not a significant amount of CPU, but it seems to be averaging about 0.4 % CPU with the 'repeat' loop compared with about 0.2 % with the 'on idle'.

Code:

repeat
        showAppSizeInTitle("Safari", 150)
        delay 10 -- updates every 10 seconds
end repeat

(Note that with the 'repeat' loop instead of the 'on idle', it is possible to test the script in Script Editor)

NovaScotian 12-14-2006 05:21 PM

I've tried the new version, Hayne, (and the perl script itself) and neither of them return a result for me.

In my /usr/bin/, I find perl and perl5.8.6. Can that be confusing the issue? I've never run either of them before.

hayne 12-14-2006 05:50 PM

Quote:

Originally Posted by NovaScotian (Post 341960)
I've tried the new version, Hayne, (and the perl script itself) and neither of them return a result for me.

Hmm - very odd.
What model of Mac and what version of OS X do you have?

Could you please run the following command (in a Terminal window) and then show us the results (I'm assuming that you have Safari running):

/bin/ps -xc -o command,rss | grep Safari | vis -w

NovaScotian 12-14-2006 06:00 PM

Quote:

Originally Posted by hayne (Post 341973)
/bin/ps -xc -o command,rss | grep Safari | vis -w

Safari\040\040\040\040\040\040\040\040\040\040\040\04037000\^JACB-G5:~ bellac$

where there was clearly no return after the instruction since ACB-G5:~bellac is my normal prompt.

My machine/system are in the signature (you must turn them off): dual-core G5/2.3 running Mac OS X 10.4.8

hayne 12-14-2006 06:40 PM

Quote:

Originally Posted by NovaScotian (Post 341979)
Safari\040\040\040\040\040\040\040\040\040\040\040\04037000\^JACB-G5:~ bellac$

where there was clearly no return after the instruction since ACB-G5:~bellac is my normal prompt

The ^J is the return.

Could you please run the following modified version of the stand-alone Perl script and show us what you get from:
getProcessRss Safari

Code:

#!/usr/bin/perl

# getProcessRss:
# This script gets the 'RSS' of the process specified as a command-line arg.
# The 'RSS' (resident set size) is a rough measure of how much RAM
# the process is using.
# Example of use:  getProcessRss Safari
#
# Cameron Hayne (macdev@hayne.net)  December 2006

use warnings;
use strict;

die "Usage: getProcessRss: name_of_process\n" unless scalar(@ARGV) == 1;
my $procName = $ARGV[0];
print "Looking for: \"$procName\"\n";

my $psCmd = "/bin/ps -xc -o command,rss";

open(PS, "$psCmd |") or die "Can't run command \"$psCmd\": $!\n";
while (<PS>)
{
    if (/$procName/o)
    {
        print "PS: \"$_\"\n";
    }

    if (/^\s*$procName\s+(\d+)\s*$/o)
    {
        my $rss = $1;
        my $rssMB = sprintf("%.1f", $rss / 1024);
        print "$rssMB MB\n";
        last;
    }
}
close(PS);


NovaScotian 12-14-2006 08:58 PM

I'm the wrong guy to do this Hayne - I'm convinced now that my problem is that I'm doing something wrong in Terminal to get your file running. If you want me to run it you'll have to supply the command line to do it. I've become totally confused and don't even know whether I've rendered your perl script executable properly, and certainly don't know how to run a perl script. perl is usually silent on all the variations I've tried.

Sorry :confused:

trevor 12-14-2006 10:28 PM

NovaScotian:

1. In your browser, select the perl script above, hit command-C to copy it to the clipboard.

2. In your Terminal, type
sudo pico /usr/local/bin/getProcessRss
(this assumes that you have a /usr/local/bin directory. If you don't, you should--create one with sudo mkdir /usr/local/bin and add it to your PATH in your shell configuration file, such as ~/.bash_profile or ~/.bashrc)

3. A simple text editor, pico, will open in your Terminal. Now, hit command-V to paste the script in your clipboard into pico. Now, hit control-X to save, and hit the Y key to confirm, then hit Return. (Note that you are using the Mac's clipboard when pasting, so you use the command key. When you are in pico, you are using a Unix app, so you use the control key.

4. Enter the following command to make the script executable:
sudo chmod 555 /usr/local/bin/getProcessRss

5. Test it as hayne requests.

Trevor

hayne 12-14-2006 10:35 PM

Quote:

Originally Posted by NovaScotian (Post 342057)
I'm the wrong guy to do this Hayne - I'm convinced now that my problem is that I'm doing something wrong in Terminal to get your file running. If you want me to run it you'll have to supply the command line to do it. I've become totally confused and don't even know whether I've rendered your perl script executable properly, and certainly don't know how to run a perl script. perl is usually silent on all the variations I've tried.

Okay, you've saved it in a file, right?
Open that file in TextWrangler (or some other text editor that can tell you about the line endings) and click (once) on the toolbar icon at the top that looks like a little page. This will bring down a menu where the first 3 menu items are: Macintosh, Unix, DOS. You want it to be Unix. Save the file if you had to change this.

Now, go into a Terminal window and navigate to the folder where you saved that script. (See the "navigation" section of this Unix FAQ if you need help with this)
Then type in (or copy & paste from here) the commands:
chmod +x getProcessRss
./getProcessRss Safari
where I have assumed that you saved the script in a file named "getProcessRss".
The first of these commands ('chmod') makes the script executable. The second command runs the script with a command-line argument of "Safari".

NovaScotian 12-15-2006 12:35 AM

Thank you both, gentlemen. The first step on Hayne's agenda was an unknown to me. I use BBEdit, but had never noticed that I had a choice of line endings - in AppleScript that's looked after for you.

I altered your processes slightly, but the result was:
ACB-G5:~/Desktop/Hayne bellac$ ./getProcessRss Safari
Looking for: "Safari"
PS: "Safari 42460
"
41.5 MB

I found it easier (because editing typing in the Terminal is so unnatural for me) to type chmod +x and drag the file icon to the terminal and return (I use iTerm).

Then cd and drag the folder it's in to the Terminal, return again

Then ./getProcessRss Safari -- after opening Safari, but it works equally well for Camino.

mark hunte 12-15-2006 01:33 AM

Just got to This.

The Latest script runs fine, with Safari running or not.
My Safari seems to be running at around 25.X MB.
But the VersionA runs at 14.x MB.

hayne 12-15-2006 01:46 AM

Quote:

Originally Posted by NovaScotian (Post 342134)
The first step on Hayne's agenda was an unknown to me. I use BBEdit, but had never noticed that I had a choice of line endings - in AppleScript that's looked after for you.

I altered your processes slightly, but the result was:
ACB-G5:~/Desktop/Hayne bellac$ ./getProcessRss Safari
Looking for: "Safari"
PS: "Safari 42460
"
41.5 MB

Okay, so the stand-alone Perl script is now working fine for you.
In general, Unix utilities (e.g. Perl) will assume that all text files have the one-true-line-ending ('\n' or "Unix-style) and will silently fail if this is not the case. This is true of the script files (like this Perl script) and the data files they are processing. See the "line endings" section of this Unix FAQ for more info on this.

I suspect that the problem with the Perl script that is embedded within the AppleScript (in the subroutine 'getProcessMB') not working is due to the same sort of problem. I worried a bit about this when I wrote that AppleScript - I wasn't sure that the line-endings on that embedded Perl code were going to be correct. But I typed it into Script Editor and then wrote the embedded script to a file and it all checked out fine.

But I just read today on the AppleScript users mailing list that "Script Editor" and Satimage's "Smile" use different line endings - Smile uses the traditional Macintosh line endings. And I now realize that this might be relevant here.
I'm guessing that maybe NovaScotian used Smile to save the AppleScript and that is why it isn't working for him.

It's a bit nasty having to rely on the behaviour of one editor (Script Editor) to get the embedded line endings correct, but the only alternative I can see is to add in explicit newlines in the embedded Perl code and then string it all together into one unreadable mess of one line in the AppleScript.
I thought it was bad enough that I had to escape all the backslashes and quotes in that embedded Perl!

Bottom line: for the moment, NovaScotian, could you please try copy & pasting the above AppleScript into "Script Editor" instead (assuming that you were using "Smile" before).

hayne 12-15-2006 01:49 AM

Quote:

Originally Posted by mark hunte (Post 342150)
The Latest script runs fine, with Safari running or not.
My Safari seems to be running at around 25.X MB.
But the VersionA runs at 14.x MB.

You mean that the "VersionA" application is taking up 14 MB of RAM?
That's the sort of thing I was seeing last night. But now it is taking much less as I mentioned above - I don't understand why there would be this difference.

mark hunte 12-15-2006 01:54 AM

Quote:

Originally Posted by hayne (Post 342153)
You mean that the "VersionA" application is taking up 14 MB of RAM?
That's the sort of thing I was seeing last night. But now it is taking much less as I mentioned above - I don't understand why there would be this difference.

Thats Correct.

Will Run VersionB shortley

hayne 12-15-2006 01:56 AM

Quote:

Originally Posted by mark hunte (Post 342155)
Will Run VersionB shortley

You didn't read carefully enough - the two versions are called
VersionA (A for AppleScript)
and
VersionP (P for Perl)
:)

mark hunte 12-15-2006 02:26 AM

Doh.. :rolleyes: its late here.. or I should say early (gone 6 in the morning). Have not been bed yet.

I also find that when safari is playing up it uses lots of cpu, this sometimes happens after I have gone into another app just after safari, and at some point things get slow, a lot of beach balling. When I look for the reason why, A lot of the time its Safari in the background.

I do not know how helpful this is but I added cpu to the script.
It rough but seems to work. I put 50% cpu warning but I may lower it when I get a gauge on it.
Code:

on showAppSizeInTitle(appName, warnMB)
        if my appIsRunning(appName) then
                tell application appName
                        set cpu to ""
                        try
                                set cpu to word 2 of (do shell script "top -u -FR -l2 | grep -v 0.0% | grep % | grep -v Load |grep Safari| grep -v COMMAND | cut -c 7-24")
                        end try
                        if cpu is "" then
                                set cpu to "0.0"
                        end if
                        if (count of windows) > 0 then
                                set title to name of window 1
                                set delim to " *** "
                                set origTitle to my removeLastPart(title, delim)
                                set numMB to my getProcessMB(appName)
                                set title to origTitle & delim & numMB & " MB" & "*** " & cpu & "%"
                                set name of window 1 to title
                        end if
                       
                        if numMB is greater than warnMB then
                                display alert appName & " is taking more than " & warnMB & " MB"
                        end if
                        if cpu is greater than "49.0" then
                                display alert appName & " is taking more than " & "50 %cpu"
                        end if
                end tell
        end if
end showAppSizeInTitle

Maybe its worth adding a output file for the warnings.

Url,time, usage

NovaScotian 12-15-2006 11:04 AM

Neither of Hayne's versions work for me, though the perl script above by itself does.

I'm running this using a hot key rather than a idle script:

set tApp to "Safari" -- name of browser
set tLim to 75 -- acceptable memory use in MB
memToTitle(tApp, tLim) -- get title to include memory use
(* Script concept by Cameron Hayne, version by Adam Bell *)

to memToTitle(appName, tooHigh)
tell application "System Events" to if exists process appName then -- it's running
try
tell process appName to set N to name of window 1
set OK to true -- it's running and has a window open
on error
set OK to false -- no window
end try
else
set OK to false -- not running
end if
-- Running with window; get memory estimate to four significant figures
if OK then set MB to (100 * ((last word of (do shell script "/bin/ps -xc -o command,rss | grep " & appName)) / 1024) as integer) / 100
-- Put estimate in window title
tell application appName -- remove previous addition first
set dlim to " @ Mem = " -- something not likely to be found in a title
set tid to AppleScript's text item delimiters
set AppleScript's text item delimiters to dlim
if (count N's text items) > 1 then set N to N's text item 1
set AppleScript's text item delimiters to tid
set name of window 1 to N & dlim & MB & " MB"
if MB > tooHigh then display alert appName & " is consuming more than " & tooHigh & " MB of memory"
end tell
end memToTitle

Note: Unlike Hayne's version, this one doe not trigger a warning if there are no windows. This works in Camino as well.

hayne 12-15-2006 01:49 PM

Quote:

Originally Posted by NovaScotian (Post 342206)
Neither of Hayne's versions work for me, though the perl script above by itself does.

Did you read my post #21 above where I explained my suspicion that the problem you are having with my AppleScript is also due to line-endings?

NovaScotian 12-15-2006 08:28 PM

I had not, Hayne, but just did (I've been out all day). I'm using Script Debugger 4 which may be part of the problem, because in Script Editor, the script with the command below succeeds every time I run it.

Problem solved, apparently. Excellent sleuthing, Hayne.

Code:

--on idle
showAppSizeInTitle("Safari", 150)
--return 10 -- updates every 10 seconds
--end idle

As an addendum, I've just written to LateNightSoftware that SD4 screws up timing comparisons too - for some sorts of script comparisons, the ratio of time taken by some handlers being compared can be off by a factor of 4 to 5 from those given in SE. Curiouser and curiouser.

NovaScotian 12-15-2006 09:09 PM

Last Minute Addition to previous post:

I copied the whole script from Script Editor to a new document in Script Debugger 4 and it works. - Obviously the line ending problem arises from copying from the web page, rather than having the script as an original.

I have found that it also works quite nicely in Camino, but getProcessMB fails when the app is Firefox, and Shiira doesn't know about its windows. It works beautifully in NetNewsWire (which does have a memory leak and grows very slowly if left running). I may modify it slightly to watch both.

Sorry to be so troublesome.

hayne 12-15-2006 09:59 PM

Quote:

Originally Posted by NovaScotian (Post 342315)
I'm using Script Debugger 4 which may be part of the problem, because in Script Editor, the script with the command below succeeds every time I run it.

Code:

--on idle
showAppSizeInTitle("Safari", 150)
--return 10 -- updates every 10 seconds
--end idle


So it would seem that my hypothesis (about the line-endings being an issue due to the use of different AppleScript editors) is verified.

This is an interesting issue for any embedded script (for use with 'do shell script') in AppleScript - a much more general issue than just the particular script under discussion in this thread. It is also an issue if some embedded text in an AppleScript were to be written to a file (using 'write' in AppleScript). See for example, the following AppleScript:
Code:

-- This AppleScript is intended for testing the behaviour of
-- embedded text strings with regard to end-of-line characters.
-- (See the "end-of-line characters" section of the following Unix FAQ:
--  http://forums.macosxhints.com/showthread.php?t=40648 )
--
-- On OS X, it is desirable that text files be saved with Unix-style
-- end-of-line characters.
-- The Unix 'file' command will report such a file to be:
-- "ASCII English text"
-- If, on the otherhand, the file is saved with traditional Mac line-endings,
-- it will be reported to be:
-- "ASCII English text, with CR line terminators"
--
-- Note that without the use of the subroutine 'fixLineEndings', this script
-- gives different results depending on which tool is used to compile it.
-- If the line that invokes 'fixLineEndings' is commented out,
-- using "Script Editor" (v 2.1.1) will give Unix line-endings
-- while Satimage's "Smile" (v 3.1.9) will give traditional Mac line-endings
--
-- Cameron Hayne (macdev@hayne.net)  December 2006

-- asks the user to specify a filename for saving
on chooseFileForSaving(defaultName)
        set savePrompt to "Save file as:"
        set filename to choose file name with prompt savePrompt default name defaultName
        return filename as string
end chooseFileForSaving

-- writes the string 'str' to 'filename'
on writeToFile(filename, str)
        set fRef to (open for access file filename with write permission)
        set eof fRef to 0
        write str to fRef
        close access fRef
end writeToFile

-- returns the result of running the Unix 'file' command on 'filename'
on unixFileCmd(filename)
        set unixFilename to POSIX path of filename
        set info to do shell script "/usr/bin/file " & unixFilename
        return info
end unixFileCmd

-- returns a string with Unix-style line endings
on fixLineEndings(str)
        set oldTIDs to AppleScript's text item delimiters
        set theLines to paragraphs of str
        set AppleScript's text item delimiters to ASCII character 10
        set fixedStr to theLines as string
        set AppleScript's text item delimiters to oldTIDs
        return fixedStr
end fixLineEndings

set theString to "Twas brillig, and the slithy toves
Did gyre and gimble in the wabe:
All mimsy were the borogoves,
And the mome raths outgrabe."

-- comment out the following line to test the behaviour of different tools
set theString to fixLineEndings(theString)

set filename to chooseFileForSaving("jabberwocky.txt")
writeToFile(filename, theString)
set fileInfo to unixFileCmd(filename)
display alert fileInfo

With the use of the 'fixLineEndings' subroutine, the above AppleScript reports that the file is "ASCII English text" (which is the desired result: Unix line-endings) whether I use "Script Editor" or Satimage's "Smile".

If I comment out the line that invokes the 'fixLineEndings' subroutine,
and then run the above AppleScript from "Script Editor", it still reports that the file is "ASCII English text".
But when I run the identical AppleScript (with the call to 'fixLineEndings' commented out) from "Smile", it reports that the file is "ASCII English text, with CR line terminators".

hayne 12-15-2006 10:12 PM

Quote:

Originally Posted by NovaScotian (Post 342321)
I copied the whole script from Script Editor to a new document in Script Debugger 4 and it works. - Obviously the line ending problem arises from copying from the web page, rather than having the script as an original.

That seems even weirder.

Quote:

getProcessMB fails when the app is Firefox
This is due to the fact that the Firefox executable is (for some strange reason known only to the Firefox developers) "firefox-bin" instead of "Firefox"
(The full path to the executable is /Applications/Firefox.app/Contents/MacOS/firefox-bin )

NovaScotian 12-16-2006 01:31 AM

Quote:

Originally Posted by hayne (Post 342339)
That seems even weirder.


This is due to the fact that the Firefox executable is (for some strange reason known only to the Firefox developers) "firefox-bin" instead of "Firefox"
(The full path to the executable is /Applications/Firefox.app/Contents/MacOS/firefox-bin )

Yes, I figured that out too. But even if I used that name, the perl script wasn't happy with it - it didn't error, it just didn't come back and I had to cancel.

I hope you'll report back to this thread if you figure out how to deal with script that have required line endings. One way (that I haven't tried yet) would be to replace all the \r with \n in a first line of the script and another would be do do that using text item delimiters and ASCII character 10. Finally, if the server of a site happens to be a Windows machine, then it can have CRLF as a paragraph delimiter, and that's often a problem with copied material. A generic way to fix this would be great, and I see that you've got "feelers" out for that. Please let us know if you find a "best" way.

hayne 12-16-2006 03:42 AM

Quote:

Originally Posted by NovaScotian (Post 342364)
I hope you'll report back to this thread if you figure out how to deal with script that have required line endings. One way (that I haven't tried yet) would be to replace all the \r with \n in a first line of the script and another would be do do that using text item delimiters and ASCII character 10. Finally, if the server of a site happens to be a Windows machine, then it can have CRLF as a paragraph delimiter, and that's often a problem with copied material. A generic way to fix this would be great, and I see that you've got "feelers" out for that. Please let us know if you find a "best" way.

I have modified the 'testEmbeddedStrings' AppleScript of post #30 to make use of a subroutine 'fixLineEndings' which takes a string and converts it to have Unix line endings.
With the use of this subroutine, that AppleScript gives the same result whether compiled in "Script Editor" or in "Smile".
If I comment out that subroutine, the same script gives different results when using "Script Editor" than when using "Smile".

I'd be very interested to hear what happens if you use "Script Debugger".

I think (but I haven't tested it) that the above 'fixLineEndings' subroutine might work to fix any string - even one with Windows-type line endings.

hayne 12-16-2006 03:51 AM

Quote:

Originally Posted by NovaScotian (Post 342364)
Yes, I figured that out too. But even if I used that name, the perl script wasn't happy with it - it didn't error, it just didn't come back and I had to cancel.

When I use the above stand-alone Perl script 'getProcessRss', it works fine if I use the process name "firefox-bin":
Code:

% getProcessRss firefox-bin
32.6 MB

But I can't see how to change the title of a Firefox window via AppleScript, so I gave up on making a version of the above AppleScript for Firefox.

mark hunte 12-16-2006 07:37 AM

Quote:

Originally Posted by hayne (Post 342381)
When I use the above stand-alone Perl script 'getProcessRss', it works fine if I use the process name "firefox-bin":
Code:

% getProcessRss firefox-bin
32.6 MB

But I can't see how to change the title of a Firefox window via AppleScript, so I gave up on making a version of the above AppleScript for Firefox.

The name is called «class pTit»
But even that does not work. The sdef for Firefox is very very basic. It does not even have the Apple Script basics. That could be why?

NovaScotian 12-16-2006 11:46 AM

Quote:

Originally Posted by hayne (Post 342338)
So it would seem that my hypothesis (about the line-endings being an issue due to the use of different AppleScript editors) is verified.

-- snip --

With the use of the 'fixLineEndings' subroutine, the above AppleScript reports that the file is "ASCII English text" (which is the desired result: Unix line-endings) whether I use "Script Editor" or Satimage's "Smile".

If I comment out the line that invokes the 'fixLineEndings' subroutine,
and then run the above AppleScript from "Script Editor", it still reports that the file is "ASCII English text".
But when I run the identical AppleScript (with the call to 'fixLineEndings' commented out) from "Smile", it reports that the file is "ASCII English text, with CR line terminators".

Script Debugger 4's results are the same as Smile's. Without fixLineEndings, the saved text returns CR line terminators, with the the handler active it's ASCII English text.

Good stuff, CH.

PS: your fixLineEndings is the simplest way to do it too.

NovaScotian 12-16-2006 12:19 PM

For the shell-script impaired, the test for line endings can be done in AppleScript too.

set txt to read alias ((path to desktop folder as text) & "SomeText.txt")
whichEnding(txt)

on whichEnding(T)
set NP to count paragraphs of T -- produces the same number for any ending
set tid to AppleScript's text item delimiters
set AppleScript's text item delimiters to (ASCII character 13) & (ASCII character 10)
if (count text items of T) = NP then
set AppleScript's text item delimiters to tid
return "Windows"
end if
set AppleScript's text item delimiters to ASCII character 13
if (count text items of T) = NP then
set AppleScript's text item delimiters to tid
return "Mac"
end if
set AppleScript's text item delimiters to ASCII character 10
if (count text items of T) = NP then
set AppleScript's text item delimiters to tid
return "Unix"
end if
set AppleScript's text item delimiters to tid
end whichEnding

hayne 12-16-2006 12:52 PM

Quote:

Originally Posted by NovaScotian (Post 342433)
set txt to read alias ((path to desktop folder as text) & "jabberwocky.txt")
whichEnding(txt)

on whichEnding(T)
set NP to count paragraphs of T -- produces the same number for any ending
set tid to AppleScript's text item delimiters
set AppleScript's text item delimiters to ASCII character 10
if (count text items of T) = NP then return "Unix"
set AppleScript's text item delimiters to ASCII character 13
if (count text items of T) = NP then return "Mac"
set AppleScript's text item delimiters to (ASCII character 13) & (ASCII character 10)
if (count text items of T) = NP then return "Windows"
set AppleScript's text item delimiters to tid
end whichEnding

Doesn't the above suffer from the problem that "AppleScript's text item delimiters" is a global for the environment?
It seems to be returning before resetting "AppleScript's text item delimiters" to what it was. And hence the delimiter value will affect subsequent AppleScript in that script and all other until the environment is restarted (e.g.Script Editor is re-launched).

NovaScotian 12-16-2006 01:28 PM

Good catch, Hayne;

Several things wrong with it and that was only one of them. Order is important too. I've corrected the original rather than leave it stand broken.

hayne 12-16-2006 01:38 PM

Quote:

Originally Posted by NovaScotian (Post 342444)
Several things wrong with it and that was only one of them. Order is important too. I've corrected the original rather than leave it stand broken.

There still seems to be one (more subtle) problem with it. It is possible for a file to have mixed line-endings - this often happens if someone brings a text file over from Windows and then edits it a bit on OS X.
Your subroutine doesn't return anything in this case ('NP' would not match any of the "pure" cases).
This subject is tricky.

NovaScotian 12-16-2006 02:12 PM

Here's an AppleScript convertor to fix line ends in a saved document.

Code:

set tDoc to (choose file without invisibles)
set NL to ASCII character 10
set DocText to ConvertEnds(read tDoc, NL) -- read and convert
set F to open for access tDoc with write permission
try
        set eof of F to 0 -- erase what was there
        write DocText to F -- replace with converted endings
        close access F
on error
        close access F
end try

to ConvertEnds(txt, LineEnd)
        set tid to text item delimiters
        set text item delimiters to LineEnd
        tell txt's paragraphs to set txt to beginning & ({""} & rest)
        set text item delimiters to tid
        return txt
end ConvertEnds


NovaScotian 12-16-2006 03:52 PM

Further question:

Why doesn't something like this work?

set foo to (read (choose file without invisibles))
set the clipboard to (do shell script "echo " & foo & " | tr \\r \\n")

mark hunte 12-16-2006 04:38 PM

Quote:

Originally Posted by NovaScotian (Post 342469)
Further question:

Why doesn't something like this work?

set foo to (read (choose file without invisibles))
set the clipboard to (do shell script "echo " & foo & " | tr \\r \\n")

Try adding quoted form of. or it will stumble on a lot of files.
Code:

set foo to (read (choose file without invisibles))

set the clipboard to (do shell script "echo " & quoted form of foo & " | tr \\r \\n")


NovaScotian 12-16-2006 05:10 PM

Thanks Mark, that does solve the problem of getting the shell script to work, but the result seems to be converted back by the system because when I check, the endings are still returns.

PS: I've been informed on another list, for the benefit of those who use Script Debugger 4 that there is a File menu pick to save with Unix endings. Would have saved a lot of grief.

mark hunte 12-16-2006 05:41 PM

If I run your script from Post #41 on a plain text file, I get "Mac".

If I combine the two scripts I get "unix"

Code:

set foo to (read (choose file without invisibles))

set T to (do shell script "echo " & quoted form of foo & " | tr \\r \\n")
set NP to count paragraphs of T -- produces the same number for any ending

set tid to AppleScript's text item delimiters
set AppleScript's text item delimiters to (ASCII character 13) & (ASCII character 10)
if (count text items of T) = NP then
        set AppleScript's text item delimiters to tid
        return "Windows"
end if
set AppleScript's text item delimiters to ASCII character 13
if (count text items of T) = NP then
        set AppleScript's text item delimiters to tid
        return "Mac"
end if
set AppleScript's text item delimiters to ASCII character 10
if (count text items of T) = NP then
        set AppleScript's text item delimiters to tid
        return "Unix"
end if
set AppleScript's text item delimiters to tid


hayne 12-16-2006 05:54 PM

Quote:

Originally Posted by NovaScotian (Post 342469)
Why doesn't something like this work?

set foo to (read (choose file without invisibles))
set the clipboard to (do shell script "echo " & foo & " | tr \\r \\n")

It's a feature of 'do shell script'.
From http://developer.apple.com/technotes/tn2002/tn2065.html
Quote:

Originally Posted by above Apple doc
By default, do shell script transforms all the line endings in the result to Mac-style carriage returns ("\r" or ASCII character 13), and removes a single trailing line ending, if one exists. This means, for example, that the result of do shell script "echo foo; echo bar" is "foo\rbar", not the "foo\nbar\n" that echo actually returned. You can suppress both of these behaviors by adding the without altering line endings parameter.


NovaScotian 12-16-2006 08:34 PM

Quote:

Originally Posted by hayne (Post 342487)
It's a feature of 'do shell script'.
From http://developer.apple.com/technotes/tn2002/tn2065.html

Thanks, Hayne. The problem is checking the result. Many apps, are all too eager to "help" by changing them back. Ugh.

hayne 12-17-2006 12:27 PM

Here's a new version of the above AppleScript for displaying Safari's memory usage. This version has the warnings (for exceeding the warn-level of memory usage) delayed longer each time you okay the warning dialog. This is useful in situations where Safari is exceeding the warning-level but you don't want to quit it immediately.

This version also has a property 'useDelayLoop' that you can set to make it possible to do test runs of the script from Script Editor.

Code:

-- This AppleScript displays the amount of memory being used by Safari
-- in the title of the frontmost Safari window.
-- It is intended to be saved as an application
-- and left running all of the time.
-- Cameron Hayne (macdev@hayne.net)  December 2006

property useDelayLoop : false -- set this to true if you want to run from Script Editor
property updateDelay : 10 -- seconds
property warnIntervalIncrement : 120 -- seconds

on update()
    updateAppSize("Safari", 150)
end update

on run
    if useDelayLoop then
        repeat
            my update()
            delay updateDelay
        end repeat
    end if
end run

on idle
    if useDelayLoop then
        -- we should never get here
        display alert "Internal error: idle handler called even though using delay loop"
        quit
    end if
   
    my update()
    return updateDelay
end idle

-- updateAppSize:
-- Displays the amount of memory being used by the app 'appName'
-- Pops up an alert dialog if the amount of memory used exceeds 'warnMB'
on updateAppSize(appName, warnMB)
    if my appIsRunning(appName) then
        set numMB to my getProcessMB(appName)
        showAppSizeInTitle(appName, numMB)
        my checkIfMemoryUseMeritsWarning(appName, warnMB, numMB)
    end if
end updateAppSize

-- showAppSizeInTitle:
-- Displays the amount of memory being used by the app 'appName' in the title
-- of the frontmost window of that app.
on showAppSizeInTitle(appName, numMB)
    tell application appName
        if (count of windows) > 0 then
            try
                set title to name of window 1
                set delim to " *** "
                set origTitle to my removeLastPart(title, delim)
                set title to origTitle & delim & numMB & " MB"
                set name of window 1 to title
            on error
                -- there shouldn't be any errors
                -- (assuming that the app responds to 'name of window')
                -- but if an error occurs, we ignore it
            end try
        end if
    end tell
end showAppSizeInTitle

-- puts up a warning dialog about 'appName's memory usage
-- but avoids doing this too soon after the last warning
-- (increases the interval between warnings each time)
on checkIfMemoryUseMeritsWarning(appName, warnMB, numMB)
    global initWarnings, lastWarnTime, numWarnings
    try
        if initWarnings then
            -- nothing to do
        end if
    on error
        -- first time this subroutine is called we initialize variables
        set lastWarnTime to ((current date) - 1)
        set numWarnings to 0
        set initWarnings to true
    end try
   
    if numMB is greater than warnMB then
        set elapsed to secondsSince(lastWarnTime)
        set warnInterval to (numWarnings * warnIntervalIncrement)
        if elapsed > warnInterval then
            tell application appName
                display alert appName & " is taking more than " & warnMB & " MB"
            end tell
            set lastWarnTime to (current date)
            set numWarnings to (numWarnings + 1)
        end if
    else
        set numWarnings to 0
    end if
end checkIfMemoryUseMeritsWarning

-- returns the number of seconds since 'aDate'
on secondsSince(aDate)
    set curr to (current date)
    set elapsed to (curr - aDate)
    return elapsed
end secondsSince

-- returns a string with Unix-style line endings
on fixLineEndings(str)
    set oldTIDs to AppleScript's text item delimiters
    set theLines to paragraphs of str
    set AppleScript's text item delimiters to ASCII character 10
    set fixedStr to theLines as string
    set AppleScript's text item delimiters to oldTIDs
    return fixedStr
end fixLineEndings

-- getProcessMB:
-- Returns the number of megabytes used by the specified process.
-- It gets this value by invoking the Unix 'ps' command via a Perl script.
on getProcessMB(procName)
    set perlCode to fixLineEndings("
# This script gets the 'RSS' of the process specified as 'procName'
# The 'RSS' (resident set size) is a rough measure of how much RAM
# the process is using. The value is given in megabytes.
open(PS, \"/bin/ps -xc -o command,rss |\");
while (<PS>)
{
    if (/^\\s*" & procName & "\\s+(\\d+)\\s*$/o)
    {
        my $rss = $1;
        my $rssMB = sprintf(\"%.1f\", $rss / 1024);
        print $rssMB;
        last;
    }
}
close(PS);
")
    set numMB to do shell script "perl -e " & quoted form of perlCode
    return numMB as real
end getProcessMB

-- removeLastPart:
-- Returns the string 'str' without the last part starting with 'delim'
on removeLastPart(str, delim)
    set saveDelim to AppleScript's text item delimiters
    set AppleScript's text item delimiters to delim
    if (count str's text items) > 1 then
        set str to str's text 1 thru text item -2
    end if
    set AppleScript's text item delimiters to saveDelim
    return str
end removeLastPart

-- appIsRunning:
-- Returns true if the app 'appName' is running, otherwise returns false
on appIsRunning(appName)
    set isRunning to false
    tell application "System Events"
        if exists process appName then
            set isRunning to true
        end if
    end tell
    return isRunning
end appIsRunning


NovaScotian 12-17-2006 12:39 PM

Runs nicely for me from either the Script Editor or Script Debugger 4. The fixLineEndings does it - bombproof!

hayne 12-17-2006 05:45 PM

New & improved! :)
Here's an improved version of the script. This version implements 'mark hunte's idea of showing CPU usage as well as memory usage.
It shows Safari's CPU usage (in the title of the Safari window) whenever it exceeds 10%

(I don't think having it give a warning dialog upon exceeding a certain CPU usage would be good since this script is intended to be left running all of the time, and sometimes you would expect Safari to be using a lot of CPU - e.g. when playing a Flash or Java game)

This version might also be slightly more efficient (use less CPU) since it gets the process-id of the Safari process from System Events and then runs the 'ps' command only for that one process instead of getting info on all processes from 'ps' and filtering out all the rest.

Code:

-- safariInfoInTitle:
-- This AppleScript displays the amount of memory being used by Safari
-- in the title of the frontmost Safari window.
-- It also displays Safari's CPU usage if it exceeds 10%.
-- This is intended to be saved as an application
-- and left running all of the time.
-- Cameron Hayne (macdev@hayne.net)  December 2006

property useDelayLoop : false -- set this to true if you want to run from Script Editor
property updateDelay : 8 -- seconds
property cpuFloor : 10 -- % above which CPU usage will be shown in the title
property warnIntervalIncrement : 120 -- seconds

on update()
    updateAppInfo("Safari", 150)
end update

on run
    if useDelayLoop then
        repeat
            my update()
            delay updateDelay
        end repeat
    end if
end run

on idle
    if useDelayLoop then
        -- we should never get here
        display alert "Internal error: idle handler called even though using delay loop"
        quit
    end if
   
    my update()
    return updateDelay
end idle

-- updateAppInfo:
-- Displays the amount of memory being used by the app 'appName'
-- Pops up an alert dialog if the amount of memory used exceeds 'warnMB'
on updateAppInfo(appName, warnMB)
    set pid to my pidOfRunningApp(appName)
    if pid is -1 then
        -- app is not running, so nothing to do
    else
        set info to my getProcessInfo(pid)
        set numMB to first word of info as real
        set percentCpu to second word of info as real
        showAppInfoInTitle(appName, numMB, percentCpu)
        my checkIfMemoryUseMeritsWarning(appName, warnMB, numMB)
    end if
end updateAppInfo

-- showAppInfoInTitle:
-- Displays the amount of memory being used by the app 'appName' in the title
-- of the frontmost window of that app.
on showAppInfoInTitle(appName, numMB, percentCpu)
    tell application appName
        if (count of windows) > 0 then
            try
                set title to name of window 1
                set delim to " *** "
                set origTitle to my removeLastPart(title, delim)
                set title to origTitle & delim & numMB & " MB"
                if percentCpu > cpuFloor then
                    set title to title & ", " & percentCpu & "% CPU"
                end if
                set name of window 1 to title
            on error
                -- there shouldn't be any errors
                -- (assuming that the app responds to 'name of window')
                -- but if an error occurs, we ignore it
            end try
        end if
    end tell
end showAppInfoInTitle

-- puts up a warning dialog about 'appName's memory usage
-- but avoids doing this too soon after the last warning
-- (increases the interval between warnings each time)
on checkIfMemoryUseMeritsWarning(appName, warnMB, numMB)
    global initWarnings, lastWarnTime, numWarnings
    try
        if initWarnings then
            -- nothing to do
        end if
    on error
        -- first time this subroutine is called we initialize variables
        set lastWarnTime to ((current date) - 1)
        set numWarnings to 0
        set initWarnings to true
    end try
   
    if numMB is greater than warnMB then
        set elapsed to secondsSince(lastWarnTime)
        set warnInterval to (numWarnings * warnIntervalIncrement)
        if elapsed > warnInterval then
            tell application appName
                display alert appName & " is taking more than " & warnMB & " MB"
            end tell
            set lastWarnTime to (current date)
            set numWarnings to (numWarnings + 1)
        end if
    else
        set numWarnings to 0
    end if
end checkIfMemoryUseMeritsWarning

-- returns the number of seconds since 'aDate'
on secondsSince(aDate)
    set curr to (current date)
    set elapsed to (curr - aDate)
    return elapsed
end secondsSince

-- returns a string with Unix-style line endings
on fixLineEndings(str)
    set oldTIDs to AppleScript's text item delimiters
    set theLines to paragraphs of str
    set AppleScript's text item delimiters to ASCII character 10
    set fixedStr to theLines as string
    set AppleScript's text item delimiters to oldTIDs
    return fixedStr
end fixLineEndings

-- getProcessInfo:
-- Returns info about the resource usage of the specified process.
-- It gets this info by invoking the Unix 'ps' command via a Perl script.
-- The return value gives the number of megabytes used
-- followed by the percentage of CPU used
on getProcessInfo(pid)
    set perlCode to fixLineEndings("
# This script gets the 'RSS' & '%CPU' of the process with the given 'pid'
# The 'RSS' (resident set size) is a rough measure of how much RAM
# the process is using. The value is converted to megabytes.
open(PS, \"/bin/ps -p " & pid & " -o pid,rss,%cpu |\");
while (<PS>)
{
    if (/^\\s*" & pid & "\\s+(\\d+)\\s+([\\d.]+)\\s*$/)
    {
        my $rss = $1;
        my $cpu = $2;
        my $rssMB = sprintf(\"%.1f\", $rss / 1024);
        print \"$rssMB $cpu\";
        last;
    }
}
close(PS);
")
    set info to do shell script "perl -e " & quoted form of perlCode
    return info
end getProcessInfo

-- removeLastPart:
-- Returns the string 'str' without the last part starting with 'delim'
on removeLastPart(str, delim)
    set oldTIDs to AppleScript's text item delimiters
    set AppleScript's text item delimiters to delim
    if (count str's text items) > 1 then
        set str to str's text 1 thru text item -2
    end if
    set AppleScript's text item delimiters to oldTIDs
    return str
end removeLastPart

-- pidOfRunningApp:
-- Returns the pid of the process if the app 'appName' is running,
-- otherwise returns -1
on pidOfRunningApp(appName)
    tell application "System Events"
        try
            set pid to the unix id of process appName
        on error
            set pid to -1
        end try
    end tell
    return pid
end pidOfRunningApp


NovaScotian 12-17-2006 08:14 PM

Works very nicely on MM, Hayne. With useDelayLoop set to true and the first handler set as:

Code:

on update()
        updateAppInfo("Safari", 150)
        updateAppInfo("NetNewsWire", 100)
        updateAppInfo("Camino", 150)
end update

I got good results on those three applications running it from Smile, Script Editor, and Script Debugger 4, and finally with useDelayLoop set to false, good results as a stay-open application.

hayne 12-18-2006 09:24 AM

Thanks for testing this, NovaScotian.

By the way, if I add a line:
updateAppInfo("firefox-bin", 150)
then it does work fine as far as giving warnings about Firefox's memory usage (if it exceeds 150 MB with the above line).
But as discussed in an earlier post, it doesn't show the memory usage in the title since Firefox is not AppleScriptable.

NovaScotian 12-18-2006 10:23 AM

There's a "hack" that makes it possible to get/set the name of a Foxfire window from Rob Griffiths in MacWorld.com. I haven't tried it for Foxfire (which I don't like) and don't particularly recommend it - I just point it out. I've used it successfully with Preview because it is useful to be able to manipulate its windows.

hayne 12-18-2006 11:07 AM

Thanks for reminding me about that hack to enable basic AppleScript support in apps that don't by default support AppleScript.
I haven't tried it yet with Firefox

hayne 12-19-2006 03:53 AM

Here's a new version of the script with the following changes:
  • support for using "Growl" (http://growl.info/) for warnings instead of AppleScript alert dialogs
  • can specify different update periods for different apps
  • added lines to watch Terminal and Mail.app as well as Safari (you can comment out any you don't want, or add other apps if you want)

Here's the script:
Code:

-- appInfoInTitle:
-- This AppleScript displays the amount of memory being used by designated applications
-- in the title of their frontmost windows.
-- It also displays the CPU usage of these apps if it exceeds 10%.
-- And it gives a warning if the memory usage of an app exceeds a specified level.
-- This was originally designed to keep watch on Safari's memory consumption
-- but it can be used for various apps.
-- It is intended to be saved as an application and left running all of the time.
-- Cameron Hayne (macdev@hayne.net)  December 2006

property scriptName : "appInfoInTitle"
property useDelayLoop : false -- set this to true if you want to run from Script Editor
property useGrowl : false -- set this to true to get warnings via Growl (http://growl.info/)
property updateDelay : 5 -- seconds
property cpuFloor : 10 -- % above which CPU usage will be shown in the title
property warnIntervalIncrement : 120 -- seconds

global appInfoList -- list of AppInfo objects (added to via 'addAppInfo')

-- registerApps:
-- Add a call to 'addAppInfo' for each app you want to watch
on registerApps()
    addAppInfo given appName:"Safari", updatePeriod:5, warnMB:100
    addAppInfo given appName:"Terminal", updatePeriod:60, warnMB:15
    addAppInfo given appName:"Mail", updatePeriod:60, warnMB:30
end registerApps

-- update:
-- This subroutine is invoked every 'updateDelay' seconds
on update()
    repeat with appInfo in appInfoList
        updateAppInfo(appInfo)
    end repeat
end update

-- the run handler (invoked at script startup)
on run
    set appInfoList to {}
    registerApps()
   
    if useGrowl then
        registerWithGrowl()
    end if
   
    if useDelayLoop then
        repeat
            update()
            delay updateDelay
        end repeat
    end if
end run

-- the idle handler (invoked every 'updateDelay' seconds)
on idle
    if useDelayLoop then
        -- we should never get here
        display alert "Internal error: idle handler called even though using delay loop"
        quit
    end if
   
    update()
    return updateDelay
end idle

-- addAppInfo:
-- Creates a new 'appInfo' object and adds it to 'appInfoList'
-- 'updatePeriod' is the time in seconds between updates for this app
-- 'warnMB' is the memory usage (in megabytes) at which warnings will be issued
on addAppInfo given appName:anAppName, updatePeriod:anUpdatePeriod, warnMB:aWarnMB
    set prevTime to (current date) - 1000
    script appInfo
        property appName : anAppName
        property updatePeriod : anUpdatePeriod
        property warnMB : aWarnMB
        property lastUpdateTime : prevTime
        property lastWarnTime : prevTime
        property numWarnings : 0
        property numMB : 0
        property percentCpu : 0
    end script
    set appInfoList to appInfoList & appInfo
end addAppInfo

-- updateAppInfo:
-- Displays the amount of memory being used by the app 'appName'
-- Pops up an alert dialog if the amount of memory used exceeds 'warnMB'
on updateAppInfo(appInfo)
    set elapsed to secondsSince(lastUpdateTime of appInfo)
    if elapsed < (updatePeriod of appInfo) then return
   
    set pid to pidOfRunningApp(appName of appInfo)
    if pid is -1 then
        -- app is not running, so nothing to do
    else
        set info to getProcessInfo(pid)
        if info is "" then return
        set numMB of appInfo to round (first word of info as real)
        set percentCpu of appInfo to round (second word of info as real)
        showAppInfoInTitle(appInfo)
        checkIfMemoryUseMeritsWarning(appInfo)
    end if
   
    set lastUpdateTime of appInfo to (current date)
end updateAppInfo

-- showAppInfoInTitle:
-- Displays the amount of memory being used by the app 'appName' in the title
-- of the frontmost window of that app.
on showAppInfoInTitle(appInfo)
    set appName to (appName of appInfo)
    set numMB to (numMB of appInfo)
    set percentCpu to (percentCpu of appInfo)
   
    try
        tell application appName
            if (count of windows) > 0 then
                set title to name of window 1
                set delim to " *** "
                set origTitle to my removeLastPart(title, delim)
                set title to origTitle & delim & numMB & " MB"
                if percentCpu > cpuFloor then
                    set title to title & ", " & percentCpu & "% CPU"
                end if
                set name of window 1 to title
            end if
        end tell
    on error
        -- there shouldn't be any errors
        -- (assuming that the app responds to 'name of window')
        -- but if an error occurs, we ignore it
    end try
end showAppInfoInTitle

-- checkIfMemoryUseMeritsWarning:
-- Puts up a warning dialog about 'appName's memory usage
-- but avoids doing this too soon after the last warning
-- (increases the interval between warnings each time)
on checkIfMemoryUseMeritsWarning(appInfo)
    set appName to (appName of appInfo)
    set numMB to (numMB of appInfo)
    set warnMB to (warnMB of appInfo)
    set lastWarnTime to (lastWarnTime of appInfo)
    set numWarnings to (numWarnings of appInfo)
   
    if numMB > warnMB then
        set elapsed to secondsSince(lastWarnTime)
        set warnInterval to (numWarnings * warnIntervalIncrement)
        if elapsed > warnInterval then
            set msg to appName & " is taking more than " & warnMB & " MB"
            displayWarning(appName, msg)
            set lastWarnTime to (current date)
            set numWarnings to (numWarnings + 1)
        end if
    else if numMB > (0.95 * warnMB) then
        -- avoid continual warnings when oscillating around 'warnMB' level
        set numWarnings to 1
    else
        set numWarnings to 0
    end if
   
    set lastWarnTime of appInfo to lastWarnTime
    set numWarnings of appInfo to numWarnings
end checkIfMemoryUseMeritsWarning

-- displayWarning:
-- Displays a warning relating to 'appName'
on displayWarning(appName, msg)
    if useGrowl then
        try
            displayWarningViaGrowl(appName, msg)
        on error
            displayWarningViaAlert(appName, msg)
        end try
    else
        displayWarningViaAlert(appName, msg)
    end if
end displayWarning

-- displayWarningViaAlert:
-- Displays a warning via an AppleScript alert dialog
on displayWarningViaAlert(appName, msg)
    tell application appName
        display alert msg
    end tell
end displayWarningViaAlert

-- displayWarningViaGrowl:
-- Displays a warning via "Growl"
on displayWarningViaGrowl(appName, msg)
    tell application "GrowlHelperApp"
        notify with name "MemoryUseWarning" title "Warning" description msg application name scriptName
    end tell
end displayWarningViaGrowl

-- registerWithGrowl:
-- Registers our notifications with the "Growl" tool
on registerWithGrowl()
    set notifList to {"MemoryUseWarning"}
    tell application "GrowlHelperApp"
        register as application scriptName all notifications notifList default notifications notifList
    end tell
end registerWithGrowl

-- secondsSince:
-- Returns the number of seconds since 'aDate'
on secondsSince(aDate)
    set curr to (current date)
    set elapsed to (curr - aDate)
    return elapsed
end secondsSince

-- fixLineEndings:
-- returns a string with Unix-style line endings
on fixLineEndings(str)
    set oldTIDs to AppleScript's text item delimiters
    set theLines to paragraphs of str
    set AppleScript's text item delimiters to ASCII character 10
    set fixedStr to theLines as string
    set AppleScript's text item delimiters to oldTIDs
    return fixedStr
end fixLineEndings

-- getProcessInfo:
-- Returns info about the resource usage of the specified process.
-- It gets this info by invoking the Unix 'ps' command via a Perl script.
-- The return value gives the number of megabytes used
-- followed by the percentage of CPU used
on getProcessInfo(pid)
    set perlCode to fixLineEndings("
# This script gets the 'RSS' & '%CPU' of the process with the given 'pid'
# The 'RSS' (resident set size) is a rough measure of how much RAM
# the process is using. The value is converted to megabytes.
open(PS, \"/bin/ps -p " & pid & " -o pid,rss,%cpu |\");
while (<PS>)
{
    if (/^\\s*" & pid & "\\s+(\\d+)\\s+([\\d.]+)\\s*$/)
    {
        my $rss = $1;
        my $cpu = $2;
        my $rssMB = sprintf(\"%.1f\", $rss / 1024);
        print \"$rssMB $cpu\";
        last;
    }
}
close(PS);
")
    set info to do shell script "perl -e " & quoted form of perlCode
    return info
end getProcessInfo

-- removeLastPart:
-- Returns the string 'str' without the last part starting with 'delim'
on removeLastPart(str, delim)
    set oldTIDs to AppleScript's text item delimiters
    set AppleScript's text item delimiters to delim
    if (count str's text items) > 1 then
        set str to str's text 1 thru text item -2
    end if
    set AppleScript's text item delimiters to oldTIDs
    return str
end removeLastPart

-- pidOfRunningApp:
-- Returns the pid of the process if the app 'appName' is running,
-- otherwise returns -1
on pidOfRunningApp(appName)
    tell application "System Events"
        try
            set pid to the unix id of process appName
        on error
            set pid to -1
        end try
    end tell
    return pid
end pidOfRunningApp

-- Copyright (C) 2006  Cameron Hayne - macdev@hayne.net
--
-- This program is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.


mark hunte 12-19-2006 09:22 PM

Hayne,
Just like to say this app is very useful,

Thanks

hayne 12-21-2006 06:59 AM

Quote:

Originally Posted by mark hunte (Post 343241)
Just like to say this app is very useful,

Glad you find it useful - I wrote it to "scratch an itch" of mine and I run it all the time.
I still find it strange that sometimes this AppleScript application seems to take 10 or 15 MB of RAM and then if I quit it and re-launch it, its RAM usage is down to 5 MB.
I'd be interested to hear if others are seeing this phenomenon.

By the way, I edited the above (latest) version of the script to make it round the memory & CPU numbers to integers. It seemed silly to be reporting 1/10ths of MBs - and this saves a bit of space on the title-bar as well.

mark hunte 12-21-2006 07:24 AM

My copy never seems to get below 14mb on my tower and PB.
I suppose I should be happy with it being consistent? :)

thanks again.


Mark

NovaScotian 12-21-2006 09:58 AM

[ot]
 
Speaking of "useful", Daniel Jalkut's Blog has some very flattering remarks, Hayne, for ash as a means of running AppleScripts via ssh when he's away from his machine. Looks neat. :)

hayne 12-21-2006 12:26 PM

Quote:

Originally Posted by mark hunte (Post 343544)
My copy never seems to get below 14mb on my tower and PB.

So you've tried quitting it and relaunching and it is always taking the same amount of RAM?
At one point I thought maybe it was somehow correlated with whether you had the script open in Script Editor or not.

hayne 12-22-2006 12:10 PM

I have edited the above (latest) version of the 'appInfoInTitle' script to add an error check on the result from 'getProcessInfo'.


And I'd encourage any users of this script to install Growl (http://growl.info/) and enable the 'appInfoInTitle' script to use Growl for warnings by setting the 'useGrowl' variable to true. Growl is very elegant.

NovaScotian 12-22-2006 02:32 PM

Works beautifully with Camino and NetNewsWire added to the mix.

mark hunte 12-27-2006 03:55 PM

Quote:

Originally Posted by hayne (Post 343600)
So you've tried quitting it and relaunching and it is always taking the same amount of RAM?

Thats correct, still it is always the same?


15190 Safram4g mark 0.60 1 14.73 MB 143.84 MB

hayne 01-02-2007 12:04 PM

Quote:

Originally Posted by mark hunte (Post 344793)
Thats correct, still it is always the same?
15190 Safram4g mark 0.60 1 14.73 MB 143.84 MB

Hmm, if it took 15 MB of RAM on my machine I would probably be reluctant to run it all the time. (It takes 5.1 MB of RAM on my iBook G4)

Have you tried quitting Script Editor and then running the script?
I never figured out what caused it to sometimes take more RAM sometimes but on my machine it now seems to be more consistently taking 5 MB.

And check that you have it compiled as an application bundle.

mark hunte 01-02-2007 01:25 PM

Ok tried again.
I did not have it as a Bundle.

It seems to take at least two new launches after quitting Script Editor to get it down from 14.. to 5..


I do sometimes get high Ram if I get a warning first thing after launch?
will try it with growl.

If I bring SE back up after launch then all is ok.

mark hunte 01-02-2007 01:52 PM

I have just go consistant results doing the below.

Open the Script (RAM app) in SE, Leave its window open
and then launch the RAM app (above 14mb)

Open the Script (RAM app) in SE, Edited it and Save it, Leave its window open
and then launch RAM app (above 14mb)

Open the Script (RAM app)in SE, Edited it and Save it, close its window.
Leaving SE running, and then launch RAM app. (above 14mb)

Open the Script (RAM app) in SE, Edited it and Save it, close its window.
Select a second script window and do a Save in that, leaving it open and then launch RAM app I get low ram. (under 4mb)

mark hunte 01-05-2007 08:34 PM

Hi, Hayne,
I noticed from the Hint you posted today that Some people had an issue with Growl, either they did not want it or they did not read the instruction correctly. and ran in to compiling issues.

So I changed the script so if you do not have growl then the the Growl property will be ignored when the script is run.
Also you will not be asked for growl when you first compile, as I have used a run script to call the growl app.
Code:

-- appInfoInTitle:
-- This AppleScript displays the amount of memory being used by designated applications
-- in the title of their frontmost windows.
-- It also displays the CPU usage of these apps if it exceeds 10%.
-- And it gives a warning if the memory usage of an app exceeds a specified level.
-- This was originally designed to keep watch on Safari's memory consumption
-- but it can be used for various apps.
-- It is intended to be saved as an application and left running all of the time.
-- Cameron Hayne (macdev@hayne.net)  December 2006

property scriptName : "appInfoInTitle"
property useDelayLoop : false -- set this to true if you want to run from Script Editor
property useGrowl : true -- set this to true to get warnings via Growl (http://growl.info/)
property updateDelay : 5 -- seconds
property cpuFloor : 10 -- % above which CPU usage will be shown in the title
property warnIntervalIncrement : 120 -- seconds
property GrowlApp : "GrowlHelperApp" as string
global appInfoList -- list of AppInfo objects (added to via 'addAppInfo')
global G_chk
-- registerApps:
-- Add a call to 'addAppInfo' for each app you want to watch
on registerApps()
        addAppInfo given appName:"Safari", updatePeriod:5, warnMB:100
        addAppInfo given appName:"Terminal", updatePeriod:60, warnMB:15
        addAppInfo given appName:"Mail", updatePeriod:60, warnMB:30
end registerApps

-- update:
-- This subroutine is invoked every 'updateDelay' seconds
on update()
        repeat with appInfo in appInfoList
                updateAppInfo(appInfo)
        end repeat
end update

-- the run handler (invoked at script startup)
on run
        --try
       
        set G_chk to true
        tell application "System Events"
                -- Check to see if Growl is installed -- is not the growl app will not be called later in the script
                if useGrowl is true then
                        set G_chk to true
                        set thePathName to name of (current user) as string
                        set stp to name of (startup disk) as string
                       
                        set Lib to (":Library:PreferencePanes:")
                        tell application "Finder"
                                set gP to "Growl.prefPane"
                                set Syssetpane to stp & Lib as alias
                                set Usrsetpane to stp & ":Users:" & thePathName & Lib as alias
                                if (exists item gP of Syssetpane) then
                                else if (exists item gP of Usrsetpane) then
                                else
                                        set G_chk to false
                                end if
                        end tell
                end if
        end tell
        --end try
        set appInfoList to {}
        registerApps()
       
        if useGrowl then
                registerWithGrowl()
        end if
       
        if useDelayLoop then
                repeat
                        update()
                        delay updateDelay
                end repeat
        end if
end run

-- the idle handler (invoked every 'updateDelay' seconds)
on idle
        if useDelayLoop then
                -- we should never get here
                display alert "Internal error: idle handler called even though using delay loop"
                quit
        end if
       
        update()
        return updateDelay
end idle

-- addAppInfo:
-- Creates a new 'appInfo' object and adds it to 'appInfoList'
-- 'updatePeriod' is the time in seconds between updates for this app
-- 'warnMB' is the memory usage (in megabytes) at which warnings will be issued
on addAppInfo given appName:anAppName, updatePeriod:anUpdatePeriod, warnMB:aWarnMB
        --on addAppInfo(anAppName, anUpdatePeriod, aWarnMB)
        set prevTime to (current date) - 1000
        script appInfo
                property appName : anAppName
                property updatePeriod : anUpdatePeriod
                property warnMB : aWarnMB
                property lastUpdateTime : prevTime
                property lastWarnTime : prevTime
                property numWarnings : 0
                property numMB : 0
                property percentCpu : 0
        end script
        set appInfoList to appInfoList & appInfo
end addAppInfo

-- updateAppInfo:
-- Displays the amount of memory being used by the app 'appName'
-- Pops up an alert dialog if the amount of memory used exceeds 'warnMB'
on updateAppInfo(appInfo)
        set elapsed to secondsSince(lastUpdateTime of appInfo)
        if elapsed < (updatePeriod of appInfo) then return
       
        set pid to pidOfRunningApp(appName of appInfo)
        if pid is -1 then
                -- app is not running, so nothing to do
        else
                set info to getProcessInfo(pid)
                if info is "" then return
                set numMB of appInfo to round (first word of info as real)
                set percentCpu of appInfo to round (second word of info as real)
                showAppInfoInTitle(appInfo)
                checkIfMemoryUseMeritsWarning(appInfo)
        end if
       
        set lastUpdateTime of appInfo to (current date)
end updateAppInfo

-- showAppInfoInTitle:
-- Displays the amount of memory being used by the app 'appName' in the title
-- of the frontmost window of that app.
on showAppInfoInTitle(appInfo)
        set appName to (appName of appInfo)
        set numMB to (numMB of appInfo)
        set percentCpu to (percentCpu of appInfo)
       
        try
                tell application appName
                        if (count of windows) > 0 then
                                set title to name of window 1
                                set delim to " *** "
                                set origTitle to my removeLastPart(title, delim)
                                set title to origTitle & delim & numMB & " MB"
                                if percentCpu > cpuFloor then
                                        set title to title & ", " & percentCpu & "% CPU"
                                end if
                                set name of window 1 to title
                        end if
                end tell
        on error
                -- there shouldn't be any errors
                -- (assuming that the app responds to 'name of window')
                -- but if an error occurs, we ignore it
        end try
end showAppInfoInTitle

-- checkIfMemoryUseMeritsWarning:
-- Puts up a warning dialog about 'appName's memory usage
-- but avoids doing this too soon after the last warning
-- (increases the interval between warnings each time)
on checkIfMemoryUseMeritsWarning(appInfo)
        set appName to (appName of appInfo)
        set numMB to (numMB of appInfo)
        set warnMB to (warnMB of appInfo)
        set lastWarnTime to (lastWarnTime of appInfo)
        set numWarnings to (numWarnings of appInfo)
       
        if numMB > warnMB then
                set elapsed to secondsSince(lastWarnTime)
                set warnInterval to (numWarnings * warnIntervalIncrement)
                if elapsed > warnInterval then
                        set msg to appName & " is taking more than " & warnMB & " MB"
                        displayWarning(appName, msg)
                        set lastWarnTime to (current date)
                        set numWarnings to (numWarnings + 1)
                end if
        else if numMB > (0.95 * warnMB) then
                -- avoid continual warnings when oscillating around 'warnMB' level
                set numWarnings to 1
        else
                set numWarnings to 0
        end if
       
        set lastWarnTime of appInfo to lastWarnTime
        set numWarnings of appInfo to numWarnings
end checkIfMemoryUseMeritsWarning

-- displayWarning:
-- Displays a warning relating to 'appName'
on displayWarning(appName, msg)
        -- if growl is not installed but the "property useGrowl" is set to true then the warning will
        -- have "Growl is not installed" under it.
        if G_chk is true then
                if useGrowl then
                        try
                                displayWarningViaGrowl(appName, msg)
                        on error
                                displayWarningViaAlert(appName, msg & return & "(Growl is not installed)")
                        end try
                else
                        displayWarningViaAlert(appName, msg)
                end if
        else
                displayWarningViaAlert(appName, msg & return & "(Growl is not installed)")
        end if
end displayWarning

-- displayWarningViaAlert:
-- Displays a warning via an AppleScript alert dialog
on displayWarningViaAlert(appName, msg)
        tell application appName
                display alert msg
        end tell
end displayWarningViaAlert

-- displayWarningViaGrowl:
-- Displays a warning via "Growl"
on displayWarningViaGrowl(appName, msg)
        set the_command to "tell application " & "\"" & GrowlApp & "\" to notify with name \"MemoryUseWarning\" title \"Warning\" description " & "\"" & msg & "\"" & " application name \"" & scriptName & "\""
        run script the_command
end displayWarningViaGrowl

-- registerWithGrowl:
-- Registers our notifications with the "Growl" tool
on registerWithGrowl()
        set notifList to "{\"MemoryUseWarning\"}" as string
        get scriptName
        set T to "tell application " & "\"" & GrowlApp & "\" to register as application  \"" & scriptName & "\"  all notifications " & notifList & "  default notifications " & notifList
        if G_chk is true then
                run script T
        end if
end registerWithGrowl

-- secondsSince:
-- Returns the number of seconds since 'aDate'
on secondsSince(aDate)
        set curr to (current date)
        set elapsed to (curr - aDate)
        return elapsed
end secondsSince

-- fixLineEndings:
-- returns a string with Unix-style line endings
on fixLineEndings(str)
        set oldTIDs to AppleScript's text item delimiters
        set theLines to paragraphs of str
        set AppleScript's text item delimiters to ASCII character 10
        set fixedStr to theLines as string
        set AppleScript's text item delimiters to oldTIDs
        return fixedStr
end fixLineEndings

-- getProcessInfo:
-- Returns info about the resource usage of the specified process.
-- It gets this info by invoking the Unix 'ps' command via a Perl script.
-- The return value gives the number of megabytes used
-- followed by the percentage of CPU used
on getProcessInfo(pid)
        set perlCode to fixLineEndings("
# This script gets the 'RSS' & '%CPU' of the process with the given 'pid'
# The 'RSS' (resident set size) is a rough measure of how much RAM
# the process is using. The value is converted to megabytes.
open(PS, \"/bin/ps -p " & pid & " -o pid,rss,%cpu |\");
while (<PS>)
{
    if (/^\\s*" & pid & "\\s+(\\d+)\\s+([\\d.]+)\\s*$/)
    {
        my $rss = $1;
        my $cpu = $2;
        my $rssMB = sprintf(\"%.1f\", $rss / 1024);
        print \"$rssMB $cpu\";
        last;
    }
}
close(PS);
")
        set info to do shell script "perl -e " & quoted form of perlCode
        return info
end getProcessInfo

-- removeLastPart:
-- Returns the string 'str' without the last part starting with 'delim'
on removeLastPart(str, delim)
        set oldTIDs to AppleScript's text item delimiters
        set AppleScript's text item delimiters to delim
        if (count str's text items) > 1 then
                set str to str's text 1 thru text item -2
        end if
        set AppleScript's text item delimiters to oldTIDs
        return str
end removeLastPart

-- pidOfRunningApp:
-- Returns the pid of the process if the app 'appName' is running,
-- otherwise returns -1
on pidOfRunningApp(appName)
        tell application "System Events"
                try
                        set pid to the unix id of process appName
                on error
                        set pid to -1
                end try
        end tell
        return pid
end pidOfRunningApp

-- Copyright 2006  Cameron Hayne - macdev@hayne.net
--
-- This program is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.


hayne 01-06-2007 03:06 AM

Mark:
Thanks for the idea of using a 'run script' to avoid compile-time issues with AppleScripts referring to apps that might not be installed.
I'll probably integrate this into my version of the script soon.


All times are GMT -5. The time now is 01:42 PM.

Powered by vBulletin® Version 3.8.7
Copyright ©2000 - 2014, vBulletin Solutions, Inc.
Site design © IDG Consumer & SMB; individuals retain copyright of their postings
but consent to the possible use of their material in other areas of IDG Consumer & SMB.