Skip to content

Latest commit

 

History

History
234 lines (210 loc) · 7.47 KB

Presentation.org

File metadata and controls

234 lines (210 loc) · 7.47 KB

Instrumenting Clojure Code

Introduction

Abstract

This presentation is about instrumenting Clojure code, that is how one goes about modifying an existing code base to monitor or evaluate its performance or behavior.

It looks at alter-var-root, a function of the Clojure runtime library that provides a canonical approach to instrumentation and it looks at figleaf, a Clojure library that provides a general purpose instrumentation library and an implementation of a code coverage facility.

Definition

In computer programming instrumentation is the activity of modifying an existing code base to monitor or evaluate its performance or behavior.

Use cases

Some of the use cases for instrumentation include:

  • code coverage
  • profiling
  • logging
  • other forms of statistical analysis

Code coverage, profiling and logging are some of the more well known use cases for instrumentation. It is possible to imagine others though. For example, a probe might be placed on a withdraw payment function in a banking application to observe and record unusually large withdrawals.

Static Instrumentation

  • Instrument at compile time
  • Requires special builds
  • Requires semantic knowledge of the language (parser technology)

Dynamic Instrumentation

  • Instrument at run-time or class-load time
  • Special build not required
  • Instrument live images
  • Remove instrumentation from live images
  • Instrumentation simple for Clojure and other languages where functions are first-class objects

Instrumentation from the REPL

john-popletts-computer:figleaf john$ lein repl
REPL started; server listening on localhost port 18864
user=> (def baz (fn [n] (* n n)))
#'user/baz
user=> (baz 8)
64
user=> baz
#<user$baz user$baz@1d417690>
user=> (def baz
	 (let [oldfn baz]
	   (fn [n] (do
		     (println "Hello baz!")
		     (oldfn n)))))
#'user/baz
user=> (baz 8)
Hello baz!
64

Instrumentation in Clojure

The alter-var-root Function

The REPL exercise above is not ideal. While it works, it is not atomic or thread-safe. The canonical method is to use the alter-var-root function.

To instrument a function, you need to take the body of an existing function, modify it in some way, and rebind the var for that function to the new modified function.

Instrumentation with alter-var-root

Here is almost the same example as above, taken from ClojureDocs, using alter-var-root:

(defn sqr [n]
  "Squares a number"
  (* n n))
user=> (sqr 5)
25
user=> (alter-var-root
	 (var sqr)                     ; var to alter
	 (fn [f]                       ; fn to apply to the var's value
	   (do (println "Squaring" %)  ; returns a new fn wrapping old fn
		(f %))))
user=> (sqr 5)
Squaring 5
25

Figleaf

Background

  • Instrumentation library
  • Minimalist code-coverage library layered on top
  • Derived from cl-figleaf, its Common Lisp predecessor

Some Requirements

With alt-var-root we have the rudimentary support we require to develop an instrumentation package. What are some candidate requirements?

  • Instrument a name space in one go
  • Unwind the instrumentation after we’re done

Figleaf’s instrument-function

(defn instrument-function [var-name pre post]
  (do
    (alter-var-root var-name
      (fn [function]
	(with-meta
	  (fn [& args]
	    (if pre (pre (str var-name) args))
	    (let [result (apply function args)]
	      (if post (post (str var-name) args))
	      result))
	  (assoc (meta function)
	    :figleaf/original function))))
    #(alter-var-root var-name (fn [function] (:figleaf/original (meta function))))))

Wrapper Macros

  • Commonly begin with a with- prefix (e.g. CL’s with-open-file or Clojure’s with-open)
  • Acquire a resource, set a condition or bind a var
  • Execute a form
  • Restore resource, original condition or value of a var
  • Guarantee restore even when bad things happen with unwind-protect (CL) or try/finally (Clojure)
  • See section on Wrapper Macro’s in Programming Clojure for more

Figleaf’s with-instrument-namespace

Figleaf implements with-instrument-namespace, a macro that takes a namespace and optional methods that are invoked before and after functions in the library.

with-instrument-namespace [ns pre post body]

In the tradition of with- macros, it instruments each public function in the namespace with the pre and post functions, executes the forms reprsented by “body” and then removes the instrumentation before returning.

The instrument-namespace function

A lot of work is performed with high-order Clojure functions by the relatively terse instrument-namespace function:

(defn instrument-namespace [namespace-under-test pre post]
  "Instrument a namespace. Wrap in docall is necessary to make sure call methods are instrumented
ahead of use."
  (doall
    (map
      #(instrument-function %1 pre post)
       (filter standard-fn?
	 (vals
	    (ns-publics namespace-under-test))))))

Putting everything back

(defn with-instrument-namespace-fn [ns pre post body]
  (let [restore-list (instrument-namespace ns pre post)
	restore #(doseq [restore-fn restore-list]
		   (restore-fn))]
    (try (body)
	 (finally (restore)))))

The with-instrument-namespace macro

(defmacro with-instrument-namespace [ns pre post & body]
  `(with-instrument-namespace-fn
    '~ns ~pre ~post (fn [] ~@body)))

Code Coverage

Introduction

Figleaf provides a run-tests method that instruments a namespace and executes unit tests on it. The current implementation assumes a one-to-one relationship between regular and unit test namespaces.

run-tests [namespace-under-test unit-test-namespace]

The run-tests macro

 (defmacro run-tests [namespace-under-test unit-test-namespace]
   `(do
     (with-instrument-namespace ~namespace-under-test increment-funcall-count nil
	(test/run-tests '~unit-test-namespace))
     (printf "CODE COVERAGE: Functions %d, Tested %d, Ratio %2.0f%%\n" (namespace-function-count)
	      (tested-function-count) (/ (tested-function-count) (namespace-function-count) 0.01))))

The Figleaf Leiningen task

Figleaf also implements a Leiningen plugin “lein-figleaf” that implements a “figleaf” task that runs figleaf code coverage in the context of a Leiningen project.

To install the plugin:

lein plugin install lein-figleaf 1.0.1-SNAPSHOT

Demonstration Run

lein figleaf html-template html-template.test

Testing html-template.test

Ran 7 tests containing 13 assertions.
0 failures, 0 errors.
CODE COVERAGE: Functions 27, Tested 23, Ratio 85%

Conclusion

Glitchettes

  • Try / catch recursion constraint
  • instrument-namespace doall fix
  • Backtick / quoting mischief

Resources

The source code for Figleaf is located on Github: https://github.com/John-Poplett/figleaf.