Skip to content

Instantly share code, notes, and snippets.

@michalmarczyk
Created July 27, 2010 19:55
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save michalmarczyk/492764 to your computer and use it in GitHub Desktop.
Save michalmarczyk/492764 to your computer and use it in GitHub Desktop.
;;; See the inspirational SO question: http://stackoverflow.com/questions/3346382
(require 'clojure.contrib.trace)
(defn trace-ns
"Replaces each function from the given namespace with a version wrapped
in a tracing call. Can be undone with untrace-ns. ns should be a namespace
object or a symbol."
[ns]
(doseq [s (keys (ns-interns ns))
:let [v (ns-resolve ns s)]
:when (and (ifn? @v) (-> v meta :macro not))]
(intern ns
(with-meta s {:traced @v})
(let [f @v] (fn [& args]
(clojure.contrib.trace/trace (str "entering: " s))
(apply f args))))))
(defn untrace-ns
"Reverses the effect of trace-ns, replacing each traced function from the
given namespace with the original, untraced version."
[ns]
(doseq [s (keys (ns-interns ns))
:let [v (ns-resolve ns s)]
:when (:traced (meta v))]
(alter-meta! (intern ns s (:traced (meta v)))
dissoc :traced)))
@dcj
Copy link

dcj commented Feb 16, 2012

Is it possible that the third argument to intern in untrace-ns is supposed to be (meta @v) ???

@michalmarczyk
Copy link
Author

No, we want to get the metadata attached to the Var v by trace-ns
-- it holds the original value of v. (meta @v) would take the
metadata of the function stored in v.

The :traced metadata is on the Var by design; trace-ns &
untrace-ns are meant to be a facility dealing with namespaces &
their Vars, so it makes sense for them to keep their bookkeeping on
the Vars.

As an alternative, it would be possible to have trace-ns attach the
metadata to the traced function itself, in which case untrace-ns
would indeed use (meta @v), although I believe back when I wrote
this metadata support on functions was considered experimental (and
might still be so for all I know, I lost track of this issue). It also
wouldn't work with older versions of Clojure (< 1.2), which I probably
cared about at the time.

@dcj
Copy link

dcj commented Feb 16, 2012

OK, I think I understand your point. I believe I did put the metadata on the function itself, and therefore I needed
to retrieve it from there.

To back up a bit, I wanted to update/upgrade the tracing capability in a server side app I am developing.

I wanted to use clojure.tools.trace. It looks OK, but doesn't do everything I want.
I wanted to be able to take a previously defined function, and say "trace that function every time it used until I say otherwise", and I did not see how to
do that with the functions and macros provided by clojure.tools.trace. So I started playing around, and before long I found the stack overflow article
to which you contributed. Your comments there, and the gist above, were extremely useful to me. Before yesterday, I had never used metadata before,
and definitely did not know about ns-resolve, etc. I learned a lot yesterday, but I do not completely understand it all yet, but do have something working.

What I evolved to was deciding I wanted a function called trace-fn, and its argument would be a function I had previously defined.

For example, after I had done

(defn foo [] 3)

I could then do

(trace-fn foo)

While studying your code to help me understand how to do that, I decided that it might be nice to modify trace-fn to also accept the symbol to which a function was bound,
and to accomplish the same thing. That way, your trance-ns could just call trance-fn when it had found a function it wanted to trace.

So, here is what I have come up with:

[https://gist.github.com/1846993]

I finished this late last night, and I definitely have not tested the trace-ns and untrace-ns functions yet.

I would welcome any comments/feedback/criticism of my proposed approach....

Thanks

Don

@michalmarczyk
Copy link
Author

Once your comment reminded me of these functions, I thought maybe they'd be useful for clojure.tools.trace -- the discussion on Clojure Dev is viewable here. You could join us there if you'd be willing to send in a CA (or have already done so), otherwise we can discuss this on the regular Clojure group or here.

I've also prepared a new version of this, here's the new Gist. This also allows you to trace individual functions / Vars and seems to have come out as the most factored version so far. You seem to be using a different function for producing the actual tracing output, which reminds me that I was once meaning to switch to the nice indented trace printout style -- might this be the correct function to accomplish that? :-)

All the best,
Michał

@dcj
Copy link

dcj commented Feb 17, 2012 via email

@dcj
Copy link

dcj commented Feb 17, 2012

if you would like to contact me directly, and email sent to gmail user don dot jackson should do the trick :-)

@dcj
Copy link

dcj commented Feb 17, 2012

Not sure if I should respond here or on the new Gist. Decided to stay here for now...
I just looked through your new code, briefly. It looks really good!
I will need to study it in more detail, but I love the direction in which you are heading.

Somehow I discovered alter-var-root yesterday, and I used that in my code. I do not yet understand the difference between alter-var-root versus intern.
But if something is already interned, then it kind of feels like altering that thing might be a good way to go versus creating a new thing with the same name.
But I have no idea if this is a valid feeling. Any thoughts?

The other thing that I noticed when working on this yesterday was that when you obtain the metadata of a function, the namespace of that function is sitting right there! (as is its symbol). In functions like ns-resolve and intern where it is asking for the namespace, would it be better to just give it the namespace that is clearly associated with the original function, by obtaining that from the original metadata?

One underlying motivation behind both the above points is that there is a function that already exists. The goal of trace-fn/trace-var is to REPLACE the existing thing with something new, and later, to put it back EXACTLY as it was before (if possible). It seems like we doing that, but I'm just wondering if we are doing everything we can to do so....

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment