You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by da...@apache.org on 2016/05/13 18:14:15 UTC

[33/50] [abbrv] isis git commit: ISIS-1399: new MetricsService to capture metrics (objects loaded etc), capture within the Interaction.Execution.

ISIS-1399: new MetricsService to capture metrics (objects loaded etc), capture within the Interaction.Execution.


Project: http://git-wip-us.apache.org/repos/asf/isis/repo
Commit: http://git-wip-us.apache.org/repos/asf/isis/commit/4ce54bcf
Tree: http://git-wip-us.apache.org/repos/asf/isis/tree/4ce54bcf
Diff: http://git-wip-us.apache.org/repos/asf/isis/diff/4ce54bcf

Branch: refs/heads/master
Commit: 4ce54bcf6f52d2905d2e618dc68cfaca50a78428
Parents: d50731c
Author: Dan Haywood <da...@haywood-associates.co.uk>
Authored: Sat May 7 15:58:21 2016 +0100
Committer: Dan Haywood <da...@haywood-associates.co.uk>
Committed: Sat May 7 15:58:21 2016 +0100

----------------------------------------------------------------------
 .../src/main/asciidoc/schema/ixn/ixn-1.0.xsd    |   4 +-
 .../isis/applib/services/iactn/Interaction.java | 170 +++++++++++++------
 .../applib/services/metrics/MetricsService.java |  80 +++++++++
 .../isis/schema/utils/InteractionDtoUtils.java  |  10 --
 .../schema/utils/MemberExecutionDtoUtils.java   |  55 ++++++
 ...onInvocationFacetForDomainEventAbstract.java |  14 +-
 ...etterOrClearFacetForDomainEventAbstract.java |  23 ++-
 .../enlist/EnlistedObjectsServiceInternal.java  |   9 +-
 .../services/metrics/MetricsServiceDefault.java |  67 ++++++++
 .../system/persistence/PersistenceSession.java  |   8 +-
 .../transaction/IsisTransactionManager.java     |   6 +-
 .../apache/isis/schema/common/common-1.0.xsd    |  21 +++
 .../org/apache/isis/schema/ixn/ixn-1.0.xsd      |  48 +++++-
 13 files changed, 436 insertions(+), 79 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/isis/blob/4ce54bcf/adocs/documentation/src/main/asciidoc/schema/ixn/ixn-1.0.xsd
----------------------------------------------------------------------
diff --git a/adocs/documentation/src/main/asciidoc/schema/ixn/ixn-1.0.xsd b/adocs/documentation/src/main/asciidoc/schema/ixn/ixn-1.0.xsd
index 116d9ab..072825d 100644
--- a/adocs/documentation/src/main/asciidoc/schema/ixn/ixn-1.0.xsd
+++ b/adocs/documentation/src/main/asciidoc/schema/ixn/ixn-1.0.xsd
@@ -147,7 +147,7 @@
                     </xs:element>
                     <xs:element name="returned" type="returnDto" minOccurs="0" maxOccurs="1">
                         <xs:annotation>
-                            <xs:documentation>The value returned by this action (including the type of that returned value).  Either the 'returned' or the 'threw' element (from 'memberInteractionDto') will be populated.
+                            <xs:documentation>The value returned by this action (including the type of that returned value).  Either the 'returned' or the 'threw' element (from 'memberExecutionDto') will be populated.
                             </xs:documentation>
                         </xs:annotation>
                     </xs:element>
@@ -156,7 +156,7 @@
         </xs:complexContent>
     </xs:complexType>
 
-    <xs:complexType name="propertyModificationDto">
+    <xs:complexType name="propertyEditDto">
         <xs:complexContent>
             <xs:extension base="interactionExecutionDto">
                 <xs:sequence>

http://git-wip-us.apache.org/repos/asf/isis/blob/4ce54bcf/core/applib/src/main/java/org/apache/isis/applib/services/iactn/Interaction.java
----------------------------------------------------------------------
diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/iactn/Interaction.java b/core/applib/src/main/java/org/apache/isis/applib/services/iactn/Interaction.java
index dc59c2f..584bbb1 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/services/iactn/Interaction.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/iactn/Interaction.java
@@ -27,6 +27,8 @@ import java.util.UUID;
 import java.util.concurrent.Callable;
 import java.util.concurrent.atomic.AtomicInteger;
 
+import javax.inject.Inject;
+
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 
@@ -38,12 +40,17 @@ import org.apache.isis.applib.services.eventbus.AbstractDomainEvent;
 import org.apache.isis.applib.services.eventbus.ActionDomainEvent;
 import org.apache.isis.applib.services.eventbus.EventBusService;
 import org.apache.isis.applib.services.eventbus.PropertyDomainEvent;
+import org.apache.isis.applib.services.metrics.MetricsService;
 import org.apache.isis.applib.services.wrapper.WrapperFactory;
+import org.apache.isis.schema.common.v1.DifferenceDto;
 import org.apache.isis.schema.common.v1.InteractionType;
 import org.apache.isis.schema.common.v1.PeriodDto;
 import org.apache.isis.schema.ixn.v1.ActionInvocationDto;
 import org.apache.isis.schema.ixn.v1.MemberExecutionDto;
+import org.apache.isis.schema.ixn.v1.MetricsDto;
+import org.apache.isis.schema.ixn.v1.ObjectCountsDto;
 import org.apache.isis.schema.ixn.v1.PropertyEditDto;
+import org.apache.isis.schema.utils.MemberExecutionDtoUtils;
 import org.apache.isis.schema.utils.jaxbadapters.JavaSqlTimestampXmlGregorianCalendarAdapter;
 
 /**
@@ -112,48 +119,27 @@ public class Interaction implements HasTransactionId {
     }
 
 
-
     public Object execute(
             final MemberExecutor<ActionInvocation> memberExecutor,
-            final ActionInvocation actionInvocation,
-            final ClockService clockService,
-            final Command command) {
+            final ActionInvocation actionInvocation) {
 
-        pushAndUpdateCommand(actionInvocation, clockService, command);
+        push(actionInvocation);
 
-        return execute(memberExecutor, actionInvocation, clockService);
+        return executeInternal(memberExecutor, actionInvocation);
     }
 
     public Object execute(
-            final MemberExecutor<PropertyModification> memberExecutor,
-            final PropertyModification propertyModification,
-            final ClockService clockService,
-            final Command command) {
-
-        pushAndUpdateCommand(propertyModification, clockService, command);
-        return execute(memberExecutor, propertyModification, clockService);
-    }
-
+            final MemberExecutor<PropertyEdit> memberExecutor,
+            final PropertyEdit propertyEdit) {
 
-    private Execution pushAndUpdateCommand(
-            final Execution execution,
-            final ClockService clockService,
-            final Command command) {
+        push(propertyEdit);
 
-        final Timestamp startedAt = clockService.nowAsJavaSqlTimestamp();
-        push(startedAt, execution);
-
-        if(command.getStartedAt() == null) {
-            command.setStartedAt(startedAt);
-        }
-        return execution;
+        return executeInternal(memberExecutor, propertyEdit);
     }
 
-
-    private <T extends Execution> Object execute(
+    private <T extends Execution> Object executeInternal(
             final MemberExecutor<T> memberExecutor,
-            final T execution,
-            final ClockService clockService) {
+            final T execution) {
 
         // as a convenience, since in all cases we want the command to start when the first interaction executes,
         // we populate the command here.
@@ -203,7 +189,7 @@ public class Interaction implements HasTransactionId {
      * </p>
      */
     @Programmatic
-    private Execution push(final Timestamp startedAt, final Execution execution) {
+    private Execution push(final Execution execution) {
 
         if(currentExecution == null) {
             // new top-level execution
@@ -214,7 +200,6 @@ public class Interaction implements HasTransactionId {
             execution.setParent(currentExecution);
         }
 
-        execution.setStartedAt(startedAt);
 
         // update this.currentExecution and this.previousExecution
         moveCurrentTo(execution);
@@ -265,7 +250,7 @@ public class Interaction implements HasTransactionId {
     /**
      * <b>NOT API</b>: intended to be called only by the framework.
      *
-     * Clears the set of {@link Execution}s that may have been {@link #push(Timestamp, Execution)}ed.
+     * Clears the set of {@link Execution}s that may have been {@link #push(Execution)}ed.
      */
     @Programmatic
     public void clear() {
@@ -432,8 +417,7 @@ public class Interaction implements HasTransactionId {
         }
 
         public void setStartedAt(final Timestamp startedAt) {
-            this.startedAt = startedAt;
-            syncMetrics();
+            syncMetrics(When.BEFORE, startedAt);
         }
 
 
@@ -448,8 +432,7 @@ public class Interaction implements HasTransactionId {
          * <b>NOT API</b>: intended to be called only by the framework.
          */
         void setCompletedAt(final Timestamp completedAt) {
-            this.completedAt = completedAt;
-            syncMetrics();
+            syncMetrics(When.AFTER, completedAt);
         }
 
         //endregion
@@ -517,28 +500,103 @@ public class Interaction implements HasTransactionId {
          */
         public void setDto(final T executionDto) {
             this.dto = executionDto;
-            syncMetrics();
         }
 
         //endregion
 
         //region > helpers (syncMetrics)
-        private void syncMetrics() {
-            if (this.dto == null) {
-                return;
+
+        enum When {
+            BEFORE {
+                @Override
+                public void syncMetrics(
+                        final Execution<?, ?> execution,
+                        final Timestamp timestamp,
+                        final int numberObjectsLoaded,
+                        final int numberObjectsDirtied,
+                        final int numberObjectPropertiesModified) {
+
+                    execution.startedAt = timestamp;
+
+                    final MetricsDto metricsDto = metricsFor(execution);
+
+                    final PeriodDto periodDto = timingsFor(metricsDto);
+                    periodDto.setStartedAt(JavaSqlTimestampXmlGregorianCalendarAdapter.print(timestamp));
+
+                    final ObjectCountsDto objectCountsDto = objectCountsFor(metricsDto);
+                    numberObjectsLoadedFor(objectCountsDto).setBefore(numberObjectsLoaded);
+                    numberObjectsDirtiedFor(objectCountsDto).setBefore(numberObjectsDirtied);
+                    numberObjectPropertiesModifiedFor(objectCountsDto).setBefore(numberObjectPropertiesModified);
+                }
+
+            },
+            AFTER {
+                @Override public void syncMetrics(
+                        final Execution<?, ?> execution,
+                        final Timestamp timestamp,
+                        final int numberObjectsLoaded,
+                        final int numberObjectsDirtied,
+                        final int numberObjectPropertiesModified) {
+
+                    execution.completedAt = timestamp;
+
+                    final MetricsDto metricsDto = metricsFor(execution);
+
+                    final PeriodDto periodDto = timingsFor(metricsDto);
+                    periodDto.setCompletedAt(JavaSqlTimestampXmlGregorianCalendarAdapter.print(timestamp));
+
+                    final ObjectCountsDto objectCountsDto = objectCountsFor(metricsDto);
+                    numberObjectsLoadedFor(objectCountsDto).setAfter(numberObjectsLoaded);
+                    numberObjectsDirtiedFor(objectCountsDto).setAfter(numberObjectsDirtied);
+                    numberObjectPropertiesModifiedFor(objectCountsDto).setAfter(numberObjectPropertiesModified);
+
+                }
+
+            };
+
+            //region > helpers
+            private static DifferenceDto numberObjectPropertiesModifiedFor(final ObjectCountsDto objectCountsDto) {
+                return MemberExecutionDtoUtils.numberObjectPropertiesModifiedFor(objectCountsDto);
+            }
+
+            private static DifferenceDto numberObjectsDirtiedFor(final ObjectCountsDto objectCountsDto) {
+                return MemberExecutionDtoUtils.numberObjectsDirtiedFor(objectCountsDto);
+            }
+
+            private static DifferenceDto numberObjectsLoadedFor(final ObjectCountsDto objectCountsDto) {
+                return MemberExecutionDtoUtils.numberObjectsLoadedFor(objectCountsDto);
+            }
+
+            private static ObjectCountsDto objectCountsFor(final MetricsDto metricsDto) {
+                return MemberExecutionDtoUtils.objectCountsFor(metricsDto);
+            }
+
+            private static MetricsDto metricsFor(final Execution<?, ?> execution) {
+                return MemberExecutionDtoUtils.metricsFor(execution.dto);
             }
-            final PeriodDto periodDto = periodDtoFor(this.dto);
-            periodDto.setStartedAt(JavaSqlTimestampXmlGregorianCalendarAdapter.print(getStartedAt()));
-            periodDto.setCompletedAt(JavaSqlTimestampXmlGregorianCalendarAdapter.print(getCompletedAt()));
-        }
 
-        private static PeriodDto periodDtoFor(final MemberExecutionDto executionDto) {
-            PeriodDto timings = executionDto.getTimings();
-            if(timings == null) {
-                timings = new PeriodDto();
+            private static PeriodDto timingsFor(final MetricsDto metricsDto) {
+                return MemberExecutionDtoUtils.timingsFor(metricsDto);
             }
-            return timings;
+            //endregion
+
+            public abstract void syncMetrics(
+                    final Execution<?,?> teExecution,
+                    final Timestamp timestamp,
+                    final int numberObjectsLoaded,
+                    final int numberObjectsDirtied,
+                    final int numberObjectPropertiesModified);
+        }
+        private void syncMetrics(final When when, final Timestamp timestamp) {
+            final MetricsService metricsService = interaction.metricsService;
+
+            final int numberObjectsLoaded = metricsService.numberObjectsLoaded();
+            final int numberObjectsDirtied = metricsService.numberObjectsDirtied();
+            final int numberObjectPropertiesModified = metricsService.numberObjectPropertiesModified();
+
+            when.syncMetrics(this, timestamp, numberObjectsLoaded, numberObjectsDirtied, numberObjectPropertiesModified);
         }
+
         //endregion
 
     }
@@ -561,11 +619,11 @@ public class Interaction implements HasTransactionId {
         }
     }
 
-    public static class PropertyModification extends Execution<PropertyEditDto, PropertyDomainEvent<?,?>> {
+    public static class PropertyEdit extends Execution<PropertyEditDto, PropertyDomainEvent<?,?>> {
 
         private final Object newValue;
 
-        public PropertyModification(
+        public PropertyEdit(
                 final Interaction interaction,
                 final String memberId,
                 final Object target,
@@ -578,4 +636,12 @@ public class Interaction implements HasTransactionId {
             return newValue;
         }
     }
+
+
+    @Inject
+    MetricsService metricsService;
+
+    @Inject
+    ClockService clockService;
+
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/4ce54bcf/core/applib/src/main/java/org/apache/isis/applib/services/metrics/MetricsService.java
----------------------------------------------------------------------
diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/metrics/MetricsService.java b/core/applib/src/main/java/org/apache/isis/applib/services/metrics/MetricsService.java
new file mode 100644
index 0000000..4404418
--- /dev/null
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/metrics/MetricsService.java
@@ -0,0 +1,80 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.isis.applib.services.metrics;
+
+import java.sql.Timestamp;
+import java.util.UUID;
+
+import javax.enterprise.context.RequestScoped;
+import javax.jdo.listener.InstanceLifecycleEvent;
+
+import org.apache.isis.applib.annotation.Programmatic;
+import org.apache.isis.applib.services.bookmark.Bookmark;
+import org.apache.isis.applib.services.iactn.InteractionContext;
+import org.apache.isis.schema.ixn.v1.MemberExecutionDto;
+
+@RequestScoped
+public interface MetricsService {
+
+    /**
+     * The number of objects that have, so far in this request, been loaded from the database.
+     *
+     * <p>
+     *     Corresponds to the number of times that {@link javax.jdo.listener.LoadLifecycleListener#postLoad(InstanceLifecycleEvent)} is fired.
+     * </p>
+     *
+     * <p>
+     *     Is captured within {@link MemberExecutionDto#getMetrics()} (accessible from {@link InteractionContext#getInteraction()}).
+     * </p>
+     */
+    @Programmatic
+    int numberObjectsLoaded();
+
+    /**
+     * The number of objects that have, so far in this request, been dirtied/will need updating in the database); a
+     * good measure of the footprint of the interaction.
+     *
+     * <p>
+     *     Corresponds to the number of times that {@link javax.jdo.listener.DirtyLifecycleListener#preDirty(InstanceLifecycleEvent)} callback is fired.
+     * </p>
+     *
+     * <p>
+     *     Is captured within {@link MemberExecutionDto#getMetrics()} (accessible from {@link InteractionContext#getInteraction()}).
+     * </p>
+     */
+    @Programmatic
+    int numberObjectsDirtied();
+
+    /**
+     * The number of individual properties of objects that were modified; a good measure of the amount of work being done in the interaction.
+     * 
+     * <p>
+     *     Related to {@link #numberObjectsDirtied()}, corresponds to the number of times that {@link org.apache.isis.applib.services.audit.AuditingService3#audit(UUID, String, Bookmark, String, String, String, String, String, Timestamp)}  would be called if the transaction were to complete.
+     * </p>
+     * 
+     * <p>
+     *     Is captured within {@link MemberExecutionDto#getMetrics()} (accessible from {@link InteractionContext#getInteraction()}).
+     * </p>
+     */
+    @Programmatic
+    int numberObjectPropertiesModified();
+
+}
+
+

http://git-wip-us.apache.org/repos/asf/isis/blob/4ce54bcf/core/applib/src/main/java/org/apache/isis/schema/utils/InteractionDtoUtils.java
----------------------------------------------------------------------
diff --git a/core/applib/src/main/java/org/apache/isis/schema/utils/InteractionDtoUtils.java b/core/applib/src/main/java/org/apache/isis/schema/utils/InteractionDtoUtils.java
index 2ed9448..deaf447 100644
--- a/core/applib/src/main/java/org/apache/isis/schema/utils/InteractionDtoUtils.java
+++ b/core/applib/src/main/java/org/apache/isis/schema/utils/InteractionDtoUtils.java
@@ -45,7 +45,6 @@ import org.apache.isis.schema.cmd.v1.ParamDto;
 import org.apache.isis.schema.cmd.v1.ParamsDto;
 import org.apache.isis.schema.common.v1.InteractionType;
 import org.apache.isis.schema.common.v1.OidDto;
-import org.apache.isis.schema.common.v1.PeriodDto;
 import org.apache.isis.schema.common.v1.ValueDto;
 import org.apache.isis.schema.common.v1.ValueType;
 import org.apache.isis.schema.common.v1.ValueWithTypeDto;
@@ -334,15 +333,6 @@ public final class InteractionDtoUtils {
         return parametersFor(invocationDto).getParameter();
     }
 
-    private static PeriodDto timingsFor(final MemberExecutionDto executionDto) {
-        PeriodDto timings = executionDto.getTimings();
-        if(timings == null) {
-            timings = new PeriodDto();
-            executionDto.setTimings(timings);
-        }
-        return timings;
-    }
-
     //endregion
 
     //region > addParamArg

http://git-wip-us.apache.org/repos/asf/isis/blob/4ce54bcf/core/applib/src/main/java/org/apache/isis/schema/utils/MemberExecutionDtoUtils.java
----------------------------------------------------------------------
diff --git a/core/applib/src/main/java/org/apache/isis/schema/utils/MemberExecutionDtoUtils.java b/core/applib/src/main/java/org/apache/isis/schema/utils/MemberExecutionDtoUtils.java
index f70c2b8..844d468 100644
--- a/core/applib/src/main/java/org/apache/isis/schema/utils/MemberExecutionDtoUtils.java
+++ b/core/applib/src/main/java/org/apache/isis/schema/utils/MemberExecutionDtoUtils.java
@@ -29,7 +29,11 @@ import javax.xml.bind.Unmarshaller;
 import javax.xml.namespace.QName;
 import javax.xml.transform.stream.StreamSource;
 
+import org.apache.isis.schema.common.v1.DifferenceDto;
+import org.apache.isis.schema.common.v1.PeriodDto;
 import org.apache.isis.schema.ixn.v1.MemberExecutionDto;
+import org.apache.isis.schema.ixn.v1.MetricsDto;
+import org.apache.isis.schema.ixn.v1.ObjectCountsDto;
 
 public final class MemberExecutionDtoUtils {
 
@@ -63,4 +67,55 @@ public final class MemberExecutionDtoUtils {
         }
     }
 
+    public static MetricsDto metricsFor(final MemberExecutionDto executionDto) {
+        MetricsDto metrics = executionDto.getMetrics();
+        if(metrics == null) {
+            metrics = new MetricsDto();
+            executionDto.setMetrics(metrics);
+        }
+        return metrics;
+    }
+
+    public static PeriodDto timingsFor(final MetricsDto metricsDto) {
+        PeriodDto timings = metricsDto.getTimings();
+        if(timings == null) {
+            timings = new PeriodDto();
+            metricsDto.setTimings(timings);
+        }
+        return timings;
+    }
+
+    public static ObjectCountsDto objectCountsFor(final MetricsDto metricsDto) {
+        ObjectCountsDto objectCounts = metricsDto.getObjectCounts();
+        if(objectCounts == null) {
+            objectCounts = new ObjectCountsDto();
+            metricsDto.setObjectCounts(objectCounts);
+        }
+        return objectCounts;
+    }
+
+    public static DifferenceDto numberObjectsLoadedFor(final ObjectCountsDto objectCountsDto) {
+        DifferenceDto differenceDto = objectCountsDto.getNumberObjectsLoaded();
+        if(differenceDto == null) {
+            differenceDto = new DifferenceDto();
+            objectCountsDto.setNumberObjectsLoaded(differenceDto);
+        }
+        return differenceDto;
+    }
+    public static DifferenceDto numberObjectsDirtiedFor(final ObjectCountsDto objectCountsDto) {
+        DifferenceDto differenceDto = objectCountsDto.getNumberObjectsDirtied();
+        if(differenceDto == null) {
+            differenceDto = new DifferenceDto();
+            objectCountsDto.setNumberObjectsDirtied(differenceDto);
+        }
+        return differenceDto;
+    }
+    public static DifferenceDto numberObjectPropertiesModifiedFor(final ObjectCountsDto objectCountsDto) {
+        DifferenceDto differenceDto = objectCountsDto.getNumberObjectPropertiesModified();
+        if(differenceDto == null) {
+            differenceDto = new DifferenceDto();
+            objectCountsDto.setNumberObjectPropertiesModified(differenceDto);
+        }
+        return differenceDto;
+    }
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/4ce54bcf/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/invocation/ActionInvocationFacetForDomainEventAbstract.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/invocation/ActionInvocationFacetForDomainEventAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/invocation/ActionInvocationFacetForDomainEventAbstract.java
index bdaeb92..c900f17 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/invocation/ActionInvocationFacetForDomainEventAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/invocation/ActionInvocationFacetForDomainEventAbstract.java
@@ -21,6 +21,7 @@ package org.apache.isis.core.metamodel.facets.actions.action.invocation;
 
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
+import java.sql.Timestamp;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -211,6 +212,17 @@ public abstract class ActionInvocationFacetForDomainEventAbstract
                                         owningAction, targetAdapter, argumentAdapterList);
                         currentExecution.setDto(invocationDto);
 
+
+                        // set the startedAt (and update command if this is the top-most member execution)
+                        // (this isn't done within Interaction#execute(...) because it requires the DTO
+                        // to have been set on the current execution).
+                        final Timestamp startedAt = getClockService().nowAsJavaSqlTimestamp();
+                        execution.setStartedAt(startedAt);
+                        if(command.getStartedAt() == null) {
+                            command.setStartedAt(startedAt);
+                        }
+
+
                         // ... post the executing event
                         final ActionDomainEvent<?> event =
                                 domainEventHelper.postEventForAction(
@@ -274,7 +286,7 @@ public abstract class ActionInvocationFacetForDomainEventAbstract
             };
 
             // sets up startedAt and completedAt on the execution, also manages the execution call graph
-            interaction.execute(callable, execution, getClockService(), command);
+            interaction.execute(callable, execution);
 
             // handle any exceptions
             final Interaction.Execution<ActionInvocationDto, ?> priorExecution = interaction.getPriorExecution();

http://git-wip-us.apache.org/repos/asf/isis/blob/4ce54bcf/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/modify/PropertySetterOrClearFacetForDomainEventAbstract.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/modify/PropertySetterOrClearFacetForDomainEventAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/modify/PropertySetterOrClearFacetForDomainEventAbstract.java
index d6a3fbf..4661cfc 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/modify/PropertySetterOrClearFacetForDomainEventAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/modify/PropertySetterOrClearFacetForDomainEventAbstract.java
@@ -19,6 +19,8 @@
 
 package org.apache.isis.core.metamodel.facets.properties.property.modify;
 
+import java.sql.Timestamp;
+
 import com.google.common.base.Objects;
 
 import org.apache.isis.applib.services.clock.ClockService;
@@ -184,12 +186,12 @@ public abstract class PropertySetterOrClearFacetForDomainEventAbstract
             final Object target = ObjectAdapter.Util.unwrap(targetAdapter);
             final Object argValue = ObjectAdapter.Util.unwrap(newValueAdapter);
 
-            final Interaction.PropertyModification execution =
-                    new Interaction.PropertyModification(interaction, propertyId, target, argValue);
-            final Interaction.MemberExecutor<Interaction.PropertyModification> executor =
-                    new Interaction.MemberExecutor<Interaction.PropertyModification>() {
+            final Interaction.PropertyEdit execution =
+                    new Interaction.PropertyEdit(interaction, propertyId, target, argValue);
+            final Interaction.MemberExecutor<Interaction.PropertyEdit> executor =
+                    new Interaction.MemberExecutor<Interaction.PropertyEdit>() {
                         @Override
-                        public Object execute(final Interaction.PropertyModification currentExecution) {
+                        public Object execute(final Interaction.PropertyEdit currentExecution) {
 
                             try {
 
@@ -200,6 +202,15 @@ public abstract class PropertySetterOrClearFacetForDomainEventAbstract
                                 currentExecution.setDto(editDto);
 
 
+                                // set the startedAt (and update command if this is the top-most member execution)
+                                // (this isn't done within Interaction#execute(...) because it requires the DTO
+                                // to have been set on the current execution).
+                                final Timestamp startedAt = getClockService().nowAsJavaSqlTimestamp();
+                                execution.setStartedAt(startedAt);
+                                if(command.getStartedAt() == null) {
+                                    command.setStartedAt(startedAt);
+                                }
+
                                 // ... post the executing event
                                 final Object oldValue = getterFacet.getProperty(targetAdapter, interactionInitiatedBy);
                                 final Object newValue = ObjectAdapter.Util.unwrap(newValueAdapter);
@@ -247,7 +258,7 @@ public abstract class PropertySetterOrClearFacetForDomainEventAbstract
                     };
 
             // sets up startedAt and completedAt on the execution, also manages the execution call graph
-            interaction.execute(executor, execution, getClockService(), command);
+            interaction.execute(executor, execution);
 
             // handle any exceptions
             final Interaction.Execution priorExecution = interaction.getPriorExecution();

http://git-wip-us.apache.org/repos/asf/isis/blob/4ce54bcf/core/runtime/src/main/java/org/apache/isis/core/runtime/services/enlist/EnlistedObjectsServiceInternal.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/enlist/EnlistedObjectsServiceInternal.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/enlist/EnlistedObjectsServiceInternal.java
index 1839b6e..053dc86 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/enlist/EnlistedObjectsServiceInternal.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/enlist/EnlistedObjectsServiceInternal.java
@@ -251,6 +251,13 @@ public class EnlistedObjectsServiceInternal {
         return object != null? object.toString(): null;
     }
 
+    @Programmatic
+    public int numberObjectsDirtied() {
+        return changedObjectProperties.size();
+    }
 
-
+    @Programmatic
+    public int numberObjectPropertiesModified() {
+        return changedObjectProperties.size();
+    }
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/4ce54bcf/core/runtime/src/main/java/org/apache/isis/core/runtime/services/metrics/MetricsServiceDefault.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/metrics/MetricsServiceDefault.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/metrics/MetricsServiceDefault.java
new file mode 100644
index 0000000..889b887
--- /dev/null
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/metrics/MetricsServiceDefault.java
@@ -0,0 +1,67 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.isis.core.runtime.services.metrics;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.enterprise.context.RequestScoped;
+import javax.inject.Inject;
+import javax.jdo.listener.InstanceLifecycleEvent;
+import javax.jdo.listener.InstanceLifecycleListener;
+import javax.jdo.listener.LoadLifecycleListener;
+
+import org.apache.isis.applib.annotation.DomainService;
+import org.apache.isis.applib.annotation.NatureOfService;
+import org.apache.isis.applib.annotation.Programmatic;
+import org.apache.isis.applib.services.metrics.MetricsService;
+import org.apache.isis.core.runtime.services.enlist.EnlistedObjectsServiceInternal;
+
+@RequestScoped
+@DomainService(nature = NatureOfService.DOMAIN)
+public class MetricsServiceDefault implements MetricsService, InstanceLifecycleListener, LoadLifecycleListener {
+
+    private AtomicInteger numberLoaded = new AtomicInteger(0);
+
+    @Override
+    public int numberObjectsLoaded() {
+        return numberLoaded.get();
+    }
+
+    @Override
+    public int numberObjectsDirtied() {
+        return enlistedObjectsServiceInternal.numberObjectsDirtied();
+    }
+
+    @Override
+    public int numberObjectPropertiesModified() {
+        return enlistedObjectsServiceInternal.numberObjectPropertiesModified();
+    }
+
+    @Programmatic
+    @Override
+    public void postLoad(final InstanceLifecycleEvent event) {
+        numberLoaded.incrementAndGet();
+    }
+
+
+    @Inject
+    EnlistedObjectsServiceInternal enlistedObjectsServiceInternal;
+
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/4ce54bcf/core/runtime/src/main/java/org/apache/isis/core/runtime/system/persistence/PersistenceSession.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/system/persistence/PersistenceSession.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/persistence/PersistenceSession.java
index 7fe16c1..d1c1058 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/system/persistence/PersistenceSession.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/persistence/PersistenceSession.java
@@ -123,6 +123,7 @@ import org.apache.isis.core.runtime.persistence.query.PersistenceQueryFindAllIns
 import org.apache.isis.core.runtime.persistence.query.PersistenceQueryFindUsingApplibQueryDefault;
 import org.apache.isis.core.runtime.runner.opts.OptionHandlerFixtureAbstract;
 import org.apache.isis.core.runtime.services.enlist.EnlistedObjectsServiceInternal;
+import org.apache.isis.core.runtime.services.metrics.MetricsServiceDefault;
 import org.apache.isis.core.runtime.system.context.IsisContext;
 import org.apache.isis.core.runtime.system.persistence.adaptermanager.OidAdapterHashMap;
 import org.apache.isis.core.runtime.system.persistence.adaptermanager.PojoAdapterHashMap;
@@ -301,6 +302,9 @@ public class PersistenceSession implements
 
         initServices();
 
+        final MetricsServiceDefault metricsServiceDefault = servicesInjector.lookupService(MetricsServiceDefault.class);
+        persistenceManager.addInstanceLifecycleListener(metricsServiceDefault, (Class[])null);
+
         setState(State.OPEN);
     }
 
@@ -396,8 +400,6 @@ public class PersistenceSession implements
     //endregion
 
     //region > Injectable
-    //endregion
-
     @Override
     public void injectInto(final Object candidate) {
         if (AdapterManagerAware.class.isAssignableFrom(candidate.getClass())) {
@@ -417,6 +419,7 @@ public class PersistenceSession implements
             cast.setConfigurationService(this);
         }
     }
+    //endregion
 
     //region > QuerySubmitter impl, findInstancesInTransaction
 
@@ -510,6 +513,7 @@ public class PersistenceSession implements
         return oidMarshaller;
     }
 
+
     //endregion
 
     //region > State

http://git-wip-us.apache.org/repos/asf/isis/blob/4ce54bcf/core/runtime/src/main/java/org/apache/isis/core/runtime/system/transaction/IsisTransactionManager.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/system/transaction/IsisTransactionManager.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/transaction/IsisTransactionManager.java
index 23d759c..18be7e1 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/system/transaction/IsisTransactionManager.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/transaction/IsisTransactionManager.java
@@ -31,6 +31,7 @@ import org.apache.isis.applib.services.clock.ClockService;
 import org.apache.isis.applib.services.command.Command;
 import org.apache.isis.applib.services.command.CommandContext;
 import org.apache.isis.applib.services.command.spi.CommandService;
+import org.apache.isis.applib.services.factory.FactoryService;
 import org.apache.isis.applib.services.iactn.Interaction;
 import org.apache.isis.applib.services.iactn.InteractionContext;
 import org.apache.isis.core.commons.authentication.AuthenticationSession;
@@ -68,6 +69,7 @@ public class IsisTransactionManager implements SessionScopedComponent {
 
     private final ServicesInjector servicesInjector;
 
+    private final FactoryService factoryService;
     private final CommandContext commandContext;
     private final CommandService commandService;
 
@@ -101,6 +103,7 @@ public class IsisTransactionManager implements SessionScopedComponent {
         this.persistenceSession = persistenceSession;
         this.servicesInjector = servicesInjector;
 
+        this.factoryService = lookupService(FactoryService.class);
         this.commandContext = lookupService(CommandContext.class);
         this.commandService = lookupService(CommandService.class);
 
@@ -288,14 +291,13 @@ public class IsisTransactionManager implements SessionScopedComponent {
                 command = createCommand();
                 transactionId = UUID.randomUUID();
             }
-            final Interaction interaction = new Interaction();
+            final Interaction interaction = factoryService.instantiate(Interaction.class);
 
             initCommandAndInteraction(transactionId, command, interaction);
 
             commandContext.setCommand(command);
             interactionContext.setInteraction(interaction);
 
-
             initOtherApplibServicesIfConfigured();
 
             final MessageBroker messageBroker = MessageBroker.acquire(getAuthenticationSession());

http://git-wip-us.apache.org/repos/asf/isis/blob/4ce54bcf/core/schema/src/main/resources/org/apache/isis/schema/common/common-1.0.xsd
----------------------------------------------------------------------
diff --git a/core/schema/src/main/resources/org/apache/isis/schema/common/common-1.0.xsd b/core/schema/src/main/resources/org/apache/isis/schema/common/common-1.0.xsd
index ac55c8e..f38a9d4 100644
--- a/core/schema/src/main/resources/org/apache/isis/schema/common/common-1.0.xsd
+++ b/core/schema/src/main/resources/org/apache/isis/schema/common/common-1.0.xsd
@@ -129,6 +129,27 @@
         </xs:sequence>
     </xs:complexType>
 
+    <xs:complexType name="differenceDto">
+        <xs:annotation>
+            <xs:documentation>Captures a pair of numbers representing a difference.  Used for example to capture metrics (number objects modified before and after).
+            </xs:documentation>
+        </xs:annotation>
+        <xs:sequence>
+        </xs:sequence>
+        <xs:attribute name="before" type="xs:int">
+            <xs:annotation>
+                <xs:documentation>The initial quantity.
+                </xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="after" type="xs:int">
+            <xs:annotation>
+                <xs:documentation>The final quantity, once known.  The difference is therefore the computation of (after - before).
+                </xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+    </xs:complexType>
+
     <xs:complexType name="valueWithTypeDto">
         <xs:annotation>
             <xs:documentation>Captures both a value and its corresponding type.  Used for the return value of action invocations, and for the new value in property edits.

http://git-wip-us.apache.org/repos/asf/isis/blob/4ce54bcf/core/schema/src/main/resources/org/apache/isis/schema/ixn/ixn-1.0.xsd
----------------------------------------------------------------------
diff --git a/core/schema/src/main/resources/org/apache/isis/schema/ixn/ixn-1.0.xsd b/core/schema/src/main/resources/org/apache/isis/schema/ixn/ixn-1.0.xsd
index 32349de..024d349 100644
--- a/core/schema/src/main/resources/org/apache/isis/schema/ixn/ixn-1.0.xsd
+++ b/core/schema/src/main/resources/org/apache/isis/schema/ixn/ixn-1.0.xsd
@@ -101,9 +101,9 @@
                     </xs:documentation>
                 </xs:annotation>
             </xs:element>
-            <xs:element name="timings" type="common:periodDto">
+            <xs:element name="metrics" type="metricsDto">
                 <xs:annotation>
-                    <xs:documentation>Captures the time taken to perform the member interaction (invoke the action, or edit the property).
+                    <xs:documentation>Profiling metrics capturng the this time/number of objects affected as a result of performing this member interaction (invoke the action, or edit the property).
                     </xs:documentation>
                 </xs:annotation>
             </xs:element>
@@ -146,7 +146,7 @@
                     </xs:element>
                     <xs:element name="returned" type="common:valueWithTypeDto" minOccurs="0" maxOccurs="1">
                         <xs:annotation>
-                            <xs:documentation>The value returned by this action (including the type of that returned value).  Either the 'returned' or the 'threw' element (from 'memberInteractionDto') will be populated.
+                            <xs:documentation>The value returned by this action (including the type of that returned value).  Either the 'returned' or the 'threw' element (from 'memberExecutionDto') will be populated.
                             </xs:documentation>
                         </xs:annotation>
                     </xs:element>
@@ -165,6 +165,48 @@
         </xs:complexContent>
     </xs:complexType>
 
+    <xs:complexType name="metricsDto">
+        <xs:sequence>
+            <xs:element name="timings" type="common:periodDto">
+                <xs:annotation>
+                    <xs:documentation>The time taken to perform the member interaction (invoke the action, or edit the property).
+                    </xs:documentation>
+                </xs:annotation>
+            </xs:element>
+            <xs:element name="objectCounts" type="objectCountsDto">
+                <xs:annotation>
+                    <xs:documentation>How many objets were affected by the member interaction.
+                    </xs:documentation>
+                </xs:annotation>
+
+            </xs:element>
+        </xs:sequence>
+    </xs:complexType>
+
+    <xs:complexType name="objectCountsDto">
+        <xs:sequence>
+            <xs:element name="numberObjectsLoaded" type="common:differenceDto">
+                <xs:annotation>
+                    <xs:documentation>The number of objects loaded.
+                    </xs:documentation>
+                </xs:annotation>
+            </xs:element>
+            <xs:element name="numberObjectsDirtied" type="common:differenceDto">
+                <xs:annotation>
+                    <xs:documentation>The number of objects dirtied (ie updated).
+                    </xs:documentation>
+                </xs:annotation>
+            </xs:element>
+            <xs:element name="numberObjectPropertiesModified" type="common:differenceDto">
+                <xs:annotation>
+                    <xs:documentation>The number of individual properties of objects that were modified (eg as per individual calls to AuditingService).
+                    </xs:documentation>
+                </xs:annotation>
+            </xs:element>
+        </xs:sequence>
+
+    </xs:complexType>
+
     <xs:complexType name="exceptionDto">
         <xs:annotation>
             <xs:documentation>Captures any exception thrown by an action invocation.  Use as the xsd:type of the 'threw' element.