Liking cljdoc? Tell your friends :D

fieldtest.client

Running the arm the server chose, and reporting how it did.

A client is a map:

{:decisions {"asset-host" {:experiment "asset-host" :revision 3
                            :arm :cdn :exposure-id "6f1c..."}}
 :reporter  (in-process-reporter server)   ; or fieldtest.http/reporter
 :on-error  (fn [e] ...)}                  ; optional

Get :decisions once per scope with decide, then reuse them for every branch in that scope. An experiment is described by the same data the decide request lists:

(def asset-host {:experiment "asset-host" :revision 3 :arms [:default :cdn]})
Running the arm the server chose, and reporting how it did.

A client is a map:

    {:decisions {"asset-host" {:experiment "asset-host" :revision 3
                                :arm :cdn :exposure-id "6f1c..."}}
     :reporter  (in-process-reporter server)   ; or fieldtest.http/reporter
     :on-error  (fn [e] ...)}                  ; optional

Get `:decisions` once per scope with `decide`, then reuse them for every
`branch` in that scope. An experiment is described by the same data the
decide request lists:

    (def asset-host {:experiment "asset-host" :revision 3 :arms [:default :cdn]})
raw docstring

fieldtest.config

Reading and validating experiment configs.

A config source returns a map of experiment name to config, like this:

{"asset-host" {:revision 3 :weights {:cdn 20} :sticky :hashed}}

Input may be JSON-shaped (string keys, string arms and modes). normalize turns it into the shape above, with keyword arms and modes, and drops every experiment that is invalid.

Every experiment has a :default arm, the code that runs today. It has no weight; it gets every unit that isn't drawn into one of the weighted candidate arms.

Reading and validating experiment configs.

A config source returns a map of experiment name to config, like this:

    {"asset-host" {:revision 3 :weights {:cdn 20} :sticky :hashed}}

Input may be JSON-shaped (string keys, string arms and modes). `normalize`
turns it into the shape above, with keyword arms and modes, and drops every
experiment that is invalid.

Every experiment has a `:default` arm, the code that runs today. It has no
weight; it gets every unit that isn't drawn into one of the weighted
candidate arms.
raw docstring

fieldtest.hash

32-bit FNV-1a over the UTF-8 bytes of a string. The JVM, ClojureScript and nbb implementations have to agree, and the selector test vectors check that they do.

32-bit FNV-1a over the UTF-8 bytes of a string. The JVM, ClojureScript and
nbb implementations have to agree, and the selector test vectors check
that they do.
raw docstring

fieldtest.http

HTTP transport for decide requests and feedback.

Each request and response is a JSON document with the same fields as the in-process version, plus "v": 1. Later versions will only add fields, so readers skip fields they don't recognise. A server answers 400 to a version it doesn't know.

The handlers take {:body ...} and return a promise of {:status :headers :body}, which makes them easy to mount in Ring, http-kit or anything else. They don't do authentication, rate limiting or routing; wrap them in whatever your server already uses.

HTTP transport for decide requests and feedback.

Each request and response is a JSON document with the same fields as the
in-process version, plus `"v": 1`. Later versions will only add fields, so
readers skip fields they don't recognise. A server answers 400 to a version
it doesn't know.

The handlers take `{:body ...}` and return a promise of `{:status :headers
:body}`, which makes them easy to mount in Ring, http-kit or anything else.
They don't do authentication, rate limiting or routing; wrap them in
whatever your server already uses.
raw docstring

fieldtest.ids

Random exposure ids. They don't encode the arm, sticky key or experiment, so they can be logged freely.

Random exposure ids. They don't encode the arm, sticky key or experiment,
so they can be logged freely.
raw docstring

fieldtest.json

JSON encoding for the HTTP transport and for decision handoff.

Uses cheshire on the JVM and js/JSON in ClojureScript and nbb. cheshire is required only inside the :clj branch so that ClojureScript builds and nbb don't load it, and bb lint:no-infra checks this.

Decoding keywordizes object keys. Keywords encode as their full name (:a/b -> "a/b") and insts as ISO-8601 strings with milliseconds.

JSON encoding for the HTTP transport and for decision handoff.

Uses cheshire on the JVM and `js/JSON` in ClojureScript and nbb. cheshire is
required only inside the `:clj` branch so that ClojureScript builds and nbb
don't load it, and `bb lint:no-infra` checks this.

Decoding keywordizes object keys. Keywords encode as their full name
(`:a/b` -> "a/b") and insts as ISO-8601 strings with milliseconds.
raw docstring

fieldtest.memory

In-memory plug-ins for tests and local development. Records are kept in an atom and are lost when the process exits.

In-memory plug-ins for tests and local development. Records are kept in an
atom and are lost when the process exits.
raw docstring

fieldtest.records

Constructors and validation for the two kinds of record a recorder gets.

An exposure is written by the server each time it makes a decision, and holds the arm, revision, sticky key and context. Feedback is a measurement the client reports later, and it refers to one or more exposures by id. To find out which arm a measurement came from, join it to its exposure.

Constructors and validation for the two kinds of record a recorder gets.

An exposure is written by the server each time it makes a decision, and
holds the arm, revision, sticky key and context. Feedback is a measurement
the client reports later, and it refers to one or more exposures by id. To
find out which arm a measurement came from, join it to its exposure.
raw docstring

fieldtest.selector

The built-in selector, which splits units between arms by weight.

A selector is a function of one map and returns an arm keyword (or a promise of one):

{:experiment     "asset-host"
 :config         {:revision 3 :weights {:cdn 20} :sticky :hashed}
 :sticky-key     "user:8f3a"      ; may be nil
 :supported-arms #{:default :cdn}
 :context        {...}}            ; the decide request's context

Units fall into a space of 10,000 buckets. Each candidate arm, ordered by name, owns a fixed slice of 10,000/n buckets and draws the first weight*100 of them. Changing one arm's weight only moves units between that arm and :default. Adding or removing a candidate changes every slice, so do that with a new revision.

The built-in selector, which splits units between arms by weight.

A selector is a function of one map and returns an arm keyword (or a
promise of one):

    {:experiment     "asset-host"
     :config         {:revision 3 :weights {:cdn 20} :sticky :hashed}
     :sticky-key     "user:8f3a"      ; may be nil
     :supported-arms #{:default :cdn}
     :context        {...}}            ; the decide request's context

Units fall into a space of 10,000 buckets. Each candidate arm, ordered by
name, owns a fixed slice of 10,000/n buckets and draws the first
weight*100 of them. Changing one arm's weight only moves units between that
arm and `:default`. Adding or removing a candidate changes every slice, so do that
with a new revision.
raw docstring

fieldtest.server

Deciding arms for clients and accepting their feedback.

A server is a map built from plug-ins. You can call it in-process, put the handlers in fieldtest.http in front of it, or both. Each plug-in is a plain function that may return a value or a promise:

  • :config-source (fn [] configs) returns the current experiment configs (see fieldtest.config).
  • :selector (fn [{:keys [experiment config sticky-key supported-arms context]}] arm) chooses an arm for one experiment. Defaults to the built-in fieldtest.selector/weighted-hash.
  • :recorder (fn [kind record]) receives every record, where kind is :exposure or :feedback (see fieldtest.records).
  • :error-hook (fn [{:keys [plugin error experiment]}]) receives every plug-in failure. Optional.

Plug-in errors don't make a decide request fail. If the config source fails, the last good configs stay in use. If the selector fails, the experiment gets :default. If the recorder fails, the record is dropped. In each case the error is passed to the error hook.

Deciding arms for clients and accepting their feedback.

A server is a map built from plug-ins. You can call it in-process, put the
handlers in `fieldtest.http` in front of it, or both. Each plug-in is a
plain function that may return a value or a promise:

- `:config-source` `(fn [] configs)` returns the current experiment configs
  (see `fieldtest.config`).
- `:selector` `(fn [{:keys [experiment config sticky-key supported-arms context]}] arm)`
  chooses an arm for one experiment. Defaults to the built-in
  `fieldtest.selector/weighted-hash`.
- `:recorder` `(fn [kind record])` receives every record, where `kind` is
  `:exposure` or `:feedback` (see `fieldtest.records`).
- `:error-hook` `(fn [{:keys [plugin error experiment]}])` receives every
  plug-in failure. Optional.

Plug-in errors don't make a decide request fail. If the config source fails,
the last good configs stay in use. If the selector fails, the experiment
gets `:default`. If the recorder fails, the record is dropped. In each case
the error is passed to the error hook.
raw docstring

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
←Move to previous article
→Move to next article
Ctrl+/Jump to the search field
× close