├── AplSource ├── nl.apla ├── utf8.aplf ├── ScrollClose.aplf ├── DemoRIDEConnect.aplf ├── DemoView.aplf ├── Reset.aplf ├── DemoAction.aplf ├── runmonitor.aplf ├── DemoClose.aplf ├── GetToken.aplf ├── AddConnection.aplf ├── LaunchDemoProcess.aplf ├── Send.aplf ├── LaunchRide.aplf ├── Connect.aplf ├── GetLastKnownState.aplf ├── Subscribe.aplf ├── Log.aplf ├── DemoForm.aplf ├── Listen.aplf ├── Wait.aplf ├── Handshake.aplf ├── DemoConnect.aplf ├── DemoUpdate.aplf ├── DemoViewLog.aplf ├── GetFacts.aplf ├── DemoApplication.aplf ├── DemoCellDown.aplf ├── SendWait.aplf ├── Init.aplf ├── Demo.aplf ├── DemoReceive.aplf └── monitor.aplf ├── README.md ├── LICENSE └── docs ├── UsageGuide.md └── Protocol.md /AplSource/nl.apla: -------------------------------------------------------------------------------- 1 | ⎕UCS 10 2 | -------------------------------------------------------------------------------- /AplSource/utf8.aplf: -------------------------------------------------------------------------------- 1 | utf8←{⎕UCS'UTF-8'⎕UCS ⍵} 2 | -------------------------------------------------------------------------------- /AplSource/ScrollClose.aplf: -------------------------------------------------------------------------------- 1 | r←ScrollClose 2 | ⎕EX 'hmonscroll' 3 | r←1 4 | -------------------------------------------------------------------------------- /AplSource/DemoRIDEConnect.aplf: -------------------------------------------------------------------------------- 1 | DemoRIDEConnect args;btn 2 | btn←⊃args 3 | :If 0≠≢btn.Data 4 | LaunchRide btn.Data 5 | :EndIf 6 | -------------------------------------------------------------------------------- /AplSource/DemoView.aplf: -------------------------------------------------------------------------------- 1 | DemoView args;btn 2 | ⍝ Toggle size of form to include logs 3 | btn←⊃args 4 | hmonform.Size[1]←btn.Posn[1]+(30 300)[1+btn.State] 5 | -------------------------------------------------------------------------------- /AplSource/Reset.aplf: -------------------------------------------------------------------------------- 1 | Reset dummy;z 2 | 3 | TOKEN ⎕TALLOC ¯1 4 | :If 0≠≢conns 5 | z←iConga.Close¨conns[;2] 6 | :EndIf 7 | conns←0⌿conns 8 | ⎕EX 'iConga' 9 | -------------------------------------------------------------------------------- /AplSource/DemoAction.aplf: -------------------------------------------------------------------------------- 1 | DemoAction args 2 | :Select ⊃args 3 | :Case 'FORM.SETID' 4 | 110⌶FORM.ID.Text 5 | :Case 'FORM.SEND' 6 | 111⌶FORM.MSG.Text 7 | :EndSelect 8 | -------------------------------------------------------------------------------- /AplSource/runmonitor.aplf: -------------------------------------------------------------------------------- 1 | {r}←runmonitor dummy 2 | :If r←~monitortid∊⎕TNUMS ⍝ Make sure monitor is running 3 | monitortid←monitor&0 ⍝ Start in non-listening mode 4 | :EndIf 5 | -------------------------------------------------------------------------------- /AplSource/DemoClose.aplf: -------------------------------------------------------------------------------- 1 | DemoClose args 2 | ⎕←'HMON terminating...' 3 | hmonform.Visible←0 4 | ⎕EX 'hmonform' 'hmongrid' 'hmonride' 'hmontxt' 'hmonstack' 'hmonscroll' 5 | ⎕TKILL ⎕TNUMS 6 | ⎕←'HMON terminated' 7 | -------------------------------------------------------------------------------- /AplSource/GetToken.aplf: -------------------------------------------------------------------------------- 1 | r←type GetToken id 2 | ⍝ t.10001-t.20000: connection 3 | ⍝ t.20001-t.30000: user id 4 | 5 | 'id must be in range 1-1000'⎕SIGNAL((id<1)∨id>999)/11 6 | r←token+(id÷100000)+0.1×'cid' 'uid'⍳⊂type 7 | -------------------------------------------------------------------------------- /AplSource/AddConnection.aplf: -------------------------------------------------------------------------------- 1 | {cid}←AddConnection (con host port);cid 2 | :Hold 'hmon' 3 | 'Maximum number of connections reached'⎕SIGNAL(999<≢conns)/11 4 | cid←⊃(⍳1+≢conns)~conns[;1] 5 | conns⍪←cid con host port 6 | :EndHold 7 | -------------------------------------------------------------------------------- /AplSource/LaunchDemoProcess.aplf: -------------------------------------------------------------------------------- 1 | LaunchDemoProcess withride;folder;ride 2 | folder←⊃⎕SE.Link.Links.dir 3 | ride←withride/' RIDE_INIT="SERVE::0"' ⍝ Start RIDE listening on available port 4 | ⎕CMD ('dyalog LOAD="',folder,'\DemoApplication.aplf"',ride) 'Normal' 5 | -------------------------------------------------------------------------------- /AplSource/Send.aplf: -------------------------------------------------------------------------------- 1 | d←cid Send u;d;i;clt 2 | i←conns[;1]⍳cid 3 | 'Connection not found' ⎕SIGNAL (i>≢conns)/11 4 | clt←⊃conns[i;2] 5 | Log 'PUT ',(⍕cid),': ',u 6 | d←iConga.Send clt (utf8 u) 7 | :If 0≠⊃d 8 | Log'PUT FAILED: ',⍕,d 9 | :EndIf 10 | -------------------------------------------------------------------------------- /AplSource/LaunchRide.aplf: -------------------------------------------------------------------------------- 1 | LaunchRide address;⎕USING 2 | ⎕USING←'' 3 | :If 0=⎕NC 'RIDE_PATH' 4 | RIDE_PATH←'C:\Program Files\Dyalog\Dyalog APL-64 19.0 Unicode\dyalog.exe' 5 | :EndIf 6 | System.Environment.SetEnvironmentVariable'RIDE_CONNECT' address 7 | ⎕CMD (RIDE_PATH) 'Normal' 8 | -------------------------------------------------------------------------------- /AplSource/Connect.aplf: -------------------------------------------------------------------------------- 1 | ok←Connect(host port);d;cid;sp;z;up 2 | 3 | d←iConga.Clt '' host port 'BlkText' 32768 ('Magic' magic) 4 | :If (⊃d)≢0 5 | ('Unable to connect to ',host,':',(⍕port),' - ',3⊃d) ⎕SIGNAL 11 6 | :EndIf 7 | AddConnection (2⊃d) host port 8 | ok←Handshake cid 9 | 'Handshake Failed ' ⎕SIGNAL ok↓11 10 | -------------------------------------------------------------------------------- /AplSource/GetLastKnownState.aplf: -------------------------------------------------------------------------------- 1 | {r}←{cbinfo}GetLastKnownState cid;m;ns 2 | :If 0=⎕NC'cbinfo' ⋄ cbinfo←'' 0 ⋄ :EndIf ⍝ do not call back 3 | 4 | r←cbinfo SendWait cid('GetLastKnownState'(⎕NS'')) 5 | :If (0=≢⊃cbinfo) ⍝ No callback fn 6 | :If 'LastKnownState'≢⊃r 7 | ⎕←r 8 | ∘∘∘ 9 | :EndIf 10 | r←r[2] 11 | :EndIf 12 | -------------------------------------------------------------------------------- /AplSource/Subscribe.aplf: -------------------------------------------------------------------------------- 1 | {r}←Subscribe(cid facts fn);m;ns 2 | 3 | facts←,⊆facts 4 | facts,←(0=≢facts)/⊂'All' 5 | :If ∨/m←~facts∊allsubs,⍳≢allsubs 6 | ('Unknown facts: ',,⍕m/facts)⎕SIGNAL 11 7 | :EndIf 8 | 9 | ns←⎕NS'' 10 | ns.Events←facts 11 | r←(fn 0) SendWait cid('Subscribe'ns) 12 | :If 'Subscribed'≡⊃r 13 | r←r[2] 14 | :EndIf 15 | -------------------------------------------------------------------------------- /AplSource/Log.aplf: -------------------------------------------------------------------------------- 1 | Log x;txt 2 | 3 | txt←,⍕(⊃'hh:mm:ss'(1200⌶)1 ⎕DT'J'),' ',x 4 | :If 0≠⎕NC 'hmonlog' 5 | :If (40↑x)≡40↑9↓⊃hmonlog.Text 6 | hmonlog.Text[1]←⊂txt ⍝ Just update top line 7 | :Else 8 | hmonlog.Text←(⊂txt),¯1↓hmonlog.Text 9 | :EndIf 10 | :EndIf 11 | 12 | :If logging>0 13 | :If logtn∊⎕NNUMS 14 | (utf8 x,nl)⎕NAPPEND logtn 15 | :EndIf 16 | :EndIf 17 | -------------------------------------------------------------------------------- /AplSource/DemoForm.aplf: -------------------------------------------------------------------------------- 1 | DemoForm dummy 2 | 'FORM'⎕WC'Form' 'Close me to Crash'('Posn'(30 30))('Size'(100 350))('Coord' 'Pixel') 3 | 'FORM.L1' ⎕WC 'Label' 'Set Identity:' (10 10) 4 | 'FORM.ID' ⎕WC 'Edit' '' (10 100)(22 150) 5 | 'FORM.SETID' ⎕WC 'Button' 'Set' (10 270)(22 50)('Event' 'Select' 'DemoAction') 6 | 7 | 'FORM.L2' ⎕WC 'Label' 'Send Message:' (40 10) 8 | 'FORM.MSG' ⎕WC 'Edit' '' (40 100)(22 150) 9 | 'FORM.SEND' ⎕WC 'Button' 'Send' (40 270)(22 50)('Event' 'Select' 'DemoAction') 10 | 11 | ⎕DQ 'FORM' 12 | -------------------------------------------------------------------------------- /AplSource/Listen.aplf: -------------------------------------------------------------------------------- 1 | r←Listen (host port);d 2 | ⍝ Listen for incoming HMON connections and add them as required 3 | 'Cannot start listener - monitor thread already running' ⎕SIGNAL (monitortid∊⎕TNUMS)/11 4 | 5 | d←iConga.Srv '' host port 'BlkText' 32768 ('Magic' magic) 6 | :If 0≢⊃d 7 | ('Unable to start listener on ',host,':',(⍕port),' - ',3⊃d) ⎕SIGNAL 11 8 | :EndIf 9 | listener←2⊃d 10 | Log'Listener ',listener,' created on port ',(⍕port),,⊃' YYYY-MM-DD hh:mm:ss'(1200⌶)1 ⎕DT'J' 11 | 12 | monitortid←monitor&1 13 | -------------------------------------------------------------------------------- /AplSource/Wait.aplf: -------------------------------------------------------------------------------- 1 | r←cid Wait uid;type;con;id;z 2 | ⍝ Wait for the right kind of token to be put 3 | 4 | con←uid=0 ⍝ Not an identified transation? 5 | (type id)←(1+con)⊃¨('uid' 'cid')(uid cid) 6 | 7 | :If uid=0 ⍝ If not a normal identified request, add a dummy queue item 8 | :Hold 'hmon' 9 | queue⍪←cid 0 '' 10 | :EndHold 11 | :EndIf 12 | 13 | runmonitor ⍬ 14 | z←5 ⎕TGET type GetToken id 15 | :If 0=≢z 16 | 'TIMEOUT'⎕SIGNAL 1006 17 | :Else 18 | Log'GET ',(⍕cid),': ',{0::,⍕⍵ ⋄ 1 ⎕JSON ⍵}r←⊃z 19 | :EndIf 20 | -------------------------------------------------------------------------------- /AplSource/Handshake.aplf: -------------------------------------------------------------------------------- 1 | ok←Handshake cid;sp;z;con;host;port 2 | ⍝ Perform RIDE/HMON handshake 3 | 4 | ok←0 5 | :If (sp←'SupportedProtocols=2')≡z←cid Wait 0 6 | :AndIf 0=⊃z←cid Send sp 7 | :AndIf 'UsingProtocol=2'≡up←cid Wait 0 8 | :AndIf 0=⊃z←cid Send up 9 | (con host port)←conns[conns[;1]⍳cid;2 3 4] 10 | Log'Connected ',con,' from ',host,':',⍕port 11 | :If 3=⎕NC onconnection 12 | ⍎onconnection,' cid' 13 | :EndIf 14 | ok←1 15 | :Else 16 | :Hold 'hmon' 17 | conns←(conns[;1]≠cid)⌿conns 18 | :EndHold 19 | :EndIf 20 | -------------------------------------------------------------------------------- /AplSource/DemoConnect.aplf: -------------------------------------------------------------------------------- 1 | DemoConnect cid;i;z 2 | ⍝ On connection, set up all subscriptions 3 | 4 | ⍝ First expand the demodata table to receive values 5 | :If (≢demodata)args[3] 3 | text←line⊃args[1].Text 4 | hdr←(¯1+text⍳'[')↑text 5 | text←(≢hdr)↓text 6 | text←{0::⍵ ⋄ ⎕JSON⍠'Compact' 0⊢⎕JSON ⍵}text 7 | 8 | :If 9≠⎕NC'hmonscroll' 9 | hmonscroll←⎕NEW 'Form' (('Caption' ('Log Record #',⍕line))('Size'(size←800 400))('Posn'(40,¯420+2⊃⊃'.'⎕WG'DevCaps'))('Coord' 'Pixel')('FontObj' hmonform.FontObj)('VScroll' ¯2)) 10 | txt←hmonscroll.⎕NEW 'Edit' (('Style' 'Multi')('Posn' (10 10))('Size' (size-20))('VScroll' ¯1)) 11 | :EndIf 12 | :If ~∧/hdr=' ' 13 | hmonscroll.Caption←hdr 14 | hmonscroll.onClose←'ScrollClose' 15 | txt.Text←{1↓¨((⎕UCS 13)=⍵)⊂⍵}(⎕UCS 13),text ⋄ 16 | :EndIf 17 | -------------------------------------------------------------------------------- /AplSource/GetFacts.aplf: -------------------------------------------------------------------------------- 1 | {r}←{cbinfo}GetFacts(cid facts);m;ns;poll;op;int;ok;retried 2 | :If 0=⎕NC'cbinfo' ⋄ cbinfo←'' 0 ⋄ :EndIf ⍝ do not call back 3 | 4 | facts←,⊆facts 5 | facts,←(0=≢facts)/allfacts 6 | :If ∨/m←~facts∊allfacts,⍳≢allfacts 7 | ('Unknown facts: ',,⍕m/facts)⎕SIGNAL 11 8 | :EndIf 9 | 10 | (ns←⎕NS'').Facts←facts 11 | :If poll←0≠int←2⊃cbinfo 12 | ns.Interval←int⊣op←'PollFacts' 13 | :Else 14 | op←'GetFacts' 15 | :EndIf 16 | 17 | retried←ok←0 18 | :Repeat 19 | r←cbinfo SendWait cid(op ns) 20 | :If ~poll 21 | :If r≢'CONNECTION CLOSED' 22 | :AndIf 0≠r[2].⎕NC 'Facts' 23 | r←r[2].Facts⊣ok←1 24 | :Else 25 | retried+←1 26 | :EndIf 27 | :EndIf 28 | :Until ok∨poll∨retried>5 29 | 30 | :If retried>5 31 | ∘∘∘ 32 | :EndIf 33 | -------------------------------------------------------------------------------- /AplSource/DemoApplication.aplf: -------------------------------------------------------------------------------- 1 | DemoApplication dummy;framework;z;a;path 2 | 2 ⎕FIX¨ ⎕SE.Link.LaunchDir∘,¨'/DemoForm.aplf' '/DemoAction.aplf' 3 | 4 | 'POLL:localhost:7000'(112⌶)2 1 ⍝ 2=Full access to info, 1 collect "breadcrumbs" for "Last Status" 5 | 6 | ⎕FX'LOOP delay;z' 'z←⎕DL delay' '→1' 7 | LOOP&1 ⍝ Launch a thread to keep things ticking over 8 | DemoForm&⍬ 9 | framework←2=⊃2250⌶⍬ 10 | 11 | ⎕USING←',System.Runtime',framework/'.dll' 12 | :Repeat 13 | ⎕DL 3 14 | 15 | :If 0=⎕NC'FORM' ⍝ Time to crash? 16 | ⎕FX'FOO' '1÷0' 17 | FOO 18 | :EndIf 19 | 20 | :Select ?5 21 | :Case 1 22 | z←⎕DL 2 23 | :Case 2 24 | (System.Threading.Tasks.Task.Delay 2000).Wait ⍬ 25 | :Case 3 26 | z←+/10000000?10000000 27 | :Case 4 28 | z←⎕WA 29 | :Case 5 30 | a←(?10000000)⍴⎕A 31 | :EndSelect 32 | :EndRepeat 33 | -------------------------------------------------------------------------------- /AplSource/DemoCellDown.aplf: -------------------------------------------------------------------------------- 1 | DemoCellDown arg;row;cid;z;hdr;host;threads;address;port;data 2 | row←7⊃arg 3 | →(row∊⍳≢demodata)↓0 4 | (cid address)←demodata[row;democols⍳'id' 'address'] 5 | hdr←'Process #',(⍕cid),' (@',address,') at time ',(⊃'hh:mm:ss'(1200⌶)1 ⎕DT'J'),':' 6 | 7 | :If cid∊conns[;1] 8 | (threads host)←GetFacts cid ('Threads' 'Host') 9 | port←{6::0 ⋄ ⊃⍵.(Port6 Port4)~0}host.Value.RIDE 10 | 11 | hmontxt.Text←hdr 12 | hmonstack.Text←2 0↓⍕'TID' '' 'Stack'⍪(⊂'')⍪{⍵[⍋⍵[;1];]}↑threads.Values.(('&',⍕Tid) (' *'[1+Suspended]) (⍪Stack.Description,⊂'')) 13 | :If 0≠port 14 | data←(⊃conns[conns[;1]⍳cid;3]),':',⍕port 15 | hmonride.(Visible Active Data Caption)←1 1 data ('RIDE to ',data) 16 | :Else 17 | hmonride.Visible←0 18 | :EndIf 19 | hmongrid.CellTypes[;1]←1+2×cid=⍳≢conns 20 | :Else 21 | hmontxt.Text←'(Process not found)' 22 | hmonstack.Text←'' 23 | :EndIf 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HMON - Dyalog Health Monitor 2 | 3 | HMON is a protocol supported by version 19.0 and later of Dyalog APL. It allows the writing of applications which monitor the state of one or more running APL interpreters. 4 | 5 | ## docs 6 | 7 | The ```docs``` folder contains documentation: 8 | 9 | [docs/Protocol.md](docs/Protocol.md) Describes the protocol supported by the interpreter. 10 | 11 | [docs/UsageGuide.md](docs/UsageGuide.md) Describes the sample application and provides guidelines for writing your own client. 12 | 13 | ## AplSource 14 | 15 | The source code for the demonstration client can be found here. See the [Usage Guide](docs/UsageGuide.md) for more instructions on how to load and run this code.. 16 | 17 | ## Reporting Issues 18 | 19 | Issues with the HMON documentation and sample can be reported here as GitHub issues or via the Dyalog Helpdesk at support@dyalog.com. 20 | 21 | Issues with the HMON interface itself within Dyalog should be reported via the Dyalog Helpdesk at support@dyalog.com (or, for Dyalog employees, using Mantis). 22 | -------------------------------------------------------------------------------- /AplSource/SendWait.aplf: -------------------------------------------------------------------------------- 1 | r←{cbinfo}SendWait(cid u);i;uid;payload;z;fn;poll;retried 2 | :If 0=⎕NC'cbinfo' ⋄ cbinfo←'' 0 ⋄ :EndIf 3 | (fn poll)←cbinfo 4 | :If 0≠≢fn 5 | ('Callback function ',fn,' not found')⎕SIGNAL(3≠⎕NC fn)/6 6 | :EndIf 7 | 8 | retried←0 9 | 10 | RETRY: 11 | 12 | :Hold 'hmon' 13 | :If (2=≢u)∧326=⎕DR u 14 | u[2].UID←⍕cid,uid←⊃(⍳1+≢queue)~queue[;1] 15 | payload←⎕JSON u 16 | :Else 17 | uid←0 ⋄ payload←u 18 | :EndIf 19 | queue←(queue∨.≢cid uid fn)⌿queue ⍝ avoid duplicates 20 | 'Queue is full'⎕SIGNAL(uid>999)/11 21 | queue←cid uid fn⍪queue ⍝ lifo 22 | :EndHold 23 | 24 | :If 0≠⊃z←cid Send payload 25 | ⎕←'Send Failed' 26 | ∘∘∘ 27 | :EndIf 28 | 29 | :If (0=≢fn)∨'Subscribe'≡⊃u ⍝ No callback function - or subscribe which gets both a result AND a subscription 30 | :Trap 1006 31 | r←cid Wait uid 32 | :Else 33 | :If retried<5 34 | retried+←1 35 | →RETRY 36 | :Else 37 | ∘∘∘ 38 | :EndIf 39 | :EndTrap 40 | :Else 41 | r←runmonitor ⍬ 42 | :EndIf 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Dyalog 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /AplSource/Init.aplf: -------------------------------------------------------------------------------- 1 | {ok}←Init 2 | 3 | allfacts←'Host' 'AccountInformation' 'Workspace' 'Threads' 'SuspendedThreads' 'ThreadCount' 4 | allsubs←'WorkspaceCompaction' 'WorkspaceResize' 'UntrappedSignal' 'TrappedSignal' 'ThreadSwitch' 'All' 5 | listener←'' ⍝ not listening (yet) 6 | onconnection←'' ⍝ nothing specified to do on connection 7 | 8 | logging←0 9 | uidcnt←cidcnt←0 ⍝ request and connection counters 10 | monitortid←¯1 ⍝ queue monitor thread not running 11 | conns←0 4⍴0 ⍝ id connection host port 12 | queue←0 3⍴0 ⍝ cid uid callbackfn 13 | token←⎕TALLOC 1 'HMON Client' 14 | 15 | ⍝ Establish connection 16 | ⎕EX 'iConga' ⍝ Let's start again 17 | :If 9≠⎕NC 'Conga' 18 | 'Conga'⎕CY'conga' 19 | :EndIf 20 | iConga←Conga.Init'HMONCLIENT' 21 | {}iConga.SetProp'' 'EventMode' 1 22 | magic←iConga.Magic 'HMON' 23 | 24 | :If 0≠logtn←2=⎕NC'logfile' 25 | :Trap 0 26 | logtn←logfile ⎕NTIE 0 27 | :Else 28 | :Trap 0 29 | logtn←logfile ⎕NCREATE 0 30 | :Else 31 | ('Unable to create ',logfile)⎕SIGNAL 22⊣logtn←0 32 | :EndTrap 33 | :EndTrap 34 | :EndIf 35 | 36 | ok←0 37 | -------------------------------------------------------------------------------- /AplSource/Demo.aplf: -------------------------------------------------------------------------------- 1 | Demo;size;widths;sz 2 | ⍝ HMON demo application 3 | 4 | democols ←'id' 'ts' 'address' 'cpu(s)' 'ws(Mb)' 'compactions' 'threads' 'suspended' 'Desc' 5 | demoprotos← 0 0 '' 0 0 '' 0 0 '' 6 | demodata←(0,≢democols)⍴demoprotos 7 | widths←60 120 100 130 130 140 100 130 150 8 | 9 | hmonform←⎕NEW 'Form' (('Caption' 'Health Monitor')('Coord' 'Pixel')('Size' (size←800,22++/widths))('Font' ('Calibri' 20))) 10 | hmonform.onClose←'DemoClose' 11 | hmongrid←hmonform.⎕NEW 'Grid' (('Posn' (10 10))('Size' (sz←size-630 20))('Values' demodata)('ColTitles' democols)('CellTypes' (1⍨¨demodata))) 12 | hmongrid.(TitleWidth CellWidths)←0 widths 13 | hmongrid.BCol←(255 255 255)(255 0 0)(196 196 255) 14 | hmongrid.FCol←(0 0 0)(255 255 255)(255 255 255) 15 | hmongrid.ColTitleAlign←⊂'Left' ⍝ 'Right' 'Left' 'Right' 'Centre' 'Centre' 'Left' 'Left' 'Left' 16 | hmongrid.onCellDown←'DemoCellDown' 17 | hmonride←hmonform.⎕NEW 'Button' (('Caption' 'Connect RIDE')('Posn' ((20+⊃sz),¯210+2⊃size))('Size' (⍬ 200))('Visible' 0)) 18 | hmonride.onSelect←'DemoRIDEConnect' 19 | hmonview←hmonform.⎕NEW 'Button' (('Caption' 'View Log')('Posn' ((300+⊃sz),10))('Size' (⍬ 100))('Style' 'Check')('State' 0)) 20 | hmonview.onSelect←'DemoView' 21 | DemoView hmonview 22 | hmontxt←hmonform.⎕NEW 'Text' (('Text' 'Click on a row to see more')('Points' ((20+⊃sz) 10))('Font' ('Calibri' 24 0 0 0 600))) 23 | hmonstack←hmonform.⎕NEW 'Text' (('Text' '')('Font' ('APL385 Unicode' 16))('Points' ((60+⊃sz) 10))) 24 | 25 | hmonlog←hmonform.⎕NEW 'Text' (('Text' (20↑⊂'Log messages will appear here'))('Points' ((320+(16×⍳20)+⊃sz) 10))) 26 | hmonlog.onMouseDown←'DemoViewLog' 27 | 28 | Init 29 | onconnection←'DemoConnect' 30 | Listen 'localhost' 7000 31 | -------------------------------------------------------------------------------- /AplSource/DemoReceive.aplf: -------------------------------------------------------------------------------- 1 | DemoReceive arg;UID;Facts;data;TS;event;ts;cid;wsused;cpuused;compactions;ai;ws;line;fn;code;txt;threadcount;f;z;uid;m;adjustedheaders;standardfacts 2 | ⍝ Receive all types of callbacks 3 | (event data)←arg 4 | ts←1 ⎕DT'J' 5 | cid←2 1⊃⎕VFI UID←data.UID 6 | :If (≢demodata)≠≢conns 7 | demodata←(m←demodata[;1]∊conns[;1])⌿demodata 8 | hmongrid.(Values CellTypes)←m∘⌿¨hmongrid.(Values CellTypes) 9 | :EndIf 10 | 11 | 12 | standardfacts←'Workspace' 'AccountInformation' 'ThreadCount' 'Host' 13 | :Select event 14 | 15 | :Case 'Notification' 16 | :If 'UntrappedSignal'≡data.Event.Name 17 | uid←(2⊃⎕VFI UID)-0 1 ⍝ /// temporary fix while we wait for a better way to ask for immediate push 18 | z←cid Send '["PollFacts",{"Facts": [',(⍕standardfacts),'], "Interval":0,"UID":"',(⍕uid),'"}]' ⍝ Request immediate push of Polling data 19 | :EndIf 20 | 21 | :Case 'Facts' 22 | :If 0≠≢data.Facts 23 | :AndIf ∧/(data.f←standardfacts)∊data.Facts.Name ⍝ Standard poll data 24 | (ws ai threadcount host)←data.(Facts[Facts.Name⍳f].Value) 25 | (wsused compactions)←ws.(Used Compactions) 26 | cpuused←ai.ComputeTime 27 | adjustedheaders←democols~democols[1 3] ⍝ adjustheaders contain headers but without 'id' and 'address' 28 | DemoUpdate cid adjustedheaders(ts cpuused wsused compactions,threadcount.(Total Suspended),⊂host.Machine.Desc) 29 | :Else 30 | Log 'Unexpected facts received: ',⎕JSON data 31 | :EndIf 32 | ⍝ 'DemoReceive' 2000 GetLastKnownState cid ⍝ Request last known state 33 | 34 | :Case 'LastKnownState' 35 | :If 0≠data.⎕NC'Activity' 36 | (code fn line)←data.(Activity.code,Location.(Function Line)) 37 | txt←(⍕code),': ',fn,'[',(⍕line),']' 38 | DemoUpdate cid('ts' 'laststate')(ts txt) 39 | :EndIf 40 | 41 | :Case 'Subscribed' 42 | subs←↑data.Events.(Name Value) 43 | ⎕←subs 44 | 45 | :Else 46 | ⎕←event(⎕JSON data) 47 | ⍝ We do nothing right now 48 | :EndSelect 49 | -------------------------------------------------------------------------------- /AplSource/monitor.aplf: -------------------------------------------------------------------------------- 1 | monitor listen;z;rc;con;event;data;ns;i;cid;uid;type;tkn;fn;m;removed;done;cb;port;host;json 2 | ⍝ While there is anything in the queue, monitor Conga messages 3 | ⍝ and release functions waiting on ⎕TPUT 4 | 5 | listening←listen 6 | :While listening∨0≠≢queue 7 | :If 0=⊃z←iConga.Wait'.' 5000 8 | (rc con event data)←4↑z 9 | 10 | :Select event 11 | :Case 'Timeout' ⋄ :Continue 12 | :Case 'Connect' 13 | (host port)←(2⊃iConga.GetProp con'PeerAddr')[2 4] 14 | cid←AddConnection con((-':'⍳⍨⌽host)↓host)port 15 | Handshake&cid ⍝ Handshake must run on another thread 16 | 17 | :Case 'Block' 18 | :If (≢conns) ',,⍕z 21 | :Continue 22 | :Else 23 | cid←conns[i;1] 24 | :EndIf 25 | 26 | uid←0 27 | :Trap 6 11 ⍝ Might be handshake and not JSON data, or JSON data with no UID 28 | json←'UTF-8' ⎕UCS ⎕UCS data 29 | data←0 ⎕JSON json 30 | Log 'GET ',(⍕cid),': ',json 31 | tkn←'uid'GetToken uid←2⊃2⊃⎕VFI data[2].UID 32 | :Else 33 | tkn←'cid'GetToken cid 34 | :EndTrap 35 | 36 | :Hold 'hmon' 37 | cb←0 38 | :If 'UserMessage'≡⊃data 39 | Log 'User Message from ',(⍕cid),': ',(2⊃data).Message 40 | :ElseIf (≢queue) ',,⍕z 43 | :Continue 44 | :ElseIf cb←(0≠≢fn←⊃queue[i;3])∧'Subscribed'≢⊃data ⍝ Callback fn (ignore for "Subscribe" call) 45 | (⍎fn)&data ⍝ Run it in a separate thread 46 | :EndIf 47 | 48 | done←{2 6::0 ⋄ ⍵[2].Interval=0}data ⍝ Polling done 49 | :If done∨0=≢fn ⍝ ... or no callback fn 50 | queue←(i≠⍳≢queue)⌿queue 51 | :EndIf 52 | :If ~cb ⍝ No callback done, return result via TPUT 53 | data ⎕TPUT tkn 54 | :EndIf 55 | :EndHold 56 | 57 | :CaseList 'Closed' 'Error' 58 | Log event,': ',con 59 | :If con≡listener 60 | ∘∘∘ 61 | :EndIf 62 | 63 | :Hold 'hmon' 64 | cid←(conns[;1],¯1)[conns[;2]⍳⊂con] 65 | removed←(m←cid=queue[;1])⌿queue 66 | :If cid≠¯1 67 | queue←(~m)⌿queue 68 | conns←(m←conns[;1]≠cid)⌿conns 69 | hmongrid.(Values CellTypes)←m∘⌿¨hmongrid.(Values CellTypes) 70 | Log'===> ',(⍕≢removed),' queue items removed' 71 | :EndIf 72 | :EndHold 73 | :For uid :In removed[;2]~0 74 | 'CONNECTION CLOSED'⎕TPUT'uid'GetToken uid 75 | :EndFor 76 | :Else 77 | ∘∘∘ 78 | :EndSelect 79 | 80 | :Else 81 | Log'*** Conga.Wait failed: ',⍕z 82 | ∘∘∘ 83 | :EndIf 84 | :EndWhile 85 | 86 | :If listen 87 | Log'Listener closed ',⊃'YYYY-MM-DD hh:mm:ss'(1200⌶)1 ⎕DT'J' 88 | iConga.Close listener 89 | listener←'' 90 | :EndIf 91 | -------------------------------------------------------------------------------- /docs/UsageGuide.md: -------------------------------------------------------------------------------- 1 | # HMON Usage Guide 2 | 3 | This document is intended to provide guidance on the use of Dyalog's Health Monitor (HMON) protocol. The document makes reference to a demonstration application which uses the protocol to implement a monitor for multiple running APL applications; the code examples in this document are taken from that, the full source code can be found in the [APLSource folder](https://github.com/Dyalog/HMon/blob/main/APLSource. 4 | 5 | This document does not go into detail about the protocol itself, instead we refer to the [official documentation for the protocol](https://github.com/Dyalog/HMon/blob/main/docs/Protocol.md). 6 | 7 | ## Configuring the Interface 8 | 9 | Configuring the HMON interface involves choosing the connection mode, access levels, event gathering Levels, and whether the Application should respond to high-priority request messages. 10 | 11 | The connection modes, access and event gathering levels can be set by the application which wants to allow monitoring, using ``112⌶`` to initiate the connection. 12 | 13 | ```apl 14 | ⍝ AplSource/DemoApplication.aplf 15 | [2] 'POLL:localhost:7000'(112⌶)2 1 16 | ``` 17 | 18 | See the [protocol document](https://github.com/Dyalog/HMon/blob/main/docs/Protocol.md) for a detailed description of the right argument. In this example, we enable full access to monitoring information (2) and also ask the interpreter to gather information that will support the ```GetLastKnownState``` request (1). Enabling this has a runtime cost, because the interpreter will keep track of status information and allow HMON to respond even in situations where the interpreter itself cannot respond because it is in the middle of a call to an external library. 19 | 20 | Note that in the current implementation, if you want to use ```GetLastKnownState```, it is not sufficient to request state information using ``112⌶``, the application must also enable 'coverage' monitoring using ```⎕PROFILE```. 21 | 22 | ```apl 23 | ⎕PROFILE 'start' 'coverage' 24 | ``` 25 | 26 | The left argument has the same format and meaning as the [RIDE_INIT environment setting](https://help.dyalog.com/18.0/Content/UserGuide/Installation%20and%20Configuration/Configuration%20Parameters/RIDE_Init.htm). 27 | 28 | Note that the demonstration client application uses ``POLL``, while the protocol document suggests ``SERVE``. This is is because the easiest way to experiment with the protocol is to have a single interpreter act as the server which you can make a connection to. However, the demo application acts as a monitor for multiple APL interpreters. In this case it is more practical for the client to start a listener (in this case on port 7000) and have each interpreter that wants to be monitored attempt to connect to this port - retrying at regular intervals if the monitor is not present. This allows application processes to come and go - and even allows the monitor to be restarted, if it disappears and comes back each monitored APL process will reconnect to it within a short period of time. 29 | 30 | Finally, note that in the current implementation, HMON will only generate timer-driven messages (for example, responses to ```POLLFacts```) when it is active. If no threads are running in the application, HMON will not respond to anything other than high priority requests. The workaround in the demo client application is to have a thread looping with a delay function: 31 | 32 | ```apl 33 | ⍝ AplSource/DemoApplication.aplf 34 | [4] ⎕FX 'LOOP delay;z' 'z←⎕DL delay' '→1' 35 | [5] LOOP&1 36 | ``` 37 | 38 | ## Connecting Client to the monitored Application(s) 39 | 40 | A client of HMON must establish a Conga connection to the interpreter which is to be monitored, using using ```BlkText``` mode, as described in the [protocol documentation](https://github.com/Dyalog/HMon/blob/main/docs/Protocol.md). As previously mentioned, both sides can initiate the connection, depending on the requirements of the application. The sample client assumes that the goal is to monitor multiple APL interpreters from a single point. 41 | 42 | In this section we describe the design choices taken in the demo client. 43 | 44 | ### Running the demo application and client 45 | 46 | Note that the demonstration client requires Dyalog APL for Microsoft Windows (but the monitored applications can run on any platform). 47 | 48 | To start the client, link the APLSource folder and run the function ``Demo`` with out any arguments. For example, if you checked the repository out to ```/tmp/hmon```: 49 | 50 | ``` 51 | ]link.create # /tmp/hmon/AplSource 52 | Demo 53 | ``` 54 | 55 | To create some simple processes that you can monitor, run ``LaunchDemoProcess 0``, which starts a new APL process to run the function ```DemoApplication```. The right argument allows for a Ride connection when set to 1, to use this functionality you must set ```RIDE_PATH``` variable to the path of your RIDE executable. 56 | 57 | Hopefully you can get an idea of what design choices have been made by playing a bit around with this Demo. 58 | 59 | ### Setting up the connection 60 | 61 | Firstly we need to set up the protocol on the client side. The client copies the Conga class from the distributed "conga" workspace, creates an instance of the Conga protocol, and computes the magic number used for the ```BlkText``` protocol: 62 | 63 | ```apl 64 | ⍝ AplSource/Init.aplf 65 | [16] :If 9≠⎕NC 'Conga' 66 | [17] 'Conga'⎕CY'conga' 67 | [18] :EndIf 68 | [19] iConga←Conga.Init'HMONCLIENT' 69 | [20] {}iConga.SetProp'' 'EventMode' 1 70 | [21] magic←iConga.Magic 'HMON' 71 | ``` 72 | 73 | The Demo creates a listener that all monitored applications can connect to, and runs the monitor function on a new thread. 74 | 75 | ```apl 76 | ⍝ AplSource/Listen.aplf 77 | [4] d←iConga.Srv '' host port 'BlkText' 32768 ('Magic' magic) 78 | [5] :If 0≢⊃d 79 | [6] ('Unable to start listener on ',host,':',(⍕port),' - ',3⊃d) ⎕SIGNAL 11 80 | [7] :EndIf 81 | ... 82 | [11] monitortid←monitor&1 83 | ``` 84 | 85 | The ```monitor``` function handles new connections and each incoming block: 86 | 87 | ```apl 88 | [ 0]monitor listen;z;rc;con;event;data;ns;i;cid;uid;type;tkn;fn;m;removed;done;cb;port;host;json 89 | [ 1] ⍝ While there is anything in the queue, monitor Conga messages 90 | [ 2] ⍝ and release functions waiting on ⎕TPUT 91 | [ 3] 92 | [ 4] listening←listen 93 | [ 5] :While listening∨0≠≢queue 94 | [ 6] :If 0=⊃z←iConga.Wait'.' 5000 95 | [ 7] (rc con event data)←4↑z 96 | [ 8] 97 | [ 9] :Select event 98 | [10] :Case 'Timeout' ⋄ :Continue 99 | [11] :Case 'Connect' 100 | [12] (host port)←(2⊃iConga.GetProp con'PeerAddr')[2 4] 101 | [13] cid←AddConnection con((-':'⍳⍨⌽host)↓host)port 102 | [14] Handshake&cid ⍝ Handshake must run on another thread 103 | [15] 104 | [16] :Case 'Block' 105 | [17] :If (≢conns) ',,⍕z 108 | [20] :Continue 109 | [21] :Else 110 | [22] cid←conns[i;1] 111 | [23] :EndIf 112 | [24] 113 | [25] uid←0 114 | [26] :Trap 6 11 ⍝ Might be handshake and not JSON data, or JSON data with no UID 115 | [27] json←'UTF-8' ⎕UCS ⎕UCS data 116 | [28] data←0 ⎕JSON json 117 | [29] Log 'GET ',(⍕cid),': ',json 118 | [30] tkn←'uid'GetToken uid←2⊃2⊃⎕VFI data[2].UID 119 | [31] :Else 120 | [32] tkn←'cid'GetToken cid 121 | [33] :EndTrap 122 | [34] 123 | [35] :Hold 'hmon' 124 | [36] cb←0 125 | [37] :If (≢queue) ',,⍕z 128 | [40] :Continue 129 | [41] :ElseIf cb←(0≠≢fn←⊃queue[i;3])∧'Subscribed'≢⊃data ⍝ Callback fn (ignore for "Subscribe" call) 130 | [42] (⍎fn)&data ⍝ Run it in a separate thread 131 | [43] :EndIf 132 | [44] 133 | [45] :If done←80≠⎕DR data ⍝ If namespace, check polling state 134 | [46] done←data[2].{6::0 ⋄ Interval=0}⍬ ⍝ Polling done 135 | [47] :EndIf 136 | [48] :If done∨0=≢fn ⍝ ... or no callback fn 137 | [49] queue←(i≠⍳≢queue)⌿queue 138 | [50] :EndIf 139 | [51] :If ~cb ⍝ No callback done, return result via TPUT 140 | [52] data ⎕TPUT tkn 141 | [53] :EndIf 142 | [54] :EndHold 143 | [55] 144 | [56] :CaseList 'Closed' 'Error' 145 | [57] Log event,': ',con 146 | [58] :If con≡listener 147 | [59] ∘∘∘ 148 | [60] :EndIf 149 | [61] 150 | [62] :Hold 'hmon' 151 | [63] cid←(conns[;1],¯1)[conns[;2]⍳⊂con] 152 | [64] removed←(m←cid=queue[;1])⌿queue 153 | [65] :If cid≠¯1 154 | [66] queue←(~m)⌿queue 155 | [67] conns←(m←conns[;1]≠cid)⌿conns 156 | [68] hmongrid.(Values CellTypes)←m∘⌿¨hmongrid.(Values CellTypes) 157 | [69] Log'===> ',(⍕≢removed),' queue items removed' 158 | [70] :EndIf 159 | [71] :EndHold 160 | [72] :For uid :In removed[;2]~0 161 | [73] 'CONNECTION CLOSED'⎕TPUT'uid'GetToken uid 162 | [74] :EndFor 163 | [75] :Else 164 | [76] ∘∘∘ 165 | [77] :EndSelect 166 | [78] 167 | [79] :Else 168 | [80] Log'*** Conga.Wait failed: ',⍕z 169 | [81] ∘∘∘ 170 | [82] :EndIf 171 | [83] :End 172 | ``` 173 | -------------------------------------------------------------------------------- /docs/Protocol.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | The Health Monitor (HMON) interface to the Dyalog interpreter allows a client 4 | application or tool to connect and monitor its activity, memory usage etc., 5 | to gauge its "state of health". The communications protocol between the 6 | interpreter and client is described in this document. 7 | 8 | THE HEALTH MONITOR INTERFACE IS UNDER CONTINUING DEVELOPMENT AND IS 9 | INCLUDED IN DYALOG 19.0 AND 20.0 AS AN EXPERIMENTAL FEATURE. FURTHER DEVELOPMENT IS 10 | PLANNED FOR SUBSEQUENT RELEASES AND THIS MAY RESULT IN CHANGES TO THE INTERFACE 11 | WHICH ARE INCOMPATIBLE WITH THE SPECIFICATION DESCRIBED HERE. 12 | 13 | > [!NOTE] 14 | > Features which differ between different versions of Dyalog are highlighted like this. 15 | 16 | # Establishing connections and controlling access levels 17 | 18 | The interpreter to be monitored may either listen for incoming client 19 | connections or initiate a connection to a client. It will not do either unless 20 | explicitly configured to do so using either the `HMON_INIT` config setting 21 | (prior to startup) or [`112⌶`](#112) (from within the interpreter). 22 | 23 | The connection mode, address and port on which the interpreter should listen 24 | or connect (the Interface Configuration) is determined by a character sequence 25 | of the form: 26 | 27 | `mode:address:port` 28 | 29 | where: 30 | 31 | - _mode_ is the connection mode - either SERVE (listen for incoming connections) 32 | or POLL (initiate outgoing connection). 33 | - _address_ is the address of an interface on the local machine on which the 34 | the interpreter should listen (SERVE mode) or the address of a machine 35 | running a client to which the interpreter should connect (POLL mode). 36 | - _port_ is the TCP port to listen on or connect to. 37 | 38 | In SERVE mode _address_ may be specified as follows: 39 | 40 | - `` - listen on all loopback interfaces, that is, the interpreter only 41 | accepts connection from the local machine. 42 | - `*` – listen on all local machine interfaces, that is, the interpreter 43 | listens for connections from any (local or remote) machine/interface. 44 | - The host/DNS name of the machine/interface running the interpreter – 45 | listen on that specific interface on the local machine. 46 | - The IPv4 address of the machine/interface running the interpreter – 47 | listen on that specific interface on the local machine. 48 | - The IPv6 address of the machine/interface running the interpreter – 49 | listen on that specific interface on the local machine. 50 | 51 | In POLL mode _address_ may be specified as follows: 52 | 53 | - `` – connect to a client on the the local machine. 54 | - The host/DNS name of the machine/interface running the client. 55 | - The IPv4 address of the machine/interface running the client. 56 | - The IPv6 address of the machine/interface running the client. 57 | 58 | For example: 59 | 60 | `HMON_INIT="SERVE:localhost:4512"` 61 | 62 | The level of information made available by the interpreter is determined by an 63 | Access Level: 64 | 65 | - 0 - Disallow connections. 66 | - 1 - Permit connections, with restricted information provided and restricted 67 | permissions once connected. 68 | - 2 - Permit connections, with full information provided but restricted 69 | permissions once connected. 70 | - 3 - Permit connectons, with full information provided and full permissions 71 | once connected. 72 | 73 | Information which is not available when restricted includes anything which 74 | exposes the code of the aplication being run, such as the SI stack. Information 75 | such as memory usage, number of running threads etc. is not restricted is 76 | always available once a connection is established. 77 | 78 | Permissions which are not available when restricted include anything which can 79 | control (rather than observe) the interpreter, such establishing a connection 80 | to RIDE. 81 | 82 | The Access Level defaults to 1 with runtime interpreters and 2 with development 83 | interpreters. Access level 3 is never enabled by default. 84 | 85 | Some information which the interpreter provides requires it to perform 86 | additional work (which slows it down) even when a connected client is not 87 | requesting it. Whether it does this or not is controlled by setting an Event 88 | Gathering Level. 89 | 90 | - 0 - Do not gather information for [`GetLastKnownState`](#getlastknownstate) 91 | requests. 92 | - 1 - Gather information for [`GetLastKnownState`](#getlastknownstate) 93 | requests - has a runtime performance impact. 94 | 95 | The Event Gathering Level defaults to 0. 96 | 97 | The Access Level and Event Gathering Level may be altered from within the 98 | interpreter using [`112⌶`](#112). Alternatively, [`112⌶`](#112) may be used to 99 | set the interface configuration and the Levels without setting `HMON_INIT` prior 100 | to starting the interpreter, or to override any existing `HMON_INIT` setting. 101 | 102 | # Messages 103 | 104 | ## Overview 105 | 106 | A message starts with a 4-byte big-endian _total length_ field, followed by 107 | the four ASCII-encoded characters `HMON` and a UTF-8-encoded payload: 108 | 109 | ``` 110 | Total length "HMON" Payload 111 | ┌───────────────────┬───────────────────┬─────~─────┐ 112 | │0x00 0x00 0x00 0x0B│0x48 0x4D 0x4F 0x4E│ ... │ 113 | └───────────────────┴───────────────────┴─────~─────┘ 114 | ``` 115 | 116 | _Total length_ is therefore 8 + the byte length of the payload. 117 | 118 | The payload is almost always a 2-element JSON array consisting of a Message 119 | Name and arguments as key/value pairs: 120 | 121 | ```json 122 | ["MessageName",{"key1":"value1","key2":222,"key3":[3,4,5]}] 123 | ``` 124 | 125 | The only exception are the first two messages that each side sends upon 126 | establishing a connection. These constitute the _handshake_ and are not 127 | JSON-encoded. Their payloads are: 128 | 129 | ``` 130 | SupportedProtocols=2 131 | UsingProtocol=2 132 | ``` 133 | 134 | Messages sent by the client to the interpreter are _request_ messages. Messages 135 | sent by the interpreter to the client are _response_ messsages. 136 | 137 | The interpreter will usually respond to a request message by sending a response 138 | containing either the information requested, confirmation of receipt, or an 139 | error report (indicating e.g. invalid syntax or invalid message type etc.) It 140 | may also send response messages at any time - for example, to inform of a 141 | particular event or to provide regular updates on its condition, or if the 142 | application that the interpreter is running initiates them. 143 | 144 | The interpreter will process most request messages it receives when it is 145 | between execution of APL code or otherwise in a position where it can safely 146 | access its workspace, and may therefore not react immediately. The special 147 | request message [`GetLastKnownState`](#getlastknownstate) is designed to remain 148 | active and provide an immediate response in the event that the the interpreter 149 | is unable to respond at all to the others. 150 | 151 | Messages must be syntactically valid JSON text and of the 2-element array form 152 | described above. 153 | 154 | Named items in the payload are described in the relevant documentation for each 155 | message type. The interpreter will ignore any unexpected named item (so long as 156 | it is described using syntactically valid JSON). Except as noted, request 157 | messages may include the named item "UID" (a string value) and if present the 158 | response will echo the same UID value. UID strings may be used by a client as 159 | it wishes - for example, to track requests and responses; the interpreter does 160 | not require them. 161 | 162 | ## Request messages 163 | 164 | ### GetFacts 165 | 166 | Requests zero or more "facts" about the application state, and will be 167 | responded to with a [`Facts`](#facts) message. 168 | 169 | ```json 170 | ["GetFacts",{"Facts":["Host","Workspace"]}] 171 | ``` 172 | 173 | The following facts may be requested: 174 | 175 | | Fact ID | Fact name | 176 | | ------- | -------------------- | 177 | | 1 | "Host" | 178 | | 2 | "AccountInformation" | 179 | | 3 | "Workspace" | 180 | | 4 | "Threads" | 181 | | 5 | "SuspendedThreads" | 182 | | 6 | "ThreadCount" | 183 | 184 | See the [`Facts`](#facts) response message for the information provided. 185 | 186 | Fact may be requested using either their numeric Fact ID or their alphanumeric 187 | (string) Fact name, so the following `GetFacts` requests are equivalent to the 188 | one above: 189 | 190 | ```json 191 | ["GetFacts",{"Facts":[1,3]}] 192 | ``` 193 | 194 | ```json 195 | ["GetFacts",{"Facts":["Host",3]}] 196 | ``` 197 | 198 | ### PollFacts 199 | 200 | `PollFacts` behaves in the same way as [`GetFacts`](#getfacts) except that it 201 | polls - that is, the [`Facts`](#facts) message response will be sent 202 | immediately and then repeat after specified or implied intervals. 203 | 204 | The interval defaults to 1000ms but any value of 500ms or more may be 205 | specified. Values less than 500 will be taken as 500. 206 | 207 | Example: 208 | 209 | ```json 210 | ["PollFacts",{"Facts":[1,"Workspace"],"Interval":750}] 211 | ``` 212 | 213 | If a UID is specified it will appear in every message that is sent. 214 | 215 | Messages will continue at the requested frequency until either a new request 216 | is made (which will supersede any already established) or a 217 | [`StopFacts`](#stopfacts) message is sent. 218 | 219 | **Note:** polling responses may occasionally stop when the interpreter is 220 | waiting on an external event such as a file operation, `⎕NA` call, etc. It is 221 | currently also a limitation that polling messages may also stop when the 222 | interpreter is inactive - that is, when it is not running APL code or 223 | responding to external input such as HMON requests or keyboard events. 224 | 225 | ### StopFacts 226 | 227 | `StopFacts` cancels [`PollFacts`](#pollfacts). 228 | 229 | A UID may not be included in the message. 230 | 231 | Example: 232 | 233 | ```json 234 | ["StopFacts",{}] 235 | ``` 236 | 237 | A [``Facts``](#facts) message will be sent back, with an empty list of facts and 238 | Interval set to 0. 239 | 240 | ### BumpFacts 241 | 242 | A `BumpFacts` message will cause a polling [`Facts`](#facts) message to be sent, 243 | regardless of the time remaining until the next message is due. Messages will 244 | then continue at the normal frequency. 245 | 246 | A UID may not be included in the message. 247 | 248 | Example: 249 | 250 | ```json 251 | ["BumpFacts",{}] 252 | ``` 253 | 254 | ### Subscribe 255 | 256 | The `Subscribe` message tells the interpreter to send notification messages 257 | when certain, specifiable, events occur. The interpreter will confirm the 258 | settings with a [`Subscribed`](#subscribed) message in response, and a 259 | [`Notification`](#notification) message whenever the subscribed events occur. 260 | If the `Subscribe` message contains a UID then the [`Subscribed`](#subscribed) 261 | response and all subsequent [`Notification`](#notification) messages of all 262 | types will echo that UID. 263 | 264 | Each subscribable event has a numeric and alphanumeric (string) identifier, and 265 | either may be used for the request. Example: 266 | 267 | ```json 268 | ["Subscribe",{"UID":"XX","Events":[1,4]}] 269 | ``` 270 | 271 | No event notifications are enabled by default. 272 | 273 | The following events may be subscribed to: 274 | 275 | | Subscription ID | Subscription name | 276 | | --------------- | ------------------- | 277 | | 1 | WorkspaceCompaction | 278 | | 2 | WorkspaceResize | 279 | | 3 | UntrappedSignal | 280 | | 4 | TrappedSignal | 281 | 282 | When a subscribed event takes place, the [`Notification`](#notification) 283 | message will include details of that event, as documented there. 284 | 285 | Sending a `Subscribe` message resets the list of subscribed events to those 286 | specified - that is, it replaces any existing subscriptions. The list may be 287 | empty. 288 | 289 | **Note:** following a WSFULL exception the interpreter may be unable to send 290 | either a TrappedSignal or UntrappedSignal. 291 | [`GetLastKnownState`](#getlastknownstate) will reliably report the last time a 292 | WSFULL event occurred. 293 | 294 | ### GetLastKnownState 295 | 296 | Requests the last known state of the interpreter, and will be responded to with 297 | a [`LastKnownState`](#lastknownstate) message. 298 | 299 | Examples: 300 | 301 | ```json 302 | ["GetLastKnownState",{}] 303 | ``` 304 | 305 | ```json 306 | ["GetLastKnownState",{"UID":"123"}] 307 | ``` 308 | 309 | `GetLastKnownState` requests can be used when a monitored interpreter becomes 310 | otherwise unresponsive. In "normal" use, [`GetFacts`](#getfacts) should be used. 311 | 312 | The "Last Known State" is provided from a repository which must be continuously 313 | maintained by the interpreter during its normal operation on the off-chance 314 | that it might be asked for at any time. This introduces an overhead so is only 315 | done if the Event Gathering Level is set to 1. Maintaining this repository is 316 | independent of whether a Health Monitor is connected at the time or not. 317 | 318 | [`112⌶`](#112) controls the Event Gathering Level. 319 | 320 | In addition, `⎕PROFILE` must currently be started to provide full 321 | information, e.g.: 322 | 323 | `⎕PROFILE 'start' 'coverage'` 324 | 325 | ### ConnectRide 326 | 327 | Requests that the interpreter connects to RIDE listening at a given address and 328 | on a given port, or disconnects a conection. Once the action has taken place 329 | the request will be responded to with a [`RideConnection`](#rideconnection) 330 | message. 331 | 332 | Examples: 333 | 334 | ```json 335 | ["ConnectRide",{"Address":"localhost","Port":4502}] 336 | ``` 337 | 338 | If `Address` and `Port` are both present in the mesasge, and have string and 339 | numeric values respectively, a connection will be attempted, otherwise any 340 | existing connection will be disconnected. 341 | 342 | For a connection request, HMON will send `3502⌶'CONNECT:
:'` 343 | to the interpreter and, if that is successful, `3502⌶1`. For a disconnecton 344 | request, HMON will send `3502⌶0`. See 345 | [Manage RIDE Connections](https://help.dyalog.com/latest/#Language/I%20Beam%20Functions/Manage%20RIDE%20Connections.htm#Manage_RIDE_Connections) 346 | for details of how `3502⌶` behaves, the return values it produces etc. 347 | 348 | Note: the interpreter Access Level must be set to 3 in order to permit this 349 | request. [`112⌶`](#112) controls the Access Level. 350 | 351 | > [!NOTE] 352 | > "ConnectRide" is not supported by Dyalog 19.0. 353 | 354 | ## Response messages 355 | 356 | ### Facts 357 | 358 | Reports one or more "facts" about the application state, corresponding to a 359 | [`GetFacts`](#getfacts), [`PollFacts`](#pollfacts), [`StopFacts`](#stopfacts) 360 | or [`BumpFacts`](#bumpfacts) request. 361 | 362 | The facts are presented as an array of objects in the same order as in the 363 | request. Each will contain a "Value" object or a "Values" array of objects, 364 | the contents of which depend on the fact type. 365 | 366 | Example: 367 | 368 | ```json 369 | ["Facts",{"UID":"xx","Interval":5000,"Facts":[{"ID":6,"Name":"ThreadCount","Value":{"Total":1,"Suspended":0}}]}] 370 | ``` 371 | 372 | "Interval" is only present in the response if polling. 373 | 374 | #### "Host" fact 375 | 376 | The "Value" object contains: 377 | 378 | - "Machine" - an object containing facts about the host machine: 379 | - "Name" - the name of the machine. 380 | - "User" - the name of the user account. 381 | - "PID" - the interpreter process ID. 382 | - "Desc" - an application-specific description - see [`110⌶`](#110). 383 | - "AccessLevel" - the level of rights the Health Monitor is permitted. 384 | 385 | > [!NOTE] 386 | > "Desc" is always a string value when produced by Dyalog 19.0. It may be any 387 | value (including an object) when produced by Dyalog 20.0 onwards. 388 | 389 | - "Interpreter" - an object containing facts about the host interpreter: 390 | - "Version" - the interpreter version, in the form "A.B.C". 391 | - "BitWidth" - the interpreter edition word size, either 32 or 64 (bits). 392 | - "IsUnicode" - a Boolean value indicating whether the interpreter is a 393 | Unicode edition or not (i.e. is Classic). 394 | - "IsRuntime" - a Boolean value indicating whether the interpreter is a 395 | Runtime edition or not (i.e. is a development version). 396 | - "SessionUUID" - a String value containing a unique Session UUID in 397 | [RFC 9562](https://datatracker.ietf.org/doc/html/rfc9562) format (see also 398 | [`113⌶0`](#113)). 399 | 400 | > [!NOTE] 401 | > "SessionUUID" is not provided by Dyalog 19.0. 402 | 403 | - "CommsLayer" - an object containing facts about the interpreter comms layer 404 | servicing the Health Monitor: 405 | - "Version" - the Comms Layer (Conga) version. 406 | - "Address" - the interpreter's network IP address. 407 | - "Port4" - the interpreter's network port number. 408 | - "Port6" - an alternate port number. 409 | 410 | - "RIDE" - an object containing facts about the interpreter comms later 411 | servicing RIDE: 412 | - "Listening" - a Boolean value indicating whether the interpreter is 413 | listening for RIDE connections. There are no further entries in this object 414 | if the value is 0. 415 | - "HTTPServer" - a Boolean value indicating whether the interpreter is 416 | running as a RIDE HTTP server ("Zero footprint" RIDE). 417 | - "Version" - the Comms Layer (Conga) version. 418 | - "Address" - the interpreter's network IP address. 419 | - "Port4" - the interpreter's network port number. 420 | - "Port6" - an alternate port number. 421 | 422 | #### "AccountInformation" fact 423 | 424 | The "Value" object contains: 425 | 426 | - "UserIdentification", "ComputeTime", "ConnectTime", "KeyingTime" - elements 427 | from `⎕AI`. 428 | 429 | #### "Workspace" fact 430 | 431 | The "Value" object contains: 432 | 433 | - "WSID" - the workspace name. 434 | - "Available", "Used", "Compactions", "GarbageCollections", "GarbagePockets", 435 | "FreePockets", "UsedPockets", "Sediment", "Allocation", "AllocationHWM", 436 | "TrapReserveWanted", "TrapReserveActual" - statistics from `2000⌶`. 437 | 438 | #### "Threads" fact 439 | 440 | The "Values" array contains one or more objects (one per thread), each containing: 441 | 442 | - "Tid" - thread ID 443 | - "Stack" - SIstack, as an array of objects each containing: 444 | - "Restricted": a Boolean value indicating whether some information is 445 | restricted (missing) because the Access Level does not permit it. 446 | - "Description" - a line of SIstack information - only if "Restricted" is 0. 447 | - "Suspended" - a Boolean value indicating whether the thread is suspended. 448 | - "State" - a string indicating the current location of the thread. 449 | - "Flags" - e.g. Normal, Paused or Terminated. 450 | 451 | If "Suspended" is 1 the object will also contain: 452 | 453 | - "DMX" - an object containing elements of `⎕DMX` in that thread, either `null` 454 | or: 455 | - "Category", "DM", "EM", "EN", "ENX", "InternalLocation", "Vendor" 456 | "Message", "OSError" - only if "Restricted" is 0. 457 | - "Restricted": a Boolean value indicating whether some information is 458 | restricted (missing) because the Access Level does not permit it. 459 | - "Exception" - an object containing elements of `⎕EXCEPTION` in that thread, 460 | either `null` or: 461 | - "Source", "StackTrace", "Message" - only if "Restricted" is 0. 462 | - "Restricted": a Boolean value indicating whether some information is 463 | restricted (missing) because the Access Level does not permit it. 464 | 465 | #### "SuspendedThreads" fact 466 | 467 | The "Values" array contains one or more objects (one per suspended thread), each 468 | containing values as descrived for ["Threads"](#threads-fact) with the exception 469 | that the "Suspended" item is omitted as it would always have the value 1. 470 | 471 | #### "ThreadCount" fact 472 | 473 | The "Value" object contains: 474 | 475 | - "Total" - the total number of threads. 476 | - "Suspended" - the number of threads which are suspended. 477 | 478 | ### Subscribed 479 | 480 | The response to [`Subscribe`](#subscribe). The message lists all subscription 481 | events and whether they are enabled or disabled. 482 | 483 | Example (showing an incomplete list of subscription events): 484 | 485 | ```json 486 | ["Subscribed",{"UID":"XX","Events":[{"ID":1,"Name":"WorkspaceCompaction","Value":1},{"ID":2,"Name":"WorkspaceResize","Value":0}]} 487 | ``` 488 | ### Notification 489 | 490 | A `Notification` message indicates that a specified event, for which 491 | notifications have been subscribed, has taken or is taking place. The 492 | [`Subscribe`](#subscribe) message is used to select notified event types. The 493 | `Notification` message always reports a single event and contains the event ID 494 | and name, along with any specific detail pertaining to that event type. 495 | 496 | Example: 497 | 498 | ```json 499 | ["Notification",{"UID":"XX","Event":{"ID":2,"Name":"WorkspaceResize"},"Size":894213}] 500 | ``` 501 | 502 | #### "WorkspaceCompaction" event 503 | 504 | Occurs when a workspace compaction has occurred. The additional values provided are: 505 | 506 | - "Tid" - thread ID. 507 | - "Stack" - SIstack, as an array of objects each containing: 508 | - "Restricted": a Boolean value indicating whether some information is 509 | restricted (missing) because the Access Level does not permit it. 510 | - "Description" - a line of SIstack information - only if "Restricted" is 0. 511 | 512 | #### "WorkspaceResize" event 513 | 514 | Occurs when the workspace size (the amount of memory committed by the OS) has 515 | grown or shrunk. The additional values provided are: 516 | 517 | - "Size" - new size, in bytes. 518 | 519 | #### "UntrappedSignal" event 520 | 521 | Occurs when an APL exception has been signalled which has not been trapped. The additional values provided are: 522 | 523 | - "Tid" - thread ID. 524 | - "Stack" - SIstack, as an array of objects each containing: 525 | - "Restricted": a Boolean value indicating whether some information is 526 | restricted (missing) because the Access Level does not permit it. 527 | - "Description" - a line of SIstack information - only if "Restricted" is 0. 528 | - "DMX" - an object containing elements of `⎕DMX` in that thread, either `null` 529 | or: 530 | - "Category", "DM", "EM", "EN", "ENX", "InternalLocation", "Vendor" 531 | "Message", "OSError" - only if "Restricted" is 0. 532 | - "Restricted": a Boolean value indicating whether some information is 533 | restricted (missing) because the Access Level does not permit it. 534 | - "Exception" - an object containing elements of `⎕EXCEPTION` in that thread, 535 | either `null` or: 536 | - "Source", "StackTrace", "Message" - only if "Restricted" is 0. 537 | - "Restricted": a Boolean value indicating whether some information is 538 | restricted (missing) because the Access Level does not permit it. 539 | 540 | #### "TrappedSignal" event 541 | 542 | Occurs when an APL exception has been signalled which has not been trapped. The 543 | additional values provided are as described for 544 | [`UntrappedSignal`](#untrappedsignal-event) 545 | 546 | ### LastKnownState 547 | 548 | The response to [`GetLastKnownState`](#getlastknownstate), containing: 549 | 550 | - The UID, if provided in the request. 551 | - The interpreter\'s current UTC clock setting, so that times elapsed since the 552 | other timings can be computed. 553 | - The line currently being executed by the interpreter and the UTC time that 554 | this line started or resumed execution, if enabled with `112⌶` and `⎕PROFILE` 555 | is running (otherwise this information is omitted). 556 | - An activity code and the UTC time that this activity started, if enabled with 557 | [`112⌶`](#112) (otherwise omitted). 558 | - The time a trapped or untrapped WSFULL event last occurred, if enabled with 559 | [`112⌶`](#112) (otherwise omitted). 560 | 561 | UTC times are in ISO format with millisecond precision, e.g. 562 | 20231231T235959.999 for the very last millisecond of 2023. 563 | 564 | Activity codes are: 565 | 566 | | Code | Meaning | 567 | | ---- | --------------------------------------- | 568 | | 1 | Anything not specifically listed below | 569 | | 2 | Performing a workspace allocation | 570 | | 3 | Performing a workspace compaction | 571 | | 4 | Performing a workspace check | 572 | | 222 | Sleeping (an internal testing feature) | 573 | 574 | _This functionality is at an early stage of development. It is anticipated that 575 | this list will be significantly extended in future._ 576 | 577 | Examples: 578 | 579 | #### No UID provided, and [`112⌶0`](#112) set: 580 | 581 | ```json 582 | ["LastKnownState",{"TS":"20230111T144700.132Z"}] 583 | ``` 584 | 585 | #### UID provided, [`112⌶2 1`](#112) set and `⎕PROFILE` started: 586 | 587 | ```json 588 | ["LastKnownState",{"UID":"123","TS":"20230111T144700.132Z","Activity":{"Code":1,"TS":"20230111T144700.132Z"},"Location":{"Function":"#.f","Line":2,"TS":"20230111T144700.132Z"},"WS FULL":{"TS":"20230111T144620.723Z"}}] 589 | ``` 590 | 591 | Note: "Location" is updated by the interpreter whenever execution of a line 592 | begins or resumes. If program execution stops for any reason (e.g. exception 593 | or program termination) it will report the last executed line. "location" does 594 | not report anything about inactive threads - full thread/stack info is 595 | available with [`GetFacts`](#getfacts), so long as the interpreter is 596 | responsive. 597 | 598 | ### RideConnection 599 | 600 | The response to [`ConenectRide`](#connectride), containing: 601 | 602 | - The UID, if provided in the request. 603 | - "Restricted": a Boolean value indicating whether the connection request was 604 | disallowed because the Access Level did not permit it. 605 | - "Connect": a Boolean indicating whether the [`ConenectRide`](#connectride) 606 | message was interpreter as a Connect (1) or Disconnect (0) request - only if 607 | "Restricted" is 0). 608 | - "Status": the return code issued by `3502⌶` (0 indicates success) - only if 609 | "Restricted" is 0). 610 | 611 | Example: 612 | 613 | ```json 614 | ["RideConnection",{"UID":"myuid","Restricted":0,"Connect":1,"Status":0}] 615 | ``` 616 | 617 | > [!NOTE] 618 | > "RideConnection" is not provided by Dyalog 19.0. 619 | 620 | ### InvalidSyntax 621 | 622 | The response to a syntactically invalid JSON message, or a message which does 623 | not strictly define a two-element array with a string name in the first array 624 | element and an object in the second array element. Because it is a response to 625 | an "unintelligible" message, it will never contain a UID response. 626 | 627 | Example: 628 | 629 | ```json 630 | ["InvalidSyntax",{}] 631 | ``` 632 | 633 | ### DisallowedUID 634 | 635 | The response to a message which contains a UID when it should not - 636 | specifically, [`StopFacts`](#stopfacts) and [`BumpFacts`](#bumpfacts) message. 637 | 638 | Example: 639 | 640 | ```json 641 | ["DisallowedUID",{"UID":"xx","Name":"StopFacts"}] 642 | ``` 643 | 644 | ### UnknownCommand 645 | 646 | The response to a syntactically correct message which contains a message with 647 | an unrecognised name. The unrecognised name, and the UID if provided, ares 648 | included in the message. 649 | 650 | Example: 651 | 652 | ```json 653 | ["UnknownCommand",{"UID":"ABC","Name":"Hatstand"}] 654 | ``` 655 | 656 | ### MalformedCommand 657 | 658 | The response to a syntactically correct request message which does not exactly 659 | conform to specification. The name, and the UID if provided, are included in 660 | the message. 661 | 662 | Example: 663 | 664 | ```json 665 | ["MalformedCommand",{"Name":"GetLastKnownState"}] 666 | ``` 667 | 668 | ### UserMessage 669 | 670 | Sent by the interpreter under user/application control using [`111⌶`](#111). 671 | 672 | Example: 673 | 674 | ```json 675 | ["UserMessage",{"UID":"123","Message":"Hello"}] 676 | ``` 677 | 678 | # Interpreter I-beam functions 679 | 680 | ## 110 681 | 682 | `{R}←(110⌶) Y` 683 | 684 | Specifies the machine description which will appear in 685 | [`Facts`](#host-fact) messages sent to the Health Monitor. 686 | The default machine description is an empty string (`""`) until one is 687 | explicitly specified. 688 | 689 | Y is any array or namespace which can be serialised as JSON text. 690 | 691 | > [!NOTE] 692 | > Dyalog 19.0 only: Y is a vector or scalar only; the "description" in the 693 | [`Facts`](#host-fact) message is therefore always a string value. 694 | 695 | The shy result is the value 1. 696 | 697 | ## 111 698 | 699 | `{R}←{X} (111⌶) Y` 700 | 701 | Will cause the interpreter to send a [`UserMessage`](#usermessage) notification 702 | message to the client, if one is connected. 703 | 704 | Y is a character vector or scalar containing the free-form message text. 705 | 706 | X is an optional character vector or scalar containing the UID. 707 | 708 | The shy result is the value 1. 709 | 710 | ## 112 711 | 712 | `R←X (112⌶) Y` 713 | 714 | Starts and stops the Health Monitor, specifies the Interface Configuration and 715 | controls the Access and Event Gathering Levels. See the section 716 | [`Establishing connections and controlling access levels`](#establishing-connections-and-controlling-access-levels) 717 | for an explanation of what these are and their permitted values. 718 | 719 | Y is a 1 or 2-element numeric array consisting of: 720 | 721 | - Settings for Access Level, _and optionally_ 722 | - Event Gathering. 723 | 724 | X may be omitted, or scalar zero, or an empty character vector, or character 725 | vector specifying the Interface Configuration. 726 | 727 | ### Starting the HMON comms layer 728 | 729 | X should be a character vector containing either: 730 | 731 | - The Interface Configuration, _or_ 732 | - Nothing (i.e. empty), in which case the previous Interface Configuration (if 733 | any) is reused. 734 | 735 | Y should contain: 736 | 737 | - The Access Level values of 1, 2 or 3, _and optionally_ 738 | - The Event Gathering setting; defaults to 0 if not specified. 739 | 740 | ### Updating the Access Level and Event Gathering settings 741 | 742 | The function should be called monadically or with 0 in X. 743 | 744 | Y should contain: 745 | 746 | - The Access Level values of 1, 2 or 3, _and optionally_ 747 | - The Event Gathering setting, which defaults to 0 if not specified. 748 | 749 | The settings can be changed whether or not a client is currently attached and 750 | take effect immediately. 751 | 752 | ### Stopping the HMON comms layer 753 | 754 | The function should be called monadically or with 0 in the left argument. 755 | 756 | Y should contain: 757 | 758 | - The Access Level value 0. 759 | 760 | ### Errors and return values 761 | 762 | No action will be taken and 0 will be returned if: 763 | 764 | - A request is made to start the HMON comms layer when it is already started. 765 | - A request is made to update the Access Level and Event Gathering settings, 766 | and the HMON comms layer is not started. 767 | - A request is made to stop the HMON comms layer when it is already stopped. 768 | 769 | Otherwise, the comms layer is stopped or started as requested and then: 770 | 771 | - An error will be signalled if the operation fails. 772 | - The shy value 1 will be returned if the operation succeeds. 773 | 774 | ## 113 775 | 776 | `R←X (113⌶) Y` 777 | 778 | Y must be 779 | 780 | The result is a character vector containing the Session UUID, as reported to an 781 | HMON client as a [Host fact](#host-fact). 782 | 783 | > [!NOTE] 784 | > `113⌶` is not available in Dyalog 19.0. 785 | --------------------------------------------------------------------------------