replay_sync_artifact.cljs 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. (ns replay-sync-artifact
  2. "Replay sync simulation artifacts with DataScript + checksum logic in nbb."
  3. (:require ["node:fs" :as fs]
  4. ["node:path" :as node-path]
  5. [babashka.cli :as cli]
  6. [clojure.string :as string]
  7. [datascript.core :as d]
  8. [logseq.db-sync.checksum :as sync-checksum]
  9. [logseq.db.frontend.schema :as db-schema]
  10. [nbb.core :as nbb]))
  11. (def cli-spec
  12. {:help {:alias :h
  13. :desc "Show help"}
  14. :artifact {:alias :a
  15. :desc "Path to artifact.json"
  16. :coerce :string}
  17. :client {:alias :c
  18. :desc "Only replay one client instance index"
  19. :coerce :long}
  20. :round {:alias :r
  21. :desc "Only replay one round index"
  22. :coerce :long}
  23. :pretty {:desc "Pretty-print JSON output"}})
  24. (def checksum-attr-map
  25. {"uuid" :block/uuid
  26. "title" :block/title
  27. "name" :block/name
  28. "parent" :block/parent
  29. "page" :block/page
  30. "order" :block/order
  31. ":block/uuid" :block/uuid
  32. ":block/title" :block/title
  33. ":block/name" :block/name
  34. ":block/parent" :block/parent
  35. ":block/page" :block/page
  36. ":block/order" :block/order
  37. "block/uuid" :block/uuid
  38. "block/title" :block/title
  39. "block/name" :block/name
  40. "block/parent" :block/parent
  41. "block/page" :block/page
  42. "block/order" :block/order})
  43. (def checksum-attrs (set (vals checksum-attr-map)))
  44. (defn usage []
  45. (str "Usage: yarn -s nbb-logseq -cp src:script:../db-sync/src script/replay_sync_artifact.cljs --artifact <path> [--client <n>] [--round <n>] [--pretty]\n"
  46. "Options:\n"
  47. (cli/format-opts {:spec cli-spec})))
  48. (defn parse-uuid
  49. [value]
  50. (when (string? value)
  51. (try
  52. (uuid value)
  53. (catch :default _
  54. nil))))
  55. (defn parse-int
  56. [value]
  57. (when-not (nil? value)
  58. (let [n (js/Number value)]
  59. (when (js/Number.isInteger n)
  60. (int n)))))
  61. (defn parse-attr
  62. [value]
  63. (cond
  64. (keyword? value)
  65. value
  66. (string? value)
  67. (let [trimmed (string/trim value)]
  68. (or (get checksum-attr-map trimmed)
  69. (get checksum-attr-map (string/lower-case trimmed))
  70. (when (string/starts-with? trimmed ":")
  71. (get checksum-attr-map (subs trimmed 1)))))
  72. :else
  73. nil))
  74. (defn snapshot-block->entity
  75. [block]
  76. (let [id (parse-int (get block "id"))
  77. uuid-value (parse-uuid (get block "uuid"))]
  78. (when (and id uuid-value)
  79. (cond-> {:db/id id
  80. :block/uuid uuid-value}
  81. (string? (get block "title"))
  82. (assoc :block/title (get block "title"))
  83. (string? (get block "name"))
  84. (assoc :block/name (get block "name"))
  85. (string? (get block "order"))
  86. (assoc :block/order (get block "order"))
  87. (parse-int (get block "parentId"))
  88. (assoc :block/parent (parse-int (get block "parentId")))
  89. (parse-int (get block "pageId"))
  90. (assoc :block/page (parse-int (get block "pageId")))))))
  91. (defn datom->tx
  92. [datom]
  93. (let [e (parse-int (get datom "e"))
  94. attr (parse-attr (get datom "a"))
  95. added? (not= false (get datom "added"))
  96. value (get datom "v")]
  97. (when (and e (contains? checksum-attrs attr))
  98. (let [value' (case attr
  99. :block/uuid (or (parse-uuid value) value)
  100. :block/parent (or (parse-int value) value)
  101. :block/page (or (parse-int value) value)
  102. value)]
  103. [(if added? :db/add :db/retract) e attr value']))))
  104. (defn replay-round
  105. [client round]
  106. (let [initial-db (get round "initialDb")
  107. blocks (if (map? initial-db) (get initial-db "blocks") nil)
  108. tx-log (-> (get round "txCapture") (get "txLog"))
  109. initial-entities (if (sequential? blocks)
  110. (keep snapshot-block->entity blocks)
  111. [])
  112. db0 (d/db-with (d/empty-db db-schema/schema) initial-entities)
  113. checksum0 (sync-checksum/recompute-checksum db0)
  114. replay-result
  115. (reduce
  116. (fn [{:keys [db checksum mismatches tx-count] :as acc} tx-entry]
  117. (let [datoms (if (map? tx-entry) (get tx-entry "datoms") nil)
  118. tx-data (if (sequential? datoms) (keep datom->tx datoms) [])
  119. op-index (when (map? tx-entry) (parse-int (get tx-entry "opIndex")))]
  120. (if (empty? tx-data)
  121. (assoc acc :tx-count (inc tx-count))
  122. (let [report (d/with db tx-data)
  123. db' (:db-after report)
  124. incremental (sync-checksum/update-checksum checksum report)
  125. full (sync-checksum/recompute-checksum db')]
  126. {:db db'
  127. :checksum incremental
  128. :tx-count (inc tx-count)
  129. :mismatches
  130. (cond-> mismatches
  131. (not= incremental full)
  132. (conj {:txIndex tx-count
  133. :opIndex op-index
  134. :incremental incremental
  135. :full full
  136. :txSize (count tx-data)}))}))))
  137. {:db db0
  138. :checksum checksum0
  139. :mismatches []
  140. :tx-count 0}
  141. (if (sequential? tx-log) tx-log []))]
  142. {:instanceIndex (parse-int (get client "instanceIndex"))
  143. :round (parse-int (get round "round"))
  144. :initialBlockCount (count initial-entities)
  145. :txCount (:tx-count replay-result)
  146. :mismatchCount (count (:mismatches replay-result))
  147. :firstMismatch (first (:mismatches replay-result))
  148. :finalChecksum (:checksum replay-result)}))
  149. (defn select-rounds
  150. [client opts]
  151. (let [rounds (if (sequential? (get client "rounds")) (get client "rounds") [])]
  152. (if-let [round-index (:round opts)]
  153. (filter #(= round-index (parse-int (get % "round"))) rounds)
  154. rounds)))
  155. (defn select-clients
  156. [artifact opts]
  157. (let [clients (if (sequential? (get artifact "clients")) (get artifact "clients") [])]
  158. (if-let [instance-index (:client opts)]
  159. (filter #(= instance-index (parse-int (get % "instanceIndex"))) clients)
  160. clients)))
  161. (defn -main
  162. [argv]
  163. (let [{:keys [opts]} (cli/parse-args argv {:spec cli-spec})
  164. artifact-path (:artifact opts)]
  165. (when (or (:help opts) (string/blank? artifact-path))
  166. (println (usage))
  167. (js/process.exit (if (:help opts) 0 1)))
  168. (let [resolved-path (.resolve node-path artifact-path)
  169. content (.readFileSync fs resolved-path "utf8")
  170. artifact (js->clj (js/JSON.parse content))
  171. selected-clients (vec (select-clients artifact opts))
  172. results (->> selected-clients
  173. (mapcat (fn [client]
  174. (map #(replay-round client %)
  175. (select-rounds client opts))))
  176. (vec))
  177. payload {:artifactPath resolved-path
  178. :runId (get artifact "runId")
  179. :createdAt (get artifact "createdAt")
  180. :selectedClient (or (:client opts) nil)
  181. :selectedRound (or (:round opts) nil)
  182. :resultCount (count results)
  183. :results results}]
  184. (if (:pretty opts)
  185. (println (js/JSON.stringify (clj->js payload) nil 2))
  186. (println (js/JSON.stringify (clj->js payload)))))))
  187. (when (= nbb/*file* (nbb/invoked-file))
  188. (-main *command-line-args*))