diff --git a/keystoneauth1/session.py b/keystoneauth1/session.py
index 63e826c0..e9c739b1 100644
--- a/keystoneauth1/session.py
+++ b/keystoneauth1/session.py
@@ -15,8 +15,10 @@ import functools
 import hashlib
 import json
 import logging
+import os
 import platform
 import socket
+import sys
 import time
 import uuid
 
@@ -93,6 +95,72 @@ class _StringFormatter(object):
         return value
 
 
+def _determine_calling_package():
+    """Walk the call frames trying to identify what is using this module."""
+    # Create a lookup table mapping file name to module name. The ``inspect``
+    # module does this but is far less efficient. Same story with the
+    # frame walking below.  One could use ``inspect.stack()`` but it
+    # has far more overhead.
+    mod_lookup = dict((m.__file__, n) for n, m in sys.modules.items()
+                      if hasattr(m, '__file__'))
+
+    # NOTE(shaleh): these are not useful because they hide the real
+    # user of the code. debtcollector did not import keystoneauth but
+    # it will show up in the call stack. Similarly we do not want to
+    # report ourselves or keystone client as the user agent. The real
+    # user is the code importing them.
+    ignored = ('debtcollector', 'keystoneauth1', 'keystoneclient')
+
+    i = 0
+    while True:
+        i += 1
+
+        try:
+            # NOTE(shaleh): this is safe in CPython but could break in
+            # other implementations of Python. Yes, the `inspect`
+            # module could be used instead. But it does a lot more
+            # work so it has worse performance.
+            f = sys._getframe(i)
+            try:
+                name = mod_lookup[f.f_code.co_filename]
+                # finds the full name module.foo.bar but all we need
+                # is the module name.
+                name, _, _ = name.partition('.')
+                if name not in ignored:
+                    return name
+            except KeyError:
+                pass  # builtin or the like
+        except ValueError:
+            # hit the bottom of the frame stack
+            break
+
+    return None
+
+
+def _determine_user_agent():
+    """Attempt to programatically generate a user agent string.
+
+    First, look at the name of the process. Return this unless it is in
+    the `ignored` list.  Otherwise, look at the function call stack and
+    try to find the name of the code that invoked this module.
+    """
+    # NOTE(shaleh): mod_wsgi is not any more useful than just
+    # reporting "keystoneauth". Ignore it and perform the package name
+    # heuristic.
+    ignored = ('mod_wsgi', )
+
+    try:
+        name = sys.argv[0]
+    except IndexError:
+        # sys.argv is empty, usually the Python interpreter prevents this.
+        return None
+
+    name = os.path.basename(name)
+    if name in ignored:
+        name = _determine_calling_package()
+    return name
+
+
 class Session(object):
     """Maintains client communication state and common functionality.
 
@@ -156,12 +224,16 @@ class Session(object):
         if timeout is not None:
             self.timeout = float(timeout)
 
-        # don't override the class variable if none provided
+        # Per RFC 7231 Section 5.5.3, identifiers in a user-agent should be
+        # ordered by decreasing significance.  If a user sets their product
+        # that value will be used. Otherwise we attempt to derive a useful
+        # product value. The value will be prepended it to the KSA version,
+        # requests version, and then the Python version.
+
+        if user_agent is None:
+            user_agent = _determine_user_agent()
+
         if user_agent is not None:
-            # Per RFC 7231 Section 5.5.3, identifiers in a user-agent
-            # should be ordered by decreasing significance.
-            # If a user sets their product, we prepend it to the KSA
-            # version, requests version, and then the Python version.
             self.user_agent = "%s %s" % (user_agent, DEFAULT_USER_AGENT)
 
         self._json = _JSONEncoder()
diff --git a/keystoneauth1/tests/unit/test_session.py b/keystoneauth1/tests/unit/test_session.py
index cc6bc799..8644fa11 100644
--- a/keystoneauth1/tests/unit/test_session.py
+++ b/keystoneauth1/tests/unit/test_session.py
@@ -90,6 +90,15 @@ class SessionTests(utils.TestCase):
         self.assertRequestBodyIs(json={'hello': 'world'})
 
     def test_user_agent(self):
+        session = client_session.Session()
+        self.stub_url('GET', text='response')
+        resp = session.get(self.TEST_URL)
+
+        self.assertTrue(resp.ok)
+        self.assertRequestHeaderEqual(
+            'User-Agent',
+            '%s %s' % ("run.py", client_session.DEFAULT_USER_AGENT))
+
         custom_agent = 'custom-agent/1.0'
         session = client_session.Session(user_agent=custom_agent)
         self.stub_url('GET', text='response')