Kyle Ward: Flex, AIR, Flash Developer.   
  • Home
  •  

    Server Load Monitor – With AIR and plink [putty]

    January 28th, 2011

    I created a Flex/AIR app that allows us to monitor the CPU usage, Memory usage and Server Load of our off site Linux server.

    It alerts us when any of these values exceed our configured maximums as well as storing the results in a sqlite database for viewing in either graph or table form.

      

    AIR’s Native Process connects to plink, commanding plink to log onto the required server and pass a shell script to be executed.

    Plink – a command-line interface to the PuTTY back ends;

    top.sh:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #!/bin/sh
    mpstat 1 1 | awk 'NR >3 {print $3}'
    echo "~"
    free -o | grep Mem | awk '{print $2}'
    echo "~"
    free -o | grep Mem | awk '{print $3}'
    echo "~"
    cat /proc/loadavg | awk '{print $1}'
    echo "~"
    cat /proc/loadavg | awk '{print $2}'
    echo "~"
    cat /proc/loadavg | awk '{print $3}'

    The shell script has six commands it expects values back from, they are delimited by tildes to allow me to parse the result of my concatenated ProgressEvent.STANDARD_OUTPUT_DATA results when the NativeProcessExitEvent.EXIT is fired.

    MonitorService.as:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
      //plink command
      private function returnStartupArgs():Vector.<String>
      {
       var output:Vector.<String> = new Vector.<String>();
       
       output.push("-batch");
       output.push("-l");
       output.push(configVO.username);
       output.push("-pw");
       output.push(configVO.password);
       output.push("-m");
       output.push(topSHFile.nativePath);
       output.push(configVO.server);
         
       return output;
      }

      //ProgressEvent.STANDARD_OUTPUT_DATA
      private function handleOutputData(e:ProgressEvent):void
      {
       _output += _process.standardOutput.readUTFBytes(_process.standardOutput.bytesAvailable);
      }

      //NativeProcessExitEvent.EXIT
      private function handleExit(e:NativeProcessExitEvent):void
      {
       var monitorResultVO:MonitorResultVO = iMonitorResultVOParser.createFromString(_output);

       validateMonitorResult(monitorResultVO);
      }
     
      private function validateMonitorResult(monitorResultVO:MonitorResultVO):void
      {
       if (iValidateMonitorResultVO.validate(configVO, monitorResultVO))
       {
        successSignal.dispatch(monitorResultVO);
       }else {
        monitorResultVO.errorCode = MonitorErrorCode.MAXIMUM;
        monitorResultVO.errorMessage = iValidateMonitorResultVO.errorMessage;
       
        failSignal.dispatch(monitorResultVO);
       }
      }
    }

    Gotcha:

    The server’s host key is not cached in the registry. You
    have no guarantee that the server is the computer you
    think it is.
    http://the.earth.li/~sgtatham/putty/0.55/htmldoc/Chapter10.html

    This message comes up if you are trying to access a server and you dont have it’s key in your registry. In putty it is a popup which you click and in plink it requires y/n.

    AIR or plink seems to close the process when this message is recieved and i am unable to repond. I now simply openWithDefaultApplication putty so it can it connect to the server and allow us to respond manually. Not very elegant but it only needs to happen once and then the app can carry on polling the server. Solution pending!


    mx:Style preventing a mx:Module being Garbage Collected

    November 1st, 2010

    Whilst profling i noticed that one of my mx:Module would not be cleared from memory.

    After an elimination process i found mx:Style to be the guilty party:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <mx:DateChooser   xmlns:mx="http://www.adobe.com/2006/mxml"
        weekDayStyleName="weekDayStyle">
       <mx:Style>
        .weekDayStyle {
         fontWeight: bold;
         ...
        }    
       </mx:Style>
       <mx:Script>
        <![CDATA[
         private function styleWeekDay():void
         {
          var style:Object = iStyleSheetM.getStyle(".dateChooserWeekDay");
          var declaration:CSSStyleDeclaration = styleManager.getStyleDeclaration(".weekDayStyle");
             
          declaration.setStyle("fontWeight", style.fontWeight);    
          ....
         }
        ]]>
       </mx:Script>    
    </mx:DateChooser>

    (My modules are styled from an external css file that gets loaded once after the applications startup and then gets implemented as you’ll see above. ie the default values in the script block get replaced by the values from the external css file.)

    Remove the mx:Style block and the mx:Module would be collected.
    But i need the style and therefore i found a solution:

    1
    2
    3
    4
    5
         public function shutdown():void
         {
          styleManager.clearStyleDeclaration(".weekDayStyle", true);
          ...
         }

    Perhaps i should use a better way to style my mx:Module, but at least this is a solution for now.


    Pagination on a Filtered ArrayCollection

    October 1st, 2010

    Thought this might be helpful as i couldn’t find a filtered example of pagination on Google.

    Simply i find a _minIndex and _maxIndex based on the galleryFilterVO.pagingIndex required as well as init an _index [initMinMax()].

    If the item successfully passes through the filterFunction “initial filter”, i then incremented the _index and see if it is in range of the _minIndex and _maxIndex.

    My ArrayCollection is now filtered to the specific data [in this case by categories] as well as been paginated.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
     public class GalleryListFilterCommand extends Command
      {
      [Inject] public var galleryListVO:GalleryListVO;  
      [Inject] public var galleryFilterVO:GalleryFilterVO;
     
      private var _minIndex:int;
      private var _maxIndex:int;
      private var _index:int;

      override public function execute():void
      {
       initMinMax();
       applyFilter();
      }  
     
      /*PRIVATE METHODS*/
      private function initMinMax():void
      {
       //http://cookbooks.adobe.com/post_Paged_ArrayCollection-11083.html
       _maxIndex = 9 * (galleryFilterVO.pagingIndex + 1);
       _minIndex = _maxIndex - 9;
       _index = -1;
      }
     
      private function applyFilter():void
      {
       galleryListVO.list.filterFunction = filterFunction;
       galleryListVO.list.refresh();  
      }
     
      private function filterFunction(item:GalleryListItemVO):Boolean
      {
       var output:Boolean = true;
       
       if (galleryFilterVO.categoryVO.id != Constants.SELECT_ALL)
       {
        output = (item.categoryID == galleryFilterVO.categoryVO.id);
       }
       
       if (output)
       {
        _index++;
       
        output = _minIndex <= _index && _index < _maxIndex;
       }
       
       return output;
      }
     }

    CIC – Communicator Installer Creator

    July 6th, 2010

    air2, flex4, robotlegs

    My employers [www.d6technology.com] provide a Desktop Application [PC/MAC] that allows companies/schools to communicate with their users. The application offers features such as News, Calendar, Gallery, Contacts, Resources etc.

    Although the same code base is used for each clients application, each application differs: Name, ID, Type, Skin etc. We also provide installers [PC/MAC] for our clients which they can either download or are provided by CD.  Like-wise each installer is different.

    Needless to say production is a tiresome process requiring above average IT knowhow, is prone to human error and is done by a non IT person with a turn around time is about 30mins.  I proposed therefore that we automate production by creating an application that would recieve input [details, images, assets], output the installers [exe/dmg] and remove all the work inbetween from our production department.

    The Process and The Solution:

    PC:

    1. Ensure the files the dtool.exe requires are named and path’d correctly.
    2. From the CMD inject dtool.exe with settings,  which will provide a customised exe that runs on the users machine.
    3. 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
        private function createEXEFromDTOOL():void
        {
         var process:NativeProcess = new NativeProcess();  
         var info:NativeProcessStartupInfo = new NativeProcessStartupInfo();
         var args:Vector.<String> = new Vector.<String>;

         args.push("inject");
         args.push("-e");
         args.push(_detailsVO.type.pcDTOOLName);
         args.push("-d");
         args.push( new File(PathsConstants.WIN_TMP_PATH).nativePath );
         args.push("-a");
         args.push(StringEdit.removeExtension(_engineVO.customD6WinAppEXE.name));
         args.push("-f");
         args.push(_detailsVO.feedID);    
         args.push("-s");
         args.push(_engineVO.mProjector.name);    
         //args.push(_assetsVO.mProjector.name);  
         info.arguments = args;
         info.workingDirectory = returnDTOOLDirectory();
         info.executable = _dtool;
         
         process.addEventListener(ProgressEvent.STANDARD_OUTPUT_DATA, handleDTOOLProcessOutput,false,0,true);
         process.addEventListener(ProgressEvent.STANDARD_ERROR_DATA,  handleDTOOLProcessError,false,0,true);
         process.addEventListener(NativeProcessExitEvent.EXIT,   handleDTOOLProcessExit,false,0,true);
         
         _message.sendMessage(MessageConstants.DEFAULT_SUCCESS_TITLE, "Starting DTOOL.", this);
         process.start(info);  
        }
    4. Edit the ISS [Inno Setup Script] with settings.
    5. 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
        private function populateISSFile():void
        {
         var shortname:String = StringEdit.removeExtension(_engineVO.customD6WinAppEXE.name);// returnShortName(_detailsVO.title);
         var mProjector:String = StringEdit.removeExtension(_engineVO.mProjector.name).substr(0,18);
         var customD6WinAppExe:String = StringEdit.removeExtension(_engineVO.customD6WinAppEXE.name).substr(0,18);
           
         _issCopy = _issCopy.replace(/%SHORTNAME%/g,   shortname);
         _issCopy = _issCopy.replace(/%MPROJECTOR_PATH%/g, _engineVO.mProjector.nativePath);
         _issCopy = _issCopy.replace(/%MPROJECTOR_NAME%/g, _engineVO.mProjector.name);
         _issCopy = _issCopy.replace(/%TITLE%/g,    _detailsVO.title);
         _issCopy = _issCopy.replace(/%COMMON%/g,   _assetsVO.commonFiles.nativePath+File.separator);
         _issCopy = _issCopy.replace(/%SPECIFIC%/g,   _assetsVO.specificFiles.nativePath+File.separator);
         _issCopy = _issCopy.replace(/%ICON%/g,    _imagesVO.icon.nativePath);
         _issCopy = _issCopy.replace(/%INSTALLER_BG%/g,  _imagesVO.installerBG.nativePath);
         _issCopy = _issCopy.replace(/%D6_EXE%/g,   _engineVO.customD6WinAppEXE.nativePath);
         _issCopy = _issCopy.replace(/%D6_EXE_NAME%/g,  _engineVO.customD6WinAppEXE.name);
         _issCopy = _issCopy.replace(/%OUTPUT_DIR%/g,  _engineVO.installer.parent.nativePath);
         _issCopy = _issCopy.replace(/%SHORT_MPROJ_TITLE%/g, mProjector);
         _issCopy = _issCopy.replace(/%SHORT_EXE_TITLE%/g, customD6WinAppExe);
         _issCopy = _issCopy.replace(/%INSTALLER%/g,   StringEdit.removeExtension(_engineVO.installer.name));

         process(++_processSEQ);
        }

        private function openISSForUPDATE():void
        {
         var fs:FileStream = new FileStream();
         
         //fs.addEventListener(Event.COMPLETE,   handleISSForUPDATEComplete,false,0,true);
         //fs.addEventListener(IOErrorEvent.IO_ERROR, handleISSForUPDATEIOError, false, 0, true);
         
         _message.sendMessage(MessageConstants.DEFAULT_SUCCESS_TITLE, "Opening ISS for UPDATE.", this);
         
         fs.open
         (
          returnISSCreateInstallerFile(),
          FileMode.WRITE
         );
         
         fs.writeMultiByte(_issCopy, CharSetConstants.WINDOWS_1252);
         fs.close();  
         process(++_processSEQ);  
        }
    6. Ensure the files the ISS requires are named and path’d correctly.
    7. Run the ISS which creates the installer.
    8. 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
        private function processISCC():void
        {
         var process:NativeProcess = new NativeProcess();  
         var info:NativeProcessStartupInfo = new NativeProcessStartupInfo();
         var args:Vector.<String> = new Vector.<String>;

         args.push("/Q");
         args.push(returnISSCreateInstallerFile().nativePath);  
       
         info.arguments = args;
         info.workingDirectory = returnWorkingDirectory();
         info.executable = _settingsVO.iscc;
         
         process.addEventListener(ProgressEvent.STANDARD_OUTPUT_DATA, handleISCCProcessOutput,false,0,true);
         process.addEventListener(ProgressEvent.STANDARD_ERROR_DATA,  handleISCCProcessError,false,0,true);
         process.addEventListener(NativeProcessExitEvent.EXIT,   handleISCCProcessExit,false,0,true);
         
         _message.sendMessage(MessageConstants.DEFAULT_SUCCESS_TITLE, "Starting ISCC.", this);
         process.start(info);  
        }

    MAC:

    1. Ensure the files the dtool.app requires are named and path’d correctly.
    2. From the Terminal run dtool.app with settings, which will package a customised app that runs on the users machine.
    3. 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
        public function process():void
        {
         _dtoolDirectory = new File(workingDirectory.nativePath+"/dtool");
         deleteDTOOLAppDirectory();
         
         _dtoolProcess = new NativeProcess();
         
         var info:NativeProcessStartupInfo = new NativeProcessStartupInfo();
         var args:Vector.<String> = new Vector.<String>;  
         
         args.push("package");
         args.push("-e");
         args.push(detailsVO.type.macDTOOLName);
         args.push("-r");
         args.push(assetsVO.specificFiles.nativePath);
         args.push("-s");
         args.push(engineVO.mProjector.nativePath);
         args.push("-f");
         args.push(detailsVO.feedID);
         args.push("-a");
         args.push(detailsVO.title);
         args.push("-t");
         args.push(detailsVO.title);
         args.push("-i");
         args.push(imagesVO.icon.nativePath);
         args.push("-d");
         args.push(_dtoolDirectory.nativePath);
         args.push("-c");
         args.push(_dtoolDirectory.nativePath+"/dtool.xml");
         args.push("-x");
         args.push(_dtoolDirectory.nativePath+"/d6.app/Contents/MacOS/d6");
         args.push("-d");
         args.push(_dtoolDirectory.nativePath+"/build");
         
         info.arguments = args;
         info.workingDirectory = _dtoolDirectory;
         info.executable = new File(_dtoolDirectory.nativePath+"/dtool");
         
         _dtoolProcess.addEventListener(ProgressEvent.STANDARD_OUTPUT_DATA,  handleDTOOLProcessOutput,false,0,true);
         _dtoolProcess.addEventListener(ProgressEvent.STANDARD_ERROR_DATA,  handleDTOOLProcessError,false,0,true);
         _dtoolProcess.addEventListener(NativeProcessExitEvent.EXIT,    handleDTOOLProcessExit,false,0,true);
         
         _dtoolProcess.start(info);  
        }
    4. Create a Packager [http://s.sudre.free.fr/Software/Packages.html] project with settings.
    5. Packaged with Installer

    6. Create the installer from the Packager project. [Updating the POST & PRE install scripts]
    7. 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
        private function parseInstallScripts():void
        {
         var pretitle:String = detailsVO.title
         
         //pretitle = pretitle.replace(/'/g,"\'")
         
         _preinstallCopy = _preinstallCopy.replace(/%TITLE%/g,pretitle);
         _postinstallCopy = _postinstallCopy.replace(/%TITLE%/g,detailsVO.title);

         processInstallScripts(2);
        }
       
        private function updateInstallScripts():void
        {
         var preinstallFS:FileStream = new FileStream();
         var postinstallFS:FileStream = new FileStream();
         var scripts:File = returnScriptsDirectory();
         var preinstallF:File = new File(scripts.nativePath+File.separator+PathsConstants.PREINSTALL_SH.toLowerCase());
         var postinstallF:File = new File(scripts.nativePath+File.separator+PathsConstants.POSTINSTALL_SH.toLowerCase());
         
         
         preinstallFS.open(preinstallF,FileMode.WRITE)
         preinstallFS.writeMultiByte(_preinstallCopy, CharSetConstants.MACINTOSH);
         
         postinstallFS.open(postinstallF,FileMode.WRITE);
         postinstallFS.writeMultiByte(_postinstallCopy, CharSetConstants.MACINTOSH);
           
         preinstallFS.close();
         postinstallFS.close();
         
         processInstallScripts(3);  
        }

        private function createPackage():void
        {
         var process:NativeProcess = new NativeProcess();
         var packageDirectory:File = returnPackageDirectory();
         var installerPKG:File = returnInstallerPKG();
         var info:NativeProcessStartupInfo = new NativeProcessStartupInfo();
         var args:Vector.<String> = new Vector.<String>;
         
         args.push("--doc");
         args.push(packageDirectory.nativePath+File.separator+"TEMPLATE.pmdoc");
         args.push("--version");
         args.push("1.0");
         args.push("--title");
         args.push(detailsVO.title);
         args.push("--id");
         args.push(returnIDByTitle(detailsVO.title));
         args.push("-w");
         args.push("--verbose");
         args.push("--out");
         args.push(installerPKG.nativePath);
         
         info.arguments = args;
         info.workingDirectory = packageDirectory;
         info.executable = settingsVO.packageMaker;
         
         process.addEventListener(ProgressEvent.STANDARD_OUTPUT_DATA, handlePackageProcessOutput,false,0,true);
         process.addEventListener(ProgressEvent.STANDARD_ERROR_DATA,  handlePackageProcessError,false,0,true);
         process.addEventListener(NativeProcessExitEvent.EXIT,   handlePackageProcessExit,false,0,true);
         
         process.start(info);
        }
    8. Init, Decorate, Compress DMG with background image and icon for Web/CD.[Using YourSway create-dmg]
    9. 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
        private function implementYourSway(type:String):void
        {
         var process:NativeProcess = new NativeProcess();  
         var info:NativeProcessStartupInfo = new NativeProcessStartupInfo();
         var args:Vector.<String> = new Vector.<String>();
         
         args.push("--volname");
         args.push(detailsVO.title);
         args.push("--background");
         args.push(_backgroundTMP.nativePath);
         args.push("--icon-size");
         args.push("32");
         args.push("--window-size");
         args.push("550");
         args.push("450");
         args.push("--icon");
         args.push(INSTALLER_PKG);
         args.push("450");
         args.push("215");
           
         if(type == EngineConstants.CD)
         {
          args.push("--icon");
          args.push
          (
           (detailsVO.type.id == DetailsConstants.CLUB_ID) ? "faq.xml" : "faq_mac.xml"
          );
          args.push("450");
          args.push("355");
         
          args.push("--icon");
          args.push("About Communicator");
          args.push("450");
          args.push("285");
         
          args.push(engineVO.cdDMG.nativePath);
          args.push
          (
           (detailsVO.type.id == DetailsConstants.CLUB_ID) ?  returnCLUBTemplateDIR().nativePath : returnSCHOOLTemplateDIR().nativePath
          );
         
         }else{
          /*TODO: Fix HORRIBLE HACK*/
          args.push("--icon");
          args.push("About Communicator");
          args.push("450");
          args.push("285");
          /*TODO: HORRIBLE HACK*/
         
          args.push(engineVO.webDMG.nativePath);
          args.push(returnEMPTYTemplateDIR().nativePath);    
         }
         
         
         info.arguments = args;
         info.workingDirectory = returnYourSwayDIR()
         info.executable = returnYourSwayApp();
         
         process.addEventListener(ProgressEvent.STANDARD_OUTPUT_DATA, handleImplementProcessOutput,false,0,true);
         process.addEventListener(ProgressEvent.STANDARD_ERROR_DATA,  handleImplementProcessError,false,0,true);
         process.addEventListener(NativeProcessExitEvent.EXIT,   handleImplementProcessExit,false,0,true);
         
         process.start(info);
        }

    I mainly used AIR 2′s Native Process for the steps above [a bit of a setup involved as well as having to create platform specific installers].
    This allowed me to interact with applications on the PC/MAC, send them commands and have them quitely [mostly] perform tasks for me:

    D6 Tool [Custom exe/app]: Custom/client specific exe/app.
    YourSway: Custom DMGs.
    Connecting to and instructing these tools was simple and straight forward.

    Inno Setup Compiler / PackageMaker: Create Installers.
    The Inno Setup Compiler just required its settings file edited and then passing as an argument with the instruction.
    PackageMaker was a total nightmare and took days to get it to work via the Terminal, eventually i got it right by a combination of a “TEMPLATE.pmdoc” and arguments which overwrote some of the settings in the doc.