diff --git a/cms/envs/common.py b/cms/envs/common.py
index 56d3fcc62ecddff6d169970016f431c328322340..434f534a275f7478ad71c0f105ccdec9beafabe2 100644
--- a/cms/envs/common.py
+++ b/cms/envs/common.py
@@ -241,10 +241,6 @@ XBLOCK_MIXINS = (LmsBlockMixin, CmsBlockMixin, InheritanceMixin, XModuleMixin)
 # xblocks can be added via advanced settings
 XBLOCK_SELECT_FUNCTION = prefer_xmodules
 
-############################ SIGNAL HANDLERS ################################
-# This is imported to register the exception signal handling that logs exceptions
-import monitoring.exceptions  # noqa
-
 ############################ DJANGO_BUILTINS ################################
 # Change DEBUG/TEMPLATE_DEBUG in your environment settings files, not here
 DEBUG = False
@@ -509,6 +505,9 @@ INSTALLED_APPS = (
     'django_openid_auth',
 
     'embargo',
+
+    # Monitoring signals
+    'monitoring',
 )
 
 
diff --git a/common/djangoapps/monitoring/signals.py b/common/djangoapps/monitoring/signals.py
new file mode 100644
index 0000000000000000000000000000000000000000..6068a2347ae4b0cb98be57a8c7ee4933d9e386d2
--- /dev/null
+++ b/common/djangoapps/monitoring/signals.py
@@ -0,0 +1,132 @@
+"""
+Add receivers for django signals, and feed data into the monitoring system.
+
+If a model has a class attribute 'METRIC_TAGS' that is a list of strings,
+those fields will be retrieved from the model instance, and added as tags to
+the recorded metrics.
+"""
+
+
+from django.db.models.signals import post_save, post_delete, m2m_changed, post_init
+from django.dispatch import receiver
+
+from dogapi import dog_stats_api
+
+
+def _database_tags(action, sender, kwargs):
+    """
+    Return a tags for the sender and database used in django.db.models signals.
+
+    Arguments:
+        action (str): What action is being performed on the db model.
+        sender (Model): What model class is the action being performed on.
+        kwargs (dict): The kwargs passed by the model signal.
+    """
+    tags = _model_tags(kwargs, 'instance')
+    tags.append(u'action:{}'.format(action))
+
+    if 'using' in kwargs:
+        tags.append(u'database:{}'.format(kwargs['using']))
+
+    return tags
+
+
+def _model_tags(kwargs, key):
+    """
+    Return a list of all tags for all attributes in kwargs[key].MODEL_TAGS,
+    plus a tag for the model class.
+    """
+    if key not in kwargs:
+        return []
+
+    instance = kwargs[key]
+    tags = [
+        u'{}.{}:{}'.format(key, attr, getattr(instance, attr))
+        for attr in getattr(instance, 'MODEL_TAGS', [])
+    ]
+    tags.append(u'model_class:{}'.format(instance.__class__.__name__))
+    return tags
+
+
+@receiver(post_init, dispatch_uid='edxapp.monitoring.post_init_metrics')
+def post_init_metrics(sender, **kwargs):
+    """
+    Record the number of times that django models are instantiated.
+
+    Args:
+        sender (Model): The model class sending the signals.
+        using (str): The name of the database being used for this initialization (optional).
+        instance (Model instance): The instance being initialized (optional).
+    """
+    tags = _database_tags('initialized', sender, kwargs)
+
+    dog_stats_api.increment('edxapp.db.model', tags=tags)
+
+
+@receiver(post_save, dispatch_uid='edxapp.monitoring.post_save_metrics')
+def post_save_metrics(sender, **kwargs):
+    """
+    Record the number of times that django models are saved (created or updated).
+
+    Args:
+        sender (Model): The model class sending the signals.
+        using (str): The name of the database being used for this update (optional).
+        instance (Model instance): The instance being updated (optional).
+    """
+    action = 'created' if kwargs.pop('created', False) else 'updated'
+
+    tags = _database_tags(action, sender, kwargs)
+    dog_stats_api.increment('edxapp.db.model', tags=tags)
+
+@receiver(post_delete, dispatch_uid='edxapp.monitoring.post_delete_metrics')
+def post_delete_metrics(sender, **kwargs):
+    """
+    Record the number of times that django models are deleted.
+
+    Args:
+        sender (Model): The model class sending the signals.
+        using (str): The name of the database being used for this deletion (optional).
+        instance (Model instance): The instance being deleted (optional).
+    """
+    tags = _database_tags('deleted', sender, kwargs)
+
+    dog_stats_api.increment('edxapp.db.model', tags=tags)
+
+
+@receiver(m2m_changed, dispatch_uid='edxapp.monitoring.m2m_changed_metrics')
+def m2m_changed_metrics(sender, **kwargs):
+    """
+    Record the number of times that Many2Many fields are updated. This is separated
+    from post_save and post_delete, because it's signaled by the database model in
+    the middle of the Many2Many relationship, rather than either of the models
+    that are the relationship participants.
+
+    Args:
+        sender (Model): The model class in the middle of the Many2Many relationship.
+        action (str): The action being taken on this Many2Many relationship.
+        using (str): The name of the database being used for this deletion (optional).
+        instance (Model instance): The instance whose many-to-many relation is being modified.
+        model (Model class): The model of the class being added/removed/cleared from the relation.
+    """
+    if 'action' not in kwargs:
+        return
+
+    action = {
+        'post_add': 'm2m.added',
+        'post_remove': 'm2m.removed',
+        'post_clear': 'm2m.cleared',
+    }.get(kwargs['action'])
+
+    if not action:
+        return
+
+    tags = _database_tags(action, sender, kwargs)
+
+    if 'model' in kwargs:
+        tags.append('target_class:{}'.format(kwargs['model'].__name__))
+
+    dog_stats_api.increment(
+        'edxapp.db.model',
+        value=len(kwargs.get('pk_set', [])),
+        tags=tags
+    )
diff --git a/common/djangoapps/monitoring/startup.py b/common/djangoapps/monitoring/startup.py
new file mode 100644
index 0000000000000000000000000000000000000000..6c3e73a18351c343854e55d78420051c083206aa
--- /dev/null
+++ b/common/djangoapps/monitoring/startup.py
@@ -0,0 +1,3 @@
+# Register signal handlers
+import signals
+import exceptions
\ No newline at end of file
diff --git a/common/djangoapps/student/models.py b/common/djangoapps/student/models.py
index 6138085540b5f83bf4d7744640ba512bf86d6609..c4fc45950daf2e4f4fa7adbd05b713c9b8a054f4 100644
--- a/common/djangoapps/student/models.py
+++ b/common/djangoapps/student/models.py
@@ -386,6 +386,8 @@ class CourseEnrollment(models.Model):
     checking course dates, user permissions, etc.) This logic is currently
     scattered across our views.
     """
+    MODEL_TAGS = ['course_id', 'is_active', 'mode']
+
     user = models.ForeignKey(User)
     course_id = models.CharField(max_length=255, db_index=True)
     created = models.DateTimeField(auto_now_add=True, null=True, db_index=True)
diff --git a/lms/djangoapps/courseware/models.py b/lms/djangoapps/courseware/models.py
index 201f617cfa862b3d60488fb3c21066aff77a2702..01a91c691ec14077e742bf9c1f34162ba0a38819 100644
--- a/lms/djangoapps/courseware/models.py
+++ b/lms/djangoapps/courseware/models.py
@@ -23,6 +23,8 @@ class StudentModule(models.Model):
     """
     Keeps student state for a particular module in a particular course.
     """
+    MODEL_TAGS = ['course_id', 'module_type']
+
     # For a homework problem, contains a JSON
     # object consisting of state
     MODULE_TYPES = (('problem', 'problem'),
diff --git a/lms/envs/common.py b/lms/envs/common.py
index e61ee8740d7f9d30152e09c892b46afaba15a824..2bfd89146431b9ce353480fa5e2d03b939e3bf8b 100644
--- a/lms/envs/common.py
+++ b/lms/envs/common.py
@@ -476,10 +476,6 @@ CODE_JAIL = {
 #   ]
 COURSES_WITH_UNSAFE_CODE = []
 
-############################ SIGNAL HANDLERS ################################
-# This is imported to register the exception signal handling that logs exceptions
-import monitoring.exceptions  # noqa
-
 ############################### DJANGO BUILT-INS ###############################
 # Change DEBUG/TEMPLATE_DEBUG in your environment settings files, not here
 DEBUG = False
@@ -1196,6 +1192,9 @@ INSTALLED_APPS = (
     'reverification',
 
     'embargo',
+
+    # Monitoring functionality
+    'monitoring',
 )
 
 ######################### MARKETING SITE ###############################