Capacity of MC on VA-HR

I am evaluating the VA-HR as a platform to host my pebble app.
The Programmers Guide lists some run-time errors such as :

  • Too Many Timers (what is the limit?)
  • Too Many Arguments (is 9 the limit?)


I know this isn't exhaustive, eg. Too Many Objects Error:
Before committing resources to porting the app, I am looking for a comprehensive specification of the actual capacity and limits of the platform.
Is such a document available?
  • It might be nice to have this, but it's not currently there.

    Might be nice???
    Boy, you ain't kidding!:mad:
    When there is a) an undefined limit (some say 256, but it's clearly more than that) and b) no way to monitor the usage and c) I'm planning to build a very large app, I can see a lot of tears ahead.:(
  • It would be nice (I believe it was said 256 on some devices, but could be 512 on the va-hr, as apps can be much larger with more memory on a va-hr vs a 1.x device).

    Part of it, is that you really want to design your data so you don't hit the max or come close, and that's things like not using multi-dimension arrays (your code for just the array would actually be 501 arrays ("an array of arrays"), where you could have 1 array with the same data, and just use the [i,j] as an index to to that single array.

    It's not that hard to write large, complex apps with lots of data, if you use the correct data structure.
  • Yes, I clearly hear the mantra about multi-dimensional arrays, but as you suggested, my problem with arrays probably isn't the memory usage, but the object usage.

    I have 3,000 lines of javascript and 30Kb of compiled C in my app to port from pebble so what I'm trying to do in this evaluation phase is to understand just what the limits are and how to monitor them before I get part way through the project and either hit the wall or have to re-work my code.

    Obviously, staying away from the limits is good advice, but that can only be achieved once the limits are well understood.
  • This is a hack, but you should be able to check the current object count with the following code.

    // tricky function to get the current number of objects
    function getNumberOfObjects() {

    var obj = new Lang.Object();

    // `s' is "Obj: 123"
    var s = obj.toString();

    // strip off "Obj: "
    s = s.substring(5, s.length());

    // now we have the object number
    return s.toNumber();
    }


    If you want to discover the maximum object count, this seemed to work just fine...

    using Toybox.Application as App;
    using Toybox.Lang as Lang;
    using Toybox.System as Sys;
    using Toybox.WatchUi as Ui;

    class XView extends Ui.View
    {
    function initialize() {
    View.initialize();
    }
    }

    class X
    {
    hidden var _M_self;

    function initialize() {
    // reference ourselves. this forces a leak of self.
    _M_self = self;
    }
    }

    class XApp extends App.AppBase
    {
    function initialize() {
    AppBase.initialize();
    }

    function getInitialView() {
    return [ new XView() ];
    }

    hidden var _M_timer;

    function onStart(params) {
    _M_timer = new Timer.Timer();
    _M_timer.start(self.method(:onTimer), 25, true);
    }

    function onStop(params) {
    _M_timer.stop();
    _M_timer = null;
    }

    function onTimer() {
    var x = new X();

    // if you want to run this on a device, I recommend doing much less frequently
    Sys.println(x);
    }
    }


    On the simulator the output is...

    Device Version 0.1.0
    Device id 1 name "A garmin device"
    Shell Version 0.1.0
    Timer interval is too small. It has been set to the minimum supported value.
    Obj: 20
    ...
    Obj: 500
    Obj: 501
    Obj: 502
    Obj: 503
    Obj: 504
    Obj: 505
    Obj: 506
    Obj: 507
    Obj: 508
    Obj: 509
    Failed invoking <symbol>
    Too Many Objects Error
    in println (D:\jenkins\workspace\Tech-CIQ-Win-Rel\mbsimulator\submodules\technology\monkeybrains\virtual-machine\api\System.mb:216)
    in onTimer (C:\Users\Travis\workspace\X\source\XApp.mc:47)
    Failed invoking <symbol>
    Error in timer callback
    Connection Finished
    Closing shell and port
  • Brilliant, thanks.
    I can now start to manage my object count and even see the GC kick in every 60 seconds to take out the trash.

    I guess there's no way to programmatically release an object (the one you create in getNumberOfObjects) is there?
    [EDIT]
    Ah, it looks like the objects created in a function are released when the function completes.
  • But something else is happening that I don't understand.
    I have let my app run for 20 mins and captured the object count output from getNumberOfObjects() every second, charted the results and annotated the image attached.

    The GC is still kicking in each minute, bringing the object count down to a MIN of around 100 and then it climbs up to a MAX of around 400, but as time marches on, the trace of object count becomes increasingly "noisy" till at around 20 minutes it's jumping all over the place, going from the MIN to the MAX in a couple of seconds and then back.
    Since I'm only at the start of my code migration, I need to get a better handle on what's happening here.

    I should add that the code is simply processing the simulated GPS data doing some manipulation on the data received in the last 60 seconds and displaying the data. The processing does not get more intense with time. I maintain an array of 60 seconds of data, pushing the old data down, losing the oldest record and pushing the new data on the top. The single dimension array remains a fixed size (60 data records of 10 properties = 600 rows).

    After 50 minutes, the chart looks like this:


    After 90 mins it's going from MIN to MAX in a second:
    getNumberOfObjects,297
    getNumberOfObjects,97
    getNumberOfObjects,407
    getNumberOfObjects,394


    I suspect something else is at work. Here, after nearly two hours of running, the number is becoming quite random, increasing and decreasing by varying amounts every second:
    getNumberOfObjects,153
    getNumberOfObjects,395
    getNumberOfObjects,155
    getNumberOfObjects,129
    getNumberOfObjects,344
    getNumberOfObjects,294
    getNumberOfObjects,248
    getNumberOfObjects,376
    getNumberOfObjects,292
    getNumberOfObjects,207



    Could this be due to the object number not actually being incremented on each instantiation but by the object number generator re-using object numbers released during the GC?
    This might mean that the object number generated by new Lang.Object() isn't in fact the number of objects, but the first one of the free object numbers available?

    This is real compiler archaeology ... wouldn't it be nice if the guys who look after the compiler could explain it to us?
  • Travis - interesting! I wouldn't have thought of using the object number!

    RaceQs - What may be happening with the noise, is that an object is being freed that's below the max, so when you run Travis' code, it sees a lower object number than max. If you were using 300 objects, and #150 is freed, you'll get the number 150, which isn't really the count used at that time. Same with the drop back to the min. It could just be you're seeing a really low object number. Garbage collection likely would only be used for memory (make free memory contiguous, etc), where with objects, GC probably isn't involved (assuming a fixed number of objects that are the same size, all you really need is a list of those available, and no reason to GC them.)

    What you may want to do is use travis' function to track the peak objects used, as current could be implicated when objects are returned.
  • Yes, Jim, you're right, GC isn't operating on the objects. I was blindsided by the 60-second cycle in the sawtooth chart.

    When I reduce the number of data points in my array, from 60 to, say, 40, guess what? The sawtooth period drops to 40 seconds.
    I don't understand that.
    What I don't understand, though, is why the object count (or object index) climbs initially. Isn't an array a single object?

    for (i = rows-2; i>=0; i--){ // move them all down one and lose the last
    for (j =0; j< cols; j++){
    track[ j*rows + i +1] = track[ j*rows + i];
    }
    }

    track[0*rows ] = timeSecs;
    track[1*rows ] = lat;
    track[2*rows ] = lon;
    track[3*rows ] = SOG;
    track[4*rows ] = COG;
    track[5*rows ] = TWD;
    track[6*rows ] = DRLat;
    track[7*rows ] = DRLon;


    Each second I'm populating the array with eight values and initially, the object count increases by 4.
    I wonder why?
  • I hadn't thought about it, but Jim is exactly right. Object numbers are recycled. The function above simply returns an unused object id, not the object count. If you aren't allocating and deallocating objects (you claim that you have a fixed array of data), then I don't really see how the object count should be a problem after initialization.

    Do you have some code I can look at?

    Travis
  • here's the module - it's pretty ugly, as I'm very much in an exploratory mode
    using Toybox.Communications as Comm;
    using Toybox.ActivityRecording as Record;
    using Toybox.Time.Gregorian as Gregorian;
    using Toybox.WatchUi as Ui;
    const mps2Knots = 1.94384;
    const degreesToRadians = 0.0174533;
    const nmToMetres = 1852;
    const degreesToMetres = 111120; //1 deg = 60 NM = 60 * 1852 metres /Nm
    const rows = 60; //60 seconds of data
    const cols = 10; // up to 10 parameters
    class engineClass {
    var m_counter = 0;
    var track ; // holds recent (30-60 sec's) data
    function start(){
    System.println("Al. engine start");
    System.println("enable GPS");
    Position.enableLocationEvents(Position.LOCATION_CONTINUOUS, method(:dispData));
    track = new [ rows*cols];
    //checkMemory();
    //checkLogin();
    }


    function checkMemory(){
    var memTestTrack;
    System.println("checkMemory");
    //var stats = System.getSystemStats().usedMemory;
    System.println("Before, "+ System.getSystemStats().usedMemory+
    ", "+ System.getSystemStats().freeMemory+
    ", "+ System.getSystemStats().totalMemory);
    var testRows = 1000;
    var testCols = 5;
    memTestTrack = new [testRows*testCols];
    for (var i=0; i<300; i++){ // crashes with 465
    /* var trkPt = {
    "time"=>12345,
    "lat"=>-33.234,
    "lon"=>155.123,
    "sog"=>5.4,
    "cog"=>123
    };

    tracks= {
    :time=>12345,
    :lat=>-33.234,
    :lon=>155.123,
    :sog=>5.4,
    :cog=>123
    };
    */ memTestTrack= [
    12345,
    -33.234,
    155.123,
    5.4,
    123
    ];
    /* tracks[0*testRows + i] = 12345;
    tracks[1*testRows + i] = -33.234;
    tracks[2*testRows + i] = 155.123;
    tracks[3*testRows + i] = 5.4;
    tracks[4*testRows + i] = 123;
    */ System.println(i+ ", Used:"+ System.getSystemStats().usedMemory+
    ", Free: "+ System.getSystemStats().freeMemory);

    }
    //stats = System.getSystemStats();

    //System.println(tracks[0]["time"] + "..." +tracks[999]["time"] );

    }
    function checkLogin(){
    var url="gpsanimator.com/.../login.php";
    var headers = {
    "Content-Type" => Comm.REQUEST_CONTENT_TYPE_URL_ENCODED,
    "Accept" => "application/json"
    };
    Comm.makeWebRequest(
    url,
    {
    "check"=>0,
    "GUID"=>Application.getApp().getProperty("GUID")}, // 0 if not set
    { :headers => headers,
    :method => Comm.HTTP_REQUEST_METHOD_POST,
    :responseType => Comm.HTTP_RESPONSE_CONTENT_TYPE_JSON
    },
    method(:loginCheckCallback)
    ) ;
    }

    function loginCheckCallback(responseCode, data) {
    System.println("loginCheckCallback(): " + responseCode+ " data:" + data);
    if (responseCode == 200) {
    if (data["GUID"]){ // initial setup generating GUID
    Application.getApp().setProperty("GUID", data["GUID"]);
    System.println("Received GUID: "+ Application.getApp().getProperty("GUID", data["GUID"]));
    }
    System.println("logged in???: "+data["userName"]);
    Application.getApp().setProperty("Login", data["userName"]);
    }
    }
    function degrees(val){
    val *= 180/ Math.PI;
    val +=360*(val<0?1:0);
    return val;
    }
    function getNumberOfObjects() {
    return (new Lang.Object()).toString().substring(5,20).toNumber();
    }
    var prevTimestampSeconds = 0;
    var SOG; //old presentPosData.SOG -in kts
    var COG; //old presentPosData.COG -in degrees TRUE
    var lat, lon; // in degrees and decimals
    var timeSecs; // JS used timeMs, but MC only has whole seconds
    var cosLatRatio;
    var MAG_VAR = -99; // " " doesn't do the trick
    var TWD = -1;
    var DRLat, DRLon;
    var magVars = [[null,14,18,18,10,0,-10,-20,20,null,null,-6,2,8,null,null,null,null,null,null,null,-12,-8,-2],
    [null,null,null,14,12,6,-8,-14,null,null,null,-2,0,4,6,6,null,null,null,null,-8,-8,null,null],
    [null,10,null,null,10,4,-6,-14,null,null,-8,-4,null,null,2,2,0,0,0,-2,-4,null,null,null],
    [null,null,null,null,null,null,-8,-10,-16,null,-10,-8,-4,null,2,2,-2,-2,0,0,0,0,null,null],
    [null,null,null,null,null,null,0,-10,-20,-22,null,null,-8,null,-4,null,null,null,-2,0,2,4,8,null],
    [null,null,null,null,null,null,null,2,-18,-24,null,null,-15,null,-20,-20,null,null,null,0,null,8,10,16],
    [null,null,null,null,null,null,null,0,-10,null,null,null,null,-24,-28,null,null,null,null,-4,4,10,15,20],
    [null,null,null,null,null,null,-12,10,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,22]];
    var trackPointCounter = 0; // increment at start of loop

    function dispData(positionInfo) {
    //working variables
    var timestruct,clocktime,coordsArray,thisTimeStampSeconds;
    var timeDelta,distDelta,degDelta ;
    var SOG15, SOG30 , formattedMagCOG5 ;
    var COG5 , COG15 , COG30 ; // initialized values
    var grooveDebugMsg, trkPtIdx, thisPos;
    var i,j;
    // create a session if we've started a timer and have GPS
    if (//timerView.timerZero != null && //#TODO understand timerZero
    $.session == null) {
    System.println("Al. starting to record");
    $.session = Record.createSession({ :name => "sailing", :sport => Record.SPORT_GENERIC });
    $.session.start();
    }
    //var timeStamp = positionInfo.when;
    timestruct = Gregorian.info(positionInfo.when, Time.FORMAT_LONG);
    clocktime = timestruct.hour.format("%02u") + ":" + timestruct.min.format("%02u") + ":" + timestruct.sec.format("%02u");
    m_counter++;
    //System.println("Al. formatted time: "+m_counter +":"+ clocktime);
    speed = positionInfo.speed;
    // $.gpsAccuracy = positionInfo.accuracy;
    heading = positionInfo.heading;
    // var coords = positionInfo.Location;
    coordsArray= positionInfo.position.toDegrees();
    //System.println("Al. report: "+m_counter + ", Lat,Lon: "+coordsArray[0]+","+ coordsArray[1]);
    thisTimeStampSeconds = positionInfo.when.value();
    if (thisTimeStampSeconds == prevTimestampSeconds){
    //System.println("Al. Same timestamp:" +thisTimeStampSeconds);
    return;
    }
    else{
    prevTimestampSeconds = thisTimeStampSeconds;
    }
    trackPointCounter ++;
    SOG = positionInfo.speed * mps2Knots;
    COG = degrees(positionInfo.heading) ;
    //System.println("COG;"+COG);
    lat = positionInfo.position.toDegrees()[0];
    lon = positionInfo.position.toDegrees()[1];
    cosLatRatio = Math.cos(lat*degreesToRadians); //JS: 1858
    timeSecs = positionInfo.when.value();
    if (MAG_VAR == -99 ){ //not yet set
    // test Sydney =>15. lat = -33; lon = 151;
    j = Math.round((60-lat)/15).toNumber();
    i = Math.round((lon+180)/15).toNumber();
    System.println("Al. i, j:" +i + "," +j);
    if(magVars[j]== null){ // no entry for this co-ordinate
    MAG_VAR = 0;}
    else {
    MAG_VAR = magVars[j];
    MAG_VAR = (MAG_VAR == ""?0:MAG_VAR);
    }
    System.println("Al. MAG_VAR:" +MAG_VAR);
    }


    if (trackPointCounter ==1){// calculate DR position for course calculations. JS 1898
    DRLat = lat;
    DRLon = lon;
    }
    else{// calculate DR position for course calculations.
    timeDelta = timeSecs - track[0*rows] ; // seconds
    distDelta = SOG/ mps2Knots * timeDelta; // Metres. using the raw SOG is m/sec
    //var trueHdgRads = positionInfo.heading; #heading is already in radians
    degDelta = distDelta * Math.cos( positionInfo.heading) / degreesToMetres; // in degrees.
    DRLat = track[6*rows] + degDelta; // previous DRLat
    degDelta = distDelta * Math.sin( positionInfo.heading)/ degreesToMetres / cosLatRatio;
    DRLon = (track[7*rows] + degDelta);
    }

    for (i = rows-2; i>=0; i--){ // move them all down one and lose the last
    for (j =0; j< cols; j++){
    track[ j*rows + i +1] = track[ j*rows + i];
    }
    }

    track[0*rows ] = timeSecs;
    track[1*rows ] = lat;
    track[2*rows ] = lon;
    track[3*rows ] = SOG;
    track[4*rows ] = COG;
    track[5*rows ] = TWD;
    track[6*rows ] = DRLat;
    track[7*rows ] = DRLon;
    //System.println("DR Drift. Lat:"+(DRLat -lat)*degreesToMetres + ", Lon:"+ (DRLon -lon)*degreesToMetres);
    COG5 = null; COG15 = null; COG30 = null; // initialized values
    /* if (trackPointCounter >= 30 ) { //need 30 sec's data
    COG5 = null; COG15 = null;COG30 = null; // initialized values

    for ( trkPtIdx = 0; trkPtIdx<30; trkPtIdx++){ // create GOG 5, 15, 30
    thisPos = {
    :timeSecs => track[0*rows + trkPtIdx],
    :DRLat => track[6*rows + trkPtIdx],
    :DRLon => track[7*rows + trkPtIdx]
    };
    if (track[0*cols + trkPtIdx] == null)
    {continue;
    }
    if (COG5 == null){
    if (timeSecs - thisPos[:timeSecs] >= 5) { // found first point over 5 secs
    //COG5 = Math.atan2( cosLatRatio*(presentPosData.lon -thisPos.lon), (presentPosData.lat -thisPos.lat))*radsToDegs;
    COG5 = Math.atan2( cosLatRatio*(DRLon -thisPos[:DRLon]), (DRLat -thisPos[:DRLat]))/degreesToRadians;
    COG5 += 360*(COG5<0?1:0); // 0<= COG05 <360
    formattedMagCOG5 = Math.round(COG5 - MAG_VAR);
    formattedMagCOG5 +=360*(formattedMagCOG5<0?1:(formattedMagCOG5 >=360?-1:0));
    formattedMagCOG5 = (formattedMagCOG5 <10?"0":(formattedMagCOG5<100?"0":""))+ formattedMagCOG5.toString();
    //System.println("formattedMagCOG5,"+COG + ","+ formattedMagCOG5);
    } // >= 5
    }// COG5==null
    } // for trackPts
    } //> 30 sec's data
    */
    //if (trackPointCounter%60==0)
    System.println(trackPointCounter+ ","+getNumberOfObjects());

    Ui.requestUpdate();
    }

    } //class

    [/CODE]