mtx-watch.lua /size: 16 Kb    last modification: 2020-07-01 14:35
1
if
not
modules
then
modules
=
{
}
end
modules
[
'
mtx-watch
'
]
=
{
2
version
=
1
.
001
,
3
comment
=
"
companion to mtxrun.lua
"
,
4
author
=
"
Hans Hagen, PRAGMA-ADE, Hasselt NL
"
,
5
copyright
=
"
PRAGMA ADE / ConTeXt Development Team
"
,
6
license
=
"
see context related readme files
"
7
}
8 9
local
helpinfo
=
[[
10<?xml version="1.0"?> 11<application> 12 <metadata> 13 <entry name="name">mtx-watch</entry> 14 <entry name="detail">ConTeXt Request Watchdog</entry> 15 <entry name="version">1.00</entry> 16 </metadata> 17 <flags> 18 <category name="basic"> 19 <subcategory> 20 <flag name="logpath"><short>optional path for log files</short></flag> 21 <flag name="watch"><short>watch given path [<ref name="delay]"/></short></flag> 22 <flag name="pipe"><short>use pipe instead of execute</short></flag> 23 <flag name="delay"><short>delay between sweeps</short></flag> 24 <flag name="automachine"><short>replace /machine/ in path /servername/</short></flag> 25 <flag name="collect"><short>condense log files</short></flag> 26 <flag name="cleanup" value="delay"><short>remove files in given path [<ref name="force]"/></short></flag> 27 <flag name="showlog"><short>show log data</short></flag> 28 </subcategory> 29 </category> 30 </flags> 31</application> 32
]]
33 34
local
application
=
logs
.
application
{
35
name
=
"
mtx-watch
"
,
36
banner
=
"
ConTeXt Request Watchdog 1.00
"
,
37
helpinfo
=
helpinfo
,
38
}
39 40
local
report
=
application
.
report
41 42
scripts
=
scripts
or
{
}
43
scripts
.
watch
=
scripts
.
watch
or
{
}
44 45
local
format
,
concat
,
difftime
,
time
=
string
.
format
,
table
.
concat
,
os
.
difftime
,
os
.
time
46
local
next
,
type
=
next
,
type
47
local
basename
,
dirname
,
joinname
=
file
.
basename
,
file
.
dirname
,
file
.
join
48
local
lfsdir
,
lfsattributes
=
lfs
.
dir
,
lfs
.
attributes
49 50
-- the machine/instance matches the server app we use
51 52
local
machine
=
socket
.
dns
.
gethostname
(
)
or
"
unknown-machine
"
53
local
instance
=
string
.
match
(
machine
,
"
(%d+)$
"
)
or
"
0
"
54 55
function
scripts
.
watch
.
save_exa_modes
(
joblog
,
ctmname
)
56
local
values
=
joblog
and
joblog
.
values
57
if
values
then
58
local
t
=
{
}
59
t
[
#
t
+
1
]
=
"
<?xml version='1.0' standalone='yes'?>\n
"
60
t
[
#
t
+
1
]
=
"
<exa:variables xmlns:exa='htpp://www.pragma-ade.com/schemas/exa-variables.rng'>
"
61
for
k
,
v
in
next
,
joblog
.
values
do
62
t
[
#
t
+
1
]
=
format
(
"
\t<exa:variable label='%s'>%s</exa:variable>
"
,
k
,
tostring
(
v
)
)
63
end
64
t
[
#
t
+
1
]
=
"
</exa:variables>
"
65
io
.
savedata
(
ctmname
,
concat
(
t
,
"
\n
"
)
)
66
else
67
os
.
remove
(
ctmname
)
68
end
69
end
70 71
local
function
toset
(
t
)
72
if
type
(
t
)
=
=
"
table
"
then
73
return
concat
(
t
,
"
,
"
)
74
else
75
return
t
76
end
77
end
78 79
local
function
noset
(
t
)
80
if
type
(
t
)
=
=
"
table
"
then
81
return
t
[
1
]
82
else
83
return
t
84
end
85
end
86 87
-- todo: split order (o-name.luj) and combine with atime to determine sort order.
88 89
local
function
glob
(
files
,
path
)
-- some day: sort by name (order prefix) and atime
90
for
name
in
lfsdir
(
path
)
do
91
if
name
:
find
(
"
^%.
"
)
then
92
-- skip . and ..
93
else
94
name
=
path
.
.
"
/
"
.
.
name
95
local
a
=
lfsattributes
(
name
)
96
if
not
a
then
97
-- weird
98
elseif
a
.
mode
=
=
"
directory
"
then
99
if
name
:
find
(
"
graphics$
"
)
or
name
:
find
(
"
figures$
"
)
or
name
:
find
(
"
resources$
"
)
then
100
-- skip these too
101
else
102
glob
(
files
,
name
)
103
end
104
elseif
name
:
find
(
"
.%luj$
"
)
then
105
local
bname
=
basename
(
name
)
106
local
dname
=
dirname
(
name
)
107
local
order
=
tonumber
(
bname
:
match
(
"
^(%d+)
"
)
)
or
0
108
files
[
#
files
+
1
]
=
{
dname
,
bname
,
order
}
109
end
110
end
111
end
112
end
113 114
local
clock
=
os
.
gettimeofday
or
(
socket
and
socket
.
gettime
)
or
os
.
time
-- we cannot trust os.clock on linux
115 116
-- local function filenamesort(a,b)
117
-- local fa, da = a[1], a[2]
118
-- local fb, db = b[1], b[2]
119
-- if da == db then
120
-- return fa < fb
121
-- else
122
-- return da < db
123
-- end
124
-- end
125 126
local
function
filenamesort
(
a
,
b
)
127
local
fa
,
oa
=
a
[
2
]
,
a
[
3
]
128
local
fb
,
ob
=
b
[
2
]
,
b
[
3
]
129
if
fa
=
=
fb
then
130
if
oa
=
=
ob
then
131
return
a
[
1
]
<
b
[
1
]
-- order file dir
132
else
133
return
oa
<
ob
-- order file
134
end
135
else
136
if
oa
=
=
ob
then
137
return
fa
<
fb
-- order file
138
else
139
return
oa
<
ob
-- order file
140
end
141
end
142
end
143 144
function
scripts
.
watch
.
watch
(
)
145
local
delay
=
tonumber
(
environment
.
argument
(
"
delay
"
)
or
5
)
or
5
146
if
delay
=
=
0
then
147
delay
=
.
25
148
end
149
local
logpath
=
environment
.
argument
(
"
logpath
"
)
or
"
"
150
local
pipe
=
environment
.
argument
(
"
pipe
"
)
or
false
151
local
watcher
=
"
mtxwatch.run
"
152
local
paths
=
environment
.
files
153
if
#
paths
>
0
then
154
if
environment
.
argument
(
"
automachine
"
)
then
155
logpath
=
string
.
gsub
(
logpath
,
"
/machine/
"
,
"
/
"
.
.
machine
.
.
"
/
"
)
156
for
i
=
1
,
#
paths
do
157
paths
[
i
]
=
string
.
gsub
(
paths
[
i
]
,
"
/machine/
"
,
"
/
"
.
.
machine
.
.
"
/
"
)
158
end
159
end
160
for
i
=
1
,
#
paths
do
161
report
(
"
watching path %s
"
,
paths
[
i
]
)
162
end
163
local
function
process
(
)
164
local
done
=
false
165
for
i
=
1
,
#
paths
do
166
local
path
=
paths
[
i
]
167
lfs
.
chdir
(
path
)
168
local
files
=
{
}
169
glob
(
files
,
path
)
170
glob
(
files
,
"
.
"
)
171
table
.
sort
(
files
,
filenamesort
)
172
-- for name, time in next, files do
173
for
i
=
1
,
#
files
do
174
local
f
=
files
[
i
]
175
local
dirname
=
f
[
1
]
176
local
basename
=
f
[
2
]
-- we can use that later on
177
local
name
=
joinname
(
dirname
,
basename
)
178
--~ local ok, joblog = xpcall(function() return dofile(name) end, function() end )
179
local
ok
,
joblog
=
pcall
(
dofile
,
name
)
180
report
(
"
checking file %s/%s: %s
"
,
dirname
,
basename
,
ok
and
"
okay
"
or
"
skipped
"
)
181
if
ok
and
joblog
then
182
if
joblog
.
status
=
=
"
processing
"
then
183
report
(
"
aborted job, %s added to queue
"
,
name
)
184
joblog
.
status
=
"
queued
"
185
io
.
savedata
(
name
,
table
.
serialize
(
joblog
,
true
)
)
186
elseif
joblog
.
status
=
=
"
queued
"
then
187
local
command
=
joblog
.
command
188
if
command
then
189
local
replacements
=
{
190
inputpath
=
toset
(
(
joblog
.
paths
and
joblog
.
paths
.
input
)
or
"
.
"
)
,
191
outputpath
=
noset
(
(
joblog
.
paths
and
joblog
.
paths
.
output
)
or
"
.
"
)
,
192
filename
=
joblog
.
filename
or
"
"
,
193
}
194
-- todo: revision path etc
195
command
=
command
:
gsub
(
"
%%(.-)%%
"
,
replacements
)
196
if
command
~
=
"
"
then
197
joblog
.
status
=
"
processing
"
198
joblog
.
runtime
=
clock
(
)
199
io
.
savedata
(
name
,
table
.
serialize
(
joblog
,
true
)
)
200
report
(
"
running: %s
"
,
command
)
201
local
newpath
=
file
.
dirname
(
name
)
202
io
.
flush
(
)
203
local
result
=
"
"
204
local
ctmname
=
file
.
basename
(
replacements
.
filename
)
205
if
ctmname
=
=
"
"
then
ctmname
=
name
end
-- use self as fallback
206
ctmname
=
file
.
replacesuffix
(
ctmname
,
"
ctm
"
)
207
if
newpath
~
=
"
"
and
newpath
~
=
"
.
"
then
208
local
oldpath
=
lfs
.
currentdir
(
)
209
lfs
.
chdir
(
newpath
)
210
scripts
.
watch
.
save_exa_modes
(
joblog
,
ctmname
)
211
if
pipe
then
result
=
os
.
resultof
(
command
)
else
result
=
os
.
execute
(
command
)
end
212
lfs
.
chdir
(
oldpath
)
213
else
214
scripts
.
watch
.
save_exa_modes
(
joblog
,
ctmname
)
215
if
pipe
then
result
=
os
.
resultof
(
command
)
else
result
=
os
.
execute
(
command
)
end
216
end
217
report
(
"
return value: %s
"
,
result
)
218
done
=
true
219
local
path
,
base
=
replacements
.
outputpath
,
file
.
basename
(
replacements
.
filename
)
220
joblog
.
runtime
=
clock
(
)
-
joblog
.
runtime
221
if
base
~
=
"
"
then
222
joblog
.
result
=
file
.
replacesuffix
(
file
.
join
(
path
,
base
)
,
"
pdf
"
)
223
joblog
.
size
=
lfs
.
attributes
(
joblog
.
result
,
"
size
"
)
224
end
225
joblog
.
status
=
"
finished
"
226
else
227
joblog
.
status
=
"
invalid command
"
228
end
229
else
230
joblog
.
status
=
"
no command
"
231
end
232
-- pcall, when error sleep + again
233
-- todo: just one log file and append
234
io
.
savedata
(
name
,
table
.
serialize
(
joblog
,
true
)
)
235
if
logpath
and
logpath
~
=
"
"
then
236
local
name
=
file
.
join
(
logpath
,
os
.
uuid
(
)
.
.
"
.lua
"
)
237
io
.
savedata
(
name
,
table
.
serialize
(
joblog
,
true
)
)
238
report
(
"
saving joblog in %s
"
,
name
)
239
end
240
end
241
end
242
end
243
end
244
end
245
local
n
,
start
=
0
,
time
(
)
246
local
wtime
=
0
247
local
function
wait
(
)
248
io
.
flush
(
)
249
if
not
done
then
250
n
=
n
+
1
251
if
n
>
=
10
then
252
report
(
"
run time: %i seconds, memory usage: %0.3g MB
"
,
difftime
(
time
(
)
,
start
)
,
(
status
.
luastate_bytes
/
1024
)
/
1000
)
253
n
=
0
254
end
255
local
ttime
=
0
256
while
ttime
<
=
delay
do
257
local
wt
=
lfs
.
attributes
(
watcher
,
"
mtime
"
)
258
if
wt
and
wt
~
=
wtime
then
259
-- fast signal that there is a request
260
wtime
=
wt
261
break
262
end
263
ttime
=
ttime
+
0
.
2
264
os
.
sleep
(
0
.
2
)
265
end
266
end
267
end
268
local
cleanupdelay
,
cleanup
=
environment
.
argument
(
"
cleanup
"
)
,
false
269
if
cleanupdelay
then
270
local
lasttime
=
time
(
)
271
cleanup
=
function
(
)
272
local
currenttime
=
time
(
)
273
local
delta
=
difftime
(
currenttime
,
lasttime
)
274
if
delta
>
cleanupdelay
then
275
lasttime
=
currenttime
276
for
i
=
1
,
#
paths
do
277
local
path
=
paths
[
i
]
278
if
string
.
find
(
path
,
"
%.
"
)
then
279
-- safeguard, we want a fully qualified path
280
else
281
local
files
=
dir
.
glob
(
file
.
join
(
path
,
"
*
"
)
)
282
for
i
=
1
,
#
files
do
283
local
name
=
files
[
i
]
284
local
filetime
=
lfs
.
attributes
(
name
,
"
modification
"
)
285
local
delta
=
difftime
(
currenttime
,
filetime
)
286
if
delta
>
cleanupdelay
then
287
-- report("cleaning up '%s'",name)
288
os
.
remove
(
name
)
289
end
290
end
291
end
292
end
293
end
294
end
295
else
296
cleanup
=
function
(
)
297
-- nothing
298
end
299
end
300
while
true
do
301
if
false
then
302
--~ if true then
303
process
(
)
304
cleanup
(
)
305
wait
(
)
306
else
307
pcall
(
process
)
308
pcall
(
cleanup
)
309
pcall
(
wait
)
310
end
311
end
312
else
313
report
(
"
no paths to watch
"
)
314
end
315
end
316 317
function
scripts
.
watch
.
collect_logs
(
path
)
-- clean 'm up too
318
path
=
path
or
environment
.
argument
(
"
logpath
"
)
or
"
"
319
path
=
(
path
=
=
"
"
and
"
.
"
)
or
path
320
local
files
=
dir
.
globfiles
(
path
,
false
,
"
^%d+%.lua$
"
)
321
local
collection
=
{
}
322
local
valid
=
table
.
tohash
(
{
"
filename
"
,
"
result
"
,
"
runtime
"
,
"
size
"
,
"
status
"
}
)
323
for
i
=
1
,
#
files
do
324
local
name
=
files
[
i
]
325
local
t
=
dofile
(
name
)
326
if
t
and
type
(
t
)
=
=
"
table
"
and
t
.
status
then
327
for
k
,
v
in
next
,
t
do
328
if
not
valid
[
k
]
then
329
t
[
k
]
=
nil
330
end
331
end
332
collection
[
name
:
gsub
(
"
[^%d]
"
,
"
"
)
]
=
t
333
end
334
end
335
return
collection
336
end
337 338
function
scripts
.
watch
.
save_logs
(
collection
,
path
)
-- play safe
339
if
collection
and
next
(
collection
)
then
340
path
=
path
or
environment
.
argument
(
"
logpath
"
)
or
"
"
341
path
=
(
path
=
=
"
"
and
"
.
"
)
or
path
342
local
filename
=
format
(
"
%s/collected-%s.lua
"
,
path
,
tostring
(
time
(
)
)
)
343
io
.
savedata
(
filename
,
table
.
serialize
(
collection
,
true
)
)
344
local
check
=
dofile
(
filename
)
345
for
k
,
v
in
next
,
check
do
346
if
not
collection
[
k
]
then
347
report
(
"
error in saving file
"
)
348
os
.
remove
(
filename
)
349
return
false
350
end
351
end
352
for
k
,
v
in
next
,
check
do
353
os
.
remove
(
format
(
"
%s.lua
"
,
k
)
)
354
end
355
return
true
356
else
357
return
false
358
end
359
end
360 361
function
scripts
.
watch
.
collect_collections
(
path
)
-- removes duplicates
362
path
=
path
or
environment
.
argument
(
"
logpath
"
)
or
"
"
363
path
=
(
path
=
=
"
"
and
"
.
"
)
or
path
364
local
files
=
dir
.
globfiles
(
path
,
false
,
"
^collected%-%d+%.lua$
"
)
365
local
collection
=
{
}
366
for
i
=
1
,
#
files
do
367
local
name
=
files
[
i
]
368
local
t
=
dofile
(
name
)
369
if
t
and
type
(
t
)
=
=
"
table
"
then
370
for
k
,
v
in
next
,
t
do
371
collection
[
k
]
=
v
372
end
373
end
374
end
375
return
collection
376
end
377 378
function
scripts
.
watch
.
show_logs
(
path
)
-- removes duplicates
379
local
collection
=
scripts
.
watch
.
collect_collections
(
path
)
or
{
}
380
local
max
=
0
381
for
k
,
v
in
next
,
collection
do
382
v
=
v
.
filename
or
"
?
"
383
if
#
v
>
max
then
max
=
#
v
end
384
end
385
-- print(max)
386
local
sorted
=
table
.
sortedkeys
(
collection
)
387
for
k
=
1
,
#
sorted
do
388
local
v
=
sorted
[
k
]
389
local
c
=
collection
[
v
]
390
local
f
,
s
,
r
,
n
=
c
.
filename
or
"
?
"
,
c
.
status
or
"
?
"
,
c
.
runtime
or
0
,
c
.
size
or
0
391
report
(
"
%s %s %3i %8i %s
"
,
string
.
padd
(
f
,
max
,
"
"
)
,
string
.
padd
(
s
,
10
,
"
"
)
,
r
,
n
,
v
)
392
end
393
end
394 395
function
scripts
.
watch
.
cleanup_stale_files
(
)
-- removes duplicates
396
local
path
=
environment
.
files
[
1
]
397
local
delay
=
tonumber
(
environment
.
argument
(
"
cleanup
"
)
)
398
local
force
=
environment
.
argument
(
"
force
"
)
399
if
not
path
or
path
=
=
"
.
"
then
400
report
(
"
provide qualified path
"
)
401
elseif
not
delay
then
402
report
(
"
missing --cleanup=delay
"
)
403
else
404
if
not
force
then
405
report
(
"
dryrun, use --force for real cleanup
"
)
406
end
407
local
files
=
dir
.
glob
(
file
.
join
(
path
,
"
*
"
)
)
408
local
rtime
=
time
(
)
409
for
i
=
1
,
#
files
do
410
local
name
=
files
[
i
]
411
local
mtime
=
lfs
.
attributes
(
name
,
"
modification
"
)
412
local
delta
=
difftime
(
rtime
,
mtime
)
413
if
delta
>
delay
then
414
report
(
"
cleaning up '%s'
"
,
name
)
415
if
force
then
416
os
.
remove
(
name
)
417
end
418
end
419
end
420
end
421
end
422 423
if
environment
.
argument
(
"
watch
"
)
then
424
scripts
.
watch
.
watch
(
)
425
elseif
environment
.
argument
(
"
collect
"
)
then
426
scripts
.
watch
.
save_logs
(
scripts
.
watch
.
collect_logs
(
)
)
427
elseif
environment
.
argument
(
"
cleanup
"
)
then
428
scripts
.
watch
.
save_logs
(
scripts
.
watch
.
cleanup_stale_files
(
)
)
429
elseif
environment
.
argument
(
"
showlog
"
)
then
430
scripts
.
watch
.
show_logs
(
)
431
elseif
environment
.
argument
(
"
exporthelp
"
)
then
432
application
.
export
(
environment
.
argument
(
"
exporthelp
"
)
,
environment
.
files
[
1
]
)
433
else
434
application
.
help
(
)
435
end
436