In this example, we will illustrate the use of:
by defining a simple profiler that prints the starting and ending time for processing a message sent to an object.
Messages as events¶
In a pure object-oriented system, all computations start by sending
messages to objects. We can thus define an event as the sending of a
message to an object. An event can then be specified by the tuple
(Object, Message, Sender). This definition can be refined by
interpreting the sending of a message and the return of the control to
the object that has sent the message as two distinct events. We call
these events respectively
after. Therefore, we end up
by representing an event by the tuple
(Event, Object, Message, Sender). For instance, if we send the
| ?- foo::bar(X). X = 1 yes
the two corresponding events will be:
(before, foo, bar(X), user) (after, foo, bar(1), user)
Note that the second event is only generated if the message succeeds. If
the message as a goal have multiple solutions, then one
will be generated for each solution.
Events are automatically generated by the message sending mechanisms for each public message sent using the ::/2 operator.
Profilers as monitors¶
A monitor is an object that reacts whenever a spied event occurs. The
monitor actions are defined by two event handlers:
and after/3 for
events. These predicates are automatically called by the message sending
mechanisms when an event registered for the monitor occurs. These event
handlers are declared as public predicates in the
In our example, we need a way to get the current time before and after
we process a message. We will assume that we have a
cpu_time/1 predicate that returns the current CPU
time for the Prolog session:
:- object(time). :- public(cpu_time/1). :- mode(cpu_time(-number), one). ... :- end_object.
Our profiler will be named
stop_watch. It must define event handlers
after events that will print the event
description (object, message, and sender) and the current time:
:- object(stop_watch, % event handler predicates protocol implements(monitoring)). :- uses(time, [cpu_time/1]). before(Object, Message, Sender) :- write(Object), write(' <-- '), writeq(Message), write(' from '), write(Sender), nl, write('STARTING at '), cpu_time(Seconds), write(Seconds), write(' seconds'), nl. after(Object, Message, Sender) :- write(Object), write(' <-- '), writeq(Message), write(' from '), write(Sender), nl, write('ENDING at '), cpu_time(Seconds), write(Seconds), write(' seconds'), nl. :- end_object.
After compiling and loading the
stop_watch object (and the objects
that we want to profile), we can use the define_events/5
built-in predicate to set up our profiler. For example, to profile all
messages that are sent to the object
foo, we need to call the goal:
| ?- define_events(_, foo, _, _, stop_watch). yes
This call will register
stop_watch as a monitor to all messages sent
foo, for both
after events. Note that
we say “as a monitor”, not “the monitor”: we can have any number of
monitors over the same events.
From now on, every time we sent a message to
monitor will print the starting and ending times for the message
execution. For instance:
| ?- foo::bar(X). foo <-- bar(X) from user STARTING at 12.87415 seconds foo <-- bar(1) from user ENDING at 12.87419 seconds X = 1 yes
To stop profiling the messages sent to
foo we use the
abolish_events/5 built-in predicate:
| ?- abolish_events(_, foo, _, _, stop_watch). yes
This call will abolish all events defined over the object
assigned to the
An event is defined as the sending of a (public) message to an object.
There are two kinds of events:
beforeevents, generated before a message is processed, and
afterevents, generated after the message processing completed successfully.
Any object can be declared as a monitor to any event. A monitor shall reference the
monitoringbuilt-in protocol in the object opening directive.