Topic
  • 6 replies
  • Latest Post - ‏2013-01-23T20:52:43Z by Mathias Mamsch
SystemAdmin
SystemAdmin
3180 Posts

Pinned topic Memory issues

‏2013-01-17T18:41:03Z |
Hello,

I'm trying to develop a script that would update three attributes then baseline the module. On certain modules the script runs out of memory and DOORS crashes. I believe this has to due with the volume of links that are being analysis(2-8K) and the way in which I'm writing the data to the attributes. Currently I can't any logic errors, if any. Any help would be greatly appreciated. Thank you.

-Jim
Updated on 2013-01-23T20:52:43Z at 2013-01-23T20:52:43Z by Mathias Mamsch
  • llandale
    llandale
    2979 Posts

    Re: Memory issues

    ‏2013-01-17T20:59:02Z  
    [1] Yes, it could be the vast volumn of open modules. If so, you may have to violate the natural laws of nature and re-write your clever Attr-DXL such that it:
    • Runs and sets ALL objects in the module
      • for o in entire mod do
        • insert the body of your code here, changing "obj" to "o"
    • Remembers which other modules it opened; and at the end then close these modules

    Then the attr-DXL actually only runs once. It would NOT run for the 2nd obj-attr values since it already has a value.

    [2] Your "ProgressMessage" looks like a potential villian. That's a lot of concatenation and wasted string table space. Consider adding this:
    • if (cnt % 100 == 0) then progressMessage...
    This it does that only every 100th object.

    [3] Concatenating errmsg with the new message is theoretically rediculous; but with few errors it doesn't really matter. Instead use a buffer to accumulate your error message; bufEror += the new message.
    • string s = ""
    • for(i=0; i<1000; i++) s = s "A"
    If I did my math correctly, you end up with a string of 1000 "A"s but use up 499,499 bytes in the string table (a string of 1000 "A"s plus a string of 999 "A"s pluse .. 998 + .. 1).

    [4] On other news:
    • refreshDXLAttr should probably use for o in entire M do loop
    • That's the very first time I've ever seen anybody use that "set(attrTo, attrFrom)" command. Excellent.

    -Louie
  • SystemAdmin
    SystemAdmin
    3180 Posts

    Re: Memory issues

    ‏2013-01-21T19:42:55Z  
    • llandale
    • ‏2013-01-17T20:59:02Z
    [1] Yes, it could be the vast volumn of open modules. If so, you may have to violate the natural laws of nature and re-write your clever Attr-DXL such that it:
    • Runs and sets ALL objects in the module
      • for o in entire mod do
        • insert the body of your code here, changing "obj" to "o"
    • Remembers which other modules it opened; and at the end then close these modules

    Then the attr-DXL actually only runs once. It would NOT run for the 2nd obj-attr values since it already has a value.

    [2] Your "ProgressMessage" looks like a potential villian. That's a lot of concatenation and wasted string table space. Consider adding this:
    • if (cnt % 100 == 0) then progressMessage...
    This it does that only every 100th object.

    [3] Concatenating errmsg with the new message is theoretically rediculous; but with few errors it doesn't really matter. Instead use a buffer to accumulate your error message; bufEror += the new message.
    • string s = ""
    • for(i=0; i<1000; i++) s = s "A"
    If I did my math correctly, you end up with a string of 1000 "A"s but use up 499,499 bytes in the string table (a string of 1000 "A"s plus a string of 999 "A"s pluse .. 998 + .. 1).

    [4] On other news:
    • refreshDXLAttr should probably use for o in entire M do loop
    • That's the very first time I've ever seen anybody use that "set(attrTo, attrFrom)" command. Excellent.

    -Louie
    Hello,

    When I trying to use the built-in wizard analysis and recursive depth of 4, DOORs is having memory issues then crashes. I was wondering what is the best way to analyis the links without without having DOORS crash? I was trying to modify the code from the wizard so that it would close the modules based on there depth but for some reason I can get my logic to work. Any help would be appreciated. Thank you.

    -Jim
  • SystemAdmin
    SystemAdmin
    3180 Posts

    Re: Memory issues

    ‏2013-01-21T19:42:57Z  
    • llandale
    • ‏2013-01-17T20:59:02Z
    [1] Yes, it could be the vast volumn of open modules. If so, you may have to violate the natural laws of nature and re-write your clever Attr-DXL such that it:
    • Runs and sets ALL objects in the module
      • for o in entire mod do
        • insert the body of your code here, changing "obj" to "o"
    • Remembers which other modules it opened; and at the end then close these modules

    Then the attr-DXL actually only runs once. It would NOT run for the 2nd obj-attr values since it already has a value.

    [2] Your "ProgressMessage" looks like a potential villian. That's a lot of concatenation and wasted string table space. Consider adding this:
    • if (cnt % 100 == 0) then progressMessage...
    This it does that only every 100th object.

    [3] Concatenating errmsg with the new message is theoretically rediculous; but with few errors it doesn't really matter. Instead use a buffer to accumulate your error message; bufEror += the new message.
    • string s = ""
    • for(i=0; i<1000; i++) s = s "A"
    If I did my math correctly, you end up with a string of 1000 "A"s but use up 499,499 bytes in the string table (a string of 1000 "A"s plus a string of 999 "A"s pluse .. 998 + .. 1).

    [4] On other news:
    • refreshDXLAttr should probably use for o in entire M do loop
    • That's the very first time I've ever seen anybody use that "set(attrTo, attrFrom)" command. Excellent.

    -Louie
    Hello,

    When I trying to use the built-in wizard analysis and recursive depth of 4, DOORs is having memory issues then crashes. I was wondering what is the best way to analyis the links without without having DOORS crash? I was trying to modify the code from the wizard so that it would close the modules based on there depth but for some reason I can get my logic to work. Any help would be appreciated. Thank you.

    -Jim
  • llandale
    llandale
    2979 Posts

    Re: Memory issues

    ‏2013-01-22T19:07:14Z  
    Hello,

    When I trying to use the built-in wizard analysis and recursive depth of 4, DOORs is having memory issues then crashes. I was wondering what is the best way to analyis the links without without having DOORS crash? I was trying to modify the code from the wizard so that it would close the modules based on there depth but for some reason I can get my logic to work. Any help would be appreciated. Thank you.

    -Jim
    To me, the wizard is almost hopeless at level 2 and a disaster any deeper. I don't think you have any mechanism for describing different link characteristics at the different levels and it seems likely you are opening LOTS of modules. If you query both in and out links then surely you are in an infinite loop in there somewhere.

    Its also likely that you are displaying the target objects multiple times, one for every different link path you took to get there.

    It is unrealistic to attempt to close modules in Layouts. The next object will just re-open them and you will end up thrashing.

    If your schema looks like A->B->C->D then I'd suggest you are conceptualizing something incorrectly if you want A to display info about all the eventually linked D objects (e.g. why display all the "Unit" (B) level test procedures (A) in the System Spec (D)?).

    -Louie
  • SystemAdmin
    SystemAdmin
    3180 Posts

    Re: Memory issues

    ‏2013-01-23T17:38:38Z  
    • llandale
    • ‏2013-01-22T19:07:14Z
    To me, the wizard is almost hopeless at level 2 and a disaster any deeper. I don't think you have any mechanism for describing different link characteristics at the different levels and it seems likely you are opening LOTS of modules. If you query both in and out links then surely you are in an infinite loop in there somewhere.

    Its also likely that you are displaying the target objects multiple times, one for every different link path you took to get there.

    It is unrealistic to attempt to close modules in Layouts. The next object will just re-open them and you will end up thrashing.

    If your schema looks like A->B->C->D then I'd suggest you are conceptualizing something incorrectly if you want A to display info about all the eventually linked D objects (e.g. why display all the "Unit" (B) level test procedures (A) in the System Spec (D)?).

    -Louie

    Hello,

    Is there anyway of improving the below code for memory management and performance. Any help would be greatly appreciated?

    -Jim
     

    pragma runLim, 0
     
    //Globals
    Buffer bsz = create
    const Regexp isPatt = regexp2 "^([^,]*),([^,]*),(.*)$"
    const Regexp line = regexp2 ".*" // matches any character except newline
    Skip Xopened = create
    int cnt = 0
     
    Module m = current
    Object o = current(m)
     
    void isVisibleXclose()
    {
        //database cleanup...
            Module m = null
            for m in database do {
                    if (!isVisible(m)) {
                            close(m)}
            }   
    }
     
    void CloseModules()
    {
            if(cnt > 10) { // Max number of open modules...
                    //initial call - remember modules already open  
                    string modpath = null
                    for modpath in Xopened do {
                            if(open(module modpath)) close(module item modpath)
                    }
                    setempty(Xopened); cnt = 0; modpath = null;
            }
    }
     
    string showIn(Object o, string depth) 
    {
            bsz = ""
        Link l
        LinkRef lr
        ModName_ otherMod = null
        ModuleVersion otherVersion = null
        Object othero
        for lr in all(o<-"*") do {
            otherMod = module (sourceVersion lr)
            if (!null otherMod) {
                if ((!isDeleted otherMod) && (null data(sourceVersion lr))) {
                    load((sourceVersion lr),false); put(Xopened, cnt++, fullName(module (sourceVersion lr)));
                }
            }
        }
        for l in all(o<-"*") do {
            otherVersion = sourceVersion l
            otherMod = module(otherVersion)
            if (null otherMod || isDeleted otherMod) continue
            othero = source l
            if (null othero) {load(otherVersion,false)}
            othero = source l
            if (null othero) continue
            if (isDeleted othero) continue
            bsz += depth; bsz += ","; bsz += fullName(module othero); bsz += ","; bsz += probeRichAttr_(othero,"Absolute Number", false); bsz += "\n";
        }
            depth = null
            CloseModules() // free all resources...
            string ret = stringOf(bsz)
            return(ret)
    }
     
    Object GOTObject(int absno, Module m)
    {
            return(object(absno, m))
    }
     
    void wDir()
    {
            string sourceDir = "c:\\AnalysisLinks"
            if(!fileExists_(sourceDir)) {
                    mkdir(sourceDir)
            }
            sourceDir = null
    }
     
    void inputFile(Buffer &oldBuff, Buffer &newBuff, string level)
    { 
            string contents = stringOf(oldBuff)
            string str = null
            string temp = null
            bool printNOW = true
            Module m = null
            
            while (!null contents && line contents) {
                    printNOW = true
                    str = contents[match 0] // match 0 is whole of match
                    
                    if (isPatt str) {
                            //str[match 1]: depth
                            //str[match 2]: module
                            //str[match 3]: object
                            m = read(str[match 2], false); put(Xopened, cnt++, fullName(m));
                            if(null m) {
                                    errorBox("{NULL} " str[match 2])
                            }
                            else {
                                    if(level == "LEVEL1") {
                                            if(str[match 1] == "LEVEL1") {
                                                    temp = showIn(GOTObject(intOf(realOf(str[match 3]"")), m),level)
                                                    if(!null temp) {
                                                            newBuff += str; newBuff += "\n"; newBuff += temp;
                                                            printNOW = false
                                                    }
                                            }
                                    } else if(level == "LEVEL2") {
                                            if(str[match 1] == "LEVEL1") {
                                                    temp = showIn(GOTObject(intOf(realOf(str[match 3]"")), m),level)
                                                    if(!null temp) {
                                                            newBuff += str; newBuff += "\n"; newBuff += temp;
                                                            printNOW = false
                                                    }
                                            }
                                    } else if(level == "LEVEL3") {
                                            if(str[match 1] == "LEVEL2") {
                                                    temp = showIn(GOTObject(intOf(realOf(str[match 3]"")), m),level)
                                                    if(!null temp) {
                                                            newBuff += str; newBuff += "\n"; newBuff += temp;
                                                            printNOW = false
                                                    }
                                            }
                                    } else if(str[match 1] == "LEVEL4") {
                                            if(level == "LEVEL4") {
                                                    temp = showIn(GOTObject(intOf(realOf(str[match 3]"")), m),level)
                                                    if(!null temp) {
                                                            newBuff += str; newBuff += "\n"; newBuff += temp;
                                                            printNOW = false
                                                    }
                                            }
                                    }
                                    if(printNOW) {
                                            newBuff += str; newBuff += "\n";
                                    }
                            }
                    }
                    else {
                            errorBox("#error {input}: '"str"'")
                    }
                    str = null; temp = null;
                    contents = contents[end 0 + 2:]  // move past newline
            }
            contents = null; level = null;
    }
     
    string nameM(string modpath) {
        return(name(module item modpath))
    }
      
    string replace (string sSource, string sSearch, string sReplace) {
    //https://www.ibm.com/developerworks/forums/thread.jspa?messageID=14892683&#14892683
     
        int iLen = length sSource
        if (iLen == 0) return("")
        
        int iLenSearch = length(sSearch)
        
        if (iLenSearch == 0) { 
            //raiseError ("Parameter error", "in strings.inc/replace: search string must not be empty")
            return("")
        }
        
        // read the first char for latter comparison -> speed optimization
        char firstChar = sSearch[0]
        
        Buffer s = create() 
        int pos = 0, d1, d2;    
        int i
        
        while (pos < iLen) { 
            char ch = sSource[pos]; 
            bool found = true
            
            if (ch != firstChar) {pos ++; s+= ch; continue}
            for (i = 1; i < iLenSearch; i++) {
               if (sSource[pos+i] != sSearch[i]) { found = false; break }
            }
            if (!found) {pos++; s+= ch; continue}
            s += sReplace
            pos += iLenSearch
        }
        
            sReplace = null
        string result = stringOf(s)
        delete s
        return(result)
    }
     
    string rMod(string contents)
    { 
            string str = contents
            
            while (!null contents && line contents) {
                    if (isPatt contents) {
                            str = replace (str, contents[match 2], name(item contents[match 2])"")
                    }
                    contents = contents[end 0 + 2:]  // move past newline
            }
            contents = null
            return(str)
    }
     
    void main(Object cObj)
    {
            int CASE = 0
            wDir()
            Buffer oldBuff =  create 
            oldBuff = ""
            Buffer newBuff = create
            newBuff = ""
     
            while(++CASE < 5) //Switch(case statement)
            {
                    if(CASE == 1) {
                            oldBuff = showIn(current Object,"LEVEL1")
                                    
                            isVisibleXclose() // free all resources...
                    } else if(CASE == 2) {
                            //oldBuff = tempStringOf(oldBuff)
                            setempty(newBuff)
                            inputFile(oldBuff, newBuff, "LEVEL2")
                                    
                            isVisibleXclose() // free all resources...
                    } else if(CASE == 3) {
                            oldBuff = tempStringOf(newBuff)
                            setempty(newBuff)
                            inputFile(oldBuff, newBuff, "LEVEL3")
                            
                            isVisibleXclose() // free all resources...
                    } else if(CASE == 4) {
                            oldBuff = tempStringOf(newBuff)
                            setempty(newBuff)
                            inputFile(oldBuff, newBuff, "LEVEL4")
                            
                            isVisibleXclose() // free all resources...
                    } else {
                            warningBox("Default Case {" CASE "}")
                    }
            }
     
            string sSource = stringOf(newBuff); delete oldBuff; delete newBuff;
            sSource = rMod(sSource)
            sSource = replace (sSource, "LEVEL1,", "")
            sSource = replace (sSource, "LEVEL2,", "\t")
            sSource = replace (sSource, "LEVEL3,", "\t\t")
            sSource = replace (sSource, "LEVEL4,", "\t\t\t")
            sSource = replace (sSource, ",", "-")
     
            Stream writtingBuf = write("c:\\AnalysisLinks\\final.txt")
                    writtingBuf << sSource
                    sSource = null
                    close(writtingBuf)      
                    
            delete isPatt
            delete line
            delete Xopened
            delete bsz
    }
     
    main(o); infoBox("done!");
    
    Updated on 2014-01-09T00:43:56Z at 2014-01-09T00:43:56Z by iron-man
  • Mathias Mamsch
    Mathias Mamsch
    1970 Posts

    Re: Memory issues

    ‏2013-01-23T20:52:43Z  

    Hello,

    Is there anyway of improving the below code for memory management and performance. Any help would be greatly appreciated?

    -Jim
     

    <pre class="javascript dw" data-editor-lang="js" data-pbcklang="javascript" dir="ltr">pragma runLim, 0 //Globals Buffer bsz = create const Regexp isPatt = regexp2 "^([^,]*),([^,]*),(.*)$" const Regexp line = regexp2 ".*" // matches any character except newline Skip Xopened = create int cnt = 0 Module m = current Object o = current(m) void isVisibleXclose() { //database cleanup... Module m = null for m in database do { if (!isVisible(m)) { close(m)} } } void CloseModules() { if(cnt > 10) { // Max number of open modules... //initial call - remember modules already open string modpath = null for modpath in Xopened do { if(open(module modpath)) close(module item modpath) } setempty(Xopened); cnt = 0; modpath = null; } } string showIn(Object o, string depth) { bsz = "" Link l LinkRef lr ModName_ otherMod = null ModuleVersion otherVersion = null Object othero for lr in all(o<-"*") do { otherMod = module (sourceVersion lr) if (!null otherMod) { if ((!isDeleted otherMod) && (null data(sourceVersion lr))) { load((sourceVersion lr),false); put(Xopened, cnt++, fullName(module (sourceVersion lr))); } } } for l in all(o<-"*") do { otherVersion = sourceVersion l otherMod = module(otherVersion) if (null otherMod || isDeleted otherMod) continue othero = source l if (null othero) {load(otherVersion,false)} othero = source l if (null othero) continue if (isDeleted othero) continue bsz += depth; bsz += ","; bsz += fullName(module othero); bsz += ","; bsz += probeRichAttr_(othero,"Absolute Number", false); bsz += "\n"; } depth = null CloseModules() // free all resources... string ret = stringOf(bsz) return(ret) } Object GOTObject(int absno, Module m) { return(object(absno, m)) } void wDir() { string sourceDir = "c:\\AnalysisLinks" if(!fileExists_(sourceDir)) { mkdir(sourceDir) } sourceDir = null } void inputFile(Buffer &oldBuff, Buffer &newBuff, string level) { string contents = stringOf(oldBuff) string str = null string temp = null bool printNOW = true Module m = null while (!null contents && line contents) { printNOW = true str = contents[match 0] // match 0 is whole of match if (isPatt str) { //str[match 1]: depth //str[match 2]: module //str[match 3]: object m = read(str[match 2], false); put(Xopened, cnt++, fullName(m)); if(null m) { errorBox("{NULL} " str[match 2]) } else { if(level == "LEVEL1") { if(str[match 1] == "LEVEL1") { temp = showIn(GOTObject(intOf(realOf(str[match 3]"")), m),level) if(!null temp) { newBuff += str; newBuff += "\n"; newBuff += temp; printNOW = false } } } else if(level == "LEVEL2") { if(str[match 1] == "LEVEL1") { temp = showIn(GOTObject(intOf(realOf(str[match 3]"")), m),level) if(!null temp) { newBuff += str; newBuff += "\n"; newBuff += temp; printNOW = false } } } else if(level == "LEVEL3") { if(str[match 1] == "LEVEL2") { temp = showIn(GOTObject(intOf(realOf(str[match 3]"")), m),level) if(!null temp) { newBuff += str; newBuff += "\n"; newBuff += temp; printNOW = false } } } else if(str[match 1] == "LEVEL4") { if(level == "LEVEL4") { temp = showIn(GOTObject(intOf(realOf(str[match 3]"")), m),level) if(!null temp) { newBuff += str; newBuff += "\n"; newBuff += temp; printNOW = false } } } if(printNOW) { newBuff += str; newBuff += "\n"; } } } else { errorBox("#error {input}: '"str"'") } str = null; temp = null; contents = contents[end 0 + 2:] // move past newline } contents = null; level = null; } string nameM(string modpath) { return(name(module item modpath)) } string replace (string sSource, string sSearch, string sReplace) { //https://www.ibm.com/developerworks/forums/thread.jspa?messageID=14892683&#14892683 int iLen = length sSource if (iLen == 0) return("") int iLenSearch = length(sSearch) if (iLenSearch == 0) { //raiseError ("Parameter error", "in strings.inc/replace: search string must not be empty") return("") } // read the first char for latter comparison -> speed optimization char firstChar = sSearch[0] Buffer s = create() int pos = 0, d1, d2; int i while (pos < iLen) { char ch = sSource[pos]; bool found = true if (ch != firstChar) {pos ++; s+= ch; continue} for (i = 1; i < iLenSearch; i++) { if (sSource[pos+i] != sSearch[i]) { found = false; break } } if (!found) {pos++; s+= ch; continue} s += sReplace pos += iLenSearch } sReplace = null string result = stringOf(s) delete s return(result) } string rMod(string contents) { string str = contents while (!null contents && line contents) { if (isPatt contents) { str = replace (str, contents[match 2], name(item contents[match 2])"") } contents = contents[end 0 + 2:] // move past newline } contents = null return(str) } void main(Object cObj) { int CASE = 0 wDir() Buffer oldBuff = create oldBuff = "" Buffer newBuff = create newBuff = "" while(++CASE < 5) //Switch(case statement) { if(CASE == 1) { oldBuff = showIn(current Object,"LEVEL1") isVisibleXclose() // free all resources... } else if(CASE == 2) { //oldBuff = tempStringOf(oldBuff) setempty(newBuff) inputFile(oldBuff, newBuff, "LEVEL2") isVisibleXclose() // free all resources... } else if(CASE == 3) { oldBuff = tempStringOf(newBuff) setempty(newBuff) inputFile(oldBuff, newBuff, "LEVEL3") isVisibleXclose() // free all resources... } else if(CASE == 4) { oldBuff = tempStringOf(newBuff) setempty(newBuff) inputFile(oldBuff, newBuff, "LEVEL4") isVisibleXclose() // free all resources... } else { warningBox("Default Case {" CASE "}") } } string sSource = stringOf(newBuff); delete oldBuff; delete newBuff; sSource = rMod(sSource) sSource = replace (sSource, "LEVEL1,", "") sSource = replace (sSource, "LEVEL2,", "\t") sSource = replace (sSource, "LEVEL3,", "\t\t") sSource = replace (sSource, "LEVEL4,", "\t\t\t") sSource = replace (sSource, ",", "-") Stream writtingBuf = write("c:\\AnalysisLinks\\final.txt") writtingBuf << sSource sSource = null close(writtingBuf) delete isPatt delete line delete Xopened delete bsz } main(o); infoBox("done!"); </pre>

    Well two things come to me from a first glance:
     

    • You are leaking a ton of ModuleVersion objects in your code. Depending on how often you call showObject in your code, this can make your code run out of memory. One of my traceability scripts had exactly this problem and it could be run through the complete database after removing the leaked ModuleVersions. With ModuleVersions it is especially dangarous, since any Assignment outside a declaration will create a new leaked object. So your showObject Code should be like this:


    You can find details regarding the ModuleVersion problem here: https://www.ibm.com/developerworks/forums/thread.jspa?messageID=14832712&#14832712

     

     

    string showIn(Object o, string depth) 
    {
        bsz = ""
        Link l
        LinkRef lr
        ModName_ otherMod = null
     
        ModuleVersion otherVersion = null
        Object othero
        for lr in all(o<-"*") do {
                ModuleVersion mvSource = sourceVersion lr
            otherMod = module mvSource
            if (!null otherMod) {
                if ((!isDeleted otherMod) && (null data(mvSource))) {
                    load((mvSource),false); put(Xopened, cnt++, fullName(module (mvSource)));
                }
            }
                    delete mvSource
        }
            
        for l in all(o<-"*") do {
            ModuleVersion otherVersion = sourceVersion l // Use assignment in declaration, otherwise: Leak
            otherMod = module(otherVersion)
            if (null otherMod || isDeleted otherMod) { delete otherVersion; continue }
            othero = source l
            if (null othero) {load(otherVersion,false)}
            othero = source l
            if (null othero) { delete otherVersion; continue }
            if (isDeleted othero) { delete otherVersion; continue }
            bsz += depth; bsz += ","; bsz += fullName(module othero); bsz += ","; bsz += probeRichAttr_(othero,"Absolute Number", false); bsz += "\n";
                    delete otherVersion
        }
            depth = null
            CloseModules() // free all resources...
            string ret = stringOf(bsz)
            return(ret)
    }
    

     

     

     

     

    • The second problem I see is, that the code

     

     

    Module m
    for m in database do { close m }
    

     


    does not work reliably, due to the 'do not delete from something you are iterating over' rule. I went into some length once to get a reliable tracking of opened modules and being able to close them, and I have one conclusion. If you want a rock solid code, that will run over complete databases without breaking down even in the worst cases, open only one module, read the data, store it, close the module. If that is infeasible since you only want to have a small script, which does not care about caching tons of data, then keep very good track of each module you open and close. Do not use for Module in database.

    Maybe this helps, regards, Mathias

     

     


    Mathias Mamsch, IT-QBase GmbH, Consultant for Requirement Engineering and D00RS

     

    Updated on 2014-01-09T00:45:13Z at 2014-01-09T00:45:13Z by iron-man