Repository: jupyterhub/jupyterhub

Issue:
Add server_stop_duration_seconds prometheus metric
We use [prometheus](https://github.com/prometheus/client_python) to expose metrics about JupyterHub operations. Currently we expose HTTP request stats and time it takes to spawn servers. We should expose more metrics!

One useful metric is 'time taken for server to stop'. It should be a [histogram](https://github.com/prometheus/client_python#histogram) metric. We should observe the timing whenever a server is stopped. This is similar to the SERVER_SPAWN_DURATION_SECONDS metric, which is a histogram that observes the amount of time servers take to start.

- [ ] Declare the metric in metrics.py
- [ ] Observe time it takes for server to stop


Discussion:
I'd love to take a wack at this
@glenak1911 go for it! Feel free to ask for help here or on gitter.im/jupyterhub/jupyterhub.
hi @yuvipanda @minrk. I would like to contribute as an Outreachy applicant to jupyterhub project 1 "Highly available JupyterHub proxy with Traefik".Is this issue meant for Outreachy applicants ?  Can I get started on this one ?
@tuhina2020 welcome! yes, this is meant for outreachy applicants. Please feel free to get started.

Resolving patch:
diff --git a/jupyterhub/handlers/base.py b/jupyterhub/handlers/base.py
--- a/jupyterhub/handlers/base.py
+++ b/jupyterhub/handlers/base.py
@@ -33,7 +33,7 @@
     SERVER_SPAWN_DURATION_SECONDS, ServerSpawnStatus,
     PROXY_ADD_DURATION_SECONDS, ProxyAddStatus,
     SERVER_POLL_DURATION_SECONDS, ServerPollStatus,
-    RUNNING_SERVERS
+    RUNNING_SERVERS, SERVER_STOP_DURATION_SECONDS, ServerStopStatus
 )
 
 # pattern for the authentication token header
@@ -889,18 +889,25 @@ async def stop():
             2. stop the server
             3. notice that it stopped
             """
-            tic = IOLoop.current().time()
+            tic = time.perf_counter()
             try:
                 await self.proxy.delete_user(user, server_name)
                 await user.stop(server_name)
+                toc = time.perf_counter()
+                self.log.info("User %s server took %.3f seconds to stop", user.name, toc - tic)
+                self.statsd.timing('spawner.stop', (toc - tic) * 1000)
+                RUNNING_SERVERS.dec()
+                SERVER_STOP_DURATION_SECONDS.labels(
+                    status=ServerStopStatus.success
+                ).observe(toc - tic)
+            except:
+                SERVER_STOP_DURATION_SECONDS.labels(
+                    status=ServerStopStatus.failure
+                ).observe(time.perf_counter() - tic)
             finally:
                 spawner._stop_future = None
                 spawner._stop_pending = False
 
-            toc = IOLoop.current().time()
-            self.log.info("User %s server took %.3f seconds to stop", user.name, toc - tic)
-            self.statsd.timing('spawner.stop', (toc - tic) * 1000)
-            RUNNING_SERVERS.dec()
 
         future = spawner._stop_future = asyncio.ensure_future(stop())
 
diff --git a/jupyterhub/metrics.py b/jupyterhub/metrics.py
--- a/jupyterhub/metrics.py
+++ b/jupyterhub/metrics.py
@@ -112,11 +112,29 @@ def from_status(cls, status):
             return cls.running
         return cls.stopped
 
+for s in ServerPollStatus:
+    SERVER_POLL_DURATION_SECONDS.labels(status=s)
+
+
+
+SERVER_STOP_DURATION_SECONDS = Histogram(
+    'server_stop_seconds',
+    'time taken for server stopping operation',
+    ['status'],
+)
+
+class ServerStopStatus(Enum):
+    """
+    Possible values for 'status' label of SERVER_STOP_DURATION_SECONDS
+    """
+    success = 'success'
+    failure = 'failure'
+
     def __str__(self):
         return self.value
 
-for s in ServerPollStatus:
-    SERVER_POLL_DURATION_SECONDS.labels(status=s)
+for s in ServerStopStatus:
+    SERVER_STOP_DURATION_SECONDS.labels(status=s)
 
 
 def prometheus_log_method(handler):


Test patch: