How to handle numbers of prowin32.exe - progress-4gl

I've been doing an OS-COMMAND that execute the -p "procedure.p" with -param that are inside a loop.
My question is how can I handle the number of prowin32.exe that will not spawn too much so it won't consume all my processor and also not less to make it fast?
Note: Version 8.3c
Sample Code
DEFINE VARIABLE Filter AS CHARACTER NO-UNDO.
For Each Item No-lock:
Assign Filter = "Item.Item = " + Item.Item NO-ERROR.
OS-COMMAND NO-WAIT
VALUE("C:\dlcs\83c\bin\prowin32 " +
"-p C:\nanox\syte_server5\ATMS-CONFIGURED-ICONS\LIVE\Nanox_utility\procedure.p " +
"-pf C:\nanox\syte_server5\ATMS-CONFIGURED-ICONS\LIVE\Nanox_utility\Conf\pilot.pf " +
"-param " + Filter).
END.
QUIT.

I would do it a bit differently. Decide ahead of time how many threads and then divvy up the work like so:
/* worker.p
*
* i.e.:
* mbpro dbName -p worker.p -param "0,5"
* mbpro dbName -p worker.p -param "1,5"
* mbpro dbName -p worker.p -param "2,5"
* mbpro dbName -p worker.p -param "3,5"
* mbpro dbName -p worker.p -param "4,5"
*
* in this sample the threadNum starts at 0 -- so end it at numThreads - 1
*
*/
define variable threadNum as integer no-undo.
define variable numThreads as integer no-undo.
assign
threadNum = integer( entry( 1, session:parameter ))
numThreads = integer( entry( 2, session:parameter ))
.
for each item no-lock where (( item.item modulo numThreads ) = threadnum ):
/* whatever */
end.
return.
For starters I suggest that numThreads be roughly the number of cores available in the server.
Also, headless batch processes shouldn't be using prowin32.exe. They should use _progres.exe.
8.3 is unspeakably ancient, obsolete and unsupported. Is this system running on a single core Pentium with Windows 3.11? That's relevant because the 8.3 "workgroup edition" will not behave very well if this is a multi-core system. If it is "enterprise" it won't be so bad. But even so you'd be very well advised to upgrade to a current release (11.3 as of this writing) if performance is at all important to you.

I do not work with version8.3, but it must pass (possibly replace the temp-table by a work-table).
Here I what I certainly do but it must improve:
DEFINE TEMP-TABLE ttpid NO-UNDO
FIELD pid AS CHARACTER
FIELD SIZE AS CHARACTER
INDEX i1 IS UNIQUE PRIMARY pid.
DEFINE VARIABLE wline AS CHARACTER NO-UNDO.
DEFINE VARIABLE wfile AS CHARACTER NO-UNDO.
wfile = "c:/tmp/list_tasklis.txt".
OS-COMMAND SILENT VALUE("tasklist > " + wfile).
INPUT FROM VALUE(wfile).
REPEAT:
IMPORT UNFORMATTED wline.
IF wline BEGINS "prowin32.exe"
THEN DO:
DO WHILE wline MATCHES "* *":
wline = REPLACE(wline, " ", " ").
END.
FIND FIRST ttpid
WHERE ttpid.pid = ENTRY(2, wline, " ")
NO-LOCK NO-ERROR.
IF NOT AVAILABLE ttpid
THEN DO:
CREATE ttpid.
ASSIGN ttpid.pid = ENTRY(2, wline, " ")
ttpid.SIZE = ENTRY(5, wline, " ").
END.
END.
END.
INPUT CLOSE.
FOR EACH ttpid
NO-LOCK:
MESSAGE "PID =" ttpid.pid SKIP
"SIZE =" ttpid.SIZE
VIEW-AS ALERT-BOX INFO BUTTONS OK.
END.
OS-DELETE VALUE (wfile) NO-ERROR.

Related

Save job output from SDSF into a PDS and using ISPF functions in REXX

We periodically runs jobs and we need to save the output into a PDS and then parse the output to extract parts of it to save into another member. It needs to be done by issuing a REXX command using the percent sign and the REXX member name as an SDSF command line. I've attempted to code a REXX to do this, but it is getting an error when trying to invoke an ISPF service, saying the ISPF environment has not been established. But, this is SDSF running under ISPF.
My code has this in it (copied from several sources and modified):
parse arg PSDSFPARMS "(" PUSERPARMS
parse var PSDSFPARMS PCURRPNL PPRIMPNL PROWTOKEN PPRIMCMD .
PRIMCMD=x2c(PPRIMCMD)
RC = isfquery()
if RC <> 0 then
do
Say "** SDSF environment does not exist, exec ending."
exit 20
end
RC = isfcalls("ON")
Address SDSF "ISFGET" PPRIMPNL "TOKEN('"PROWTOKEN"')" ,
" (" VERBOSE ")"
LRC = RC
if LRC > 0 then
call msgrtn "ISFGET"
if LRC <> 0 then
Exit 20
JOBNAME = value(JNAME.1)
JOBNBR = value(JOBID.1)
SMPDSN = "SMPE.*.OUTPUT.LISTINGS"
LISTC. = ''
SMPODSNS. = ''
SMPODSNS.0 = 0
$ = outtrap('LISTC.')
MSGVAL = msg('ON')
address TSO "LISTC LVL('"SMPDSN"') ALL"
MSGVAL = msg(MSGVAL)
$ = outtrap('OFF')
do LISTCi = 1 to LISTC.0
if word(LISTC.LISTCi,1) = 'NONVSAM' then
do
parse var LISTC.LISTCi . . DSN
SMPODSNS.0 = SMPODSNS.0 + 1
i = SMPODSNS.0
SMPODSNS.i = DSN
end
IX = pos('ENTRY',LISTC.LISTCi)
if IX <> 0 then
do
IX = pos('NOT FOUND',LISTC.LISTCi,IX + 8)
if IX <> 0 then
do
address ISPEXEC "SETMSG MSG(IPLL403E)"
EXITRC = 16
leave
end
end
end
LISTC. = ''
if EXITRC = 16 then
exit 0
address ISPEXEC "TBCREATE SMPDSNS NOWRITE" ,
"NAMES(TSEL TSMPDSN)"
I execute this code by typing %SMPSAVE next to the spool output line on the "H" SDSF panel and it runs fine until it gets to this point in the REXX:
114 *-* address ISPEXEC "TBCREATE SMPDSNS NOWRITE" ,
"NAMES(TSEL TSMPDSN)"
>>> "TBCREATE SMPDSNS NOWRITE NAMES(TSEL TSMPDSN)"
ISPS118S SERVICE NOT INVOKED. A VALID ISPF ENVIRONMENT DOES NOT EXIST.
+++ RC(20) +++
Does anyone know why it says I don't have a valid ISPF environment and how I can get around this?
I've done quite a bit in the past with REXX, including writing REXX code to handle line commands, but this is the first time I've tried to use ISPEXEC commands within this code.
Thank you,
Alan

Updating my current script which modifies multiple lines into a single one

My current script copies text like this with a shortcut:
:WiltedFlower: aetheryxflower ─ 4
:Alcohol: alcohol ─ 3,709
:Ant: ant ─ 11,924
:Apple: apple ─ 15
:ArmpitHair: armpithair ─ 2
and pastes it modified into a single line
Pls trade 4 aetheryxflower 3 alcohol 11 ant 15 apple 2 armpithair <#id>
As you can see there are already two little problems, the first one is that it copies only the number/s before a comma if one existed instead of ignoring it. The second is that I always need to also copy before hitting the hotkey and start re/start the script, I've thought of modifying the script so that it uses the already selected text instead of the copied one so that I can bind it with a single hotkey.
That is my current script, it would be cool if anyone can also tell me what they used and why exactly, so that I also get better with ahk
!q::
list =
While pos := RegExMatch(Clipboard, "(\w*) ─ (\d*)", m, pos ? pos + StrLen(m) : 1)
list .= m2 " " m1 " "
Clipboard := "", Clipboard := "Pls trade " list " <#951737931159187457>"
ClipWait, 0
If ErrorLevel
MsgBox, 48, Error, An error occurred while waiting for the clipboard.
return
If the pattern of your copied text dont change, you can use something like this:
#Persistent
OnClipboardChange:
list =
a := StrSplit(StrReplace(Clipboard, "`r"), "`n")
Loop,% a.Count() {
b := StrSplit( a[A_Index], ": " )
c := StrSplit( b[2], " - " )
list .= Trim( c[2] ) " " Trim( c[1] ) " "
}
Clipboard := "Pls trade " list " <#951737931159187457>"]
ToolTip % Clipboard ; just for debug
return
With your example text, the output will be:
Pls trade aetheryxflower ─ 4 alcohol ─ 3,709 ant ─ 11,924 apple ─ 15 armpithair ─ 2 <#951737931159187457>
And this will run EVERY TIME your clipboard changes, to avoid this, you can add at the top of the script #IfWinActive, WinTitle or #IfWinExist, WinTitle depending of your need.
The answer given would solve the problem, assuming that it never changes pattern as Diesson mentions.
I did the explanation of the code you provided with comments in the code below:
!q::
list = ; initalize a blank variable
; regexMatch(Haystack, regexNeedle, OutputVar, startPos)
; just for frame of reference in explanation of regexMatch
While ; loop while 'pos' <> 0
pos := RegExMatch(Clipboard ; Haystack is the string to be searched,
in this case the Clipboard
, "(\w*) ─ (\d*)" ; regex needle in this case "capture word characters
(a-z OR A-Z OR 0-9 OR _) any number of times, space dash space
then capture any number of digits (0-9)"
, m ; output var array base name, ie first capture will be in m1
second m2 and so on.
, pos ? pos + StrLen(m) : 1) ; starting position for search
"? :"used in this way is called a ternary operator, what is saying
is "if pos<>0 then length of string+pos is start position, otherwise
start at 1". Based on the docs, this shouldn't actually work well
since 'm' in this case should be left blank
list .= m2 " " m1 " " ; append the results to the 'list' variable
followed with a space
Clipboard := "" ; clear the clipboard.
Clipboard := "Pls trade " list " <#951737931159187457>"
ClipWait, 0 ; wait zero seconds for the clipboard to change
If ErrorLevel ; if waiting zero seconds for the clipboard to change
doesn't work, give error msg to user.
MsgBox, 48, Error, An error occurred while waiting for the clipboard.
return
Frankly this code is what I would call quick and dirty, and seems unlikely to work well all the time.

ultisnips: How to "freeze" vim.current.window.cursor value for snippet

I had a snippet that used to work well (neovim 0.2.0)
snippet #= "comment ===" b
# `!p snip.rv = '=' * (78 - vim.current.window.cursor[1])`
# ${1:comments}
# `!p snip.rv = '=' * (78 - vim.current.window.cursor[1])`
endsnippet
This snippet is basically writing python comments block when triggered,
where the length of "=" depends on the position of the cursor.
For a few days now (I don't know which update makes it failing), the length of "=" is decreasing as long as I type my comment.
It looks like vim.current.window.cursor[1] is constantly re-evaluated.
Any idea how to "freeze" the value?
I finally found:
snippet #= "comment ===" b
`!p
if not snip.c:
width = int(vim.eval("78 - virtcol('.')"))
snip.rv = '# ' + '=' * width`
# ${1:comments}
`!p snip.rv = '# ' + '=' * width`
endsnippet

Panel doesn't execute )PNTS Section

I'm coding a ISPF Panel with "Point and shoot" elements. The elements say "yes" and "no" and the default cursor have to point to "yes".
1st Case:
Declaration of the fields: + TYPE(INPUT) PAS(ON)
When I use this declaration, the panel closes by pressing [enter] and generating rc = 0. However, the )PNTS section doesn't run.
2nd CASE:
Declaration of the fields: + TYPE (PS)
The )PNTS section runs by pressing [enter]. However, I cannot set the .cursor to the field "yes".
I tryed different ways with different field names (e.g. ZPS00001). I tryed to simulate Point and Shoot with Rexx, but nothing worked really fine.
Pressing enter will cause the point and shoot fields to be processed. However the cursor must be on one of the fields for the )PNTS section to set the value associated with a field. It would sound like panel may have not been coded correctly. PAS should be used for input or output fields and PS should be used for text fields. For instance if you have the following panel:
)ATTR
$ TYPE(PS)
! TYPE(OUTPUT) PAS(ON)
)BODY
+ --------------------- +
+ ===>_ZCMD +
+
$Field1 : _FLD +
$Field2 : _ABC +
$Field3 : !IN1 +
$Field4 : !IN2 +
)INIT
&INV1 = 111
&INV2 = 222
&INV3 = 333
)REINIT
REFRESH(*)
)PROC
)PNTS
FIELD(IN1) VAR(INV1) VAL(ON)
FIELD(IN2) VAR(INV2) VAL(OFF)
FIELD(ZPS00001) VAR(INV3) VAL(1)
FIELD(ZPS00002) VAR(INV3) VAL(2)
FIELD(ZPS00003) VAR(INV3) VAL(3)
FIELD(ZPS00004) VAR(INV3) VAL(4)
)END
With the following REXX exec:
/* REXX */
RCC = 0
INV1 = 0
INV2 = 1
DO WHILE RCC = 0
ADDRESS ISPEXEC 'DISPLAY PANEL(PAS)'
RCC = RC
SAY INV1 '-' INV2 '-' INV3
END
You can test the values of inv1, inv2 and inv3 based on where you put the cursor when you hit enter. You will get 1, 2, 3 or 4 if the cursor in on field1, field2, field3 or field4. If it is on IN1 or IN2 then you get ON or OFF. It all depends on where the cursor is positioned when ENTER is hit. Based on the example you can see point and shoot is not limited to Menus. Hope the example helps.
Marv Knight

How do I do HTTP GET and POST in Progress/OpenEdge ABL?

The Progress docs spill plenty of ink on SOAP, but I'm having trouble finding the example for a simple HTTP GET/POST with Progress ABL.
How do I GET and POST strings to/from a URL?
Can the URL be https://?
Can Progress provide HTTP Basic or HTTP Digest authentication?
For future onlookers at this question:
Openedge now (since 11.5.1 I believe) has built in support for calling REST based webservices. These are enclosed in a provided .pl archive that is not in your PROPATH by default so that needs to be handled first (or the archive can be moved to a "better location").
The propath can be set in a number of ways, init files, registry, programatically etc. This is how it can be done in ABL (if done this way it must be repeated for each new session).
PROPATH = PROPATH + ",c:\pathtoprogress\OpenEdge\gui\OpenEdge.Net.pl".
There's also a version in "tty" directory, as well as an archive containing source code in the "src" directory.
Here's a very basic example:
USING OpenEdge.Net.HTTP.IHttpRequest.
USING OpenEdge.Net.HTTP.IHttpResponse.
USING OpenEdge.Net.HTTP.ClientBuilder.
USING OpenEdge.Net.HTTP.RequestBuilder.
DEFINE VARIABLE oRequest AS IHttpRequest NO-UNDO.
DEFINE VARIABLE oResponse AS IHttpResponse NO-UNDO.
oRequest = RequestBuilder:Get('http://stackoverflow.com/'):Request.
oResponse = ClientBuilder:Build():Client:Execute(oRequest).
MESSAGE
oResponse:StatusCode SKIP
oResponse:StatusReason SKIP
VIEW-AS ALERT-BOX.
Documentation for 11.6 can be found here.
Openedge has built in statements for handling SOAP services, but no simple statement for a GET/POST. What it does have, however, is a mechanism to read / write to specific sockets. So you could use this to build a HTTP post routine, or for that matter a routine to handle any other socket based protocol.
There is a routine - http.p - which will do a GET for you. Will let you see how the socket programming works if nothing else. You should be able to modify it quite easily to do a simple POST, but using SSL or getting into authentication might take quite a bit of work. You might be easier just dropping out to CURL in this case.
http.p used to be available from freeframework.org, but I've just checked and that domain has expired, so I've posted the code below.
/*-----------------------------------------------------------------------*
File........: http.p
Version.....: 1.1
Description : Makes a "Get" request from an HTTP server
Input Param : pHost = host name (without the "http://")
pPort = port number (usually 80)
pURL = begin with the first slash after the domain name.
Output Param: pResult = 0 or 1 (character)
pResponse = http headers returned
pContent = the document returned.
Author......: S.E. Southwell, Mario Paranhos - United Systems, Inc. (770) 449-9696
Created.....: 12/13/2000
Notes.......: Will not work with HTTPS.
Usage:
define var v-result as char no-undo.
define var v-response as char no-undo.
define var v-content as char no-undo.
{&out} "Hello" skip.
put stream webstream control null(0).
run proc/http.p("www.whosplayin.com","80","/",output v-result,output v-response,output v-content).
{&out} v-result "<hr>" skip
html-encode(v-response) "<hr>" skip
html-encode(v-content) "<hr>" skip
.
Last Modified: 10/20/01 - SES - Fixed to work in batch mode, or within a UDF.
--------------------------------------------------------------------------*/
&SCOPED-DEFINE HTTP-NEWLINE CHR(13) + CHR(10)
&SCOPED-DEFINE RESPONSE-TIMEOUT 45
DEFINE INPUT PARAMETER pHOST AS CHAR NO-UNDO.
DEFINE INPUT PARAMETER pPORT AS CHAR NO-UNDO.
DEFINE INPUT PARAMETER pURL AS CHAR NO-UNDO.
DEFINE OUTPUT PARAMETER pRESULT AS CHAR NO-UNDO.
DEFINE OUTPUT PARAMETER pRESPONSE AS CHAR NO-UNDO.
DEFINE OUTPUT PARAMETER pContent AS CHAR NO-UNDO.
DEFINE VARIABLE requestString AS CHAR NO-UNDO.
DEFINE VARIABLE vSocket AS HANDLE NO-UNDO.
DEFINE VARIABLE vBuffer AS MEMPTR NO-UNDO.
DEFINE VARIABLE vloop AS LOGICAL NO-UNDO.
DEFINE VARIABLE vPackets AS INTEGER NO-UNDO.
DEFINE VARIABLE wStatus AS LOGICAL NO-UNDO.
ASSIGN requestString = "GET " + pURL + " HTTP/1.0" + {&HTTP-NEWLINE} +
"Accept: */*" + {&HTTP-NEWLINE} +
"Host: " + phost + {&HTTP-NEWLINE} +
/*"Connection: Keep-Alive" + {&HTTP-NEWLINE} + */
{&HTTP-NEWLINE}.
/*OPEN THE SOCKET*/
CREATE SOCKET vSocket.
vSocket:SET-READ-RESPONSE-PROCEDURE ("readHandler",THIS-PROCEDURE).
ASSIGN wstatus = vSocket:CONNECT("-H " + phost + " -S " + pport) NO-ERROR.
/*Now make sure the socket is open*/
IF wstatus = NO THEN DO:
pResult = "0:No Socket".
DELETE OBJECT vSocket.
RETURN.
END.
/*Got socket - Now make HTTP request*/
SET-SIZE(vBuffer) = LENGTH(requestString) + 1.
PUT-STRING(vBuffer,1) = requestString.
vSocket:WRITE(vBuffer, 1, LENGTH(requestString)).
SET-SIZE(vBuffer) = 0.
/*Wait for a response*/
ASSIGN vloop = TRUE. /*Turns off automatically when request is done*/
DEFINE VAR vstarttime AS INTEGER.
ASSIGN vstarttime = etime.
WAITLOOP: DO WHILE vloop:
PROCESS EVENTS.
PAUSE 1.
/* Build in timer in case sending is never set to NO
this will terminate the program after 60 seconds
start-Etime will be reset by WriteData each time there
is activity on the socket to allow for long transmissions */
IF vstarttime + ({&RESPONSE-TIMEOUT} * 1000) < ETIME
THEN DO:
MESSAGE "timed out at " + string(etime - vstarttime) + " msec".
vSocket:DISCONNECT().
ASSIGN pResult = "0:Failure".
RETURN.
END. /*No Response, or timed out*/
END.
/*At this point, pResponse should be populated with the result (up to 32K)*/
vSocket:DISCONNECT().
DELETE OBJECT vSocket.
/*All Done!*/
ASSIGN pResult = "1:Success".
ASSIGN
pContent = SUBSTRING(pResponse,INDEX(pResponse,{&HTTP-NEWLINE} + {&HTTP-NEWLINE}),-1)
.
ASSIGN
pResponse = SUBSTRING(pResponse,1,INDEX(pResponse,{&HTTP-NEWLINE} + {&HTTP-NEWLINE}))
.
RETURN.
/*Handle the response from the webserver*/
PROCEDURE readHandler:
DEFINE VARIABLE bytesAvail AS INTEGER NO-UNDO.
DEFINE VARIABLE b AS MEMPTR NO-UNDO.
DEFINE VARIABLE lastBytes AS INTEGER NO-UNDO.
IF vSocket:connected() THEN ASSIGN bytesAvail = vSocket:GET-BYTES-AVAILABLE().
IF bytesAvail = 0 THEN DO: /*All Done*/
ASSIGN vloop = FALSE.
RETURN.
END.
/*OK, there's something on the wire... Read it in*/
SET-SIZE(b) = bytesAvail + 1.
vSocket:READ(b, 1, bytesAvail, 1).
ASSIGN pResponse = pResponse + GET-STRING(b,1).
SET-SIZE(b) = 0.
END PROCEDURE. /*readHandler*/
Progress Kbase ID: 20011: "Sample Code To Access a Web Site via HTTP with 4GL Sockets" is also a nice, generic example.
I recommend using the code example of Gordon Roberertson above because there is the "WAIT-FOR" of the Progress KB article replaced with a loop. So the prcoedure terminates after timeout period if anything goes wrong.
Please note that changing anything in requestString can cause timeouts. But adding a User-Agent is possible if you need for logging on your webserver:
ASSIGN requestString = "GET " + Path + " HTTP/1.0" + {&HTTP-NEWLINE}
+ "Accept: */*" + {&HTTP-NEWLINE}
+ "User-Agent: " + "User Agent String" + {&HTTP-NEWLINE}
+ "Host: " + Host + {&HTTP-NEWLINE}
+ {&HTTP-NEWLINE}.
Thanks to Gordon for his code example.