[SEBA-284] Pusblishing prometheus counter for gRPC endpoints

Change-Id: I7143b301227b9961363754e83d7ff9826aae6920
diff --git a/.gitignore b/.gitignore
index 7da3feb..23ed2a9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -44,3 +44,7 @@
 lib/xos-config/cover/
 lib/xos-genx/cover/
 .vscode
+
+lib/xos-kafka/build/
+lib/xos-kafka/dist/
+lib/xos-kafka/xoskafka.egg-info/
\ No newline at end of file
diff --git a/VERSION b/VERSION
index d302656..f316ecb 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2.1.18
+2.1.19
diff --git a/containers/xos/Dockerfile.client b/containers/xos/Dockerfile.client
index 3b29b0d..19d6163 100644
--- a/containers/xos/Dockerfile.client
+++ b/containers/xos/Dockerfile.client
@@ -13,7 +13,7 @@
 # limitations under the License.
 
 # xosproject/xos-client
-FROM xosproject/xos-libraries:2.1.18
+FROM xosproject/xos-libraries:2.1.19
 
 # Install XOS client
 COPY xos/xos_client /tmp/xos_client
diff --git a/containers/xos/Dockerfile.libraries b/containers/xos/Dockerfile.libraries
index 364f44a..d3808f1 100644
--- a/containers/xos/Dockerfile.libraries
+++ b/containers/xos/Dockerfile.libraries
@@ -12,7 +12,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-FROM xosproject/xos-base:2.1.18
+# xosproject/xos-libraries
+FROM xosproject/xos-base:2.1.19
 
 # Add libraries
 COPY lib /opt/xos/lib
diff --git a/containers/xos/Dockerfile.synchronizer-base b/containers/xos/Dockerfile.synchronizer-base
index 9f4a785..7caf211 100644
--- a/containers/xos/Dockerfile.synchronizer-base
+++ b/containers/xos/Dockerfile.synchronizer-base
@@ -13,7 +13,7 @@
 # limitations under the License.
 
 # xosproject/xos-synchronizer-base
-FROM xosproject/xos-client:2.1.18
+FROM xosproject/xos-client:2.1.19
 
 COPY xos/synchronizers/new_base /opt/xos/synchronizers/new_base
 COPY xos/xos/logger.py /opt/xos/xos/logger.py
diff --git a/containers/xos/Dockerfile.xos-core b/containers/xos/Dockerfile.xos-core
index f9c8ae3..0edef02 100644
--- a/containers/xos/Dockerfile.xos-core
+++ b/containers/xos/Dockerfile.xos-core
@@ -13,7 +13,7 @@
 # limitations under the License.
 
 # xosproject/xos-core
-FROM xosproject/xos-libraries:2.1.18
+FROM xosproject/xos-libraries:2.1.19
 
 # Install XOS
 ADD xos /opt/xos
diff --git a/containers/xos/Makefile b/containers/xos/Makefile
new file mode 100644
index 0000000..9b893b7
--- /dev/null
+++ b/containers/xos/Makefile
@@ -0,0 +1,53 @@
+# Copyright 2017 the original author or authors.
+#
+# Licensed 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.
+
+
+# Container are build via the `make build` commands.
+
+# Optional parameters are:
+# `REGISTRY=192.168.99.100:3000/ REPOSITORY=xosproject/ DOCKER_BUILD_ARGS="--no-cache" TAG=dev make build`
+
+ifeq ($(TAG),)
+TAG := candidate
+endif
+
+ifeq ($(REPOSITORY),)
+REPOSITORY := xosproject/
+endif
+
+summary:
+	@echo Building images with:
+	@echo "    Build args:    $(DOCKER_BUILD_ARGS)"
+	@echo "    Registry:      ${REGISTRY}"
+	@echo "    Repository:    ${REPOSITORY}"
+	@echo "    Tag:           ${TAG}"
+
+build: summary xos-base xos-libraries xos-client xos-core xos-synchronizer-base
+
+xos-base:
+	docker build $(DOCKER_BUILD_ARGS) -t ${REGISTRY}${REPOSITORY}xos-base:${TAG} -f Dockerfile.base .
+
+xos-libraries:
+	docker build $(DOCKER_BUILD_ARGS) -t ${REGISTRY}${REPOSITORY}xos-libraries:${TAG} -f Dockerfile.libraries ../..
+
+xos-client:
+	rm -rf tmp.chameleon
+	cp -R ../../../../component/chameleon tmp.chameleon
+	docker build $(DOCKER_BUILD_ARGS) -t ${REGISTRY}${REPOSITORY}xos-client:${TAG} -f Dockerfile.client ../..
+
+xos-core:
+	docker build $(DOCKER_BUILD_ARGS) -t ${REGISTRY}${REPOSITORY}xos-core:${TAG} -f Dockerfile.xos-core ../..
+
+xos-synchronizer-base:
+	docker build $(DOCKER_BUILD_ARGS) -t ${REGISTRY}${REPOSITORY}xos-synchronizer-base:${TAG} -f Dockerfile.synchronizer-base ../..
diff --git a/containers/xos/pip_requested.txt b/containers/xos/pip_requested.txt
index 60b06b0..a4a7825 100644
--- a/containers/xos/pip_requested.txt
+++ b/containers/xos/pip_requested.txt
@@ -30,6 +30,7 @@
 ply==3.11
 plyxproto==3.1.0
 protobuf==3.5.2
+prometheus_client==0.4.0
 # Avoids a warning, see http://initd.org/psycopg/docs/faq.html#faq-compile
 psycopg2==2.7.4 --no-binary psycopg2
 pyOpenSSL==17.5.0
diff --git a/containers/xos/pip_requirements.txt b/containers/xos/pip_requirements.txt
index dce794e..49cc324 100644
--- a/containers/xos/pip_requirements.txt
+++ b/containers/xos/pip_requirements.txt
@@ -88,6 +88,7 @@
 ply==3.11
 plyxproto==3.1.0
 prettytable==0.7.2
+prometheus-client==0.4.0
 protobuf==3.5.2
 # Avoids a warning, see http://initd.org/psycopg/docs/faq.html#faq-compile
 psycopg2==2.7.4 --no-binary psycopg2
diff --git a/lib/xos-genx/xosgenx/targets/grpc_api.xtarget b/lib/xos-genx/xosgenx/targets/grpc_api.xtarget
index dffc347..4abf3dc 100644
--- a/lib/xos-genx/xosgenx/targets/grpc_api.xtarget
+++ b/lib/xos-genx/xosgenx/targets/grpc_api.xtarget
@@ -2,10 +2,12 @@
 import time
 from protos import xos_pb2, xos_pb2_grpc
 from google.protobuf.empty_pb2 import Empty
+from apistats import track_request_time, REQUEST_COUNT
 
 from django.contrib.auth import authenticate as django_authenticate
 from xos.exceptions import *
 from apihelper import XOSAPIHelperMixin, translate_exceptions
+import grpc
 
 class XosService(xos_pb2_grpc.xosServicer, XOSAPIHelperMixin):
     def __init__(self, thread_pool):
@@ -17,41 +19,59 @@
 
 {% for object in proto.messages | sort(attribute='name') %}
 {%- if object.name!='XOSBase' %}
-    @translate_exceptions
+    @translate_exceptions("{{ object.name }}", "List{{ object.name }}")
+    @track_request_time("{{ object.name }}", "List{{ object.name }}")
     def List{{ object.name }}(self, request, context):
       user=self.authenticate(context)
       model=self.get_model("{{ object.name }}")
-      return self.list(model, user)
+      res = self.list(model, user)
+      REQUEST_COUNT.labels('xos-core', "{{ object.name }}", "List{{ object.name }}", grpc.StatusCode.OK).inc()
+      return res
 
-    @translate_exceptions
+    @translate_exceptions("{{ object.name }}", "Filter{{ object.name }}")
+    @track_request_time("{{ object.name }}", "Filter{{ object.name }}")
     def Filter{{ object.name }}(self, request, context):
       user=self.authenticate(context)
       model=self.get_model("{{ object.name }}")
-      return self.filter(model, user, request)
+      res = self.filter(model, user, request)
+      REQUEST_COUNT.labels('xos-core', "{{ object.name }}", "List{{ object.name }}", grpc.StatusCode.OK).inc()
+      return res
 
-    @translate_exceptions
+    @translate_exceptions("{{ object.name }}", "Get{{ object.name }}")
+    @track_request_time("{{ object.name }}", "Get{{ object.name }}")
     def Get{{ object.name }}(self, request, context):
       user=self.authenticate(context)
       model=self.get_model("{{ object.name }}")
-      return self.get(model, user, request.id)
+      res = self.get(model, user, request.id)
+      REQUEST_COUNT.labels('xos-core', "{{ object.name }}", "List{{ object.name }}", grpc.StatusCode.OK).inc()
+      return res
 
-    @translate_exceptions
+    @translate_exceptions("{{ object.name }}", "Create{{ object.name }}")
+    @track_request_time("{{ object.name }}", "Create{{ object.name }}")
     def Create{{ object.name }}(self, request, context):
       user=self.authenticate(context)
       model=self.get_model("{{ object.name }}")
-      return self.create(model, user, request)
+      res = self.create(model, user, request)
+      REQUEST_COUNT.labels('xos-core', "{{ object.name }}", "List{{ object.name }}", grpc.StatusCode.OK).inc()
+      return res
 
-    @translate_exceptions
+    @translate_exceptions("{{ object.name }}", "Delete{{ object.name }}")
+    @track_request_time("{{ object.name }}", "Delete{{ object.name }}")
     def Delete{{ object.name }}(self, request, context):
       user=self.authenticate(context)
       model=self.get_model("{{ object.name }}")
-      return self.delete(model, user, request.id)
+      res = self.delete(model, user, request.id)
+      REQUEST_COUNT.labels('xos-core', "{{ object.name }}", "List{{ object.name }}", grpc.StatusCode.OK).inc()
+      return res
 
-    @translate_exceptions
+    @translate_exceptions("{{ object.name }}", "Update{{ object.name }}")
+    @track_request_time("{{ object.name }}", "Update{{ object.name }}")
     def Update{{ object.name }}(self, request, context):
       user=self.authenticate(context)
       model=self.get_model("{{ object.name }}")
-      return self.update(model, user, request.id, request, context)
+      res = self.update(model, user, request.id, request, context)
+      REQUEST_COUNT.labels('xos-core', "{{ object.name }}", "List{{ object.name }}", grpc.StatusCode.OK).inc()
+      return res
 {%- endif %}
 {% endfor %}
 
diff --git a/xos/coreapi/apihelper.py b/xos/coreapi/apihelper.py
index c394fc8..fb47e5d 100644
--- a/xos/coreapi/apihelper.py
+++ b/xos/coreapi/apihelper.py
@@ -23,9 +23,7 @@
 from protos import xos_pb2
 from google.protobuf.empty_pb2 import Empty
 import grpc
-import json
 
-from django.contrib.contenttypes.models import ContentType
 from django.contrib.auth import authenticate as django_authenticate
 from django.db.models import F, Q
 from core.models import *
@@ -37,7 +35,7 @@
 from xosconfig import Config
 from multistructlog import create_logger
 log = create_logger(Config().get('logging'))
-
+from apistats import REQUEST_COUNT
 
 class XOSDefaultSecurityContext(object):
     grant_access = True
@@ -61,39 +59,45 @@
 
 SessionStore = import_module(settings.SESSION_ENGINE).SessionStore
 
-
-def translate_exceptions(function):
+def translate_exceptions(model, method):
     """ this decorator translates XOS exceptions to grpc status codes """
-    def wrapper(*args, **kwargs):
-        try:
-            return function(*args, **kwargs)
-        except Exception as e:
 
-            import traceback
-            tb = traceback.format_exc()
-            print tb
-            # TODO can we propagate it over the APIs?
+    def decorator(function):
+        def wrapper(*args, **kwargs):
+            try:
+                return function(*args, **kwargs)
+            except Exception as e:
 
-            if "context" in kwargs:
-                context = kwargs["context"]
-            else:
-                context = args[2]
+                import traceback
+                tb = traceback.format_exc()
+                print tb
+                # TODO can we propagate it over the APIs?
 
-            if hasattr(e, 'json_detail'):
-                context.set_details(e.json_detail)
-            elif hasattr(e, 'detail'):
-                context.set_details(e.detail)
+                if "context" in kwargs:
+                    context = kwargs["context"]
+                else:
+                    context = args[2]
 
-            if (isinstance(e, XOSPermissionDenied)):
-                context.set_code(grpc.StatusCode.PERMISSION_DENIED)
-            elif (isinstance(e, XOSValidationError)):
-                context.set_code(grpc.StatusCode.INVALID_ARGUMENT)
-            elif (isinstance(e, XOSNotAuthenticated)):
-                context.set_code(grpc.StatusCode.UNAUTHENTICATED)
-            elif (isinstance(e, XOSNotFound)):
-                context.set_code(grpc.StatusCode.NOT_FOUND)
-            raise
-    return wrapper
+                if hasattr(e, 'json_detail'):
+                    context.set_details(e.json_detail)
+                elif hasattr(e, 'detail'):
+                    context.set_details(e.detail)
+
+                if (isinstance(e, XOSPermissionDenied)):
+                    REQUEST_COUNT.labels('xos-core', model, method, grpc.StatusCode.PERMISSION_DENIED).inc()
+                    context.set_code(grpc.StatusCode.PERMISSION_DENIED)
+                elif (isinstance(e, XOSValidationError)):
+                    REQUEST_COUNT.labels('xos-core', model, method, grpc.StatusCode.INVALID_ARGUMENT).inc()
+                    context.set_code(grpc.StatusCode.INVALID_ARGUMENT)
+                elif (isinstance(e, XOSNotAuthenticated)):
+                    REQUEST_COUNT.labels('xos-core', model, method, grpc.StatusCode.UNAUTHENTICATED).inc()
+                    context.set_code(grpc.StatusCode.UNAUTHENTICATED)
+                elif (isinstance(e, XOSNotFound)):
+                    REQUEST_COUNT.labels('xos-core', model, method, grpc.StatusCode.NOT_FOUND).inc()
+                    context.set_code(grpc.StatusCode.NOT_FOUND)
+                raise
+        return wrapper
+    return decorator
 
 
 bench_tStart = time.time()
@@ -477,7 +481,8 @@
 
             self.handle_m2m(new_obj, request, [])
 
-            return self.objToProto(new_obj)
+            response = self.objToProto(new_obj)
+            return response
         except:
             log.exception("Exception in apihelper.create")
             raise
@@ -521,7 +526,8 @@
             if not obj.deleted:
                 self.handle_m2m(obj, message, m2m_update_fields)
 
-            return self.objToProto(obj)
+            response = self.objToProto(obj)
+            return response
         except:
             log.exception("Exception in apihelper.update")
             raise
@@ -577,7 +583,8 @@
             # FIXME: Implement auditing here
             # logging.info("User requested x objects, y objects were filtered out by policy z")
 
-            return self.querysetToProto(djangoClass, filtered_queryset)
+            response = self.querysetToProto(djangoClass, filtered_queryset)
+            return response
         except:
             log.exception("Exception in apihelper.list")
             raise
@@ -615,7 +622,8 @@
             # FIXME: Implement auditing here
             # logging.info("User requested x objects, y objects were filtered out by policy z")
 
-            return self.querysetToProto(djangoClass, filtered_queryset)
+            response = self.querysetToProto(djangoClass, filtered_queryset)
+            return response
         except:
             log.exception("Exception in apihelper.filter")
             raise
diff --git a/xos/coreapi/apistats.py b/xos/coreapi/apistats.py
new file mode 100644
index 0000000..a209e78
--- /dev/null
+++ b/xos/coreapi/apistats.py
@@ -0,0 +1,48 @@
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed 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.
+
+from prometheus_client import Counter, Histogram
+import time
+
+REQUEST_COUNT = Counter(
+    'grpc_request_count', 'GRPC Request Count',
+    ['app_name', 'model_name', 'endpoint', 'status']
+)
+
+# TODO (teone) add caller as label for the counter (eg: GUI, TOSCA, SYNCHRONIZER)
+# TODO (teone) add user informations as label for the counter
+
+REQUEST_LATENCY = Histogram(
+    'grpc_request_latency_seconds', 'GRPC Request latency',
+    ['app_name', 'model_name', 'endpoint']
+)
+
+def track_request_time(model, method):
+    """
+    This decorator register the request time of a request
+    """
+
+    def decorator(function):
+
+        def wrapper(*args, **kwargs):
+
+            start_time = time.time()
+            res = function(*args, **kwargs)
+            resp_time = time.time() - start_time
+            REQUEST_LATENCY.labels('xos-core', model, method).observe(resp_time)
+            return res
+
+        return wrapper
+
+    return decorator
\ No newline at end of file
diff --git a/xos/coreapi/core_main.py b/xos/coreapi/core_main.py
index c4553c1..948ebd1 100644
--- a/xos/coreapi/core_main.py
+++ b/xos/coreapi/core_main.py
@@ -30,6 +30,8 @@
 from xoskafka import XOSKafkaProducer
 XOSKafkaProducer.init()
 
+import prometheus_client
+
 def parse_args():
     parser = argparse.ArgumentParser()
     parser.add_argument("--model_status", dest="model_status", type=int, default=0, help="status of model prep")
@@ -57,6 +59,10 @@
 if __name__ == '__main__':
     args = parse_args()
 
+    # start the prometheus server
+    # TODO (teone) consider moving this in a separate process so that it won't die when we load services
+    prometheus_client.start_http_server(8000)
+
     server = XOSGrpcServer(model_status = args.model_status,
                            model_output = args.model_output)
     server.start()
diff --git a/xos/coreapi/xos_dynamicload_api.py b/xos/coreapi/xos_dynamicload_api.py
index 30ef79b..1f9cd48 100644
--- a/xos/coreapi/xos_dynamicload_api.py
+++ b/xos/coreapi/xos_dynamicload_api.py
@@ -13,7 +13,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-
 import base64
 import fnmatch
 import os
@@ -28,8 +27,8 @@
 
 from xosutil.autodiscover_version import autodiscover_version_of_main
 from dynamicbuild import DynamicBuilder
-# NOTE/FIXME this file is loaded before Django, we can't import apihelper
-# from apihelper import XOSAPIHelperMixin, translate_exceptions
+from apistats import REQUEST_COUNT, track_request_time
+import grpc
 
 class DynamicLoadService(dynamicload_pb2_grpc.dynamicloadServicer):
     def __init__(self, thread_pool, server):
@@ -47,6 +46,7 @@
         """
         self.django_apps = django_apps
 
+    @track_request_time("DynamicLoad", "LoadModels")
     def LoadModels(self, request, context):
         try:
             builder = DynamicBuilder()
@@ -57,12 +57,14 @@
 
             response = dynamicload_pb2.LoadModelsReply()
             response.status = response.SUCCESS
-
+            REQUEST_COUNT.labels('xos-core', "DynamicLoad", "LoadModels", grpc.StatusCode.OK).inc()
             return response
         except Exception, e:
             import traceback; traceback.print_exc()
+            REQUEST_COUNT.labels('xos-core', "DynamicLoad", "LoadModels", grpc.StatusCode.INTERNAL).inc()
             raise e
 
+    @track_request_time("DynamicLoad", "UnloadModels")
     def UnloadModels(self, request, context):
         try:
             builder = DynamicBuilder()
@@ -73,12 +75,14 @@
 
             response = dynamicload_pb2.LoadModelsReply()
             response.status = response.SUCCESS
-
+            REQUEST_COUNT.labels('xos-core', "DynamicLoad", "UnloadModels", grpc.StatusCode.OK).inc()
             return response
         except Exception, e:
             import traceback; traceback.print_exc()
+            REQUEST_COUNT.labels('xos-core', "DynamicLoad", "UnloadModels", grpc.StatusCode.INTERNAL).inc()
             raise e
 
+    @track_request_time("DynamicLoad", "GetLoadStatus")
     def GetLoadStatus(self, request, context):
         django_apps_by_name = {}
         if self.django_apps:
@@ -112,12 +116,14 @@
                 item.state = "present"
             else:
                 item.state = "load"
-
+            REQUEST_COUNT.labels('xos-core', "DynamicLoad", "GetLoadStatus", grpc.StatusCode.OK).inc()
             return response
         except Exception, e:
             import traceback; traceback.print_exc()
+            REQUEST_COUNT.labels('xos-core', "DynamicLoad", "GetLoadStatus", grpc.StatusCode.INTERNAL).inc()
             raise e
 
+    @track_request_time("DynamicLoad", "GetConvenienceMethods")
     def GetConvenienceMethods(self, request, context):
         # self.authenticate(context, required=True)
         try:
@@ -131,10 +137,12 @@
                     item = response.convenience_methods.add()
                     item.filename = cm["filename"]
                     item.contents = open(cm["path"]).read()
+            REQUEST_COUNT.labels('xos-core', "DynamicLoad", "GetConvenienceMethods", grpc.StatusCode.OK).inc()
             return response
 
         except Exception, e:
             import traceback; traceback.print_exc()
+            REQUEST_COUNT.labels('xos-core', "DynamicLoad", "GetConvenienceMethods", grpc.StatusCode.INTERNAL).inc()
             raise e
 
 
diff --git a/xos/coreapi/xos_modeldefs_api.py b/xos/coreapi/xos_modeldefs_api.py
index fb79281..5a03b6d 100644
--- a/xos/coreapi/xos_modeldefs_api.py
+++ b/xos/coreapi/xos_modeldefs_api.py
@@ -13,15 +13,12 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-
-import base64
-import time
 import yaml
 from protos import modeldefs_pb2, modeldefs_pb2_grpc
-from google.protobuf.empty_pb2 import Empty
-
+import grpc
 from xos.exceptions import *
 from apihelper import XOSAPIHelperMixin
+from apistats import REQUEST_COUNT, track_request_time
 
 def yaml_to_grpc(yaml_repr, grpc_container, yaml_key = None, grpc_parent = None):
     if isinstance(yaml_repr, dict):
@@ -42,6 +39,7 @@
     def stop(self):
         pass
 
+    @track_request_time("Modeldefs", "ListModelDefs")
     def ListModelDefs(self, request, context):
         ystr = open('protos/modeldefs.yaml').read()
         yaml_repr = yaml.load(ystr)
@@ -50,6 +48,7 @@
 
         yaml_to_grpc(yaml_repr, modeldefs)
 
+        REQUEST_COUNT.labels('xos-core', "Modeldefs", "ListModelDefs", grpc.StatusCode.OK).inc()
         return modeldefs
 
 
diff --git a/xos/coreapi/xos_utility_api.py b/xos/coreapi/xos_utility_api.py
index cddf861..d48ad4e 100644
--- a/xos/coreapi/xos_utility_api.py
+++ b/xos/coreapi/xos_utility_api.py
@@ -32,6 +32,8 @@
 from core.models import *
 from xos.exceptions import *
 from apihelper import XOSAPIHelperMixin, translate_exceptions
+import grpc
+from apistats import REQUEST_COUNT, track_request_time
 
 # The Tosca engine expects to be run from /opt/xos/tosca/ or equivalent. It
 # needs some sys.path fixing up.
@@ -68,7 +70,8 @@
     def stop(self):
         pass
 
-    @translate_exceptions
+    @translate_exceptions("Utilities", "Login")
+    @track_request_time("Utilities", "Login")
     def Login(self, request, context):
         if not request.username:
             raise XOSNotAuthenticated("No username")
@@ -87,9 +90,11 @@
         response = utility_pb2.LoginResponse()
         response.sessionid = session.session_key
 
+        REQUEST_COUNT.labels('xos-core', "Utilities", "Login", grpc.StatusCode.OK).inc()
         return response
 
-    @translate_exceptions
+    @translate_exceptions("Utilities", "Logout")
+    @track_request_time("Utilities", "Logout")
     def Logout(self, request, context):
         for (k, v) in context.invocation_metadata():
             if (k.lower()=="x-xossession"):
@@ -97,9 +102,12 @@
                 if "_auth_user_id" in s:
                     del s["_auth_user_id"]
                     s.save()
+        REQUEST_COUNT.labels('xos-core', "Utilities", "Login", grpc.StatusCode.OK).inc()
         return Empty()
 
-    @translate_exceptions
+    # FIXME are we still using these?
+    @translate_exceptions("Utilities", "RunTosca")
+    @track_request_time("Utilities", "RunTosca")
     def RunTosca(self, request, context):
         user=self.authenticate(context, required=True)
 
@@ -123,7 +131,8 @@
 
         return response
 
-    @translate_exceptions
+    @translate_exceptions("Utilities", "DestryTosca")
+    @track_request_time("Utilities", "DestryTosca")
     def DestroyTosca(self, request, context):
         user=self.authenticate(context, required=True)
 
@@ -146,17 +155,23 @@
         response.messages = "\n".join(xt.log_msgs)
 
         return response
+    # end FIXME
 
-    @translate_exceptions
+    @translate_exceptions("Utilities", "NoOp")
+    @track_request_time("Utilities", "NoOp")
     def NoOp(self, request, context):
+        REQUEST_COUNT.labels('xos-core', "Utilities", "NoOp", grpc.StatusCode.OK).inc()
         return Empty()
 
-    @translate_exceptions
+    @translate_exceptions("Utilities", "AuthenticatedNoOp")
+    @track_request_time("Utilities", "AuthenticatedNoOp")
     def AuthenticatedNoOp(self, request, context):
         self.authenticate(context, required=True)
+        REQUEST_COUNT.labels('xos-core', "Utilities", "AuthenticatedNoOp", grpc.StatusCode.OK).inc()
         return Empty()
 
-    @translate_exceptions
+    @translate_exceptions("Utilities", "ListDirtyModels")
+    @track_request_time("Utilities", "ListDirtyModels")
     def ListDirtyModels(self, request, context):
         dirty_models = utility_pb2.ModelList()
 
@@ -175,9 +190,11 @@
                 item.class_name = model.__name__
                 item.id = obj.id
 
+        REQUEST_COUNT.labels('xos-core', "Utilities", "ListDirtyModels", grpc.StatusCode.OK).inc()
         return dirty_models
 
-    @translate_exceptions
+    @translate_exceptions("Utilities", "SetDirtyModels")
+    @track_request_time("Utilities", "SetDirtyModels")
     def SetDirtyModels(self, request, context):
         user=self.authenticate(context, required=True)
 
@@ -206,9 +223,11 @@
                     item.id = obj.id
                     item.info = str(e)
 
+        REQUEST_COUNT.labels('xos-core', "Utilities", "SetDirtyModels", grpc.StatusCode.OK).inc()
         return dirty_models
 
-    @translate_exceptions
+    @translate_exceptions("Utilities", "GetXproto")
+    @track_request_time("Utilities", "GetXproto")
     def GetXproto(self, request, context):
         res = utility_pb2.XProtos()
 
@@ -231,9 +250,11 @@
             xproto += content
 
         res.xproto = xproto
+        REQUEST_COUNT.labels('xos-core', "Utilities", "GetXproto", grpc.StatusCode.OK).inc()
         return res
 
-    @translate_exceptions
+    @translate_exceptions("Utilities", "GetPopulatedServiceInstances")
+    @track_request_time("Utilities", "GetPopulatedServiceInstances")
     def GetPopulatedServiceInstances(self, request, context):
         """
         Return a service instance with provider and subsciber service instance ids
@@ -268,4 +289,5 @@
             elif l.subscriber_network:
                 response.subscribed_network.append(l.subscriber_network.id)
 
+        REQUEST_COUNT.labels('xos-core', "Utilities", "GetPopulatedServiceInstances", grpc.StatusCode.OK).inc()
         return response