$listbuild() and all other functions, like $length(), $char() etc.  are  (I call them) basic  functions of COS and have nothing to do with objects. The language of Cache (and of course IRIS )  handles data as  raw (basic) data and not as objects, like some other (but not all) languages.

For example, in JavaScript you can do

 var  text="Some text";
 alert("'" + text + "' has "+text.length + " characters");
 alert("'Some text' has " + "Some text".length + " characters");

because both, a (text)variable and a (text)string have an (object)property name 'length'.

Beside the raw data type COS also has object data type and most of the standard COS functions will accept an object property as argument, for example:

Class Test.Class Extends %RegisteredObject
{
Property Data as %String;
}

set text="Some text"
set obj = ##class(Test.Class).%New()
set obj.Data = "Some text";
write $length(text),!
write $length(obj.Data),!
$lb(data1, data2, data3, ... dataN) is built as a string of item1 item2 item3 ... itemN

itemN:= <len> <type> <dataN>

assuming, you are on a little-endian CPU and

l1 = $length(data)
l2 = l1 + 1 // for type byte
l3 = l2 + 1 // for the length itself

then the length is as follows

if l1<=253 then len: <l3>
elseif l1<=65534 len:= <0> <l2-lowbyte> <l2-highbyte>
else len:= <0> <0> <0> <l2-lowbyte-loworderword> ... <l2-highbyte-highorderword>

And don't forget,
$lb(123) is not the same as $lb(1230/10), hence we have a $ls() function!

 set x="1.2.3", y=$name(tmp)
 for i=1:1:$length(x,".") { set y=$name(@y@($piece(x,".",i))) }
 set @y=""
 kill x,y,i

You can put al the above in one line, if you are in terminal. Or put it in a method

ClassMethod setTemp(x) [ PublicList = tmp]
{
 set y=$name(tmp)
 for i=1:1:$length(x,".") { set y=$name(@y@($piece(x,".",i))) }
 set @y=""
} 

Or you take the shorthand

set x="1.2.3", @("tmp("_$tr(x,".",",")_")=""""")

But this works only as long as x contains number_period_numeber_period_...No leading zeros, no alphabetics...

A warning about long time open transactions will be placed in cconsole.log and alerts.log (both files in $system.Util.InstallDirectory()_"mgr" directory) - at least  this is the case what I see on a customers system. The entries look like this:

04/05/19-02:31:34:470 (21012) 1 [SYSTEM MONITOR] TransOpenSecs Warning: One or more transactions open longer than 10 minutes. Process id  18624 22208 (only top 5 shown)
08/29/19-14:31:03:802 (18576) 1 [SYSTEM MONITOR] TransOpenSecs Warning: One or more transactions open longer than 10 minutes. Process id  4128 (only top 5 shown)
09/04/19-17:16:19:090 (21344) 1 [SYSTEM MONITOR] TransOpenSecs Warning: One or more transactions open longer than 10 minutes. Process id  25872 (only top 5 shown)

So it should be as easy as monitoring those two logfiles. But how it looks like on an ECP server, don't ask me.

If your routine works in a terminal session (by issuing a DO ^Routinname command) but does not work, when the same routine is started via the Job commannd (JOB ^Routinname) then you lack something in background, what you have in the foreground.

Put an errortrap in your routine and simple report a possible error (and if you wich, additionally the execution progress of your routine).


rvExt2012 ; Test
    set $zt="bgErr"
    // ...
    // ...
    do signal("checkpoint 1") // this is optional
    // ...
    // ...    do signal("checkpoint 2") // this is optional
    // ...
    // ...
    do signal("Done")
    quit

bgErr do signal("Error: "_$ze)
    quit

signal(msg) do $system.Event.Signal($zp, $username_", "_$roles_", "_msg)
    quit

  

Now, in the terminal session enter following  line

 
job ^rvExt2012 do { set $lb(s,m)=$system.Event.WaitMsg("",1) write m,! } while s

The intention behind my post was, to give you one idea (of many other possibilities), how to convert XML to CSV. A empty (chars) element is just one of some other unhandled cases (missing tags, other tags inside of COLx tag, etc.).

If you need some speed and your XML-File obeys following constraints:

- the file is a correct XML-File
- contains only the 'Data', 'Details' and 'ColX' tags
- no selfclosing tags, like <sometag/>

then you could try the QAD-way (quick-and-dirty) of conversion.

Again, below an example routine (without excessive testing).
All ISC people and ordinary programer, please look the other way ;-))

Convert() Public
{
   set len=32000 // chunk size, a safe value is:
                 // 32767 - longestColumnValue - tagSizes
   set fi="c:\temp\example-t.xml" // inp file (xml)
   set fo="c:\temp\example-t.csv" // output file (csv)

   open fi:"ru":1 open:$t fo:"nw":1
   if '$t close fi quit

   set xml=$$inp(fi,len) // first xml-chunk

   set i=0, p=0
   while 1 {
      set i=$locate(xml,"\<\/?\w+\>",i,j,v) // next (opening or closing) tag

      if i {                                            // let see, what we got 
         if v="<Details>" set row="", p=-1 // start a new row
elseif v="</Details>" out(fo,row) p=0 // complete, write out
elseif p,v["<Col" p=j, o=$zstrip(v,"*AP") // new column, keep start
elseif p,v["</Col" {$li(row,o)=$$val($e(xml,p,i-1)) // get value
         }                                   // everything else is don't care
         set i=j

else {
         set tmp=$$inp(fi,len) // next xml-chunk
         if tmp="" quit                                   // done

         // remove processed data, add new one
         if p>0 set xml=$e(xml,p,*)_tmp,p=1,i=0 else xml=$e(xml,i,*)_tmp,i=0 }
      }
   }
   close fi
   close fo
}

val(val)
{
   quit $zstrip(val,"<>w") // add handling of charcter entities like &lt; etc.
}

out(fo,row)
{
   use fo
   write $listtostring(row,";",1),! // delimiter!
}

inp(fn,len)
{
   use fn
   try read xml#len catch xml="" // in case, $zeof-mode is off
   quit xml
}

The above converter reads a simple test XML-file with two million 'ColX' items in 5 seconds and creates a CSV file with 100000 rows and 20 columns (in each row).

Instead of file you can also use an stream.

If you work in Cache-Terminal, go to:
ManagementPortal-->SystemAdmin-->Configurations-->DeviceSettings-->DeviceSubtypes

Select: C-Cache-Terminal, click on Edit
Go to bottom line (Caption: ZUFormFeed) and change the value from $C(27,91,72,27,91,74) to your needs.
For example, if you want to retain the first and the second lines, to: $C(27,91,51,59,48,72,27,91,74).

In case, your device type is not the Cache-Terminal, search for your device in
ManagementPortal-->SystemAdmin-->Configurations-->DeviceSettings-->Devices

Select your device, for example TERM, |TNT| or whatever device type you have.
Click on edit and notice the Sub-Type.
Now, as above, go to the sub-types, and change the ZUFormFeed field.

In case, you wont find your device and/or device sub-type, there is a direct way too,
to set the desired FormFeed (i.e. write #) and the Backspace behavior:

do ##class(%Device).SetFFBS($C(27,91,51,59,48,72,27,91,74),$c(8,32,8))
The first sequence, as the method name implies, is the FormFeed sequence and
the second is the Backspace sequence

If you have your binary or character data in (sequential) global nodes, then:
- create your own stream clas
-in the %OnNew() method fetch the data from your global

and voila, you are ready to use your data with stream methods.

set ^mydata(1)="abcd"
set ^mydata(2)="efgh"

set ^yourdata("id",1)="1234"
set ^yourdata("id",2)="5678"

Class Test.Stream Extends %Stream.TmpBinary
{
Method %OnNew(Global, initval As %String = "") As %Status [ Private ]
{
Set i%%Location=$select(initval="":$$$streamGlobal,1:initval)
do ..Clear()
for set Global=$query(@Global,1,data) quit:Global="" do ..Write(data)
do ..Rewind()
Quit $$$OK
}
}

set stream=##class(Test.Stream).%New($na(^mydata))
while 'stream.AtEnd { write stream.Read(5) }
abcdefgh

set stream=##class(Test.Stream).%New($na(^yourdata("id")))
while 'stream.AtEnd { write stream.Read(5) }
12345678

The correct way is:

ClassMethod ind() [ PublicList = (active,reactive) ]
{
  new active,reactive

  kill info
 // the above command (kill info) is a kind of NOP (no-operation)
 // at the time of entry into a methode there are no variables,
 // except those in the public list (if defined)

  set active = 1
  set reactive = 2

  for i="active","reactive" {
    set info(i)= @i
  }

  zw info

  break
}

If you want to use name-indirection in a procedure block, then

- variables, used by indirection, must be made public (via PublicList)
- and, depending on the situation, should be saved with the NEW command

"like a centipede with a wooden leg: 99 times tic and 1 toc
the stream is then truncated and still requires extra coding"

That's the whole point!
If we turncate the stream to $$$MaxStringLength, no matter where (in a calculated property or in a GETter method), just to present this (string)value to a variable, a function or whatever, then we can instantly stick to a string.

By the way, it's possible to store $$$MaxStringLength bytes in a local or global variable.
Properties of classes are stored in globals as $listelements, so the maxlength for a single string property depends on things like count and types of other properties in the class and not least, does this class extends %Persistent or extends another class(es) which extends %Persistent - in this case we need some extra bytes for those (extended) class names too.

Hello Kevin,

if I anderstand you correctly, several your Cache systems (development, life, ...) will store their files on a third server (possibly on a file server) and you want to put the files from each (source) Cache (instance) into a different (target) folder, where the folder name being the "name" of the source Cache system - am I correct?

If yes, I think, your best choice ist:

...\hostNameOfCache\instanceNameofCache...

or, as John Murray suggested

...\GUIDofCacheInstance...

or just a fixed string like

...\kevinNotebook(development)...

You can put (John's) GUID or my "fixed string"
in a (possibly system) Global like ^%Zconfig("path")=...

Why?
Things like MAC- or IP-Addresses can change more often then you think. Especially IP-Addresses will change, hence are there DNS servers.

On the other hand, it's uncommon to change host- or instance names (of Cache or of whatever installation).

Your IP-Address preference has an other downside too. Many servers have more then one IP-Address (and if they do not have one today, maybe tomorrow!).

If you persist to take the systems IP-Address, here is, what you wanted:

set iplist = $system.INetInfo.GetListOfConfiguredInterfaces(0)
for i = 1:1:$listlength(iplist) zw $list(iplist,i)

Regards and
have a nice day
Julius