Server Free Space Tracker

It was suggested by Adam Bell and James Nierodzik that I post this little script of mine for others to get some use out of. :wink:

It’s less useful as-is really than it is for the individual handlers and key lines of code, especially the parts dealing with updating a running datasheet in Excel and updating an Excel chart on an ongoing basis.

BACKGROUND
In my work environment we an AppleShare OS X server with 1.2 TB of storage…that the staff chronically eats-into at an alarming rate. We’ve put our foot down about adding more space, and so I got it into my head to put an “in your face” update on the amount of free space (as a percentage) that users can’t miss.

At the same time, I wanted to track long-term trends of server useage.

DEPENDENCIES
This script will not work “as-is” in the wild. It depends on certain preconditions.

–“SERVER STATUS” folder at the root level of the server called “Diamond Design”: clever scripters can likely find better ways to customize this.

–A pre-existing spreadsheet named “Diamond Server Free Space.xls” with two tabs…“Data Capture” and “Charting”

–A pre-existing, pre-formatted chart on the “Charting” tab. This script cannot generate a chart from scratch. Try as I might, I could never get Excel to do that in a way I was happy with. But, it does have a feature called “ExtendSeries” which will very neatly append data to an existing chart.

–The “Data Capture” tab contains four columns…Date (A), Capacity (B), Free Space (C), and Percentage Free (D). Percentage Free is calculated by Excel as “=C2/B2”. columns B and C are numbers to two decimal places.

–Requires Microsoft Office X. A quick Get Info on Excel shows version “10.1.5 (030814)”. Pretty good bet this script will break under Excel 2004.

–I think LogMeLibrary depends on a locally-stored logging routine. Just kill all the “logMe” and “logMeLibrary” lines and you won’t cause any harm to the script…they’re for debugging and logging and don’t affect how it works. I’ve included it at the bottom of this post too, for the curious.

–Under Panther we had a problem with this script encountering an OS bug which caused it not to work properly every other time it was run. You will see some of that remnant code commented out and checks against gBugFlag still existing, which caused the scrip tto execute TWICE to overcome the problem. Tiger doesn’t seem to cause this problem so I started to comment-out those sections. Uncomment them to put the “bug check” safeties back in.

Probably other dependencies I might have missed. I’ll update this as I notice or have them brought to my attention.

THE BASICS
The script on my end is set to activate in the wee hours of the morning when no one is around (using CronniX). Diamond Design is a mounted volume on my local desktop. At execution it loads a couple external shared routines (provided after the main script below), then gets the free space and capacity from the server and converts them to gigabytes. The results are rounded, truncated, and converted out of exponential notation.

The bogus text file which “displays” the free space is deleted if it exists so a new one can be put in it’s place. It’s just a generic text file.

Excel data is updated, as is the chart. You will notice the “alert” file and the Excel data also have warning hightlights:

–Green or no coloring for over 20% free space.
–Yellow highlighting for 11-20% free space
–Red highlighting at 0-10%

SUPPORT/DISCLAIMER
Provided as-is. Originally it was suggested this go into ScriptBuilders but I don’t have the time to make it more generally useable “as is” (i.e. the boss isn’t going to let me…)…it’s up to y’all to customize it for yourselves. So it’s into Code Exchange instead. :wink:

However, I’ve subscribed to this thread and will answer e-mails if I can.

I also have no doubt there are more efficient ways to do this. I bet Nigel could turn this into a dozen lines of code and add a few features in the process. :wink:

MAIN SCRIPT


--Diamond Design Monitor
--by Kevin Quosig
--
--Started: 4/11/06
--Last Update: 4/11/06
--
--Use Excel to build table (and possibly graph) that tracks
--Diamond Design free space.
--


--
-- DECLARE VARIABLES
--

global gReturnChar
global gTabChar

global gFreeSpace
global gCapacity

global gEndRow
global gFreePercent
global gFreePercentColor

global gDateStamp

global gPrefaceExists
global gBugFlag

--
--SUBROUTINES, DEBUGGING
--
set targetLibrary to "Data: Automation:Script Library:logMe.scpt" as string
load script file targetLibrary
set logMeLibrary to result

--
--SUBROUTINES, UTILITY
--
set targetLibrary to "Data: Automation:Script Library:dateStamp.scpt" as string
load script file targetLibrary
set dateStampLibrary to result

--
-- SUBROUTINES, FINDER
--

--create reverse-order date stamp (YYMMDD)
tell dateStampLibrary
	dateStamp()
end tell


--get basic volume information
on getVolumeInfo()
	set serverName to "Diamond Design"
	
	tell application "Finder"
		set capacityRaw to (capacity of disk serverName)
		set freeSpaceRaw to (free space of disk serverName)
		
		--trap for free space "missing value" OS bug on every-other launch		
		if freeSpaceRaw is missing value then
			set gBugFlag to "yes"
		else
			set gBugFlag to "no"
		end if
		
		if gBugFlag is "no" then
			--convert to gigabytes (GB)
			set capacityRawGB to (capacityRaw / 1024 / 1024 / 1024)
			set freeSpaceRawGB to (freeSpaceRaw / 1024 / 1024 / 1024)
		end if
		
		--
	end tell
	
	if gBugFlag is "no" then
		set gFreeSpace to round_truncate(freeSpaceRawGB, 2)
		set gCapacity to round_truncate(capacityRawGB, 2)
	end if
end getVolumeInfo

--round number and truncate
--(from Apple's web site, requires "text_to_number" script)

on round_truncate(this_number, decimal_places)
	if gBugFlag is "no" then
		if decimal_places is 0 then
			set this_number to this_number + 0.5
			return number_to_text(this_number div 1)
		end if
		
		set the rounding_value to "5"
		repeat decimal_places times
			set the rounding_value to "0" & the rounding_value
		end repeat
		set the rounding_value to ("." & the rounding_value) as number
		
		set this_number to this_number + rounding_value
		
		set the mod_value to "1"
		repeat decimal_places - 1 times
			set the mod_value to "0" & the mod_value
		end repeat
		set the mod_value to ("." & the mod_value) as number
		
		set second_part to (this_number mod 1) div the mod_value
		if the length of (the second_part as text) is less than the ¬
			decimal_places then
			repeat decimal_places - ¬
				(the length of (the second_part as text)) times
				set second_part to ("0" & second_part) as string
			end repeat
		end if
		
		set first_part to this_number div 1
		set first_part to number_to_text(first_part)
		set this_number to (first_part & "." & second_part)
		
		return this_number
	end if
end round_truncate

--convert scientific notation
--(from Apple's web site)
on number_to_text(this_number)
	set this_number to this_number as text
	if this_number contains "E+" then
		set x to the offset of "." in this_number
		set y to the offset of "+" in this_number
		set z to the offset of "E" in this_number
		set the decimal_adjust to characters (y - (length of this_number)) thru ¬
			-1 of this_number as string as number
		if x is not 0 then
			set the first_part to characters 1 thru (x - 1) of this_number as string
		else
			set the first_part to ""
		end if
		set the second_part to characters (x + 1) thru (z - 1) of this_number as string
		set the converted_number to the first_part
		repeat with i from 1 to the decimal_adjust
			try
				set the converted_number to ¬
					the converted_number & character i of the second_part
			on error
				set the converted_number to the converted_number & "0"
			end try
		end repeat
		return the converted_number
	else
		return this_number
	end if
end number_to_text


-- check file name for supplied alert preface
on examineFileName(checkFileName, checkPreface)
	--get preface of supplied file name
	set nameLength to length of checkFileName
	set prefaceLength to length of checkPreface
	set currentPreface to (text items 1 thru prefaceLength) of checkFileName as string
	
	--check preface of supplied file against sought preface and report
	if currentPreface is equal to checkPreface then
		set gPrefaceExists to "yes"
	else
		set gPrefaceExists to "no"
	end if
end examineFileName


--post notice to Server Status Folder
on serverStatus(messagePrefix, messageString, messageColor)
	if gBugFlag is "no" then
		set messagePath to "Diamond Design: SERVER STATUS:"
		set messageName to (messagePrefix & "--" & messageString & "--" & gDateStamp) as text
		set messageAlias to (messagePath & messageName) as text
		
		--get list of files
		tell application "Finder"
			set fileList to name of every file of (messagePath as alias)
			set listCount to number of items in fileList
		end tell
		
		--check each file in list for existing preface, delete if found
		repeat with listItem from 1 to listCount
			examineFileName(item listItem of fileList, messagePrefix)
			if gPrefaceExists = "yes" then
				set deleteFilePath to (messagePath & (item listItem of fileList)) as text
				tell application "Finder"
					delete alias deleteFilePath
				end tell
			end if
		end repeat
		
		--create file and title with status message
		tell application "Finder"
			make new file at messagePath with properties {name:messageName}
			set label index of (messageAlias as alias) to messageColor
			open for access file messageAlias with write permission
			write messageName to (messageAlias as alias) starting at eof
			close access (messageAlias as alias)
		end tell
	end if
end serverStatus



--
-- SUBROUTINES, EXCEL
--

on appendSpreadsheet(currentCapacity, currentFreeSpace)
	if gBugFlag is "no" then
		set spreadSheetFile to "Diamond Server Free Space.xls"
		set spreadSheetFolder to "Data: Automation:0100--Diamond Design Monitor:"
		set spreadSheetLocation to (spreadSheetFolder & spreadSheetFile) as text
		set tempFile to "Temp.xls"
		set tempLocation to (spreadSheetFolder & tempFile) as text
		
		tell application "Microsoft Excel"
			Activate
			Open spreadSheetLocation
			--
			--set correct sheet
			Select Sheet "Data Capture"
			
			--set starting row to begin search for empty cell to skip header
			set currentRow to "2"
			set currentColumn to "1"
			set currentCell to ("R" & currentRow & "C" & currentColumn) as text
			Select Range currentCell
			
			--find next empty cell to begin data entry
			repeat until Formula of ActiveCell is ""
				set currentRow to (currentRow + 1)
				set currentCell to ("R" & currentRow & "C" & currentColumn) as text
				Select Range currentCell
			end repeat
			
			--store row for use in selecting data for charts
			set gEndRow to (currentRow - 1)
			
			--enter today's date
			Select Range currentCell
			set Formula of ActiveCell to current date
			
			--move over and enter capacity data
			set currentColumn to (currentColumn + 1)
			set currentCell to ("R" & currentRow & "C" & currentColumn) as text
			
			Select Range currentCell
			set Formula of ActiveCell to currentCapacity
			
			--move over one column and enter free space data
			set currentColumn to (currentColumn + 1)
			set currentCell to ("R" & currentRow & "C" & currentColumn) as text
			
			Select Range currentCell
			set Formula of ActiveCell to currentFreeSpace
			
			--move over one column and enter percentage free calculation
			set currentColumn to (currentColumn + 1)
			set currentCell to ("R" & currentRow & "C" & currentColumn) as text
			
			Select Range currentCell
			set Formula of ActiveCell to ("=C" & currentRow & "/B" & currentRow) as text
			
			--is server space low?
			set gFreePercent to (Value of ActiveCell) * 100
			set gFreePercentColor to "6"
			
			if gFreePercent is less than or equal to 20 then
				if gFreePercent is less than or equal to 10 then
					--select current data, color red
					Select Range ("R" & currentRow & "C1:R" & currentRow & "C4")
					set ColorIndex of Interior of Selection to 38.0
					set Pattern of Interior of Selection to xlSolid
					set gFreePercentColor to "2"
				else
					--select latest data, color yellow
					Select Range ("R" & currentRow & "C1:R" & currentRow & "C4")
					set ColorIndex of Interior of Selection to 36.0
					set Pattern of Interior of Selection to xlSolid
					set gFreePercentColor to "3"
				end if
			end if
			
			--update existing chart
			--(requires pre-existing chart, but easier to implement)
			--
			Select Sheet "Data Capture"
			set myRange to Range ("A" & (gEndRow + 1) & ",D" & (gEndRow + 1)) of ActiveSheet
			Select Chart "Charting"
			ExtendSeries of every Series of ActiveChart RowOrCol xlColumns Source myRange
			
			--reset to data entry sheet
			Select Sheet "Data Capture"
			
			--
			Save ActiveWorkbook In tempLocation As xlNormal --Excel can't overwrite existing, save in temp file
			Close ActiveWorkbook
		end tell
		
		--delete old file, swap and rename temp file
		tell application "Finder"
			delete alias spreadSheetLocation
			update alias spreadSheetFolder
			set name of alias tempLocation to spreadSheetFile
		end tell
	end if
end appendSpreadsheet

--
-- EXECUTION
--
-- starting log entry
tell logMeLibrary
	logMe("---", 1, "LOG--Overnight Automation.txt")
	logMe("Diamond Design Monitor Begin--" & (current date), 1, "LOG--Overnight Automation.txt")
end tell

-- collect info
getVolumeInfo()

--update Excel spreadsheet
if gBugFlag is "no" then
	appendSpreadsheet(gCapacity, gFreeSpace)
end if

--update Server Notice
if gBugFlag is "no" then
	serverStatus("FREE SPACE", round_truncate(gFreePercent, 2) & "%", gFreePercentColor)
end if

-- ending log entry
tell logMeLibrary
	--logMe("[Script run twice to trap for Free Space Bug]", 2, "LOG--Overnight Automation.txt")
	if gBugFlag is "yes" then
		logMe("Free Space Bug trapped", 2, "LOG--Overnight Automation.txt")
	else
		logMe("Free Space Bug not encountered", 2, "LOG--Overnight Automation.txt")
		
	end if
	logMe("Diamond Design Monitor End--" & (current date), 1, "LOG--Overnight Automation.txt")
	logMe("---", 1, "LOG--Overnight Automation.txt")
end tell

dateStamp LIBRARY


--
--General Date Stamp Script (for use as library)
--by Kevin Quosig
--
--Created: 4/18/06
--Last Modified: 4/18/06
--
--This creates a date stamp in the format YYMMDD
--I prefer this format since it sorts better, especiall
--in the Finder, where I like to use it in file names
--for version control
--

global gDateStamp

on dateStamp()
	-- Load date components from system
	set dayStamp to day of (current date) as integer
	set monthStamp to month of (current date) as text
	set yearStamp to year of (current date) as text
	
	--Coerce day component to two-digit form
	if dayStamp < 10 then set dayStamp to "0" & dayStamp
	
	--Coerce month component to two-digit form
	if monthStamp is "January" then set monthStamp to "01"
	if monthStamp is "February" then set monthStamp to "02"
	if monthStamp is "March" then set monthStamp to "03"
	if monthStamp is "April" then set monthStamp to "04"
	if monthStamp is "May" then set monthStamp to "05"
	if monthStamp is "June" then set monthStamp to "06"
	if monthStamp is "July" then set monthStamp to "07"
	if monthStamp is "August" then set monthStamp to "08"
	if monthStamp is "September" then set monthStamp to "09"
	if monthStamp is "October" then set monthStamp to "10"
	if monthStamp is "November" then set monthStamp to "11"
	if monthStamp is "December" then set monthStamp to "12"
	
	-- Coerce year into two-digit form
	set yearStamp to characters 3 thru 4 of yearStamp as text
	
	--Assemble datestamp
	set gDateStamp to yearStamp & monthStamp & dayStamp as text
	
end dateStamp

logMe LIBRARY


--
--General Logging Script (for use as library)
--by Kevin Quosig
--
--Created: 4/3/06
--Last Modified: 4/18/06
--


global gReturnChar
global gTabChar

on logMe(logString, indentLevel, logFileName)
	set logFilePath to "Data: Automation:Logs:"
	set logFile to (logFilePath & logFileName) as text
	
	try
		writeEntry(logString, indentLevel, logFile)
	on error
		tell application "Finder"
			make new file at logFilePath with properties {name:logFileName}
			close access (logFile as alias)
		end tell
		writeEntry(logString, indentLevel, logFile)
	end try
end logMe

on writeEntry(logString, indentLevel, logFile)
	set gReturnChar to "
"
	set gTabChar to "	"
	
	tell application "Finder"
		open for access file logFile with write permission
		repeat indentLevel times
			write gTabChar to (logFile as alias) starting at eof
		end repeat
		write (logString & gReturnChar) to (logFile as alias) starting at eof
		close access (logFile as alias)
	end tell
end writeEntry