fetchpull.w 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997
  1. #!/usr/bin/env bash
  2. # -*- wisp -*-
  3. guile -L $(dirname $(realpath "$0")) -c '(import (language wisp spec))'
  4. PROG="$0"
  5. if [[ "$1" == "-i" ]]; then
  6. shift
  7. exec -a "${PROG}" guile -L $(dirname $(realpath "$0")) --language=wisp -x .w -e '(fetchpull)' -- "${@}"
  8. else
  9. exec -a "${0}" guile -L $(dirname $(realpath "$0")) --language=wisp -x .w -e '(fetchpull)' -c '' "${@}"
  10. fi;
  11. ; !#
  12. ;; for emacs (defun test-this-file () (interactive) (save-current-buffer) (async-shell-command (concat (buffer-file-name (current-buffer)) " --test")))
  13. define-module : fetchpull
  14. . #:export : main
  15. define version "0.0.0 just-do-it"
  16. define design
  17. '
  18. keys are KSK@<prefix>--DATE-uploaded-xxx-days-before-using-MODE
  19. process:
  20. get the current date
  21. for realtime or bulk as MODE
  22. for each power of two up to 512 as iii
  23. insert a random chunk (without compression)
  24. to {DATE + iii days}-uploaded-iii-days-before-using-MODE
  25. request the key DATE-uploaded-iii-days-before-using-MODE
  26. write the times along with the keys (without the prefix)
  27. into insert-times.csv and request-times.csv
  28. format as
  29. DATE-as-seconds-since-epoch duration iii MODE
  30. prefix is generated from securepassword.w and stored in the file fetchpull-prefix.txt
  31. import
  32. only (srfi srfi-19) current-date date->string string->date date->time-utc time-utc->date
  33. . make-time time-utc time-duration add-duration current-time
  34. only (securepassword) letterblocks-nice
  35. only (srfi srfi-9) define-record-type
  36. only (srfi srfi-9 gnu) set-record-type-printer!
  37. only (ice-9 pretty-print) pretty-print
  38. only (ice-9 rdelim) read-line read-delimited
  39. only (ice-9 format) format
  40. only (ice-9 iconv) string->bytevector
  41. only (srfi srfi-1) first second third alist-cons assoc lset<= lset-intersection lset-difference
  42. only (rnrs bytevectors) make-bytevector bytevector-length string->utf8 bytevector?
  43. only (rnrs io ports) get-bytevector-all get-bytevector-n
  44. . put-bytevector bytevector->string port-eof?
  45. only (ice-9 popen) open-output-pipe
  46. only (ice-9 expect) expect-strings ;; for quick experimentation. Expect needs additional functions and variables available:
  47. . expect expect-regexec expect-timeout expect-select expect-timeout-proc
  48. . expect-char-proc expect-eof-proc expect-strings-compile-flags
  49. only (ice-9 regex) string-match match:substring
  50. ice-9 threads
  51. ice-9 atomic
  52. only (ice-9 q) make-q enq! deq! q-empty?
  53. sxml simple
  54. doctests
  55. define today : current-time time-utc
  56. define : time->iso time
  57. date->string (time-utc->date time) "~1"
  58. define : iso->time string
  59. date->time-utc : string->date string "~Y~m~d"
  60. define : add-days time number-of-days
  61. let : : seconds : * 3600 24 number-of-days
  62. add-duration time
  63. make-time time-duration 0 seconds
  64. define : string-replace-string s char replacement-string
  65. string-join (string-split s char) replacement-string
  66. define : replace-KSK-escaped s
  67. string-replace-string : string-replace-string s #\+ "-"
  68. . #\= "-"
  69. define : task-id
  70. replace-KSK-escaped : letterblocks-nice 6
  71. define prefix-filename "fetchpull-prefix.txt"
  72. define prefix-cache #f
  73. define : prefix
  74. cond
  75. prefix-cache
  76. . prefix-cache
  77. : file-exists? prefix-filename
  78. read-line : open-input-file prefix-filename
  79. else
  80. let
  81. : pw : task-id
  82. port : open-output-file prefix-filename
  83. display pw port
  84. close-port port
  85. . pw
  86. define : KSK-for-insert prefix today days-before mode
  87. format #f "KSK@~a--~a-uploaded-~3,'0d-days-before-using-~a" prefix
  88. time->iso : add-days today days-before
  89. . days-before mode
  90. define : KSK-for-request prefix time days-before mode
  91. ##
  92. tests
  93. test-equal
  94. . "KSK@WwL6-UXTu-sa5n.fAk2-s7kj.5Kp6--2018-11-23-uploaded-005-days-before-using-realtime"
  95. KSK-for-request "WwL6-UXTu-sa5n.fAk2-s7kj.5Kp6" (iso->time "2018-11-23") 5 'realtime
  96. format #f "KSK@~a--~a-uploaded-~3,'0d-days-before-using-~a" prefix
  97. time->iso time
  98. . days-before mode
  99. ;; the shared FCP socket
  100. define sock #f
  101. define : fcp-socket-create
  102. define addrs : getaddrinfo "127.0.0.1" "9489"
  103. define addr : first addrs
  104. define s : socket (addrinfo:fam addr) (addrinfo:socktype addr) (addrinfo:protocol addr)
  105. connect s : addrinfo:addr addr
  106. . s
  107. define-record-type <message>
  108. message-create task type data fields
  109. . message?
  110. task message-task
  111. type message-type
  112. data message-data
  113. fields message-fields ;; avoid duplicates: fred joins duplicate fields with ";" to a single value
  114. ;; use a custom printer which avoids printing the full data
  115. set-record-type-printer! <message>
  116. lambda : record port
  117. format port "#<<message> task: ~A type: ~A data: ~a, fields: ~A"
  118. message-task record
  119. message-type record
  120. if : bytevector? : message-data record
  121. format #f "length=~a" : bytevector-length : message-data record
  122. message-data record
  123. message-fields record
  124. define : format-field field
  125. format #f "~a=~a"
  126. car field
  127. cdr field
  128. define : join-fields fields
  129. ## : tests : test-equal "A=B\nX=V" : join-fields : list (cons 'A "B") (cons 'X 'V)
  130. string-join
  131. map format-field fields
  132. . "\n"
  133. define field-key car
  134. define field-value cdr
  135. define : field-split s
  136. let : : where : string-index s #\=
  137. if where
  138. cons
  139. string->symbol : substring/shared s 0 where
  140. substring/shared s (+ where 1) (string-length s)
  141. cons s ""
  142. define : write-message message sock
  143. display (message-type message) sock
  144. newline sock
  145. when : message-task message
  146. format sock "Identifier=~a\n"
  147. message-task message
  148. when : not : null? : message-fields message
  149. display : join-fields : message-fields message
  150. . sock
  151. newline sock
  152. cond
  153. : message-data message
  154. format sock "~a\n"
  155. format-field : cons 'DataLength : bytevector-length : message-data message
  156. format sock "Data\n"
  157. put-bytevector sock : message-data message
  158. else
  159. display 'EndMessage sock
  160. newline sock
  161. atomic-box-set! sending-message #f
  162. ;; avoid overloading the node ;; FIXME: is this actually needed? Just added because it might fix crashes.
  163. usleep 1000 ;; max of 1000 messages per second
  164. define : message-client-hello
  165. message-create #f 'ClientHello #f
  166. list : cons 'Name "FetchpullClient"
  167. cons 'ExpectedVersion "2.0"
  168. define : message-watch-global
  169. message-create #f 'WatchGlobal #f
  170. list : cons 'Enabled "true"
  171. cons 'VerbosityMask 0 ;; simple progress
  172. define : message-disconnect
  173. message-create #f 'Disconnect #f
  174. list
  175. define : message-client-get task URI custom-fields
  176. ;; https://github.com/freenet/wiki/wiki/FCPv2-ClientGet
  177. message-create task 'ClientGet #f
  178. append
  179. list : cons 'URI URI
  180. ' : Verbosity . 0 ;; only be informed when the download is finished
  181. ReturnType . direct
  182. Global . true
  183. Persistence . reboot
  184. . custom-fields
  185. define : message-client-get-realtime task URI
  186. message-client-get task URI
  187. '
  188. PriorityClass . 2
  189. RealTimeFlag . true
  190. FilterData . false
  191. MaxRetries . 0
  192. define : message-client-get-bulk task URI
  193. message-client-get task URI
  194. '
  195. PriorityClass . 3 ;; medium
  196. RealTimeFlag . false
  197. FilterData . false
  198. MaxRetries . 1 ;; -1 means: try indefinitely, with ULPR, essentially long polling
  199. define : message-client-put task URI data custom-fields
  200. ;; https://github.com/freenet/wiki/wiki/FCPv2-ClientPut
  201. message-create task 'ClientPut data
  202. append
  203. list : cons 'URI URI
  204. ` : Global . true
  205. Persistence . reboot
  206. UploadFrom . direct
  207. . custom-fields
  208. define : message-client-put-realtime task URI data
  209. message-client-put task URI data
  210. '
  211. PriorityClass . 2
  212. MaxRetries . 0 ;; default: 10
  213. RealTimeFlag . true
  214. DontCompress . true
  215. ExtraInsertsSingleBlock . 0
  216. ExtraInsertsSplitfileHeaderBlock . 0
  217. ;; for realtime do NOT send Metadata.ContentType (or set it
  218. ;; to "" -> Metadata.isTrivial()), else you force at least
  219. ;; one level redirect.
  220. define : message-client-put-bulk task URI data
  221. message-client-put task URI data
  222. '
  223. PriorityClass . 3 ;; medium
  224. RealTimeFlag . false
  225. DontCompress . false
  226. define : message-remove-request task
  227. message-create task 'RemoveRequest #f
  228. list : cons 'Global 'true
  229. define supported-messages
  230. ' NodeHello GetFailed DataFound AllData PutSuccessful PutFailed
  231. define ignored-messages ;; TODO: implement support for these messages
  232. ' CompatibilityMode ExpectedDataLength ExpectedHashes ExpectedMIME PersistentGet PersistentPut SendingToNetwork SimpleProgress URIGenerated PersistentRequestRemoved
  233. define : log-warning message things
  234. format : current-output-port
  235. . "Warning: ~a: ~a\n" message things
  236. define : read-message port
  237. if : or (port-closed? port) (port-eof? port)
  238. . #f
  239. let loop : : type : string->symbol : read-line port
  240. define DataLength #f
  241. define task #f
  242. let readlines : : lines : list : read-line port
  243. define line : first lines
  244. define field : field-split line
  245. when : equal? 'DataLength : field-key field
  246. set! DataLength
  247. field-value field
  248. when : equal? 'Identifier : field-key field
  249. set! task
  250. field-value field
  251. ;; pretty-print : list 'line line 'type type
  252. cond
  253. : string-index line #\=
  254. readlines : cons (read-line port) lines
  255. : member type supported-messages
  256. let
  257. :
  258. data ;; EndMessage has no Data
  259. if : and DataLength : not : equal? "EndMessage" line
  260. get-bytevector-n port (string->number DataLength)
  261. . #f
  262. message-create task type data
  263. map field-split : cdr lines
  264. else
  265. when : not : member type ignored-messages
  266. log-warning "unsupported message type" type
  267. if : port-eof? port
  268. . #f
  269. loop : string->symbol : read-line port
  270. define next-message
  271. make-atomic-box #f
  272. define sending-message
  273. make-atomic-box #f
  274. define : send-message message
  275. ;; wait until the message was retrieved. This only replaces if the previous content was #f. take-message-to-send switches takes the messages
  276. let try : : failed : atomic-box-compare-and-swap! next-message #f message
  277. when failed
  278. usleep 100
  279. try : atomic-box-compare-and-swap! next-message #f message
  280. define : take-message-to-send
  281. ;; get the message and reset next-message to #f to allow taking another message
  282. atomic-box-set! sending-message #t ;; set to false again after successful write-message
  283. atomic-box-swap! next-message #f
  284. define message-processors
  285. make-atomic-box : list
  286. define : process message
  287. let loop : (processors (atomic-box-ref message-processors)) (msg message)
  288. cond
  289. : not msg
  290. . #f
  291. : null? processors
  292. . msg
  293. else
  294. loop (cdr processors)
  295. (first processors) msg
  296. define : processor-put! processor
  297. let loop : : old : atomic-box-ref message-processors
  298. define old-now : atomic-box-compare-and-swap! message-processors old : cons processor old
  299. when : not : equal? old old-now
  300. loop : atomic-box-ref message-processors
  301. define : processor-delete! processor
  302. let loop : : old : atomic-box-ref message-processors
  303. define old-now : atomic-box-compare-and-swap! message-processors old : delete processor old
  304. when : not : equal? old old-now
  305. loop : atomic-box-ref message-processors
  306. define stop-fcp-threads #f
  307. define : fcp-read-loop sock
  308. let loop : : message : read-message sock
  309. when message
  310. warn-unhandled
  311. process message
  312. usleep 10
  313. when : not stop-fcp-threads
  314. loop : read-message sock
  315. define : fcp-write-loop sock
  316. let loop : : message : take-message-to-send
  317. if message
  318. write-message message sock
  319. begin
  320. atomic-box-set! sending-message #f
  321. usleep 10
  322. when : not stop-fcp-threads
  323. loop : take-message-to-send
  324. define : warn-unhandled message
  325. when message
  326. format #t ;; avoid writing to the error port elsewhere, that causes multithreading problems. Use current-output-port instead
  327. . "Unhandled message ~a: ~a\n"
  328. message-type message
  329. message-task message
  330. . #f
  331. define : printing-passthrough-processor message
  332. pretty-print message
  333. . message
  334. define : printing-discarding-processor message
  335. pretty-print message
  336. . #f
  337. define : discarding-processor message
  338. . #f
  339. define : help args
  340. format : current-output-port
  341. . "~a [-i] [--help | --version | --test | --site [target-folder] | YYYY-mm-dd]
  342. Options:
  343. -i load the script and run an interactive REPL."
  344. first args
  345. ;; timing information (alists)
  346. define get-successful : list
  347. define get-failed : list
  348. define put-successful : list
  349. define put-failed : list
  350. define get-alldata : list ; the actual data, for debugging
  351. define : processor-datafound-getdata message
  352. cond
  353. : equal? 'DataFound : message-type message
  354. send-message
  355. message-create : message-task message
  356. . 'GetRequestStatus #f
  357. list : cons 'Global 'true
  358. . #f
  359. else message
  360. define : processor-record-datafound-time message
  361. cond
  362. : equal? 'DataFound : message-type message
  363. let : : task : message-task message
  364. when : not : assoc task get-successful ;; only add if not yet known
  365. set! get-successful
  366. alist-cons task (current-time-seconds) get-successful
  367. . #f
  368. else message
  369. define : current-time-seconds
  370. car : gettimeofday
  371. define : processor-record-alldata-time message
  372. cond
  373. : equal? 'AllData : message-type message
  374. let : : task : message-task message
  375. when : not : assoc task get-successful ;; only add if not yet known
  376. set! get-successful
  377. alist-cons task (current-time-seconds) get-successful
  378. . #f
  379. else message
  380. define : processor-record-getfailed-time message
  381. cond
  382. : equal? 'GetFailed : message-type message
  383. let : : task : message-task message
  384. when : not : assoc task get-failed ;; only add if not yet known
  385. set! get-failed
  386. alist-cons task (current-time-seconds) get-failed
  387. . #f
  388. else message
  389. define : processor-record-putfailed-time message
  390. cond
  391. : equal? 'PutFailed : message-type message
  392. let : : task : message-task message
  393. when : not : assoc task put-failed ;; only add if not yet known
  394. set! put-failed
  395. alist-cons task (current-time-seconds) put-failed
  396. . #f
  397. else message
  398. define : processor-record-putsuccessful-time message
  399. cond
  400. : equal? 'PutSuccessful : message-type message
  401. let : : task : message-task message
  402. when : not : assoc task put-successful ;; only add if not yet known
  403. set! put-successful
  404. alist-cons task (current-time-seconds) put-successful
  405. . #f
  406. else message
  407. define : processor-record-identifier-collision-put-time message
  408. cond
  409. : equal? 'IdentifierCollision : message-type message
  410. let : : task : message-task message
  411. when : not : assoc task put-failed ;; only add if not yet known
  412. set! put-failed
  413. alist-cons task (current-time-seconds) put-failed
  414. . #f
  415. else message
  416. define : generate-data seed-string size-bytes-min
  417. . "permutate the seed-string randomly to generate bulk data of at least size-bytes-min"
  418. define required-permutations {size-bytes-min / (string-length seed-string)}
  419. define chars : string->list seed-string
  420. define compare : λ (a b) : < (car a) (car b)
  421. define data
  422. let loop : : permutations : list chars
  423. if {(length permutations) >= required-permutations} permutations
  424. loop
  425. cons
  426. map cdr
  427. sort
  428. map : λ (x) : cons (random 1.0) x
  429. . chars
  430. . compare
  431. . permutations
  432. string->utf8 : string-join (map list->string data) "\n"
  433. define-record-type <duration-entry>
  434. duration-entry key duration successful operation mode
  435. . timing-entry?
  436. key duration-entry-key
  437. duration duration-entry-duration
  438. successful duration-entry-success
  439. operation duration-entry-operation ;; get or put
  440. mode duration-entry-mode ;; realtime bulk speehacks
  441. define timeout-seconds : * 3600 3 ;; 3 hours maximum wait time
  442. define : timeout? timeout-seconds start-times
  443. and : not : null? start-times
  444. pair? : car start-times
  445. > : - (current-time-seconds) timeout-seconds
  446. cdr : car start-times
  447. define : remove-all-keys keys
  448. define : remove-key key
  449. send-message
  450. message-remove-request key
  451. map remove-key keys
  452. define* : time-get mode keys
  453. define start-times : list
  454. define : get-message key
  455. if : equal? mode 'realtime
  456. message-client-get-realtime key key
  457. message-client-get-bulk key key
  458. define : finished-tasks
  459. append
  460. map car get-successful
  461. map car get-failed
  462. ;; cleanup old state
  463. remove-all-keys keys
  464. set! get-successful : list
  465. set! get-failed : list
  466. ;; setup a processing chain which saves the time information about the request
  467. ;; processor-put! processor-datafound-getdata
  468. processor-put! processor-record-datafound-time
  469. processor-put! processor-record-getfailed-time
  470. ;; just use the keys as task-IDs (Identifiers)
  471. let loop : (keys keys)
  472. when : not : null? keys
  473. ;; first remove requests which might still be in the upload or download queue
  474. send-message
  475. message-remove-request : first keys
  476. set! start-times : alist-cons (first keys) (current-time-seconds) start-times
  477. ;; now request the data
  478. send-message
  479. get-message (first keys)
  480. loop (cdr keys)
  481. ;; wait for completion
  482. let loop : (finished (finished-tasks))
  483. when : not : lset<= equal? keys finished
  484. format #t "debug: lset-intersection equal? keys finished -> ~a, keys -> ~a, get-successful -> ~a, get-failed -> ~a\n" (length finished) (length keys) (length get-successful) (length get-failed)
  485. let : : unfinished : lset-difference equal? keys : lset-intersection equal? keys finished
  486. format : current-output-port
  487. . "~d download keys still in flight\n" (length unfinished)
  488. cond
  489. : timeout? timeout-seconds start-times
  490. map ;; fail all unfinished
  491. λ : key
  492. send-message
  493. message-remove-request key
  494. set! get-failed
  495. alist-cons key (current-time-seconds) get-failed
  496. . unfinished
  497. else
  498. usleep 1000000
  499. loop (finished-tasks)
  500. ;; all done: cleanup and take the timing
  501. format #t "finished trying to fetch ~a keys\n" : length keys
  502. processor-delete! processor-record-getfailed-time
  503. processor-delete! processor-record-datafound-time
  504. ;; processor-delete! processor-datafound-getdata
  505. remove-all-keys keys
  506. let loop : (keys keys) (times '())
  507. if : null? keys
  508. . times
  509. let :
  510. define key : first keys
  511. define (gettime L) : cdr : assoc key L
  512. define start-time : gettime start-times
  513. define finish-time : gettime : append get-successful get-failed
  514. define successful : and (assoc key get-successful) #t ;; forces boolean
  515. send-message
  516. message-remove-request key
  517. loop : cdr keys
  518. cons : duration-entry (first keys) {finish-time - start-time} successful 'GET mode
  519. . times
  520. define : time-put mode keys
  521. define 80Bytes 80 ;; raw KSK, no other keys needed
  522. define 1MiB : expt 2 20 ;; 1 MiB are about 40 blocks, should forward to CHK splitfile, TODO: check with KeyUtils
  523. define 512kiB : expt 2 19 ;; 500kiB MiB are about 20 blocks, the KSK is a splitfile
  524. define 128kiB : expt 2 17 ;; 128 kiB text are about 4 blocks, the KSK is a splitfile
  525. define start-times : list
  526. define : put-message key
  527. cond
  528. : equal? mode 'realtime
  529. message-client-put-realtime key key : generate-data key 80Bytes
  530. : equal? mode 'small
  531. message-client-put-bulk key key : generate-data key 128kiB
  532. else
  533. message-client-put-bulk key key : generate-data key 1MiB
  534. define : finished-tasks
  535. append
  536. map car put-successful
  537. map car put-failed
  538. ;; cleanup old state
  539. remove-all-keys keys
  540. set! put-successful : list
  541. set! put-failed : list
  542. ;; setup a processing chain which saves the time information about the request
  543. processor-put! processor-record-putsuccessful-time
  544. processor-put! processor-record-putfailed-time
  545. processor-put! processor-record-identifier-collision-put-time
  546. ;; insert all files, using the keys as task-IDs (Identifiers)
  547. let loop : (keys keys)
  548. when : not : null? keys
  549. ;; first remove requests which might still be in the upload or download queue
  550. send-message
  551. message-remove-request : first keys
  552. set! start-times : alist-cons (first keys) (current-time-seconds) start-times
  553. ;; now insert the data
  554. send-message
  555. put-message (first keys)
  556. ;; avoid too many simultaneous inserts at the same time, finish-times are recorded asynchronously
  557. cond
  558. : equal? mode 'realtime
  559. ;; the typical realtime insert takes 30s, so
  560. ;; sleeping 30s should effectively serialize the
  561. ;; inserts, giving a better estimate of the expected
  562. ;; performance of messaging applications.
  563. sleep 30
  564. else ;; wait one minute for other files, while
  565. ;; avoiding to run into the global timeout
  566. let : : insert-count-divisor-with-buffer : * 2 (length keys)
  567. sleep : min 60 {timeout-seconds / insert-count-divisor-with-buffer}
  568. loop (cdr keys)
  569. ;; wait for completion
  570. let loop : (finished (finished-tasks))
  571. when : not : lset<= equal? keys finished
  572. let : : unfinished : lset-difference equal? keys : lset-intersection equal? keys finished
  573. format : current-output-port
  574. . "~d upload keys still in flight\n" (length unfinished)
  575. cond
  576. : timeout? timeout-seconds start-times
  577. map
  578. λ : key
  579. send-message
  580. message-remove-request key
  581. set! put-failed
  582. alist-cons key (current-time-seconds) put-failed
  583. . unfinished
  584. else
  585. sleep 1
  586. loop (finished-tasks)
  587. ;; all done: cleanup and take the timing
  588. format #t "finished trying to insert ~a keys\n" : length keys
  589. processor-delete! processor-record-identifier-collision-put-time
  590. processor-delete! processor-record-putfailed-time
  591. processor-delete! processor-record-putsuccessful-time
  592. remove-all-keys keys
  593. let loop : (keys keys) (times '())
  594. if : null? keys
  595. . times
  596. let :
  597. define key : first keys
  598. define (gettime L) : cdr : assoc key L
  599. define start-time : gettime start-times
  600. define finish-time : gettime : append put-successful put-failed
  601. define successful : and (assoc key put-successful) #t ;; forces boolean
  602. send-message
  603. message-remove-request key
  604. loop : cdr keys
  605. cons : duration-entry (first keys) {finish-time - start-time} successful 'PUT mode
  606. . times
  607. define %this-module : current-module
  608. define : test
  609. processor-put! discarding-processor
  610. processor-put! printing-passthrough-processor
  611. set! sock : fcp-socket-create
  612. let
  613. :
  614. fcp-read-thread
  615. begin-thread
  616. fcp-read-loop sock
  617. fcp-write-thread
  618. begin-thread
  619. fcp-write-loop sock
  620. send-message : message-client-hello
  621. send-message : message-watch-global
  622. send-message : message-client-get-realtime (letterblocks-nice 6) "USK@N82omidQlapADLWIym1u4rXvEQhjoIFbMa5~p1SKoOY,LE3WlYKas1AIdoVX~9wahrTlV5oZYhvJ4AcYYGsBq-w,AQACAAE/irclogs/772/2018-11-23.weechatlog"
  623. sleep 30
  624. send-message : message-disconnect
  625. doctests-testmod %this-module
  626. join-thread fcp-write-thread : + 30 : current-time-seconds
  627. join-thread fcp-read-thread : + 30 : current-time-seconds
  628. close sock
  629. define : call-with-fcp-connection thunk
  630. set! sock : fcp-socket-create
  631. set! stop-fcp-threads #f
  632. let
  633. :
  634. fcp-read-thread
  635. begin-thread
  636. fcp-read-loop sock
  637. fcp-write-thread
  638. begin-thread
  639. fcp-write-loop sock
  640. send-message : message-client-hello
  641. send-message : message-watch-global
  642. thunk
  643. while : or (atomic-box-ref next-message) (atomic-box-ref sending-message)
  644. format #t "waiting for message to be sent: next-message: ~a , sending: ~a\n" (atomic-box-ref next-message) (atomic-box-ref sending-message)
  645. sleep 1
  646. send-message : message-disconnect
  647. set! stop-fcp-threads #t
  648. sleep 3
  649. close sock
  650. join-thread fcp-write-thread : + 3 : current-time-seconds
  651. join-thread fcp-read-thread : + 3 : current-time-seconds
  652. ;; FIXME: using new fcp connections in sequential code-parts fails with
  653. ;; ERROR: In procedure display: Wrong type argument in position 2: #<closed: file 7f106e118770>
  654. ;; ERROR: In procedure fport_read: Die Verbindung wurde vom Kommunikationspartner zurückgesetzt
  655. define-syntax-rule : with-fcp-connection exp ...
  656. call-with-fcp-connection
  657. λ () exp ...
  658. define* : stats->csv stats #:key (target-filename #f)
  659. . "Format the all duration-entry in stats as csv file.
  660. example:
  661. date;key;duration;days-before;mode;success
  662. KSK@...;32;16;realtime;false
  663. KSK@...;40;32;realtime;true
  664. "
  665. define : days-before key
  666. string->number
  667. match:substring
  668. string-match "uploaded-([0-9]*)-days-before" key
  669. . 1
  670. define new : not : and target-filename : file-exists? target-filename
  671. define port
  672. cond
  673. target-filename
  674. open-file target-filename "al"
  675. else
  676. current-output-port
  677. when new
  678. display "day;key;duration;days-before;mode;success" port
  679. newline port
  680. let loop : : stats stats
  681. when : not : null? stats
  682. let : : s : first stats
  683. format port "~a;~a;~f;~d;~a;~a\n"
  684. time->iso today
  685. duration-entry-key s
  686. duration-entry-duration s
  687. days-before : duration-entry-key s
  688. duration-entry-mode s
  689. duration-entry-success s
  690. loop : cdr stats
  691. when target-filename : close-port port
  692. ;; the following is just for fun. Not ready for production. You have been warned :-)
  693. ;; use text without quotes in tags via ,(>- any text )
  694. define : ->string x
  695. cond
  696. : symbol? x
  697. symbol->string x
  698. : number? x
  699. format #f "~a" x
  700. : unspecified? x
  701. . ""
  702. else
  703. format #f "~A" x
  704. define-syntax-rule : >- . args
  705. string-join
  706. map ->string : quasiquote args
  707. . " "
  708. define : website-content port
  709. define title "Fetch-Pull-Stats re-woven"
  710. sxml->xml
  711. ` *TOP*
  712. html
  713. head : meta : @ (charset "utf-8")
  714. title ,title
  715. body : h1 ,title
  716. p "These are the fetch-pull statistics. They provide an estimate of lifetimes of real files in Freenet and a somewhat early warning when network quality should degrade."
  717. p "Realtime are 80 bytes. Small are 128 kiB. Bulk is 1MiB."
  718. p "Further details are explained below the diagrams."
  719. h2 "Lifetime diagrams"
  720. p "Compare the success count at different ages. The age before the success count drops marks the expected lifetime."
  721. ,@ map : λ (attributes) : ` p : img ,attributes
  722. '
  723. @ (src "fetchpull-lifetime-realtime-success-count.png") (alt "lifetime plot: successes per month, realtime")
  724. @ (src "fetchpull-lifetime-small-success-count.png") (alt "lifetime plot: successes per month, small bulk")
  725. @ (src "fetchpull-lifetime-bulk-success-count.png") (alt "lifetime plot: successes per month, large bulk")
  726. h2 "Download time and upload time plots"
  727. p "Compare the time to retrieve or insert a file at different ages."
  728. ,@ map : λ (attributes) : ` p : img ,attributes
  729. '
  730. @ (src "fetchpull-get-realtime.png") (alt "fetch-pull realtime download graph")
  731. @ (src "fetchpull-get-small.png") (alt "fetch-pull small download graph")
  732. @ (src "fetchpull-get-bulk.png") (alt "fetch-pull bulk download graph")
  733. @ (src "fetchpull-get-failed-realtime.png") (alt "fetch-pull failed realtime download graph")
  734. @ (src "fetchpull-get-failed-small.png") (alt "fetch-pull failed small download graph")
  735. @ (src "fetchpull-get-failed-bulk.png") (alt "fetch-pull failed bulk download graph")
  736. @ (src "fetchpull-put.png") (alt "fetch-pull upload graph")
  737. @ (src "fetchpull-put-failed.png") (alt "fetch-pull failed upload graph")
  738. ;; @ (src "fetchpull-lifetime-realtime.png") (alt "lifetime plot: time per download, realtime")
  739. ;; @ (src "fetchpull-lifetime-small.png") (alt "lifetime plot: time per download, small bulk")
  740. ;; @ (src "fetchpull-lifetime-bulk.png") (alt "lifetime plot: time per download, large bulk")
  741. h2 "Explanation"
  742. h3 "Uploads and settings"
  743. p "The files are uploaded regularly. Downloads are attempted after some delay.
  744. Realtime is uploaded with realtime priority, small and bulk with bulk priority.
  745. Details are available in fetchpull.w (see sources)"
  746. ul
  747. li "Realtime is a raw KSK without any redirect. Size 80 bytes, Uploaded and downloaded in realtime mode without compression, using all tricks to reduce latency. This is the fake chat-message: What you would use for interactive status updates and such."
  748. li "Small is a KSK splitfile (a KSK that has the links to about 7 CHKs, needs 3-4). Size 128kiB uncompressed, around 80kiB compressed, Uploaded and downloaded in bulk mode."
  749. li "Bulk is a KSK which forwards to a CHK splitfile that has around 40 blocks, needs about 20 to download. Size 1MiB uncompressed, around 650kiB compressed, uploaded and downloaded in bulk mode. These fetchpullstats need about 1 MiB."
  750. h3 "Understanding the lifetime diagrams"
  751. p "On the y-axis you have the days since the upload. That means: A file is uploaded (as KSK) and then downloaded that many days later. So for example the crosses in the top line of 2019 are downloaded 128 days after they have been inserted."
  752. p "The successes are aggregated per month and the color gives you the number of successful downloads in the month."
  753. p "If you look at the '1 day after insert' line, you get the total number of files inserted in that month. For 2019-07 that’s for example around 80. Now you can look upwards how many downloads succeeded with longer delay."
  754. p "By comparing the color at the 128-day line (above 3 months) with the color at the 1-day line, you can see how many inserts are still alive after 128 days. You can tell from that after how many days the success-count breaks down."
  755. p "That we have a line at 256 for " (b "realtime") " with colors almost equal to the 64 day line means that those files are still available after 256 days."
  756. p "For the " (b "small") " graph: You can see that the colors above and below the 2 weeks line are almost equal. That means that a file of 128kiB lives for at least 16 days without being accessed. Above that you see the success counts slowly falling off: more and more of the blocks are overwritten, so there’s a chance for the files do be down. After 32 days around 50 out of 80 files are still available. After 64 days, around 30 out of 80 files are still there. After 128 days most are gone."
  757. p "Going to the " (b "bulk") " line you see a slightly different pattern: there is no visible difference between 4 days and 8 days, so lifetime of 1MiB files is at least 8 days, but you already see some reduction at 16 days. At 32 days up to 40 out of 80 files are still alive, but almost none survive for 64 days. The reason for that is that a 1MiB file has an intermediate CHK splitfile and once the single top-key falls out, it is dead."
  758. h3 "This site"
  759. p "This page is generated by running " : code "./fetchpull.w --site fetchpullstats"
  760. ;; the following is just for fun. Not ready for production. You have been warned :-)
  761. . " " ,(>- and then ,(string-append "uploaded" " " "with") freesitemgr (from pyFreenet ,{1 + 2}) as freesite.)
  762. br
  763. . "Feel free to create your own version."
  764. h2 "Sources"
  765. ul
  766. li "created with "
  767. a : @ (href "fetchpull.w") (title "link to exact file which generated this site")
  768. . "fetchpull.w"
  769. li "from project "
  770. a : @ (href "https://bitbucket.org/ArneBab/freenet-guile") (title "link to project")
  771. . "guile-fcp"
  772. li "plotted with "
  773. a : @ (href "fetchpull-plot.gnuplot") (title "plotting script for gnuplot")
  774. . "fetchpull-plot.gnuplot"
  775. li "using data from "
  776. a : @ (href "fetchpull-stats-get.csv") (title "download stats")
  777. . "fetchpull-stats-get.csv"
  778. . " and "
  779. a : @ (href "fetchpull-stats-put.csv") (title "upload stats")
  780. . "fetchpull-stats-put.csv"
  781. p
  782. a : @ (href "/?newbookmark=USK@lwR9sLnZD3QHveZa1FB0dAHgeck~dFNBg368mY09wSU,0Vq~4FXSUj1-op3QdzqjZsIvrNMYWlnSdUwCl-Z1fYA,AQACAAE/fetchpullstats/8/&desc=fetchpullstats&hasAnActivelink=true")
  783. . "bookmark this freesite"
  784. . port
  785. define : create-plot
  786. define gnuplot : open-output-pipe "gnuplot"
  787. define input : open-input-file "fetchpull-plot.gnuplot"
  788. let loop :
  789. when : not : port-eof? input
  790. display (read-char input) gnuplot
  791. loop
  792. newline gnuplot
  793. display "quit" gnuplot
  794. newline gnuplot
  795. close input
  796. close gnuplot
  797. sync
  798. define : copy-resources-to path
  799. ;; remove all KSK information from the stats to prevent people from tampering with them
  800. let loop : (files '("fetchpull-stats-get.csv" "fetchpull-stats-put.csv"))
  801. when : not : null? files
  802. when : file-exists? : first files
  803. let : : new-filename : string-append path file-name-separator-string : first files
  804. copy-file : first files
  805. . new-filename
  806. close : open-output-pipe : string-append "sed -i 's/KSK@.*using-[^;]*;/KEY;/' " new-filename "\n"
  807. loop : cdr files
  808. ;; simply copy over the plot and plotting script
  809. ;; FIXME: the resulting images can be empty, need to copy them manually.
  810. sleep 3
  811. let loop : (files '("fetchpull.w" "fetchpull-plot.gnuplot" "fetchpull-get-realtime.png" "fetchpull-get-small.png" "fetchpull-get-bulk.png" "fetchpull-get-failed-realtime.png" "fetchpull-get-failed-small.png" "fetchpull-get-failed-bulk.png" "fetchpull-put.png" "fetchpull-put-failed.png" "fetchpull-lifetime-realtime.png" "fetchpull-lifetime-small.png" "fetchpull-lifetime-bulk.png" "fetchpull-lifetime-realtime-success-count.png" "fetchpull-lifetime-small-success-count.png" "fetchpull-lifetime-bulk-success-count.png"))
  812. when : not : null? files
  813. when : file-exists? : first files
  814. copy-file : first files
  815. string-append path file-name-separator-string
  816. first files
  817. loop : cdr files
  818. sync
  819. define : ensure-directory-exists path
  820. cond
  821. : not : file-exists? path
  822. mkdir path
  823. : not : file-is-directory? path
  824. error 'system-error "Selected path ~A is no directory" path
  825. else path
  826. define : write-site-to path
  827. define filepath : string-append path file-name-separator-string "index.html"
  828. define port : open-output-file filepath
  829. display "<!doctype html>\n" port
  830. website-content port
  831. close port
  832. define : create-site path
  833. ensure-directory-exists path
  834. create-plot
  835. copy-resources-to path
  836. write-site-to path
  837. define : final-action? args
  838. if {(length args) >= 2}
  839. cond
  840. : equal? "--help" : second args
  841. help args
  842. . #t
  843. : equal? "--version" : second args
  844. format : current-output-port
  845. . "~a\n" version
  846. . #t
  847. : equal? "--test" : second args
  848. test
  849. . #t
  850. : equal? "--site" : second args
  851. create-site : if {(length args) >= 3} (third args) "site"
  852. . #t
  853. else #f
  854. . #f
  855. define : main args
  856. when : not : final-action? args
  857. when {(length args) >= 2}
  858. pretty-print : second args
  859. set! today : iso->time : second args
  860. ;; processor-put! printing-passthrough-processor
  861. let : (get-stats '()) (put-stats '())
  862. define : stats-get stat
  863. set! get-stats : append get-stats stat
  864. . stat
  865. define : stats-put stat
  866. set! put-stats : append put-stats stat
  867. . stat
  868. with-fcp-connection
  869. let loop
  870. : modes '(realtime small bulk)
  871. define days-before
  872. cons 0
  873. map : λ(x) : expt 2 x
  874. iota 10 ;; up to 2**9: 512 days
  875. define* : KSK-for-get days #:key (append "") (mode 'realtime)
  876. KSK-for-request (string-append (prefix) append) today days mode
  877. define* : KSK-for-put days #:key (append "") (mode 'realtime)
  878. KSK-for-insert (string-append (prefix) append) today days mode
  879. when : not : null? modes
  880. let : : mode : first modes
  881. format #t "collecting ~a statistics\n" mode
  882. stats-put
  883. time-put mode
  884. apply append
  885. map : λ(x) : map (λ (y) (KSK-for-put y #:append (number->string x) #:mode mode)) days-before
  886. iota 3
  887. stats-get
  888. time-get mode
  889. apply append
  890. map : λ(x) : map (λ (y) (KSK-for-get y #:append (number->string x) #:mode mode)) days-before
  891. iota 3
  892. loop : cdr modes
  893. format #t "Finished collecting statistics\n"
  894. ;; pretty-print get-stats
  895. ;; pretty-print put-stats
  896. let : (get-statsfile "fetchpull-stats-get.csv") (put-statsfile "fetchpull-stats-put.csv")
  897. stats->csv get-stats #:target-filename get-statsfile
  898. format #t "Finished writing get statistics to ~a\n" get-statsfile
  899. stats->csv put-stats #:target-filename put-statsfile
  900. format #t "Finished writing put statistics to ~a\n" put-statsfile