Collect layer 2/3 information when subs run

We can then use it for determining subscription layer level when subs
are created or destroyed.
This commit is contained in:
Daniel Compton 2018-01-29 00:26:56 +13:00
parent b87a584589
commit b62ed52847
5 changed files with 65 additions and 24 deletions

View File

@ -18,6 +18,7 @@ All notable changes to this project will be documented in this file. This change
* External windows not loading
* All app-db and subscription path expansions are now independent of each other [#134](https://github.com/Day8/re-frame-trace/issues/134).
* Layer 2/3 calculations are more accurate now. We now use the last seen layer level when a subscription runs, to inform it's layer level if it was created or destroyed.

View File

@ -1,43 +1,59 @@
This document briefly explains why `re-frame-trace` gives you an option to
ignore unchanged layer 2 subscriptions.
This document briefly explains why `re-frame-trace` gives you an option to
ignore unchanged layer 2 subscriptions.
### Background
The `re-frame` docs
The `re-frame` docs
[make a distinction](https://github.com/Day8/re-frame/blob/master/docs/SubscriptionInfographic.md)
between `layer 2` and `layer 3` subscriptions:
- `layer 2` subscriptions extract data directly from `app-db` and should be
trivial in nature. There should be no computation in them beyond
what is necessary to extract a value from `app-db`
- `layer 3` subscriptions take values from `layer 2` nodes as inputs, and
compute a materialised view of those values. Just to repeat: they never directly
- `layer 3` subscriptions take values from `layer 2` nodes as inputs, and
compute a materialised view of those values. Just to repeat: they never directly
extract values from `app-db`. They create new values where necessary, and because of it
they to do more serious CPU work. So we never want to run a
`layer 3` subscriptions unless it is necessary.
This structure delivers efficiency. You see, **all** (currently instantiated) `layer 2` subscriptions
`layer 3` subscriptions unless it is necessary.
This structure delivers efficiency. You see, **all** (currently instantiated) `layer 2` subscriptions
will run **every** time `app-db` changes in any way. All of them. Every time.
And `app-db` changes on almost every event, so we want them to be computationally
trivial.
And `app-db` changes on almost every event, so we want them to be computationally
trivial.
If the value of a `layer 2` subscription tests `=` to its previous value, then the further
propagation of values through the signal graph will be pruned.
The more computationally intensive `layer 3` subscriptions, and ultimately
the views, will only recompute if and when there has been a change in their data inputs.
We don't want your app recomputing views only to find that nothing has changed. Inefficient.
We don't want your app recomputing views only to find that nothing has changed. Inefficient.
### Back To Tracing
Because `layer 2` subs run on every single modification of `app-db`, and because
very often nothing has changed, their trace can be a bit noisy. Yes, it happened,
very often nothing has changed, their trace can be a bit noisy. Yes, it happened,
but it just isn't that interesting.
So `re-frame-trace` gives you the option of filtering out trace for
the `layer 2` subscriptions where the value "this time" is the same as the
So `re-frame-trace` gives you the option of filtering out trace for
the `layer 2` subscriptions where the value "this time" is the same as the
value "last time".
On the other hand, if a `layer 2` subscription runs and its value is
different to last time, that's potentially fascinating and you'll want to
be told all about it. :-)
On the other hand, if a `layer 2` subscription runs and its value is
different to last time, that's potentially fascinating and you'll want to
be told all about it. :-)
### Why do I sometimes see "Layer ?" when viewing a subscription?
To determine whether a subscription is a layer 2 or layer 3, re-frame-trace
looks at the input signals to a subscription. If one of the input signals is
app-db then the subscription is a layer 2 sub, otherwise it is a layer 3. If
a subscription hasn't run yet, then we can't know if it is a layer 2 or 3.
In almost all cases, a subscription will be created (by `(subscribe [:my-sub])`)
and run (by dereferencing the subscription) within the same epoch, providing
the layer level. If you see "Layer ?" this means that a subscription was created
but not used. This may indicate a bug in your application, although there are
cases where this is ok.
In most cases, after a few more epochs, that subscription will have run, and we
know it's layer level, and can use it for any subscriptions shown on any future
(and past) epochs.

View File

@ -510,10 +510,26 @@
(contains? events-to-ignore (first event)))) new-matches)
all-matches (reduce conj previous-matches new-matches)
retained-matches (into [] (take-last number-of-epochs-to-retain all-matches))
app-db-id (get-in db [:app-db :reagent-id])
subscription-info (->> new-traces
(filter metam/subscription-re-run?)
(reduce (fn [state trace]
;; TODO: can we take any shortcuts by assuming that a sub with
;; multiple input signals is a layer 3? I don't *think* so because
;; one of those input signals could be a naughty subscription to app-db
;; directly.
;; If any of the input signals are app-db, it is a layer 2 sub, else 3
(assoc-in state
[(:operation trace) :layer]
(if (some #(= app-db-id %) (get-in trace [:tags :input-signals]))
2
3)))
(get-in db [:epochs :subscription-info] {})))
first-id-to-retain (:id (ffirst retained-matches))
retained-traces (into [] (comp (drop-while #(< (:id %) first-id-to-retain))
(remove (fn [trace] (or (when drop-reagent (metam/low-level-reagent-trace? trace))
(when drop-re-frame (metam/low-level-re-frame-trace? trace)))))) all-traces)]
(remove (fn [trace]
(or (when drop-reagent (metam/low-level-reagent-trace? trace))
(when drop-re-frame (metam/low-level-re-frame-trace? trace)))))) all-traces)]
(-> db
(assoc-in [:traces :all-traces] retained-traces)
(update :epochs (fn [epochs]
@ -521,7 +537,8 @@
:matches retained-matches
:matches-by-id (into {} (map (juxt first-match-id identity)) retained-matches)
:match-ids (mapv first-match-id retained-matches)
:parse-state parse-state)))))
:parse-state parse-state
:subscription-info subscription-info)))))
;; Else
db)))

View File

@ -351,6 +351,12 @@
(fn [traces]
(filter metam/subscription? traces)))
(rf/reg-sub
:subs/subscription-info
:<- [:epochs/epoch-root]
(fn [epoch]
(:subscription-info epoch)))
(defn sub-sort-val
[sub]
(case (:type sub)
@ -375,7 +381,8 @@
:subs/all-subs
:<- [:subs/all-sub-traces]
:<- [:app-db/reagent-id]
(fn [[traces app-db-id]]
:<- [:subs/subscription-info]
(fn [[traces app-db-id sub-info]]
(let [raw (map (fn [trace] (let [pod-type (sub-op-type->type trace)
path-data (get-in trace [:tags :query-v])
;; TODO: detect layer 2/3 for sub/create and sub/destroy
@ -385,7 +392,7 @@
3)]
{:id (str pod-type (get-in trace [:tags :reaction]))
:type pod-type
:layer layer
:layer (get-in sub-info [(:operation trace) :layer])
:path-data path-data
:path (pr-str path-data)
:value (get-in trace [:tags :value])

View File

@ -133,7 +133,7 @@
:model path
:disabled? true]]]
[rc/gap-f :size common/gs-12s]
[rc/label :label (str "Layer " layer)]
[rc/label :label (str "Layer " (if (some? layer) layer "?"))]
;; TODO: capture previous sub run value and allow diffing it.
#_[rc/gap-f :size common/gs-12s]