go to post Norman W. Freeman · Jun 27 I wrote a script that calculate database fragmentation level (between 0 and 100%). The idea is to fetch all blocks, to find which global they belongs to, and then count how many segments exists (one sequence being a set of consecutive blocks belonging to same global (eg: in AAAADDBBAACCC there is 5 segments). It's based on Dmitry BlocksExplorer open source project. The formula is as such : Fragmentation % = (TotalSegments - GlobalCount) / (TotalBlocks - GlobalCount) Blocks Formula Fragmentation AAAAAACCBBDD (best) (4-4) / (13-4) 0% AAAADDBBAACCC (5-4) / (13-4) 11% ACADDAABBAACC (8-4) / (13-4) 44% ACABADADBACAC (worst) (13-4) / (13-4) 100% ///usage: do ..ReadBlocks("D:\YOUR_DATABASE\") ClassMethod ReadBlocks(path As %String) { new $namespace znspace "%sys" //get total amount of blocks set db = ##class(SYS.Database).%OpenId(path) set totalblocks = db.Blocks set db = "" set blockcount = 0 open 63:"^^"_path set ^TEMP("DEFRAG", "NODES", 3)=$listbuild("", 0) while $data(^TEMP("DEFRAG", "NODES"))=10 //any childs { set blockId = "" for { set blockId = $order(^TEMP("DEFRAG", "NODES", blockId),1,node) quit:blockId="" kill ^TEMP("DEFRAG", "NODES", blockId) set globalname = $lg(node,1) set hasLong = $lg(node,2) do:blockId'=0 ..ReadBlock(blockId, globalname, hasLong, .totalblocks, .blockcount) } } close 63 set ^TEMP("DEFRAG","PROGRESS") = "DONE" do ..CalculateFragmentation() } ClassMethod ReadBlock(blockId As %String, globalname As %String, hasLong As %Boolean, ByRef totalblocks As %Integer, ByRef blockcount As %Integer) { view blockId set blockType=$view(4,0,1) if blockType=8 //data block { if hasLong { for N=1:1 { set X=$VIEW(N*2,-6) quit:X="" set gdview=$ascii(X) if $listfind($listbuild(5,7,3),gdview) { set cnt=$piece(X,",",2) set blocks=$piece(X,",",4,*) for i=1:1:cnt { set nextBlock=$piece(X,",",3+i) set ^TEMP("DEFRAG","GLOBAL",nextBlock) = globalname set blockcount = blockcount + 1 //update progress set ^TEMP("DEFRAG","PROGRESS") = $number(blockcount / totalblocks * 100, 2) } } } } } else //block of pointers { if blockType = 9 //catalog { set nextglobal=$view(8,0,4) //large catalogs might spawn on multiple blocks quit:$data(^TEMP("DEFRAG","GLOBAL",nextglobal)) set:nextglobal'=0 ^TEMP("DEFRAG", "NODES", nextglobal) = $listbuild("",0) //next catalog } for N=1:1 { set X=$VIEW(N-1*2+1,-6) quit:X="" set nextBlock=$VIEW(N*2,-5) if blockType=9 set globalname=X set haslong=0 if $piece($view(N*2,-6),",",1) { set haslong=1 } continue:$data(^TEMP("DEFRAG","GLOBAL",nextBlock) )//already seen? set ^TEMP("DEFRAG", "NODES", nextBlock) = $listbuild(globalname,haslong) set ^TEMP("DEFRAG","GLOBAL",nextBlock) = globalname set blockcount = blockcount + 1 set ^TEMP("DEFRAG","PROGRESS") = $number(blockcount / totalblocks * 100, 2) } } } ClassMethod CalculateFragmentation() { set segments = 0, blocks = 0, blocktypes = 0 kill ^TEMP("DEFRAG", "UNIQUE") set previousglobal = "" set key = "" for { set key = $order(^TEMP("DEFRAG","GLOBAL",key),1,global) quit:key="" if global '= previousglobal { set previousglobal = global set segments = segments + 1 } if '$data(^TEMP("DEFRAG", "UNIQUE", global)) { set ^TEMP("DEFRAG", "UNIQUE", global)="" set blocktypes = blocktypes + 1 } set blocks = blocks + 1 } write $number((segments - blocktypes) / (blocks - blocktypes) * 100, 2) } Notes : Use it at your own risks. It's not supposed to write anything in database (doing only read operations) but I'm unfamiliar with the VIEW command and it's possible caveats. This might take a really long time to complete (several hours), especially if database is huge (TB in size). Progress can be checked by reading ^TEMP("DEFRAG","PROGRESS") node.
go to post Norman W. Freeman · Jun 27, 2024 The solution I found is to create a new static method that creates an instance and returns it : Class Foo Extends %Exception.AbstractException { ClassMethod Create(arg1 As %String, arg2 As %String, arg3 As %String, arg4 As %String, arg5 As %String) As %Status { quit ..%New("some message") } } Before : throw ##class(Foo).%New("args1", "args2", "args3", ...) After : throw ##class(Foo).Create("args1", "args2", "args3", ...)
go to post Norman W. Freeman · Aug 6, 2023 Here is the solution I end up using. This is based on a solution suggested by Julius Kavay : set cmd="netstat -anp TCP" set oldIO = $io open cmd:"QR":10 use cmd // in case, $zeof is not set per default // set old=$system.Process.SetZEOF(1) for { read line //timeout alternative, no need of $zeof: read line:1 quit:$zeof ... //do something with line } close cmd use:oldIO]"" oldIO // d $system.Process.SetZEOF(old) You can find more info about it here. As an alternative, it's also possible to use pipes ($zeof must be used as well): set dev="|CPIPE|"_$job open dev:cmd:10 use dev ... close dev
go to post Norman W. Freeman · Feb 9, 2023 It's possible to change (and thus disable) concurrency of a persistent object by calling this private method (eg: in constructor) : Method %OnNew() As %Status { do ..%ConcurrencySet(0) // <--- here quit $$$OK } Disclaimer : this is an undocumented method. It might be removed from IRIS implementation in the future, use it at your own risk. The parameter of that method accept same values as what is returned by $system.OBJ.GetConcurrencyMode().Once is concurrency is disabled, updates from other processes to related global can occurs even within transactions (data consistency is lost). A possible workaround is to create a single lock on the global : do ##class(Test.Test).%LockExtent() //same as lock +^Test.TestD //do something here //... do ##class(Test.Test).%UnlockExtent() //same as lock -^Test.TestD This prevent having many locks created during long duration transactions that create many persistent objects (but on the other side, it locks the whole global, not individual nodes).
go to post Norman W. Freeman · Sep 23, 2020 I found the following XSD file which seems to be what I want : C:\InterSystems\Cache\bin\cacheexport.xsd It's much more complex than what I thought. Maybe exporting all cache files as UDL (as Dmitriy suggested) is a better approach. I don't know if SyncTool is able to export cache entities in that format directly. If not, I will need a second pass (that convert xml to udl).